diff --git a/solitaire_engine/src/cursor_plugin.rs b/solitaire_engine/src/cursor_plugin.rs index 06d4bdc..d0e871c 100644 --- a/solitaire_engine/src/cursor_plugin.rs +++ b/solitaire_engine/src/cursor_plugin.rs @@ -38,7 +38,7 @@ use solitaire_core::game_state::{DrawMode, GameState}; use solitaire_core::pile::PileType; use solitaire_core::rules::{can_place_on_foundation, can_place_on_tableau}; -use crate::card_plugin::{RightClickHighlight, TABLEAU_FAN_FRAC}; +use crate::card_plugin::RightClickHighlight; use crate::layout::{Layout, LayoutResource}; use crate::resources::{DragState, GameStateResource}; use crate::table_plugin::{PileMarker, PILE_MARKER_DEFAULT_COLOUR}; @@ -387,7 +387,7 @@ fn drop_overlay_rect(pile: &PileType, layout: &Layout, game: &GameState) -> (Vec if matches!(pile, PileType::Tableau(_)) { let card_count = game.piles.get(pile).map_or(0, |p| p.cards.len()); if card_count > 1 { - let fan = -layout.card_size.y * TABLEAU_FAN_FRAC; + let fan = -layout.card_size.y * layout.tableau_fan_frac; let bottom_card_centre_y = centre.y + fan * (card_count - 1) as f32; let top_edge = centre.y + layout.card_size.y / 2.0; let bottom_edge = bottom_card_centre_y - layout.card_size.y / 2.0; @@ -478,7 +478,7 @@ fn tableau_or_stack_pos( if is_tableau { Vec2::new( base.x, - base.y - layout.card_size.y * TABLEAU_FAN_FRAC * (index as f32), + base.y - layout.card_size.y * layout.tableau_fan_frac * (index as f32), ) } else if matches!(pile, PileType::Waste) && game.draw_mode == DrawMode::DrawThree { let pile_len = game.piles.get(pile).map_or(0, |p| p.cards.len()); diff --git a/solitaire_engine/src/home_plugin.rs b/solitaire_engine/src/home_plugin.rs index ee745a7..1c49dc6 100644 --- a/solitaire_engine/src/home_plugin.rs +++ b/solitaire_engine/src/home_plugin.rs @@ -35,6 +35,7 @@ use crate::stats_plugin::StatsResource; use crate::ui_focus::{Disabled, FocusGroup, Focusable}; use crate::ui_modal::{ spawn_modal, spawn_modal_actions, spawn_modal_button, spawn_modal_header, ButtonVariant, + ModalButton, ScrimDismissible, }; use crate::ui_theme::{ @@ -373,6 +374,7 @@ fn toggle_home_screen( daily: Option>, font_res: Option>, screens: Query>, + other_modal_scrims: Query<(), (With, Without)>, diff_expanded: Res, ) { if !keys.just_pressed(KeyCode::KeyM) { @@ -380,7 +382,7 @@ fn toggle_home_screen( } if let Ok(entity) = screens.single() { commands.entity(entity).despawn(); - } else { + } else if other_modal_scrims.is_empty() { spawn_home_screen( &mut commands, build_home_context( @@ -1348,6 +1350,7 @@ fn spawn_mode_card( // bevy::ui — the click handler queries on `&Interaction` // which Button drives. Button, + ModalButton(ButtonVariant::Secondary), Node { flex_direction: FlexDirection::Column, row_gap: VAL_SPACE_2, diff --git a/solitaire_engine/src/input_plugin.rs b/solitaire_engine/src/input_plugin.rs index 177623b..0c82d2d 100644 --- a/solitaire_engine/src/input_plugin.rs +++ b/solitaire_engine/src/input_plugin.rs @@ -940,10 +940,10 @@ fn touch_end_drag( continue; } - // Uncommitted tap — cancel cleanly. + // Uncommitted tap — cancel cleanly. No StateChangedEvent: nothing + // changed. The mouse path (end_drag) follows the same convention. if !drag.committed { drag.clear(); - changed.write(StateChangedEvent); return; } diff --git a/solitaire_engine/src/pause_plugin.rs b/solitaire_engine/src/pause_plugin.rs index 035b978..3e67ba1 100644 --- a/solitaire_engine/src/pause_plugin.rs +++ b/solitaire_engine/src/pause_plugin.rs @@ -437,10 +437,15 @@ fn close_forfeit_modal( /// The player reaches these overlays via the HUD menu while paused, which /// causes both the pause modal and the overlay to be live simultaneously. /// That is always unintentional — the overlay should own the screen. +/// Query filter for modals that are not part of the pause flow. +/// Excludes both `PauseScreen` (the pause modal itself) and +/// `ForfeitConfirmScreen` (spawned from within the pause flow). +type NonPauseFamilyScrim = (With, Without, Without); + fn auto_resume_on_overlay( mut commands: Commands, pause_screens: Query>, - other_modal_scrims: Query, Without)>, + other_modal_scrims: Query, mut paused: ResMut, ) { if pause_screens.is_empty() || other_modal_scrims.is_empty() { @@ -449,7 +454,9 @@ fn auto_resume_on_overlay( for entity in &pause_screens { commands.entity(entity).despawn(); } - paused.0 = false; + if paused.0 { + paused.0 = false; + } } /// Spawns the pause modal using the standard `ui_modal` scaffold — diff --git a/solitaire_engine/src/selection_plugin.rs b/solitaire_engine/src/selection_plugin.rs index c121ad5..0b2c660 100644 --- a/solitaire_engine/src/selection_plugin.rs +++ b/solitaire_engine/src/selection_plugin.rs @@ -156,7 +156,13 @@ impl Plugin for SelectionPlugin { .in_set(SelectionKeySet) .before(GameMutation), clear_selection_on_state_change.after(GameMutation), - update_selection_highlight.after(GameMutation), + update_selection_highlight + .after(GameMutation) + .run_if( + resource_changed:: + .or(resource_changed::) + .or(resource_changed::), + ), ), ); }