Create a swappable UI component #160

Merged
savanni merged 7 commits from fitnesstrax/swappable into main 2024-01-18 12:56:56 +00:00
4 changed files with 196 additions and 55 deletions
Showing only changes of commit 104760c754 - Show all commits

View File

@ -1,5 +1,5 @@
/* /*
Copyright 2023, Savanni D'Gerinel <savanni@luminescent-dreams.com> Copyright 2023-2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
This file is part of FitnessTrax. This file is part of FitnessTrax.
@ -22,6 +22,8 @@ use glib::Object;
use gtk::{prelude::*, subclass::prelude::*}; use gtk::{prelude::*, subclass::prelude::*};
use std::cell::RefCell; use std::cell::RefCell;
use super::weight::WeightEdit;
pub struct DaySummaryPrivate { pub struct DaySummaryPrivate {
date: gtk::Label, date: gtk::Label,
weight: RefCell<Option<gtk::Label>>, weight: RefCell<Option<gtk::Label>>,
@ -119,10 +121,17 @@ glib::wrapper! {
} }
impl CommandRow { impl CommandRow {
fn new() -> Self { fn new<OnEdit>(on_edit: OnEdit) -> Self
where
OnEdit: Fn() + 'static,
{
let s: Self = Object::builder().build(); let s: Self = Object::builder().build();
s.set_halign(gtk::Align::End); s.set_halign(gtk::Align::End);
s.append(&gtk::Button::builder().label("Edit").build());
let edit_button = gtk::Button::builder().label("Edit").build();
edit_button.connect_clicked(move |_| on_edit());
s.append(&edit_button);
s s
} }
} }
@ -159,21 +168,19 @@ glib::wrapper! {
} }
impl DayDetail { impl DayDetail {
pub fn new<PutRecordFn, UpdateRecordFn>( pub fn new<OnEdit>(
date: chrono::NaiveDate, date: chrono::NaiveDate,
records: Vec<Record<ft_core::TraxRecord>>, records: Vec<Record<ft_core::TraxRecord>>,
on_put_record: PutRecordFn, on_edit: OnEdit,
on_update_record: UpdateRecordFn,
) -> Self ) -> Self
where where
PutRecordFn: Fn(ft_core::TraxRecord) + 'static, OnEdit: Fn() + 'static,
UpdateRecordFn: Fn(Record<ft_core::TraxRecord>) + 'static,
{ {
let s: Self = Object::builder().build(); let s: Self = Object::builder().build();
s.set_orientation(gtk::Orientation::Vertical); s.set_orientation(gtk::Orientation::Vertical);
s.set_hexpand(true); s.set_hexpand(true);
s.append(&CommandRow::new()); s.append(&CommandRow::new(on_edit));
/* /*
let click_controller = gtk::GestureClick::new(); let click_controller = gtk::GestureClick::new();
@ -199,18 +206,8 @@ impl DayDetail {
}); });
let weight_view = match weight_record { let weight_view = match weight_record {
Some((id, data)) => Weight::new(Some(data.clone()), move |weight| { Some((id, data)) => Weight::new(Some(data.clone())),
on_update_record(Record { None => Weight::new(None),
id: id.clone(),
data: ft_core::TraxRecord::Weight(ft_core::Weight { date, weight }),
})
}),
None => Weight::new(None, move |weight| {
on_put_record(ft_core::TraxRecord::Weight(ft_core::Weight {
date,
weight,
}));
}),
}; };
s.append(&weight_view.widget()); s.append(&weight_view.widget());
@ -261,3 +258,77 @@ impl DayDetail {
s s
} }
} }
pub struct DayEditPrivate {
date: gtk::Label,
weight: WeightEdit,
}
impl Default for DayEditPrivate {
fn default() -> Self {
Self {
date: gtk::Label::new(None),
weight: WeightEdit::new(None),
}
}
}
#[glib::object_subclass]
impl ObjectSubclass for DayEditPrivate {
const NAME: &'static str = "DayEdit";
type Type = DayEdit;
type ParentType = gtk::Box;
}
impl ObjectImpl for DayEditPrivate {}
impl WidgetImpl for DayEditPrivate {}
impl BoxImpl for DayEditPrivate {}
glib::wrapper! {
pub struct DayEdit(ObjectSubclass<DayEditPrivate>) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable;
}
impl DayEdit {
pub fn new<PutRecordFn, UpdateRecordFn>(
date: chrono::NaiveDate,
records: Vec<Record<ft_core::TraxRecord>>,
on_put_record: PutRecordFn,
on_update_record: UpdateRecordFn,
) -> Self
where
PutRecordFn: Fn(ft_core::TraxRecord) + 'static,
UpdateRecordFn: Fn(Record<ft_core::TraxRecord>) + 'static,
{
let s: Self = Object::builder().build();
/*, move |weight| {
on_update_record(Record {
id: id.clone(),
data: ft_core::TraxRecord::Weight(ft_core::Weight { date, weight }),
})
}*/
/*, move |weight| {
on_put_record(ft_core::TraxRecord::Weight(ft_core::Weight {
date,
weight,
}));
}*/
let weight_record = records.iter().find_map(|record| match record {
Record {
id,
data: ft_core::TraxRecord::Weight(record),
} => Some((id.clone(), record.clone())),
_ => None,
});
let weight_view = match weight_record {
Some((_id, data)) => WeightEdit::new(Some(data.clone())),
None => WeightEdit::new(None),
};
s.append(&weight_view.widget());
s
}
}

