/* Copyright 2023, Savanni D'Gerinel 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 . */ use crate::components::{EditView, ParseError, Singleton, TextEntry}; use chrono::{Local, NaiveDate}; use dimensioned::si; use ft_core::Weight; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use std::cell::RefCell; pub struct WeightViewPrivate { date: RefCell, record: RefCell>, widget: RefCell>>>, container: Singleton, on_edit_finished: RefCell)>>, } impl Default for WeightViewPrivate { fn default() -> Self { Self { date: RefCell::new(Local::now().date_naive()), record: RefCell::new(None), widget: RefCell::new(EditView::Unconfigured), container: Singleton::new(), on_edit_finished: RefCell::new(Box::new(|_| {})), } } } #[glib::object_subclass] impl ObjectSubclass for WeightViewPrivate { const NAME: &'static str = "WeightView"; type Type = WeightView; type ParentType = gtk::Box; } impl ObjectImpl for WeightViewPrivate {} impl WidgetImpl for WeightViewPrivate {} impl BoxImpl for WeightViewPrivate {} glib::wrapper! { pub struct WeightView(ObjectSubclass) @extends gtk::Box, gtk::Widget; } impl WeightView { pub fn new( date: NaiveDate, weight: Option, on_edit_finished: OnEditFinished, ) -> Self where OnEditFinished: Fn(si::Kilogram) + 'static, { let s: Self = Object::builder().build(); s.append(&s.imp().container); *s.imp().on_edit_finished.borrow_mut() = Box::new(on_edit_finished); *s.imp().date.borrow_mut() = date; *s.imp().record.borrow_mut() = weight; s.view(); s } fn view(&self) { let view = gtk::Label::builder() .css_classes(["card", "weight-view"]) .halign(gtk::Align::Start) .can_focus(true) .build(); let view_click_controller = gtk::GestureClick::new(); view_click_controller.connect_released({ let s = self.clone(); move |_, _, _, _| { s.edit(); } }); view.add_controller(view_click_controller); match *self.imp().record.borrow() { Some(ref record) => { view.remove_css_class("dim_label"); view.set_label(&format!("{:?}", record.weight)); } None => { view.add_css_class("dim_label"); view.set_label("No weight recorded"); } } self.swap(EditView::View(view)); } fn edit(&self) { let edit = TextEntry::>::new( "weight", None, |val: &si::Kilogram| val.to_string(), |v: &str| v.parse::().map(|w| w * si::KG).map_err(|_| ParseError), ); match *self.imp().record.borrow() { Some(ref record) => edit.set_value(Some(record.weight)), None => edit.set_value(None), } self.swap(EditView::Edit(edit.clone())); edit.grab_focus(); } fn swap(&self, new_view: EditView>>) { match new_view { EditView::Unconfigured => {} EditView::View(view) => self.imp().container.swap(&view.upcast()), EditView::Edit(editor) => self.imp().container.swap(&editor.widget()), } /* let mut widget = self.imp().widget.borrow_mut(); match *widget { EditView::Unconfigured => {} EditView::View(ref view) => self.remove(view), EditView::Edit(ref editor) => self.remove(&editor.widget()), } match new_view { EditView::Unconfigured => {} EditView::View(ref view) => self.append(view), EditView::Edit(ref editor) => self.append(&editor.widget()), } *widget = new_view; */ } pub fn blur(&self) { match *self.imp().widget.borrow() { EditView::Unconfigured => {} EditView::View(_) => {} EditView::Edit(ref editor) => { let weight = editor.value(); // This has really turned into rubbish // on_edit_finished needs to accept a full record now. // needs to be possible to delete a record if the value is None // it's hard to be sure whether I need the full record object or if I need to update // it. I probably don't. I think I need to borrow it and call on_edit_finished with an // updated version of it. // on_edit_finished still doesn't have a way to support a delete operation let record = match (self.imp().record.borrow().clone(), weight) { // update an existing record (Some(record), Some(weight)) => Some(Weight { date: record.date, weight, }), // create a new record (None, Some(weight)) => Some(Weight { date: self.imp().date.borrow().clone(), weight, }), // do nothing or delete an existing record (_, None) => None, }; match record { Some(record) => { self.imp().on_edit_finished.borrow()(record.weight); *self.imp().record.borrow_mut() = Some(record); } None => {} } } } self.view(); } }