grx 0.1.0

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

//! An debug window for the gstore global state and action history.

use gdk::{gio::SimpleAction, prelude::ActionMapExt};
use grx_macros::component;
use gtk::gdk;
use gtk::traits::GtkApplicationExt;
use std::{collections::LinkedList, rc::Rc};

use crate::{
    action_row, clamp, container, grx, header_bar,
    list::{self, List},
    props, scroll, text, window, ContainerExt, Text, Textual,
};

type PushFn = Box<dyn Fn(&dyn std::fmt::Debug, &dyn std::fmt::Debug) + 'static>;

thread_local! {
    static PUSH: std::cell::RefCell<Option<PushFn>> = Default::default();
}

pub fn gstore_debug_window(app: &libadwaita::Application) -> Rc<GstoreDebug> {
    let d = gstore_debug(Default::default());
    let action = SimpleAction::new("gstore-debug", None);
    let debug_window = d.clone();
    action.connect_activate(move |_, _| debug_window.show());
    app.set_accels_for_action("app.gstore-debug", &["<Shift><Alt>G"]);
    app.add_action(&action);

    let debug_window = d.clone();
    PUSH.with(|c| {
        let mut c = c.borrow_mut();
        *c = Some(Box::new(move |action, state| {
            debug_window.update(action, state)
        }));
    });
    d
}

pub fn send_state_update(action: &dyn std::fmt::Debug, state: &dyn std::fmt::Debug) {
    PUSH.with(|c| {
        if let Some(p) = &*c.borrow() {
            p(action, state);
        }
    })
}

#[props]
#[derive(Default, Debug)]
pub struct Props {}

#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct State {
    actions: LinkedList<String>,
    state: String,
}

#[component(Props, State)]
#[derive(Debug)]
pub struct GstoreDebug {}

pub fn gstore_debug(props: Props) -> Rc<GstoreDebug> {
    let action_list: Rc<List>;
    let text: Rc<Text>;

    let comp = grx! {
        window (styles=[w(600), h(800)]) [
            container (styles=[vertical]) [
                header_bar (title=Some("gstore debugging".into())),
                container (styles=[vexpand]) [
                    scroll (styles=[vexpand]) [
                        clamp (styles=[m(4)]) [
                            list (
                                id=action_list,
                                classes="boxed-list".into(),
                                styles=[valign(start), hexpand, vexpand])
                        ]
                    ],
                    container (styles=[w(400)]) [
                        scroll [
                            container (styles=[hexpand, vexpand]) [
                                text(id=text)
                            ]
                        ]
                    ]
                ]
            ]
        ]
    };

    let comp = GstoreDebug::new(props, comp);

    comp.on_state(move |s| {
        text.set_text(&s.state);
        action_list.clear();
        for action in s.actions.iter() {
            action_list.append(grx! {
                action_row(title=action.clone())
            });
        }
    });

    comp
}

impl GstoreDebug {
    pub fn show(self: &Rc<Self>) {
        let window: Rc<window::Window> = self.root.clone().into_any().downcast().unwrap();
        window.show();
    }

    pub fn update(self: &Rc<Self>, action: &dyn std::fmt::Debug, state: &dyn std::fmt::Debug) {
        let action = format!("{:?}", action);
        let state = format!("{:#?}", state);
        self.set_state(move |s| {
            s.state = state.clone();
            s.actions.push_front(action.clone())
        });
    }
}