Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 38b81a4004 |
@@ -84,7 +84,8 @@ impl Plugin for SafeAreaInsetsPlugin {
|
||||
#[cfg(target_os = "android")]
|
||||
app.init_resource::<android::SafeAreaPollTries>()
|
||||
.add_systems(Update, android::refresh_insets)
|
||||
.add_systems(Update, android::rearm_on_resumed);
|
||||
.add_systems(Update, android::rearm_on_resumed)
|
||||
.add_systems(Update, android::refresh_surface_size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,6 +226,7 @@ fn on_app_resumed(
|
||||
mod android {
|
||||
use super::{AppLifecycle, SafeAreaInsets};
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::WindowResized;
|
||||
|
||||
/// Tracks how many frames `refresh_insets` has polled. Stored as a
|
||||
/// `Resource` (not `Local`) so that `rearm_on_resumed` can reset it to 0
|
||||
@@ -299,11 +301,108 @@ mod android {
|
||||
) {
|
||||
for event in lifecycle.read() {
|
||||
if matches!(event, AppLifecycle::WillResume) {
|
||||
// Evidence line for #130: winit's Android backend has open
|
||||
// TODOs around forwarding resume notifications, so whether
|
||||
// this ever fires on a given device is an open question.
|
||||
info!("safe_area: AppLifecycle::WillResume received; re-arming inset poll");
|
||||
poll.0 = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Polls the decor view's size via JNI and forces a relayout when it
|
||||
/// disagrees with Bevy's cached `Window` resolution (#130).
|
||||
///
|
||||
/// winit's Android backend does not forward content-rect changes that
|
||||
/// happen while the app is backgrounded (fold/unfold on foldables), so
|
||||
/// after a fold cycle Bevy can keep rendering and laying out for the
|
||||
/// previous screen's dimensions. Unlike `refresh_insets` this poller
|
||||
/// never settles: it cannot rely on `AppLifecycle::WillResume` to re-arm
|
||||
/// it, because that event is itself delivered through the same unreliable
|
||||
/// lifecycle plumbing. A JNI round-trip every `POLL_INTERVAL_FRAMES`
|
||||
/// frames is cheap.
|
||||
///
|
||||
/// On a mismatch it:
|
||||
/// 1. writes the real size into `window.resolution` so the renderer
|
||||
/// reconfigures the surface and systems reading `window.width()` see
|
||||
/// the truth,
|
||||
/// 2. emits a synthetic `WindowResized` (logical pixels) so
|
||||
/// `on_window_resized` in `table_plugin` recomputes the board layout,
|
||||
/// 3. re-arms the inset poller, because a screen change almost always
|
||||
/// moves the system bars too — covering the "re-poll never fires"
|
||||
/// hole left open in #116.
|
||||
pub(super) fn refresh_surface_size(
|
||||
mut frame: Local<u32>,
|
||||
mut windows: Query<(Entity, &mut Window)>,
|
||||
mut resize_events: MessageWriter<WindowResized>,
|
||||
mut poll: ResMut<SafeAreaPollTries>,
|
||||
) {
|
||||
const POLL_INTERVAL_FRAMES: u32 = 30; // ~0.5 s @ 60 fps
|
||||
|
||||
*frame += 1;
|
||||
if !frame.is_multiple_of(POLL_INTERVAL_FRAMES) {
|
||||
return;
|
||||
}
|
||||
let Some((entity, mut window)) = windows.iter_mut().next() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (decor_w, decor_h) = match query_decor_size() {
|
||||
Ok(size) => size,
|
||||
Err(e) => {
|
||||
// One-time note; the bridge simply isn't up yet during the
|
||||
// first frames of a launch.
|
||||
if *frame == POLL_INTERVAL_FRAMES {
|
||||
warn!("safe_area: decor size query failed (will retry): {e}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
if decor_w == 0 || decor_h == 0 {
|
||||
return; // decor view not laid out yet
|
||||
}
|
||||
|
||||
// Reads go through `Deref` and do not trip change detection; only
|
||||
// mutate `window` once a mismatch is confirmed.
|
||||
let cached_w = window.resolution.physical_width();
|
||||
let cached_h = window.resolution.physical_height();
|
||||
if decor_w == cached_w && decor_h == cached_h {
|
||||
return;
|
||||
}
|
||||
|
||||
info!(
|
||||
"safe_area: decor view is {decor_w}x{decor_h} but cached resolution is \
|
||||
{cached_w}x{cached_h}; forcing relayout (fold/unfold missed by winit?)"
|
||||
);
|
||||
window.resolution.set_physical_resolution(decor_w, decor_h);
|
||||
let scale = window.scale_factor();
|
||||
resize_events.write(WindowResized {
|
||||
window: entity,
|
||||
width: decor_w as f32 / scale,
|
||||
height: decor_h as f32 / scale,
|
||||
});
|
||||
poll.0 = 0;
|
||||
}
|
||||
|
||||
/// Physical pixel size of the activity's decor view — the ground truth
|
||||
/// for the surface we are actually being displayed on, independent of
|
||||
/// whatever winit last told Bevy.
|
||||
fn query_decor_size() -> Result<(u32, u32), String> {
|
||||
use solitaire_data::android_jni;
|
||||
|
||||
android_jni::with_activity_env(|env, activity| {
|
||||
let window = env
|
||||
.call_method(activity, "getWindow", "()Landroid/view/Window;", &[])?
|
||||
.l()?;
|
||||
let decor = env
|
||||
.call_method(&window, "getDecorView", "()Landroid/view/View;", &[])?
|
||||
.l()?;
|
||||
let w = env.call_method(&decor, "getWidth", "()I", &[])?.i()?;
|
||||
let h = env.call_method(&decor, "getHeight", "()I", &[])?.i()?;
|
||||
Ok((w.max(0) as u32, h.max(0) as u32))
|
||||
})
|
||||
}
|
||||
|
||||
fn query_insets() -> Result<SafeAreaInsets, String> {
|
||||
use solitaire_data::android_jni;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user