diff --git a/solitaire_engine/src/safe_area.rs b/solitaire_engine/src/safe_area.rs index 6544bd5..9c0c48b 100644 --- a/solitaire_engine/src/safe_area.rs +++ b/solitaire_engine/src/safe_area.rs @@ -191,10 +191,12 @@ fn apply_safe_area_to_modal_scrims( /// whatever `SafeAreaInsets` are current at that moment. /// /// On Android the `android::rearm_on_resumed` system runs in the same frame -/// and resets both `SafeAreaPollTries` and `SafeAreaInsets` to zero, causing -/// `refresh_insets` to re-poll JNI over the next few frames. When it resolves -/// the correct values, `on_safe_area_changed` in `table_plugin` emits a second -/// synthetic `WindowResized` and the layout converges to the right position. +/// and resets `SafeAreaPollTries` (the cached `SafeAreaInsets` keep their +/// last-known values), causing `refresh_insets` to re-poll JNI over the next +/// few frames. If the insets changed while backgrounded, `on_safe_area_changed` +/// in `table_plugin` emits a second synthetic `WindowResized` and the layout +/// converges to the right position; if they didn't, nothing is rewritten and +/// the layout stays put. /// /// On non-Android targets this handler still fires — it ensures that a resume /// event always refreshes the layout (e.g., after a minimise/restore on @@ -232,9 +234,14 @@ mod android { pub(super) struct SafeAreaPollTries(pub u32); /// Polls Android for safe-area insets until we get a non-zero - /// reading, then stops. `getRootWindowInsets()` returns `null` (or - /// all-zero `Insets`) until the decor view has been laid out, which - /// is typically frame 1–3 of a fresh launch. + /// reading, then settles until [`rearm_on_resumed`] re-arms it on the + /// next foreground resume — insets can change while backgrounded + /// (rotation, fold/unfold, gesture ↔ 3-button nav). The poll counter + /// (not `insets.is_populated()`) gates the loop, so a re-armed cycle + /// re-queries JNI even though cached values are already populated. + /// `getRootWindowInsets()` returns `null` (or all-zero `Insets`) + /// until the decor view has been laid out, which is typically frame + /// 1–3 of a fresh launch. pub(super) fn refresh_insets( mut insets: ResMut, mut poll: ResMut, @@ -243,18 +250,25 @@ mod android { // devices that genuinely report zero insets. const MAX_TRIES: u32 = 120; // ~2 seconds @ 60 fps - if poll.0 >= MAX_TRIES || insets.is_populated() { + if poll.0 >= MAX_TRIES { return; } poll.0 += 1; match query_insets() { Ok(v) if v.is_populated() => { - info!( - "safe_area: insets resolved top={} bottom={} left={} right={} (after {} frames)", - v.top, v.bottom, v.left, v.right, poll.0 - ); - *insets = v; + if *insets != v { + info!( + "safe_area: insets resolved top={} bottom={} left={} right={} (after {} frames)", + v.top, v.bottom, v.left, v.right, poll.0 + ); + *insets = v; + } + // Settled for this poll cycle; `rearm_on_resumed` re-arms on + // the next resume. Writing `insets` only on an actual change + // keeps resource change detection (and the relayout it + // triggers) quiet on resumes where nothing moved. + poll.0 = MAX_TRIES; } Ok(_) => { // Layout not ready yet; try again next frame.