From 4b12005800dc2e0c6601cfe1d658cd9d8342d27d Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Sun, 17 May 2026 19:11:53 -0500 Subject: [PATCH] Update ARCHITECTURE.md and CLAUDE.md for new module structure Documents render.rs, editor.rs, and glyph_picker.rs; updates the module structure diagram and main.rs section to reflect the refactored layout. Co-Authored-By: Claude Sonnet 4.6 --- ARCHITECTURE.md | 80 +++++++++++++++++++++++++++++++++++-------------- CLAUDE.md | 30 ++++++++++++------- 2 files changed, 77 insertions(+), 33 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 9ef102f..a22d036 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -24,11 +24,14 @@ This document records the architectural decisions made so far and the reasoning ``` src/ - main.rs — app entry point, rendering, input - game.rs — core data types and game logic - map_file.rs — TOML deserialization, Board loader + main.rs — App, AppMode, frame loop (input → menu → panels → board → dialogs) + game.rs — core data types and game logic + map_file.rs — TOML deserialization, Board loader + render.rs — cell-size constants, draw_glyph/draw_board, board_origin, pos_to_cell + editor.rs — EditorTab, EditorState, show_editor_panel + glyph_picker.rs — floating Glyph picker dialog; decoupled (open, glyph) interface maps/ - start.toml — the starting map (loaded at launch) + start.toml — the starting map (loaded at launch) ``` ### `game.rs` — core types @@ -86,36 +89,67 @@ eframe requires `NativeOptions` (including window size) to be set before calling --- -### `main.rs` — app + rendering +### `main.rs` — app + frame loop -`App` holds a `GameState`, an `AppMode` (`Play` | `Edit`), and an `EditorState`. `EditorState` carries: -- `selected: Archetype` — the archetype class to stamp -- `glyph: Glyph` — the visual to stamp (independent of the archetype's default; resets to `archetype.default_glyph()` when the archetype selection changes) -- `glyph_picker_open: bool` — whether the glyph picker dialog is visible -- `tab: EditorTab` (`Palette` | `Board` | `World`) — which side-panel tab is active - -The `update` method: +`App` holds a `GameState`, an `AppMode` (`Play` | `Edit`), and an `EditorState`. The `update` method phases: 1. Reads arrow key input and calls `GameState::try_move` — **Play mode only** 2. Draws the menu bar with a File menu and a Play/Edit mode toggle -3. In Edit mode: draws a resizable right-side panel (default 200 px) with a Palette/Board/World tab bar — declared before `CentralPanel` so egui allocates its space first -4. Draws the board and player (see viewport sections below) -5. In Edit mode: renders the glyph picker `egui::Window` if open — board-palette of unique glyphs, fg/bg `color_edit_button_srgba`, and a 16×6 ASCII character grid +3. In Edit mode: calls `editor::show_editor_panel` — declared before `CentralPanel` so egui allocates its space first +4. Draws the board and player via `render::draw_board` (see viewport sections below) +5. In Edit mode: calls `glyph_picker::show` if `editor.glyph_picker_open` -**Viewport — Play mode (`board_origin`):** -The `board_origin(available, board_w, board_h, player)` function computes the pixel position of cell (0,0). If the board fits along an axis, it is centered. If it overflows, the viewport is centered on the player and clamped so no empty space appears at the board edges. No scroll bars. +**Viewport — Play mode:** +`render::board_origin(available, board_w, board_h, player)` computes the pixel position of cell (0,0). If the board fits along an axis it is centered; if it overflows the viewport is centered on the player and clamped so no empty space appears at the board edges. No scroll bars. -**Viewport — Edit mode (`ScrollArea`):** -The board is wrapped in `egui::ScrollArea::new([true, true])` which shows scroll bars when the board overflows the viewport. Content is allocated at exact board size; `rect.min` from `allocate_exact_size` serves as the origin. - -**Cell rendering constants:** `CELL_W = 14.0`, `CELL_H = 20.0` pixels. Window uses fixed default size (`DEFAULT_WINDOW_W = 840`, `DEFAULT_WINDOW_H = 524`) and a small fixed minimum — map size no longer drives window dimensions. - -**Click-to-cell math:** `rel = click_pos - origin; cell_x = (rel.x / CELL_W).floor() as i32`. Negative `rel` values stay negative and are caught by a `>= 0` guard. This mirrors the rendering math exactly so clicks land on the correct cell. +**Viewport — Edit mode:** +The board is wrapped in `egui::ScrollArea::new([true, true])`, which shows scroll bars when the board overflows the viewport. Content is allocated at exact board size; `rect.min` from `allocate_exact_size` serves as the origin passed to `render::draw_board`. **Stamp action:** `*board.get_mut(cx, cy) = (self.editor.glyph, self.editor.selected)` — the custom glyph and selected archetype are written together. The glyph can differ from the archetype's `default_glyph()` if the user has customized it. --- +### `render.rs` — constants and drawing primitives + +All pixel-level rendering knowledge lives here. No dependency on `EditorState` or `App`. + +**Constants:** `CELL_W = 14.0`, `CELL_H = 20.0` pixels per cell. Window sizing constants (`DEFAULT_WINDOW_W = 840`, `DEFAULT_WINDOW_H = 524`, `MIN_WINDOW_W/H`) are defined here so they stay co-located with the cell measurements they derive from. + +**`draw_glyph(painter, origin, x, y, glyph)`** — renders one cell as a filled background rect plus a centered monospace character. + +**`draw_board(painter, origin, board)`** — iterates all cells calling `draw_glyph`, then draws the player as an overlay on top. + +**`pos_to_cell(origin, pos) -> (i32, i32)`** — converts a pixel position to cell coordinates using floor division. Returns negative values for clicks above/left of the origin; callers must guard `>= 0`. Mirrors the rendering math so clicks land on the correct cell. + +**`board_origin(available, board_w, board_h, player) -> Pos2`** — computes the pixel origin for Play-mode rendering (centering or player-tracking with edge clamping). + +--- + +### `editor.rs` — editor state and side panel + +**`EditorTab`** (`Palette` | `Board` | `World`) — which tab is active in the side panel. + +**`EditorState`** — transient editor state: +- `selected: Archetype` — the archetype class to stamp +- `glyph: Glyph` — the visual to stamp (independent of the archetype's default; resets to `archetype.default_glyph()` when the archetype selection changes) +- `glyph_picker_open: bool` — whether the glyph picker dialog is visible +- `tab: EditorTab` — which side-panel tab is active + +**`show_editor_panel(ctx, editor, board)`** — renders the resizable right-side panel (default 200 px). On the Palette tab: a scrollable archetype list (at most 5 rows visible) and a current-glyph preview button that sets `glyph_picker_open = true`. Board and World tabs are placeholders. + +--- + +### `glyph_picker.rs` — glyph picker dialog + +**`show(ctx, open, glyph, board_cells)`** — renders a floating `egui::Window`. Takes `open: &mut bool` and `glyph: &mut Glyph` directly, with no dependency on `EditorState`, so it can be invoked from any context that needs glyph selection. + +Three sections: +1. **Board palette** — unique glyphs found on the current board, shown as clickable cells. The current glyph is highlighted with a white stroke. +2. **Colors** — `color_edit_button_srgba` pickers for `glyph.fg` and `glyph.bg`. +3. **Character grid** — 16 × 6 grid of printable ASCII (0x20–0x7E). Clicking a cell updates `glyph.ch`. The current character is highlighted. + +--- + ## Map file format XPM-inspired (XPM is an old X11 image format that uses a character palette to define pixel colors). A `[palette]` section maps single characters to both a `Glyph` (visual) and an `Element` (behavior). The `[grid] content` is a TOML multi-line string where each character is a palette key. diff --git a/CLAUDE.md b/CLAUDE.md index 78a944c..07ab6af 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,17 +50,27 @@ The game is a single Rust binary using **eframe 0.33 / egui 0.33** for the GUI. - `impl From for Board` — converts a parsed file into a ready-to-use `Board` - `pub fn load(path: &str) -> Result>` — reads and converts a `.toml` map file -**`src/main.rs`** — app entry point: +**`src/main.rs`** — app entry point and frame loop: - `AppMode` enum (`Play` | `Edit`) — gates arrow-key input; toggles the side panel and viewport mode -- `EditorTab` enum (`Palette` | `Board` | `World`) — which tab is active in the side panel -- `EditorState` — holds `selected: Archetype`, `glyph: Glyph` (the glyph to stamp, independent of archetype default), `glyph_picker_open: bool`, and `tab: EditorTab`. Selecting a new archetype resets `glyph` to that archetype's default. -- Window uses fixed default size (`DEFAULT_WINDOW_W = 840`, `DEFAULT_WINDOW_H = 524`) independent of map dimensions; `board_origin()` handles centering or player-centered scrolling at runtime -- `draw_glyph(painter, origin, x, y, glyph)` draws one cell: filled rect (bg) + monospace char (fg) -- `board_origin(available, board_w, board_h, player)` — if the board fits in the viewport, centers it; if it overflows, centers on the player and clamps to board edges (no empty space shown) -- Player is rendered on top of the board using `Glyph::player()` -- **Play mode**: arrow keys move player; viewport scrolls to keep player centered when map > window -- **Edit mode**: `ScrollArea::both()` wraps the board so scroll bars appear when map > window; click-to-paint stamps `(self.editor.glyph, self.editor.selected)` at the clicked cell; side panel (200 px, resizable) shows Palette/Board/World tabs -- **Glyph picker dialog** (`egui::Window`): opened from the Palette tab; shows a board-palette of unique glyphs, fg/bg `color_edit_button_srgba` pickers, and a 16×6 grid of printable ASCII chars (0x20–0x7E) +- `App` holds `GameState`, `AppMode`, and `EditorState`; `update` phases: input → menu bar → editor panel → board → glyph picker dialog +- **Play mode**: arrow keys move player; `render::board_origin` centers or player-tracks the viewport +- **Edit mode**: `ScrollArea::both()` wraps the board; click-to-paint stamps `(editor.glyph, editor.selected)`; calls `editor::show_editor_panel` and `glyph_picker::show` + +**`src/render.rs`** — cell rendering constants and drawing primitives: +- `CELL_W = 14.0`, `CELL_H = 20.0` and window sizing constants (`DEFAULT_WINDOW_W = 840`, `DEFAULT_WINDOW_H = 524`) +- `draw_glyph(painter, origin, x, y, glyph)` — filled rect (bg) + centered monospace char (fg) +- `draw_board(painter, origin, board)` — draws all cells then player overlay +- `board_origin(available, board_w, board_h, player)` — centers board or clamps to player with no empty space +- `pos_to_cell(origin, pos) -> (i32, i32)` — pixel → cell coordinates via floor division; negatives signal out-of-bounds + +**`src/editor.rs`** — editor state and side panel: +- `EditorTab` enum (`Palette` | `Board` | `World`) — which tab is active +- `EditorState` — holds `selected: Archetype`, `glyph: Glyph`, `glyph_picker_open: bool`, `tab: EditorTab`; selecting a new archetype resets `glyph` to that archetype's default +- `show_editor_panel(ctx, editor, board)` — resizable right-side panel (default 200 px); Palette tab shows archetype list and glyph preview button; Board/World tabs are placeholders + +**`src/glyph_picker.rs`** — floating glyph picker dialog: +- `show(ctx, open, glyph, board_cells)` — takes `open: &mut bool` and `glyph: &mut Glyph` directly; no dependency on `EditorState` +- Three sections: board palette (unique glyphs, click to select), FG/BG color pickers, 16×6 printable ASCII character grid ### Map file format (`maps/*.toml`)