feat: implmented half working dynamic update system

This commit is contained in:
Byson94
2025-08-08 20:10:56 +05:30
parent 4d5c2fe170
commit 9f9d5dfee3
8 changed files with 477 additions and 138 deletions

81
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

View File

@@ -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<glib::SignalHandlerId>,
}
@@ -322,7 +325,15 @@ impl<B: DisplayBackend> App<B> {
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<B: DisplayBackend> App<B> {
}
}));
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(&gtk_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(&gtk_widget);
// container_for_task.show_all();
// }
// Err(err) => {
// eprintln!("Widget render failed: {:?}", err);
// }
// }
glib::ControlFlow::Continue
});
@@ -503,23 +521,7 @@ fn initialize_window<B: DisplayBackend>(
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<B: DisplayBackend>(
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<String, String>, code_path: &Path) -> Result<WidgetNode> {

View File

@@ -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<gtk::Widget> {
pub fn build_gtk_widget(input: WidgetInput, widget_reg: &mut WidgetRegistry) -> Result<gtk::Widget> {
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<gtk::Widget> {
fn build_gtk_widget_from_node(root_node: WidgetNode, widget_reg: &mut WidgetRegistry) -> Result<gtk::Widget> {
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));

View File

@@ -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<WidgetNode>) -> Result<gtk::Box> {
pub type UpdateFn = Box<dyn Fn(&Map)>;
pub struct WidgetEntry {
// pub widget: gtk::Widget, // not needed now
pub update_fn: UpdateFn,
}
pub struct WidgetRegistry {
widgets: HashMap<u64, WidgetEntry>,
}
impl WidgetRegistry {
pub fn new() -> Self {
Self {
widgets: HashMap::new(),
}
}
pub fn update_prop_changes(&self, id_to_props: HashMap<u64, Map>) {
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<WidgetNode>, widget_registry: &mut WidgetRegistry) -> Result<gtk::Box> {
// Parse initial props to create the widget:
let orientation = props
.get("orientation")
.and_then(|v| v.clone().try_cast::<String>())
@@ -65,14 +108,39 @@ pub(super) fn build_gtk_box(props: Map, children: Vec<WidgetNode>) -> Result<gtk
gtk_widget.set_homogeneous(space_evenly);
for child in children {
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);
}
let gtk_widget_clone = gtk_widget.clone();
let update_fn: UpdateFn = Box::new(move |props: &Map| {
if let Some(orientation_str) = props.get("orientation").and_then(|v| v.clone().try_cast::<String>()) {
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::<i64>()) {
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<WidgetNode>) -> Result<gtk::Overlay> {
pub(super) fn build_gtk_overlay(children: Vec<WidgetNode>, widget_registry: &mut WidgetRegistry) -> Result<gtk::Overlay> {
let gtk_widget = gtk::Overlay::new();
let count = children.len();
@@ -81,7 +149,7 @@ pub(super) fn build_gtk_overlay(children: Vec<WidgetNode>) -> Result<gtk::Overla
bail!("overlay must contain at least one element");
}
let mut children = children.into_iter().map(|child| build_gtk_widget(WidgetInput::Node(child)));
let mut children = children.into_iter().map(|child| build_gtk_widget(WidgetInput::Node(child), widget_registry));
// we have more than one child, we can unwrap
let first = children.next().unwrap()?;
@@ -96,7 +164,7 @@ pub(super) fn build_gtk_overlay(children: Vec<WidgetNode>) -> Result<gtk::Overla
Ok(gtk_widget)
}
pub(super) fn build_tooltip(children: Vec<WidgetNode>) -> Result<gtk::Box> {
pub(super) fn build_tooltip(children: Vec<WidgetNode>, widget_registry: &mut WidgetRegistry) -> Result<gtk::Box> {
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<WidgetNode>) -> Result<gtk::Box> {
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<WidgetNode>) -> Result<gtk::Box> {
Ok(gtk_widget)
}
pub(super) fn build_center_box(props: Map, children: Vec<WidgetNode>) -> Result<gtk::Box> {
pub(super) fn build_center_box(props: Map, children: Vec<WidgetNode>, widget_registry: &mut WidgetRegistry) -> Result<gtk::Box> {
let orientation = props
.get("orientation")
.and_then(|v| v.clone().try_cast::<String>())
@@ -143,9 +211,9 @@ pub(super) fn build_center_box(props: Map, children: Vec<WidgetNode>) -> 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<WidgetNode>) -> 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::<String>())
.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<WidgetNode>) -> Result<gtk::EventBox> {
pub(super) fn build_gtk_event_box(props: Map, children: Vec<WidgetNode>, widget_registry: &mut WidgetRegistry) -> Result<gtk::EventBox> {
let gtk_widget = gtk::EventBox::new();
// Support :hover selector
@@ -353,21 +446,23 @@ pub(super) fn build_gtk_event_box(props: Map, children: Vec<WidgetNode>) -> 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<WidgetNode>) -> Result<gtk::Stack> {
pub(super) fn build_gtk_stack(props: Map, children: Vec<WidgetNode>, widget_registry: &mut WidgetRegistry) -> Result<gtk::Stack> {
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<WidgetNode>) -> Result<g
let same_size = get_bool_prop(&props, "same_size", Some(false))?;
gtk_widget.set_homogeneous(same_size);
let id = hash_props_and_type(&props, "Stack");
Ok(gtk_widget)
}
pub(super) fn build_transform(props: Map) -> Result<Transform> {
pub(super) fn build_transform(props: Map, widget_registry: &mut WidgetRegistry) -> Result<Transform> {
let widget = Transform::new();
// rotate - the percentage to rotate
@@ -427,10 +524,12 @@ pub(super) fn build_transform(props: Map) -> Result<Transform> {
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<CircProg> {
pub(super) fn build_circular_progress_bar(props: Map, widget_registry: &mut WidgetRegistry) -> Result<CircProg> {
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<CircProg> {
widget.set_property("clockwise", clockwise);
}
let id = hash_props_and_type(&props, "CircularProgressBar");
Ok(widget)
}
pub(super) fn build_graph(props: Map) -> Result<super::graph::Graph> {
pub(super) fn build_graph(props: Map, widget_registry: &mut WidgetRegistry) -> Result<super::graph::Graph> {
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<super::graph::Graph> {
widget.set_property("vertical", vertical);
}
let id = hash_props_and_type(&props, "Graph");
Ok(widget)
}
pub(super) fn build_gtk_progress(props: Map) -> Result<gtk::ProgressBar> {
pub(super) fn build_gtk_progress(props: Map, widget_registry: &mut WidgetRegistry) -> Result<gtk::ProgressBar> {
let gtk_widget = gtk::ProgressBar::new();
let orientation = props
@@ -532,10 +635,13 @@ pub(super) fn build_gtk_progress(props: Map) -> Result<gtk::ProgressBar> {
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<gtk::Image> {
pub(super) fn build_gtk_image(props: Map, widget_registry: &mut WidgetRegistry) -> Result<gtk::Image> {
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<gtk::Image> {
return Ok(gtk_widget);
}
let id = hash_props_and_type(&props, "Image");
Ok(gtk_widget)
}
pub(super) fn build_gtk_button(props: Map) -> Result<gtk::Button> {
pub(super) fn build_gtk_button(props: Map, widget_registry: &mut WidgetRegistry) -> Result<gtk::Button> {
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::Button> {
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<gtk::Label> {
pub(super) fn build_gtk_label(props: Map, widget_registry: &mut WidgetRegistry) -> Result<gtk::Label> {
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::Label> {
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<gtk::Entry> {
pub(super) fn build_gtk_input(props: Map, widget_registry: &mut WidgetRegistry) -> Result<gtk::Entry> {
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<gtk::Entry> {
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<gtk::Calendar> {
pub(super) fn build_gtk_calendar(props: Map, widget_registry: &mut WidgetRegistry) -> Result<gtk::Calendar> {
let gtk_widget = gtk::Calendar::new();
// day - the selected day
@@ -828,10 +942,12 @@ pub(super) fn build_gtk_calendar(props: Map) -> Result<gtk::Calendar> {
);
}
let id = hash_props_and_type(&props, "Calendar");
Ok(gtk_widget)
}
pub(super) fn build_gtk_combo_box_text(props: Map) -> Result<gtk::ComboBoxText> {
pub(super) fn build_gtk_combo_box_text(props: Map, widget_registry: &mut WidgetRegistry) -> Result<gtk::ComboBoxText> {
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<gtk::ComboBoxText>
})
);
let id = hash_props_and_type(&props, "ComboBoxText");
Ok(gtk_widget)
}
pub(super) fn build_gtk_expander(props: Map, children: Vec<WidgetNode>) -> Result<gtk::Expander> {
pub(super) fn build_gtk_expander(props: Map, children: Vec<WidgetNode>, widget_registry: &mut WidgetRegistry) -> Result<gtk::Expander> {
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<WidgetNode>) -> 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<WidgetNode>) -> 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<WidgetNode>) -> Result<gtk::Revealer> {
pub(super) fn build_gtk_revealer(props: Map, children: Vec<WidgetNode>, widget_registry: &mut WidgetRegistry) -> Result<gtk::Revealer> {
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<WidgetNode>) -> 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<WidgetNode>) -> Resul
}
}
let id = hash_props_and_type(&props, "Revealer");
Ok(gtk_widget)
}
pub(super) fn build_gtk_checkbox(props: Map) -> Result<gtk::CheckButton> {
pub(super) fn build_gtk_checkbox(props: Map, widget_registry: &mut WidgetRegistry) -> Result<gtk::CheckButton> {
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<gtk::CheckButton> {
})
);
let id = hash_props_and_type(&props, "Checkbox");
Ok(gtk_widget)
}
pub(super) fn build_gtk_color_button(props: Map) -> Result<gtk::ColorButton> {
pub(super) fn build_gtk_color_button(props: Map, widget_registry: &mut WidgetRegistry) -> Result<gtk::ColorButton> {
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<gtk::ColorButton> {
);
}
let id = hash_props_and_type(&props, "ColorButton");
Ok(gtk_widget)
}
pub(super) fn build_gtk_color_chooser(props: Map) -> Result<gtk::ColorChooserWidget> {
pub(super) fn build_gtk_color_chooser(props: Map, widget_registry: &mut WidgetRegistry) -> Result<gtk::ColorChooserWidget> {
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<gtk::ColorChooserWid
);
}
let id = hash_props_and_type(&props, "ColorChooser");
Ok(gtk_widget)
}
pub(super) fn build_gtk_scale(props: Map) -> Result<gtk::Scale> {
pub(super) fn build_gtk_scale(props: Map, widget_registry: &mut WidgetRegistry) -> Result<gtk::Scale> {
let gtk_widget = gtk::Scale::new(gtk::Orientation::Horizontal, Some(&gtk::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<gtk::Scale> {
resolve_range_attrs(&props, gtk_widget.upcast_ref::<gtk::Range>())?;
let id = hash_props_and_type(&props, "Scale");
Ok(gtk_widget)
}
pub(super) fn build_gtk_scrolledwindow(props: Map, children: Vec<WidgetNode>) -> Result<gtk::ScrolledWindow> {
pub(super) fn build_gtk_scrolledwindow(props: Map, children: Vec<WidgetNode>, widget_registry: &mut WidgetRegistry) -> Result<gtk::ScrolledWindow> {
// 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::<&gtk::Adjustment>, None::<&gtk::Adjustment>);
@@ -1026,10 +1156,12 @@ pub(super) fn build_gtk_scrolledwindow(props: Map, children: Vec<WidgetNode>) ->
}
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)
}

View File

@@ -24,3 +24,4 @@ serde = { workspace = true, features = ["derive"] }
chrono.workspace = true
textwrap.workspace =true
termsize.workspace = true
ahash.workspace = true

View File

@@ -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<I: Iterator<Item = f32>> 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<I: Iterator<Item = f32>> 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
// }
// }

View File

@@ -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<WidgetNode>),
}
pub fn get_id_to_props_map(root_node: &WidgetNode) -> Result<HashMap<u64, Map>> {
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::<String>() {
value.clone_cast::<String>()
} else if value.is::<bool>() {
value.clone_cast::<bool>().to_string()
} else if value.is::<i64>() {
value.clone_cast::<i64>().to_string()
} else if value.is::<f64>() {
value.clone_cast::<f64>().to_string()
} else if value.is::<Array>() {
let arr = value.clone_cast::<Array>();
let serialized_items: Vec<String> = arr.iter().map(|v| serialize_value(v)).collect();
format!("[{}]", serialized_items.join(","))
} else if value.is::<Map>() {
let map = value.clone_cast::<Map>();
let mut kvs: Vec<_> = map.iter().collect();
kvs.sort_by_key(|(k, _)| k.clone());
let serialized_pairs: Vec<String> = 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");
}
}