diff --git a/fitnesstrax/app/src/components/day.rs b/fitnesstrax/app/src/components/day.rs index b121a4c..e2dc78c 100644 --- a/fitnesstrax/app/src/components/day.rs +++ b/fitnesstrax/app/src/components/day.rs @@ -17,7 +17,10 @@ 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::{steps_editor, time_distance_summary, weight_editor, ActionGroup, Steps, Weight}, + components::{ + steps_editor, text_entry::distance_field, time_distance_summary, weight_field, ActionGroup, + Steps, Weight, + }, view_models::DayDetailViewModel, }; use dimensioned::si; @@ -268,7 +271,7 @@ fn weight_and_steps_row(view_model: &DayDetailViewModel) -> gtk::Box { .orientation(gtk::Orientation::Horizontal) .build(); row.append( - &weight_editor(view_model.weight(), { + &weight_field(view_model.weight(), { let view_model = view_model.clone(); move |w| { view_model.set_weight(w); diff --git a/fitnesstrax/app/src/components/mod.rs b/fitnesstrax/app/src/components/mod.rs index 858f7d9..3a5c8c1 100644 --- a/fitnesstrax/app/src/components/mod.rs +++ b/fitnesstrax/app/src/components/mod.rs @@ -27,13 +27,15 @@ mod steps; pub use steps::{steps_editor, Steps}; mod text_entry; -pub use text_entry::{ParseError, TextEntry}; +pub use text_entry::{ + distance_field, duration_field, time_field, weight_field, ParseError, TextEntry, +}; mod time_distance; pub use time_distance::{time_distance_detail, time_distance_summary}; mod weight; -pub use weight::{weight_editor, Weight}; +pub use weight::Weight; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; diff --git a/fitnesstrax/app/src/components/text_entry.rs b/fitnesstrax/app/src/components/text_entry.rs index 12c0a6c..c3fc96b 100644 --- a/fitnesstrax/app/src/components/text_entry.rs +++ b/fitnesstrax/app/src/components/text_entry.rs @@ -14,6 +14,7 @@ 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 dimensioned::si; use gtk::prelude::*; use std::{cell::RefCell, rc::Rc}; @@ -27,7 +28,6 @@ type Parser = dyn Fn(&str) -> Result; pub struct TextEntry { value: Rc>>, widget: gtk::Entry, - #[allow(unused)] renderer: Rc>, parser: Rc>, } @@ -109,3 +109,67 @@ impl TextEntry { self.widget.clone().upcast::() } } + +pub fn weight_field( + weight: Option>, + on_update: OnUpdate, +) -> TextEntry> +where + OnUpdate: Fn(si::Kilogram) + 'static, +{ + TextEntry::new( + "0 kg", + weight, + |val: &si::Kilogram| val.to_string(), + move |v: &str| { + let new_weight = v.parse::().map(|w| w * si::KG).map_err(|_| ParseError); + match new_weight { + Ok(w) => { + on_update(w); + Ok(w) + } + Err(err) => Err(err), + } + }, + ) +} + +pub fn time_field(value: chrono::NaiveTime) -> TextEntry { + TextEntry::new( + "hh:mm", + Some(value), + |v| v.format("%H:%M").to_string(), + |s| chrono::NaiveTime::parse_from_str(s, "%H:%M").map_err(|_| ParseError), + ) +} + +pub fn distance_field(value: Option>) -> TextEntry> { + TextEntry::new( + "0 km", + value, + |v| format!("{} km", v.value_unsafe / 1000.), + |s| { + let digits = take_digits(s.to_owned()); + let value = digits.parse::().map_err(|_| ParseError)?; + println!("value: {}", value); + Ok(value * 1000. * si::M) + }, + ) +} + +pub fn duration_field(value: Option>) -> TextEntry> { + TextEntry::new( + "0 minutes", + value, + |v| format!("{} minutes", v.value_unsafe / 1000.), + |s| { + let digits = take_digits(s.to_owned()); + let value = digits.parse::().map_err(|_| ParseError)?; + Ok(value * 60. * si::S) + }, + ) +} + +fn take_digits(s: String) -> String { + s.chars().take_while(|t| t.is_digit(10)).collect::() +} diff --git a/fitnesstrax/app/src/components/time_distance.rs b/fitnesstrax/app/src/components/time_distance.rs index 201b284..f7fe9d7 100644 --- a/fitnesstrax/app/src/components/time_distance.rs +++ b/fitnesstrax/app/src/components/time_distance.rs @@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along with Fit // use crate::components::{EditView, ParseError, TextEntry}; // use chrono::{Local, NaiveDate}; // use dimensioned::si; +use crate::components::distance_field; use dimensioned::si; use ft_core::{RecordType, TimeDistance}; use glib::Object; @@ -102,5 +103,54 @@ pub fn time_distance_detail(type_: ft_core::RecordType, record: ft_core::TimeDis ) .build(), ); + layout } + +#[derive(Default)] +pub struct TimeDistanceEditPrivate { + type_: RefCell>, + record: RefCell>, +} + +#[glib::object_subclass] +impl ObjectSubclass for TimeDistanceEditPrivate { + const NAME: &'static str = "TimeDistanceEdit"; + type Type = TimeDistanceEdit; + type ParentType = gtk::Box; +} + +impl ObjectImpl for TimeDistanceEditPrivate {} +impl WidgetImpl for TimeDistanceEditPrivate {} +impl BoxImpl for TimeDistanceEditPrivate {} + +glib::wrapper! { + pub struct TimeDistanceEdit(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; +} + +impl Default for TimeDistanceEdit { + fn default() -> Self { + let s: Self = Object::builder().build(); + s.set_orientation(gtk::Orientation::Horizontal); + s.set_css_classes(&["time-distance-edit"]); + + s + } +} + +impl TimeDistanceEdit { + fn empty(on_update: OnUpdate) -> Self + where + OnUpdate: Fn(&ft_core::RecordType, &ft_core::TimeDistance), + { + let s = Self::default(); + + s + } + + /* + fn with_record(type_: ft_core::RecordType, record: ft_core::TimeDistance, on_update: OnUpdate) -> Self + where OnUpdate: Fn(&ft_core::RecordType, &ft_core::TimeDistance) { + } + */ +} diff --git a/fitnesstrax/app/src/components/weight.rs b/fitnesstrax/app/src/components/weight.rs index 059e903..3b167a2 100644 --- a/fitnesstrax/app/src/components/weight.rs +++ b/fitnesstrax/app/src/components/weight.rs @@ -14,7 +14,6 @@ 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 crate::components::{ParseError, TextEntry}; use dimensioned::si; use gtk::prelude::*; @@ -41,27 +40,3 @@ impl Weight { self.label.clone().upcast() } } - -pub fn weight_editor( - weight: Option>, - on_update: OnUpdate, -) -> TextEntry> -where - OnUpdate: Fn(si::Kilogram) + 'static, -{ - TextEntry::new( - "0 kg", - weight, - |val: &si::Kilogram| val.to_string(), - move |v: &str| { - let new_weight = v.parse::().map(|w| w * si::KG).map_err(|_| ParseError); - match new_weight { - Ok(w) => { - on_update(w); - Ok(w) - } - Err(err) => Err(err), - } - }, - ) -} diff --git a/fitnesstrax/app/src/views/historical_view.rs b/fitnesstrax/app/src/views/historical_view.rs index 2e80e40..b388769 100644 --- a/fitnesstrax/app/src/views/historical_view.rs +++ b/fitnesstrax/app/src/views/historical_view.rs @@ -56,6 +56,7 @@ impl ObjectSubclass for HistoricalViewPrivate { .single_click_activate(true) .build(), }; + factory.connect_bind({ let app = s.app.clone(); move |_, list_item| { @@ -122,9 +123,7 @@ impl HistoricalView { s.imp().list_view.connect_activate({ let on_select_day = on_select_day.clone(); move |s, idx| { - // This gets triggered whenever the user clicks on an item on the list. What we - // actually want to do here is to open a modal dialog that shows all of the details of - // the day and which allows the user to edit items within that dialog. + // This gets triggered whenever the user clicks on an item on the list. let item = s.model().unwrap().item(idx).unwrap(); let records = item.downcast_ref::().unwrap(); on_select_day(records.date(), records.records());