diff --git a/fitnesstrax/app/src/components/action_group.rs b/fitnesstrax/app/src/components/action_group.rs new file mode 100644 index 0000000..4197edd --- /dev/null +++ b/fitnesstrax/app/src/components/action_group.rs @@ -0,0 +1,135 @@ +/* +Copyright 2024, Savanni D'Gerinel + +This file is part of FitnessTrax. + +FitnessTrax 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. + +FitnessTrax 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 FitnessTrax. If not, see . +*/ + +use glib::Object; +use gtk::{prelude::*, subclass::prelude::*}; + +#[derive(Default)] +pub struct ActionGroupPrivate; + +#[glib::object_subclass] +impl ObjectSubclass for ActionGroupPrivate { + const NAME: &'static str = "ActionGroup"; + type Type = ActionGroup; + type ParentType = gtk::Box; +} + +impl ObjectImpl for ActionGroupPrivate {} +impl WidgetImpl for ActionGroupPrivate {} +impl BoxImpl for ActionGroupPrivate {} + +glib::wrapper! { + pub struct ActionGroup(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; +} + +impl ActionGroup { + fn new(builder: ActionGroupBuilder) -> Self { + let s: Self = Object::builder().build(); + s.set_orientation(builder.orientation); + + let primary_button = builder.primary_action.button(); + let secondary_button = builder.secondary_action.map(|action| action.button()); + let tertiary_button = builder.tertiary_action.map(|action| action.button()); + + if let Some(button) = tertiary_button { + s.append(&button); + } + + s.set_halign(gtk::Align::End); + if let Some(button) = secondary_button { + s.append(&button); + } + s.append(&primary_button); + + s + } + + pub fn builder() -> ActionGroupBuilder { + ActionGroupBuilder { + orientation: gtk::Orientation::Horizontal, + primary_action: Action { + label: "Ok".to_owned(), + action: Box::new(|| {}), + }, + secondary_action: None, + tertiary_action: None, + } + } +} + +struct Action { + label: String, + action: Box, +} + +impl Action { + fn button(self) -> gtk::Button { + let button = gtk::Button::builder().label(self.label).build(); + button.connect_clicked(move |_| (self.action)()); + button + } +} + +pub struct ActionGroupBuilder { + orientation: gtk::Orientation, + primary_action: Action, + secondary_action: Option, + tertiary_action: Option, +} + +impl ActionGroupBuilder { + pub fn orientation(mut self, orientation: gtk::Orientation) -> Self { + self.orientation = orientation; + self + } + + pub fn primary_action(mut self, label: &str, action: A) -> Self + where + A: Fn() + 'static, + { + self.primary_action = Action { + label: label.to_owned(), + action: Box::new(action), + }; + self + } + + pub fn secondary_action(mut self, label: &str, action: A) -> Self + where + A: Fn() + 'static, + { + self.secondary_action = Some(Action { + label: label.to_owned(), + action: Box::new(action), + }); + self + } + + pub fn tertiary_action(mut self, label: &str, action: A) -> Self + where + A: Fn() + 'static, + { + self.tertiary_action = Some(Action { + label: label.to_owned(), + action: Box::new(action), + }); + self + } + + pub fn build(self) -> ActionGroup { + ActionGroup::new(self) + } +} diff --git a/fitnesstrax/app/src/components/day.rs b/fitnesstrax/app/src/components/day.rs index 066b0f1..c72a270 100644 --- a/fitnesstrax/app/src/components/day.rs +++ b/fitnesstrax/app/src/components/day.rs @@ -16,11 +16,11 @@ You should have received a copy of the GNU General Public License along with Fit // use chrono::NaiveDate; // use ft_core::TraxRecord; -use crate::components::{TimeDistanceView, Weight}; +use crate::components::{ActionGroup, TimeDistanceView, Weight}; use emseries::Record; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use std::cell::RefCell; +use std::{cell::RefCell, rc::Rc}; use super::weight::WeightEdit; @@ -102,40 +102,6 @@ impl DaySummary { } } -#[derive(Default)] -struct CommandRowPrivate; - -#[glib::object_subclass] -impl ObjectSubclass for CommandRowPrivate { - const NAME: &'static str = "DayDetailCommandRow"; - type Type = CommandRow; - type ParentType = gtk::Box; -} - -impl ObjectImpl for CommandRowPrivate {} -impl WidgetImpl for CommandRowPrivate {} -impl BoxImpl for CommandRowPrivate {} - -glib::wrapper! { - struct CommandRow(ObjectSubclass) @extends gtk::Box, gtk::Widget; -} - -impl CommandRow { - fn new(on_edit: OnEdit) -> Self - where - OnEdit: Fn() + 'static, - { - let s: Self = Object::builder().build(); - s.set_halign(gtk::Align::End); - - let edit_button = gtk::Button::builder().label("Edit").build(); - edit_button.connect_clicked(move |_| on_edit()); - s.append(&edit_button); - - s - } -} - pub struct DayDetailPrivate { date: gtk::Label, weight: RefCell>, @@ -180,7 +146,11 @@ impl DayDetail { s.set_orientation(gtk::Orientation::Vertical); s.set_hexpand(true); - s.append(&CommandRow::new(on_edit)); + s.append( + &ActionGroup::builder() + .primary_action("Edit", Box::new(on_edit)) + .build(), + ); /* let click_controller = gtk::GestureClick::new(); @@ -261,14 +231,14 @@ impl DayDetail { pub struct DayEditPrivate { date: gtk::Label, - weight: WeightEdit, + weight: Rc, } impl Default for DayEditPrivate { fn default() -> Self { Self { date: gtk::Label::new(None), - weight: WeightEdit::new(None), + weight: Rc::new(WeightEdit::new(None)), } } } @@ -289,17 +259,21 @@ glib::wrapper! { } impl DayEdit { - pub fn new( + pub fn new( date: chrono::NaiveDate, records: Vec>, on_put_record: PutRecordFn, on_update_record: UpdateRecordFn, + on_cancel: CancelFn, ) -> Self where PutRecordFn: Fn(ft_core::TraxRecord) + 'static, UpdateRecordFn: Fn(Record) + 'static, + CancelFn: Fn() + 'static, { let s: Self = Object::builder().build(); + s.set_orientation(gtk::Orientation::Vertical); + s.set_hexpand(true); /*, move |weight| { on_update_record(Record { @@ -315,6 +289,18 @@ impl DayEdit { })); }*/ + s.append( + &ActionGroup::builder() + .primary_action("Save", { + let s = s.clone(); + move || { + println!("weight value: {:?}", s.imp().weight.value()); + } + }) + .secondary_action("Cancel", on_cancel) + .build(), + ); + let weight_record = records.iter().find_map(|record| match record { Record { id, diff --git a/fitnesstrax/app/src/components/mod.rs b/fitnesstrax/app/src/components/mod.rs index b5adeff..75ecdd2 100644 --- a/fitnesstrax/app/src/components/mod.rs +++ b/fitnesstrax/app/src/components/mod.rs @@ -14,6 +14,9 @@ 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 action_group; +pub use action_group::ActionGroup; + mod day; pub use day::{DayDetail, DayEdit, DaySummary}; diff --git a/fitnesstrax/app/src/components/weight.rs b/fitnesstrax/app/src/components/weight.rs index 69db27e..e2fb631 100644 --- a/fitnesstrax/app/src/components/weight.rs +++ b/fitnesstrax/app/src/components/weight.rs @@ -22,204 +22,7 @@ use gtk::{prelude::*, subclass::prelude::*}; use std::{borrow::Borrow, cell::RefCell}; #[derive(Default)] -pub struct WeightViewPrivate { - /* - date: RefCell, - record: RefCell>, - - widget: RefCell>>>, - - on_edit_finished: RefCell)>>, - */ -} - -/* -impl Default for WeightViewPrivate { - fn default() -> Self { - Self { - date: RefCell::new(Local::now().date_naive()), - record: RefCell::new(None), - widget: RefCell::new(EditView::Unconfigured), - container: Singleton::default(), - on_edit_finished: RefCell::new(Box::new(|_| {})), - } - } -} -*/ - -/* -#[glib::object_subclass] -impl ObjectSubclass for WeightViewPrivate { - const NAME: &'static str = "WeightView"; - type Type = WeightView; - type ParentType = gtk::Label; -} - -impl ObjectImpl for WeightViewPrivate {} -impl WidgetImpl for WeightViewPrivate {} -impl LabelImpl for WeightViewPrivate {} - -glib::wrapper! { - pub struct WeightView(ObjectSubclass) @extends gtk::Label, gtk::Widget; -} - -impl WeightView { - pub fn new( - date: NaiveDate, - weight: Option, - on_edit_finished: OnEditFinished, - ) -> Self - where - OnEditFinished: Fn(si::Kilogram) + 'static, - { - let s: Self = Object::builder().build(); - s.set_css_classes(&["card", "weight-view"]); - s.set_can_focus(true); - - s.append(&s.imp().container); - - match weight { - Some(weight) => { - s.remove_css_class("dim_label"); - s.set_label(&format!("{:?}", weight)); - } - None => { - s.add_css_class("dim_label"); - s.set_label("No weight recorded"); - } - } - /* - *s.imp().on_edit_finished.borrow_mut() = Box::new(on_edit_finished); - *s.imp().date.borrow_mut() = date; - - *s.imp().record.borrow_mut() = weight; - s.view(); - */ - - s - } - - /* - fn view(&self) { - let view = gtk::Label::builder() - .css_classes(["card", "weight-view"]) - .halign(gtk::Align::Start) - .can_focus(true) - .build(); - - let view_click_controller = gtk::GestureClick::new(); - view_click_controller.connect_released({ - let s = self.clone(); - move |_, _, _, _| { - s.edit(); - } - }); - - view.add_controller(view_click_controller); - - match *self.imp().record.borrow() { - Some(ref record) => { - view.remove_css_class("dim_label"); - view.set_label(&format!("{:?}", record.weight)); - } - None => { - view.add_css_class("dim_label"); - view.set_label("No weight recorded"); - } - } - - // self.swap(EditView::View(view)); - } - - fn edit(&self) { - let edit = TextEntry::>::new( - "weight", - None, - |val: &si::Kilogram| val.to_string(), - |v: &str| v.parse::().map(|w| w * si::KG).map_err(|_| ParseError), - ); - - match *self.imp().record.borrow() { - Some(ref record) => edit.set_value(Some(record.weight)), - None => edit.set_value(None), - } - - // self.swap(EditView::Edit(edit.clone())); - edit.grab_focus(); - } - */ - - /* - fn swap(&self, new_view: EditView>>) { - match new_view { - EditView::Unconfigured => {} - EditView::View(view) => self.imp().container.swap(&view.upcast()), - EditView::Edit(editor) => self.imp().container.swap(&editor.widget()), - } - /* - let mut widget = self.imp().widget.borrow_mut(); - match *widget { - EditView::Unconfigured => {} - EditView::View(ref view) => self.remove(view), - EditView::Edit(ref editor) => self.remove(&editor.widget()), - } - - match new_view { - EditView::Unconfigured => {} - EditView::View(ref view) => self.append(view), - EditView::Edit(ref editor) => self.append(&editor.widget()), - } - *widget = new_view; - */ - } - */ - - /* - pub fn blur(&self) { - match *self.imp().widget.borrow() { - EditView::Unconfigured => {} - EditView::View(_) => {} - EditView::Edit(ref editor) => { - let weight = editor.value(); - // This has really turned into rubbish - // on_edit_finished needs to accept a full record now. - // needs to be possible to delete a record if the value is None - // it's hard to be sure whether I need the full record object or if I need to update - // it. I probably don't. I think I need to borrow it and call on_edit_finished with an - // updated version of it. - // on_edit_finished still doesn't have a way to support a delete operation - let record = match (self.imp().record.borrow().clone(), weight) { - // update an existing record - (Some(record), Some(weight)) => Some(Weight { - date: record.date, - weight, - }), - - // create a new record - (None, Some(weight)) => Some(Weight { - date: self.imp().date.borrow().clone(), - weight, - }), - - // do nothing or delete an existing record - (_, None) => None, - }; - - match record { - Some(record) => { - self.imp().on_edit_finished.borrow()(record.weight); - *self.imp().record.borrow_mut() = Some(record); - } - None => {} - } - } - } - - self.view(); - } - */ -} -*/ +pub struct WeightViewPrivate {} pub struct Weight { label: gtk::Label, diff --git a/fitnesstrax/app/src/views/day_detail_view.rs b/fitnesstrax/app/src/views/day_detail_view.rs index 65bc114..cbd47b7 100644 --- a/fitnesstrax/app/src/views/day_detail_view.rs +++ b/fitnesstrax/app/src/views/day_detail_view.rs @@ -95,8 +95,12 @@ impl DayDetailView { self.imp().container.swap(&DayEdit::new( self.imp().date.borrow().clone(), self.imp().records.borrow().clone(), - |_| {}, - |_| {}, + self.on_put_record(), + self.on_update_record(), + { + let s = self.clone(); + move || s.view() + }, )); } @@ -117,17 +121,20 @@ impl DayDetailView { }) } - /* - fn on_update_record(&self, record: TraxRecord) -> dyn Fn(ft_core::TraxRecord) { + fn on_update_record(&self) -> Box)> { let app = self.imp().app.clone(); - move |record| { + Box::new(move |record| { let app = app.clone(); glib::spawn_future_local({ async move { - app.update_record(record).await; + match &*app.borrow() { + Some(app) => { + let _ = app.update_record(record).await; + } + None => {} + } } }); - } + }) } - */ }