diff --git a/Cargo.toml b/Cargo.toml
index 8d8228685b..bbd6f8d1a1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -149,6 +149,7 @@ objc2-foundation = { version = "0.2.2", features = [
"NSDictionary",
"NSDistributedNotificationCenter",
"NSEnumerator",
+ "NSGeometry",
"NSKeyValueObserving",
"NSNotification",
"NSObjCRuntime",
@@ -296,6 +297,7 @@ web_sys = { package = "web-sys", version = "0.3.70", features = [
"FocusEvent",
"HtmlCanvasElement",
"HtmlElement",
+ "HtmlHtmlElement",
"HtmlImageElement",
"ImageBitmap",
"ImageBitmapOptions",
diff --git a/docs/res/ATTRIBUTION.md b/docs/ATTRIBUTION.md
similarity index 66%
rename from docs/res/ATTRIBUTION.md
rename to docs/ATTRIBUTION.md
index 268316f946..259a91d2bb 100644
--- a/docs/res/ATTRIBUTION.md
+++ b/docs/ATTRIBUTION.md
@@ -9,3 +9,10 @@ by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It was
originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)
License. Minor modifications have been made by [John Nunley](https://github.com/notgull),
which have been released under the same license as a derivative work.
+
+## `coordinate-systems*`
+
+These files are created by [Mads Marquart](https://github.com/madsmtm) using
+[draw.io](https://draw.io/), and compressed using [svgomg.net](https://svgomg.net/).
+
+They are licensed under the [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) license.
diff --git a/docs/coordinate-systems.drawio b/docs/coordinate-systems.drawio
new file mode 100644
index 0000000000..2bfb009883
--- /dev/null
+++ b/docs/coordinate-systems.drawio
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/res/coordinate-systems-desktop.svg b/docs/res/coordinate-systems-desktop.svg
new file mode 100644
index 0000000000..1171c53a39
--- /dev/null
+++ b/docs/res/coordinate-systems-desktop.svg
@@ -0,0 +1 @@
+
diff --git a/docs/res/coordinate-systems-mobile.svg b/docs/res/coordinate-systems-mobile.svg
new file mode 100644
index 0000000000..b1ed4c8274
--- /dev/null
+++ b/docs/res/coordinate-systems-mobile.svg
@@ -0,0 +1 @@
+
diff --git a/dpi/CHANGELOG.md b/dpi/CHANGELOG.md
index 0c4e4544f3..6dd72ea3d8 100644
--- a/dpi/CHANGELOG.md
+++ b/dpi/CHANGELOG.md
@@ -11,6 +11,8 @@ Unreleased` header.
## Unreleased
+- Added `Insets`, `LogicalInsets` and `PhysicalInsets` types.
+
## 0.1.1
- Derive `Debug`, `Copy`, `Clone`, `PartialEq`, `Serialize`, `Deserialize` traits for `PixelUnit`.
diff --git a/dpi/src/lib.rs b/dpi/src/lib.rs
index 04b7df00bc..2e6c17f57d 100644
--- a/dpi/src/lib.rs
+++ b/dpi/src/lib.rs
@@ -759,6 +759,150 @@ impl From> for Position {
}
}
+/// The logical distance between the edges of two rectangles.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+pub struct LogicalInsets
{
+ /// The distance to the top edge.
+ pub top: P,
+ /// The distance to the left edge.
+ pub left: P,
+ /// The distance to the bottom edge.
+ pub bottom: P,
+ /// The distance to the right edge.
+ pub right: P,
+}
+
+impl
{
+ /// The distance to the top edge.
+ pub top: P,
+ /// The distance to the left edge.
+ pub left: P,
+ /// The distance to the bottom edge.
+ pub bottom: P,
+ /// The distance to the right edge.
+ pub right: P,
+}
+
+impl
) -> Self {
+ Self::Logical(insets.cast())
+ }
+}
+
#[cfg(test)]
mod tests {
use std::collections::HashSet;
diff --git a/examples/window.rs b/examples/window.rs
index 6f5589f5ce..3d87e2f8d9 100644
--- a/examples/window.rs
+++ b/examples/window.rs
@@ -242,6 +242,10 @@ impl Application {
Action::ToggleResizable => window.toggle_resizable(),
Action::ToggleDecorations => window.toggle_decorations(),
Action::ToggleFullscreen => window.toggle_fullscreen(),
+ #[cfg(macos_platform)]
+ Action::ToggleSimpleFullscreen => {
+ window.window.set_simple_fullscreen(!window.window.simple_fullscreen());
+ },
Action::ToggleMaximize => window.toggle_maximize(),
Action::ToggleImeInput => window.toggle_ime(),
Action::Minimize => window.minimize(),
@@ -941,18 +945,38 @@ impl WindowState {
return Ok(());
}
- const WHITE: u32 = 0xffffffff;
- const DARK_GRAY: u32 = 0xff181818;
+ let mut buffer = self.surface.buffer_mut()?;
- let color = match self.theme {
- Theme::Light => WHITE,
- Theme::Dark => DARK_GRAY,
- };
+ // Draw a different color inside the safe area
+ let surface_size = self.window.surface_size();
+ let insets = self.window.safe_area();
+ for y in 0..surface_size.height {
+ for x in 0..surface_size.width {
+ let index = y as usize * surface_size.width as usize + x as usize;
+ if insets.left <= x
+ && x <= (surface_size.width - insets.right)
+ && insets.top <= y
+ && y <= (surface_size.height - insets.bottom)
+ {
+ // In safe area
+ buffer[index] = match self.theme {
+ Theme::Light => 0xffe8e8e8, // Light gray
+ Theme::Dark => 0xff525252, // Medium gray
+ };
+ } else {
+ // Outside safe area
+ buffer[index] = match self.theme {
+ Theme::Light => 0xffffffff, // White
+ Theme::Dark => 0xff181818, // Dark gray
+ };
+ }
+ }
+ }
- let mut buffer = self.surface.buffer_mut()?;
- buffer.fill(color);
+ // Present the buffer
self.window.pre_present_notify();
buffer.present()?;
+
Ok(())
}
@@ -989,6 +1013,8 @@ enum Action {
ToggleDecorations,
ToggleResizable,
ToggleFullscreen,
+ #[cfg(macos_platform)]
+ ToggleSimpleFullscreen,
ToggleMaximize,
Minimize,
NextCursor,
@@ -1022,6 +1048,8 @@ impl Action {
Action::ToggleDecorations => "Toggle decorations",
Action::ToggleResizable => "Toggle window resizable state",
Action::ToggleFullscreen => "Toggle fullscreen",
+ #[cfg(macos_platform)]
+ Action::ToggleSimpleFullscreen => "Toggle simple fullscreen",
Action::ToggleMaximize => "Maximize",
Action::Minimize => "Minimize",
Action::ToggleResizeIncrements => "Use resize increments when resizing window",
@@ -1164,6 +1192,8 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("Q", ModifiersState::CONTROL, Action::CloseWindow),
Binding::new("H", ModifiersState::CONTROL, Action::PrintHelp),
Binding::new("F", ModifiersState::CONTROL, Action::ToggleFullscreen),
+ #[cfg(macos_platform)]
+ Binding::new("F", ModifiersState::ALT, Action::ToggleSimpleFullscreen),
Binding::new("D", ModifiersState::CONTROL, Action::ToggleDecorations),
Binding::new("I", ModifiersState::CONTROL, Action::ToggleImeInput),
Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab),
diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md
index 6c9ac5c9e2..4e91f91773 100644
--- a/src/changelog/unreleased.md
+++ b/src/changelog/unreleased.md
@@ -73,6 +73,8 @@ changelog entry.
- Add `DeviceId::into_raw()` and `from_raw()`.
- On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env
variables to test the respective modifiers of window creation.
+- Added `Window::surface_position`, which is the position of the surface inside the window.
+- Added `Window::safe_area`, which describes the area of the surface that is unobstructed.
### Changed
@@ -157,7 +159,7 @@ changelog entry.
identify a finger in a multi-touch interaction. Replaces the old `Touch::id`.
- In the same spirit rename `DeviceEvent::MouseMotion` to `PointerMotion`.
- Remove `Force::Calibrated::altitude_angle`.
- - On X11, use bottom-right corner for IME hotspot in `Window::set_ime_cursor_area`.
+- On X11, use bottom-right corner for IME hotspot in `Window::set_ime_cursor_area`.
### Removed
@@ -189,6 +191,7 @@ changelog entry.
- Remove `WindowEvent::Touch` and `Touch` in favor of the new `PointerKind`, `PointerSource` and
`ButtonSource` as part of the new pointer event overhaul.
- Remove `Force::altitude_angle`.
+- Removed `Window::inner_position`, use the new `Window::surface_position` instead.
### Fixed
@@ -201,4 +204,5 @@ changelog entry.
- On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`.
- On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again.
- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again.
-- On X11, fix XInput handling that prevented a new window from getting the focus in some cases.
\ No newline at end of file
+- On X11, fix XInput handling that prevented a new window from getting the focus in some cases.
+- On iOS, fixed `SurfaceResized` and `Window::surface_size` not reporting the size of the actual surface.
diff --git a/src/event.rs b/src/event.rs
index 7c56417d4a..84837dbc76 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -156,7 +156,10 @@ pub enum WindowEvent {
/// [`Window::surface_size`]: crate::window::Window::surface_size
SurfaceResized(PhysicalSize),
- /// The position of the window has changed. Contains the window's new position.
+ /// The position of the window has changed.
+ ///
+ /// Contains the window's new position in desktop coordinates (can also be retrieved with
+ /// [`Window::outer_position`]).
///
/// ## Platform-specific
///
@@ -469,13 +472,15 @@ pub enum WindowEvent {
/// Emitted when a window should be redrawn.
///
- /// This gets triggered in two scenarios:
+ /// This gets triggered in a few scenarios:
/// - The OS has performed an operation that's invalidated the window's contents (such as
- /// resizing the window).
+ /// resizing the window, or changing [the safe area]).
/// - The application has explicitly requested a redraw via [`Window::request_redraw`].
///
/// Winit will aggregate duplicate redraw requests into a single event, to
/// help avoid duplicating rendering work.
+ ///
+ /// [the safe area]: crate::window::Window::safe_area
RedrawRequested,
}
diff --git a/src/lib.rs b/src/lib.rs
index bda0a32ba9..6502d97cbd 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -123,6 +123,45 @@
//! [`visible` set to `false`][crate::window::WindowAttributes::with_visible] and explicitly make
//! the window visible only once you're ready to render into it.
//!
+//! There is another important concept you need to know about when drawing: the "safe area". This
+//! can be accessed with [`Window::safe_area`], and describes a rectangle in the surface that is not
+//! obscured by notches, the status bar, and so on. You should be drawing your background and
+//! non-important content on the entire surface, but restrict important content (such as
+//! interactable UIs, text, etc.) to only being drawn inside the safe area.
+//!
+//! [`Window::safe_area`]: crate::window::Window::safe_area
+//!
+//! # Coordinate systems
+//!
+//! Windowing systems use many different coordinate systems, and this is reflected in Winit as well;
+//! there are "desktop coordinates", which is the coordinates of a window or monitor relative to the
+//! desktop at large, "window coordinates" which is the coordinates of the surface, relative to the
+//! window, and finally "surface coordinates", which is the coordinates relative to the drawn
+//! surface. All of these coordinates are relative to the top-left corner of their respective
+//! origin.
+//!
+//! Most of the functionality in Winit works with surface coordinates, so usually you only need to
+//! concern yourself with those. In case you need to convert to some other coordinate system, Winit
+//! provides [`Window::surface_position`] and [`Window::surface_size`] to describe the surface's
+//! location in window coordinates, and Winit provides [`Window::outer_position`] and
+//! [`Window::outer_size`] to describe the window's location in desktop coordinates. Using these
+//! methods, you should be able to convert a position in one coordinate system to another.
+//!
+//! An overview of how these four methods fit together can be seen in the image below:
+#![doc = concat!("\n\n", include_str!("../docs/res/coordinate-systems-desktop.svg"), "\n\n")] // Rustfmt removes \n, adding them like this works around that.
+//! On mobile, the situation is usually a bit different; because of the smaller screen space,
+//! windows usually fill the whole screen at a time, and as such there is _rarely_ a difference
+//! between these three coordinate systems, although you should still strive to handle this, as
+//! they're still relevant in more niche area such as Mac Catalyst, or multi-tasking on tablets.
+//!
+//! This is illustrated in the image below, along with the safe area since it's often relevant on
+//! mobile.
+#![doc = concat!("\n\n", include_str!("../docs/res/coordinate-systems-mobile.svg"), "\n\n")] // Rustfmt removes \n, adding them like this works around that.
+//! [`Window::surface_position`]: crate::window::Window::surface_position
+//! [`Window::surface_size`]: crate::window::Window::surface_size
+//! [`Window::outer_position`]: crate::window::Window::outer_position
+//! [`Window::outer_size`]: crate::window::Window::outer_size
+//!
//! # UI scaling
//!
//! UI scaling is important, go read the docs for the [`dpi`] crate for an
diff --git a/src/monitor.rs b/src/monitor.rs
index 898f9b2003..aeb64b4a01 100644
--- a/src/monitor.rs
+++ b/src/monitor.rs
@@ -141,8 +141,11 @@ impl MonitorHandle {
self.inner.name()
}
- /// Returns the top-left corner position of the monitor relative to the larger full
- /// screen area.
+ /// Returns the top-left corner position of the monitor in desktop coordinates.
+ ///
+ /// This position is in the same coordinate system as [`Window::outer_position`].
+ ///
+ /// [`Window::outer_position`]: crate::window::Window::outer_position
///
/// ## Platform-specific
///
diff --git a/src/platform/macos.rs b/src/platform/macos.rs
index beeee0148d..79c0d57c8b 100644
--- a/src/platform/macos.rs
+++ b/src/platform/macos.rs
@@ -92,6 +92,9 @@ pub trait WindowExtMacOS {
/// This is how fullscreen used to work on macOS in versions before Lion.
/// And allows the user to have a fullscreen window without using another
/// space or taking control over the entire monitor.
+ ///
+ /// Make sure you only draw your important content inside the safe area so that it does not
+ /// overlap with the notch on newer devices, see [`Window::safe_area`] for details.
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
/// Returns whether or not the window has shadow.
diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs
index 713c7cb700..0d07506a02 100644
--- a/src/platform_impl/android/mod.rs
+++ b/src/platform_impl/android/mod.rs
@@ -13,7 +13,7 @@ use tracing::{debug, trace, warn};
use crate::application::ApplicationHandler;
use crate::cursor::Cursor;
-use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
+use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event::{self, DeviceId, FingerId, Force, StartCause, SurfaceSizeWriter};
use crate::event_loop::{
@@ -833,8 +833,8 @@ impl CoreWindow for Window {
fn pre_present_notify(&self) {}
- fn inner_position(&self) -> Result, RequestError> {
- Err(NotSupportedError::new("inner_position is not supported").into())
+ fn surface_position(&self) -> PhysicalPosition {
+ (0, 0).into()
}
fn outer_position(&self) -> Result, RequestError> {
@@ -857,6 +857,10 @@ impl CoreWindow for Window {
screen_size(&self.app)
}
+ fn safe_area(&self) -> PhysicalInsets {
+ PhysicalInsets::new(0, 0, 0, 0)
+ }
+
fn set_min_surface_size(&self, _: Option) {}
fn set_max_surface_size(&self, _: Option) {}
diff --git a/src/platform_impl/apple/appkit/window.rs b/src/platform_impl/apple/appkit/window.rs
index e70f5934b8..abc3bb2f48 100644
--- a/src/platform_impl/apple/appkit/window.rs
+++ b/src/platform_impl/apple/appkit/window.rs
@@ -107,12 +107,12 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.reset_dead_keys());
}
- fn inner_position(&self) -> Result, RequestError> {
- Ok(self.maybe_wait_on_main(|delegate| delegate.inner_position()))
+ fn surface_position(&self) -> dpi::PhysicalPosition {
+ self.maybe_wait_on_main(|delegate| delegate.surface_position())
}
fn outer_position(&self) -> Result, RequestError> {
- Ok(self.maybe_wait_on_main(|delegate| delegate.outer_position()))
+ self.maybe_wait_on_main(|delegate| delegate.outer_position())
}
fn set_outer_position(&self, position: Position) {
@@ -131,6 +131,10 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.outer_size())
}
+ fn safe_area(&self) -> dpi::PhysicalInsets {
+ self.maybe_wait_on_main(|delegate| delegate.safe_area())
+ }
+
fn set_min_surface_size(&self, min_size: Option) {
self.maybe_wait_on_main(|delegate| delegate.set_min_surface_size(min_size))
}
diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs
index ddbc371bda..62707277e8 100644
--- a/src/platform_impl/apple/appkit/window_delegate.rs
+++ b/src/platform_impl/apple/appkit/window_delegate.rs
@@ -6,7 +6,7 @@ use std::ptr;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
-use core_graphics::display::{CGDisplay, CGPoint};
+use core_graphics::display::CGDisplay;
use monitor::VideoModeHandle;
use objc2::rc::{autoreleasepool, Retained};
use objc2::runtime::{AnyObject, ProtocolObject};
@@ -21,10 +21,10 @@ use objc2_app_kit::{
NSWindowToolbarStyle,
};
use objc2_foundation::{
- ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSKeyValueChangeKey,
- NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSObject,
- NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint,
- NSRect, NSSize, NSString,
+ ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSEdgeInsets,
+ NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey,
+ NSKeyValueObservingOptions, NSObject, NSObjectNSDelayedPerforming,
+ NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSRect, NSSize, NSString,
};
use tracing::{trace, warn};
@@ -35,7 +35,10 @@ use super::observer::RunLoop;
use super::view::WinitView;
use super::window::WinitWindow;
use super::{ffi, Fullscreen, MonitorHandle};
-use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
+use crate::dpi::{
+ LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
+ Position, Size,
+};
use crate::error::{NotSupportedError, RequestError};
use crate::event::{SurfaceSizeWriter, WindowEvent};
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
@@ -442,9 +445,15 @@ declare_class!(
// NOTE: We don't _really_ need to check the key path, as there should only be one, but
// in the future we might want to observe other key paths.
if key_path == Some(ns_string!("effectiveAppearance")) {
- let change = change.expect("requested a change dictionary in `addObserver`, but none was provided");
- let old = change.get(unsafe { NSKeyValueChangeOldKey }).expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`");
- let new = change.get(unsafe { NSKeyValueChangeNewKey }).expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");
+ let change = change.expect(
+ "requested a change dictionary in `addObserver`, but none was provided",
+ );
+ let old = change
+ .get(unsafe { NSKeyValueChangeOldKey })
+ .expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`");
+ let new = change
+ .get(unsafe { NSKeyValueChangeNewKey })
+ .expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");
// SAFETY: The value of `effectiveAppearance` is `NSAppearance`
let old: *const AnyObject = old;
@@ -561,6 +570,12 @@ fn new_window(
}
if attrs.platform_specific.fullsize_content_view {
+ // NOTE: If we decide to add an option to change this at runtime, we must emit a
+ // `SurfaceResized` event to let applications know that the safe area changed.
+ //
+ // An alternative would be to add a `WindowEvent::SafeAreaChanged` event, this could be
+ // done with an observer on `safeAreaRect` / `contentLayoutRect`, see:
+ //
masks |= NSWindowStyleMask::FullSizeContentView;
}
@@ -934,15 +949,15 @@ impl WindowDelegate {
#[inline]
pub fn pre_present_notify(&self) {}
- pub fn outer_position(&self) -> PhysicalPosition {
+ pub fn outer_position(&self) -> Result, RequestError> {
let position = flip_window_screen_coordinates(self.window().frame());
- LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor())
+ Ok(LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor()))
}
- pub fn inner_position(&self) -> PhysicalPosition {
+ pub fn surface_position(&self) -> PhysicalPosition {
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
- let position = flip_window_screen_coordinates(content_rect);
- LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor())
+ let logical = LogicalPosition::new(content_rect.origin.x, content_rect.origin.y);
+ logical.to_physical(self.scale_factor())
}
pub fn set_outer_position(&self, position: Position) {
@@ -968,6 +983,32 @@ impl WindowDelegate {
logical.to_physical(self.scale_factor())
}
+ pub fn safe_area(&self) -> PhysicalInsets {
+ // Only available on macOS 11.0
+ let insets = if self.view().respondsToSelector(sel!(safeAreaInsets)) {
+ // Includes NSWindowStyleMask::FullSizeContentView by default, and the notch because
+ // we've set it up with `additionalSafeAreaInsets`.
+ unsafe { self.view().safeAreaInsets() }
+ } else {
+ let content_rect = self.window().contentRectForFrameRect(self.window().frame());
+ // Includes NSWindowStyleMask::FullSizeContentView
+ // Convert from window coordinates to view coordinates
+ let safe_rect = unsafe {
+ self.view().convertRect_fromView(self.window().contentLayoutRect(), None)
+ };
+ NSEdgeInsets {
+ top: safe_rect.origin.y - content_rect.origin.y,
+ left: safe_rect.origin.x - content_rect.origin.x,
+ bottom: (content_rect.size.height + content_rect.origin.x)
+ - (safe_rect.size.height + safe_rect.origin.x),
+ right: (content_rect.size.width + content_rect.origin.y)
+ - (safe_rect.size.width + safe_rect.origin.y),
+ }
+ };
+ let insets = LogicalInsets::new(insets.top, insets.left, insets.bottom, insets.right);
+ insets.to_physical(self.scale_factor())
+ }
+
#[inline]
pub fn request_surface_size(&self, size: Size) -> Option> {
let scale_factor = self.scale_factor();
@@ -1164,13 +1205,12 @@ impl WindowDelegate {
#[inline]
pub fn set_cursor_position(&self, cursor_position: Position) -> Result<(), RequestError> {
- let physical_window_position = self.inner_position();
- let scale_factor = self.scale_factor();
- let window_position = physical_window_position.to_logical::(scale_factor);
- let logical_cursor_position = cursor_position.to_logical::(scale_factor);
- let point = CGPoint {
- x: logical_cursor_position.x + window_position.x,
- y: logical_cursor_position.y + window_position.y,
+ let content_rect = self.window().contentRectForFrameRect(self.window().frame());
+ let window_position = flip_window_screen_coordinates(content_rect);
+ let cursor_position = cursor_position.to_logical::(self.scale_factor());
+ let point = core_graphics::display::CGPoint {
+ x: window_position.x + cursor_position.x,
+ y: window_position.y + cursor_position.y,
};
CGDisplay::warp_mouse_cursor_position(point)
.map_err(|status| os_error!(format!("CGError {status}")))?;
@@ -1752,12 +1792,15 @@ impl WindowExtMacOS for WindowDelegate {
let screen = self.window().screen().expect("expected screen to be available");
self.window().setFrame_display(screen.frame(), true);
+ // Configure the safe area rectangle, to ensure that we don't obscure the notch.
+ if NSScreen::class().responds_to(sel!(safeAreaInsets)) {
+ unsafe { self.view().setAdditionalSafeAreaInsets(screen.safeAreaInsets()) };
+ }
+
// Fullscreen windows can't be resized, minimized, or moved
self.toggle_style_mask(NSWindowStyleMask::Miniaturizable, false);
self.toggle_style_mask(NSWindowStyleMask::Resizable, false);
self.window().setMovable(false);
-
- true
} else {
let new_mask = self.saved_style();
self.set_style_mask(new_mask);
@@ -1770,11 +1813,22 @@ impl WindowExtMacOS for WindowDelegate {
app.setPresentationOptions(presentation_opts);
}
+ if NSScreen::class().responds_to(sel!(safeAreaInsets)) {
+ unsafe {
+ self.view().setAdditionalSafeAreaInsets(NSEdgeInsets {
+ top: 0.0,
+ left: 0.0,
+ bottom: 0.0,
+ right: 0.0,
+ });
+ }
+ }
+
self.window().setFrame_display(frame, true);
self.window().setMovable(true);
-
- true
}
+
+ true
}
#[inline]
diff --git a/src/platform_impl/apple/uikit/view.rs b/src/platform_impl/apple/uikit/view.rs
index 21fa353e4d..a74cfcbb78 100644
--- a/src/platform_impl/apple/uikit/view.rs
+++ b/src/platform_impl/apple/uikit/view.rs
@@ -6,11 +6,12 @@ use objc2::runtime::{NSObjectProtocol, ProtocolObject};
use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet, NSString};
use objc2_ui_kit::{
- UICoordinateSpace, UIEvent, UIForceTouchCapability, UIGestureRecognizer,
- UIGestureRecognizerDelegate, UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer,
- UIPinchGestureRecognizer, UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer,
- UITextInputTraits, UITouch, UITouchPhase, UITouchType, UITraitEnvironment, UIView,
+ UIEvent, UIForceTouchCapability, UIGestureRecognizer, UIGestureRecognizerDelegate,
+ UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer, UIPinchGestureRecognizer,
+ UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer, UITextInputTraits, UITouch,
+ UITouchPhase, UITouchType, UITraitEnvironment, UIView,
};
+use tracing::debug;
use super::app_state::{self, EventWrapper};
use super::window::WinitUIWindow;
@@ -72,26 +73,15 @@ declare_class!(
let mtm = MainThreadMarker::new().unwrap();
let _: () = unsafe { msg_send![super(self), layoutSubviews] };
- let window = self.window().unwrap();
- let window_bounds = window.bounds();
- let screen = window.screen();
- let screen_space = screen.coordinateSpace();
- let screen_frame = self.convertRect_toCoordinateSpace(window_bounds, &screen_space);
- let scale_factor = screen.scale();
+ let frame = self.frame();
+ let scale_factor = self.contentScaleFactor() as f64;
let size = crate::dpi::LogicalSize {
- width: screen_frame.size.width as f64,
- height: screen_frame.size.height as f64,
- }
- .to_physical(scale_factor as f64);
-
- // If the app is started in landscape, the view frame and window bounds can be mismatched.
- // The view frame will be in portrait and the window bounds in landscape. So apply the
- // window bounds to the view frame to make it consistent.
- let view_frame = self.frame();
- if view_frame != window_bounds {
- self.setFrame(window_bounds);
+ width: frame.size.width as f64,
+ height: frame.size.height as f64,
}
+ .to_physical(scale_factor);
+ let window = self.window().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
@@ -126,13 +116,10 @@ declare_class!(
"invalid scale_factor set on UIView",
);
let scale_factor = scale_factor as f64;
- let bounds = self.bounds();
- let screen = window.screen();
- let screen_space = screen.coordinateSpace();
- let screen_frame = self.convertRect_toCoordinateSpace(bounds, &screen_space);
+ let frame = self.frame();
let size = crate::dpi::LogicalSize {
- width: screen_frame.size.width as f64,
- height: screen_frame.size.height as f64,
+ width: frame.size.width as f64,
+ height: frame.size.height as f64,
};
let window_id = window.id();
app_state::handle_nonuser_events(
@@ -153,6 +140,13 @@ declare_class!(
);
}
+ #[method(safeAreaInsetsDidChange)]
+ fn safe_area_changed(&self) {
+ debug!("safeAreaInsetsDidChange was called, requesting redraw");
+ // When the safe area changes we want to make sure to emit a redraw event
+ self.setNeedsDisplay();
+ }
+
#[method(touchesBegan:withEvent:)]
fn touches_began(&self, touches: &NSSet, _event: Option<&UIEvent>) {
self.handle_touches(touches)
diff --git a/src/platform_impl/apple/uikit/window.rs b/src/platform_impl/apple/uikit/window.rs
index be4bef4536..e992e8072e 100644
--- a/src/platform_impl/apple/uikit/window.rs
+++ b/src/platform_impl/apple/uikit/window.rs
@@ -8,8 +8,8 @@ use objc2_foundation::{
CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker, NSObject, NSObjectProtocol,
};
use objc2_ui_kit::{
- UIApplication, UICoordinateSpace, UIResponder, UIScreen, UIScreenOverscanCompensation,
- UIViewController, UIWindow,
+ UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen,
+ UIScreenOverscanCompensation, UIViewController, UIWindow,
};
use tracing::{debug, warn};
@@ -18,7 +18,10 @@ use super::view::WinitView;
use super::view_controller::WinitViewController;
use super::{app_state, monitor, ActiveEventLoop, Fullscreen, MonitorHandle};
use crate::cursor::Cursor;
-use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
+use crate::dpi::{
+ LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
+ Position, Size,
+};
use crate::error::{NotSupportedError, RequestError};
use crate::event::{Event, WindowEvent};
use crate::icon::Icon;
@@ -158,20 +161,19 @@ impl Inner {
pub fn pre_present_notify(&self) {}
- pub fn inner_position(&self) -> PhysicalPosition {
- let safe_area = self.safe_area_screen_space();
+ pub fn surface_position(&self) -> PhysicalPosition {
+ let view_position = self.view.frame().origin;
let position =
- LogicalPosition { x: safe_area.origin.x as f64, y: safe_area.origin.y as f64 };
- let scale_factor = self.scale_factor();
- position.to_physical(scale_factor)
+ unsafe { self.window.convertPoint_fromView(view_position, Some(&self.view)) };
+ let position = LogicalPosition::new(position.x, position.y);
+ position.to_physical(self.scale_factor())
}
- pub fn outer_position(&self) -> PhysicalPosition {
+ pub fn outer_position(&self) -> Result, RequestError> {
let screen_frame = self.screen_frame();
let position =
LogicalPosition { x: screen_frame.origin.x as f64, y: screen_frame.origin.y as f64 };
- let scale_factor = self.scale_factor();
- position.to_physical(scale_factor)
+ Ok(position.to_physical(self.scale_factor()))
}
pub fn set_outer_position(&self, physical_position: Position) {
@@ -187,29 +189,36 @@ impl Inner {
}
pub fn surface_size(&self) -> PhysicalSize {
- let scale_factor = self.scale_factor();
- let safe_area = self.safe_area_screen_space();
- let size = LogicalSize {
- width: safe_area.size.width as f64,
- height: safe_area.size.height as f64,
- };
- size.to_physical(scale_factor)
+ let frame = self.view.frame();
+ let size = LogicalSize::new(frame.size.width, frame.size.height);
+ size.to_physical(self.scale_factor())
}
pub fn outer_size(&self) -> PhysicalSize {
- let scale_factor = self.scale_factor();
- let screen_frame = self.screen_frame();
- let size = LogicalSize {
- width: screen_frame.size.width as f64,
- height: screen_frame.size.height as f64,
- };
- size.to_physical(scale_factor)
+ let frame = self.window.frame();
+ let size = LogicalSize::new(frame.size.width, frame.size.height);
+ size.to_physical(self.scale_factor())
}
pub fn request_surface_size(&self, _size: Size) -> Option> {
Some(self.surface_size())
}
+ pub fn safe_area(&self) -> PhysicalInsets {
+ // Only available on iOS 11.0
+ let insets = if app_state::os_capabilities().safe_area {
+ self.view.safeAreaInsets()
+ } else {
+ // Assume the status bar frame is the only thing that obscures the view
+ let app = UIApplication::sharedApplication(MainThreadMarker::new().unwrap());
+ #[allow(deprecated)]
+ let status_bar_frame = app.statusBarFrame();
+ UIEdgeInsets { top: status_bar_frame.size.height, left: 0.0, bottom: 0.0, right: 0.0 }
+ };
+ let insets = LogicalInsets::new(insets.top, insets.left, insets.bottom, insets.right);
+ insets.to_physical(self.scale_factor())
+ }
+
pub fn set_min_surface_size(&self, _dimensions: Option) {
warn!("`Window::set_min_surface_size` is ignored on iOS")
}
@@ -513,14 +522,9 @@ impl Window {
let scale_factor = view.contentScaleFactor();
let scale_factor = scale_factor as f64;
if scale_factor != 1.0 {
- let bounds = view.bounds();
- let screen = window.screen();
- let screen_space = screen.coordinateSpace();
- let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space);
- let size = LogicalSize {
- width: screen_frame.size.width as f64,
- height: screen_frame.size.height as f64,
- };
+ let frame = view.frame();
+ let size =
+ LogicalSize { width: frame.size.width as f64, height: frame.size.height as f64 };
app_state::handle_nonuser_events(
mtm,
std::iter::once(EventWrapper::ScaleFactorChanged(app_state::ScaleFactorChanged {
@@ -599,12 +603,12 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.reset_dead_keys());
}
- fn inner_position(&self) -> Result, RequestError> {
- Ok(self.maybe_wait_on_main(|delegate| delegate.inner_position()))
+ fn surface_position(&self) -> PhysicalPosition {
+ self.maybe_wait_on_main(|delegate| delegate.surface_position())
}
fn outer_position(&self) -> Result, RequestError> {
- Ok(self.maybe_wait_on_main(|delegate| delegate.outer_position()))
+ self.maybe_wait_on_main(|delegate| delegate.outer_position())
}
fn set_outer_position(&self, position: Position) {
@@ -623,6 +627,10 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.outer_size())
}
+ fn safe_area(&self) -> PhysicalInsets {
+ self.maybe_wait_on_main(|delegate| delegate.safe_area())
+ }
+
fn set_min_surface_size(&self, min_size: Option) {
self.maybe_wait_on_main(|delegate| delegate.set_min_surface_size(min_size))
}
@@ -881,7 +889,7 @@ impl Inner {
impl Inner {
fn screen_frame(&self) -> CGRect {
- self.rect_to_screen_space(self.window.bounds())
+ self.rect_to_screen_space(self.window.frame())
}
fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
@@ -893,43 +901,6 @@ impl Inner {
let screen_space = self.window.screen().coordinateSpace();
self.window.convertRect_fromCoordinateSpace(rect, &screen_space)
}
-
- fn safe_area_screen_space(&self) -> CGRect {
- let bounds = self.window.bounds();
- if app_state::os_capabilities().safe_area {
- let safe_area = self.window.safeAreaInsets();
- let safe_bounds = CGRect {
- origin: CGPoint {
- x: bounds.origin.x + safe_area.left,
- y: bounds.origin.y + safe_area.top,
- },
- size: CGSize {
- width: bounds.size.width - safe_area.left - safe_area.right,
- height: bounds.size.height - safe_area.top - safe_area.bottom,
- },
- };
- self.rect_to_screen_space(safe_bounds)
- } else {
- let screen_frame = self.rect_to_screen_space(bounds);
- let status_bar_frame = {
- let app = UIApplication::sharedApplication(MainThreadMarker::new().unwrap());
- #[allow(deprecated)]
- app.statusBarFrame()
- };
- let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height {
- (screen_frame.origin.y, screen_frame.size.height)
- } else {
- let y = status_bar_frame.size.height;
- let height = screen_frame.size.height
- - (status_bar_frame.size.height - screen_frame.origin.y);
- (y, height)
- };
- CGRect {
- origin: CGPoint { x: screen_frame.origin.x, y },
- size: CGSize { width: screen_frame.size.width, height },
- }
- }
- }
}
#[derive(Clone, Debug, Default, PartialEq)]
diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs
index 725cb72bcb..cb7317737a 100644
--- a/src/platform_impl/linux/wayland/window/mod.rs
+++ b/src/platform_impl/linux/wayland/window/mod.rs
@@ -17,7 +17,7 @@ use super::output::MonitorHandle;
use super::state::WinitState;
use super::types::xdg_activation::XdgActivationTokenData;
use super::ActiveEventLoop;
-use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
+use crate::dpi::{LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{NotSupportedError, RequestError};
use crate::event::{Ime, WindowEvent};
use crate::event_loop::AsyncRequestSerial;
@@ -303,9 +303,8 @@ impl CoreWindow for Window {
crate::platform_impl::common::xkb::reset_dead_keys()
}
- fn inner_position(&self) -> Result, RequestError> {
- Err(NotSupportedError::new("window position information is not available on Wayland")
- .into())
+ fn surface_position(&self) -> PhysicalPosition {
+ (0, 0).into()
}
fn outer_position(&self) -> Result, RequestError> {
@@ -336,6 +335,10 @@ impl CoreWindow for Window {
super::logical_to_physical_rounded(window_state.outer_size(), scale_factor)
}
+ fn safe_area(&self) -> PhysicalInsets {
+ PhysicalInsets::new(0, 0, 0, 0)
+ }
+
fn set_min_surface_size(&self, min_size: Option) {
let scale_factor = self.scale_factor();
let min_size = min_size.map(|size| size.to_logical(scale_factor));
diff --git a/src/platform_impl/linux/x11/util/geometry.rs b/src/platform_impl/linux/x11/util/geometry.rs
index 70a286b81a..c935d162f7 100644
--- a/src/platform_impl/linux/x11/util/geometry.rs
+++ b/src/platform_impl/linux/x11/util/geometry.rs
@@ -67,15 +67,20 @@ pub struct FrameExtentsHeuristic {
}
impl FrameExtentsHeuristic {
- pub fn inner_pos_to_outer(&self, x: i32, y: i32) -> (i32, i32) {
+ pub fn surface_position(&self) -> (i32, i32) {
use self::FrameExtentsHeuristicPath::*;
if self.heuristic_path != UnsupportedBordered {
- (x - self.frame_extents.left as i32, y - self.frame_extents.top as i32)
+ (self.frame_extents.left as i32, self.frame_extents.top as i32)
} else {
- (x, y)
+ (0, 0)
}
}
+ pub fn inner_pos_to_outer(&self, x: i32, y: i32) -> (i32, i32) {
+ let (left, top) = self.surface_position();
+ (x - left, y - top)
+ }
+
pub fn surface_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) {
(
width.saturating_add(
diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs
index 004e8aa038..e3a72ad228 100644
--- a/src/platform_impl/linux/x11/window.rs
+++ b/src/platform_impl/linux/x11/window.rs
@@ -21,7 +21,7 @@ use super::{
ffi, ActiveEventLoop, CookieResultExt, ImeRequest, ImeSender, VoidCookie, XConnection,
};
use crate::cursor::{Cursor, CustomCursor as RootCustomCursor};
-use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
+use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{NotSupportedError, RequestError};
use crate::event::{Event, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::AsyncRequestSerial;
@@ -82,8 +82,8 @@ impl CoreWindow for Window {
common::xkb::reset_dead_keys();
}
- fn inner_position(&self) -> Result, RequestError> {
- self.0.inner_position()
+ fn surface_position(&self) -> PhysicalPosition {
+ self.0.surface_position()
}
fn outer_position(&self) -> Result, RequestError> {
@@ -106,6 +106,10 @@ impl CoreWindow for Window {
self.0.outer_size()
}
+ fn safe_area(&self) -> PhysicalInsets {
+ self.0.safe_area()
+ }
+
fn set_min_surface_size(&self, min_size: Option) {
self.0.set_min_surface_size(min_size)
}
@@ -1508,7 +1512,7 @@ impl UnownedWindow {
}
}
- pub(crate) fn inner_position_physical(&self) -> (i32, i32) {
+ fn inner_position_physical(&self) -> (i32, i32) {
// This should be okay to unwrap since the only error XTranslateCoordinates can return
// is BadWindow, and if the window handle is bad we have bigger problems.
self.xconn
@@ -1518,8 +1522,14 @@ impl UnownedWindow {
}
#[inline]
- pub fn inner_position(&self) -> Result, RequestError> {
- Ok(self.inner_position_physical().into())
+ pub fn surface_position(&self) -> PhysicalPosition {
+ let extents = self.shared_state_lock().frame_extents.clone();
+ if let Some(extents) = extents {
+ extents.surface_position().into()
+ } else {
+ self.update_cached_frame_extents();
+ self.surface_position()
+ }
}
pub(crate) fn set_position_inner(
@@ -1582,6 +1592,10 @@ impl UnownedWindow {
}
}
+ fn safe_area(&self) -> PhysicalInsets {
+ PhysicalInsets::new(0, 0, 0, 0)
+ }
+
pub(crate) fn request_surface_size_physical(&self, width: u32, height: u32) {
self.xconn
.xcb_connection()
@@ -1989,7 +2003,7 @@ impl UnownedWindow {
.query_pointer(self.xwindow, util::VIRTUAL_CORE_POINTER)
.map_err(|err| os_error!(err))?;
- let window_position = self.inner_position()?;
+ let window_position = self.inner_position_physical();
let atoms = self.xconn.atoms();
let message = atoms[_NET_WM_MOVERESIZE];
@@ -2016,8 +2030,8 @@ impl UnownedWindow {
| xproto::EventMask::SUBSTRUCTURE_NOTIFY,
),
[
- (window_position.x + xinput_fp1616_to_float(pointer.win_x) as i32) as u32,
- (window_position.y + xinput_fp1616_to_float(pointer.win_y) as i32) as u32,
+ (window_position.0 + xinput_fp1616_to_float(pointer.win_x) as i32) as u32,
+ (window_position.1 + xinput_fp1616_to_float(pointer.win_y) as i32) as u32,
action.try_into().unwrap(),
1, // Button 1
1,
diff --git a/src/platform_impl/orbital/window.rs b/src/platform_impl/orbital/window.rs
index 76c698363d..c800566ca5 100644
--- a/src/platform_impl/orbital/window.rs
+++ b/src/platform_impl/orbital/window.rs
@@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex};
use super::event_loop::EventLoopProxy;
use super::{ActiveEventLoop, MonitorHandle, RedoxSocket, WindowProperties};
use crate::cursor::Cursor;
-use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
+use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{NotSupportedError, RequestError};
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::window::{self, Fullscreen, ImePurpose, Window as CoreWindow, WindowId};
@@ -198,17 +198,17 @@ impl CoreWindow for Window {
}
#[inline]
- fn inner_position(&self) -> Result, RequestError> {
- let mut buf: [u8; 4096] = [0; 4096];
- let path = self.window_socket.fpath(&mut buf).expect("failed to read properties");
- let properties = WindowProperties::new(path);
- Ok((properties.x, properties.y).into())
+ fn surface_position(&self) -> PhysicalPosition {
+ // TODO: adjust for window decorations
+ (0, 0).into()
}
#[inline]
fn outer_position(&self) -> Result, RequestError> {
- // TODO: adjust for window decorations
- self.inner_position()
+ let mut buf: [u8; 4096] = [0; 4096];
+ let path = self.window_socket.fpath(&mut buf).expect("failed to read properties");
+ let properties = WindowProperties::new(path);
+ Ok((properties.x, properties.y).into())
}
#[inline]
@@ -239,6 +239,10 @@ impl CoreWindow for Window {
self.surface_size()
}
+ fn safe_area(&self) -> PhysicalInsets {
+ PhysicalInsets::new(0, 0, 0, 0)
+ }
+
#[inline]
fn set_min_surface_size(&self, _: Option) {}
diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs
index 39523e18bb..a7a8e920de 100644
--- a/src/platform_impl/web/event_loop/runner.rs
+++ b/src/platform_impl/web/event_loop/runner.rs
@@ -20,7 +20,7 @@ use crate::dpi::PhysicalSize;
use crate::event::{DeviceEvent, ElementState, Event, RawKeyEvent, StartCause, WindowEvent};
use crate::event_loop::{ControlFlow, DeviceEvents};
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
-use crate::platform_impl::platform::backend::EventListenerHandle;
+use crate::platform_impl::platform::backend::{EventListenerHandle, SafeAreaHandle};
use crate::platform_impl::platform::r#async::DispatchRunner;
use crate::platform_impl::platform::window::Inner;
use crate::window::WindowId;
@@ -57,6 +57,7 @@ struct Execution {
redraw_pending: RefCell>,
destroy_pending: RefCell>,
pub(crate) monitor: Rc,
+ safe_area: Rc,
page_transition_event_handle: RefCell