grx 0.3.2

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

//! Displays a toggle button.
//!
//! A toggle button works like a button but keeps its pressed state until untoggled.
//!
//! # Example
//!
//! ```no_run
//! # struct Action;
//! # impl gstore::Actionable for Action {
//! # fn name(&self) -> &'static str { "" }
//! # fn list() -> Vec<Self> { vec![] }
//! # fn keybinding(&self) -> Option<gstore::Keybinding> { None }
//! # fn try_from_name(name: &str) -> Option<Self> where Self: Sized { None }
//! # }
//! use grx::prelude::*;
//!
//! let menu: Rc<Menu>;
//! grx! {
//!     container [
//!         // a simple button with text
//!         toggle_button (on_click=|s| { println!("{}", s.pressed()) }) [
//!             "Toggle Me"
//!         ]
//!     ]
//! };
//! ```

use std::rc::Rc;

use glib::prelude::Cast;
use grx_macros::{gtk_component, props};
use gtk::prelude::ToggleButtonExt;
use gtk::{glib, prelude::ButtonExt};

use crate::{handlers::Handler, new_gc, Interactable};

use super::gtk_props::apply;

#[props]
#[derive(Default)]
pub struct Props {
    /// Register a handler for when this component is clicked.
    pub on_click: Option<Handler<ToggleButton>>,
}

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_click", &self.on_click.is_some())
            .finish()
    }
}

/// Renders the toggle button component.
pub fn toggle_button(mut props: Props) -> Rc<ToggleButton> {
    let gtk_button = gtk::ToggleButton::builder().build();
    if !props.children.is_empty() {
        let inner = props.children[0].inner();
        let w: &gtk::Widget = inner.downcast_ref().unwrap();
        if let Some(lbl) = w.downcast_ref::<gtk::Label>() {
            gtk_button.set_label(lbl.label().as_str());
        }
        if let Some(img) = w.downcast_ref::<gtk::Image>() {
            if let Some(name) = img.icon_name() {
                gtk_button.set_icon_name(name.as_str());
            }
        }
    }
    let w = gtk_button.clone();
    let on_click = props.on_click.take();
    let button_component = new_gc!(ToggleButton { w, props });

    apply(button_component.clone());

    if let Some(on_click) = on_click {
        let btn = button_component.clone();
        gtk_button.connect_toggled(move |_| {
            on_click(btn.clone());
        });
    }

    button_component
}

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

impl ToggleButton {
    pub fn pressed(self: &Rc<Self>) -> bool {
        self.widget.is_active()
    }

    pub fn set_pressed(self: &Rc<Self>, pressed: bool) {
        self.widget.set_active(pressed);
    }

    pub fn toggle(self: &Rc<Self>) {
        self.widget.set_active(self.widget.is_active());
    }
}

impl Interactable for ToggleButton {
    fn on_click(self: &Rc<Self>, handler: impl Fn(&Rc<Self>) + 'static) {
        let s = self.clone();
        self.widget.connect_toggled(move |_| {
            handler(&s);
        });
    }

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

    #[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
    }
}