use crate::{
core::{i18n, UnitSystem},
model::Unitsize,
prelude::*,
widgets::UnitSpinButton,
};
use gtk::{gio::subclass::prelude::*, glib, prelude::*};
use std::str::FromStr;
use uom::si::{
f32::Length,
length::{foot, kilometer, meter, mile},
};
mod imp {
use crate::{
core::{Settings, UnitKind, UnitSystem},
model::Unitsize,
widgets::UnitSpinButton,
};
use adw::subclass::prelude::*;
use gtk::{
glib::{self, clone, subclass::Signal},
prelude::*,
CompositeTemplate,
};
use std::{cell::RefCell, str::FromStr};
use uom::si::{
f32::Length,
length::{foot, kilometer, meter, mile},
};
#[derive(Debug, Default)]
pub struct DistanceActionRowMut {
pub unitsize: Unitsize,
pub value: Length,
}
#[derive(Debug, CompositeTemplate, Default)]
#[template(resource = "/dev/Cogitri/Health/ui/distance_action_row.ui")]
pub struct DistanceActionRow {
pub inner: RefCell<DistanceActionRowMut>,
pub settings: Settings,
pub settings_handler_id: RefCell<Option<glib::SignalHandlerId>>,
#[template_child]
pub distance_adjustment: TemplateChild<gtk::Adjustment>,
#[template_child]
pub distance_spin_button: TemplateChild<UnitSpinButton>,
#[template_child]
pub big_unit_togglebutton: TemplateChild<gtk::ToggleButton>,
#[template_child]
pub small_unit_togglebutton: TemplateChild<gtk::ToggleButton>,
}
#[glib::object_subclass]
impl ObjectSubclass for DistanceActionRow {
const NAME: &'static str = "HealthDistanceActionRow";
type ParentType = adw::ActionRow;
type Type = super::DistanceActionRow;
fn class_init(klass: &mut Self::Class) {
UnitSpinButton::static_type();
Self::bind_template(klass);
Self::Type::bind_template_callbacks(klass);
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for DistanceActionRow {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.set_togglebutton_text();
self.settings_handler_id
.replace(Some(self.settings.connect_unit_system_changed(
clone!(@weak obj => move |_, _| obj.set_togglebutton_text()),
)));
}
fn signals() -> &'static [Signal] {
use once_cell::sync::Lazy;
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
vec![
Signal::builder("input").build(),
Signal::builder("changed").build(),
]
});
SIGNALS.as_ref()
}
fn dispose(&self) {
self.settings
.disconnect(self.settings_handler_id.borrow_mut().take().unwrap())
}
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecString::builder("unitsize")
.default_value(Some("small"))
.build(),
glib::ParamSpecFloat::builder("value-meter")
.minimum(0.0)
.build(),
]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"unitsize" => {
let adjustment = &self.distance_adjustment;
let unitsize = Unitsize::from_str(value.get::<&str>().unwrap()).unwrap();
self.inner.borrow_mut().unitsize = unitsize;
if unitsize == Unitsize::Small {
adjustment.set_step_increment(100.0);
adjustment.set_page_increment(1000.0);
self.distance_spin_button
.set_unit_kind(UnitKind::LikeMeters);
} else {
adjustment.set_step_increment(1.0);
adjustment.set_page_increment(5.0);
self.distance_spin_button
.set_unit_kind(UnitKind::LikeKilometers);
}
if unitsize == Unitsize::Big && !self.big_unit_togglebutton.is_active() {
self.big_unit_togglebutton.set_active(true);
} else if unitsize == Unitsize::Small
&& !self.small_unit_togglebutton.is_active()
{
self.small_unit_togglebutton.set_active(true);
}
}
"value-meter" => {
let unitsize = self.inner.borrow().unitsize;
let val = Length::new::<meter>(value.get().unwrap());
if self.settings.unit_system() == UnitSystem::Metric {
if unitsize == Unitsize::Small {
self.distance_spin_button
.set_value(val.get::<meter>().into())
} else if unitsize == Unitsize::Big {
self.distance_spin_button
.set_value(val.get::<kilometer>().into())
}
} else if unitsize == Unitsize::Small {
self.distance_spin_button
.set_value(val.get::<foot>().into())
} else if unitsize == Unitsize::Big {
self.distance_spin_button
.set_value(val.get::<mile>().into())
}
self.inner.borrow_mut().value = val;
}
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"unitsize" => self.inner.borrow().unitsize.to_value(),
"value-meter" => self.inner.borrow().value.get::<meter>().to_value(),
_ => unimplemented!(),
}
}
}
impl WidgetImpl for DistanceActionRow {}
impl ListBoxRowImpl for DistanceActionRow {}
impl PreferencesRowImpl for DistanceActionRow {}
impl ActionRowImpl for DistanceActionRow {}
}
glib::wrapper! {
pub struct DistanceActionRow(ObjectSubclass<imp::DistanceActionRow>)
@extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, adw::ActionRow,
@implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget;
}
#[gtk::template_callbacks]
impl DistanceActionRow {
pub fn connect_changed<F: Fn(&Self) + 'static>(&self, callback: F) -> glib::SignalHandlerId {
self.connect_local("changed", false, move |values| {
callback(&values[0].get().unwrap());
None
})
}
pub fn connect_input<F: Fn(&Self) + 'static>(&self, callback: F) -> glib::SignalHandlerId {
self.connect_local("input", false, move |values| {
callback(&values[0].get().unwrap());
None
})
}
pub fn new() -> Self {
glib::Object::new()
}
pub fn set_unitsize(&self, unitsize: Unitsize) {
self.set_property("unitsize", &unitsize)
}
pub fn set_value(&self, value: Length) {
self.set_property("value-meter", value.get::<meter>())
}
pub fn unitsize(&self) -> Unitsize {
Unitsize::from_str(&self.property::<String>("unitsize")).unwrap()
}
pub fn value(&self) -> Length {
Length::new::<meter>(self.property("value-meter"))
}
#[template_callback]
fn handle_distance_spin_button_changed(&self, spinbutton: UnitSpinButton) {
let imp = self.imp();
let value = spinbutton.raw_value::<f32>().unwrap_or_default();
let unitsize = imp.inner.borrow().unitsize;
if imp.settings.unit_system() == UnitSystem::Metric {
if unitsize == Unitsize::Small {
imp.inner.borrow_mut().value = Length::new::<meter>(value);
} else {
imp.inner.borrow_mut().value = Length::new::<kilometer>(value);
}
} else if unitsize == Unitsize::Small {
imp.inner.borrow_mut().value = Length::new::<foot>(value);
} else {
imp.inner.borrow_mut().value = Length::new::<mile>(value);
}
self.emit_by_name::<()>("changed", &[]);
}
#[template_callback]
fn handle_distance_spin_button_input(&self) {
self.emit_by_name::<()>("input", &[]);
}
fn set_togglebutton_text(&self) {
let imp = self.imp();
if imp.settings.unit_system() == UnitSystem::Metric {
imp.big_unit_togglebutton.set_label(&i18n("KM"));
imp.small_unit_togglebutton.set_label(&i18n("Meters"));
} else {
imp.big_unit_togglebutton.set_label(&i18n("Miles"));
imp.small_unit_togglebutton.set_label(&i18n("Feet"));
}
}
}
#[cfg(test)]
mod test {
use super::DistanceActionRow;
use crate::{model::Unitsize, utils::init_gtk};
use gtk::subclass::prelude::*;
use uom::si::{f32::Length, length::meter};
#[gtk::test]
fn new() {
init_gtk();
DistanceActionRow::new();
}
#[gtk::test]
pub fn set_unitsize() {
init_gtk();
let row = DistanceActionRow::new();
let row_ = row.imp();
row.set_value(Length::new::<meter>(1500.0));
assert_eq!(row_.distance_spin_button.value(), 1500.0);
row.set_unitsize(Unitsize::Big);
assert_eq!(row_.distance_spin_button.value(), 1.5);
}
}