builtin macro

This commit is contained in:
2026-06-23 01:08:01 -05:00
parent a67f3fa8cf
commit 2e160d6c61
12 changed files with 237 additions and 237 deletions
+2 -2
View File
@@ -22,7 +22,7 @@ Renders the board as text: each `Glyph.tile` index is reinterpreted as a charact
**`kiln-tui/src/editor.rs`** — world editor mode:
- `EditorState` — a non-ticking editing session over a freshly reloaded `World`: `world`, `board_name` (board being edited), `menu: Vec<MenuLevel>` (sidebar menu stack, for breadcrumb + escape-to-parent), `dialog: Option<Dialog<EditorState>>`, `code_editor: Option<CodeEditor>` (the open script editor; replaces the board view), `glyph_dialog: Option<GlyphDialog<EditorState>>` (the open glyph picker overlay), a blinking `cursor`, the **drawing tool** (`current_archetype` + `current_glyph` — the thing stamped on `space`, defaulting to a wall — and `draw_mode`, the toggle that re-stamps on every cursor move), `sidebar_width` (mouse-resizable), blink state, `last_board_area` (written at draw, read for right-click hit-testing), editor-side `log`, and `pending_playtest: Option<GameState>` (a playtest game parked for the run loop to swap into `Mode::Play`).
- `MenuLevel` (`Main`/`World`/`Floor`/`Terrain`/`Machines`/`Pushers`/`Items`) — a sidebar menu level; (`Main``[i] Items``[g] ♦ Gem`, which `set_current(Archetype::Gem)`); `title()` (breadcrumb segment) and `entries() -> Vec<MenuEntry>` (the level's lines). A `MenuEntry` is a key-activated `Item` (`[k] Label` + a boxed `FnOnce(&mut EditorState)` action), a `CurrentValue` (a boxed `Fn(&EditorState) -> String` shown indented beneath an item, e.g. the world name), or a `Blank`/`Separator` spacer. An item's action **opens a dialog** (`World`/scripts/boards), **pushes a child level** (`Main``World`/`Floor`/`Terrain`/`Machines`; `Machines``Pushers`), **sets the drawing tool** (`Terrain`/`Pushers``set_current(arch)`, plus `Machines``[s]` CW / `[c]` CCW spinner), or **launches a playtest** (`Main``[p] Play board``playtest()`). The menu is a static letter-keyed stack — picking from a list is always a dialog, so arrow keys never conflict with the board cursor.
- `MenuLevel` (`Main`/`World`/`Floor`/`Terrain`/`Machines`/`Pushers`/`Items`) — a sidebar menu level; (`Main``[i] Items``[t] ♦ Gem`, which `set_current(Archetype::Builtin(Builtin::Gem, "gem"))`); `title()` (breadcrumb segment) and `entries() -> Vec<MenuEntry>` (the level's lines). A `MenuEntry` is a key-activated `Item` (`[k] Label` + a boxed `FnOnce(&mut EditorState)` action), a `CurrentValue` (a boxed `Fn(&EditorState) -> String` shown indented beneath an item, e.g. the world name), or a `Blank`/`Separator` spacer. An item's action **opens a dialog** (`World`/scripts/boards), **pushes a child level** (`Main``World`/`Floor`/`Terrain`/`Machines`; `Machines``Pushers`), **sets the drawing tool** (`Terrain`/`Pushers``set_current(arch)`, plus `Machines``[s]` CW / `[c]` CCW spinner, all using `Archetype::Builtin(Builtin::…, "alias")`), or **launches a playtest** (`Main``[p] Play board``playtest()`). The menu is a static letter-keyed stack — picking from a list is always a dialog, so arrow keys never conflict with the board cursor.
- Methods: `breadcrumb()`, `current_menu()`, `menu_escape(ui)` (pop a level, or at the root open the overlay `editor_menu_items()`), `menu_key(c)` (dispatch a letter → the matched `entries()` item's action via `MenuLevel::perform_key`), `playtest()` (deep-clones the world, sets its start to the edited board, **expands script-backed archetypes** in every board via `Board::expand_builtin_archetypes` so editor-stamped spinners/pushers run, builds a `GameState` + runs `init()`, and parks it in `pending_playtest` for the run loop), and the `open_{name,entry,scripts,boards}_dialog` builders (name → text dialog writing `world.name`; entry → list dialog writing `world.start`; boards → select-only; scripts → **opens the script in `code_editor`**). `open_script_editor(name)` builds a `CodeEditor` from `world.scripts[name]`; `close_script_editor()` saves its text back into `world.scripts` (in-memory, like the other dialogs).
- Drawing: `set_current(arch)` sets `current_archetype` and resets `current_glyph` to its default; `open_glyph_picker()` seeds a `GlyphDialog` from `current_glyph` and writes the chosen glyph back (bound to **`g`**); `place_current()` stamps the current archetype+glyph at the cursor via `Board::place_archetype`; `toggle_draw_mode()` flips `draw_mode`. `place_current` is bound to **`space`** and `toggle_draw_mode` to **`tab`** in `handle_editor_input`; `move_cursor`/`set_cursor_at_screen` also call `place_current` when `draw_mode` is on and the cursor enters a new cell. The sidebar's bottom **drawing-controls footer** (`draw_footer_lines`) shows the archetype name, `[g] Glyph` + a live colored preview, `[spc] Place`, and `[tab] Draw mode (on/off)`.
- `editor_menu_items()` — the overlay editor menu (`[p]` play, `[q]` quit, `[esc]` resume), opened at the root of the menu tree. `handle_dialog_input(event, ed)` — forwards to the open dialog and, on `Submit`/`Cancel`, `take`s it out and calls `finish(.., ed)`. `handle_glyph_dialog_input(event, ed)` — the same pattern for the open `glyph_dialog`. `handle_code_editor_input(event, ed)` — forwards to the open code editor and, on `Exit` (Esc), calls `close_script_editor()`. `draw_editor(frame, ed, ui)` / `editor_layout(..)` — the **code editor (when open) else** the board (with blinking cursor + coord readout) in the left area, a resizable right-hand sidebar (breadcrumb title + the menu choices, each with its optional current-value line), and the optional full-width log panel.
@@ -61,7 +61,7 @@ Renders the board as text: each `Glyph.tile` index is reinterpreted as a charact
- `board_screen_pos(area, board, bx, by) -> Option<(u16, u16)>` — converts a board cell coordinate to terminal screen coordinates, returning `None` if off-screen. Used by bubble placement.
**`kiln-tui/src/status.rs`** — play-mode status sidebar widget:
- `StatusSidebarWidget` — a `ratatui::widgets::Widget` built with `new(health, gems)` that draws the player's stats in a bordered `Block` titled `Status`: a `Health:` label above a row of `MAX_HEARTS` (5) `♥` glyphs (first `health` bright red, rest dark red) and a `Gems: ♦ N` line. The gem indicator is rendered from `kiln_core::Archetype::Gem.default_glyph()` (char via `cp437::tile_to_char`, color via `rgba8_to_color`) so it matches gems on the board — not a hardcoded glyph/color. Purely presentational — reads the values handed in, never mutates game state. Drawn by `draw_play` when `ui.show_status` is set; `Tab` toggles it.
- `StatusSidebarWidget` — a `ratatui::widgets::Widget` built with `new(health, gems)` that draws the player's stats in a bordered `Block` titled `Status`: a `Health:` label above a row of `MAX_HEARTS` (5) `♥` glyphs (first `health` bright red, rest dark red) and a `Gems: ♦ N` line. The gem indicator is rendered from `Archetype::Builtin(Builtin::Gem, "gem").default_glyph()` (char via `cp437::tile_to_char`, color via `rgba8_to_color`) so it matches gems on the board — not a hardcoded glyph/color. Purely presentational — reads the values handed in, never mutates game state. Drawn by `draw_play` when `ui.show_status` is set; `Tab` toggles it.
**`kiln-tui/src/log.rs`** — log panel widget:
- `LogState { open, height, scroll }` — panel visibility, height in rows, and scroll offset. `toggle()`, `scroll_by(delta, log_len)`, `resize(target, total)`.
+9 -9
View File
@@ -1,7 +1,7 @@
use crate::editor::EditorState;
use crate::menu::{MenuItem, MenuKey};
use crate::mode::PendingMode;
use kiln_core::{Archetype, Direction, SpinDirection};
use kiln_core::{Archetype, Builtin};
/// One level in the editor's sidebar menu tree.
///
@@ -80,29 +80,29 @@ impl MenuLevel {
MenuLevel::Machines => vec![
MenuEntry::item('p', "Pushers...", |ed| ed.menu.push(MenuLevel::Pushers)),
MenuEntry::item('s', "/ CW spinner", |ed| {
ed.set_current(Archetype::Spinner(SpinDirection::Clockwise))
ed.set_current(Archetype::Builtin(Builtin::Spinner, "spinner_cw"))
}),
MenuEntry::item('c', "\\ CCW spinner", |ed| {
ed.set_current(Archetype::Spinner(SpinDirection::CounterClockwise))
ed.set_current(Archetype::Builtin(Builtin::Spinner, "spinner_ccw"))
}),
],
MenuLevel::Pushers => vec![
MenuEntry::item('n', "▲ Pusher", |ed| {
ed.set_current(Archetype::Pusher(Direction::North))
ed.set_current(Archetype::Builtin(Builtin::Pusher, "pusher_north"))
}),
MenuEntry::item('s', "▼ Pusher", |ed| {
ed.set_current(Archetype::Pusher(Direction::South))
ed.set_current(Archetype::Builtin(Builtin::Pusher, "pusher_south"))
}),
MenuEntry::item('e', "► Pusher", |ed| {
ed.set_current(Archetype::Pusher(Direction::East))
ed.set_current(Archetype::Builtin(Builtin::Pusher, "pusher_east"))
}),
MenuEntry::item('w', "◄ Pusher", |ed| {
ed.set_current(Archetype::Pusher(Direction::West))
ed.set_current(Archetype::Builtin(Builtin::Pusher, "pusher_west"))
}),
],
MenuLevel::Items => vec![MenuEntry::item('t', "♦ Gem", |ed| {
// gem and glyph start with the same letter
ed.set_current(Archetype::Gem)
// 't' for 'treasure' — 'g' conflicts with glyph picker
ed.set_current(Archetype::Builtin(Builtin::Gem, "gem"))
})],
}
}
+2 -2
View File
@@ -6,7 +6,7 @@
//! never mutates game state.
use crate::utils::rgba8_to_color;
use kiln_core::Archetype;
use kiln_core::{Archetype, Builtin};
use kiln_core::cp437::tile_to_char;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
@@ -54,7 +54,7 @@ impl Widget for StatusSidebarWidget {
// Draw the gem indicator from the gem archetype's default glyph, so the
// sidebar matches what gems look like on the board (no hardcoded ♦/color).
let gem_glyph = Archetype::Gem.default_glyph();
let gem_glyph = Archetype::Builtin(Builtin::Gem, "gem").default_glyph();
let gem_char = tile_to_char(gem_glyph.tile).to_string();
let gem_style = Style::default().fg(rgba8_to_color(gem_glyph.fg));