diff --git a/config/src/lib.rs b/config/src/lib.rs index 054d96a..4a24421 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -35,7 +35,7 @@ macro_rules! define_config { $($name($struct)),+ } - #[derive(Clone, Debug)] + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct Config { values: std::collections::HashMap, } diff --git a/kifu/core/src/api.rs b/kifu/core/src/api.rs index 9c01419..5871d7e 100644 --- a/kifu/core/src/api.rs +++ b/kifu/core/src/api.rs @@ -1,6 +1,23 @@ +/* +Copyright 2024, Savanni D'Gerinel + +This file is part of Kifu. + +Kifu is free software: you can redistribute it and/or modify it under the terms of the GNU +General Public License as published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +Kifu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License along with Kifu. If not, see . +*/ + use crate::{ database::Database, types::{AppState, Config, ConfigOption, GameState, LibraryPath, Player, Rank}, + settings, }; use async_std::{ channel::{Receiver, Sender}, @@ -18,8 +35,10 @@ pub trait Observable { fn subscribe(&self) -> Receiver; } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum CoreRequest { + Settings(settings::SettingsRequest) + /* ChangeSetting(ChangeSettingRequest), CreateGame(CreateGameRequest), Home, @@ -27,6 +46,7 @@ pub enum CoreRequest { PlayingField, PlayStone(PlayStoneRequest), StartGame, + */ } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -66,15 +86,22 @@ impl From for Player { } } -/* #[derive(Clone, Debug, Serialize, Deserialize)] pub enum CoreResponse { + Settings(settings::SettingsResponse) +/* ConfigurationView(ConfigurationView), HomeView(HomeView), PlayingFieldView(PlayingFieldView), UpdatedConfigurationView(ConfigurationView), -} */ +} + +impl From for CoreResponse { + fn from(r: settings::SettingsResponse) -> Self { + Self::Settings(r) + } +} #[derive(Clone, Debug)] pub enum CoreNotification { @@ -92,6 +119,7 @@ pub struct Core { impl Core { pub fn new(config: Config) -> Self { + println!("config: {:?}", config); Self { config: Arc::new(RwLock::new(config)), // state, @@ -123,6 +151,12 @@ impl Core { self.library.read().unwrap() } + pub async fn dispatch(&self, request: CoreRequest) -> CoreResponse { + match request { + CoreRequest::Settings(request) => settings::handle(&self, request).await.into(), + } + } + /* pub async fn dispatch(&self, request: CoreRequest) -> CoreResponse { match request { diff --git a/kifu/core/src/lib.rs b/kifu/core/src/lib.rs index 8a7f40f..58c17c9 100644 --- a/kifu/core/src/lib.rs +++ b/kifu/core/src/lib.rs @@ -1,7 +1,7 @@ extern crate config_derive; mod api; -pub use api::{Core, CoreNotification, Observable}; +pub use api::{Core, CoreNotification, CoreRequest, CoreResponse, Observable}; mod board; pub use board::*; @@ -11,3 +11,4 @@ mod database; mod types; pub use types::{BoardError, Color, Config, ConfigOption, LibraryPath, Player, Rank, Size}; +pub mod settings; diff --git a/kifu/core/src/settings.rs b/kifu/core/src/settings.rs new file mode 100644 index 0000000..1bcbc95 --- /dev/null +++ b/kifu/core/src/settings.rs @@ -0,0 +1,37 @@ +/* +Copyright 2024, Savanni D'Gerinel + +This file is part of Kifu. + +Kifu is free software: you can redistribute it and/or modify it under the terms of the GNU +General Public License as published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +Kifu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License along with Kifu. If not, see . +*/ + +use crate::{types::LibraryPath, Core, Config}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum SettingsRequest { + Get, + Set(Config), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SettingsResponse(pub Config); + +pub async fn handle(model: &Core, request: SettingsRequest) -> SettingsResponse { + match request { + SettingsRequest::Get => SettingsResponse(model.get_config()), + SettingsRequest::Set(config) => { + model.set_config(config).await; + SettingsResponse(model.get_config()) + } + } +} diff --git a/kifu/gtk/src/app_window.rs b/kifu/gtk/src/app_window.rs index 020d72c..322d585 100644 --- a/kifu/gtk/src/app_window.rs +++ b/kifu/gtk/src/app_window.rs @@ -14,20 +14,19 @@ General Public License for more details. You should have received a copy of the GNU General Public License along with Kifu. If not, see . */ +use crate::CoreApi; use adw::prelude::*; -/* -use gio::resources_lookup_data; -use glib::IsA; -use gtk::STYLE_PROVIDER_PRIORITY_USER; -*/ -use kifu_core::{Config, Core}; +use async_std::task::{block_on, spawn}; +use kifu_core::settings::SettingsResponse; +use kifu_core::CoreResponse; +use kifu_core::{settings::SettingsRequest, Config, CoreRequest}; use std::sync::{Arc, RwLock}; -use crate::{view_models::HomeViewModel, view_models::SettingsViewModel}; +use crate::view_models::HomeViewModel; +use crate::views::SettingsView; #[derive(Clone)] enum AppView { - Settings(SettingsViewModel), Home(HomeViewModel), } @@ -52,15 +51,15 @@ pub struct AppWindow { // Overlays are for transient content, such as about and settings, which can be accessed from // anywhere but shouldn't be part of the main application flow. panel_overlay: gtk::Overlay, - core: Core, + core: CoreApi, // Not liking this, but I have to keep track of the settings view model separately from // anything else. I'll have to look into this later. - settings_view_model: Arc>>, + settings_view_model: Arc>>, } impl AppWindow { - pub fn new(app: &adw::Application, core: Core) -> Self { + pub fn new(app: &adw::Application, core: CoreApi) -> Self { let window = Self::setup_window(app); let header = Self::setup_header(); let panel_overlay = Self::setup_panel_overlay(); @@ -87,22 +86,52 @@ impl AppWindow { } pub fn open_settings(&self) { - let view_model = SettingsViewModel::new(&self.window, self.core.clone(), { + // This should return instantly and allow the UI to continue being functional. However, + // some tests indicate that this may not actually be working correctly, and that a + // long-running background thread may delay things. + glib::spawn_future_local({ let s = self.clone(); - move || { - s.close_overlay(); + async move { + let CoreResponse::Settings(SettingsResponse(settings)) = s + .core + .dispatch(CoreRequest::Settings(SettingsRequest::Get)) + .await; + let view_model = SettingsView::new( + &s.window, + settings, + { + let s = s.clone(); + move |config| { + glib::spawn_future_local({ + let s = s.clone(); + async move { + s.core.dispatch(CoreRequest::Settings(SettingsRequest::Set( + config, + ))).await + } + }); + s.close_overlay(); + } + }, + { + let s = s.clone(); + move || { + s.close_overlay(); + } + }, + ); + s.panel_overlay.add_overlay(&view_model); + *s.settings_view_model.write().unwrap() = Some(view_model); } }); - self.panel_overlay.add_overlay(&view_model.widget); - *self.settings_view_model.write().unwrap() = Some(view_model); } pub fn close_overlay(&self) { - let mut vm = self.settings_view_model.write().unwrap(); - match *vm { - Some(ref mut view_model) => { - self.panel_overlay.remove_overlay(&view_model.widget); - *vm = None; + let mut view = self.settings_view_model.write().unwrap(); + match *view { + Some(ref mut settings) => { + self.panel_overlay.remove_overlay(settings); + *view = None; } None => {} } @@ -140,7 +169,7 @@ impl AppWindow { gtk::Overlay::new() } - fn setup_content(core: Core) -> (adw::NavigationView, Vec) { + fn setup_content(core: CoreApi) -> (adw::NavigationView, Vec) { let stack = adw::NavigationView::new(); let mut content = Vec::new(); @@ -152,19 +181,7 @@ impl AppWindow { .child(¬hing_page) .build(), ); - content.push(AppView::Home(HomeViewModel::new(core.clone()))); - - /* - match *core.library() { - Some(_) => { - } - None => { - let settings_vm = SettingsViewModel::new(core.clone()); - let _ = stack.push(&adw::NavigationPage::new(&settings_vm.widget, "Settings")); - content.push(AppView::Settings(settings_vm)); - } - } - */ + content.push(AppView::Home(HomeViewModel::new(core))); (stack, content) } diff --git a/kifu/gtk/src/lib.rs b/kifu/gtk/src/lib.rs index ca2ab98..0034608 100644 --- a/kifu/gtk/src/lib.rs +++ b/kifu/gtk/src/lib.rs @@ -22,31 +22,20 @@ pub use app_window::AppWindow; mod view_models; mod views; -use async_std::task::yield_now; -use kifu_core::{Core, Observable}; +use async_std::task::{spawn, yield_now}; +use kifu_core::{Core, Observable, CoreRequest, CoreResponse}; use std::{rc::Rc, sync::Arc}; use tokio::runtime::Runtime; #[derive(Clone)] pub struct CoreApi { - pub rt: Arc, pub core: Core, } impl CoreApi { - /* - pub fn dispatch(&self, request: CoreRequest) { - /* - spawn({ - /* - let gtk_tx = self.gtk_tx.clone(); - let core = self.core.clone(); - async move { gtk_tx.send(core.dispatch(request).await) } - */ - }); - */ + pub async fn dispatch(&self, request: CoreRequest) -> CoreResponse { + self.core.dispatch(request).await } -*/ } pub fn perftrace(trace_name: &str, f: F) -> A diff --git a/kifu/gtk/src/main.rs b/kifu/gtk/src/main.rs index 0eaf729..b059371 100644 --- a/kifu/gtk/src/main.rs +++ b/kifu/gtk/src/main.rs @@ -90,7 +90,8 @@ fn setup_app_configuration_action(app: &adw::Application, app_window: AppWindow) println!("setup_app_configuration_action"); let action = ActionEntry::builder("show_settings") .activate(move |_app: &adw::Application, _, _| { - app_window.open_settings(); + let app_window = app_window.clone(); + app_window.open_settings() }) .build(); app.add_action_entries([action]); @@ -109,28 +110,7 @@ fn main() { let config = load_config(&app_id); - let runtime = Arc::new( - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap(), - ); - - /* - let config_path = std::env::var("CONFIG") - .map(std::path::PathBuf::from) - .or({ - std::env::var("HOME").map(|base| { - let mut config_path = std::path::PathBuf::from(base); - config_path.push(".config"); - config_path.push("kifu"); - config_path - }) - }) - .expect("no config path could be found"); - */ - - let core = Core::new(config); + let core = Core::new(config.clone()); spawn({ let notifier = core.subscribe(); @@ -138,70 +118,22 @@ fn main() { handler(notifier, app_id) }); - /* - let core_handle = runtime.spawn({ - let core = core.clone(); - async move { - core.run().await; - } - }); - */ - let app = adw::Application::builder() .application_id("com.luminescent-dreams.kifu-gtk") .resource_base_path("/com/luminescent-dreams/kifu-gtk") .build(); app.connect_activate({ - let runtime = runtime.clone(); move |app| { - let mut app_window = AppWindow::new(app, core.clone()); - - match *core.library() { - Some(_) => {} - None => app_window.open_settings(), - } + let core_api = CoreApi { core: core.clone() }; + let app_window = AppWindow::new(app, core_api); setup_app_configuration_action(app, app_window.clone()); - /* - let api = CoreApi { - rt: runtime.clone(), - core: core.clone(), - }; - */ - - /* - let action_config = gio::SimpleAction::new("show-config", None); - action_config.connect_activate({ - let api = api.clone(); - move |_, _| { - api.dispatch(CoreRequest::OpenConfiguration); - } - }); - app.add_action(&action_config); - */ - app_window.window.present(); - - /* - gtk_rx.attach(None, { - let api = api.clone(); - move |message| { - perftrace("handle_response", || { - handle_response(api.clone(), &app_window, message) - }); - glib::ControlFlow::Continue - } - }); - */ - - // api.dispatch(CoreRequest::Home); } }); println!("running the gtk loop"); app.run(); - - /* let _ = runtime.block_on(core_handle); */ } diff --git a/kifu/gtk/src/view_models/home_view_model.rs b/kifu/gtk/src/view_models/home_view_model.rs index 5af979a..0ce1e7e 100644 --- a/kifu/gtk/src/view_models/home_view_model.rs +++ b/kifu/gtk/src/view_models/home_view_model.rs @@ -16,18 +16,22 @@ You should have received a copy of the GNU General Public License along with Kif use crate::LocalObserver; use kifu_core::{Core, CoreNotification}; +use crate::CoreApi; use std::sync::Arc; /// Home controls the view that the user sees when starting the application if there are no games in progress. It provides a window into the database, showing a list of recently recorded games. It also provides the UI for starting a new game. This will render an empty database view if the user hasn't configured a database yet. #[derive(Clone)] pub struct HomeViewModel { - core: Core, + /* + core: CoreApi, notification_observer: Arc>, widget: gtk::Box, + */ } impl HomeViewModel { - pub fn new(core: Core) -> Self { + pub fn new(core: CoreApi) -> Self { + /* let notification_observer = LocalObserver::new(&core, |msg| { println!("HomeViewModel handler called with message: {:?}", msg) }); @@ -37,6 +41,8 @@ impl HomeViewModel { notification_observer: Arc::new(notification_observer), widget: gtk::Box::new(gtk::Orientation::Horizontal, 0), } + */ + Self {} } /// Create a new game with the given parameters. diff --git a/kifu/gtk/src/view_models/mod.rs b/kifu/gtk/src/view_models/mod.rs index c69dcf9..a819b9b 100644 --- a/kifu/gtk/src/view_models/mod.rs +++ b/kifu/gtk/src/view_models/mod.rs @@ -28,6 +28,3 @@ pub use game_review_view_model::GameReviewViewModel; mod home_view_model; pub use home_view_model::HomeViewModel; - -mod settings_view_model; -pub use settings_view_model::SettingsViewModel; diff --git a/kifu/gtk/src/view_models/settings_view_model.rs b/kifu/gtk/src/view_models/settings_view_model.rs deleted file mode 100644 index e505a11..0000000 --- a/kifu/gtk/src/view_models/settings_view_model.rs +++ /dev/null @@ -1,73 +0,0 @@ -/* -Copyright 2024, Savanni D'Gerinel - -This file is part of Kifu. - -Kifu is free software: you can redistribute it and/or modify it under the terms of the GNU -General Public License as published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -Kifu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -General Public License for more details. - -You should have received a copy of the GNU General Public License along with Kifu. If not, see . -*/ - -use crate::{views, views::SettingsView, LocalObserver}; -use async_std::task::spawn; -use gtk::prelude::*; -use kifu_core::{Config, Core, CoreNotification}; -use std::{sync::Arc, rc::Rc}; - -/// SettingsViewModel -/// -/// Listens for messages from the core, and serves as intermediary between the Settings UI and the -/// core. Because it needs to respond to events from the core, it owns the widget, which allows it -/// to tell the widget to update after certain events. -#[derive(Clone)] -pub struct SettingsViewModel { - core: Core, - // Technically, Settings doesn't care about any events from Core. We will keep this around for - // now as reference, until something which does care shows up. - notification_observer: Arc>, - pub widget: views::SettingsView, -} - -impl SettingsViewModel { - pub fn new(parent: &impl IsA, core: Core, on_close: impl Fn() + 'static) -> Self { - let on_close = Arc::new(on_close); - - let notification_observer = LocalObserver::new(&core, |msg| { - println!("SettingsViewModel called with message: {:?}", msg) - }); - - let config = core.get_config(); - - let widget = SettingsView::new( - parent, - config, - { - let core = core.clone(); - let on_close = on_close.clone(); - move |new_config| { - spawn({ - let core = core.clone(); - on_close(); - async move { - println!("running set_config in the background"); - core.set_config(new_config).await; - } - }); - } - }, - move || on_close(), - ); - - Self { - core: core.clone(), - notification_observer: Arc::new(notification_observer), - widget, - } - } -}