diff --git a/Cargo.toml b/Cargo.toml
index ea54b49b27..2e67b79f31 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -150,6 +150,7 @@ objc2-foundation = { version = "0.2.2", features = [
"NSDictionary",
"NSDistributedNotificationCenter",
"NSEnumerator",
+ "NSGeometry",
"NSKeyValueObserving",
"NSNotification",
"NSObjCRuntime",
diff --git a/docs/res/ATTRIBUTION.md b/docs/res/ATTRIBUTION.md
index 268316f946..259a91d2bb 100644
--- a/docs/res/ATTRIBUTION.md
+++ b/docs/res/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/res/coordinate-systems-desktop.svg b/docs/res/coordinate-systems-desktop.svg
new file mode 100644
index 0000000000..fad51789af
--- /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..259ee59ce8
--- /dev/null
+++ b/docs/res/coordinate-systems-mobile.svg
@@ -0,0 +1 @@
+
diff --git a/docs/res/coordinate-systems.drawio b/docs/res/coordinate-systems.drawio
new file mode 100644
index 0000000000..98bb4badc0
--- /dev/null
+++ b/docs/res/coordinate-systems.drawio
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/window.rs b/examples/window.rs
index 650b0a7cb2..9a8a305441 100644
--- a/examples/window.rs
+++ b/examples/window.rs
@@ -216,6 +216,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(),
@@ -896,18 +900,55 @@ impl WindowState {
return Ok(());
}
- const WHITE: u32 = 0xffffffff;
- const DARK_GRAY: u32 = 0xff181818;
+ let mut buffer = self.surface.buffer_mut()?;
+
+ // Fill the whole surface with a plain background
+ buffer.fill(match self.theme {
+ Theme::Light => 0xffffffff, // White
+ Theme::Dark => 0xff181818, // Dark gray
+ });
+
+ // Draw a star (without anti-aliasing) inside the safe area
+ let surface_size = self.window.surface_size();
+ let (origin, size) = self.window.safe_area();
+ let in_star = |x, y| -> bool {
+ // Shamelessly adapted from https://stackoverflow.com/a/2049593.
+ let sign = |p1: (i32, i32), p2: (i32, i32), p3: (i32, i32)| -> i32 {
+ (p1.0 - p3.0) * (p2.1 - p3.1) - (p2.0 - p3.0) * (p1.1 - p3.1)
+ };
+
+ let pt = (x as i32, y as i32);
+ let v1 = (0, size.height as i32 / 2);
+ let v2 = (size.width as i32 / 2, 0);
+ let v3 = (size.width as i32, size.height as i32 / 2);
+ let v4 = (size.width as i32 / 2, size.height as i32);
+
+ let d1 = sign(pt, v1, v2);
+ let d2 = sign(pt, v2, v3);
+ let d3 = sign(pt, v3, v4);
+ let d4 = sign(pt, v4, v1);
+
+ let has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0) || (d4 < 0);
+ let has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0) || (d4 > 0);
- let color = match self.theme {
- Theme::Light => WHITE,
- Theme::Dark => DARK_GRAY,
+ !(has_neg && has_pos)
};
+ for y in 0..size.height {
+ for x in 0..size.width {
+ if in_star(x, y) {
+ let index = (origin.y + y) * surface_size.width + (origin.x + x);
+ buffer[index as usize] = match self.theme {
+ Theme::Light => 0xffe8e8e8, // Light gray
+ Theme::Dark => 0xff525252, // Medium gray
+ };
+ }
+ }
+ }
- let mut buffer = self.surface.buffer_mut()?;
- buffer.fill(color);
+ // Present the buffer
self.window.pre_present_notify();
buffer.present()?;
+
Ok(())
}
@@ -944,6 +985,8 @@ enum Action {
ToggleDecorations,
ToggleResizable,
ToggleFullscreen,
+ #[cfg(macos_platform)]
+ ToggleSimpleFullscreen,
ToggleMaximize,
Minimize,
NextCursor,
@@ -977,6 +1020,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",
@@ -1119,6 +1164,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 6f55ff7f2c..2f1dfa9673 100644
--- a/src/changelog/unreleased.md
+++ b/src/changelog/unreleased.md
@@ -65,6 +65,8 @@ changelog entry.
- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`.
- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game`
to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games.
+- 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
@@ -150,6 +152,7 @@ changelog entry.
- Remove `MonitorHandle::size()` and `refresh_rate_millihertz()` in favor of
`MonitorHandle::current_video_mode()`.
- On Android, remove all `MonitorHandle` support instead of emitting false data.
+- Removed `Window::inner_position`, use the new `Window::surface_position` instead.
### Fixed
diff --git a/src/event.rs b/src/event.rs
index 223992db8f..ac945ccb0d 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
///
diff --git a/src/lib.rs b/src/lib.rs
index f747c13928..9d7d44f44c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -115,6 +115,41 @@
//! [`visible` set to `false`][crate::window::WindowAttributes::with_visible] and explicitly make
//! the window visible only once you're ready to render into it.
//!
+//! # 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, re-add them
+//! 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.
+//!
+//! There is, however, a different important concept: 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
+//! being drawn in the safe area.
+#![doc = concat!("\n\n", include_str!("../docs/res/coordinate-systems-mobile.svg"), "\n\n")] // Rustfmt removes \n, re-add them
+//! [`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
+//! [`Window::safe_area`]: crate::window::Window::safe_area
+//!
//! # 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 d36d5694e9..ad3027de29 100644
--- a/src/platform/macos.rs
+++ b/src/platform/macos.rs
@@ -91,6 +91,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/apple/appkit/window.rs b/src/platform_impl/apple/appkit/window.rs
index 3a454e0457..183cd2223d 100644
--- a/src/platform_impl/apple/appkit/window.rs
+++ b/src/platform_impl/apple/appkit/window.rs
@@ -111,12 +111,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) {
@@ -135,6 +135,10 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.outer_size())
}
+ fn safe_area(&self) -> (dpi::PhysicalPosition, dpi::PhysicalSize) {
+ 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 fdad0c515e..81677588e4 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};
@@ -20,10 +20,10 @@ use objc2_app_kit::{
NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility,
};
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};
@@ -439,9 +439,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;
@@ -930,10 +936,10 @@ impl WindowDelegate {
LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor())
}
- pub fn inner_position(&self) -> PhysicalPosition {
+ pub fn surface_position(&self) -> Result, RequestError> {
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) {
@@ -959,6 +965,13 @@ impl WindowDelegate {
logical.to_physical(self.scale_factor())
}
+ pub fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) {
+ let safe_rect = unsafe { self.view().safeAreaRect() };
+ let position = LogicalPosition::new(safe_rect.origin.x, safe_rect.origin.y);
+ let size = LogicalSize::new(safe_rect.size.width, safe_rect.size.height);
+ (position.to_physical(self.scale_factor()), size.to_physical(self.scale_factor()))
+ }
+
#[inline]
pub fn request_surface_size(&self, size: Size) -> Option> {
let scale_factor = self.scale_factor();
@@ -1155,13 +1168,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}")))?;
@@ -1742,12 +1754,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);
@@ -1760,11 +1775,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/window.rs b/src/platform_impl/apple/uikit/window.rs
index b4ca0bc721..714fda1515 100644
--- a/src/platform_impl/apple/uikit/window.rs
+++ b/src/platform_impl/apple/uikit/window.rs
@@ -9,8 +9,8 @@ use objc2_foundation::{
CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker, NSObjectProtocol,
};
use objc2_ui_kit::{
- UIApplication, UICoordinateSpace, UIResponder, UIScreen, UIScreenOverscanCompensation,
- UIViewController, UIWindow,
+ UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen,
+ UIScreenOverscanCompensation, UIViewController, UIWindow,
};
use tracing::{debug, warn};
@@ -159,20 +159,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, NotSupportedError> {
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) {
@@ -188,29 +187,41 @@ 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 size = LogicalSize::new(screen_frame.size.width, screen_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) -> (PhysicalPosition, PhysicalSize) {
+ let frame = self.view.frame();
+ let safe_area = 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 position = LogicalPosition::new(safe_area.left, safe_area.top);
+ let size = LogicalSize::new(
+ frame.size.width - safe_area.left - safe_area.right,
+ frame.size.height - safe_area.top - safe_area.bottom,
+ );
+ let scale_factor = self.scale_factor();
+ (position.to_physical(scale_factor), size.to_physical(scale_factor))
+ }
+
pub fn set_min_surface_size(&self, _dimensions: Option) {
warn!("`Window::set_min_surface_size` is ignored on iOS")
}
@@ -606,12 +617,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) {
@@ -630,6 +641,10 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.outer_size())
}
+ fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) {
+ 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))
}
@@ -890,7 +905,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 {
@@ -902,43 +917,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(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
diff --git a/src/window.rs b/src/window.rs
index 1ded68b146..ed1dfb43d2 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -590,41 +590,33 @@ pub trait Window: AsAny + Send + Sync {
// extension trait
fn reset_dead_keys(&self);
- /// Returns the position of the top-left hand corner of the window's client area relative to the
- /// top-left hand corner of the desktop.
+ /// The position of the top-left hand corner of the surface relative to the top-left hand corner
+ /// of the window.
///
- /// The same conditions that apply to [`Window::outer_position`] apply to this method.
+ /// This can be useful for figuring out the size of the window's decorations (such as buttons,
+ /// title, etc.).
///
- /// ## Platform-specific
- ///
- /// - **iOS:** Returns the top left coordinates of the window's [safe area] in the screen space
- /// coordinate system.
- /// - **Web:** Returns the top-left coordinates relative to the viewport. _Note: this returns
- /// the same value as [`Window::outer_position`]._
- /// - **Android / Wayland:** Always returns [`RequestError::NotSupported`].
- ///
- /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc
- fn inner_position(&self) -> Result, RequestError>;
+ /// If the window does not have any decorations, and the surface is in the exact same position
+ /// as the window itself, this simply returns `(0, 0)`.
+ fn surface_position(&self) -> PhysicalPosition;
- /// Returns the position of the top-left hand corner of the window relative to the
- /// top-left hand corner of the desktop.
+ /// The position of the top-left hand corner of the window relative to the top-left hand corner
+ /// of the desktop.
///
/// Note that the top-left hand corner of the desktop is not necessarily the same as
/// the screen. If the user uses a desktop with multiple monitors, the top-left hand corner
- /// of the desktop is the top-left hand corner of the monitor at the top-left of the desktop.
+ /// of the desktop is the top-left hand corner of the primary monitor of the desktop.
///
/// The coordinates can be negative if the top-left hand corner of the window is outside
- /// of the visible screen region.
+ /// of the visible screen region, or on another monitor than the primary.
///
/// ## Platform-specific
///
- /// - **iOS:** Returns the top left coordinates of the window in the screen space coordinate
- /// system.
/// - **Web:** Returns the top-left coordinates relative to the viewport.
/// - **Android / Wayland:** Always returns [`RequestError::NotSupported`].
fn outer_position(&self) -> Result, RequestError>;
- /// Modifies the position of the window.
+ /// Sets the position of the window on the desktop.
///
/// See [`Window::outer_position`] for more information about the coordinates.
/// This automatically un-maximizes the window if it's maximized.
@@ -654,16 +646,20 @@ pub trait Window: AsAny + Send + Sync {
/// Returns the size of the window's render-able surface.
///
- /// This is the dimensions you should pass to things like Wgpu or Glutin when configuring.
+ /// This is the dimensions you should pass to things like Wgpu or Glutin when configuring the
+ /// surface for drawing. See [`WindowEvent::SurfaceResized`] for listening to changes to this
+ /// field.
+ ///
+ /// Note that to ensure that your content is not obscured by things such as notches, you will
+ /// likely want to only draw inside a specific area of the surface, see [`Window::safe_area`]
+ /// for details.
///
/// ## Platform-specific
///
- /// - **iOS:** Returns the `PhysicalSize` of the window's [safe area] in screen space
- /// coordinates.
/// - **Web:** Returns the size of the canvas element. Doesn't account for CSS [`transform`].
///
- /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
+ /// [`WindowEvent::SurfaceResized`]: crate::event::WindowEvent::SurfaceResized
fn surface_size(&self) -> PhysicalSize;
/// Request the new size for the surface.
@@ -710,11 +706,29 @@ pub trait Window: AsAny + Send + Sync {
///
/// ## Platform-specific
///
- /// - **iOS:** Returns the [`PhysicalSize`] of the window in screen space coordinates.
/// - **Web:** Returns the size of the canvas element. _Note: this returns the same value as
/// [`Window::surface_size`]._
fn outer_size(&self) -> PhysicalSize;
+ /// The area of the surface that is unobstructed.
+ ///
+ /// On some devices, especially mobile devices, the screen is not a perfect rectangle, and may
+ /// have rounded corners, notches, bezels, and so on. When drawing your content, you usually
+ /// want to draw your background and other such unimportant content on the entire surface, while
+ /// you will want to restrict important content such as text, interactable or visual indicators
+ /// to the part of the screen that is actually visible; for this, you use the safe area.
+ ///
+ /// The safe area is a rectangle that is defined relative to the origin at the top-left corner
+ /// of the surface, and the size extending downwards to the right. The area will not extend
+ /// beyond [the bounds of the surface][Window::surface_size].
+ ///
+ /// ## Platform-specific
+ ///
+ /// - **Wayland / Android / Orbital:** Unimplemented, returns `((0, 0), surface_size)`.
+ /// - **macOS:** This must be used when using `set_simple_fullscreen` to prevent overlapping the
+ /// notch on newer devices.
+ fn safe_area(&self) -> (PhysicalPosition, PhysicalSize);
+
/// Sets a minimum dimensions of the window's surface.
///
/// ```no_run
@@ -987,8 +1001,8 @@ pub trait Window: AsAny + Send + Sync {
fn set_window_icon(&self, window_icon: Option);
/// Set the IME cursor editing area, where the `position` is the top left corner of that area
- /// and `size` is the size of this area starting from the position. An example of such area
- /// could be a input field in the UI or line in the editor.
+ /// in surface coordinates and `size` is the size of this area starting from the position. An
+ /// example of such area could be a input field in the UI or line in the editor.
///
/// The windowing system could place a candidate box close to that area, but try to not obscure
/// the specified area, so the user input to it stays visible.
@@ -1218,7 +1232,7 @@ pub trait Window: AsAny + Send + Sync {
/// - **iOS / Android / Web:** Always returns an [`RequestError::NotSupported`].
fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError>;
- /// Show [window menu] at a specified position .
+ /// Show [window menu] at a specified position in surface coordinates.
///
/// This is the context menu that is normally shown when interacting with
/// the title bar. This is useful when implementing custom decorations.