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 <noreply@anthropic.com>
This commit is contained in:
2026-05-17 19:11:53 -05:00
parent 03d6337f04
commit 4b12005800
2 changed files with 77 additions and 33 deletions
+57 -23
View File
@@ -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 (0x200x7E). 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.
+20 -10
View File
@@ -50,17 +50,27 @@ The game is a single Rust binary using **eframe 0.33 / egui 0.33** for the GUI.
- `impl From<MapFile> for Board` — converts a parsed file into a ready-to-use `Board`
- `pub fn load(path: &str) -> Result<Board, Box<dyn std::error::Error>>` — 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 (0x200x7E)
- `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`)