View File

@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with Fit
*/ */
mod day; mod day;
pub use day::{DayDetail, DaySummary}; pub use day::{DayDetail, DayEdit, DaySummary};
mod edit_view; mod edit_view;
pub use edit_view::EditView; pub use edit_view::EditView;

View File

@ -226,13 +226,7 @@ pub struct Weight {
} }
impl Weight { impl Weight {
pub fn new<OnEditFinished>( pub fn new(weight: Option<ft_core::Weight>) -> Self {
weight: Option<ft_core::Weight>,
on_edit_finished: OnEditFinished,
) -> Self
where
OnEditFinished: Fn(si::Kilogram<f64>) + 'static,
{
let label = gtk::Label::builder() let label = gtk::Label::builder()
.css_classes(["card", "weight-view"]) .css_classes(["card", "weight-view"])
.can_focus(true) .can_focus(true)
@ -250,3 +244,28 @@ impl Weight {
self.label.clone().upcast() self.label.clone().upcast()
} }
} }
pub struct WeightEdit {
entry: TextEntry<si::Kilogram<f64>>,
}
impl WeightEdit {
pub fn new(weight: Option<ft_core::Weight>) -> Self {
Self {
entry: TextEntry::new(
"0 kg",
weight.map(|w| w.weight),
|val: &si::Kilogram<f64>| val.to_string(),
|v: &str| v.parse::<f64>().map(|w| w * si::KG).map_err(|_| ParseError),
),
}
}
pub fn value(&self) -> Option<si::Kilogram<f64>> {
self.entry.value()
}
pub fn widget(&self) -> gtk::Widget {
self.entry.widget()
}
}

View File

@ -16,16 +16,20 @@ You should have received a copy of the GNU General Public License along with Fit
use crate::{ use crate::{
app::App, app::App,
components::{DayDetail, Singleton, SingletonImpl}, components::{DayDetail, DayEdit, Singleton, SingletonImpl},
}; };
use emseries::Record; use emseries::Record;
use ft_core::TraxRecord; use ft_core::TraxRecord;
use glib::Object; use glib::Object;
use gtk::{prelude::*, subclass::prelude::*}; use gtk::{prelude::*, subclass::prelude::*};
use std::cell::RefCell;
#[derive(Default)] #[derive(Default)]
pub struct DayDetailViewPrivate { pub struct DayDetailViewPrivate {
app: RefCell<Option<App>>,
container: Singleton, container: Singleton,
date: RefCell<chrono::NaiveDate>,
records: RefCell<Vec<Record<TraxRecord>>>,
} }
#[glib::object_subclass] #[glib::object_subclass]
@ -48,35 +52,82 @@ impl DayDetailView {
pub fn new(date: chrono::NaiveDate, records: Vec<Record<TraxRecord>>, app: App) -> Self { pub fn new(date: chrono::NaiveDate, records: Vec<Record<TraxRecord>>, app: App) -> Self {
let s: Self = Object::builder().build(); let s: Self = Object::builder().build();
*s.imp().date.borrow_mut() = date;
*s.imp().records.borrow_mut() = records;
s.append(&s.imp().container); s.append(&s.imp().container);
s.imp().container.swap(&DayDetail::new( /*
date, s.imp()
records, .container
{ .swap(&DayDetail::new(date, records.clone(), {
let app = app.clone(); let s = s.clone();
move |record| { let records = records.clone();
let app = app.clone(); move || {
glib::spawn_future_local({ s.imp().container.swap(&DayEdit::new(
async move { date,
app.put_record(record).await; records,
} s.on_put_record(),
}); // s.on_update_record(),
|_| {},
))
} }
}, }));
{ */
let app = app.clone();
move |record| { s.view();
let app = app.clone();
glib::spawn_future_local({
async move {
app.update_record(record).await;
}
});
}
},
));
s s
} }
fn view(&self) {
self.imp().container.swap(&DayDetail::new(
self.imp().date.borrow().clone(),
self.imp().records.borrow().clone(),
{
let s = self.clone();
move || s.edit()
},
));
}
fn edit(&self) {
self.imp().container.swap(&DayEdit::new(
self.imp().date.borrow().clone(),
self.imp().records.borrow().clone(),
|_| {},
|_| {},
));
}
fn on_put_record(&self) -> Box<dyn Fn(ft_core::TraxRecord)> {
let app = self.imp().app.clone();
Box::new(move |record| {
let app = app.clone();
glib::spawn_future_local({
async move {
match &*app.borrow() {
Some(app) => {
let _ = app.put_record(record).await;
}
None => {}
}
}
});
})
}
/*
fn on_update_record(&self, record: TraxRecord) -> dyn Fn(ft_core::TraxRecord) {
let app = self.imp().app.clone();
move |record| {
let app = app.clone();
glib::spawn_future_local({
async move {
app.update_record(record).await;
}
});
}
}
*/
} }