11 — UI-Geruest und Screen-Modell #12

Closed
opened 2026-08-11 13:37:53 +00:00 by RDietermann · 1 comment
Owner

Teil der Map #1 · Type: wayfinder:grilling · Blocked by: — · graduiert aus #7

Wie sieht das konkrete Screen-Modell aus, das auf der Empfehlung aus #7 aufsetzt? Ein Stack oder zwei (Screens + Popups)? Wo lebt der ListState? Nur KeyEventKind::Press oder auch Repeat? Bleibt game/ ratatui-frei?

Volle Fragestellung (kanonisch): .scratch/rustkeep-1.0/issues/11-ui-geruest-screen-modell.md

Teil der Map #1 · **Type: `wayfinder:grilling`** · **Blocked by: —** · graduiert aus #7 Wie sieht das konkrete Screen-Modell aus, das auf der Empfehlung aus #7 aufsetzt? Ein Stack oder zwei (Screens + Popups)? Wo lebt der ListState? Nur `KeyEventKind::Press` oder auch `Repeat`? Bleibt `game/` ratatui-frei? Volle Fragestellung (kanonisch): `.scratch/rustkeep-1.0/issues/11-ui-geruest-screen-modell.md`
RDietermann added the wayfinder:grilling label 2026-08-11 13:37:53 +00:00
Author
Owner

Aufgeloest in 2c19cb2. Kanonische Fassung: .scratch/rustkeep-1.0/issues/11-ui-geruest-screen-modell.md

"Aktion" hiess zwei verschiedene Dinge

Der Bericht aus #7 empfahl "einen Eintrittspunkt Game::apply(Action)" — #8 hat Game::apply aber fuer die Aktionen aus #2 vergeben. "Geh nach Norden" ist keine der sieben Aktionen und wird nie eine.

Action TOML-Effektvokabular aus #2 — Game::apply(action, source, target, reg)
Command Kommando der UI ans Spiel — Game::execute(cmd, reg)

Game::execute ist die eine Stelle, an der ein Zug endet: Zaehler hoch, Modifikatoren ticken, turn_started feuern, Event-Warteschlange aus #9 leerarbeiten. Zwoelf Einzelmethoden auf Game haetten dasselbe zwoelfmal gebraucht — eine haette es vergessen.

Zwoelf Commands, alle kosten einen Zug

Move(Direction) · UseAbility(AbilityId, CreatureRef) · UseItem(usize) · Equip(usize) · Unequip(SlotId) · Drop(usize, u32) · PickUp(usize) · Descend · Talk(NpcId) · Buy(ItemId, u32) · Sell(usize, u32) · Wait

Weder Angriff noch Flucht sind eigene Varianten — beides sind Faehigkeiten (UseAbility("core:attack"), UseAbility("core:flee")). #3 hat Faehigkeiten als Registry festgelegt, also gehoeren sie dorthin; eine Klasse, die besser flieht, ist damit eine Datenaenderung statt eines Engine-Sonderfalls.

Auch Handel kostet einen Zug. Ein costs_turn()-Flag waere eine Ausnahmeliste, bei der irgendwann jemand falsch raet. Screen-Navigation ist reine UI und fasst Game nie an; Speichern und Laden sind keine Commands und bekommen ihren Weg von #10.

Ein Stack, eine Antwort

enum Response { None, Pop, Push(Screen), Command(Command), Quit }
fn handle_key(&mut self, key: KeyEvent) -> Response

Ein gemeinsamer Vec<Screen> fuer Screens und Popups — Popup ueber Popup ist real (Inventar -> Detail -> "wirklich wegwerfen?"), getrennte Felder braeuchten fuer den dritten Layer eine zweite Mechanik.

Der Screen gibt zurueck statt selbst zu schieben: sonst haelt er ein &mut auf genau die Vec, in der er liegt. Dasselbe Muster wie die Event-Warteschlange in #9. Nebeneffekt: Response::Command ist der einzige Weg nach game/.

Ein Response::Command schliesst alle Overlay-Screens — das Flag existiert schon fuers Rendern und beantwortet damit auch "schliesst sich nach einer Rueckfrage von selbst". Eine Zeile, keine neue Variante. Confirm { text, on_yes: Command } ist dadurch fuer jede Rueckfrage wiederverwendbar; Inventory als Vollbild bleibt nach UseItem offen.

Zehn Screens

