Skip to main content

nightshade_api/
ui.rs

1//! Retained UI: panels anchored to the window that stack their children,
2//! themed buttons, and labels, with click and hover queries. These build on the
3//! same retained UI tree as [`spawn_text`](crate::prelude::spawn_text).
4
5use crate::text::ScreenAnchor;
6use crate::text::ui_root;
7use nightshade::ecs::ui::layout_types::FlowDirection;
8use nightshade::ecs::ui::units::UiValue;
9use nightshade::prelude::*;
10
11/// Turns on immediate-mode egui (requires the `egui` feature). Call it from
12/// setup, then draw each frame by pulling the frame's context with
13/// [`egui_context`](crate::prelude::egui_context) from your update closure or
14/// any system. `egui` and `egui_context` are re-exported from the prelude.
15///
16/// ```ignore
17/// run(
18///     |world| { enable_egui(world); spawn_cube(world, vec3(0.0, 0.5, 0.0)) },
19///     |world, cube| {
20///         if let Some(ctx) = egui_context(world) {
21///             egui::Window::new("Inspector").show(&ctx, |ui| ui.label(format!("{cube:?}")));
22///         }
23///     },
24/// )
25/// .unwrap();
26/// ```
27#[cfg(feature = "egui")]
28pub fn enable_egui(world: &mut World) {
29    world.resources.egui.enabled = true;
30}
31
32/// Spawns an empty panel anchored to a window corner or the center, sized in
33/// pixels. It has a themed translucent background and stacks whatever you add to
34/// it from top to bottom. Fill it with [`panel_button`] and [`panel_label`].
35pub fn spawn_panel(world: &mut World, anchor: ScreenAnchor, width: f32, height: f32) -> Entity {
36    let root = ui_root(world);
37    let (position, anchor_kind) = panel_anchor(anchor);
38    let panel = {
39        let mut tree = UiTreeBuilder::from_parent(world, root);
40        tree.add_node()
41            .window(position, Ab(vec2(width, height)), anchor_kind)
42            .with_rect(8.0, 1.0, Vec4::new(1.0, 1.0, 1.0, 0.1))
43            .color_raw::<UiBase>(Vec4::new(0.05, 0.05, 0.08, 0.85))
44            .flow(FlowDirection::Vertical, 12.0, 8.0)
45            .entity()
46    };
47    ui_mark_render_dirty(world);
48    panel
49}
50
51/// Adds a themed button to a panel and returns it. Poll it every frame with
52/// [`button_clicked`].
53pub fn panel_button(world: &mut World, panel: Entity, text: &str) -> Entity {
54    let button = {
55        let mut tree = UiTreeBuilder::from_parent(world, panel);
56        tree.add_button(text)
57    };
58    ui_mark_render_dirty(world);
59    button
60}
61
62/// Adds a line of text to a panel and returns it. Update it with
63/// [`set_text`](crate::prelude::set_text) and restyle it with
64/// [`set_text_color`](crate::prelude::set_text_color) and
65/// [`set_text_size`](crate::prelude::set_text_size).
66pub fn panel_label(world: &mut World, panel: Entity, text: &str) -> Entity {
67    let label = {
68        let mut tree = UiTreeBuilder::from_parent(world, panel);
69        tree.add_node()
70            .flow_child(Rl(vec2(100.0, 0.0)) + Ab(vec2(0.0, 24.0)))
71            .with_text(text, 18.0)
72            .color_raw::<UiBase>(Vec4::new(1.0, 1.0, 1.0, 1.0))
73            .entity()
74    };
75    ui_mark_render_dirty(world);
76    label
77}
78
79/// Adds a labeled checkbox to a panel and returns it. Read it with
80/// [`checkbox_value`].
81pub fn panel_checkbox(world: &mut World, panel: Entity, label: &str, initial: bool) -> Entity {
82    let entity = {
83        let mut tree = UiTreeBuilder::from_parent(world, panel);
84        tree.add_checkbox(label, initial)
85    };
86    ui_mark_render_dirty(world);
87    entity
88}
89
90/// The checkbox's current on or off value.
91#[inline]
92pub fn checkbox_value(world: &World, checkbox: Entity) -> bool {
93    ui_checkbox_value(world, checkbox).unwrap_or(false)
94}
95
96/// Adds a slider from `min` to `max` starting at `initial` to a panel. Read it
97/// with [`slider_value`], set it with [`set_slider_value`].
98pub fn panel_slider(world: &mut World, panel: Entity, min: f32, max: f32, initial: f32) -> Entity {
99    let entity = {
100        let mut tree = UiTreeBuilder::from_parent(world, panel);
101        tree.add_slider(min, max, initial)
102    };
103    ui_mark_render_dirty(world);
104    entity
105}
106
107/// The slider's current value.
108#[inline]
109pub fn slider_value(world: &World, slider: Entity) -> f32 {
110    ui_slider_value(world, slider).unwrap_or(0.0)
111}
112
113/// Sets the slider's value.
114pub fn set_slider_value(world: &mut World, slider: Entity, value: f32) {
115    ui_slider_set_value(world, slider, value);
116    ui_mark_render_dirty(world);
117}
118
119/// Adds a single line text input with grey `placeholder` text to a panel. Read
120/// edits with [`text_input_changed`].
121pub fn panel_text_input(world: &mut World, panel: Entity, placeholder: &str) -> Entity {
122    let entity = {
123        let mut tree = UiTreeBuilder::from_parent(world, panel);
124        tree.add_text_input(placeholder)
125    };
126    ui_mark_render_dirty(world);
127    entity
128}
129
130/// The input's new contents if it changed this frame, else `None`.
131#[inline]
132pub fn text_input_changed(world: &World, input: Entity) -> Option<String> {
133    ui_text_input_changed(world, input).map(str::to_string)
134}
135
136/// Adds a dropdown of `options` with `initial` selected to a panel. Read it with
137/// [`dropdown_selected`].
138pub fn panel_dropdown(
139    world: &mut World,
140    panel: Entity,
141    options: &[&str],
142    initial: usize,
143) -> Entity {
144    let entity = {
145        let mut tree = UiTreeBuilder::from_parent(world, panel);
146        tree.add_dropdown(options, initial)
147    };
148    ui_mark_render_dirty(world);
149    entity
150}
151
152/// The newly chosen index if the dropdown selection changed this frame.
153#[inline]
154pub fn dropdown_selected(world: &World, dropdown: Entity) -> Option<usize> {
155    ui_dropdown_selected_changed(world, dropdown)
156}
157
158/// Adds a progress bar filled to `initial` (0.0 to 1.0) to a panel. Update it
159/// with [`set_progress`].
160pub fn panel_progress_bar(world: &mut World, panel: Entity, initial: f32) -> Entity {
161    let entity = {
162        let mut tree = UiTreeBuilder::from_parent(world, panel);
163        tree.add_progress_bar(initial)
164    };
165    ui_mark_render_dirty(world);
166    entity
167}
168
169/// Sets a progress bar's fill, 0.0 to 1.0.
170pub fn set_progress(world: &mut World, bar: Entity, value: f32) {
171    ui_progress_bar_set_value(world, bar, value);
172    ui_mark_render_dirty(world);
173}
174
175/// Adds an on or off toggle starting at `initial` to a panel. Read it with
176/// [`toggle_value`].
177pub fn panel_toggle(world: &mut World, panel: Entity, initial: bool) -> Entity {
178    let entity = {
179        let mut tree = UiTreeBuilder::from_parent(world, panel);
180        tree.add_toggle(initial)
181    };
182    ui_mark_render_dirty(world);
183    entity
184}
185
186/// The toggle's current on or off value.
187#[inline]
188pub fn toggle_value(world: &World, toggle: Entity) -> bool {
189    ui_toggle_value(world, toggle).unwrap_or(false)
190}
191
192/// Adds a radio button to a panel, one option of a group identified by
193/// `group_id`. Radios sharing a group are mutually exclusive. Read the group's
194/// choice with [`radio_selected`].
195pub fn panel_radio(
196    world: &mut World,
197    panel: Entity,
198    label: &str,
199    group_id: u32,
200    option_index: usize,
201) -> Entity {
202    let entity = {
203        let mut tree = UiTreeBuilder::from_parent(world, panel);
204        tree.add_radio(label, group_id, option_index)
205    };
206    ui_mark_render_dirty(world);
207    entity
208}
209
210/// The selected option index of a radio group, if any option is selected.
211#[inline]
212pub fn radio_selected(world: &World, group_id: u32) -> Option<usize> {
213    ui_radio_group_value(world, group_id)
214}
215
216/// Adds a dual-handle range slider to a panel, selecting a span from `low` to
217/// `high` within `min` and `max`. Set both handles with [`set_range`].
218pub fn panel_range_slider(
219    world: &mut World,
220    panel: Entity,
221    min: f32,
222    max: f32,
223    low: f32,
224    high: f32,
225) -> Entity {
226    let entity = {
227        let mut tree = UiTreeBuilder::from_parent(world, panel);
228        tree.add_range_slider(min, max, low, high)
229    };
230    ui_mark_render_dirty(world);
231    entity
232}
233
234/// Sets both handles of a range slider.
235pub fn set_range(world: &mut World, slider: Entity, low: f32, high: f32) {
236    ui_range_slider_set_values(world, slider, low, high);
237    ui_mark_render_dirty(world);
238}
239
240/// Adds a tab bar of `labels` to a panel with `initial` selected. Switch the
241/// active tab with [`set_tab`].
242pub fn panel_tabs(world: &mut World, panel: Entity, labels: &[&str], initial: usize) -> Entity {
243    let entity = {
244        let mut tree = UiTreeBuilder::from_parent(world, panel);
245        tree.add_tab_bar(labels, initial)
246    };
247    ui_mark_render_dirty(world);
248    entity
249}
250
251/// Selects a tab bar's active tab by index.
252pub fn set_tab(world: &mut World, tabs: Entity, index: usize) {
253    ui_tab_bar_set_value(world, tabs, index);
254    ui_mark_render_dirty(world);
255}
256
257/// Adds a collapsing section titled `label` to a panel and returns its content
258/// container. Add child widgets to the returned entity; they hide and show as
259/// the header is toggled.
260pub fn panel_collapsing(world: &mut World, panel: Entity, label: &str, open: bool) -> Entity {
261    let entity = {
262        let mut tree = UiTreeBuilder::from_parent(world, panel);
263        tree.add_collapsing_header(label, open)
264    };
265    ui_mark_render_dirty(world);
266    entity
267}
268
269/// Adds a color picker to a panel starting at `initial` linear RGBA. Read the
270/// chosen color with [`color_value`].
271pub fn panel_color_picker(world: &mut World, panel: Entity, initial: [f32; 4]) -> Entity {
272    let entity = {
273        let mut tree = UiTreeBuilder::from_parent(world, panel);
274        tree.add_color_picker(Vec4::new(initial[0], initial[1], initial[2], initial[3]))
275    };
276    ui_mark_render_dirty(world);
277    entity
278}
279
280/// The color picker's current color as linear RGBA.
281pub fn color_value(world: &World, picker: Entity) -> [f32; 4] {
282    ui_color_picker_color(world, picker)
283        .map(|color| [color.x, color.y, color.z, color.w])
284        .unwrap_or([1.0, 1.0, 1.0, 1.0])
285}
286
287/// Whether the button was clicked this frame, a press and release on the same
288/// button. Poll this every frame.
289#[inline]
290pub fn button_clicked(world: &World, button: Entity) -> bool {
291    ui_clicked(world, button)
292}
293
294/// Whether the pointer is currently over the button.
295#[inline]
296pub fn button_hovered(world: &World, button: Entity) -> bool {
297    world
298        .ui
299        .get_ui_node_interaction(button)
300        .is_some_and(|interaction| interaction.hovered)
301}
302
303/// Removes a panel and everything in it.
304#[inline]
305pub fn despawn_panel(world: &mut World, panel: Entity) {
306    despawn_recursive_immediate(world, panel);
307    ui_mark_render_dirty(world);
308}
309
310/// Adds a horizontal row to a panel and returns it. Widgets added to the row
311/// lay out left to right instead of the panel's top to bottom stacking, which is
312/// how you put a label next to a value or a run of buttons on one line.
313pub fn panel_row(world: &mut World, panel: Entity, height: f32) -> Entity {
314    let row = {
315        let mut tree = UiTreeBuilder::from_parent(world, panel);
316        tree.add_node()
317            .flow_child(Rl(vec2(100.0, 0.0)) + Ab(vec2(0.0, height)))
318            .flow(FlowDirection::Horizontal, 0.0, 8.0)
319            .entity()
320    };
321    ui_mark_render_dirty(world);
322    row
323}
324
325/// Adds a fixed-column grid to a panel and returns it. Children flow into
326/// `columns` cells of `row_height`, wrapping to a new row as they fill, for
327/// inventory grids, palettes, and icon sheets.
328pub fn panel_grid(
329    world: &mut World,
330    panel: Entity,
331    columns: usize,
332    row_height: f32,
333    height: f32,
334) -> Entity {
335    let grid = {
336        let mut tree = UiTreeBuilder::from_parent(world, panel);
337        tree.add_node()
338            .flow_child(Rl(vec2(100.0, 0.0)) + Ab(vec2(0.0, height)))
339            .grid(columns, row_height, 0.0, 8.0, 8.0)
340            .entity()
341    };
342    ui_mark_render_dirty(world);
343    grid
344}
345
346/// Adds a scrollable region of the given pixel `height` to a panel and returns
347/// its content container. Add widgets to the returned entity; when they exceed
348/// the height the region scrolls with the mouse wheel and a draggable thumb.
349/// This is what dense inventory and dialogue lists need.
350pub fn panel_scroll(world: &mut World, panel: Entity, height: f32) -> Entity {
351    let content = {
352        let mut tree = UiTreeBuilder::from_parent(world, panel);
353        let area = tree.add_scroll_area(vec2(0.0, height));
354        widget::<UiScrollAreaData>(tree.world_mut(), area)
355            .map(|data| data.content_entity)
356            .unwrap_or(area)
357    };
358    ui_mark_render_dirty(world);
359    content
360}
361
362/// Scrolls a [`panel_scroll`] region (pass the panel-scroll entity, not the
363/// content) to a pixel offset from the top.
364pub fn set_scroll_offset(world: &mut World, scroll_area: Entity, offset: f32) {
365    ui_scroll_area_set_offset(world, scroll_area, offset);
366    ui_mark_render_dirty(world);
367}
368
369/// Sets a widget's keyboard focus order. Widgets with a focus order participate
370/// in Tab navigation, lowest order first. Use it to make a form's fields step
371/// in a sensible sequence.
372pub fn set_focus_order(world: &mut World, entity: Entity, order: i32) {
373    if let Some(interaction) = world.ui.get_ui_node_interaction_mut(entity) {
374        interaction.tab_index = Some(order);
375    }
376}
377
378/// Gives keyboard focus to a widget immediately, as if the user had tabbed to
379/// it. Useful to focus the first field when a form opens.
380pub fn focus_widget(world: &mut World, entity: Entity) {
381    world.resources.retained_ui.interaction.focused_entity = Some(entity);
382}
383
384/// Adds a multi-line text area with grey `placeholder` text and `rows` visible
385/// rows to a panel. Set its contents with [`set_text_area`].
386pub fn panel_text_area(world: &mut World, panel: Entity, placeholder: &str, rows: usize) -> Entity {
387    let entity = {
388        let mut tree = UiTreeBuilder::from_parent(world, panel);
389        tree.add_text_area(placeholder, rows)
390    };
391    ui_mark_render_dirty(world);
392    entity
393}
394
395/// Adds a multi-line text area pre-filled with `initial` to a panel.
396pub fn panel_text_area_with_value(
397    world: &mut World,
398    panel: Entity,
399    placeholder: &str,
400    rows: usize,
401    initial: &str,
402) -> Entity {
403    let entity = {
404        let mut tree = UiTreeBuilder::from_parent(world, panel);
405        tree.add_text_area_with_value(placeholder, rows, initial)
406    };
407    ui_mark_render_dirty(world);
408    entity
409}
410
411/// Replaces a text area's contents.
412pub fn set_text_area(world: &mut World, area: Entity, text: &str) {
413    ui_text_area_set_value(world, area, text);
414    ui_mark_render_dirty(world);
415}
416
417/// Adds a multi-select chip list of `options` to a panel. Set which indices are
418/// chosen with [`set_multi_select`].
419pub fn panel_multi_select(world: &mut World, panel: Entity, options: &[&str]) -> Entity {
420    let entity = {
421        let mut tree = UiTreeBuilder::from_parent(world, panel);
422        tree.add_multi_select(options)
423    };
424    ui_mark_render_dirty(world);
425    entity
426}
427
428/// Sets a multi-select's chosen option indices.
429pub fn set_multi_select(world: &mut World, widget: Entity, indices: &[usize]) {
430    ui_multi_select_set_selected(world, widget, indices);
431    ui_mark_render_dirty(world);
432}
433
434/// Adds a date picker starting at the given date to a panel. Set it later with
435/// [`set_date`].
436pub fn panel_date_picker(
437    world: &mut World,
438    panel: Entity,
439    year: i32,
440    month: u32,
441    day: u32,
442) -> Entity {
443    let entity = {
444        let mut tree = UiTreeBuilder::from_parent(world, panel);
445        tree.add_date_picker(year, month, day)
446    };
447    ui_mark_render_dirty(world);
448    entity
449}
450
451/// Sets a date picker's value.
452pub fn set_date(world: &mut World, picker: Entity, year: i32, month: u32, day: u32) {
453    ui_date_picker_set_value(world, picker, year, month, day);
454    ui_mark_render_dirty(world);
455}
456
457/// Adds a dropdown menu labeled `label` listing `items` to a panel.
458pub fn panel_menu(world: &mut World, panel: Entity, label: &str, items: &[&str]) -> Entity {
459    let entity = {
460        let mut tree = UiTreeBuilder::from_parent(world, panel);
461        tree.add_menu(label, items)
462    };
463    ui_mark_render_dirty(world);
464    entity
465}
466
467/// Adds an HSV color picker starting at `initial` linear RGBA to a panel. Read it
468/// with [`color_value`].
469pub fn panel_color_picker_hsv(world: &mut World, panel: Entity, initial: [f32; 4]) -> Entity {
470    let entity = {
471        let mut tree = UiTreeBuilder::from_parent(world, panel);
472        tree.add_color_picker_hsv(rgba(initial))
473    };
474    ui_mark_render_dirty(world);
475    entity
476}
477
478/// Adds a draggable splitter dividing a panel into two resizable panes at
479/// `initial_ratio` (0.0 to 1.0). Returns the splitter; its two panes are its
480/// children. `direction` is [`SplitDirection::Horizontal`] or `Vertical`.
481pub fn panel_splitter(
482    world: &mut World,
483    panel: Entity,
484    direction: SplitDirection,
485    initial_ratio: f32,
486) -> Entity {
487    let entity = {
488        let mut tree = UiTreeBuilder::from_parent(world, panel);
489        tree.add_splitter(direction, initial_ratio)
490    };
491    ui_mark_render_dirty(world);
492    entity
493}
494
495/// Adds a breadcrumb trail of `segments` to a panel, for showing a path or
496/// navigation hierarchy.
497pub fn panel_breadcrumb(world: &mut World, panel: Entity, segments: &[&str]) -> Entity {
498    let entity = {
499        let mut tree = UiTreeBuilder::from_parent(world, panel);
500        tree.add_breadcrumb(segments)
501    };
502    ui_mark_render_dirty(world);
503    entity
504}
505
506/// Adds a virtual list to a panel that recycles a pool of `pool_size` rows of
507/// `item_height` pixels each, so it scrolls thousands of items cheaply.
508pub fn panel_virtual_list(
509    world: &mut World,
510    panel: Entity,
511    item_height: f32,
512    pool_size: usize,
513) -> Entity {
514    let entity = {
515        let mut tree = UiTreeBuilder::from_parent(world, panel);
516        tree.add_virtual_list(item_height, pool_size)
517    };
518    ui_mark_render_dirty(world);
519    entity
520}
521
522/// Adds a simple table with column `headers` of the given pixel `widths` to a
523/// panel. For large or sortable data use [`panel_data_grid`].
524pub fn panel_table(world: &mut World, panel: Entity, headers: &[&str], widths: &[f32]) -> Entity {
525    let entity = {
526        let mut tree = UiTreeBuilder::from_parent(world, panel);
527        tree.add_table(headers, widths)
528    };
529    ui_mark_render_dirty(world);
530    entity
531}
532
533/// Adds a sortable, selectable data grid to a panel. `columns` are `(header,
534/// width)` pairs and `pool_size` is how many rows to keep instantiated for
535/// recycling. Fill cells with [`set_data_grid_rows`] and [`set_data_grid_cell`],
536/// read clicks with [`data_grid_selection_changed`].
537pub fn panel_data_grid(
538    world: &mut World,
539    panel: Entity,
540    columns: &[(&str, f32)],
541    pool_size: usize,
542) -> Entity {
543    let grid_columns: Vec<DataGridColumn> = columns
544        .iter()
545        .map(|(header, width)| DataGridColumn::new(header, *width))
546        .collect();
547    let entity = {
548        let mut tree = UiTreeBuilder::from_parent(world, panel);
549        tree.add_data_grid(&grid_columns, pool_size)
550    };
551    ui_mark_render_dirty(world);
552    entity
553}
554
555/// Sets a data grid's total row count.
556pub fn set_data_grid_rows(world: &mut World, grid: Entity, count: usize) {
557    ui_data_grid_set_row_count(world, grid, count);
558}
559
560/// Sets the text of a single data grid cell.
561pub fn set_data_grid_cell(world: &mut World, grid: Entity, row: usize, column: usize, text: &str) {
562    ui_data_grid_set_cell(world, grid, row, column, text);
563}
564
565/// Whether the data grid's row selection changed this frame.
566#[inline]
567pub fn data_grid_selection_changed(world: &World, grid: Entity) -> bool {
568    ui_data_grid_selection_changed(world, grid)
569}
570
571/// Adds a command palette to a panel: a searchable overlay of actions opened
572/// from a hotkey, keeping `pool_size` result rows instantiated.
573pub fn panel_command_palette(world: &mut World, panel: Entity, pool_size: usize) -> Entity {
574    let entity = {
575        let mut tree = UiTreeBuilder::from_parent(world, panel);
576        tree.add_command_palette(pool_size)
577    };
578    ui_mark_render_dirty(world);
579    entity
580}
581
582/// Adds a property grid to a panel, a two-column label and value layout for
583/// inspectors. `label_width` is the pixel width of the label column. Add rows
584/// with [`panel_property_row`].
585pub fn panel_property_grid(world: &mut World, panel: Entity, label_width: f32) -> Entity {
586    let entity = {
587        let mut tree = UiTreeBuilder::from_parent(world, panel);
588        tree.add_property_grid(label_width)
589    };
590    ui_mark_render_dirty(world);
591    entity
592}
593
594/// Adds a labeled row to a [`panel_property_grid`] and returns its value cell.
595/// Add the value widget (a slider, text input, and so on) to the returned cell.
596pub fn panel_property_row(world: &mut World, grid: Entity, label: &str) -> Entity {
597    let row = {
598        let mut tree = UiTreeBuilder::from_parent(world, grid);
599        tree.add_property_row(grid, grid, label)
600    };
601    ui_mark_render_dirty(world);
602    row
603}
604
605/// Adds a tree view to a panel. Add root nodes to its [`tree_content`]
606/// container with [`tree_node`], read the selection with [`tree_view_selected`].
607pub fn panel_tree_view(world: &mut World, panel: Entity, multi_select: bool) -> Entity {
608    let entity = {
609        let mut tree = UiTreeBuilder::from_parent(world, panel);
610        tree.add_tree_view(multi_select)
611    };
612    ui_mark_render_dirty(world);
613    entity
614}
615
616/// The root container of a [`panel_tree_view`]; add top-level [`tree_node`]s to
617/// it.
618pub fn tree_content(world: &World, tree_view: Entity) -> Entity {
619    widget::<UiTreeViewData>(world, tree_view)
620        .map(|data| data.content_entity)
621        .unwrap_or(tree_view)
622}
623
624/// Adds a node labeled `label` at `depth` under `parent_container` (a tree's
625/// [`tree_content`] for a root node, or another node's [`tree_node_children`]
626/// for a nested one). `user_data` is an id you choose to recognize the node.
627pub fn tree_node(
628    world: &mut World,
629    tree_view: Entity,
630    parent_container: Entity,
631    label: &str,
632    depth: usize,
633    user_data: u64,
634) -> Entity {
635    let node = {
636        let mut tree = UiTreeBuilder::from_parent(world, parent_container);
637        tree.add_tree_node(tree_view, parent_container, label, depth, user_data)
638    };
639    ui_mark_render_dirty(world);
640    node
641}
642
643/// The container a node's children go into; pass it as `parent_container` to
644/// [`tree_node`] to nest under this node.
645pub fn tree_node_children(world: &World, node: Entity) -> Entity {
646    widget::<UiTreeNodeData>(world, node)
647        .map(|data| data.children_container)
648        .unwrap_or(node)
649}
650
651/// Expands or collapses a [`tree_node`].
652pub fn set_tree_node_expanded(world: &mut World, node: Entity, expanded: bool) {
653    ui_tree_node_set_expanded(world, node, expanded);
654    ui_mark_render_dirty(world);
655}
656
657/// The entities of the currently selected [`tree_node`]s.
658#[inline]
659pub fn tree_view_selected(world: &World, tree_view: Entity) -> Vec<Entity> {
660    ui_tree_view_selected(world, tree_view)
661}
662
663/// Adds a numeric drag value from `min` to `max` starting at `initial` to a
664/// panel: a field whose value scrubs as you drag across it. Read it with
665/// [`drag_value`].
666pub fn panel_drag_value(
667    world: &mut World,
668    panel: Entity,
669    min: f32,
670    max: f32,
671    initial: f32,
672) -> Entity {
673    let entity = {
674        let mut tree = UiTreeBuilder::from_parent(world, panel);
675        tree.add_drag_value(min, max, initial)
676    };
677    ui_mark_render_dirty(world);
678    entity
679}
680
681/// The drag value's current value.
682#[inline]
683pub fn drag_value(world: &World, widget: Entity) -> f32 {
684    ui_drag_value(world, widget).unwrap_or(0.0)
685}
686
687/// Adds a selectable label to a panel. Labels sharing a `group` are mutually
688/// exclusive, like a list selection. Pass `None` for a standalone toggle.
689pub fn panel_selectable(
690    world: &mut World,
691    panel: Entity,
692    text: &str,
693    group: Option<u32>,
694) -> Entity {
695    let entity = {
696        let mut tree = UiTreeBuilder::from_parent(world, panel);
697        tree.add_selectable_label(text, group)
698    };
699    ui_mark_render_dirty(world);
700    entity
701}
702
703/// Adds a centered modal dialog titled `title`, `width` by `height` pixels, to a
704/// panel and returns its content container. Add the dialog's body to the
705/// returned entity; toggle the whole modal with [`set_panel_visible`].
706pub fn panel_modal(
707    world: &mut World,
708    panel: Entity,
709    title: &str,
710    width: f32,
711    height: f32,
712) -> Entity {
713    let entity = {
714        let mut tree = UiTreeBuilder::from_parent(world, panel);
715        tree.add_modal_dialog(title, width, height)
716    };
717    ui_mark_render_dirty(world);
718    entity
719}
720
721/// Adds an animated loading spinner to a panel.
722pub fn panel_spinner(world: &mut World, panel: Entity) -> Entity {
723    let entity = {
724        let mut tree = UiTreeBuilder::from_parent(world, panel);
725        tree.add_spinner()
726    };
727    ui_mark_render_dirty(world);
728    entity
729}
730
731/// Adds a thin horizontal divider line to a panel.
732pub fn panel_separator(world: &mut World, panel: Entity) -> Entity {
733    let entity = {
734        let mut tree = UiTreeBuilder::from_parent(world, panel);
735        tree.add_separator()
736    };
737    ui_mark_render_dirty(world);
738    entity
739}
740
741/// Adds a larger heading-styled line of text to a panel.
742pub fn panel_heading(world: &mut World, panel: Entity, text: &str) -> Entity {
743    let entity = {
744        let mut tree = UiTreeBuilder::from_parent(world, panel);
745        tree.add_heading(text)
746    };
747    ui_mark_render_dirty(world);
748    entity
749}
750
751fn panel_anchor(anchor: ScreenAnchor) -> (UiValue<Vec2>, Anchor) {
752    match anchor {
753        ScreenAnchor::TopLeft => (Ab(vec2(20.0, 20.0)).into(), Anchor::TopLeft),
754        ScreenAnchor::TopCenter => (Rl(vec2(50.0, 0.0)) + Ab(vec2(0.0, 20.0)), Anchor::TopCenter),
755        ScreenAnchor::TopRight => (
756            Rl(vec2(100.0, 0.0)) + Ab(vec2(-20.0, 20.0)),
757            Anchor::TopRight,
758        ),
759        ScreenAnchor::BottomLeft => (
760            Rl(vec2(0.0, 100.0)) + Ab(vec2(20.0, -20.0)),
761            Anchor::BottomLeft,
762        ),
763        ScreenAnchor::BottomCenter => (
764            Rl(vec2(50.0, 100.0)) + Ab(vec2(0.0, -20.0)),
765            Anchor::BottomCenter,
766        ),
767        ScreenAnchor::BottomRight => (
768            Rl(vec2(100.0, 100.0)) + Ab(vec2(-20.0, -20.0)),
769            Anchor::BottomRight,
770        ),
771        ScreenAnchor::Center => (Rl(vec2(50.0, 50.0)).into(), Anchor::Center),
772    }
773}
774
775/// The anchor's relative base position (as a percentage for [`Rl`]) and engine
776/// anchor, with no margin, so a caller can add its own pixel offset.
777fn anchor_base(anchor: ScreenAnchor) -> (Vec2, Anchor) {
778    match anchor {
779        ScreenAnchor::TopLeft => (vec2(0.0, 0.0), Anchor::TopLeft),
780        ScreenAnchor::TopCenter => (vec2(50.0, 0.0), Anchor::TopCenter),
781        ScreenAnchor::TopRight => (vec2(100.0, 0.0), Anchor::TopRight),
782        ScreenAnchor::BottomLeft => (vec2(0.0, 100.0), Anchor::BottomLeft),
783        ScreenAnchor::BottomCenter => (vec2(50.0, 100.0), Anchor::BottomCenter),
784        ScreenAnchor::BottomRight => (vec2(100.0, 100.0), Anchor::BottomRight),
785        ScreenAnchor::Center => (vec2(50.0, 50.0), Anchor::Center),
786    }
787}
788
789fn rgba(color: [f32; 4]) -> Vec4 {
790    Vec4::new(color[0], color[1], color[2], color[3])
791}
792
793const PANEL_BORDER: Vec4 = Vec4::new(0.12, 0.14, 0.2, 0.7);
794const PANEL_SHADOW: Vec4 = Vec4::new(0.0, 0.0, 0.0, 0.55);
795
796/// Spawns a panel anchored to any of the nine screen positions at a pixel
797/// `offset` from that anchor, `size` pixels large, filled with `color`. Unlike
798/// [`spawn_panel`] this gives full control over placement, size, and color for a
799/// custom HUD; fill it with absolutely positioned [`panel_text`], [`panel_box`],
800/// and [`panel_button_at`] children. Returns the panel entity.
801pub fn spawn_panel_at(
802    world: &mut World,
803    anchor: ScreenAnchor,
804    offset: Vec2,
805    size: Vec2,
806    color: [f32; 4],
807) -> Entity {
808    let root = ui_root(world);
809    let (base, anchor_kind) = anchor_base(anchor);
810    let panel = {
811        let mut tree = UiTreeBuilder::from_parent(world, root);
812        tree.add_node()
813            .window(Rl(base) + Ab(offset), Ab(size), anchor_kind)
814            .with_rect(10.0, 1.5, PANEL_BORDER)
815            .color_raw::<UiBase>(rgba(color))
816            .with_shadow(PANEL_SHADOW, vec2(0.0, 6.0), 18.0, 0.0)
817            .entity()
818    };
819    ui_mark_render_dirty(world);
820    panel
821}
822
823/// Adds a text label to `parent` in the pixel rectangle `rect` (`[x, y, width,
824/// height]` from the parent's top left), at `font_size` in `color` with the
825/// given horizontal alignment. Returns the label entity; update it with
826/// [`set_panel_text`] and recolor it with [`set_panel_text_color`].
827pub fn panel_text(
828    world: &mut World,
829    parent: Entity,
830    text: &str,
831    rect: [f32; 4],
832    font_size: f32,
833    color: [f32; 4],
834    align: TextAlignment,
835) -> Entity {
836    let label = {
837        let mut tree = UiTreeBuilder::from_parent(world, parent);
838        tree.add_node()
839            .window(
840                Ab(vec2(rect[0], rect[1])),
841                Ab(vec2(rect[2], rect[3])),
842                Anchor::TopLeft,
843            )
844            .with_text(text, font_size)
845            .with_text_alignment(align, VerticalAlignment::Middle)
846            .color_raw::<UiBase>(rgba(color))
847            .without_pointer_events()
848            .entity()
849    };
850    ui_mark_render_dirty(world);
851    label
852}
853
854/// Adds a solid colored rectangle to `parent` at a pixel `offset`, sized `size`.
855/// Use it for bars, swatches, and dividers. Resize it with [`set_panel_rect`]
856/// and recolor it with [`set_panel_color`].
857pub fn panel_box(
858    world: &mut World,
859    parent: Entity,
860    offset: Vec2,
861    size: Vec2,
862    color: [f32; 4],
863) -> Entity {
864    let box_entity = {
865        let mut tree = UiTreeBuilder::from_parent(world, parent);
866        tree.add_node()
867            .window(Ab(offset), Ab(size), Anchor::TopLeft)
868            .with_rect(6.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
869            .color_raw::<UiBase>(rgba(color))
870            .without_pointer_events()
871            .entity()
872    };
873    ui_mark_render_dirty(world);
874    box_entity
875}
876
877/// Adds an interactive button to `parent` at a pixel `offset`, sized `size`,
878/// with a centered `label` (pass `""` for none and add your own children). It
879/// has themed hover and press states and a selected state for
880/// [`set_panel_selected`]. Poll it with [`button_clicked`]. Returns the button.
881pub fn panel_button_at(
882    world: &mut World,
883    parent: Entity,
884    label: &str,
885    offset: Vec2,
886    size: Vec2,
887) -> Entity {
888    let button = {
889        let mut tree = UiTreeBuilder::from_parent(world, parent);
890        tree.add_node()
891            .window(Ab(offset), Ab(size), Anchor::TopLeft)
892            .with_rect(8.0, 1.5, PANEL_BORDER)
893            .color_raw::<UiBase>(Vec4::new(0.05, 0.06, 0.09, 0.92))
894            .color_raw::<UiHover>(Vec4::new(0.09, 0.11, 0.17, 0.95))
895            .color_raw::<UiPressed>(Vec4::new(0.04, 0.05, 0.08, 1.0))
896            .with_transition::<UiHover>(12.0, 6.0)
897            .with_transition::<UiPressed>(18.0, 10.0)
898            .with_transition::<UiSelected>(10.0, 5.0)
899            .with_interaction()
900            .entity()
901    };
902    if !label.is_empty() {
903        let mut tree = UiTreeBuilder::from_parent(world, button);
904        tree.add_node()
905            .window(Rl(vec2(50.0, 50.0)), Ab(size), Anchor::Center)
906            .with_text(label, 14.0)
907            .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
908            .color_raw::<UiBase>(Vec4::new(0.92, 0.94, 1.0, 1.0))
909            .without_pointer_events()
910            .entity();
911    }
912    ui_mark_render_dirty(world);
913    button
914}
915
916/// Repositions and resizes a UI node within its parent, in pixels. Drives a bar
917/// fill or any element that changes size at runtime.
918pub fn set_panel_rect(world: &mut World, node: Entity, offset: Vec2, size: Vec2) {
919    if let Some(layout) = world.ui.get_ui_layout_node_mut(node) {
920        layout.base_layout = Some(UiLayoutType::Window(WindowLayout {
921            position: Ab(offset).into(),
922            size: Ab(size).into(),
923            anchor: Anchor::TopLeft,
924        }));
925    }
926    ui_mark_render_dirty(world);
927}
928
929/// Sets a UI node's background color (linear RGBA), for panels, boxes, and bars.
930pub fn set_panel_color(world: &mut World, node: Entity, color: [f32; 4]) {
931    if let Some(node_color) = world.ui.get_ui_node_color_mut(node) {
932        node_color.colors[UiBase::INDEX] = Some(rgba(color));
933    }
934    ui_mark_render_dirty(world);
935}
936
937/// Replaces a [`panel_text`] label's content. Skips work when unchanged, so it
938/// is fine to call every frame with a formatted string.
939pub fn set_panel_text(world: &mut World, label: Entity, text: &str) {
940    ui_set_text(world, label, text);
941}
942
943/// Recolors a [`panel_text`] label.
944pub fn set_panel_text_color(world: &mut World, label: Entity, color: [f32; 4]) {
945    set_panel_color(world, label, color);
946}
947
948/// Toggles a button's selected highlight, tinting it with `accent` while
949/// selected. Use it to show the active choice in a row of options.
950pub fn set_panel_selected(world: &mut World, button: Entity, selected: bool, accent: [f32; 4]) {
951    if let Some(node_color) = world.ui.get_ui_node_color_mut(button) {
952        node_color.colors[UiSelected::INDEX] = Some(rgba(accent));
953    }
954    ui_set_selected(world, button, selected);
955    ui_mark_render_dirty(world);
956}
957
958/// Shows or hides a UI node and its children, for toggling overlays like a wave
959/// banner or a game over panel.
960pub fn set_panel_visible(world: &mut World, node: Entity, visible: bool) {
961    ui_set_visible(world, node, visible);
962    ui_mark_render_dirty(world);
963}