109 lines
3.4 KiB
Rust
109 lines
3.4 KiB
Rust
/*
|
|
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};
|
|
|
|
pub struct ParseError;
|
|
|
|
#[derive(Clone)]
|
|
pub struct TextEntry<T: Clone + std::fmt::Debug> {
|
|
value: Rc<RefCell<Option<T>>>,
|
|
widget: gtk::Entry,
|
|
renderer: Rc<Box<dyn Fn(&T) -> String>>,
|
|
parser: Rc<Box<dyn Fn(&str) -> Result<T, ParseError>>>,
|
|
}
|
|
|
|
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();
|
|
match value {
|
|
Some(ref v) => widget.set_text(&renderer(&v)),
|
|
None => {}
|
|
}
|
|
|
|
let s = Self {
|
|
value: Rc::new(RefCell::new(value)),
|
|
widget,
|
|
renderer: Rc::new(Box::new(renderer)),
|
|
parser: Rc::new(Box::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) => {
|
|
println!("setting the value: {}", (self.renderer)(&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");
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn value(&self) -> Option<T> {
|
|
let v = self.value.borrow().clone();
|
|
println!("retrieving the value: {:?}", v.map(|v| (self.renderer)(&v)));
|
|
self.value.borrow().clone()
|
|
}
|
|
|
|
pub fn set_value(&self, value: Option<T>) {
|
|
match value {
|
|
Some(ref v) => self.widget.set_text(&(self.renderer)(&v)),
|
|
None => {}
|
|
}
|
|
*self.value.borrow_mut() = value;
|
|
}
|
|
|
|
pub fn grab_focus(&self) {
|
|
self.widget.grab_focus();
|
|
}
|
|
|
|
pub fn widget(&self) -> gtk::Widget {
|
|
self.widget.clone().upcast::<gtk::Widget>()
|
|
}
|
|
}
|