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
+8 -2
View File
@@ -8,7 +8,9 @@ use bevy::prelude::*;
use solitaire_core::game_state::GameMode;
use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL;
use crate::events::{GameWonEvent, InfoToastEvent, NewGameRequestEvent};
use crate::events::{
GameWonEvent, InfoToastEvent, NewGameRequestEvent, StartTimeAttackRequestEvent,
};
use crate::game_plugin::GameMutation;
use crate::progress_plugin::ProgressResource;
use crate::resources::GameStateResource;
@@ -40,6 +42,7 @@ impl Plugin for TimeAttackPlugin {
.add_message::<TimeAttackEndedEvent>()
.add_message::<GameWonEvent>()
.add_message::<NewGameRequestEvent>()
.add_message::<StartTimeAttackRequestEvent>()
.add_message::<InfoToastEvent>()
.add_systems(
Update,
@@ -52,12 +55,15 @@ impl Plugin for TimeAttackPlugin {
fn handle_start_time_attack_request(
keys: Res<ButtonInput<KeyCode>>,
mut requests: MessageReader<StartTimeAttackRequestEvent>,
progress: Res<ProgressResource>,
mut session: ResMut<TimeAttackResource>,
mut new_game: MessageWriter<NewGameRequestEvent>,
mut info_toast: MessageWriter<InfoToastEvent>,
) {
if !keys.just_pressed(KeyCode::KeyT) {
// Either T or the HUD Modes-popover "Time Attack" row triggers this.
let button_clicked = requests.read().count() > 0;
if !keys.just_pressed(KeyCode::KeyT) && !button_clicked {
return;
}
if progress.0.level < CHALLENGE_UNLOCK_LEVEL {