Skip to content

Commit df2b454

Browse files
refactor(ui): migrate all push_parent/pop_parent pairs to in_parent closures
269 hand-balanced parent stack mutations across 22 files become closure- scoped tree.in_parent(entity, |tree| { ... }) calls. The closures are impossible to mis-balance, eliminate the debug-build cycle panic surface that bit us earlier, and make the structure of each builder visually obvious through nesting. Touches gallery, showcase, editor (top_bar/inspector/tree/materials/ browsers/overlays/status/viewport_controls/detached_window), game (title/pause/hud/confirm_quit/settings_screen), jukebox panel, mosaic window_ui, picking, decals, fireworks, navmesh. Also collapses several Rc<RefCell<Cell<Entity>>> closure-capture patterns into plain mut bindings, drops dead let _ = var; suppression lines, and removes the unused TRANSPARENT constant in jukebox theme.
1 parent 88953ad commit df2b454

23 files changed

Lines changed: 2912 additions & 2923 deletions

File tree

apps/decals/src/main.rs

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -387,30 +387,38 @@ impl DecalsDemo {
387387
wrap: false,
388388
});
389389
}
390-
tree.push_parent(content);
391-
392-
tree.add_label("Current Decal Type");
393-
let type_label = tree.add_label(initial_type_name);
394-
395-
tree.add_label("Decal Size");
396-
let decal_size = tree.add_slider(0.2, 5.0, initial_decal_size);
397-
398-
let emissive_label = tree.add_label("Emissive Strength");
399-
let emissive_strength = tree.add_slider(0.0, 10.0, initial_emissive_strength);
400-
401-
let show_preview = tree.add_checkbox("Show Preview", initial_show_preview);
402-
let use_awesomeface = tree.add_checkbox("Use Awesomeface Texture", initial_use_awesomeface);
403-
404-
let active_label = tree.add_label("Active Decals: 0");
405-
let clear_button = tree.add_button("Clear All Decals");
406-
407-
tree.add_label("");
408-
tree.add_label("Bloom");
409-
let bloom_enabled = tree.add_checkbox("Enable Bloom", bloom_enabled_initial);
410-
tree.add_label("Intensity");
411-
let bloom_intensity = tree.add_slider(0.0, 2.0, bloom_intensity_initial);
412-
413-
tree.pop_parent();
390+
let mut type_label = Entity::default();
391+
let mut decal_size = Entity::default();
392+
let mut emissive_label = Entity::default();
393+
let mut emissive_strength = Entity::default();
394+
let mut show_preview = Entity::default();
395+
let mut use_awesomeface = Entity::default();
396+
let mut active_label = Entity::default();
397+
let mut clear_button = Entity::default();
398+
let mut bloom_enabled = Entity::default();
399+
let mut bloom_intensity = Entity::default();
400+
tree.in_parent(content, |tree| {
401+
tree.add_label("Current Decal Type");
402+
type_label = tree.add_label(initial_type_name);
403+
404+
tree.add_label("Decal Size");
405+
decal_size = tree.add_slider(0.2, 5.0, initial_decal_size);
406+
407+
emissive_label = tree.add_label("Emissive Strength");
408+
emissive_strength = tree.add_slider(0.0, 10.0, initial_emissive_strength);
409+
410+
show_preview = tree.add_checkbox("Show Preview", initial_show_preview);
411+
use_awesomeface = tree.add_checkbox("Use Awesomeface Texture", initial_use_awesomeface);
412+
413+
active_label = tree.add_label("Active Decals: 0");
414+
clear_button = tree.add_button("Clear All Decals");
415+
416+
tree.add_label("");
417+
tree.add_label("Bloom");
418+
bloom_enabled = tree.add_checkbox("Enable Bloom", bloom_enabled_initial);
419+
tree.add_label("Intensity");
420+
bloom_intensity = tree.add_slider(0.0, 2.0, bloom_intensity_initial);
421+
});
414422
tree.finish();
415423

416424
ui_set_visible(world, panel, self.show_controls);

apps/editor/src/systems/retained_ui/browsers.rs

