diff --git a/CLAUDE.md b/CLAUDE.md index d4723b4..7a2ea12 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -91,6 +91,7 @@ The core types that were formerly monolithic in `game.rs` are now split into foc - `can_push(x, y, dir) -> bool` — read-only: does the chain of pushable solids end in open space? - `push(x, y, dir)` — mutating: shoves the chain one step, leaving a transparent cell behind (so a lower floor layer is revealed). A pushed solid moves within its own layer (found via `solid_cell_layer`). - `add_object(obj) -> ObjectId`, `object_ids_at(x, y)`, `solid_object_id_at(x, y)` — object queries. +- `place_archetype(x, y, arch, glyph)` — editor stamp primitive (used by kiln-tui's drawing tools). Keyed only on the archetype, leaving any floor untouched: a non-`Empty` (solid) arch removes a solid object in the cell then writes `(glyph, arch)` into the existing terrain layer (`terrain_layer_at`, the single non-`Empty` cell) or else the top layer; `Empty` erases — removes every object plus the terrain cell (→ transparent `Empty`). - `in_bounds((i32, i32))` — bounds-checks a possibly-negative coord. - `is_valid()` / `load_errors()` / `report_error(msg)` — nonfatal load-error surface. @@ -186,10 +187,10 @@ The terminal **player + world editor**, depending on both `kiln-core` and `kiln- - `Mode { Play(GameState), Edit(EditorState) }` — exactly one is live at a time (play and edit are mutually exclusive: entering the editor drops the running game; returning to play rebuilds it fresh from the world file). `PendingMode { EnterEditor, PlayWorld }` — a deferred Play↔Edit switch requested by a menu closure (which only has `&mut Ui`) and applied by the run loop, mirroring the `should_quit` pattern. **`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` (sidebar menu stack, for breadcrumb + escape-to-parent), `dialog: Option>`, `code_editor: Option` (the open script editor; replaces the board view), `glyph_dialog: Option>` (the open glyph picker overlay), a blinking `cursor`, `sidebar_width` (mouse-resizable), blink state, `last_board_area` (written at draw, read for right-click hit-testing), and editor-side `log`. -- `MenuLevel { World }` — a sidebar menu level; `title()` (breadcrumb segment) and `choices()` (static `(key, label)` list — `[n] Name`, `[e] Entry board`, `[s] Scripts`, `[b] Boards`). The menu is a stack so nested levels can be added later; today only `World` exists and every choice opens a dialog (no sub-menu). Per the design, menus are static letter-keyed lists — 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 → `open_*_dialog`), `menu_value(key)` (the "current value" shown beneath a choice in the sidebar — world name for `n`, start board for `e`), 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). -- `open_glyph_picker()` — seeds a `GlyphDialog` from `board.glyph_at(cursor)` and opens it; its callback logs the chosen glyph to `ed.log` (no paint tool yet). Bound to **`g`** in `handle_editor_input`. +- `EditorState` — a non-ticking editing session over a freshly reloaded `World`: `world`, `board_name` (board being edited), `menu: Vec` (sidebar menu stack, for breadcrumb + escape-to-parent), `dialog: Option>`, `code_editor: Option` (the open script editor; replaces the board view), `glyph_dialog: Option>` (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), and editor-side `log`. +- `MenuLevel` (`Main`/`World`/`Floor`/`Terrain`/`Machines`/`Pushers`) — a sidebar menu level; `title()` (breadcrumb segment) and `entries() -> Vec` (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`), or **sets the drawing tool** (`Terrain`/`Pushers` → `set_current(arch)`). 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`), 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. **`kiln-tui/src/ui.rs`** — front-end presentation state (not on `GameState`): diff --git a/kiln-tui/src/editor.rs b/kiln-tui/src/editor.rs index 913ff74..56e28cb 100644 --- a/kiln-tui/src/editor.rs +++ b/kiln-tui/src/editor.rs @@ -67,10 +67,12 @@ pub(crate) struct EditorState { /// Cursor position in board cells, clamped to the board bounds. cursor: (i32, i32), /// The archetype the drawing tools stamp on [`place_current`](EditorState::place_current). - /// Defaults to [`Archetype::Wall`]; the UI to change it comes later. + /// Defaults to [`Archetype::Wall`]; changed via the Terrain / Pushers menus + /// ([`set_current`](EditorState::set_current)). current_archetype: Archetype, - /// The glyph the drawing tools stamp. Starts as the current archetype's default - /// glyph and is edited via the glyph picker (`g`). + /// The glyph the drawing tools stamp. Reset to the current archetype's default + /// whenever the archetype changes ([`set_current`](EditorState::set_current)), and + /// overridable via the glyph picker (`g`). current_glyph: Glyph, /// Whether draw mode is on: when set, moving the cursor stamps the current thing /// into each newly-entered cell (as if `space` were pressed after the move). @@ -226,6 +228,9 @@ impl EditorState { )); } + /// Sets the archetype the drawing tools stamp, resetting the drawing glyph to + /// that archetype's default (the glyph picker `g` can then override it). Called by + /// the Terrain / Pushers menu choices. pub(crate) fn set_current(&mut self, archetype: Archetype) { self.current_archetype = archetype; self.current_glyph = self.current_archetype.default_glyph(); diff --git a/kiln-tui/src/editor_menu.rs b/kiln-tui/src/editor_menu.rs index 3efd312..ab7af98 100644 --- a/kiln-tui/src/editor_menu.rs +++ b/kiln-tui/src/editor_menu.rs @@ -6,9 +6,12 @@ use crate::mode::PendingMode; /// One level in the editor's sidebar menu tree. /// /// The menu is a stack of levels (the current one is the stack's last element); a -/// breadcrumb is built from their [`title`](MenuLevel::title)s. A choice either opens -/// a [dialog](kiln_ui::dialog) or pushes a child level — e.g. [`Main`](MenuLevel::Main) -/// descends into [`World`](MenuLevel::World) or [`Floor`](MenuLevel::Floor). +/// breadcrumb is built from their [`title`](MenuLevel::title)s. A choice opens a +/// [dialog](kiln_ui::dialog), pushes a child level (e.g. [`Main`](MenuLevel::Main) +/// descends into [`World`](MenuLevel::World) or [`Terrain`](MenuLevel::Terrain)), or +/// sets the drawing tools' current archetype (e.g. the [`Terrain`](MenuLevel::Terrain) +/// and [`Pushers`](MenuLevel::Pushers) choices, via +/// [`set_current`](EditorState::set_current)). #[derive(Clone, Copy, PartialEq)] pub(crate) enum MenuLevel { /// The top-level main menu: switch boards, view scripts, more later