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"),
- }
- }
-}