Start setting up date range widgets

This commit is contained in:
Savanni D'Gerinel 2024-02-15 09:11:34 -05:00
parent e87b332705
commit 3d6e5470ed
3 changed files with 94 additions and 47 deletions

View File

@ -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 <https://www.gnu.org/licenses/>. You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see <https://www.gnu.org/licenses/>.
*/ */
use crate::types::ParseError; use crate::{components::TextEntry, types::ParseError};
use chrono::Datelike; use chrono::{Datelike, Local};
use gtk::prelude::*; use glib::Object;
use gtk::{prelude::*, subclass::prelude::*};
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct DateField { enum Part {
value: Rc<RefCell<chrono::NaiveDate>>, Year,
buffer: gtk::TextBuffer, Month,
widget: gtk::TextView, Day,
} }
impl DateField { pub struct DateFieldPrivate {
pub fn new(date: chrono::NaiveDate) -> Self { value: Rc<RefCell<chrono::NaiveDate>>,
let buffer = gtk::TextBuffer::new(None); year: TextEntry<i32>,
let tag = buffer.create_tag( month: TextEntry<u32>,
Some("placeholder"), day: TextEntry<u32>,
&[("foreground", &String::from("grey"))], }
#[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::<i32>().map_err(|_| ParseError),
|_| {},
);
let month = TextEntry::new(
"month",
Some(date.month()),
|v| format!("{}", v),
|v| v.parse::<u32>().map_err(|_| ParseError),
|_| {},
);
let day = TextEntry::new(
"day",
Some(date.day()),
|v| format!("{}", v),
|v| v.parse::<u32>().map_err(|_| ParseError),
|_| {},
); );
buffer.set_text("regular text placeholder text"); Self {
let (start, end) = buffer.bounds(); value: Rc::new(RefCell::new(date.clone())),
let mut placeholder_start = start.clone(); year,
placeholder_start.forward_chars(13); month,
day,
}
}
}
let placeholder_markup = buffer.tag_table().lookup("placeholder").unwrap(); impl ObjectImpl for DateFieldPrivate {}
buffer.apply_tag(&placeholder_markup, &placeholder_start, &end); impl WidgetImpl for DateFieldPrivate {}
impl BoxImpl for DateFieldPrivate {}
let widget = gtk::TextView::builder() glib::wrapper! {
.buffer(&buffer) pub struct DateField(ObjectSubclass<DateFieldPrivate>) @extends gtk::Box, gtk::Widget;
.editable(true) }
.can_focus(true)
.height_request(50)
.width_request(200)
.build();
let s = Self { /* 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.
value: Rc::new(RefCell::new(date)), */
buffer, impl DateField {
widget, pub fn new(date: chrono::NaiveDate) -> Self {
}; let s: Self = Object::builder().build();
s.widget.buffer().connect_text_notify({ println!("{}", date);
let s = s.clone();
move |buffer| s.on_update(buffer) 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 s
} }
}
pub fn widget(&self) -> gtk::Widget {
self.widget.clone().upcast::<gtk::Widget>() /* As soon as the field gets focus, highlight the first element
} */
fn on_update(&self, buffer: &gtk::TextBuffer) { #[cfg(test)]
let (start, end) = buffer.bounds(); mod test {
println!("[on_update] {}", buffer.text(&start, &end, true)); use super::*;
} use crate::gtk_init::gtk_init;
} }

View File

@ -28,6 +28,7 @@ pub struct TextEntry<T: Clone + std::fmt::Debug> {
value: Rc<RefCell<Option<T>>>, value: Rc<RefCell<Option<T>>>,
widget: gtk::Entry, widget: gtk::Entry,
renderer: Rc<dyn Fn(&T) -> String>,
parser: Rc<Parser<T>>, parser: Rc<Parser<T>>,
on_update: Rc<OnUpdate<T>>, on_update: Rc<OnUpdate<T>>,
} }
@ -64,6 +65,7 @@ impl<T: Clone + std::fmt::Debug + 'static> TextEntry<T> {
let s = Self { let s = Self {
value: Rc::new(RefCell::new(value)), value: Rc::new(RefCell::new(value)),
widget, widget,
renderer: Rc::new(renderer),
parser: Rc::new(parser), parser: Rc::new(parser),
on_update: Rc::new(on_update), on_update: Rc::new(on_update),
}; };
@ -76,6 +78,13 @@ impl<T: Clone + std::fmt::Debug + 'static> TextEntry<T> {
s s
} }
pub fn set_value(&self, val: Option<T>) {
if let Some(ref v) = val {
self.widget.set_text(&(self.renderer)(v));
}
*self.value.borrow_mut() = val;
}
fn handle_text_change(&self, buffer: &gtk::EntryBuffer) { fn handle_text_change(&self, buffer: &gtk::EntryBuffer) {
if buffer.text().is_empty() { if buffer.text().is_empty() {
*self.value.borrow_mut() = None; *self.value.borrow_mut() = None;

View File

@ -112,9 +112,9 @@ impl HistoricalView {
let date_row = gtk::Box::builder() let date_row = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal) .orientation(gtk::Orientation::Horizontal)
.build(); .build();
date_row.append(&DateField::new(interval.start).widget()); date_row.append(&DateField::new(interval.start));
date_row.append(&gtk::Label::new(Some("to"))); date_row.append(&gtk::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() let quick_picker = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal) .orientation(gtk::Orientation::Horizontal)