Skip to content

Commit

Permalink
Android: Don't draw the UI under the system bars or the keyboard
Browse files Browse the repository at this point in the history
  • Loading branch information
ogoffart committed Jan 31, 2024
1 parent c31b621 commit fb78bbd
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import android.view.inputmethod.InputConnection;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.view.inputmethod.InputMethodManager;
import android.app.Activity;
import android.widget.FrameLayout;
Expand Down Expand Up @@ -194,4 +195,11 @@ public boolean dark_color_scheme() {
int nightModeFlags = mActivity.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
return nightModeFlags == Configuration.UI_MODE_NIGHT_YES;
}

// Get the geometry of the view minus the system bars and the keyboard
public Rect get_view_rect() {
Rect rect = new Rect();
mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
return rect;
}
}
109 changes: 85 additions & 24 deletions internal/backends/android-activity/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ pub use android_activity::{self, AndroidApp};
use android_activity::{InputStatus, MainEvent, PollEvent};
use core::ops::ControlFlow;
use i_slint_core::api::{EventLoopError, PhysicalPosition, PhysicalSize, PlatformError, Window};
use i_slint_core::platform::{Key, PointerEventButton, WindowAdapter, WindowEvent};
use i_slint_core::platform::{
Key, PointerEventButton, WindowAdapter, WindowEvent, WindowProperties,
};
use i_slint_core::{Property, SharedString};
use i_slint_renderer_skia::{SkiaRenderer, SkiaRendererExt};
use jni::objects::{JClass, JString};
use jni::sys::{jboolean, jint};
use jni::JNIEnv;
Expand Down Expand Up @@ -57,11 +60,13 @@ impl AndroidPlatform {
let window = Rc::<AndroidWindowAdapter>::new_cyclic(|w| AndroidWindowAdapter {
app: app.clone(),
window: Window::new(w.clone()),
renderer: i_slint_renderer_skia::SkiaRenderer::default(),
renderer: SkiaRenderer::default(),
event_queue: Default::default(),
pending_redraw: Default::default(),
dark_color_scheme,
slint_java_helper,
fullscreen: Cell::new(false),
offset: Default::default(),
});
CURRENT_WINDOW.set(Rc::downgrade(&window));
Self { app, window, event_listener: None }
Expand Down Expand Up @@ -127,8 +132,16 @@ impl i_slint_core::platform::Platform for AndroidPlatform {
{
return Ok(());
}
if self.window.pending_redraw.take() && self.app.native_window().is_some() {
self.window.renderer.render()?;
if self.window.pending_redraw.take() {
if let Some(win) = self.app.native_window() {
let o = self.window.offset.get();
self.window.renderer.render_transformed_with_post_callback(
0.,
(o.x as f32, o.y as f32),
PhysicalSize { width: win.width() as _, height: win.height() as _ },
None,
)?;
}
}
}
}
Expand Down Expand Up @@ -178,17 +191,27 @@ struct AndroidWindowAdapter {
pending_redraw: Cell<bool>,
slint_java_helper: SlintJavaHelper,
dark_color_scheme: core::pin::Pin<Box<Property<bool>>>,
fullscreen: Cell<bool>,
/// The offset at which the Slint view is drawn in the native window (account for status bar)
offset: Cell<PhysicalPosition>,
}

impl WindowAdapter for AndroidWindowAdapter {
fn window(&self) -> &Window {
&self.window
}
fn size(&self) -> PhysicalSize {
self.app.native_window().map_or_else(Default::default, |w| PhysicalSize {
width: w.width() as u32,
height: w.height() as u32,
})
if self.fullscreen.get() {
self.app.native_window().map_or_else(Default::default, |w| PhysicalSize {
width: w.width() as u32,
height: w.height() as u32,
})
} else {
self.slint_java_helper
.get_view_rect(&self.app)
.unwrap_or_else(|e| print_jni_error(&self.app, e))
.1
}
}
fn renderer(&self) -> &dyn i_slint_core::platform::Renderer {
&self.renderer
Expand All @@ -198,6 +221,13 @@ impl WindowAdapter for AndroidWindowAdapter {
self.pending_redraw.set(true);
}

fn update_window_properties(&self, properties: WindowProperties<'_>) {
let f = properties.fullscreen();
if self.fullscreen.replace(f) != f {
self.resize();
}
}

fn internal(
&self,
_: i_slint_core::InternalToken,
Expand Down Expand Up @@ -291,9 +321,6 @@ impl AndroidWindowAdapter {
if (scale_factor - self.window.scale_factor()).abs() > f32::EPSILON {
self.window
.dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor });
self.window.dispatch_event(WindowEvent::Resized {
size: size.to_logical(scale_factor),
});
}

// Safety: This is safe because the handle remains valid; the next rwh release provides `new()` without unsafe.
Expand All @@ -312,14 +339,12 @@ impl AndroidWindowAdapter {
)
};
self.renderer.set_window_handle(window_handle, display_handle, size)?;
self.resize();
}
}
PollEvent::Main(
MainEvent::WindowResized { .. } | MainEvent::ContentRectChanged { .. },
) => {
let size = self.size().to_logical(self.window.scale_factor());
self.window.dispatch_event(WindowEvent::Resized { size })
}
) => self.resize(),
PollEvent::Main(MainEvent::RedrawNeeded { .. }) => {
self.pending_redraw.set(false);
self.renderer.render()?;
Expand Down Expand Up @@ -361,23 +386,23 @@ impl AndroidWindowAdapter {
InputEvent::MotionEvent(motion_event) => match motion_event.action() {
MotionAction::Down | MotionAction::ButtonPress | MotionAction::PointerDown => {
self.window.dispatch_event(WindowEvent::PointerPressed {
position: position_for_event(motion_event)
position: position_for_event(motion_event, self.offset.get())
.to_logical(self.window.scale_factor()),
button: PointerEventButton::Left,
});
InputStatus::Handled
}
MotionAction::ButtonRelease | MotionAction::PointerUp => {
self.window.dispatch_event(WindowEvent::PointerReleased {
position: position_for_event(motion_event)
position: position_for_event(motion_event, self.offset.get())
.to_logical(self.window.scale_factor()),
button: PointerEventButton::Left,
});
InputStatus::Handled
}
MotionAction::Up => {
self.window.dispatch_event(WindowEvent::PointerReleased {
position: position_for_event(motion_event)
position: position_for_event(motion_event, self.offset.get())
.to_logical(self.window.scale_factor()),
button: PointerEventButton::Left,
});
Expand All @@ -387,7 +412,7 @@ impl AndroidWindowAdapter {
}
MotionAction::Move | MotionAction::HoverMove => {
self.window.dispatch_event(WindowEvent::PointerMoved {
position: position_for_event(motion_event)
position: position_for_event(motion_event, self.offset.get())
.to_logical(self.window.scale_factor()),
});
InputStatus::Handled
Expand Down Expand Up @@ -441,13 +466,32 @@ impl AndroidWindowAdapter {
}
}
}

fn resize(&self) {
let Some(win) = self.app.native_window() else { return };
let (offset, size) = if self.fullscreen.get() {
(
Default::default(),
PhysicalSize { width: win.width() as u32, height: win.height() as u32 },
)
} else {
self.slint_java_helper
.get_view_rect(&self.app)
.unwrap_or_else(|e| print_jni_error(&self.app, e))
};

self.window.dispatch_event(WindowEvent::Resized {
size: size.to_logical(self.window.scale_factor()),
});
self.offset.set(offset);
}
}

fn position_for_event(motion_event: &MotionEvent) -> PhysicalPosition {
motion_event
.pointers()
.next()
.map_or_else(Default::default, |p| PhysicalPosition { x: p.x() as i32, y: p.y() as i32 })
fn position_for_event(motion_event: &MotionEvent, offset: PhysicalPosition) -> PhysicalPosition {
motion_event.pointers().next().map_or_else(Default::default, |p| PhysicalPosition {
x: p.x() as i32 - offset.x,
y: p.y() as i32 - offset.y,
})
}

fn map_key_event(key_event: &android_activity::input::KeyEvent) -> Option<WindowEvent> {
Expand Down Expand Up @@ -890,6 +934,23 @@ impl SlintJavaHelper {
let helper = self.0.as_obj();
Ok(env.call_method(helper, "dark_color_scheme", "()Z", &[])?.z()?)
}

fn get_view_rect(
&self,
app: &AndroidApp,
) -> Result<(PhysicalPosition, PhysicalSize), jni::errors::Error> {
// Safety: as documented in android-activity to obtain a jni::JavaVM
let vm = unsafe { jni::JavaVM::from_raw(app.vm_as_ptr() as *mut _) }?;
let mut env = vm.attach_current_thread()?;
let helper = self.0.as_obj();
let rect =
env.call_method(helper, "get_view_rect", "()Landroid/graphics/Rect;", &[])?.l()?;
let x = env.get_field(&rect, "left", "I")?.i()?;
let y = env.get_field(&rect, "top", "I")?.i()?;
let width = env.get_field(&rect, "right", "I")?.i()? - x;
let height = env.get_field(&rect, "bottom", "I")?.i()? - y;
Ok((PhysicalPosition::new(x as _, y as _), PhysicalSize::new(width as _, height as _)))
}
}

#[no_mangle]
Expand Down

0 comments on commit fb78bbd

Please sign in to comment.