feat(engine): add Modes dropdown with Classic/Daily/Zen/Challenge/Time Attack

Continues the UI-first pass. The five game modes were each behind a
keyboard shortcut (N/Z/X/T/C) with no visible UI affordance, three of
them additionally gated by an unlock level the player has to discover
themselves.

Add a "Modes ▾" button to the action bar that toggles a popover panel
beneath. Each row dispatches the same code path the keyboard
accelerator uses by writing a new `Start*RequestEvent` (or
`NewGameRequestEvent` for Classic):

- Classic        → NewGameRequestEvent::default()
- Daily Challenge → StartDailyChallengeRequestEvent
- Zen            → StartZenRequestEvent
- Challenge      → StartChallengeRequestEvent
- Time Attack    → StartTimeAttackRequestEvent

The existing keyboard handlers in input_plugin (Z), challenge_plugin
(X), time_attack_plugin (T), and daily_challenge_plugin (C) now read
either their key or the matching request event, so level gates,
TimeAttackResource setup, daily seed lookup, and toast feedback for
locked modes all stay in their owning plugins — the popover never
duplicates that logic.

The popover only lists modes available to the player: Classic always
shows, Daily Challenge shows when DailyChallengeResource is loaded,
and Zen/Challenge/Time Attack show once the player reaches level 5
(the existing CHALLENGE_UNLOCK_LEVEL).

Click handler despawns the popover after dispatch; clicking the
Modes button again toggles it shut.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
funman300
2026-04-29 23:49:40 +00:00
parent 97f38085e3
commit 1d9fb1884a
7 changed files with 265 additions and 29 deletions
+30
View File
@@ -99,6 +99,36 @@ pub struct PauseRequestEvent;
#[derive(Message, Debug, Clone, Copy, Default)]
pub struct HelpRequestEvent;
/// Request to start a Zen-mode game. Fired by the HUD Modes-popover "Zen"
/// row alongside the existing `Z` accelerator. The handler in
/// `input_plugin` enforces the level gate (Zen unlocks at level 5) and
/// shows an informational toast when locked.
#[derive(Message, Debug, Clone, Copy, Default)]
pub struct StartZenRequestEvent;
/// Request to start the next Challenge-mode game. Fired by the HUD
/// Modes-popover "Challenge" row alongside the existing `X` accelerator.
/// The handler in `challenge_plugin` enforces the level gate, picks the
/// next seed from `progress.challenge_index`, and writes the
/// corresponding `NewGameRequestEvent`.
#[derive(Message, Debug, Clone, Copy, Default)]
pub struct StartChallengeRequestEvent;
/// Request to start a Time Attack session. Fired by the HUD
/// Modes-popover "Time Attack" row alongside the existing `T`
/// accelerator. The handler in `time_attack_plugin` enforces the level
/// gate, initialises `TimeAttackResource`, and writes the corresponding
/// `NewGameRequestEvent`.
#[derive(Message, Debug, Clone, Copy, Default)]
pub struct StartTimeAttackRequestEvent;
/// Request to start today's Daily Challenge. Fired by the HUD
/// Modes-popover "Daily Challenge" row alongside the existing `C`
/// accelerator. The handler in `daily_challenge_plugin` reads
/// `DailyChallengeResource::seed` and writes a `NewGameRequestEvent`.
#[derive(Message, Debug, Clone, Copy, Default)]
pub struct StartDailyChallengeRequestEvent;
/// Fired by `SyncPlugin` after a pull task resolves and the merged result has
/// been persisted to disk. `Ok(SyncResponse)` carries the merged payload plus
/// any `ConflictReport`s the merge produced. `Err(String)` carries a