fix(engine): re-poll safe-area insets after app resume
refresh_insets gated its loop on insets.is_populated(), so once insets resolved at first launch, rearm_on_resumed's poll-counter reset was a no-op and JNI was never queried again. Insets that changed while the app was backgrounded (fold/unfold, rotation, gesture/3-button nav switch) stayed stale until process restart. Gate the loop on the poll counter alone and settle a cycle by exhausting it once a populated reading arrives. The cached resource is rewritten only when the value actually differs, so resumes where nothing moved trigger no change detection and no relayout — preserving the no-flash resume behaviour. Also correct the stale on_app_resumed doc that still described the old inset-zeroing approach. Closes #116 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -191,10 +191,12 @@ fn apply_safe_area_to_modal_scrims(
|
|||||||
/// whatever `SafeAreaInsets` are current at that moment.
|
/// whatever `SafeAreaInsets` are current at that moment.
|
||||||
///
|
///
|
||||||
/// On Android the `android::rearm_on_resumed` system runs in the same frame
|
/// On Android the `android::rearm_on_resumed` system runs in the same frame
|
||||||
/// and resets both `SafeAreaPollTries` and `SafeAreaInsets` to zero, causing
|
/// and resets `SafeAreaPollTries` (the cached `SafeAreaInsets` keep their
|
||||||
/// `refresh_insets` to re-poll JNI over the next few frames. When it resolves
|
/// last-known values), causing `refresh_insets` to re-poll JNI over the next
|
||||||
/// the correct values, `on_safe_area_changed` in `table_plugin` emits a second
|
/// few frames. If the insets changed while backgrounded, `on_safe_area_changed`
|
||||||
/// synthetic `WindowResized` and the layout converges to the right position.
|
/// 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
|
/// 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
|
/// event always refreshes the layout (e.g., after a minimise/restore on
|
||||||
@@ -232,9 +234,14 @@ mod android {
|
|||||||
pub(super) struct SafeAreaPollTries(pub u32);
|
pub(super) struct SafeAreaPollTries(pub u32);
|
||||||
|
|
||||||
/// Polls Android for safe-area insets until we get a non-zero
|
/// Polls Android for safe-area insets until we get a non-zero
|
||||||
/// reading, then stops. `getRootWindowInsets()` returns `null` (or
|
/// reading, then settles until [`rearm_on_resumed`] re-arms it on the
|
||||||
/// all-zero `Insets`) until the decor view has been laid out, which
|
/// next foreground resume — insets can change while backgrounded
|
||||||
/// is typically frame 1–3 of a fresh launch.
|
/// (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(
|
pub(super) fn refresh_insets(
|
||||||
mut insets: ResMut<SafeAreaInsets>,
|
mut insets: ResMut<SafeAreaInsets>,
|
||||||
mut poll: ResMut<SafeAreaPollTries>,
|
mut poll: ResMut<SafeAreaPollTries>,
|
||||||
@@ -243,19 +250,26 @@ mod android {
|
|||||||
// devices that genuinely report zero insets.
|
// devices that genuinely report zero insets.
|
||||||
const MAX_TRIES: u32 = 120; // ~2 seconds @ 60 fps
|
const MAX_TRIES: u32 = 120; // ~2 seconds @ 60 fps
|
||||||
|
|
||||||
if poll.0 >= MAX_TRIES || insets.is_populated() {
|
if poll.0 >= MAX_TRIES {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
poll.0 += 1;
|
poll.0 += 1;
|
||||||
|
|
||||||
match query_insets() {
|
match query_insets() {
|
||||||
Ok(v) if v.is_populated() => {
|
Ok(v) if v.is_populated() => {
|
||||||
|
if *insets != v {
|
||||||
info!(
|
info!(
|
||||||
"safe_area: insets resolved top={} bottom={} left={} right={} (after {} frames)",
|
"safe_area: insets resolved top={} bottom={} left={} right={} (after {} frames)",
|
||||||
v.top, v.bottom, v.left, v.right, poll.0
|
v.top, v.bottom, v.left, v.right, poll.0
|
||||||
);
|
);
|
||||||
*insets = v;
|
*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(_) => {
|
Ok(_) => {
|
||||||
// Layout not ready yet; try again next frame.
|
// Layout not ready yet; try again next frame.
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user