From 6193d31497950fcd1ca389d30f5c6942fbb85463 Mon Sep 17 00:00:00 2001 From: funman300 Date: Mon, 8 Jun 2026 17:34:28 -0700 Subject: [PATCH] fix(engine): centre modal cards within usable area (status-bar + gesture-bar) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit apply_safe_area_to_modal_scrims now sets both padding.top (status-bar height) and padding.bottom (gesture-bar height) on every ModalScrim. With align_items/justify_content: Center on the scrim, the modal card lands at the visual midpoint of the visible area between the two system bars, fixing the slight upward shift that occurred when only the bottom inset was applied. Also: mark all rewrite-plan phases (0–3) complete; drop obsolete stash whose 20 files are already incorporated into master; update CLAUDE.md §14.3 to document both edges. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 8 +++++--- docs/in-place-card-game-rewrite-plan.md | 2 +- solitaire_engine/src/safe_area.rs | 19 +++++++++++++++++-- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2482370..26b5b08 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -430,9 +430,11 @@ explicitly replacing the current one (despawn first, then spawn). ## 14.3 Safe area -Every `ModalScrim` automatically receives `padding.bottom` equal to the -logical gesture-bar height via `apply_safe_area_to_modal_scrims` in -`SafeAreaInsetsPlugin`. Do not manually add bottom padding to scrim nodes. +Every `ModalScrim` automatically receives `padding.top` equal to the logical +status-bar height and `padding.bottom` equal to the logical gesture-bar height +via `apply_safe_area_to_modal_scrims` in `SafeAreaInsetsPlugin`. This centres +the modal card within the usable area between both system bars. Do not manually +add top or bottom padding to scrim nodes. ## 14.4 Z-ordering diff --git a/docs/in-place-card-game-rewrite-plan.md b/docs/in-place-card-game-rewrite-plan.md index 4bc11df..089c949 100644 --- a/docs/in-place-card-game-rewrite-plan.md +++ b/docs/in-place-card-game-rewrite-plan.md @@ -2,7 +2,7 @@ **Date:** 2026-06-08 **Upstream rev:** `99b49e62` -**Status:** Phases 0–2 complete. Pre-Phase 3 undo audit complete (see §5 below). Awaiting approval for Phase 3. +**Status:** All phases complete (0–3). recycle_count drift and score compound error on undo fixed in `56e3b62`. --- diff --git a/solitaire_engine/src/safe_area.rs b/solitaire_engine/src/safe_area.rs index 1fc4e16..b499bb3 100644 --- a/solitaire_engine/src/safe_area.rs +++ b/solitaire_engine/src/safe_area.rs @@ -147,8 +147,13 @@ fn apply_safe_area_bottom_anchors( } } -/// Pads the bottom of every [`ModalScrim`] by the logical bottom inset so -/// modal cards don't extend into the Android gesture-navigation zone. +/// Pads both edges of every [`ModalScrim`] by the logical system-bar insets so +/// modal cards are centred within the usable area (between the status bar at +/// the top and the gesture-navigation bar at the bottom). +/// +/// `padding.top` = status-bar inset; `padding.bottom` = gesture-bar inset. +/// With `align_items: Center` / `justify_content: Center` on the scrim the +/// `ModalCard` lands at the visual midpoint of the visible content area. /// /// Fires when [`SafeAreaInsets`] changes (covers the common case of insets /// arriving a few frames after app start) AND when a new `ModalScrim` is @@ -165,8 +170,18 @@ fn apply_safe_area_to_modal_scrims( } let scale = windows.iter().next().map_or(1.0, |w| w.scale_factor()); let window_height = windows.iter().next().map_or(800.0, |w| w.height()); + // Clamp each inset to 25% of screen height so an unexpectedly large OS + // value can't push the modal card off the visible area entirely. + let top_logical = (insets.top / scale).min(window_height * 0.25); let bottom_logical = (insets.bottom / scale).min(window_height * 0.25); for mut node in &mut scrims { + // Set both edges so the scrim's content box equals the usable area + // between the status bar and the gesture/navigation bar. With + // `align_items: Center` / `justify_content: Center` on the scrim, + // the modal card is centred within that usable region rather than + // the full viewport, correcting the slight upward shift seen when + // only the bottom inset was applied. + node.padding.top = Val::Px(top_logical); node.padding.bottom = Val::Px(bottom_logical); } }