diff --git a/fitnesstrax/app/src/components/date_field.rs b/fitnesstrax/app/src/components/date_field.rs index 6cc5335..64bf7ab 100644 --- a/fitnesstrax/app/src/components/date_field.rs +++ b/fitnesstrax/app/src/components/date_field.rs @@ -14,62 +14,100 @@ 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::types::ParseError; -use chrono::Datelike; -use gtk::prelude::*; +use crate::{components::TextEntry, types::ParseError}; +use chrono::{Datelike, Local}; +use glib::Object; +use gtk::{prelude::*, subclass::prelude::*}; use std::{cell::RefCell, rc::Rc}; -#[derive(Clone)] -pub struct DateField { - value: Rc>, - buffer: gtk::TextBuffer, - widget: gtk::TextView, +#[derive(Clone, Debug)] +enum Part { + Year, + Month, + Day, } -impl DateField { - pub fn new(date: chrono::NaiveDate) -> Self { - let buffer = gtk::TextBuffer::new(None); - let tag = buffer.create_tag( - Some("placeholder"), - &[("foreground", &String::from("grey"))], +pub struct DateFieldPrivate { + value: Rc>, + year: TextEntry, + month: TextEntry, + day: TextEntry, +} + +#[glib::object_subclass] +impl ObjectSubclass for DateFieldPrivate { + const NAME: &'static str = "DateField"; + type Type = DateField; + type ParentType = gtk::Box; + + fn new() -> Self { + let date = Local::now().date_naive(); + let year = TextEntry::new( + "year", + Some(date.year()), + |v| format!("{}", v), + |v| v.parse::().map_err(|_| ParseError), + |_| {}, + ); + let month = TextEntry::new( + "month", + Some(date.month()), + |v| format!("{}", v), + |v| v.parse::().map_err(|_| ParseError), + |_| {}, + ); + let day = TextEntry::new( + "day", + Some(date.day()), + |v| format!("{}", v), + |v| v.parse::().map_err(|_| ParseError), + |_| {}, ); - buffer.set_text("regular text placeholder text"); - let (start, end) = buffer.bounds(); - let mut placeholder_start = start.clone(); - placeholder_start.forward_chars(13); + Self { + value: Rc::new(RefCell::new(date.clone())), + year, + month, + day, + } + } +} - let placeholder_markup = buffer.tag_table().lookup("placeholder").unwrap(); - buffer.apply_tag(&placeholder_markup, &placeholder_start, &end); +impl ObjectImpl for DateFieldPrivate {} +impl WidgetImpl for DateFieldPrivate {} +impl BoxImpl for DateFieldPrivate {} - let widget = gtk::TextView::builder() - .buffer(&buffer) - .editable(true) - .can_focus(true) - .height_request(50) - .width_request(200) - .build(); +glib::wrapper! { + pub struct DateField(ObjectSubclass) @extends gtk::Box, gtk::Widget; +} - let s = Self { - value: Rc::new(RefCell::new(date)), - buffer, - widget, - }; +/* Render a date in the format 2006 Jan 01. The entire date is editable. When the user moves to one part of the date, it will be erased and replaced with a grey placeholder. + */ +impl DateField { + pub fn new(date: chrono::NaiveDate) -> Self { + let s: Self = Object::builder().build(); - s.widget.buffer().connect_text_notify({ - let s = s.clone(); - move |buffer| s.on_update(buffer) - }); + println!("{}", date); + + s.append(&s.imp().year.widget()); + s.append(&s.imp().month.widget()); + s.append(&s.imp().day.widget()); + + s.imp().year.set_value(Some(date.year())); + s.imp().month.set_value(Some(date.month())); + s.imp().day.set_value(Some(date.day())); + + *s.imp().value.borrow_mut() = date; s } - - pub fn widget(&self) -> gtk::Widget { - self.widget.clone().upcast::() - } - - fn on_update(&self, buffer: >k::TextBuffer) { - let (start, end) = buffer.bounds(); - println!("[on_update] {}", buffer.text(&start, &end, true)); - } +} + +/* As soon as the field gets focus, highlight the first element + */ + +#[cfg(test)] +mod test { + use super::*; + use crate::gtk_init::gtk_init; } diff --git a/fitnesstrax/app/src/components/text_entry.rs b/fitnesstrax/app/src/components/text_entry.rs index a8f89cb..71a4ed4 100644 --- a/fitnesstrax/app/src/components/text_entry.rs +++ b/fitnesstrax/app/src/components/text_entry.rs @@ -28,6 +28,7 @@ pub struct TextEntry { value: Rc>>, widget: gtk::Entry, + renderer: Rc String>, parser: Rc>, on_update: Rc>, } @@ -64,6 +65,7 @@ impl TextEntry { let s = Self { value: Rc::new(RefCell::new(value)), widget, + renderer: Rc::new(renderer), parser: Rc::new(parser), on_update: Rc::new(on_update), }; @@ -76,6 +78,13 @@ impl TextEntry { s } + pub fn set_value(&self, val: Option) { + if let Some(ref v) = val { + self.widget.set_text(&(self.renderer)(v)); + } + *self.value.borrow_mut() = val; + } + fn handle_text_change(&self, buffer: >k::EntryBuffer) { if buffer.text().is_empty() { *self.value.borrow_mut() = None; diff --git a/fitnesstrax/app/src/views/historical_view.rs b/fitnesstrax/app/src/views/historical_view.rs index a7accc4..51ec3a5 100644 --- a/fitnesstrax/app/src/views/historical_view.rs +++ b/fitnesstrax/app/src/views/historical_view.rs @@ -112,9 +112,9 @@ impl HistoricalView { let date_row = gtk::Box::builder() .orientation(gtk::Orientation::Horizontal) .build(); - date_row.append(&DateField::new(interval.start).widget()); + date_row.append(&DateField::new(interval.start)); date_row.append(>k::Label::new(Some("to"))); - date_row.append(&DateField::new(interval.end).widget()); + date_row.append(&DateField::new(interval.end)); let quick_picker = gtk::Box::builder() .orientation(gtk::Orientation::Horizontal)