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        .get::<nightshade::ecs::ui::components::UiNodeInteraction>(button)
299        .is_some_and(|interaction| interaction.hovered)
300}
301
302/// Removes a panel and everything in it.
303#[inline]
304pub fn despawn_panel(world: &mut World, panel: Entity) {
305    despawn_recursive_immediate(world, panel);
306    ui_mark_render_dirty(world);
307}
308
309/// Adds a horizontal row to a panel and returns it. Widgets added to the row
310/// lay out left to right instead of the panel's top to bottom stacking, which is
311/// how you put a label next to a value or a run of buttons on one line.
312pub fn panel_row(world: &mut World, panel: Entity, height: f32) -> Entity {
313    let row = {
314        let mut tree = UiTreeBuilder::from_parent(world, panel);
315        tree.add_node()
316            .flow_child(Rl(vec2(100.0, 0.0)) + Ab(vec2(0.0, height)))
317            .flow(FlowDirection::Horizontal, 0.0, 8.0)
318            .entity()
319    };
320    ui_mark_render_dirty(world);
321    row
322}
323
324/// Adds a fixed-column grid to a panel and returns it. Children flow into
325/// `columns` cells of `row_height`, wrapping to a new row as they fill, for
326/// inventory grids, palettes, and icon sheets.
327pub fn panel_grid(
328    world: &mut World,
329    panel: Entity,
330    columns: usize,
331    row_height: f32,
332    height: f32,
333) -> Entity {
334    let grid = {
335        let mut tree = UiTreeBuilder::from_parent(world, panel);
336        tree.add_node()
337            .flow_child(Rl(vec2(100.0, 0.0)) + Ab(vec2(0.0, height)))
338            .grid(columns, row_height, 0.0, 8.0, 8.0)
339            .entity()
340    };
341    ui_mark_render_dirty(world);
342    grid
343}
344
345/// Adds a scrollable region of the given pixel `height` to a panel and returns
346/// its content container. Add widgets to the returned entity; when they exceed
347/// the height the region scrolls with the mouse wheel and a draggable thumb.
348/// This is what dense inventory and dialogue lists need.
349pub fn panel_scroll(world: &mut World, panel: Entity, height: f32) -> Entity {
350    let content = {
351        let mut tree = UiTreeBuilder::from_parent(world, panel);
352        let area = tree.add_scroll_area(vec2(0.0, height));
353        widget::<UiScrollAreaData>(tree.world_mut(), area)
354            .map(|data| data.content_entity)
355            .unwrap_or(area)
356    };
357    ui_mark_render_dirty(world);
358    content
359}
360
361/// Scrolls a [`panel_scroll`] region (pass the panel-scroll entity, not the
362/// content) to a pixel offset from the top.
363pub fn set_scroll_offset(world: &mut World, scroll_area: Entity, offset: f32) {
364    ui_scroll_area_set_offset(world, scroll_area, offset);
365    ui_mark_render_dirty(world);
366}
367
368/// Sets a widget's keyboard focus order. Widgets with a focus order participate
369/// in Tab navigation, lowest order first. Use it to make a form's fields step
370/// in a sensible sequence.
371pub fn set_focus_order(world: &mut World, entity: Entity, order: i32) {
372    if let Some(interaction) =
373        world.get_mut::<nightshade::ecs::ui::components::UiNodeInteraction>(entity)
374    {
375        interaction.tab_index = Some(order);
376    }
377}
378
379/// Gives keyboard focus to a widget immediately, as if the user had tabbed to
380/// it. Useful to focus the first field when a form opens.
381pub fn focus_widget(world: &mut World, entity: Entity) {
382    world.resources.retained_ui.interaction.focused_entity = Some(entity);
383}
384
385/// Adds a multi-line text area with grey `placeholder` text and `rows` visible
386/// rows to a panel. Set its contents with [`set_text_area`].
387pub fn panel_text_area(world: &mut World, panel: Entity, placeholder: &str, rows: usize) -> Entity {
388    let entity = {
389        let mut tree = UiTreeBuilder::from_parent(world, panel);
390        tree.add_text_area(placeholder, rows)
391    };
392    ui_mark_render_dirty(world);
393    entity
394}
395
396/// Adds a multi-line text area pre-filled with `initial` to a panel.
397pub fn panel_text_area_with_value(
398    world: &mut World,
399    panel: Entity,
400    placeholder: &str,
401    rows: usize,
402    initial: &str,
403) -> Entity {
404    let entity = {
405        let mut tree = UiTreeBuilder::from_parent(world, panel);
406        tree.add_text_area_with_value(placeholder, rows, initial)
407    };
408    ui_mark_render_dirty(world);
409    entity
410}
411
412/// Replaces a text area's contents.
413pub fn set_text_area(world: &mut World, area: Entity, text: &str) {
414    ui_text_area_set_value(world, area, text);
415    ui_mark_render_dirty(world);
416}
417
418/// Adds a multi-select chip list of `options` to a panel. Set which indices are
419/// chosen with [`set_multi_select`].
420pub fn panel_multi_select(world: &mut World, panel: Entity, options: &[&str]) -> Entity {
421    let entity = {
422        let mut tree = UiTreeBuilder::from_parent(world, panel);
423        tree.add_multi_select(options)
424    };
425    ui_mark_render_dirty(world);
426    entity
427}
428
429/// Sets a multi-select's chosen option indices.
430pub fn set_multi_select(world: &mut World, widget: Entity, indices: &[usize]) {
431    ui_multi_select_set_selected(world, widget, indices);
432    ui_mark_render_dirty(world);
433}
434
435/// Adds a date picker starting at the given date to a panel. Set it later with
436/// [`set_date`].
437pub fn panel_date_picker(
438    world: &mut World,
439    panel: Entity,
440    year: i32,
441    month: u32,
442    day: u32,
443) -> Entity {
444    let entity = {
445        let mut tree = UiTreeBuilder::from_parent(world, panel);
446        tree.add_date_picker(year, month, day)
447    };
448    ui_mark_render_dirty(world);
449    entity
450}
451
452/// Sets a date picker's value.
453pub fn set_date(world: &mut World, picker: Entity, year: i32, month: u32, day: u32) {
454    ui_date_picker_set_value(world, picker, year, month, day);
455    ui_mark_render_dirty(world);
456}
457
458/// Adds a dropdown menu labeled `label` listing `items` to a panel.
459pub fn panel_menu(world: &mut World, panel: Entity, label: &str, items: &[&str]) -> Entity {
460    let entity = {
461        let mut tree = UiTreeBuilder::from_parent(world, panel);
462        tree.add_menu(label, items)
463    };
464    ui_mark_render_dirty(world);
465    entity
466}
467
468/// Adds an HSV color picker starting at `initial` linear RGBA to a panel. Read it
469/// with [`color_value`].
470pub fn panel_color_picker_hsv(world: &mut World, panel: Entity, initial: [f32; 4]) -> Entity {
471    let entity = {
472        let mut tree = UiTreeBuilder::from_parent(world, panel);
473        tree.add_color_picker_hsv(rgba(initial))
474    };
475    ui_mark_render_dirty(world);
476    entity
477}
478
479/// Adds a draggable splitter dividing a panel into two resizable panes at
480/// `initial_ratio` (0.0 to 1.0). Returns the splitter; its two panes are its
481/// children. `direction` is [`SplitDirection::Horizontal`] or `Vertical`.
482pub fn panel_splitter(
483    world: &mut World,
484    panel: Entity,
485    direction: SplitDirection,
486    initial_ratio: f32,
487) -> Entity {
488    let entity = {
489        let mut tree = UiTreeBuilder::from_parent(world, panel);
490        tree.add_splitter(direction, initial_ratio)
491    };
492    ui_mark_render_dirty(world);
493    entity
494}
495
496/// Adds a breadcrumb trail of `segments` to a panel, for showing a path or
497/// navigation hierarchy.
498pub fn panel_breadcrumb(world: &mut World, panel: Entity, segments: &[&str]) -> Entity {
499    let entity = {
500        let mut tree = UiTreeBuilder::from_parent(world, panel);
501        tree.add_breadcrumb(segments)
502    };
503    ui_mark_render_dirty(world);
504    entity
505}
506
507/// Adds a virtual list to a panel that recycles a pool of `pool_size` rows of
508/// `item_height` pixels each, so it scrolls thousands of items cheaply.
509pub fn panel_virtual_list(
510    world: &mut World,
511    panel: Entity,
512    item_height: f32,
513    pool_size: usize,
514) -> Entity {
515    let entity = {
516        let mut tree = UiTreeBuilder::from_parent(world, panel);
517        tree.add_virtual_list(item_height, pool_size)
518    };
519    ui_mark_render_dirty(world);
520    entity
521}
522
523/// Adds a simple table with column `headers` of the given pixel `widths` to a
524/// panel. For large or sortable data use [`panel_data_grid`].
525pub fn panel_table(world: &mut World, panel: Entity, headers: &[&str], widths: &[f32]) -> Entity {
526    let entity = {
527        let mut tree = UiTreeBuilder::from_parent(world, panel);
528        tree.add_table(headers, widths)
529    };
530    ui_mark_render_dirty(world);
531    entity
532}
533
534/// Adds a sortable, selectable data grid to a panel. `columns` are `(header,
535/// width)` pairs and `pool_size` is how many rows to keep instantiated for
536/// recycling. Fill cells with [`set_data_grid_rows`] and [`set_data_grid_cell`],
537/// read clicks with [`data_grid_selection_changed`].
538pub fn panel_data_grid(
539    world: &mut World,
540    panel: Entity,
541    columns: &[(&str, f32)],
542    pool_size: usize,
543) -> Entity {
544    let grid_columns: Vec<DataGridColumn> = columns
545        .iter()
546        .map(|(header, width)| DataGridColumn::new(header, *width))
547        .collect();
548    let entity = {
549        let mut tree = UiTreeBuilder::from_parent(world, panel);
550        tree.add_data_grid(&grid_columns, pool_size)
551    };
552    ui_mark_render_dirty(world);
553    entity
554}
555
556/// Sets a data grid's total row count.
557pub fn set_data_grid_rows(world: &mut World, grid: Entity, count: usize) {
558    ui_data_grid_set_row_count(world, grid, count);
559}
560
561/// Sets the text of a single data grid cell.
562pub fn set_data_grid_cell(world: &mut World, grid: Entity, row: usize, column: usize, text: &str) {
563    ui_data_grid_set_cell(world, grid, row, column, text);
564}
565
566/// Whether the data grid's row selection changed this frame.
567#[inline]
568pub fn data_grid_selection_changed(world: &World, grid: Entity) -> bool {
569    ui_data_grid_selection_changed(world, grid)
570}
571
572/// Adds a command palette to a panel: a searchable overlay of actions opened
573/// from a hotkey, keeping `pool_size` result rows instantiated.
574pub fn panel_command_palette(world: &mut World, panel: Entity, pool_size: usize) -> Entity {
575    let entity = {
576        let mut tree = UiTreeBuilder::from_parent(world, panel);
577        tree.add_command_palette(pool_size)
578    };
579    ui_mark_render_dirty(world);
580    entity
581}
582
583/// Adds a property grid to a panel, a two-column label and value layout for
584/// inspectors. `label_width` is the pixel width of the label column. Add rows
585/// with [`panel_property_row`].
586pub fn panel_property_grid(world: &mut World, panel: Entity, label_width: f32) -> Entity {
587    let entity = {
588        let mut tree = UiTreeBuilder::from_parent(world, panel);
589        tree.add_property_grid(label_width)
590    };
591    ui_mark_render_dirty(world);
592    entity
593}
594
595/// Adds a labeled row to a [`panel_property_grid`] and returns its value cell.
596/// Add the value widget (a slider, text input, and so on) to the returned cell.
597pub fn panel_property_row(world: &mut World, grid: Entity, label: &str) -> Entity {
598    let row = {
599        let mut tree = UiTreeBuilder::from_parent(world, grid);
600        tree.add_property_row(grid, grid, label)
601    };
602    ui_mark_render_dirty(world);
603    row
604}
605
606/// Adds a tree view to a panel. Add root nodes to its [`tree_content`]
607/// container with [`tree_node`], read the selection with [`tree_view_selected`].
608pub fn panel_tree_view(world: &mut World, panel: Entity, multi_select: bool) -> Entity {
609    let entity = {
610        let mut tree = UiTreeBuilder::from_parent(world, panel);
611        tree.add_tree_view(multi_select)
612    };
613    ui_mark_render_dirty(world);
614    entity
615}
616
617/// The root container of a [`panel_tree_view`]; add top-level [`tree_node`]s to
618/// it.
619pub fn tree_content(world: &World, tree_view: Entity) -> Entity {
620    widget::<UiTreeViewData>(world, tree_view)
621        .map(|data| data.content_entity)
622        .unwrap_or(tree_view)
623}
624
625/// Adds a node labeled `label` at `depth` under `parent_container` (a tree's
626/// [`tree_content`] for a root node, or another node's [`tree_node_children`]
627/// for a nested one). `user_data` is an id you choose to recognize the node.
628pub fn tree_node(
629    world: &mut World,
630    tree_view: Entity,
631    parent_container: Entity,
632    label: &str,
633    depth: usize,
634    user_data: u64,
635) -> Entity {
636    let node = {
637        let mut tree = UiTreeBuilder::from_parent(world, parent_container);
638        tree.add_tree_node(tree_view, parent_container, label, depth, user_data)
639    };
640    ui_mark_render_dirty(world);
641    node
642}
643
644/// The container a node's children go into; pass it as `parent_container` to
645/// [`tree_node`] to nest under this node.
646pub fn tree_node_children(world: &World, node: Entity) -> Entity {
647    widget::<UiTreeNodeData>(world, node)
648        .map(|data| data.children_container)
649        .unwrap_or(node)
650}
651
652/// Expands or collapses a [`tree_node`].
653pub fn set_tree_node_expanded(world: &mut World, node: Entity, expanded: bool) {
654    ui_tree_node_set_expanded(world, node, expanded);
655    ui_mark_render_dirty(world);
656}
657
658/// The entities of the currently selected [`tree_node`]s.
659#[inline]
660pub fn tree_view_selected(world: &World, tree_view: Entity) -> Vec<Entity> {
661    ui_tree_view_selected(world, tree_view)
662}
663
664/// Adds a numeric drag value from `min` to `max` starting at `initial` to a
665/// panel: a field whose value scrubs as you drag across it. Read it with
666/// [`drag_value`].
667pub fn panel_drag_value(
668    world: &mut World,
669    panel: Entity,
670    min: f32,
671    max: f32,
672    initial: f32,
673) -> Entity {
674    let entity = {
675        let mut tree = UiTreeBuilder::from_parent(world, panel);
676        tree.add_drag_value(min, max, initial)
677    };
678    ui_mark_render_dirty(world);
679    entity
680}
681
682/// The drag value's current value.
683#[inline]
684pub fn drag_value(world: &World, widget: Entity) -> f32 {
685    ui_drag_value(world, widget).unwrap_or(0.0)
686}
687
688/// Adds a selectable label to a panel. Labels sharing a `group` are mutually
689/// exclusive, like a list selection. Pass `None` for a standalone toggle.
690pub fn panel_selectable(
691    world: &mut World,
692    panel: Entity,
693    text: &str,
694    group: Option<u32>,
695) -> Entity {
696    let entity = {
697        let mut tree = UiTreeBuilder::from_parent(world, panel);
698        tree.add_selectable_label(text, group)
699    };
700    ui_mark_render_dirty(world);
701    entity
702}
703
704/// Adds a centered modal dialog titled `title`, `width` by `height` pixels, to a
705/// panel and returns its content container. Add the dialog's body to the
706/// returned entity; toggle the whole modal with [`set_panel_visible`].
707pub fn panel_modal(
708    world: &mut World,
709    panel: Entity,
710    title: &str,
711    width: f32,
712    height: f32,
713) -> Entity {
714    let entity = {
715        let mut tree = UiTreeBuilder::from_parent(world, panel);
716        tree.add_modal_dialog(title, width, height)
717    };
718    ui_mark_render_dirty(world);
719    entity
720}
721
722/// Adds an animated loading spinner to a panel.
723pub fn panel_spinner(world: &mut World, panel: Entity) -> Entity {
724    let entity = {
725        let mut tree = UiTreeBuilder::from_parent(world, panel);
726        tree.add_spinner()
727    };
728    ui_mark_render_dirty(world);
729    entity
730}
731
732/// Adds a thin horizontal divider line to a panel.
733pub fn panel_separator(world: &mut World, panel: Entity) -> Entity {
734    let entity = {
735        let mut tree = UiTreeBuilder::from_parent(world, panel);
736        tree.add_separator()
737    };
738    ui_mark_render_dirty(world);
739    entity
740}
741
742/// Adds a larger heading-styled line of text to a panel.
743pub fn panel_heading(world: &mut World, panel: Entity, text: &str) -> Entity {
744    let entity = {
745        let mut tree = UiTreeBuilder::from_parent(world, panel);
746        tree.add_heading(text)
747    };
748    ui_mark_render_dirty(world);
749    entity
750}
751
752fn panel_anchor(anchor: ScreenAnchor) -> (UiValue<Vec2>, Anchor) {
753    match anchor {
754        ScreenAnchor::TopLeft => (Ab(vec2(20.0, 20.0)).into(), Anchor::TopLeft),
755        ScreenAnchor::TopCenter => (Rl(vec2(50.0, 0.0)) + Ab(vec2(0.0, 20.0)), Anchor::TopCenter),
756        ScreenAnchor::TopRight => (
757            Rl(vec2(100.0, 0.0)) + Ab(vec2(-20.0, 20.0)),
758            Anchor::TopRight,
759        ),
760        ScreenAnchor::BottomLeft => (
761            Rl(vec2(0.0, 100.0)) + Ab(vec2(20.0, -20.0)),
762            Anchor::BottomLeft,
763        ),
764        ScreenAnchor::BottomCenter => (
765            Rl(vec2(50.0, 100.0)) + Ab(vec2(0.0, -20.0)),
766            Anchor::BottomCenter,
767        ),
768        ScreenAnchor::BottomRight => (
769            Rl(vec2(100.0, 100.0)) + Ab(vec2(-20.0, -20.0)),
770            Anchor::BottomRight,
771        ),
772        ScreenAnchor::Center => (Rl(vec2(50.0, 50.0)).into(), Anchor::Center),
773    }
774}
775
776/// The anchor's relative base position (as a percentage for [`Rl`]) and engine
777/// anchor, with no margin, so a caller can add its own pixel offset.
778fn anchor_base(anchor: ScreenAnchor) -> (Vec2, Anchor) {
779    match anchor {
780        ScreenAnchor::TopLeft => (vec2(0.0, 0.0), Anchor::TopLeft),
781        ScreenAnchor::TopCenter => (vec2(50.0, 0.0), Anchor::TopCenter),
782        ScreenAnchor::TopRight => (vec2(100.0, 0.0), Anchor::TopRight),
783        ScreenAnchor::BottomLeft => (vec2(0.0, 100.0), Anchor::BottomLeft),
784        ScreenAnchor::BottomCenter => (vec2(50.0, 100.0), Anchor::BottomCenter),
785        ScreenAnchor::BottomRight => (vec2(100.0, 100.0), Anchor::BottomRight),
786        ScreenAnchor::Center => (vec2(50.0, 50.0), Anchor::Center),
787    }
788}
789
790fn rgba(color: [f32; 4]) -> Vec4 {
791    Vec4::new(color[0], color[1], color[2], color[3])
792}
793
794const PANEL_BORDER: Vec4 = Vec4::new(0.12, 0.14, 0.2, 0.7);
795const PANEL_SHADOW: Vec4 = Vec4::new(0.0, 0.0, 0.0, 0.55);
796
797/// Spawns a panel anchored to any of the nine screen positions at a pixel
798/// `offset` from that anchor, `size` pixels large, filled with `color`. Unlike
799/// [`spawn_panel`] this gives full control over placement, size, and color for a
800/// custom HUD; fill it with absolutely positioned [`panel_text`], [`panel_box`],
801/// and [`panel_button_at`] children. Returns the panel entity.
802pub fn spawn_panel_at(
803    world: &mut World,
804    anchor: ScreenAnchor,
805    offset: Vec2,
806    size: Vec2,
807    color: [f32; 4],
808) -> Entity {
809    let root = ui_root(world);
810    let (base, anchor_kind) = anchor_base(anchor);
811    let panel = {
812        let mut tree = UiTreeBuilder::from_parent(world, root);
813        tree.add_node()
814            .window(Rl(base) + Ab(offset), Ab(size), anchor_kind)
815            .with_rect(10.0, 1.5, PANEL_BORDER)
816            .color_raw::<UiBase>(rgba(color))
817            .with_shadow(PANEL_SHADOW, vec2(0.0, 6.0), 18.0, 0.0)
818            .entity()
819    };
820    ui_mark_render_dirty(world);
821    panel
822}
823
824/// Adds a text label to `parent` in the pixel rectangle `rect` (`[x, y, width,
825/// height]` from the parent's top left), at `font_size` in `color` with the
826/// given horizontal alignment. Returns the label entity; update it with
827/// [`set_panel_text`] and recolor it with [`set_panel_text_color`].
828pub fn panel_text(
829    world: &mut World,
830    parent: Entity,
831    text: &str,
832    rect: [f32; 4],
833    font_size: f32,
834    color: [f32; 4],
835    align: TextAlignment,
836) -> Entity {
837    let label = {
838        let mut tree = UiTreeBuilder::from_parent(world, parent);
839        tree.add_node()
840            .window(
841                Ab(vec2(rect[0], rect[1])),
842                Ab(vec2(rect[2], rect[3])),
843                Anchor::TopLeft,
844            )
845            .with_text(text, font_size)
846            .with_text_alignment(align, VerticalAlignment::Middle)
847            .color_raw::<UiBase>(rgba(color))
848            .without_pointer_events()
849            .entity()
850    };
851    ui_mark_render_dirty(world);
852    label
853}
854
855/// Adds a solid colored rectangle to `parent` at a pixel `offset`, sized `size`.
856/// Use it for bars, swatches, and dividers. Resize it with [`set_panel_rect`]
857/// and recolor it with [`set_panel_color`].
858pub fn panel_box(
859    world: &mut World,
860    parent: Entity,
861    offset: Vec2,
862    size: Vec2,
863    color: [f32; 4],
864) -> Entity {
865    let box_entity = {
866        let mut tree = UiTreeBuilder::from_parent(world, parent);
867        tree.add_node()
868            .window(Ab(offset), Ab(size), Anchor::TopLeft)
869            .with_rect(6.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
870            .color_raw::<UiBase>(rgba(color))
871            .without_pointer_events()
872            .entity()
873    };
874    ui_mark_render_dirty(world);
875    box_entity
876}
877
878/// Adds an interactive button to `parent` at a pixel `offset`, sized `size`,
879/// with a centered `label` (pass `""` for none and add your own children). It
880/// has themed hover and press states and a selected state for
881/// [`set_panel_selected`]. Poll it with [`button_clicked`]. Returns the button.
882pub fn panel_button_at(
883    world: &mut World,
884    parent: Entity,
885    label: &str,
886    offset: Vec2,
887    size: Vec2,
888) -> Entity {
889    let button = {
890        let mut tree = UiTreeBuilder::from_parent(world, parent);
891        tree.add_node()
892            .window(Ab(offset), Ab(size), Anchor::TopLeft)
893            .with_rect(8.0, 1.5, PANEL_BORDER)
894            .color_raw::<UiBase>(Vec4::new(0.05, 0.06, 0.09, 0.92))
895            .color_raw::<UiHover>(Vec4::new(0.09, 0.11, 0.17, 0.95))
896            .color_raw::<UiPressed>(Vec4::new(0.04, 0.05, 0.08, 1.0))
897            .with_transition::<UiHover>(12.0, 6.0)
898            .with_transition::<UiPressed>(18.0, 10.0)
899            .with_transition::<UiSelected>(10.0, 5.0)
900            .with_interaction()
901            .entity()
902    };
903    if !label.is_empty() {
904        let mut tree = UiTreeBuilder::from_parent(world, button);
905        tree.add_node()
906            .window(Rl(vec2(50.0, 50.0)), Ab(size), Anchor::Center)
907            .with_text(label, 14.0)
908            .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
909            .color_raw::<UiBase>(Vec4::new(0.92, 0.94, 1.0, 1.0))
910            .without_pointer_events()
911            .entity();
912    }
913    ui_mark_render_dirty(world);
914    button
915}
916
917/// Repositions and resizes a UI node within its parent, in pixels. Drives a bar
918/// fill or any element that changes size at runtime.
919pub fn set_panel_rect(world: &mut World, node: Entity, offset: Vec2, size: Vec2) {
920    if let Some(layout) = world.get_mut::<nightshade::ecs::ui::components::UiLayoutNode>(node) {
921        layout.base_layout = Some(UiLayoutType::Window(WindowLayout {
922            position: Ab(offset).into(),
923            size: Ab(size).into(),
924            anchor: Anchor::TopLeft,
925        }));
926    }
927    ui_mark_render_dirty(world);
928}
929
930/// Sets a UI node's background color (linear RGBA), for panels, boxes, and bars.
931pub fn set_panel_color(world: &mut World, node: Entity, color: [f32; 4]) {
932    if let Some(node_color) = world.get_mut::<nightshade::ecs::ui::components::UiNodeColor>(node) {
933        node_color.colors[UiBase::INDEX] = Some(rgba(color));
934    }
935    ui_mark_render_dirty(world);
936}
937
938/// Replaces a [`panel_text`] label's content. Skips work when unchanged, so it
939/// is fine to call every frame with a formatted string.
940pub fn set_panel_text(world: &mut World, label: Entity, text: &str) {
941    ui_set_text(world, label, text);
942}
943
944/// Recolors a [`panel_text`] label.
945pub fn set_panel_text_color(world: &mut World, label: Entity, color: [f32; 4]) {
946    set_panel_color(world, label, color);
947}
948
949/// Toggles a button's selected highlight, tinting it with `accent` while
950/// selected. Use it to show the active choice in a row of options.
951pub fn set_panel_selected(world: &mut World, button: Entity, selected: bool, accent: [f32; 4]) {
952    if let Some(node_color) = world.get_mut::<nightshade::ecs::ui::components::UiNodeColor>(button)
953    {
954        node_color.colors[UiSelected::INDEX] = Some(rgba(accent));
955    }
956    ui_set_selected(world, button, selected);
957    ui_mark_render_dirty(world);
958}
959
960/// Shows or hides a UI node and its children, for toggling overlays like a wave
961/// banner or a game over panel.
962pub fn set_panel_visible(world: &mut World, node: Entity, visible: bool) {
963    ui_set_visible(world, node, visible);
964    ui_mark_render_dirty(world);
965}