grx 0.3.2

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

//! A row element that can have title, subtitle, prefix and suffix children.
//! Should be placed in a `list`.

use gdk::{gio::ListStore, prelude::ListModelExt};
use glib::prelude::Cast;
use grx_macros::gtk_component;
use gtk::gdk;
use gtk::glib;
use gtk::{
    prelude::GObjectPropertyExpressionExt,
    prelude::{ListBoxRowExt, WidgetExt},
};
use libadwaita::prelude::{ActionRowExt, ComboRowExt, PreferencesRowExt};
use std::rc::Rc;

use crate::Component;
use crate::ComponentExt;
use crate::Interactable;
use crate::{handlers::Handler, item::Item, new_gc, props, ItemExt};

use super::{gtk_props::apply, utils::get_nth_child};

#[props]
#[derive(Default)]
pub struct Props {
    pub title: String,
    pub subtitle: String,
    pub selectable: Option<bool>,
    pub activatable: Option<bool>,

    pub expand_suffix: Option<bool>,

    pub on_change: Option<Handler<ComboRow>>,
}

impl std::fmt::Debug for Props {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Props")
            .field("title", &self.title)
            .field("subtitle", &self.subtitle)
            .field("selectable", &self.selectable)
            .field("activatable", &self.activatable)
            .field("expand_suffix", &self.expand_suffix)
            .field("on_click", &self.on_change.is_some())
            .finish()
    }
}

pub fn combo_row(mut props: Props) -> Rc<ComboRow> {
    let combo_row = libadwaita::ComboRow::builder()
        .title(&props.title)
        .subtitle(&props.subtitle)
        .build();

    let model = gtk::gdk::gio::ListStore::new::<glib::Object>();
    combo_row.set_model(Some(&model));

    combo_row.set_expression(Some(Item::this_expression("value")));

    if let Some(b) = props.activatable {
        combo_row.set_activatable(b)
    }
    if let Some(b) = props.selectable {
        combo_row.set_selectable(b)
    }

    if let Some(true) = props.expand_suffix {
        if let Some(header) = get_nth_child::<gtk::Box>(&combo_row, 0) {
            if let Some(suffixes) = get_nth_child::<gtk::Box>(&header, 3) {
                suffixes.set_hexpand(true);
            }
        }
    }

    let change = props.on_change.take();

    let comp = new_gc!(ComboRow { combo_row, props });

    apply(comp.clone());

    if let Some(handler) = change {
        let c = comp.clone();
        comp.widget.connect_selected_notify(move |_| {
            handler(c.clone());
        });
    }

    for c in comp.children().iter() {
        if c.annotations().contains_key("prefix") {
            comp.add_prefix(c.clone())
        } else if c.annotations().contains_key("suffix") {
            comp.add_suffix(c.clone())
        }
    }

    comp
}

#[gtk_component(libadwaita::ComboRow)]
#[derive(Debug)]
pub struct ComboRow {}

impl Interactable for ComboRow {
    fn on_change(self: &Rc<Self>, handler: impl Fn(&Rc<Self>) + 'static) {
        let sel = self.clone();
        self.widget.connect_selected_notify(move |_| handler(&sel));
    }
}

impl ComboRow {
    pub fn set_title(self: &Rc<Self>, title: &str) {
        self.widget.set_title(title);
    }
    pub fn on_click(self: &Rc<Self>, handler: Handler<Self>) {
        let s = self.clone();
        self.widget.connect_activated(move |_| {
            handler(s.clone());
        });
    }
    pub fn items(self: &Rc<Self>) -> Vec<impl ItemExt> {
        let mut items = Vec::new();
        let model = &self.widget.model().unwrap();
        let len = model.n_items();
        for i in 0..len {
            if let Some(item) = model.item(i).and_then(|o| o.downcast::<Item>().ok()) {
                items.push(item);
            }
        }
        items
    }

    pub fn set_disabled(self: &Rc<Self>, disabled: bool) {
        self.widget.set_sensitive(!disabled);
    }

    pub fn clear(self: &Rc<Self>) {
        let model = self.widget.model().unwrap();
        let list = model.downcast_ref::<ListStore>().unwrap();
        list.remove_all();
    }
    pub fn append(self: &Rc<Self>, item: Rc<impl ItemExt + 'static>) {
        let item: Rc<dyn std::any::Any> = item;
        let item: Rc<Item> = item.downcast().unwrap();
        let model = self.widget.model().unwrap();
        let list = model.downcast_ref::<ListStore>().unwrap();
        list.append(item.as_ref());
    }

    /// Get the selected item.
    pub fn selected(self: &Rc<Self>) -> Option<impl ItemExt> {
        self.widget
            .selected_item()
            .and_then(|i| i.downcast::<Item>().ok())
    }

    /// Select the item with the given id.
    pub fn select(self: &Rc<Self>, id: &str) {
        for (i, item) in self.items().iter().enumerate() {
            if item.id() == id {
                self.widget.set_selected(i as u32);
            }
        }
    }
    pub fn add_prefix(self: &Rc<Self>, component: Component) {
        let widget: Rc<gtk::Widget> = component.inner().downcast().unwrap();
        self.widget.add_prefix(widget.as_ref());
    }

    pub fn add_suffix(self: &Rc<Self>, component: Component) {
        let widget: Rc<gtk::Widget> = component.inner().downcast().unwrap();
        self.widget.add_suffix(widget.as_ref());
    }
}