From 4144325ede8bebcdce0440475906ef80737e02f2 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sun, 24 Dec 2023 12:26:34 -0500 Subject: [PATCH] Move the modal into components --- fitnesstrax/app/src/components/mod.rs | 3 + fitnesstrax/app/src/components/modal.rs | 92 ++++++++++ fitnesstrax/app/src/main.rs | 1 - fitnesstrax/app/src/ui/mod.rs | 2 - fitnesstrax/app/src/ui/modal.rs | 213 ------------------------ 5 files changed, 95 insertions(+), 216 deletions(-) create mode 100644 fitnesstrax/app/src/components/modal.rs delete mode 100644 fitnesstrax/app/src/ui/mod.rs delete mode 100644 fitnesstrax/app/src/ui/modal.rs diff --git a/fitnesstrax/app/src/components/mod.rs b/fitnesstrax/app/src/components/mod.rs index 7fcba76..a394229 100644 --- a/fitnesstrax/app/src/components/mod.rs +++ b/fitnesstrax/app/src/components/mod.rs @@ -14,8 +14,11 @@ General Public License for more details. You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see . */ +mod modal; + use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; +pub use modal::Modal; use std::{cell::RefCell, path::PathBuf, rc::Rc}; pub struct FileChooserRowPrivate { diff --git a/fitnesstrax/app/src/components/modal.rs b/fitnesstrax/app/src/components/modal.rs new file mode 100644 index 0000000..a3b83d0 --- /dev/null +++ b/fitnesstrax/app/src/components/modal.rs @@ -0,0 +1,92 @@ +//! The Modal is a reusable component with a title, arbitrary content, and up to three action +//! buttons. It does not itself enforce being a modal, but is meant to become a child of an Overlay +//! component. +use glib::Object; +use gtk::{prelude::*, subclass::prelude::*}; +use std::{ + cell::RefCell, + path::{Path, PathBuf}, +}; + +pub struct ModalPrivate { + title: gtk::Label, + content: RefCell, + primary_action: RefCell, + // secondary_action: RefCell>, + // tertiary_action: RefCell>, + footer: gtk::Box, +} + +#[glib::object_subclass] +impl ObjectSubclass for ModalPrivate { + const NAME: &'static str = "Modal"; + type Type = Modal; + type ParentType = gtk::Box; + + fn new() -> Self { + let title = gtk::Label::builder().label("Modal").build(); + let content = gtk::Box::new(gtk::Orientation::Vertical, 0); + let primary_action = gtk::Button::builder().label("Primary").build(); + + let footer = gtk::Box::builder() + .orientation(gtk::Orientation::Horizontal) + .hexpand(true) + .build(); + footer.append(&primary_action); + + Self { + title, + content: RefCell::new(content.upcast()), + primary_action: RefCell::new(primary_action), + // secondary_action: RefCell::new(None), + // tertiary_action: RefCell::new(None), + footer, + } + } +} + +impl ObjectImpl for ModalPrivate {} +impl WidgetImpl for ModalPrivate {} +impl BoxImpl for ModalPrivate {} + +glib::wrapper! { + pub struct Modal(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; +} + +impl Modal { + pub fn new() -> Self { + let s: Self = Object::builder().build(); + + s.set_margin_start(100); + s.set_margin_end(100); + s.set_margin_top(100); + s.set_margin_bottom(100); + s.set_orientation(gtk::Orientation::Vertical); + + s.append(&s.imp().title); + s.append(&*s.imp().content.borrow()); + s.append(&s.imp().footer); + + s + } + + pub fn set_title(&self, text: &str) { + self.imp().title.set_text(text); + } + + pub fn set_content(&self, content: gtk::Widget) { + self.remove(&*self.imp().content.borrow()); + self.insert_child_after(&content, Some(&self.imp().title)); + *self.imp().content.borrow_mut() = content; + } + + pub fn set_primary_action(&self, action: gtk::Button) { + self.imp() + .footer + .remove(&*self.imp().primary_action.borrow()); + *self.imp().primary_action.borrow_mut() = action; + self.imp() + .footer + .append(&*self.imp().primary_action.borrow()); + } +} diff --git a/fitnesstrax/app/src/main.rs b/fitnesstrax/app/src/main.rs index d2abdba..6bcd8e7 100644 --- a/fitnesstrax/app/src/main.rs +++ b/fitnesstrax/app/src/main.rs @@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License along with Fit mod app; mod app_window; mod components; -mod ui; mod views; use adw::prelude::*; diff --git a/fitnesstrax/app/src/ui/mod.rs b/fitnesstrax/app/src/ui/mod.rs deleted file mode 100644 index 3a0fe05..0000000 --- a/fitnesstrax/app/src/ui/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod modal; -pub use modal::{welcome_modal, Modal}; diff --git a/fitnesstrax/app/src/ui/modal.rs b/fitnesstrax/app/src/ui/modal.rs deleted file mode 100644 index 4f708c0..0000000 --- a/fitnesstrax/app/src/ui/modal.rs +++ /dev/null @@ -1,213 +0,0 @@ -//! The Modal is a reusable component with a title, arbitrary content, and up to three action -//! buttons. It does not itself enforce being a modal, but is meant to become a child of an Overlay -//! component. -use glib::Object; -use gtk::{prelude::*, subclass::prelude::*}; -use std::{ - cell::RefCell, - path::{Path, PathBuf}, - rc::Rc, -}; - -pub struct ModalPrivate { - title: gtk::Label, - content: RefCell, - primary_action: RefCell, - secondary_action: RefCell>, - tertiary_action: RefCell>, - - footer: gtk::Box, -} - -#[glib::object_subclass] -impl ObjectSubclass for ModalPrivate { - const NAME: &'static str = "Modal"; - type Type = Modal; - type ParentType = gtk::Box; - - fn new() -> Self { - let title = gtk::Label::builder().label("Modal").build(); - let content = gtk::Box::new(gtk::Orientation::Vertical, 0); - let primary_action = gtk::Button::builder().label("Primary").build(); - - let footer = gtk::Box::builder() - .orientation(gtk::Orientation::Horizontal) - .hexpand(true) - .build(); - footer.append(&primary_action); - - Self { - title, - content: RefCell::new(content.upcast()), - primary_action: RefCell::new(primary_action), - secondary_action: RefCell::new(None), - tertiary_action: RefCell::new(None), - - footer, - } - } -} - -impl ObjectImpl for ModalPrivate {} -impl WidgetImpl for ModalPrivate {} -impl BoxImpl for ModalPrivate {} - -glib::wrapper! { - pub struct Modal(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; -} - -impl Modal { - pub fn new() -> Self { - let s: Self = Object::builder().build(); - - s.set_margin_start(100); - s.set_margin_end(100); - s.set_margin_top(100); - s.set_margin_bottom(100); - s.set_orientation(gtk::Orientation::Vertical); - - s.append(&s.imp().title); - s.append(&*s.imp().content.borrow()); - s.append(&s.imp().footer); - - s - } - - pub fn set_title(&self, text: &str) { - self.imp().title.set_text(text); - } - - pub fn set_content(&self, content: gtk::Widget) { - self.remove(&*self.imp().content.borrow()); - self.insert_child_after(&content, Some(&self.imp().title)); - *self.imp().content.borrow_mut() = content; - } - - pub fn set_primary_action(&self, action: gtk::Button) { - self.imp() - .footer - .remove(&*self.imp().primary_action.borrow()); - *self.imp().primary_action.borrow_mut() = action; - self.imp() - .footer - .append(&*self.imp().primary_action.borrow()); - } -} - -/// The welcome modal is the first thing the user will see when FitnessTrax starts up if the -/// database has not been configured yet. -/// -/// This is a [Modal] component with all of the welcome content. -pub fn welcome_modal(database_selected: F) -> Modal -where - F: Fn(&Path) + 'static, -{ - let modal = Modal::new(); - modal.set_title("Welcome to FitnessTrax"); - - // The content should be a friendly dialog that explains to the user that they're going to set - // up the database. - let content = gtk::Box::builder() - .orientation(gtk::Orientation::Vertical) - .build(); - - content.append(>k::Label::new(Some("Welcome to FitnessTrax. The application has not yet been configured, so I will walk you through that. Let's start out by selecting your database."))); - - let db_row = DatabaseFileChooserRow::new(database_selected); - content.append(&db_row); - - modal.set_content(content.upcast()); - modal.set_primary_action(gtk::Button::builder().label("Save Settings").build()); - modal -} - -pub struct DatabaseFileChooserRowPrivate { - path: RefCell>, - label: gtk::Label, - on_selected_: RefCell>, -} - -#[glib::object_subclass] -impl ObjectSubclass for DatabaseFileChooserRowPrivate { - const NAME: &'static str = "DatabaseFileChooser"; - type Type = DatabaseFileChooserRow; - type ParentType = gtk::Box; - - fn new() -> Self { - Self { - path: RefCell::new(None), - label: gtk::Label::builder().hexpand(true).build(), - on_selected_: RefCell::new(Box::new(|_| {})), - } - } -} - -impl ObjectImpl for DatabaseFileChooserRowPrivate {} -impl WidgetImpl for DatabaseFileChooserRowPrivate {} -impl BoxImpl for DatabaseFileChooserRowPrivate {} - -glib::wrapper! { - pub struct DatabaseFileChooserRow(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; -} - -impl DatabaseFileChooserRow { - pub fn new(database_selected: F) -> Self - where - F: Fn(&Path) + 'static, - { - let s: Self = Object::builder().build(); - - s.set_orientation(gtk::Orientation::Horizontal); - - // The database selection row should be a box that shows a default database path, along with a - // button that triggers a file chooser dialog. Once the dialog returns, the box should be - // updated to reflect the chosen path. - s.imp().label.set_text("No database selected"); - - *s.imp().on_selected_.borrow_mut() = Box::new(database_selected); - - let db_file_chooser_button = gtk::Button::builder().label("Select Database").build(); - db_file_chooser_button.connect_clicked({ - let s = s.clone(); - move |_| { - let no_window: Option<>k::Window> = None; - let not_cancellable: Option<&gio::Cancellable> = None; - let s = s.clone(); - gtk::FileDialog::builder().build().open( - no_window, - not_cancellable, - move |file_id| s.on_selected(file_id), - ); - } - }); - - s.append(&s.imp().label); - s.append(&db_file_chooser_button); - - s - } - - fn on_selected(&self, m_file_id: Result) { - match m_file_id { - Ok(file_id) => { - println!("The user selected {:?}", file_id.path()); - *self.imp().path.borrow_mut() = file_id.path(); - match *self.imp().path.borrow() { - Some(ref path) => { - (*self.imp().on_selected_.borrow())(path); - self.redraw(); - } - None => {} - } - } - Err(err) => println!("file opening failed: {}", err), - } - } - - fn redraw(&self) { - match *self.imp().path.borrow() { - Some(ref path) => self.imp().label.set_text(path.to_str().unwrap()), - None => self.imp().label.set_text("No database selected"), - } - } -}