feat: make localsignal simpler through localbind container
This commit is contained in:
@@ -528,6 +528,9 @@ impl<B: DisplayBackend> App<B> {
|
||||
let store = self.pl_handler_store.clone();
|
||||
let b_interval = self.rt_engine_config.batching_interval;
|
||||
|
||||
// kick start the localsignal
|
||||
rhai_impl::updates::handle_localsignal_changes();
|
||||
|
||||
glib::MainContext::default().spawn_local(async move {
|
||||
let mut pending_updates = HashSet::new();
|
||||
|
||||
|
||||
@@ -50,6 +50,9 @@ fn build_gtk_widget_from_node(
|
||||
WidgetNode::ToolTip { props, children } => {
|
||||
build_tooltip(props, children, widget_reg)?.upcast()
|
||||
}
|
||||
WidgetNode::LocalBind { props, children } => {
|
||||
build_localbind(props, children, widget_reg)?.upcast()
|
||||
}
|
||||
WidgetNode::CircularProgress { props } => {
|
||||
build_circular_progress_bar(props, widget_reg)?.upcast()
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use gtk4::{
|
||||
};
|
||||
use rhai::Map;
|
||||
use rhai_impl::ast::{get_id_to_widget_info, hash_props_and_type, WidgetNode};
|
||||
use rhai_impl::updates::LocalSignal;
|
||||
|
||||
use super::widget_definitions_helper::*;
|
||||
use shared_utils::extract_props::*;
|
||||
@@ -382,6 +383,91 @@ pub(super) fn build_tooltip(
|
||||
Ok(gtk_widget)
|
||||
}
|
||||
|
||||
pub(super) fn build_localbind(
|
||||
props: &Map,
|
||||
children: &Vec<WidgetNode>,
|
||||
widget_registry: &mut WidgetRegistry,
|
||||
) -> Result<gtk4::Box> {
|
||||
let gtk_widget = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
||||
|
||||
let count = children.len();
|
||||
|
||||
if count < 1 {
|
||||
bail!("localbind must contain exactly 1 child");
|
||||
} else if count > 1 {
|
||||
bail!("localbind must contain exactly 1 child, but got more");
|
||||
}
|
||||
|
||||
let child_node = children.get(0).cloned().ok_or_else(|| anyhow!("missing child"))?;
|
||||
let child_widget = build_gtk_widget(&WidgetInput::Node(child_node), widget_registry)?;
|
||||
|
||||
gtk_widget.append(&child_widget);
|
||||
|
||||
let apply_props = |props: &Map,
|
||||
gtk_widget: >k4::Box|
|
||||
-> Result<()> {
|
||||
let bindings_variant = props.get("bindings");
|
||||
|
||||
if bindings_variant.is_none() {
|
||||
bail!("No 'bindings' map found in props.");
|
||||
}
|
||||
|
||||
let bindings = match bindings_variant.and_then(|v| v.clone().try_cast::<rhai::Map>()) {
|
||||
Some(map) => map,
|
||||
None => {
|
||||
bail!("'bindings' is not a valid map.");
|
||||
}
|
||||
};
|
||||
|
||||
for (prop_name, localsignal_val) in bindings {
|
||||
let prop_name = prop_name.clone();
|
||||
|
||||
let localsignal = match localsignal_val.clone().try_cast::<LocalSignal>() {
|
||||
Some(sig) => sig,
|
||||
None => {
|
||||
bail!("Invalid localsignal for property '{}'", prop_name);
|
||||
}
|
||||
};
|
||||
|
||||
let signal_widget = localsignal.data;
|
||||
connect_signal_handler!(
|
||||
signal_widget,
|
||||
signal_widget.connect_notify_local(Some("value"), glib::clone!(#[weak] gtk_widget, move |obj, _| {
|
||||
if let Some(child) = gtk_widget.first_child() {
|
||||
child.set_property(&prop_name, &obj.property::<String>("value"));
|
||||
}
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
apply_props(&props, >k_widget)?;
|
||||
|
||||
let gtk_widget_clone = gtk_widget.clone();
|
||||
let update_fn: UpdateFn = Box::new(move |props: &Map| {
|
||||
let _ = apply_props(&props, >k_widget_clone);
|
||||
|
||||
// now re-apply generic widget attrs
|
||||
if let Err(err) =
|
||||
resolve_rhai_widget_attrs(>k_widget_clone.clone().upcast::<gtk4::Widget>(), &props)
|
||||
{
|
||||
eprintln!("Failed to update widget attrs: {:?}", err);
|
||||
}
|
||||
});
|
||||
|
||||
let id = hash_props_and_type(&props, "LocalBind");
|
||||
|
||||
widget_registry
|
||||
.widgets
|
||||
.insert(id, WidgetEntry { widget: gtk_widget.clone().upcast(), update_fn });
|
||||
|
||||
resolve_rhai_widget_attrs(>k_widget.clone().upcast::<gtk4::Widget>(), &props)?;
|
||||
|
||||
Ok(gtk_widget)
|
||||
}
|
||||
|
||||
struct EventBoxCtrlData {
|
||||
// hover controller data
|
||||
onhover_cmd: String,
|
||||
|
||||
@@ -32,6 +32,7 @@ pub enum WidgetNode {
|
||||
Transform { props: Map },
|
||||
EventBox { props: Map, children: Vec<WidgetNode> },
|
||||
ToolTip { props: Map, children: Vec<WidgetNode> },
|
||||
LocalBind { props: Map, children: Vec<WidgetNode> },
|
||||
|
||||
// Top-level macros
|
||||
DefWindow { name: String, props: Map, node: Box<WidgetNode> },
|
||||
@@ -151,6 +152,13 @@ pub fn get_id_to_widget_info<'a>(
|
||||
get_id_to_widget_info(child, id_to_props, Some(id))?;
|
||||
}
|
||||
}
|
||||
WidgetNode::LocalBind { props, children } => {
|
||||
let id = hash_props_and_type(props, "LocalBind");
|
||||
insert_wdgt_info(node, props, "LocalBind", children.as_slice(), parent_id, id_to_props)?;
|
||||
for child in children {
|
||||
get_id_to_widget_info(child, id_to_props, Some(id))?;
|
||||
}
|
||||
}
|
||||
WidgetNode::ColorChooser { props } => {
|
||||
// let id = hash_props_and_type(props, "ColorChooser");
|
||||
insert_wdgt_info(node, props, "ColorChooser", &[], parent_id, id_to_props)?;
|
||||
@@ -227,6 +235,14 @@ pub fn hash_props_and_type(props: &Map, widget_type_str: &str) -> u64 {
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
pub fn hash_props(props: &Map) -> u64 {
|
||||
let mut hasher = AHasher::default();
|
||||
|
||||
props.hash(&mut hasher);
|
||||
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
use crate::ast::WidgetNode;
|
||||
use crate::ast::{WidgetNode, hash_props};
|
||||
use crate::updates::{LocalSignal, LocalDataBinder, register_signal};
|
||||
use rhai::{Array, Engine, EvalAltResult, Map, NativeCallContext};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
|
||||
|
||||
fn unique_id() -> u64 {
|
||||
NEXT_ID.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Converts a Dynamic array into a Vec<WidgetNode>, returning proper errors with position.
|
||||
fn children_to_vec(
|
||||
children: Array,
|
||||
@@ -85,10 +78,11 @@ pub fn register_all_widgets(engine: &mut Engine, all_nodes: &Rc<RefCell<Vec<Widg
|
||||
register_with_children!("stack", Stack);
|
||||
register_with_children!("eventbox", EventBox);
|
||||
register_with_children!("tooltip", ToolTip);
|
||||
register_with_children!("localbind", LocalBind);
|
||||
|
||||
// == Special signal
|
||||
engine.register_fn("localsignal", |props: Map| -> Result<LocalSignal, Box<EvalAltResult>> {
|
||||
let id = unique_id();
|
||||
let id = hash_props(&props);
|
||||
let signal = LocalSignal {
|
||||
id,
|
||||
props,
|
||||
|
||||
@@ -71,6 +71,10 @@ impl WidgetNode {
|
||||
props: with_dyn_id(props.clone(), parent_path),
|
||||
children: process_children(children, parent_path, "tooltip"),
|
||||
},
|
||||
WidgetNode::LocalBind { props, children } => WidgetNode::LocalBind {
|
||||
props: with_dyn_id(props.clone(), parent_path),
|
||||
children: process_children(children, parent_path, "localbind"),
|
||||
},
|
||||
|
||||
// == Top-level container for multiple widgets ==
|
||||
WidgetNode::Enter(children) => {
|
||||
|
||||
@@ -56,6 +56,8 @@ impl ParseConfig {
|
||||
None => Scope::new(),
|
||||
};
|
||||
|
||||
crate::updates::clear_local_signals();
|
||||
|
||||
// Just eval as node will be in `all_nodes`
|
||||
if let Some(ast) = compiled_ast {
|
||||
let _ = self
|
||||
|
||||
@@ -99,6 +99,12 @@ pub fn register_signal(id: u64, signal: Rc<LocalSignal>) {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn clear_local_signals() {
|
||||
LOCAL_SIGNALS.with(|registry| {
|
||||
registry.borrow_mut().clear();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn handle_localsignal_changes() {
|
||||
let shell = get_prefered_shell();
|
||||
let get_string_fn = shared_utils::extract_props::get_string_prop;
|
||||
@@ -111,6 +117,10 @@ pub fn handle_localsignal_changes() {
|
||||
for (id, signal) in registry_ref.iter() {
|
||||
let props = &signal.props;
|
||||
|
||||
if let Ok(initial_str) = get_string_fn(&props, "initial", None) {
|
||||
signal.data.set_value(&initial_str);
|
||||
}
|
||||
|
||||
match get_string_fn(&props, "type", None) {
|
||||
Ok(signal_type) => {
|
||||
match signal_type.to_ascii_lowercase().as_str() {
|
||||
|
||||
Reference in New Issue
Block a user