grx 0.2.0

Abstraction layer for UI development
// SPDX-License-Identifier: GPL-3.0-or-later

//! The switch component

use super::gtk_props::apply;
use crate::{handlers::Handler, new_gc, Interactable};
use glib::Cast;
use grx_macros::{gtk_component, props};
use gtk::glib;
use gtk::{
    traits::{AdjustmentExt, EditableExt, WidgetExt},
    Adjustment,
};
use std::rc::Rc;

#[props]
#[derive(Default)]
pub struct Props {
    pub on_change: Option<Handler<SpinButton>>,
    pub lower: f64,
    pub upper: f64,
    pub value: f64,
    pub step: f64,
    pub page: f64,
    pub value_mapper: Option<Mapper>,
}

impl std::fmt::Debug for Props {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Props")
            .field("id", &self.id)
            .field("classes", &self.classes)
            .field("styles", &self.styles)
            .field("children", &self.children)
            .field("on_change", &self.on_change.is_some())
            .field("lower", &self.lower)
            .field("upper", &self.upper)
            .field("value", &self.value)
            .field("step", &self.step)
            .field("page", &self.page)
            .field("value_mapper", &self.value_mapper.is_some())
            .finish()
    }
}

type Mapper = (
    Rc<dyn Fn(f64) -> String + 'static>,
    Rc<dyn Fn(String) -> Option<f64> + 'static>,
);

pub fn spin_button(mut props: Props) -> Rc<SpinButton> {
    let switch = gtk::SpinButton::builder().build();
    let sw = switch.clone();

    let value_mapper = props.value_mapper.take();
    let on_change = props.on_change.take();

    let lower = props.lower;
    let upper = props.upper;
    let step = props.step;
    let page = props.page;

    let switch = new_gc!(SpinButton { switch, props });

    let adj = Adjustment::builder()
        .lower(lower)
        .upper(upper)
        .step_increment(step)
        .page_increment(page)
        .build();

    sw.set_adjustment(&adj);

    if let Some((to, from)) = value_mapper {
        sw.connect_output(move |sb| {
            let value = sb.value();
            sb.set_text(&to(value));
            gtk::Inhibit(true)
        });
        sw.connect_input(move |sb| {
            let text = sb.text().to_string();
            from(text).map(Ok)
        });
    }

    sw.set_value(switch.props.value);

    apply(switch.clone());

    if let Some(onchange) = on_change {
        let btn = switch.clone();
        let onc = onchange.clone();
        adj.connect_value_changed(move |_| {
            onc(btn.clone());
        });
    }

    switch
}

#[gtk_component(gtk::SpinButton)]
#[derive(Debug)]
pub struct SpinButton {}

impl SpinButton {
    pub fn has_focus(self: &Rc<Self>) -> bool {
        let mut focus = self.widget.has_focus();
        let mut c = self.widget.first_child();
        while let Some(child) = c {
            focus |= child.has_focus();
            c = child.next_sibling();
        }
        focus
    }
    pub fn set_value(self: &Rc<Self>, value: f64) {
        self.widget.set_value(value);
    }
    pub fn value(self: &Rc<Self>) -> f64 {
        self.widget.adjustment().value()
    }
    pub fn upper(self: &Rc<Self>) -> f64 {
        self.widget.adjustment().upper()
    }
    pub fn lower(self: &Rc<Self>) -> f64 {
        self.widget.adjustment().lower()
    }
    pub fn step(self: &Rc<Self>) -> f64 {
        self.widget.adjustment().step_increment()
    }
    pub fn page(self: &Rc<Self>) -> f64 {
        self.widget.adjustment().page_increment()
    }
}

impl Interactable for SpinButton {
    #[allow(unused_variables)]
    fn on_click(self: &Rc<Self>, handler: impl Fn(&Rc<Self>) + 'static) {
        // ignore
    }

    #[allow(unused_variables)]
    fn on_change(self: &Rc<Self>, handler: impl Fn(&Rc<Self>) + 'static) {
        let s = self.clone();
        self.widget.adjustment().connect_value_changed(move |_| {
            handler(&s);
        });
    }

    #[allow(unused_variables)]
    fn on_swipe(self: &Rc<Self>, handler: impl Fn(&Rc<Self>, f64, f64) + 'static) {
        // optional
    }

    #[allow(unused_variables)]
    fn on_blur(self: &Rc<Self>, handler: impl Fn(&Rc<Self>) + 'static) {
        // optional
    }
}