From a9fe4fe74c10bd555e971268ec101daf295e509b Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 26 Jan 2024 09:53:42 -0500 Subject: [PATCH] Build some convenienc functions for measurement entry fields --- fitnesstrax/app/src/components/day.rs | 5 +- fitnesstrax/app/src/components/mod.rs | 4 +- fitnesstrax/app/src/components/text_entry.rs | 66 ++++++++++++++++++- .../app/src/components/time_distance.rs | 50 ++++++++++++++ fitnesstrax/app/src/views/historical_view.rs | 5 +- 5 files changed, 124 insertions(+), 6 deletions(-) diff --git a/fitnesstrax/app/src/components/day.rs b/fitnesstrax/app/src/components/day.rs index b121a4c..8c2321a 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_editor, + ActionGroup, Steps, Weight, + }, view_models::DayDetailViewModel, }; use dimensioned::si; diff --git a/fitnesstrax/app/src/components/mod.rs b/fitnesstrax/app/src/components/mod.rs index 858f7d9..7485059 100644 --- a/fitnesstrax/app/src/components/mod.rs +++ b/fitnesstrax/app/src/components/mod.rs @@ -27,7 +27,9 @@ 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}; 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/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());