From 9f9d5dfee35e3034581ed02fe9313a25e52aaf01 Mon Sep 17 00:00:00 2001 From: Byson94 Date: Fri, 8 Aug 2025 20:10:56 +0530 Subject: [PATCH] feat: implmented half working dynamic update system --- Cargo.lock | 81 ++++++- Cargo.toml | 1 + crates/ewwii/src/app.rs | 84 +++---- crates/ewwii/src/widgets/build_widget.rs | 83 ++++--- .../ewwii/src/widgets/widget_definitions.rs | 206 ++++++++++++++---- crates/iirhai/Cargo.toml | 1 + crates/iirhai/src/providers/helper/mod.rs | 30 +-- crates/iirhai/src/widgetnode.rs | 129 ++++++++++- 8 files changed, 477 insertions(+), 138 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7670c9..0e498d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,16 +19,16 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "const-random", - "getrandom", + "getrandom 0.3.3", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.8.26", ] [[package]] @@ -556,7 +556,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom", + "getrandom 0.2.15", "once_cell", "tiny-keccak", ] @@ -1166,10 +1166,22 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + [[package]] name = "gimli" version = "0.31.0" @@ -1273,7 +1285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7a68216437ef68f0738e48d6c7bb9e6e6a92237e001b03d838314b068f33c94" dependencies = [ "clap", - "getrandom", + "getrandom 0.2.15", "grass_compiler", ] @@ -1444,6 +1456,7 @@ dependencies = [ name = "iirhai" version = "0.1.0" dependencies = [ + "ahash", "anyhow", "chrono", "colored", @@ -1679,7 +1692,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -1691,7 +1704,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -1966,7 +1979,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -2066,6 +2079,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -2093,7 +2112,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -2807,6 +2826,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.93" @@ -3118,6 +3146,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "x11" version = "2.21.0" @@ -3229,7 +3266,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive 0.8.26", ] [[package]] @@ -3243,6 +3289,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "zvariant" version = "3.15.2" diff --git a/Cargo.toml b/Cargo.toml index b6df497..8afc56e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ iirhai = { version = "0.1.0", path = "crates/iirhai" } notifier_host = { version = "0.1.0", path = "crates/notifier_host" } anyhow = "1.0.86" +ahash = "0.8.12" bincode = "1.3.3" bytesize = "2.0.1" cached = "0.53.1" diff --git a/crates/ewwii/src/app.rs b/crates/ewwii/src/app.rs index 7d12714..a19be28 100644 --- a/crates/ewwii/src/app.rs +++ b/crates/ewwii/src/app.rs @@ -7,7 +7,11 @@ use crate::{ paths::EwwPaths, widgets::window::Window, // dynval::DynVal, - widgets::{build_widget::build_gtk_widget, build_widget::WidgetInput}, + widgets::{ + build_widget::build_gtk_widget, + build_widget::WidgetInput, + widget_definitions::WidgetRegistry, + }, window::{ coords::Coords, monitor::MonitorIdentifier, @@ -23,7 +27,7 @@ use ewwii_shared_util::Span; use gdk::Monitor; use glib::ObjectExt; use gtk::{gdk, glib}; -use iirhai::widgetnode::WidgetNode; +use iirhai::widgetnode::{WidgetNode, get_id_to_props_map}; use itertools::Itertools; use once_cell::sync::Lazy; use rhai::{Dynamic, Scope}; @@ -78,7 +82,6 @@ pub enum DaemonCommand { pub struct EwwiiWindow { pub name: String, pub gtk_window: Window, - pub content_box: gtk::Box, pub destroy_event_handler_id: Option, } @@ -322,7 +325,15 @@ impl App { let initiator = WindowInitiator::new(&window_def, window_args)?; - let root_widget = build_gtk_widget(WidgetInput::Window(window_def))?; + /// Holds the id and the props of a widget + /// It is crutual for supporting dynamic updates + let mut widget_reg_store = WidgetRegistry::new(); + // note for future me: ^ this might need cloning. + + let root_widget = build_gtk_widget( + WidgetInput::Window(window_def), + &mut widget_reg_store + )?; root_widget.style_context().add_class(window_name); @@ -367,25 +378,32 @@ impl App { } })); - let window_for_task = ewwii_window.gtk_window.clone(); - let container_for_task = ewwii_window.content_box.clone(); - update_receiver.attach(None, move |new_root_widget| { - for child in container_for_task.children() { - container_for_task.remove(&child); - } + let id_to_prop = get_id_to_props_map(&new_root_widget.expect( + "Failed to hash id's and props" + )); - match new_root_widget { - Ok(node) => { - let gtk_widget: gtk::Widget = - build_gtk_widget(WidgetInput::Node(node)).expect("Unable to create the gtk widget."); - container_for_task.add(>k_widget); - container_for_task.show_all(); - } - Err(err) => { - eprintln!("Widget render failed: {:?}", err); - } - } + println!("RECEIVED UPDATE!"); + + widget_reg_store.update_prop_changes(id_to_prop.expect( + "Failed to update property changes." + )); + + // for child in container_for_task.children() { + // container_for_task.remove(&child); + // } + + // match new_root_widget { + // Ok(node) => { + // let gtk_widget: gtk::Widget = + // build_gtk_widget(WidgetInput::Node(node)).expect("Unable to create the gtk widget."); + // container_for_task.add(>k_widget); + // container_for_task.show_all(); + // } + // Err(err) => { + // eprintln!("Widget render failed: {:?}", err); + // } + // } glib::ControlFlow::Continue }); @@ -503,23 +521,7 @@ fn initialize_window( on_screen_changed(&window, None); window.connect_screen_changed(on_screen_changed); - // crate container that will be replaced on rerender - - // !FIXME - // The following is the layout of ewwii. Due to the usage of a container during creation, - // the css styling has an issue where it doesnt apply correctly. - // I have to removing it and add a smarter system to update the gtk widgets - // instead of relying on replacing a container every time which resets the state. - // GtkWindow - // └── GtkBox (vertical) - // └── GtkBox (centerbox for example, with window_name as class) - - let container = gtk::Box::new(gtk::Orientation::Vertical, 0); - container.set_hexpand(true); - container.set_vexpand(true); - - container.add(&root_widget); - window.add(&container); + window.add(&root_widget); window.realize(); @@ -551,7 +553,11 @@ fn initialize_window( window.show_all(); - Ok(EwwiiWindow { name: window_init.name.clone(), gtk_window: window, content_box: container, destroy_event_handler_id: None }) + Ok(EwwiiWindow { + name: window_init.name.clone(), + gtk_window: window, + destroy_event_handler_id: None + }) } async fn generate_new_widgetnode(all_vars: &HashMap, code_path: &Path) -> Result { diff --git a/crates/ewwii/src/widgets/build_widget.rs b/crates/ewwii/src/widgets/build_widget.rs index b204ea3..9a117ed 100644 --- a/crates/ewwii/src/widgets/build_widget.rs +++ b/crates/ewwii/src/widgets/build_widget.rs @@ -1,19 +1,10 @@ use anyhow::Result; -// use codespan_reporting::diagnostic::Severity; -// use ewwii_shared_util::{AttrName, Spanned}; use gtk::{ gdk::prelude::Cast, - // prelude::{BoxExt, ContainerExt, WidgetExt}, - // Orientation, }; -// use maplit::hashmap; -// use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::{ config::WindowDefinition, - // gen_diagnostic_macro, - // error_handling_ctx, - // dynval::DynVal, widgets::widget_definitions::*, }; @@ -27,42 +18,64 @@ pub enum WidgetInput { Window(WindowDefinition), } -pub fn build_gtk_widget(input: WidgetInput) -> Result { +pub fn build_gtk_widget(input: WidgetInput, widget_reg: &mut WidgetRegistry) -> Result { let node = match input { WidgetInput::Node(n) => n, WidgetInput::Window(w) => w.root_widget, }; - build_gtk_widget_from_node(node) + build_gtk_widget_from_node(node, widget_reg) } // TODO: implement the commented lines -fn build_gtk_widget_from_node(root_node: WidgetNode) -> Result { +fn build_gtk_widget_from_node(root_node: WidgetNode, widget_reg: &mut WidgetRegistry) -> Result { let root_node2 = root_node.clone(); let gtk_widget = match root_node { - WidgetNode::Box { props, children } => build_gtk_box(props, children)?.upcast(), - WidgetNode::CenterBox { props, children } => build_center_box(props, children)?.upcast(), - WidgetNode::EventBox { props, children } => build_gtk_event_box(props, children)?.upcast(), - WidgetNode::ToolTip { children } => build_tooltip(children)?.upcast(), - WidgetNode::CircularProgress { props } => build_circular_progress_bar(props)?.upcast(), - WidgetNode::Graph { props } => build_graph(props)?.upcast(), - WidgetNode::Transform { props } => build_transform(props)?.upcast(), - WidgetNode::Slider { props } => build_gtk_scale(props)?.upcast(), - WidgetNode::Progress { props } => build_gtk_progress(props)?.upcast(), - WidgetNode::Image { props } => build_gtk_image(props)?.upcast(), - WidgetNode::Button { props } => build_gtk_button(props)?.upcast(), - WidgetNode::Label { props } => build_gtk_label(props)?.upcast(), + WidgetNode::Box { props, children } => + build_gtk_box(props, children, widget_reg)?.upcast(), + WidgetNode::CenterBox { props, children } => + build_center_box(props, children, widget_reg)?.upcast(), + WidgetNode::EventBox { props, children } => + build_gtk_event_box(props, children, widget_reg)?.upcast(), + WidgetNode::ToolTip { children } => build_tooltip(children, widget_reg)?.upcast(), + WidgetNode::CircularProgress { props } => + build_circular_progress_bar(props, widget_reg)?.upcast(), + WidgetNode::Graph { props } => + build_graph(props, widget_reg)?.upcast(), + WidgetNode::Transform { props } => + build_transform(props, widget_reg)?.upcast(), + WidgetNode::Slider { props } => + build_gtk_scale(props, widget_reg)?.upcast(), + WidgetNode::Progress { props } => + build_gtk_progress(props, widget_reg)?.upcast(), + WidgetNode::Image { props } => + build_gtk_image(props, widget_reg)?.upcast(), + WidgetNode::Button { props } => + build_gtk_button(props, widget_reg)?.upcast(), + WidgetNode::Label { props } => + build_gtk_label(props, widget_reg)?.upcast(), // WIDGET_NAME_LITERAL => build_gtk_literal(node)?.upcast(), - WidgetNode::Input { props } => build_gtk_input(props)?.upcast(), - WidgetNode::Calendar { props } => build_gtk_calendar(props)?.upcast(), - WidgetNode::ColorButton { props } => build_gtk_color_button(props)?.upcast(), - WidgetNode::Expander { props, children } => build_gtk_expander(props, children)?.upcast(), - WidgetNode::ColorChooser { props } => build_gtk_color_chooser(props)?.upcast(), - WidgetNode::ComboBoxText { props } => build_gtk_combo_box_text(props)?.upcast(), - WidgetNode::Checkbox { props } => build_gtk_checkbox(props)?.upcast(), - WidgetNode::Revealer { props, children } => build_gtk_revealer(props, children)?.upcast(), - WidgetNode::Scroll { props, children } => build_gtk_scrolledwindow(props, children)?.upcast(), - WidgetNode::OverLay { children } => build_gtk_overlay(children)?.upcast(), - WidgetNode::Stack { props, children } => build_gtk_stack(props, children)?.upcast(), + WidgetNode::Input { props } => + build_gtk_input(props, widget_reg)?.upcast(), + WidgetNode::Calendar { props } => + build_gtk_calendar(props, widget_reg)?.upcast(), + WidgetNode::ColorButton { props } => + build_gtk_color_button(props, widget_reg)?.upcast(), + WidgetNode::Expander { props, children } => + build_gtk_expander(props, children, widget_reg)?.upcast(), + WidgetNode::ColorChooser { props } => + build_gtk_color_chooser(props, widget_reg)?.upcast(), + WidgetNode::ComboBoxText { props } => + build_gtk_combo_box_text(props, widget_reg)?.upcast(), + WidgetNode::Checkbox { props } => + build_gtk_checkbox(props, widget_reg)?.upcast(), + WidgetNode::Revealer { props, children } => + build_gtk_revealer(props, children, widget_reg)?.upcast(), + WidgetNode::Scroll { props, children } => + build_gtk_scrolledwindow(props, children, widget_reg)?.upcast(), + WidgetNode::OverLay { children } => + build_gtk_overlay(children, widget_reg)?.upcast(), + WidgetNode::Stack { props, children } => + build_gtk_stack(props, children, widget_reg)?.upcast(), // WIDGET_NAME_SYSTRAY => build_systray(node)?.upcast(), unknown => { return Err(anyhow::anyhow!("Cannot build GTK widget from node: {:?}", unknown)); diff --git a/crates/ewwii/src/widgets/widget_definitions.rs b/crates/ewwii/src/widgets/widget_definitions.rs index dec056b..4916738 100644 --- a/crates/ewwii/src/widgets/widget_definitions.rs +++ b/crates/ewwii/src/widgets/widget_definitions.rs @@ -8,7 +8,7 @@ use anyhow::{anyhow, bail, Result}; use gdk::{ModifierType, NotifyType}; use gtk::{self, prelude::*, DestDefaults, TargetEntry, TargetList}; use gtk::{gdk, glib, pango}; -use iirhai::widgetnode::WidgetNode; +use iirhai::widgetnode::{WidgetNode, hash_props_and_type}; use once_cell::sync::Lazy; use rhai::Map; @@ -20,6 +20,7 @@ use std::{ collections::HashSet, rc::Rc, time::Duration, + collections::HashMap, }; // custom widgets @@ -49,7 +50,49 @@ macro_rules! connect_signal_handler { }}; } -pub(super) fn build_gtk_box(props: Map, children: Vec) -> Result { +pub type UpdateFn = Box; + +pub struct WidgetEntry { + // pub widget: gtk::Widget, // not needed now + pub update_fn: UpdateFn, +} + +pub struct WidgetRegistry { + widgets: HashMap, +} + +impl WidgetRegistry { + pub fn new() -> Self { + Self { + widgets: HashMap::new(), + } + } + + pub fn update_prop_changes(&self, id_to_props: HashMap) { + println!("--- id_to_props keys ---"); + for id in id_to_props.keys() { + println!("{}", id); + } + + println!("--- self.widgets keys ---"); + for id in self.widgets.keys() { + println!("{}", id); + } + + println!("--- processing update ids ---"); + for (id, props) in id_to_props { + if let Some(entry) = self.widgets.get(&id) { + println!("Updating widget id: {}", id); + (entry.update_fn)(&props); + } else { + println!("Warning: id {} not found in widget_registry", id); + } + } + } +} + +pub(super) fn build_gtk_box(props: Map, children: Vec, widget_registry: &mut WidgetRegistry) -> Result { + // Parse initial props to create the widget: let orientation = props .get("orientation") .and_then(|v| v.clone().try_cast::()) @@ -65,14 +108,39 @@ pub(super) fn build_gtk_box(props: Map, children: Vec) -> Result()) { + if let Ok(orientation) = parse_orientation(&orientation_str) { + gtk_widget_clone.set_orientation(orientation); + } + } + + if let Some(spacing_val) = props.get("spacing").and_then(|v| v.clone().try_cast::()) { + gtk_widget_clone.set_spacing(spacing_val as i32); + } + + if let Ok(space_evenly) = get_bool_prop(props, "space_evenly", Some(true)) { + gtk_widget_clone.set_homogeneous(space_evenly); + } + }); + + let id = hash_props_and_type(&props, "Box"); + + widget_registry.widgets.insert(id, WidgetEntry { + // widget: gtk_widget.upcast(), + update_fn, + }); + Ok(gtk_widget) } -pub(super) fn build_gtk_overlay(children: Vec) -> Result { +pub(super) fn build_gtk_overlay(children: Vec, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::Overlay::new(); let count = children.len(); @@ -81,7 +149,7 @@ pub(super) fn build_gtk_overlay(children: Vec) -> Result) -> Result) -> Result { +pub(super) fn build_tooltip(children: Vec, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::Box::new(gtk::Orientation::Horizontal, 0); gtk_widget.set_has_tooltip(true); @@ -112,14 +180,14 @@ pub(super) fn build_tooltip(children: Vec) -> Result { let content_node = children.get(1).cloned().ok_or_else(|| anyhow!("missing content"))?; // The visible child immediately - let content_widget = build_gtk_widget(WidgetInput::Node(content_node))?; + let content_widget = build_gtk_widget(WidgetInput::Node(content_node), widget_registry)?; gtk_widget.add(&content_widget); let tooltip_node = Rc::new(tooltip_node); + let tooltip_widget = build_gtk_widget(WidgetInput::Node(Rc::clone(&tooltip_node).as_ref().clone()), widget_registry) + .expect("Failed to build tooltip widget"); gtk_widget.connect_query_tooltip(move |_widget, _x, _y, _keyboard_mode, tooltip| { - let tooltip_widget = build_gtk_widget(WidgetInput::Node(Rc::clone(&tooltip_node).as_ref().clone())) - .expect("Failed to build tooltip widget"); tooltip.set_custom(Some(&tooltip_widget)); true }); @@ -127,7 +195,7 @@ pub(super) fn build_tooltip(children: Vec) -> Result { Ok(gtk_widget) } -pub(super) fn build_center_box(props: Map, children: Vec) -> Result { +pub(super) fn build_center_box(props: Map, children: Vec, widget_registry: &mut WidgetRegistry) -> Result { let orientation = props .get("orientation") .and_then(|v| v.clone().try_cast::()) @@ -143,9 +211,9 @@ pub(super) fn build_center_box(props: Map, children: Vec) -> Result< bail!("centerbox must contain exactly 3 children, but got more"); } - let first = build_gtk_widget(WidgetInput::Node(children.get(0).cloned().ok_or_else(|| anyhow!("missing child 0"))?))?; - let center = build_gtk_widget(WidgetInput::Node(children.get(1).cloned().ok_or_else(|| anyhow!("missing child 1"))?))?; - let end = build_gtk_widget(WidgetInput::Node(children.get(2).cloned().ok_or_else(|| anyhow!("missing child 2"))?))?; + let first = build_gtk_widget(WidgetInput::Node(children.get(0).cloned().ok_or_else(|| anyhow!("missing child 0"))?), widget_registry)?; + let center = build_gtk_widget(WidgetInput::Node(children.get(1).cloned().ok_or_else(|| anyhow!("missing child 1"))?), widget_registry)?; + let end = build_gtk_widget(WidgetInput::Node(children.get(2).cloned().ok_or_else(|| anyhow!("missing child 2"))?), widget_registry)?; let gtk_widget = gtk::Box::new(orientation, 0); gtk_widget.pack_start(&first, true, true, 0); @@ -156,10 +224,35 @@ pub(super) fn build_center_box(props: Map, children: Vec) -> Result< center.show(); end.show(); + let gtk_widget_clone = gtk_widget.clone(); + + let update_fn: UpdateFn = Box::new(move |props: &Map| { + let orientation = match props + .get("orientation") + .and_then(|v| v.clone().try_cast::()) + .map(|s| parse_orientation(&s)) + .transpose() { + Ok(opt) => opt.unwrap_or(gtk::Orientation::Horizontal), + Err(e) => { + eprintln!("Error parsing orientation: {:?}", e); + gtk::Orientation::Horizontal + } + }; + + gtk_widget_clone.set_orientation(orientation); + }); + + + let id = hash_props_and_type(&props, "CenterBox"); + + widget_registry.widgets.insert(id, WidgetEntry { + update_fn + }); + Ok(gtk_widget) } -pub(super) fn build_gtk_event_box(props: Map, children: Vec) -> Result { +pub(super) fn build_gtk_event_box(props: Map, children: Vec, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::EventBox::new(); // Support :hover selector @@ -353,21 +446,23 @@ pub(super) fn build_gtk_event_box(props: Map, children: Vec) -> Resu } let child = children.get(0).cloned().ok_or_else(|| anyhow!("missing child 0"))?; - let child_widget = build_gtk_widget(WidgetInput::Node(child))?; + let child_widget = build_gtk_widget(WidgetInput::Node(child), widget_registry)?; gtk_widget.add(&child_widget); child_widget.show(); + let id = hash_props_and_type(&props, "EventBox"); + Ok(gtk_widget) } -pub(super) fn build_gtk_stack(props: Map, children: Vec) -> Result { +pub(super) fn build_gtk_stack(props: Map, children: Vec, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::Stack::new(); if children.is_empty() { return Err(anyhow!("stack must contain at least one element")); } - let children = children.into_iter().map(|child| build_gtk_widget(WidgetInput::Node(child))); + let children = children.into_iter().map(|child| build_gtk_widget(WidgetInput::Node(child), widget_registry)); for (i, child) in children.enumerate() { let child = child?; @@ -386,10 +481,12 @@ pub(super) fn build_gtk_stack(props: Map, children: Vec) -> Result Result { +pub(super) fn build_transform(props: Map, widget_registry: &mut WidgetRegistry) -> Result { let widget = Transform::new(); // rotate - the percentage to rotate @@ -427,10 +524,12 @@ pub(super) fn build_transform(props: Map) -> Result { widget.set_property("scale-y", scale_y); } + let id = hash_props_and_type(&props, "Transform"); + Ok(widget) } -pub(super) fn build_circular_progress_bar(props: Map) -> Result { +pub(super) fn build_circular_progress_bar(props: Map, widget_registry: &mut WidgetRegistry) -> Result { let widget = CircProg::new(); if let Ok(value) = get_f64_prop(&props, "value", None) { @@ -449,10 +548,12 @@ pub(super) fn build_circular_progress_bar(props: Map) -> Result { widget.set_property("clockwise", clockwise); } + let id = hash_props_and_type(&props, "CircularProgressBar"); + Ok(widget) } -pub(super) fn build_graph(props: Map) -> Result { +pub(super) fn build_graph(props: Map, widget_registry: &mut WidgetRegistry) -> Result { let widget = super::graph::Graph::new(); if let Ok(value) = get_f64_prop(&props, "value", None) { @@ -510,10 +611,12 @@ pub(super) fn build_graph(props: Map) -> Result { widget.set_property("vertical", vertical); } + let id = hash_props_and_type(&props, "Graph"); + Ok(widget) } -pub(super) fn build_gtk_progress(props: Map) -> Result { +pub(super) fn build_gtk_progress(props: Map, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::ProgressBar::new(); let orientation = props @@ -532,10 +635,13 @@ pub(super) fn build_gtk_progress(props: Map) -> Result { if let Ok(bar_value) = get_f64_prop(&props, "value", None) { gtk_widget.set_fraction(bar_value / 100f64) } + + let id = hash_props_and_type(&props, "Progress"); + Ok(gtk_widget) } -pub(super) fn build_gtk_image(props: Map) -> Result { +pub(super) fn build_gtk_image(props: Map, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::Image::new(); let path = get_string_prop(&props, "path", None)?; @@ -590,10 +696,12 @@ pub(super) fn build_gtk_image(props: Map) -> Result { return Ok(gtk_widget); } + let id = hash_props_and_type(&props, "Image"); + Ok(gtk_widget) } -pub(super) fn build_gtk_button(props: Map) -> Result { +pub(super) fn build_gtk_button(props: Map, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::Button::new(); let timeout = get_duration_prop(&props, "timeout", Some(Duration::from_millis(200)))?; @@ -644,10 +752,12 @@ pub(super) fn build_gtk_button(props: Map) -> Result { gtk_widget.set_label(&button_label); } + let id = hash_props_and_type(&props, "Button"); + Ok(gtk_widget) } -pub(super) fn build_gtk_label(props: Map) -> Result { +pub(super) fn build_gtk_label(props: Map, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::Label::new(None); let truncate = get_bool_prop(&props, "truncate", Some(false))?; @@ -735,10 +845,12 @@ pub(super) fn build_gtk_label(props: Map) -> Result { gtk_widget.set_lines(lines); } + let id = hash_props_and_type(&props, "Label"); + Ok(gtk_widget) } -pub(super) fn build_gtk_input(props: Map) -> Result { +pub(super) fn build_gtk_input(props: Map, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::Entry::new(); if let Ok(value) = get_string_prop(&props, "value", None) { @@ -768,10 +880,12 @@ pub(super) fn build_gtk_input(props: Map) -> Result { let password: bool = get_bool_prop(&props, "password", Some(false))?; gtk_widget.set_visibility(!password); + let id = hash_props_and_type(&props, "Input"); + Ok(gtk_widget) } -pub(super) fn build_gtk_calendar(props: Map) -> Result { +pub(super) fn build_gtk_calendar(props: Map, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::Calendar::new(); // day - the selected day @@ -828,10 +942,12 @@ pub(super) fn build_gtk_calendar(props: Map) -> Result { ); } + let id = hash_props_and_type(&props, "Calendar"); + Ok(gtk_widget) } -pub(super) fn build_gtk_combo_box_text(props: Map) -> Result { +pub(super) fn build_gtk_combo_box_text(props: Map, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::ComboBoxText::new(); if let Ok(items) = get_vec_string_prop(&props, "items", None) { @@ -851,10 +967,12 @@ pub(super) fn build_gtk_combo_box_text(props: Map) -> Result }) ); + let id = hash_props_and_type(&props, "ComboBoxText"); + Ok(gtk_widget) } -pub(super) fn build_gtk_expander(props: Map, children: Vec) -> Result { +pub(super) fn build_gtk_expander(props: Map, children: Vec, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::Expander::new(None); let count = children.len(); @@ -866,7 +984,7 @@ pub(super) fn build_gtk_expander(props: Map, children: Vec) -> Resul } let child = children.get(0).cloned().ok_or_else(|| anyhow!("missing child 0"))?; - let child_widget = build_gtk_widget(WidgetInput::Node(child))?; + let child_widget = build_gtk_widget(WidgetInput::Node(child), widget_registry)?; gtk_widget.add(&child_widget); child_widget.show(); @@ -878,10 +996,12 @@ pub(super) fn build_gtk_expander(props: Map, children: Vec) -> Resul gtk_widget.set_expanded(expanded); } + let id = hash_props_and_type(&props, "Expander"); + Ok(gtk_widget) } -pub(super) fn build_gtk_revealer(props: Map, children: Vec) -> Result { +pub(super) fn build_gtk_revealer(props: Map, children: Vec, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::Revealer::new(); let transition = get_string_prop(&props, "transition", Some("crossfade"))?; @@ -898,7 +1018,7 @@ pub(super) fn build_gtk_revealer(props: Map, children: Vec) -> Resul match children.len() { 0 => { /* maybe warn? */ } 1 => { - let child_widget = build_gtk_widget(WidgetInput::Node(children[0].clone()))?; + let child_widget = build_gtk_widget(WidgetInput::Node(children[0].clone()), widget_registry)?; gtk_widget.set_child(Some(&child_widget)); } n => { @@ -906,10 +1026,12 @@ pub(super) fn build_gtk_revealer(props: Map, children: Vec) -> Resul } } + let id = hash_props_and_type(&props, "Revealer"); + Ok(gtk_widget) } -pub(super) fn build_gtk_checkbox(props: Map) -> Result { +pub(super) fn build_gtk_checkbox(props: Map, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::CheckButton::new(); let checked = get_bool_prop(&props, "checked", Some(false))?; @@ -926,10 +1048,12 @@ pub(super) fn build_gtk_checkbox(props: Map) -> Result { }) ); + let id = hash_props_and_type(&props, "Checkbox"); + Ok(gtk_widget) } -pub(super) fn build_gtk_color_button(props: Map) -> Result { +pub(super) fn build_gtk_color_button(props: Map, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::ColorButton::builder().build(); // use-alpha - bool to wether or not use alpha @@ -950,10 +1074,12 @@ pub(super) fn build_gtk_color_button(props: Map) -> Result { ); } + let id = hash_props_and_type(&props, "ColorButton"); + Ok(gtk_widget) } -pub(super) fn build_gtk_color_chooser(props: Map) -> Result { +pub(super) fn build_gtk_color_chooser(props: Map, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::ColorChooserWidget::new(); // use-alpha - bool to wether or not use alpha @@ -974,10 +1100,12 @@ pub(super) fn build_gtk_color_chooser(props: Map) -> Result Result { +pub(super) fn build_gtk_scale(props: Map, widget_registry: &mut WidgetRegistry) -> Result { let gtk_widget = gtk::Scale::new(gtk::Orientation::Horizontal, Some(>k::Adjustment::new(0.0, 0.0, 100.0, 1.0, 1.0, 1.0))); let flipped = get_bool_prop(&props, "flipped", Some(false))?; @@ -1002,10 +1130,12 @@ pub(super) fn build_gtk_scale(props: Map) -> Result { resolve_range_attrs(&props, gtk_widget.upcast_ref::())?; + let id = hash_props_and_type(&props, "Scale"); + Ok(gtk_widget) } -pub(super) fn build_gtk_scrolledwindow(props: Map, children: Vec) -> Result { +pub(super) fn build_gtk_scrolledwindow(props: Map, children: Vec, widget_registry: &mut WidgetRegistry) -> Result { // I don't have single idea of what those two generics are supposed to be, but this works. let gtk_widget = gtk::ScrolledWindow::new(None::<>k::Adjustment>, None::<>k::Adjustment>); @@ -1026,10 +1156,12 @@ pub(super) fn build_gtk_scrolledwindow(props: Map, children: Vec) -> } let child = children.get(0).cloned().ok_or_else(|| anyhow!("missing child 0"))?; - let child_widget = build_gtk_widget(WidgetInput::Node(child))?; + let child_widget = build_gtk_widget(WidgetInput::Node(child), widget_registry)?; gtk_widget.add(&child_widget); child_widget.show(); + let id = hash_props_and_type(&props, "ScrolledWindow"); + Ok(gtk_widget) } diff --git a/crates/iirhai/Cargo.toml b/crates/iirhai/Cargo.toml index 2e985c9..6cdb6cc 100644 --- a/crates/iirhai/Cargo.toml +++ b/crates/iirhai/Cargo.toml @@ -24,3 +24,4 @@ serde = { workspace = true, features = ["derive"] } chrono.workspace = true textwrap.workspace =true termsize.workspace = true +ahash.workspace = true diff --git a/crates/iirhai/src/providers/helper/mod.rs b/crates/iirhai/src/providers/helper/mod.rs index bcfd28d..b59fea4 100644 --- a/crates/iirhai/src/providers/helper/mod.rs +++ b/crates/iirhai/src/providers/helper/mod.rs @@ -1,15 +1,17 @@ -pub trait IterAverage { - fn avg(self) -> f32; -} +//! Used by buildin_signals.rs +//! it is currently removed as buildtin_singals.rs is empty +// pub trait IterAverage { +// fn avg(self) -> f32; +// } -impl> IterAverage for I { - fn avg(self) -> f32 { - let mut total = 0f32; - let mut cnt = 0f32; - for value in self { - total += value; - cnt += 1f32; - } - total / cnt - } -} +// impl> IterAverage for I { +// fn avg(self) -> f32 { +// let mut total = 0f32; +// let mut cnt = 0f32; +// for value in self { +// total += value; +// cnt += 1f32; +// } +// total / cnt +// } +// } diff --git a/crates/iirhai/src/widgetnode.rs b/crates/iirhai/src/widgetnode.rs index 85a1c17..7a340a7 100644 --- a/crates/iirhai/src/widgetnode.rs +++ b/crates/iirhai/src/widgetnode.rs @@ -1,4 +1,8 @@ -use rhai::Map; +use rhai::{Dynamic, Map, Array}; +use anyhow::{Result, bail}; +use ahash::AHasher; +use std::hash::{Hasher, Hash}; +use std::collections::HashMap; #[derive(Debug, Clone)] pub enum WidgetNode { @@ -34,3 +38,126 @@ pub enum WidgetNode { Listen { var: String, props: Map }, Enter(Vec), } + +pub fn get_id_to_props_map(root_node: &WidgetNode) -> Result> { + let mut id_to_props = HashMap::new(); + + let mut insert_props = |props: &Map, widget_type: &str| -> Result<()> { + let id = hash_props_and_type(props, widget_type); + id_to_props.insert(id, props.clone()); + Ok(()) + }; + + match root_node { + WidgetNode::Box { props, .. } => insert_props(props, "Box")?, + WidgetNode::CenterBox { props, .. } => insert_props(props, "CenterBox")?, + WidgetNode::EventBox { props, .. } => insert_props(props, "EventBox")?, + WidgetNode::CircularProgress { props } => insert_props(props, "CircularProgress")?, + WidgetNode::Graph { props } => insert_props(props, "Graph")?, + WidgetNode::Transform { props } => insert_props(props, "Transform")?, + WidgetNode::Slider { props } => insert_props(props, "Slider")?, + WidgetNode::Progress { props } => insert_props(props, "Progress")?, + WidgetNode::Image { props } => insert_props(props, "Image")?, + WidgetNode::Button { props } => insert_props(props, "Button")?, + WidgetNode::Label { props } => insert_props(props, "Label")?, + WidgetNode::Input { props } => insert_props(props, "Input")?, + WidgetNode::Calendar { props } => insert_props(props, "Calendar")?, + WidgetNode::ColorButton { props } => insert_props(props, "ColorButton")?, + WidgetNode::Expander { props, .. } => insert_props(props, "Expander")?, + WidgetNode::ColorChooser { props } => insert_props(props, "ColorChooser")?, + WidgetNode::ComboBoxText { props } => insert_props(props, "ComboBoxText")?, + WidgetNode::Checkbox { props } => insert_props(props, "Checkbox")?, + WidgetNode::Revealer { props, .. } => insert_props(props, "Revealer")?, + WidgetNode::Scroll { props, .. } => insert_props(props, "Scroll")?, + WidgetNode::Stack { props, .. } => insert_props(props, "Stack")?, + _ => { + // do nothing for now ig? + } + } + + Ok(id_to_props) +} + +pub fn hash_props_and_type(props: &Map, widget_type_str: &str) -> u64 { + let mut hasher = AHasher::default(); + + widget_type_str.hash(&mut hasher); + + let mut kv_pairs: Vec<_> = props.iter().collect(); + kv_pairs.sort_by_key(|(k, _)| k.clone()); + + for (k, v) in kv_pairs { + k.hash(&mut hasher); + let val_str = serialize_value(v); + val_str.hash(&mut hasher); + } + + hasher.finish() +} + +fn serialize_value(value: &Dynamic) -> String { + if value.is::() { + value.clone_cast::() + } else if value.is::() { + value.clone_cast::().to_string() + } else if value.is::() { + value.clone_cast::().to_string() + } else if value.is::() { + value.clone_cast::().to_string() + } else if value.is::() { + let arr = value.clone_cast::(); + let serialized_items: Vec = arr.iter().map(|v| serialize_value(v)).collect(); + format!("[{}]", serialized_items.join(",")) + } else if value.is::() { + let map = value.clone_cast::(); + let mut kvs: Vec<_> = map.iter().collect(); + kvs.sort_by_key(|(k, _)| k.clone()); + let serialized_pairs: Vec = kvs.iter() + .map(|(k, v)| format!("{}:{}", k, serialize_value(v))) + .collect(); + format!("{{{}}}", serialized_pairs.join(",")) + } else { + format!("{:?}", value) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rhai::{Map, Dynamic}; + + #[test] + fn test_hash_props_and_type_consistency() { + let mut props = Map::new(); + props.insert("class".into(), Dynamic::from("mywidget")); + props.insert("enabled".into(), Dynamic::from(true)); + props.insert("count".into(), Dynamic::from(42_i64)); + + // Nested map + let mut nested = Map::new(); + nested.insert("nested_key".into(), Dynamic::from("value")); + props.insert("nested".into(), Dynamic::from(nested)); + + // Array + let arr = vec![Dynamic::from(1_i64), Dynamic::from(2_i64), Dynamic::from(3_i64)]; + props.insert("arr".into(), Dynamic::from(arr)); + + let widget_type = "Box"; + + let hash1 = hash_props_and_type(&props, widget_type); + let hash2 = hash_props_and_type(&props, widget_type); + + assert_eq!(hash1, hash2, "Hashes should be consistent on same input"); + + // Change one prop and expect different hash + let mut props_modified = props.clone(); + props_modified.insert("count".into(), Dynamic::from(43_i64)); + + let hash3 = hash_props_and_type(&props_modified, widget_type); + assert_ne!(hash1, hash3, "Hashes should differ when props change"); + + // Different widget type string + let hash4 = hash_props_and_type(&props, "Button"); + assert_ne!(hash1, hash4, "Hashes should differ for different widget types"); + } +}