monorepo/fitnesstrax/app/src/components/date_field.rs

178 lines
5.1 KiB
Rust
Raw Normal View History

/*
Copyright 2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
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 <https://www.gnu.org/licenses/>.
*/
use crate::{components::{i32_field, TextEntry, month_field}, types::ParseError};
2024-02-15 14:11:34 +00:00
use chrono::{Datelike, Local};
use glib::Object;
use gtk::{prelude::*, subclass::prelude::*};
use std::{cell::RefCell, rc::Rc};
2024-02-15 14:11:34 +00:00
#[derive(Clone, Debug)]
enum Part {
Year,
Month,
Day,
}
pub struct DateFieldPrivate {
date: Rc<RefCell<chrono::NaiveDate>>,
2024-02-15 14:11:34 +00:00
year: TextEntry<i32>,
month: TextEntry<u32>,
day: TextEntry<u32>,
}
#[glib::object_subclass]
impl ObjectSubclass for DateFieldPrivate {
const NAME: &'static str = "DateField";
type Type = DateField;
type ParentType = gtk::Box;
fn new() -> Self {
let date = Rc::new(RefCell::new(Local::now().date_naive()));
let year = i32_field(date.borrow().year(),
{
let date = date.clone();
move |value| {
if let Some(year) = value {
let mut date = date.borrow_mut();
if let Some(new_date) = date.with_year(year) {
*date = new_date;
}
}
}
},
2024-02-15 14:11:34 +00:00
);
year.widget.set_max_length(4);
year.add_css_class("date-field__year");
let month = month_field(
date.borrow().month(),
{
let date = date.clone();
move |value| {
if let Some(month) = value {
let mut date = date.borrow_mut();
if let Some(new_date) = date.with_month(month) {
*date = new_date;
}
}
}
},
2024-02-15 14:11:34 +00:00
);
month.add_css_class("date-field__month");
/* Modify this so that it enforces the number of days per month */
2024-02-15 14:11:34 +00:00
let day = TextEntry::new(
"day",
Some(date.borrow().day()),
2024-02-15 14:11:34 +00:00
|v| format!("{}", v),
|v| v.parse::<u32>().map_err(|_| ParseError),
{
let date = date.clone();
move |value| {
if let Some(day) = value {
let mut date = date.borrow_mut();
if let Some(new_date) = date.with_day(day) {
*date = new_date;
}
}
}
},
2024-02-15 14:11:34 +00:00
);
day.add_css_class("date-field__day");
2024-02-15 14:11:34 +00:00
Self {
date,
2024-02-15 14:11:34 +00:00
year,
month,
day,
}
}
}
2024-02-15 14:11:34 +00:00
impl ObjectImpl for DateFieldPrivate {}
impl WidgetImpl for DateFieldPrivate {}
impl BoxImpl for DateFieldPrivate {}
glib::wrapper! {
pub struct DateField(ObjectSubclass<DateFieldPrivate>) @extends gtk::Box, gtk::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 {
2024-02-15 14:11:34 +00:00
let s: Self = Object::builder().build();
println!("{}", date);
2024-02-15 14:11:34 +00:00
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().date.borrow_mut() = date;
s
}
pub fn date(&self) -> chrono::NaiveDate {
self.imp().date.borrow().clone()
}
/*
pub fn is_valid(&self) -> bool {
false
}
*/
}
2024-02-15 14:11:34 +00:00
#[cfg(test)]
mod test {
use super::*;
use crate::gtk_init::gtk_init;
// Enabling this test pushes tests on the TextField into an infinite loop. That likely indicates a bad interaction within the TextField itself, and that is going to need to be fixed.
#[test]
#[ignore]
fn it_allows_valid_dates() {
let reference = chrono::NaiveDate::from_ymd_opt(2006, 01, 02).unwrap();
let field = DateField::new(reference);
field.imp().year.set_value(Some(2023));
field.imp().month.set_value(Some(10));
field.imp().day.set_value(Some(13));
assert!(field.is_valid());
}
#[test]
#[ignore]
fn it_disallows_out_of_range_months() {
unimplemented!()
}
#[test]
#[ignore]
fn it_allows_days_within_range_for_month() {
unimplemented!()
}
}