/* Copyright 2023, 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 gtk::prelude::*; use std::{cell::RefCell, rc::Rc}; #[derive(Clone, Debug)] pub struct ParseError; type Renderer<T> = dyn Fn(&T) -> String; type Parser<T> = dyn Fn(&str) -> Result<T, ParseError>; #[derive(Clone)] pub struct TextEntry<T: Clone + std::fmt::Debug> { value: Rc<RefCell<Option<T>>>, widget: gtk::Entry, #[allow(unused)] renderer: Rc<Renderer<T>>, parser: Rc<Parser<T>>, } impl<T: Clone + std::fmt::Debug> std::fmt::Debug for TextEntry<T> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { write!( f, "{{ value: {:?}, widget: {:?} }}", self.value, self.widget ) } } // I do not understand why the data should be 'static. impl<T: Clone + std::fmt::Debug + 'static> TextEntry<T> { pub fn new<R, V>(placeholder: &str, value: Option<T>, renderer: R, parser: V) -> Self where R: Fn(&T) -> String + 'static, V: Fn(&str) -> Result<T, ParseError> + 'static, { let widget = gtk::Entry::builder().placeholder_text(placeholder).build(); if let Some(ref v) = value { widget.set_text(&renderer(v)) } let s = Self { value: Rc::new(RefCell::new(value)), widget, renderer: Rc::new(renderer), parser: Rc::new(parser), }; s.widget.buffer().connect_text_notify({ let s = s.clone(); move |buffer| s.handle_text_change(buffer) }); s } fn handle_text_change(&self, buffer: >k::EntryBuffer) { if buffer.text().is_empty() { *self.value.borrow_mut() = None; self.widget.remove_css_class("error"); return; } match (self.parser)(buffer.text().as_str()) { Ok(v) => { *self.value.borrow_mut() = Some(v); self.widget.remove_css_class("error"); } // need to change the border to provide a visual indicator of an error Err(_) => { self.widget.add_css_class("error"); } } } #[allow(unused)] pub fn value(&self) -> Option<T> { let v = self.value.borrow().clone(); self.value.borrow().clone() } pub fn set_value(&self, value: Option<T>) { if let Some(ref v) = value { self.widget.set_text(&(self.renderer)(v)) } *self.value.borrow_mut() = value; } #[allow(unused)] pub fn grab_focus(&self) { self.widget.grab_focus(); } pub fn widget(&self) -> gtk::Widget { self.widget.clone().upcast::<gtk::Widget>() } }