grx 0.3.2

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

//! Displays a button.
//!
//! A button is a component that can be pressed to do something.
//!
//! # 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
//!         button (on_click=|| { println!("Test") }) [
//!             "Test"
//!         ],
//!         // a button that opens a menu
//!         menu (id=menu, items=vec![
//!             menu_item("A".to_string(), None::<Action>),
//!             menu_item("B".to_string(), None::<Action>),
//!             menu_item("C".to_string(), None::<Action>),
//!         ]),
//!         button (on_click=move || { menu.show() }) [
//!            icon("open-menu-symbolic")
//!         ]
//!     ]
//! };
//! ```

use std::rc::Rc;

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

use crate::{handlers::Handler, menu::Menu, 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<Button>>,
    /// Make this button open the provided drop down menu.
    pub menu: Option<Rc<Menu>>,

    pub url: Option<String>,
}

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())
            .field("menu", &self.menu)
            .finish()
    }
}

/// Renders the button component.
pub fn button(mut props: Props) -> Rc<Button> {
    let gtk_button: gtk::Button = if let Some(url) = &props.url {
        gtk::LinkButton::builder().uri(url).build().upcast()
    } else {
        gtk::Button::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!(Button { w, props });

    apply(button_component.clone());

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

    button_component
}

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

impl Button {
    pub fn set_url(self: &Rc<Self>, url: &str) -> bool {
        if let Some(btn) = self.widget.dynamic_cast_ref::<gtk::LinkButton>() {
            btn.set_uri(url);
            true
        } else {
            false
        }
    }
    pub fn click(self: &Rc<Self>) {
        self.widget.emit_clicked();
    }
}

impl Interactable for Button {
    fn on_click(self: &Rc<Self>, handler: impl Fn(&Rc<Self>) + 'static) {
        let s = self.clone();
        self.widget.connect_clicked(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
    }
}