diff --git a/kifu/gtk/resources/style.css b/kifu/gtk/resources/style.css index e3c8988..713aadc 100644 --- a/kifu/gtk/resources/style.css +++ b/kifu/gtk/resources/style.css @@ -4,5 +4,14 @@ .settings-view { margin: 8px; + padding: 4px; background-color: @view_bg_color; } + +.settings-view { + padding: 8px; +} + +.preference-item > suffixes { + margin: 4px; +} diff --git a/kifu/gtk/src/app_window.rs b/kifu/gtk/src/app_window.rs index 05914e8..020d72c 100644 --- a/kifu/gtk/src/app_window.rs +++ b/kifu/gtk/src/app_window.rs @@ -23,12 +23,12 @@ use gtk::STYLE_PROVIDER_PRIORITY_USER; use kifu_core::{Config, Core}; use std::sync::{Arc, RwLock}; -use crate::view_models::SettingsViewModel; +use crate::{view_models::HomeViewModel, view_models::SettingsViewModel}; #[derive(Clone)] enum AppView { Settings(SettingsViewModel), - Home, + Home(HomeViewModel), } // An application window should generally contain @@ -87,11 +87,27 @@ impl AppWindow { } pub fn open_settings(&self) { - let view_model = SettingsViewModel::new(&self.window, self.core.clone()); + let view_model = SettingsViewModel::new(&self.window, self.core.clone(), { + let s = self.clone(); + move || { + s.close_overlay(); + } + }); 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; + } + None => {} + } + } + fn setup_window(app: &adw::Application) -> adw::ApplicationWindow { let window = adw::ApplicationWindow::builder() .application(app) @@ -136,7 +152,7 @@ impl AppWindow { .child(¬hing_page) .build(), ); - content.push(AppView::Home); + content.push(AppView::Home(HomeViewModel::new(core.clone()))); /* match *core.library() { diff --git a/kifu/gtk/src/view_models/home_view_model.rs b/kifu/gtk/src/view_models/home_view_model.rs index effe97a..5af979a 100644 --- a/kifu/gtk/src/view_models/home_view_model.rs +++ b/kifu/gtk/src/view_models/home_view_model.rs @@ -16,23 +16,25 @@ You should have received a copy of the GNU General Public License along with Kif use crate::LocalObserver; use kifu_core::{Core, CoreNotification}; +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, - notification_observer: LocalObserver, + notification_observer: Arc>, widget: gtk::Box, } impl HomeViewModel { - fn new(core: Core) -> Self { + pub fn new(core: Core) -> Self { let notification_observer = LocalObserver::new(&core, |msg| { - println!("DatabaseViewModelHandler called with message: {:?}", msg) + println!("HomeViewModel handler called with message: {:?}", msg) }); Self { core, - notification_observer, + notification_observer: Arc::new(notification_observer), widget: gtk::Box::new(gtk::Orientation::Horizontal, 0), } } diff --git a/kifu/gtk/src/view_models/settings_view_model.rs b/kifu/gtk/src/view_models/settings_view_model.rs index 2fe8d78..e505a11 100644 --- a/kifu/gtk/src/view_models/settings_view_model.rs +++ b/kifu/gtk/src/view_models/settings_view_model.rs @@ -18,7 +18,7 @@ use crate::{views, views::SettingsView, LocalObserver}; use async_std::task::spawn; use gtk::prelude::*; use kifu_core::{Config, Core, CoreNotification}; -use std::sync::Arc; +use std::{sync::Arc, rc::Rc}; /// SettingsViewModel /// @@ -35,24 +35,39 @@ pub struct SettingsViewModel { } impl SettingsViewModel { - pub fn new(parent: &impl IsA, core: Core) -> Self { + 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: SettingsView::new(parent, config, { - &|new_config| { - spawn({ - let core = core.clone(); - async move { core.set_config(new_config).await } - }); - } - }), + widget, } } } diff --git a/kifu/gtk/src/views/settings.rs b/kifu/gtk/src/views/settings.rs index f591d69..9b6338b 100644 --- a/kifu/gtk/src/views/settings.rs +++ b/kifu/gtk/src/views/settings.rs @@ -29,29 +29,41 @@ fn library_chooser_row( let dialog_button = gtk::Button::builder() .child(>k::Label::new(Some("Select Library"))) + .valign(gtk::Align::Center) .build(); let parent = parent.clone(); - dialog_button.connect_clicked(move |_| { - let no_parent: Option<>k::Window> = None; - let not_cancellable: Option<&gio::Cancellable> = None; - let on_library_chosen = on_library_chosen.clone(); - dialog.select_folder(no_parent, not_cancellable, move |result| match result { - Ok(path) => { - on_library_chosen(ConfigOption::LibraryPath(LibraryPath(path.path().unwrap()))) - } - Err(err) => println!("Error choosing a library: {:?}", err), - }); - }); let library_row = adw::ActionRow::builder() .title("Library Path") .subtitle("No library set") - // .child(&library_row) + .css_classes(["preference-item"]) .build(); + dialog_button.connect_clicked({ + let library_row = library_row.clone(); + move |_| { + let no_parent: Option<>k::Window> = None; + let not_cancellable: Option<&gio::Cancellable> = None; + let on_library_chosen = on_library_chosen.clone(); + dialog.select_folder(no_parent, not_cancellable, { + let library_row = library_row.clone(); + move |result| match result { + Ok(path) => { + let path_str: String = + path.path().unwrap().into_os_string().into_string().unwrap(); + library_row.set_subtitle(&path_str); + on_library_chosen(ConfigOption::LibraryPath(LibraryPath( + path.path().unwrap(), + ))) + } + Err(err) => println!("Error choosing a library: {:?}", err), + } + }); + } + }); + library_row.add_suffix(&dialog_button); - library_row.connect_activate(|_| println!("library row activated")); library_row } @@ -82,12 +94,13 @@ impl SettingsView { pub fn new( parent: &impl IsA, config: Config, - on_save: &impl FnOnce(Config), + on_save: impl Fn(Config) + 'static, + on_cancel: impl Fn() + 'static, ) -> Self { let s: Self = Object::builder().build(); let config = Rc::new(RefCell::new(config)); - let group = adw::PreferencesGroup::builder().vexpand(true).build(); + let group = adw::PreferencesGroup::builder().build(); let library_row = library_chooser_row( parent, @@ -95,26 +108,37 @@ impl SettingsView { let config = config.clone(); move |library_path| { config.borrow_mut().set(library_path); - println!("library path changed, need to update the UI"); } }), ); group.add(&library_row); let cancel_button = gtk::Button::builder().label("Cancel").build(); + cancel_button.connect_clicked(move |_| on_cancel()); let save_button = gtk::Button::builder().label("Save").build(); + save_button.connect_clicked({ + let config = config.clone(); + move |_| on_save(config.borrow().clone()) + }); + let action_row = gtk::Box::builder() .orientation(gtk::Orientation::Horizontal) .halign(gtk::Align::End) + .valign(gtk::Align::End) .build(); action_row.append(&cancel_button); action_row.append(&save_button); + let preferences_box = gtk::Box::builder() + .orientation(gtk::Orientation::Vertical) + .build(); + preferences_box.append(&group); + let layout = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) - .vexpand(true) + .spacing(8) .build(); - layout.append(&group); + layout.append(&preferences_box); layout.append(&action_row); s.set_child(Some(&layout));