Lines changed: 109 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,14 @@ fn build_khronos(tree: &mut UiTreeBuilder) -> KhronosHandles {
8282
let panel = tree.add_floating_panel("khronos_browser", "Khronos sample assets", KHRONOS_RECT);
8383
ui_set_visible(tree.world_mut(), panel, false);
8484
let content = super::panel_content(tree, panel);
85-
tree.push_parent(content);
86-
87-
let filter_input = tree.add_text_input("Search assets");
88-
let status_label = browser_status_label(tree, "Loading index...");
89-
let grid_root = browser_grid_root(tree);
90-
91-
tree.pop_parent();
85+
let mut filter_input = Entity::default();
86+
let mut status_label = Entity::default();
87+
let mut grid_root = Entity::default();
88+
tree.in_parent(content, |tree| {
89+
filter_input = tree.add_text_input("Search assets");
90+
status_label = browser_status_label(tree, "Loading index...");
91+
grid_root = browser_grid_root(tree);
92+
});
9293

9394
KhronosHandles {
9495
panel,
@@ -106,9 +107,7 @@ fn browser_grid_root(tree: &mut UiTreeBuilder) -> Entity {
106107
.flex_grow(1.0)
107108
.without_pointer_events()
108109
.entity();
109-
tree.push_parent(wrapper);
110-
let scroll = tree.add_scroll_area_fill(8.0, 8.0);
111-
tree.pop_parent();
110+
let scroll = tree.in_parent(wrapper, |tree| tree.add_scroll_area_fill(8.0, 8.0));
112111
widget::<UiScrollAreaData>(tree.world_mut(), scroll)
113112
.map(|d| d.content_entity)
114113
.unwrap_or(scroll)
@@ -118,26 +117,26 @@ fn build_polyhaven(tree: &mut UiTreeBuilder) -> PolyhavenHandles {
118117
let panel = tree.add_floating_panel("polyhaven_browser", "Polyhaven", POLYHAVEN_RECT);
119118
ui_set_visible(tree.world_mut(), panel, false);
120119
let content = super::panel_content(tree, panel);
121-
tree.push_parent(content);
122-
123-
let tab_bar = tree.add_tab_bar(&["HDRIs", "Models"], 0);
124-
let filter_input = tree.add_text_input("Search Polyhaven");
125-
let status = browser_status_label(tree, "Loading index...");
126-
127-
let wrapper = tree
128-
.add_node()
129-
.fill_width()
130-
.flex_grow(1.0)
131-
.without_pointer_events()
132-
.entity();
133-
tree.push_parent(wrapper);
134-
let scroll = tree.add_scroll_area_fill(4.0, 2.0);
135-
tree.pop_parent();
136-
let list_root = widget::<UiScrollAreaData>(tree.world_mut(), scroll)
137-
.map(|d| d.content_entity)
138-
.unwrap_or(scroll);
139-
140-
tree.pop_parent();
120+
let mut tab_bar = Entity::default();
121+
let mut filter_input = Entity::default();
122+
let mut status = Entity::default();
123+
let mut list_root = Entity::default();
124+
tree.in_parent(content, |tree| {
125+
tab_bar = tree.add_tab_bar(&["HDRIs", "Models"], 0);
126+
filter_input = tree.add_text_input("Search Polyhaven");
127+
status = browser_status_label(tree, "Loading index...");
128+
129+
let wrapper = tree
130+
.add_node()
131+
.fill_width()
132+
.flex_grow(1.0)
133+
.without_pointer_events()
134+
.entity();
135+
let scroll = tree.in_parent(wrapper, |tree| tree.add_scroll_area_fill(4.0, 2.0));
136+
list_root = widget::<UiScrollAreaData>(tree.world_mut(), scroll)
137+
.map(|d| d.content_entity)
138+
.unwrap_or(scroll);
139+
});
141140

142141
PolyhavenHandles {
143142
panel,
@@ -154,7 +153,6 @@ fn build_sketchfab(tree: &mut UiTreeBuilder) -> SketchfabHandles {
154153
let panel = tree.add_floating_panel("sketchfab_browser", "Sketchfab", SKETCHFAB_RECT);
155154
ui_set_visible(tree.world_mut(), panel, false);
156155
let content = super::panel_content(tree, panel);
157-
tree.push_parent(content);
158156

159157
let theme = tree
160158
.world_mut()
@@ -166,50 +164,56 @@ fn build_sketchfab(tree: &mut UiTreeBuilder) -> SketchfabHandles {
166164
let text_color = theme.text_color;
167165
let dim = vec4(0.7, 0.7, 0.75, 1.0);
168166

169-
tree.add_node()
170-
.size(100.pct(), (18.0).px())
171-
.with_text("Sketchfab API token", font * 0.85)
172-
.text_left()
173-
.with_color::<UiBase>(dim)
174-
.without_pointer_events()
175-
.entity();
176-
let token_input = tree.add_text_input("paste your API token");
177-
ui_text_input_set_password(tree.world_mut(), token_input, true);
178-
179-
let link_color = vec4(0.49, 0.71, 0.96, 1.0);
180-
let link_hover = vec4(0.66, 0.84, 1.0, 1.0);
181-
let token_link = tree
182-
.add_node()
183-
.size(100.pct(), (18.0).px())
184-
.with_text("Get a token at sketchfab.com/settings/password", font * 0.8)
185-
.text_left()
186-
.with_color::<UiBase>(link_color)
187-
.with_color::<UiHover>(link_hover)
188-
.with_interaction()
189-
.with_cursor_icon(winit::window::CursorIcon::Pointer)
190-
.entity();
191-
192-
tree.add_node()
193-
.size(100.pct(), (18.0).px())
194-
.with_text("Model URL", font * 0.85)
195-
.text_left()
196-
.with_color::<UiBase>(dim)
197-
.without_pointer_events()
198-
.entity();
199-
let url_input = tree.add_text_input("https://sketchfab.com/3d-models/...");
200-
201-
let fetch_button = tree.add_button("Fetch");
202-
203-
let status_label = tree
204-
.add_node()
205-
.size(100.pct(), (18.0).px())
206-
.with_text("idle", font * 0.85)
207-
.text_left()
208-
.with_color::<UiBase>(text_color)
209-
.without_pointer_events()
210-
.entity();
211-
212-
tree.pop_parent();
167+
let mut token_input = Entity::default();
168+
let mut url_input = Entity::default();
169+
let mut fetch_button = Entity::default();
170+
let mut status_label = Entity::default();
171+
let mut token_link = Entity::default();
172+
173+
tree.in_parent(content, |tree| {
174+
tree.add_node()
175+
.size(100.pct(), (18.0).px())
176+
.with_text("Sketchfab API token", font * 0.85)
177+
.text_left()
178+
.with_color::<UiBase>(dim)
179+
.without_pointer_events()
180+
.entity();
181+
token_input = tree.add_text_input("paste your API token");
182+
ui_text_input_set_password(tree.world_mut(), token_input, true);
183+
184+
let link_color = vec4(0.49, 0.71, 0.96, 1.0);
185+
let link_hover = vec4(0.66, 0.84, 1.0, 1.0);
186+
token_link = tree
187+
.add_node()
188+
.size(100.pct(), (18.0).px())
189+
.with_text("Get a token at sketchfab.com/settings/password", font * 0.8)
190+
.text_left()
191+
.with_color::<UiBase>(link_color)
192+
.with_color::<UiHover>(link_hover)
193+
.with_interaction()
194+
.with_cursor_icon(winit::window::CursorIcon::Pointer)
195+
.entity();
196+
197+
tree.add_node()
198+
.size(100.pct(), (18.0).px())
199+
.with_text("Model URL", font * 0.85)
200+
.text_left()
201+
.with_color::<UiBase>(dim)
202+
.without_pointer_events()
203+
.entity();
204+
url_input = tree.add_text_input("https://sketchfab.com/3d-models/...");
205+
206+
fetch_button = tree.add_button("Fetch");
207+
208+
status_label = tree
209+
.add_node()
210+
.size(100.pct(), (18.0).px())
211+
.with_text("idle", font * 0.85)
212+
.text_left()
213+
.with_color::<UiBase>(text_color)
214+
.without_pointer_events()
215+
.entity();
216+
});
213217

214218
SketchfabHandles {
215219
panel,
@@ -318,33 +322,31 @@ fn build_thumbnail_card(tree: &mut UiTreeBuilder, label_text: &str) -> Thumbnail
318322
.with_cursor_icon(winit::window::CursorIcon::Pointer)
319323
.entity();
320324

321-
tree.push_parent(card);
322-
323-
let slot = tree
324-
.add_node()
325-
.size((124.0).px(), (84.0).px())
326-
.without_pointer_events()
327-
.entity();
328-
329-
tree.push_parent(slot);
330-
let image = tree
331-
.add_node()
332-
.solid(Ab(vec2(1.0, 1.0)), ScalingMode::Fit, vec2(0.0, 0.0))
333-
.with_color::<UiBase>(vec4(1.0, 1.0, 1.0, 1.0))
334-
.without_pointer_events()
335-
.entity();
336-
tree.pop_parent();
337-
338-
tree.add_node()
339-
.size((124.0).px(), (24.0).px())
340-
.with_text(label_text, font * 0.75)
341-
.text_center()
342-
.with_text_overflow(TextOverflow::Clip)
343-
.with_color::<UiBase>(text_color)
344-
.without_pointer_events()
345-
.entity();
325+
let mut image = Entity::default();
326+
tree.in_parent(card, |tree| {
327+
let slot = tree
328+
.add_node()
329+
.size((124.0).px(), (84.0).px())
330+
.without_pointer_events()
331+
.entity();
346332

347-
tree.pop_parent();
333+
image = tree.in_parent(slot, |tree| {
334+
tree.add_node()
335+
.solid(Ab(vec2(1.0, 1.0)), ScalingMode::Fit, vec2(0.0, 0.0))
336+
.with_color::<UiBase>(vec4(1.0, 1.0, 1.0, 1.0))
337+
.without_pointer_events()
338+
.entity()
339+
});
340+
341+
tree.add_node()
342+
.size((124.0).px(), (24.0).px())
343+
.with_text(label_text, font * 0.75)
344+
.text_center()
345+
.with_text_overflow(TextOverflow::Clip)
346+
.with_color::<UiBase>(text_color)
347+
.without_pointer_events()
348+
.entity();
349+
});
348350
ThumbnailCard { card, image }
349351
}
350352

@@ -539,12 +541,12 @@ fn update_khronos(viewer_world: &mut ViewerWorld, world: &mut World) {
539541
{
540542
let mut tree_builder = UiTreeBuilder::from_parent(world, grid_root);
541543
let grid = build_card_grid(&mut tree_builder);
542-
tree_builder.push_parent(grid);
543-
for label in &card_labels {
544-
let card = build_thumbnail_card(&mut tree_builder, label);
545-
new_cards.push(card);
546-
}
547-
tree_builder.pop_parent();
544+
tree_builder.in_parent(grid, |tree_builder| {
545+
for label in &card_labels {
546+
let card = build_thumbnail_card(tree_builder, label);
547+
new_cards.push(card);
548+
}
549+
});
548550
tree_builder.finish_subtree();
549551
}
550552
let lookup = &mut viewer_world.resources.ui_handles.browsers.khronos.lookup;

apps/editor/src/systems/retained_ui/detached_window.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,14 @@ pub fn build(world: &mut World, window_index: usize, camera: Entity) -> Detached
101101
.with_depth(UiDepthMode::Set(15.0))
102102
.flow(FlowDirection::Horizontal, 0.0, 0.0)
103103
.entity();
104-
tree.push_parent(restore_button);
105-
tree.add_node()
106-
.fill()
107-
.with_icon(lookup.arrow_back, 12.0)
108-
.with_color::<UiBase>(ICON_COLOR)
109-
.without_pointer_events()
110-
.entity();
111-
tree.pop_parent();
104+
tree.in_parent(restore_button, |tree| {
105+
tree.add_node()
106+
.fill()
107+
.with_icon(lookup.arrow_back, 12.0)
108+
.with_color::<UiBase>(ICON_COLOR)
109+
.without_pointer_events()
110+
.entity();
111+
});
112112

113113
let controls = viewport_controls::build(&mut tree, camera);
114114

0 commit comments

Comments
 (0)