tails
This commit is contained in:
@@ -119,26 +119,51 @@ The core types that were formerly monolithic in `game.rs` are now split into foc
|
||||
|
||||
The terminal player. Renders the board as text: each `Glyph.tile` index is reinterpreted as a character (the default kiln font is CP437, where the tile index equals the code point) and drawn with the glyph's RGB colors. No bitmap font or UV mapping is involved.
|
||||
|
||||
**Drawing rule: prefer ratatui primitives.** Before writing manual cell-by-cell buffer code in `render.rs`, check whether a `Block`, `Paragraph`, `Layout`, `Line::centered()`, or other ratatui widget/method achieves the same result. If a small design change (e.g. fixed vs. content-driven sizing) would unlock a ratatui-native approach, raise it as a question rather than writing manual code. The documented exceptions are: the background dimming pass in `draw_scroll_overlay` (no ratatui primitive for "tint the whole buffer"), and the two tail characters in `draw_speech_bubbles` (the tail-join `╮` and the descending `│`, written after ratatui renders the box).
|
||||
**Drawing rule: prefer ratatui primitives.** Before writing manual cell-by-cell buffer code, check whether a `Block`, `Paragraph`, `Layout`, `Line::centered()`, or other ratatui widget/method achieves the same result. If a small design change (e.g. fixed vs. content-driven sizing) would unlock a ratatui-native approach, raise it as a question rather than writing manual code. The documented exceptions are: the background dimming pass in `ScrollOverlayWidget` (no ratatui primitive for "tint the whole buffer"), and the tail characters in `SpeechBubblesWidget` (`speech.rs`): the tail-join `┬` on the bottom border and the `│` column drawn below the box down to the source object.
|
||||
|
||||
**`kiln-tui/src/main.rs`** — entry point and play loop:
|
||||
- Parses a single positional arg (the map path); prints usage and exits non-zero if missing. Loads the board via `kiln_core::map_file::load` *before* touching the terminal so load errors print to a normal screen.
|
||||
- `ratatui::init()` → detect `TerminalCaps` → push Kitty flags when supported → `run` loop → pop Kitty flags → `ratatui::restore()`.
|
||||
- `run` is a ~30 FPS real-time loop: it `event::poll`s only until the next frame deadline (so input stays responsive) and otherwise wakes to advance the game by the real elapsed `dt` via `GameState::tick`. It acts on `Press` **and** `Repeat` key events (so holding a key moves continuously) and ignores `Release` (which the Kitty protocol also emits, and which would otherwise double-fire each move). Arrow keys move the player via `GameState::try_move`; `l` toggles the log panel; `q`/`Esc` quit. Mouse capture is enabled so the log panel scrolls (wheel) and resizes (drag the divider). `game.run_init()` runs object `init()` hooks after the board is loaded and before the loop. When `game.active_scroll` is `Some`, all input is redirected to scroll navigation (Up/Down to scroll, letter keys for choices, Esc to dismiss if no choices); `game.tick()` is skipped until the overlay is closed.
|
||||
- `draw` wraps the board in a bordered `Block` titled with the board name and controls. When the log panel is closed, the latest log line is previewed in the bottom-left border after a `[l]og:` label; when open, a bottom panel lists the log newest-first (`render::logline_to_line` converts each `LogLine`). The scroll overlay is drawn last (above everything) by `render::draw_scroll_overlay`.
|
||||
- `Ui { log_open, log_height, scroll, scroll_offset, scroll_anim }` — view-only panel state kept in the front-end (not on `GameState`).
|
||||
- `ScrollAnimState { Opening(f32), Open, Closing { progress, choice } }` — tracks the 0.5 s open/close animation for the scroll overlay. `begin_close(ui, choice)` starts closing. When `Closing` reaches 0, `game.close_scroll(choice)` is called and the animation resets.
|
||||
- `Ui { log: LogState, overlay: ScrollOverlayState }` — view-only panel state kept in the front-end (not on `GameState`). `Ui::tick(dt, game)` advances animations, dispatches finished close events, and ticks the game when no overlay is blocking. `begin_close(choice)` starts the close animation.
|
||||
|
||||
**`kiln-tui/src/cp437.rs`** — CP437 → Unicode mapping:
|
||||
- `CP437: [char; 256]` — full code-page-437 table (graphic glyphs for 0x00–0x1F, box-drawing for 0xB0–0xDF, etc.).
|
||||
- `tile_to_char(tile: u32) -> char` — indexes the table for `tile < 256`; falls back to `char::from_u32` then a blank space. Unit-tested.
|
||||
|
||||
**`kiln-tui/src/render.rs`** — board → ratatui buffer:
|
||||
- `rgba8_to_color(Rgba8) -> Color::Rgb` — lossless RGB bridge (alpha dropped); truecolor terminals show exact colors, 256-color ones approximate.
|
||||
- `BoardWidget<'a>` — a `ratatui::widgets::Widget` that draws the board. Per axis, `axis(board_len, view, player) -> (offset, pad, count)` (pub): a board smaller than the viewport is **centered** with screen padding, a larger one **scrolls** to keep the player on screen with no empty margins. Each cell calls `Board::glyph_at`, which already folds in the player glyph at the player's position — no separate player-overlay pass needed.
|
||||
- `board_screen_pos(area, board, bx, by) -> Option<(u16, u16)>` — converts a board cell coordinate to terminal screen coordinates, returning `None` if the cell is scrolled off-screen. Used by the speech-bubble overlay.
|
||||
- `draw_speech_bubbles(buf, area, board, bubbles)` — draws speech bubbles over the board for all active `SpeechBubble`s. Uses `Block::bordered().border_type(BorderType::Rounded)` + `Paragraph` for the box; only the tail-join character (`╮` on the bottom border at the speaker's column) and the descending tail `│` are written manually. Bubbles that would overlap are shifted upward one row at a time until no collision.
|
||||
- `draw_scroll_overlay(buf, area, scroll, offset, anim)` — draws the full-screen scroll overlay. Dims `area` manually (no ratatui primitive for this), then renders a `Block`-bordered window at half screen width × 3/4 screen height (height forced even). Height animates with `anim` (0.0→1.0 = opening, 1.0→0.0 = closing), always staying even. Lines starting with ASCII whitespace are stripped and centered via `Line::centered()`; choice lines show `[a]`/`[b]` prefixes. `Paragraph::line_count(inner_w)` measures wrapped content height; `Layout::vertical` centers short content. `offset` scrolls the content.
|
||||
|
||||
**`kiln-tui/src/speech.rs`** — speech bubble widget:
|
||||
- `SpeechBubblesWidget<'a>` — a `ratatui::widgets::Widget` that draws speech bubbles for all active `SpeechBubble`s over the board. Two-pass: first collects all placements (sorted bottom-first so upward-shift decisions don't interfere with higher bubbles), then renders in **reverse** order (top-most first) so lower bubbles' boxes naturally cover extended tails without an explicit occlusion check.
|
||||
- Each bubble: `Block::bordered()` + `Paragraph` for the box; the tail-join `┬` on the bottom border and a column of `│` chars from below the box down to the source object are written manually. Bubbles that would overlap are shifted upward one row at a time (up to 20 attempts) until clear.
|
||||
- `place_bubble(area, obj_sx, obj_sy, lines, placed) -> Option<Rect>` — sizes and places one bubble, appending its rect to `placed` on success.
|
||||
- `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/scroll_overlay.rs`** — scroll overlay widget:
|
||||
- `ScrollAnimState { Opening(f32), Open, Closing(f32), Done }` — open/close animation progress (0.0–1.0). `Done` signals the caller to dispatch the pending choice and reset state.
|
||||
- `ScrollOverlayState { offset, max_scroll, anim, pending_choice }` — threaded through render as `StatefulWidget` state. `advance_anim(dt)` ticks the animation; `close_finished()` / `take_choice()` let the caller dispatch when `Done`. `max_scroll` is an output written during render.
|
||||
- `ScrollOverlayWidget<'a>` — `StatefulWidget` that dims the background, renders a bordered window at ½ width × ¾ height with animated height (forced even), wraps text, centers whitespace-leading lines, prefixes choices with `[a]`/`[b]`, and renders a scrollbar when content overflows.
|
||||
|
||||
**`kiln-tui/src/ui.rs`** — top-level front-end state:
|
||||
- `Ui { log: LogState, overlay: ScrollOverlayState }` — aggregates all presentation state. `tick(dt, game)` advances animations, dispatches finished close events, ticks the game when no overlay blocks, and starts the open animation when a new scroll appears. `scroll_lines(delta)` adjusts `overlay.offset`. `begin_close(choice)` starts the closing animation (uses current opening progress if still animating in).
|
||||
|
||||
**`kiln-tui/src/input.rs`** — input dispatch:
|
||||
- `InputMode { Board(bool), Scroll }` — which input mode is active; `Board(log_open)` for normal play, `Scroll` when the overlay is blocking.
|
||||
- `current_input_mode(game, ui) -> InputMode` — derives mode from game + animation state.
|
||||
- `handle_board_input(event, log_open, terminal_h, game, ui) -> bool` — processes arrow keys (move player), `l` (toggle log), `q`/`Esc` (quit), PageUp/Down (scroll log), mouse drag (resize log). Returns `true` if the player quit.
|
||||
- `handle_scroll_input(event, game, ui)` — processes Up/Down (scroll), letter keys (select choice via `resolve_choice`), Esc (dismiss if no choices), mouse wheel.
|
||||
|
||||
**`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)`.
|
||||
- `LogWidget<'a>` — `StatefulWidget` that renders all log lines newest-first in a bordered block with word-wrap.
|
||||
- `logline_to_line(line) -> Line` — converts a core `LogLine` into a styled ratatui `Line`; each `LogSpan`'s optional fg/bg is applied only when present.
|
||||
- `log_preview_line(logs) -> Line` — builds the `[l]og: <latest>` preview span for the board's bottom border.
|
||||
|
||||
**`kiln-tui/src/utils.rs`** — shared rendering helpers:
|
||||
- `rgba8_to_color(Rgba8) -> Color` — lossless RGB bridge (alpha dropped).
|
||||
- `rects_overlap(a, b) -> bool` — true if two `Rect`s share at least one cell; used by bubble placement.
|
||||
|
||||
**`kiln-tui/src/term.rs`** — terminal capability detection and Kitty setup:
|
||||
- `TerminalCaps { keyboard_enhancement: bool, truecolor: bool }` — detected once at startup. `keyboard_enhancement` is a real query/response (`supports_keyboard_enhancement()`); `truecolor` reads `$COLORTERM`. Intended to be handed to scripts so they can pick bindings the terminal actually supports.
|
||||
|
||||
Reference in New Issue
Block a user