fix(engine): Esc dismisses the topmost modal when Profile stacks on Home

Clicking the new Home header chip opens Profile on top of Home.
Pressing Esc then closed Home (because handle_home_cancel_button
fired on Esc with no awareness of layered modals) and left Profile
orphaned over the game — the player had to press P afterwards just
to dismiss what they meant to dismiss in the first place.

Two changes restore the standard "Esc closes the topmost modal"
contract:

- profile_plugin: split P/button (toggle) from Esc (close-only).
  Esc only fires when Profile is currently open.
- home_plugin: handle_home_cancel_button now skips its Esc branch
  when any other ModalScrim exists, deferring to whichever modal
  is on top. Click on the explicit Cancel button is unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-06 17:15:18 +00:00
parent d065d49fe7
commit 9aa0dd23b1
2 changed files with 18 additions and 2 deletions
+7 -1
View File
@@ -475,13 +475,19 @@ fn handle_home_cancel_button(
keys: Option<Res<ButtonInput<KeyCode>>>, keys: Option<Res<ButtonInput<KeyCode>>>,
cancel_buttons: Query<&Interaction, (With<HomeCancelButton>, Changed<Interaction>)>, cancel_buttons: Query<&Interaction, (With<HomeCancelButton>, Changed<Interaction>)>,
screens: Query<Entity, With<HomeScreen>>, screens: Query<Entity, With<HomeScreen>>,
other_modal_scrims: Query<(), (With<crate::ui_modal::ModalScrim>, Without<HomeScreen>)>,
) { ) {
if screens.is_empty() { if screens.is_empty() {
return; return;
} }
let click = cancel_buttons.iter().any(|i| *i == Interaction::Pressed); let click = cancel_buttons.iter().any(|i| *i == Interaction::Pressed);
let esc = keys.is_some_and(|k| k.just_pressed(KeyCode::Escape)); let esc = keys.is_some_and(|k| k.just_pressed(KeyCode::Escape));
if !click && !esc { // Esc only closes Home when it is the *topmost* modal. With Profile
// (or any other ModalScrim) layered on top, the topmost owns the
// dismissal — without this gate a single Esc closed the back
// modal (Home) and left the front modal orphaned.
let esc_targets_home = esc && other_modal_scrims.is_empty();
if !click && !esc_targets_home {
return; return;
} }
for entity in &screens { for entity in &screens {
+11 -1
View File
@@ -146,7 +146,17 @@ fn toggle_profile_screen(
screens: Query<Entity, With<ProfileScreen>>, screens: Query<Entity, With<ProfileScreen>>,
) { ) {
let button_clicked = requests.read().count() > 0; let button_clicked = requests.read().count() > 0;
if !keys.just_pressed(KeyCode::KeyP) && !button_clicked { let p_pressed = keys.just_pressed(KeyCode::KeyP);
let esc_pressed = keys.just_pressed(KeyCode::Escape);
let already_open = !screens.is_empty();
// P / button toggles open-or-close. Esc only ever closes — when
// Profile is layered over Home (clicking the new Home stats chip
// opens this on top), Esc must dismiss the *topmost* modal.
// Without this branch, Esc fell through to Home's cancel handler
// and closed the wrong modal.
let want_open = !already_open && (p_pressed || button_clicked);
let want_close = already_open && (p_pressed || button_clicked || esc_pressed);
if !want_open && !want_close {
return; return;
} }
if let Ok(entity) = screens.single() { if let Ok(entity) = screens.single() {