mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Add support for the safe area on iOS (#7578)
This PR is a continuation of #4915 by @frederik-uni and @lucasmerlin that introduces support for keeping egui content within the 'safe area' on iOS (avoiding the notch / dynamic island / menu bar etc.), with the following changes: - `SafeArea` now wraps `MarginF32` and has been renamed to `SafeAreaInsets` to clarify its purpose. - `InputState::screen_rect` is now marked as deprecated in favour of either `viewport_rect` (which contains the entire screen), or `content_rect` (which is the viewport rect with the safe area insets removed). - I added some comments to the safe area insets logic pointing out the [safe area API coming in winit v0.31](https://github.com/rust-windowing/winit/issues/3910). --------- Co-authored-by: frederik-uni <147479464+frederik-uni@users.noreply.github.com> Co-authored-by: Lucas Meurer <hi@lucasmerlin.me> Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
@@ -76,6 +76,24 @@ document-features = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
webbrowser = { version = "1.0.0", optional = true }
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
objc2 = { workspace = true }
|
||||
objc2-foundation = { workspace = true, features = [
|
||||
"std",
|
||||
"NSThread",
|
||||
] }
|
||||
objc2-ui-kit = { workspace = true, features = [
|
||||
"std",
|
||||
"UIApplication",
|
||||
"UIGeometry",
|
||||
"UIResponder",
|
||||
"UIScene",
|
||||
"UISceneDefinitions",
|
||||
"UIView",
|
||||
"UIWindow",
|
||||
"UIWindowScene",
|
||||
] }
|
||||
|
||||
[target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies]
|
||||
smithay-clipboard = { version = "0.7.2", optional = true }
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ use egui::{Pos2, Rect, Theme, Vec2, ViewportBuilder, ViewportCommand, ViewportId
|
||||
pub use winit;
|
||||
|
||||
pub mod clipboard;
|
||||
mod safe_area;
|
||||
mod window_settings;
|
||||
|
||||
pub use window_settings::WindowSettings;
|
||||
@@ -274,6 +275,21 @@ impl State {
|
||||
}
|
||||
|
||||
use winit::event::WindowEvent;
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
match &event {
|
||||
WindowEvent::Resized(_)
|
||||
| WindowEvent::ScaleFactorChanged { .. }
|
||||
| WindowEvent::Focused(true)
|
||||
| WindowEvent::Occluded(false) => {
|
||||
// Once winit v0.31 has been released this can be reworked to get the safe area from
|
||||
// `Window::safe_area`, and updated from a new event which is being discussed in
|
||||
// https://github.com/rust-windowing/winit/issues/3911.
|
||||
self.egui_input_mut().safe_area_insets = Some(safe_area::get_safe_area_insets());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match event {
|
||||
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
||||
let native_pixels_per_point = *scale_factor as f32;
|
||||
|
||||
56
crates/egui-winit/src/safe_area.rs
Normal file
56
crates/egui-winit/src/safe_area.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
#[cfg(target_os = "ios")]
|
||||
pub use ios::get_safe_area_insets;
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
mod ios {
|
||||
use egui::{SafeAreaInsets, epaint::MarginF32};
|
||||
use objc2::{ClassType, rc::Retained};
|
||||
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
|
||||
use objc2_ui_kit::{UIApplication, UISceneActivationState, UIWindowScene};
|
||||
|
||||
/// Gets the ios safe area insets.
|
||||
///
|
||||
/// A safe area defines the area within a view that isn’t covered by a navigation bar, tab bar,
|
||||
/// toolbar, or other views a window might provide. Safe areas are essential for avoiding a
|
||||
/// device’s interactive and display features, like Dynamic Island on iPhone or the camera
|
||||
/// housing on some Mac models.
|
||||
///
|
||||
/// Once winit v0.31 has been released this can be removed in favor of
|
||||
/// `winit::Window::safe_area`.
|
||||
pub fn get_safe_area_insets() -> SafeAreaInsets {
|
||||
let Some(main_thread_marker) = MainThreadMarker::new() else {
|
||||
log::error!("Getting safe area insets needs to be performed on the main thread");
|
||||
return SafeAreaInsets::default();
|
||||
};
|
||||
|
||||
let app = UIApplication::sharedApplication(main_thread_marker);
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
// Look for the first window scene that's in the foreground
|
||||
for scene in app.connectedScenes() {
|
||||
if scene.isKindOfClass(UIWindowScene::class())
|
||||
&& matches!(
|
||||
scene.activationState(),
|
||||
UISceneActivationState::ForegroundActive
|
||||
| UISceneActivationState::ForegroundInactive
|
||||
)
|
||||
{
|
||||
// Safe to cast, the class kind was checked above
|
||||
let window_scene = Retained::cast::<UIWindowScene>(scene.clone());
|
||||
if let Some(window) = window_scene.keyWindow() {
|
||||
let insets = window.safeAreaInsets();
|
||||
return SafeAreaInsets(MarginF32 {
|
||||
top: insets.top as f32,
|
||||
left: insets.left as f32,
|
||||
right: insets.right as f32,
|
||||
bottom: insets.bottom as f32,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SafeAreaInsets::default()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user