MainMenu (mit der Ladewarnungs-Zeile aus #5) · CharacterCreation · Explore · Combat · Inventory · CharacterSheet · QuestLog · Shop · Dialog · Confirm

ListState lebt in der Screen-Variante. Der Stack macht den wichtigen Fall gratis richtig: solange ein Screen im Stack liegt, ueberlebt sein Cursor.

Eingabe

Nur KeyEventKind::Press. Der Grund ist schwerer als die UX: ein Schritt ist ein Zug, bei Repeat bestimmte die Tastaturwiederholrate des Betriebssystems die Spielzeit — ein gehaltener Pfeil verbrennte ein Gift-Debuff mit dreissig Zuegen pro Sekunde. Gegen zaehes Laufen hilft ein Renn-Kommando ("laufe, bis etwas passiert"), das in game/ viele Zuege aufloest.

Tastenbelegung fest im Code, Pfeiltasten und hjkl. Eine keybindings.toml braeuchte Parser, Konfliktpruefung und einen Einstellungs-Screen.

Modulstruktur

src/
  main.rs          # Laden, Fehlerpfad aus #5, dann ui::run
  game/            # kennt ratatui nicht — Test prueft das
    creature.rs  floor.rs  command.rs  action.rs  event.rs  condition.rs
  data/
    loader.rs  registries.rs  item.rs  affix.rs  quest.rs
  ui/
    app.rs         # App { game, registries, screens: Vec<Screen> } + Schleife
    input.rs  theme.rs
    screens/  widgets/grid.rs

App haelt game und registries als getrennte Felder (disjunkte Borrows aus #8). Command liegt in game/, sonst zeigte die Abhaengigkeit in die falsche Richtung. Die ratatui-Freiheit prueft ein #[test], der src/game/**/*.rs liest — ein Workspace waere echte Compiler-Durchsetzung und ist die Decke.

Nachtrag an #2 (01): Darstellung muss moddbar sein

#2 hat symbol als Schluessel definiert, dessen Zuordnung ui/theme.rs macht. Darin steckt eine Luecke: ui/theme.rs ist Code. Ein Modder mit meinmod:frostbite kann dort nichts eintragen — sein Statuseffekt haette weder Zeichen noch Farbe. Stattdessen traegt die TOML beides selbst:

symbol = "†"
color  = "cyan"

data/ kennt nur char und ein Farbnamen-Enum, ui/theme.rs uebersetzt in ratatui::Color.

Auflagen

  • #10 (09 Save): Speichern und Laden sind keine Commands
  • #13 (12 Prototyp): der Durchstich baut MainMenu, Explore und das Grid-Widget — alles andere ist Kulisse
  • #15 (14 Kampf): core:attack und core:flee muessen als Faehigkeiten im Kernspiel existieren
**Aufgeloest** in 2c19cb2. Kanonische Fassung: `.scratch/rustkeep-1.0/issues/11-ui-geruest-screen-modell.md` ### "Aktion" hiess zwei verschiedene Dinge Der Bericht aus #7 empfahl "einen Eintrittspunkt `Game::apply(Action)`" — #8 hat `Game::apply` aber fuer die Aktionen aus #2 vergeben. "Geh nach Norden" ist keine der sieben Aktionen und wird nie eine. | | | |---|---| | **Action** | TOML-Effektvokabular aus #2 — `Game::apply(action, source, target, reg)` | | **Command** | Kommando der UI ans Spiel — `Game::execute(cmd, reg)` | `Game::execute` ist die eine Stelle, an der ein **Zug endet**: Zaehler hoch, Modifikatoren ticken, `turn_started` feuern, Event-Warteschlange aus #9 leerarbeiten. Zwoelf Einzelmethoden auf `Game` haetten dasselbe zwoelfmal gebraucht — eine haette es vergessen. ### Zwoelf Commands, alle kosten einen Zug `Move(Direction)` · `UseAbility(AbilityId, CreatureRef)` · `UseItem(usize)` · `Equip(usize)` · `Unequip(SlotId)` · `Drop(usize, u32)` · `PickUp(usize)` · `Descend` · `Talk(NpcId)` · `Buy(ItemId, u32)` · `Sell(usize, u32)` · `Wait` Weder Angriff noch Flucht sind eigene Varianten — beides sind Faehigkeiten (`UseAbility("core:attack")`, `UseAbility("core:flee")`). #3 hat Faehigkeiten als Registry festgelegt, also gehoeren sie dorthin; eine Klasse, die besser flieht, ist damit eine Datenaenderung statt eines Engine-Sonderfalls. Auch Handel kostet einen Zug. Ein `costs_turn()`-Flag waere eine Ausnahmeliste, bei der irgendwann jemand falsch raet. **Screen-Navigation ist reine UI** und fasst `Game` nie an; Speichern und Laden sind keine Commands und bekommen ihren Weg von #10. ### Ein Stack, eine Antwort ```rust enum Response { None, Pop, Push(Screen), Command(Command), Quit } fn handle_key(&mut self, key: KeyEvent) -> Response ``` Ein gemeinsamer `Vec<Screen>` fuer Screens und Popups — Popup ueber Popup ist real (Inventar -> Detail -> "wirklich wegwerfen?"), getrennte Felder braeuchten fuer den dritten Layer eine zweite Mechanik. Der Screen **gibt zurueck statt selbst zu schieben**: sonst haelt er ein `&mut` auf genau die `Vec`, in der er liegt. Dasselbe Muster wie die Event-Warteschlange in #9. Nebeneffekt: `Response::Command` ist der einzige Weg nach `game/`. **Ein `Response::Command` schliesst alle Overlay-Screens** — das Flag existiert schon fuers Rendern und beantwortet damit auch "schliesst sich nach einer Rueckfrage von selbst". Eine Zeile, keine neue Variante. `Confirm { text, on_yes: Command }` ist dadurch fuer jede Rueckfrage wiederverwendbar; `Inventory` als Vollbild bleibt nach `UseItem` offen. ### Zehn Screens `MainMenu` (mit der Ladewarnungs-Zeile aus #5) · `CharacterCreation` · `Explore` · `Combat` · `Inventory` · `CharacterSheet` · `QuestLog` · `Shop` · `Dialog` · `Confirm` `ListState` lebt in der Screen-Variante. Der Stack macht den wichtigen Fall gratis richtig: solange ein Screen im Stack *liegt*, ueberlebt sein Cursor. ### Eingabe **Nur `KeyEventKind::Press`.** Der Grund ist schwerer als die UX: ein Schritt ist ein Zug, bei `Repeat` bestimmte die Tastaturwiederholrate des Betriebssystems die Spielzeit — ein gehaltener Pfeil verbrennte ein Gift-Debuff mit dreissig Zuegen pro Sekunde. Gegen zaehes Laufen hilft ein Renn-Kommando ("laufe, bis etwas passiert"), das in `game/` viele Zuege aufloest. Tastenbelegung fest im Code, Pfeiltasten und `hjkl`. Eine `keybindings.toml` braeuchte Parser, Konfliktpruefung und einen Einstellungs-Screen. ### Modulstruktur ``` src/ main.rs # Laden, Fehlerpfad aus #5, dann ui::run game/ # kennt ratatui nicht — Test prueft das creature.rs floor.rs command.rs action.rs event.rs condition.rs data/ loader.rs registries.rs item.rs affix.rs quest.rs ui/ app.rs # App { game, registries, screens: Vec<Screen> } + Schleife input.rs theme.rs screens/ widgets/grid.rs ``` `App` haelt `game` und `registries` als getrennte Felder (disjunkte Borrows aus #8). `Command` liegt in `game/`, sonst zeigte die Abhaengigkeit in die falsche Richtung. Die ratatui-Freiheit prueft ein `#[test]`, der `src/game/**/*.rs` liest — ein Workspace waere echte Compiler-Durchsetzung und ist die Decke. ### Nachtrag an #2 (01): Darstellung muss moddbar sein #2 hat `symbol` als *Schluessel* definiert, dessen Zuordnung `ui/theme.rs` macht. Darin steckt eine Luecke: **`ui/theme.rs` ist Code.** Ein Modder mit `meinmod:frostbite` kann dort nichts eintragen — sein Statuseffekt haette weder Zeichen noch Farbe. Stattdessen traegt die TOML beides selbst: ```toml symbol = "†" color = "cyan" ``` `data/` kennt nur `char` und ein Farbnamen-Enum, `ui/theme.rs` uebersetzt in `ratatui::Color`. ### Auflagen - **#10** (09 Save): Speichern und Laden sind keine Commands - **#13** (12 Prototyp): der Durchstich baut `MainMenu`, `Explore` und das Grid-Widget — alles andere ist Kulisse - **#15** (14 Kampf): `core:attack` und `core:flee` muessen als Faehigkeiten im Kernspiel existieren
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RDietermann/rustkeep#12