Skip to content

Commit ae97fd4

Browse files
authored
Merge pull request #5 from Ivy-Interactive/tendril/00225-AutomaticWidgetIdAssignmentInRustyFramework
[00225] Automatic widget ID assignment in Rusty-Framework
2 parents fa63d8f + 27a95f9 commit ae97fd4

13 files changed

Lines changed: 552 additions & 1 deletion

File tree

rusty-macros/src/lib.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,38 @@ pub fn derive_widget(input: TokenStream) -> TokenStream {
9999
quote! {}
100100
};
101101

102+
let assign_id_impl = if has_id_field {
103+
quote! {
104+
fn assign_id(&mut self, id: String) {
105+
self.id = Some(id);
106+
}
107+
108+
fn get_id(&self) -> Option<&str> {
109+
self.id.as_deref()
110+
}
111+
}
112+
} else {
113+
quote! {
114+
fn assign_id(&mut self, _id: String) {}
115+
fn get_id(&self) -> Option<&str> { None }
116+
}
117+
};
118+
119+
// Detect children field (Vec<Element>) for container widgets
120+
let has_children_field = fields
121+
.iter()
122+
.any(|f| f.ident.as_ref().is_some_and(|i| i == "children"));
123+
124+
let children_mut_impl = if has_children_field {
125+
quote! {
126+
fn children_mut(&mut self) -> Option<&mut Vec<crate::views::view::Element>> {
127+
Some(&mut self.children)
128+
}
129+
}
130+
} else {
131+
quote! {}
132+
};
133+
102134
let expanded = quote! {
103135
impl crate::views::view::WidgetData for #name {
104136
fn widget_type(&self) -> &str {
@@ -117,6 +149,9 @@ pub fn derive_widget(input: TokenStream) -> TokenStream {
117149
fn clone_box(&self) -> Box<dyn crate::views::view::WidgetData> {
118150
Box::new(self.clone())
119151
}
152+
153+
#assign_id_impl
154+
#children_mut_impl
120155
}
121156
};
122157

rusty/src/core/runtime.rs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ impl Runtime {
6666
let store = self.hook_stores.entry(self.root_view_id).or_default();
6767
let mut ctx = BuildContext::new(store, Some(self.rebuild_tx.clone()));
6868
ctx.reset();
69-
let element = self.root.build(&mut ctx);
69+
let mut element = self.root.build(&mut ctx);
70+
71+
// Automatic widget ID assignment: walk the tree and assign IDs
72+
// to any widgets that don't already have one, and register their events.
73+
// This mirrors Ivy Framework's WidgetTree.BuildWidget() pattern.
74+
element.assign_ids(&mut ctx);
7075

7176
// Extract the event registry populated during build
7277
let registry = ctx.take_event_registry();
@@ -167,6 +172,79 @@ mod tests {
167172
assert!(json.to_string().contains("Hello from runtime"));
168173
}
169174

175+
#[tokio::test]
176+
async fn test_runtime_build_assigns_ids_automatically() {
177+
use crate::widgets::layout::Layout;
178+
179+
struct AutoIdView;
180+
181+
impl View for AutoIdView {
182+
fn build(&self, _ctx: &mut BuildContext) -> Element {
183+
Layout::vertical()
184+
.child(TextBlock::new("First"))
185+
.child(TextBlock::new("Second"))
186+
.child(Button::new("Click"))
187+
.into()
188+
}
189+
}
190+
191+
let mut runtime = Runtime::new(AutoIdView);
192+
let tree = runtime.build().await;
193+
let json = serde_json::to_value(&tree).unwrap();
194+
let json_str = json.to_string();
195+
196+
// All widgets should have auto-assigned IDs
197+
assert!(json_str.contains("\"id\":\"w-0\"")); // Layout
198+
assert!(json_str.contains("\"id\":\"w-1\"")); // TextBlock "First"
199+
assert!(json_str.contains("\"id\":\"w-2\"")); // TextBlock "Second"
200+
assert!(json_str.contains("\"id\":\"w-3\"")); // Button
201+
}
202+
203+
#[tokio::test]
204+
async fn test_runtime_auto_id_registers_events() {
205+
let clicked = Arc::new(AtomicBool::new(false));
206+
let clicked_clone = clicked.clone();
207+
208+
struct AutoClickView {
209+
clicked: Arc<AtomicBool>,
210+
}
211+
212+
impl View for AutoClickView {
213+
fn build(&self, _ctx: &mut BuildContext) -> Element {
214+
let clicked = self.clicked.clone();
215+
// No .build(ctx) call — relies on automatic ID assignment
216+
Button::new("Auto Click")
217+
.on_click(move || {
218+
clicked.store(true, Ordering::SeqCst);
219+
})
220+
.into()
221+
}
222+
}
223+
224+
let mut runtime = Runtime::new(AutoClickView {
225+
clicked: clicked_clone.clone(),
226+
});
227+
228+
let _ = runtime.build().await;
229+
230+
// Send click event to the auto-assigned ID
231+
let tx = runtime.event_sender();
232+
tx.send(RuntimeMessage::Event {
233+
widget_id: "w-0".to_string(),
234+
event_name: "click".to_string(),
235+
args: serde_json::Value::Null,
236+
})
237+
.await
238+
.unwrap();
239+
240+
tokio::spawn(async move {
241+
runtime.run().await;
242+
});
243+
244+
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
245+
assert!(clicked_clone.load(Ordering::SeqCst));
246+
}
247+
170248
#[tokio::test]
171249
async fn test_button_click_dispatched() {
172250
let clicked = Arc::new(AtomicBool::new(false));

0 commit comments

Comments
 (0)