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