fix(engine): Esc on a modal no longer also opens Pause underneath
A single Esc press while the Confirm New Game / Restore / Home / Onboarding / Settings modals were open would both close the modal (via its own input handler) and spawn the Pause overlay on top in the same frame, dumping the player on a screen they didn't ask for. toggle_pause now skips when any non-Pause `ModalScrim` is in the world. The HUD-button path is gated too — clicking Pause while another modal is up is almost always an accident. The four modal queries are bundled into a `PauseModalQueries` SystemParam to stay under Bevy's 16-parameter cap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,8 +36,9 @@ use crate::settings_plugin::{SettingsChangedEvent, SettingsResource, SettingsSto
|
|||||||
use crate::stats_plugin::StatsResource;
|
use crate::stats_plugin::StatsResource;
|
||||||
use crate::ui_modal::{
|
use crate::ui_modal::{
|
||||||
spawn_modal, spawn_modal_actions, spawn_modal_body_text, spawn_modal_button,
|
spawn_modal, spawn_modal_actions, spawn_modal_body_text, spawn_modal_button,
|
||||||
spawn_modal_header, ButtonVariant,
|
spawn_modal_header, ButtonVariant, ModalScrim,
|
||||||
};
|
};
|
||||||
|
use bevy::ecs::system::SystemParam;
|
||||||
use crate::ui_theme::{
|
use crate::ui_theme::{
|
||||||
self, TEXT_PRIMARY, TEXT_SECONDARY, TYPE_BODY_LG, TYPE_CAPTION, VAL_SPACE_3,
|
self, TEXT_PRIMARY, TEXT_SECONDARY, TYPE_BODY_LG, TYPE_CAPTION, VAL_SPACE_3,
|
||||||
};
|
};
|
||||||
@@ -126,15 +127,24 @@ impl Plugin for PausePlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bundles the modal-related queries `toggle_pause` reads each tick.
|
||||||
|
/// Pulled into a [`SystemParam`] so the system stays under Bevy's 16-
|
||||||
|
/// parameter cap after the cross-modal Esc guard query was added.
|
||||||
|
#[derive(SystemParam)]
|
||||||
|
struct PauseModalQueries<'w, 's> {
|
||||||
|
pause_screens: Query<'w, 's, Entity, With<PauseScreen>>,
|
||||||
|
forfeit_screens: Query<'w, 's, Entity, With<ForfeitConfirmScreen>>,
|
||||||
|
game_over_screens: Query<'w, 's, Entity, With<GameOverScreen>>,
|
||||||
|
other_modal_scrims: Query<'w, 's, Entity, (With<ModalScrim>, Without<PauseScreen>)>,
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn toggle_pause(
|
fn toggle_pause(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
keys: Res<ButtonInput<KeyCode>>,
|
keys: Res<ButtonInput<KeyCode>>,
|
||||||
mut requests: MessageReader<PauseRequestEvent>,
|
mut requests: MessageReader<PauseRequestEvent>,
|
||||||
mut paused: ResMut<PausedResource>,
|
mut paused: ResMut<PausedResource>,
|
||||||
screens: Query<Entity, With<PauseScreen>>,
|
modal_queries: PauseModalQueries<'_, '_>,
|
||||||
forfeit_screens: Query<Entity, With<ForfeitConfirmScreen>>,
|
|
||||||
game_over_screens: Query<Entity, With<GameOverScreen>>,
|
|
||||||
game: Option<Res<GameStateResource>>,
|
game: Option<Res<GameStateResource>>,
|
||||||
path: Option<Res<GameStatePath>>,
|
path: Option<Res<GameStatePath>>,
|
||||||
progress: Option<Res<ProgressResource>>,
|
progress: Option<Res<ProgressResource>>,
|
||||||
@@ -145,6 +155,13 @@ fn toggle_pause(
|
|||||||
mut changed: MessageWriter<StateChangedEvent>,
|
mut changed: MessageWriter<StateChangedEvent>,
|
||||||
selection: Option<Res<SelectionState>>,
|
selection: Option<Res<SelectionState>>,
|
||||||
) {
|
) {
|
||||||
|
let PauseModalQueries {
|
||||||
|
pause_screens: screens,
|
||||||
|
forfeit_screens,
|
||||||
|
game_over_screens,
|
||||||
|
other_modal_scrims,
|
||||||
|
} = modal_queries;
|
||||||
|
|
||||||
// Either Esc or a click on the HUD "Pause" button (which fires
|
// Either Esc or a click on the HUD "Pause" button (which fires
|
||||||
// PauseRequestEvent) opens or closes the overlay. Drain the queue so a
|
// PauseRequestEvent) opens or closes the overlay. Drain the queue so a
|
||||||
// burst of clicks doesn't queue future toggles.
|
// burst of clicks doesn't queue future toggles.
|
||||||
@@ -157,6 +174,16 @@ fn toggle_pause(
|
|||||||
if !forfeit_screens.is_empty() {
|
if !forfeit_screens.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Any other modal (Confirm New Game, Restore, Home, Onboarding,
|
||||||
|
// Settings, etc.) owns its own dismissal — pause must not stack
|
||||||
|
// on top of it. Without this guard a single Esc both closes the
|
||||||
|
// open modal AND spawns the pause overlay underneath, leaving the
|
||||||
|
// player on a screen they didn't ask for. The HUD-button path
|
||||||
|
// (`button_clicked`) is gated too; clicking Pause while another
|
||||||
|
// modal is up is almost always an accident.
|
||||||
|
if !other_modal_scrims.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// If a card is currently selected, let SelectionPlugin handle this Escape
|
// If a card is currently selected, let SelectionPlugin handle this Escape
|
||||||
// (it will clear the selection). Pause must not also open in the same frame.
|
// (it will clear the selection). Pause must not also open in the same frame.
|
||||||
if selection.is_some_and(|s| s.selected_pile.is_some()) {
|
if selection.is_some_and(|s| s.selected_pile.is_some()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user