nightshade-api 0.53.0

Procedural high level API for the nightshade game engine
Documentation
//! A gallery of the retained-UI widgets beyond the basics shown in grand_tour:
//! tabs, trees of containers, tables and grids, text entry, pickers, and the
//! layout primitives that arrange them, with a few values polled live into a HUD.

use nightshade_api::prelude::*;

const RADIO_GROUP: u32 = 1;

struct Gallery {
    radio_label: Entity,
    text_input: Entity,
    drag: Entity,
    data_grid: Entity,
    hud: Entity,
}

fn main() {
    run(setup, update).unwrap();
}

fn setup(world: &mut World) -> Gallery {
    set_window_title(world, "Widget gallery");
    set_background(world, Background::Nebula);

    let left = spawn_panel(world, ScreenAnchor::TopLeft, 360.0, 620.0);
    panel_heading(world, left, "Inputs");
    panel_breadcrumb(world, left, &["Home", "Widgets", "Inputs"]);
    panel_separator(world, left);

    let tabs = panel_tabs(world, left, &["Form", "Pickers", "Layout"], 0);
    set_tab(world, tabs, 0);

    let scroll = panel_scroll(world, left, 460.0);
    set_scroll_offset(world, scroll, 0.0);

    let radio_label = panel_label(world, scroll, "Difficulty: none");
    panel_radio(world, scroll, "Easy", RADIO_GROUP, 0);
    panel_radio(world, scroll, "Normal", RADIO_GROUP, 1);
    panel_radio(world, scroll, "Hard", RADIO_GROUP, 2);
    panel_separator(world, scroll);

    panel_label(world, scroll, "Volume range");
    let range_slider = panel_range_slider(world, scroll, 0.0, 1.0, 0.2, 0.8);
    set_range(world, range_slider, 0.25, 0.75);

    let slider = panel_slider(world, scroll, 0.0, 100.0, 50.0);
    set_slider_value(world, slider, 70.0);

    let drag = panel_drag_value(world, scroll, 0.0, 10.0, 3.0);

    let text_input = panel_text_input(world, scroll, "Player name");
    set_focus_order(world, text_input, 0);
    focus_widget(world, text_input);

    let text_area = panel_text_area_with_value(world, scroll, "Notes", 3, "First note");
    set_text_area(world, text_area, "Welcome to the gallery");
    panel_text_area(world, scroll, "Scratch", 2);

    let multi = panel_multi_select(world, scroll, &["Fire", "Ice", "Wind", "Earth"]);
    set_multi_select(world, multi, &[0, 2]);

    let date = panel_date_picker(world, scroll, 2026, 6, 27);
    set_date(world, date, 2026, 7, 1);

    panel_menu(world, scroll, "Actions", &["New", "Open", "Save"]);
    panel_selectable(world, scroll, "Selectable A", Some(2));
    panel_selectable(world, scroll, "Selectable B", Some(2));
    panel_spinner(world, scroll);

    let collapsing = panel_collapsing(world, scroll, "Advanced", false);
    panel_label(world, collapsing, "Hidden until expanded");
    panel_color_picker_hsv(world, collapsing, [0.4, 0.7, 0.9, 1.0]);

    let right = spawn_panel(world, ScreenAnchor::TopRight, 380.0, 620.0);
    panel_heading(world, right, "Data");

    let grid_row = panel_row(world, right, 28.0);
    panel_label(world, grid_row, "Two");
    panel_label(world, grid_row, "columns");

    let swatches = panel_grid(world, right, 4, 24.0, 56.0);
    for index in 0..8 {
        let shade = index as f32 / 8.0;
        panel_box(
            world,
            swatches,
            vec2(0.0, 0.0),
            vec2(24.0, 24.0),
            [shade, 0.4, 1.0 - shade, 1.0],
        );
    }

    let property_grid = panel_property_grid(world, right, 90.0);
    let health_row = panel_property_row(world, property_grid, "Health");
    panel_label(world, health_row, "100");
    let mana_row = panel_property_row(world, property_grid, "Mana");
    panel_label(world, mana_row, "45");

    panel_table(world, right, &["Name", "Score"], &[140.0, 80.0]);

    let split = panel_splitter(world, right, SplitDirection::Horizontal, 0.5);
    panel_label(world, split, "Resizable split panes");

    let data_grid = panel_data_grid(world, right, &[("Item", 160.0), ("Count", 80.0)], 8);
    set_data_grid_rows(world, data_grid, 3);
    for row in 0..3 {
        set_data_grid_cell(world, data_grid, row, 0, &format!("Loot {row}"));
        set_data_grid_cell(world, data_grid, row, 1, &format!("{}", row * 5));
    }

    let virtual_list = panel_virtual_list(world, right, 22.0, 12);
    panel_label(world, virtual_list, "Recycling rows");

    panel_command_palette(world, right, 6);

    let modal = panel_modal(world, right, "Confirm", 220.0, 100.0);
    panel_label(world, modal, "A centered dialog");
    panel_button(world, modal, "OK");

    let hud_panel = spawn_panel(world, ScreenAnchor::BottomLeft, 460.0, 60.0);
    let hud = panel_label(world, hud_panel, "Interact to see live values");

    Gallery {
        radio_label,
        text_input,
        drag,
        data_grid,
        hud,
    }
}

fn update(world: &mut World, gallery: &mut Gallery) {
    if let Some(choice) = radio_selected(world, RADIO_GROUP) {
        let label = match choice {
            0 => "Difficulty: Easy",
            1 => "Difficulty: Normal",
            _ => "Difficulty: Hard",
        };
        set_panel_text(world, gallery.radio_label, label);
    }

    let drag_value = drag_value(world, gallery.drag);
    let typed = text_input_changed(world, gallery.text_input).unwrap_or_default();
    let grid_clicked = data_grid_selection_changed(world, gallery.data_grid);

    set_panel_text(
        world,
        gallery.hud,
        &format!("drag {drag_value:.1} | name '{typed}' | grid clicked {grid_clicked}"),
    );
}