From 98a9914676e84817a114cee14fb1ec59f69a4d95 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 26 Aug 2024 00:44:54 +0200 Subject: [PATCH 01/42] Add safe area and document coordinate systems Added `Window::safe_area`, which describes the area of the surface that is unobstructed by notches, bezels etc. The drawing code in the examples have been updated to draw a star inside the safe area, and the plain background outside of it. Also renamed `Window::inner_position` to `Window::surface_position`, and changed it to from screen coordinates to window coordinates, to better align how these coordinate systems work together. Finally, added some SVG images and documentation to describe how all of this works. --- Cargo.toml | 1 + docs/res/ATTRIBUTION.md | 7 + docs/res/coordinate-systems-desktop.svg | 1 + docs/res/coordinate-systems-mobile.svg | 1 + docs/res/coordinate-systems.drawio | 132 ++++++++++++++++++ examples/window.rs | 61 +++++++- src/changelog/unreleased.md | 3 + src/event.rs | 5 +- src/lib.rs | 35 +++++ src/monitor.rs | 7 +- src/platform/macos.rs | 3 + src/platform_impl/apple/appkit/window.rs | 10 +- .../apple/appkit/window_delegate.rs | 70 +++++++--- src/platform_impl/apple/uikit/window.rs | 106 ++++++-------- src/window.rs | 70 ++++++---- 15 files changed, 385 insertions(+), 127 deletions(-) create mode 100644 docs/res/coordinate-systems-desktop.svg create mode 100644 docs/res/coordinate-systems-mobile.svg create mode 100644 docs/res/coordinate-systems.drawio 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 @@ +
outer_position
outer...
outer_size
outer...
surface_size
surfa...
surface_position
surfa...
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 @@ +
safe_area
safe_...
surface_size
surfa...
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. From 335a1c67d735ee24494a56d8f62ab290565430ab Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 08:45:37 +0200 Subject: [PATCH 02/42] Attempt to show better show that positions are relative to something --- docs/res/coordinate-systems-desktop.svg | 2 +- docs/res/coordinate-systems.drawio | 63 ++++++++++++------------- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/docs/res/coordinate-systems-desktop.svg b/docs/res/coordinate-systems-desktop.svg index fad51789af..269d50d6ed 100644 --- a/docs/res/coordinate-systems-desktop.svg +++ b/docs/res/coordinate-systems-desktop.svg @@ -1 +1 @@ -
outer_position
outer...
outer_size
outer...
surface_size
surfa...
surface_position
surfa...
+
outer_position
outer...
outer_size
outer...
surface_size
surfa...
surface_position
surfa...
diff --git a/docs/res/coordinate-systems.drawio b/docs/res/coordinate-systems.drawio index 98bb4badc0..34a9c558ec 100644 --- a/docs/res/coordinate-systems.drawio +++ b/docs/res/coordinate-systems.drawio @@ -1,54 +1,50 @@ - - + + - + - + - + - + - + - + - + - + - - + + - - - - - + - + @@ -59,7 +55,7 @@ - + @@ -70,35 +66,34 @@ - - + + - + - - + - + - - + + - + - + - + - + @@ -109,7 +104,7 @@ - + @@ -120,10 +115,10 @@ - + - + From 3cd2fa6ad6ff604ae3251ea530abd441f1ded25f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 08:48:31 +0200 Subject: [PATCH 03/42] Remove extra comment that was more confusing than helpful --- src/window.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/window.rs b/src/window.rs index ed1dfb43d2..4fd177b63a 100644 --- a/src/window.rs +++ b/src/window.rs @@ -725,8 +725,6 @@ pub trait Window: AsAny + Send + Sync { /// ## 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. From 9bcd3ffb285d7089ece22be0b9fc7d659ac3b823 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 09:03:58 +0200 Subject: [PATCH 04/42] Improve docs, allow safe area to include transparent title bar --- src/window.rs | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/window.rs b/src/window.rs index 4fd177b63a..12908e2bfc 100644 --- a/src/window.rs +++ b/src/window.rs @@ -593,11 +593,26 @@ pub trait Window: AsAny + Send + Sync { /// The position of the top-left hand corner of the surface relative to the top-left hand corner /// of the window. /// - /// This can be useful for figuring out the size of the window's decorations (such as buttons, - /// title, etc.). + /// This, combined with [`outer_position`], can be useful for calculating the position of the + /// surface relative to the desktop. + /// + /// This may also be useful for figuring out the size of the window's decorations (such as + /// buttons, title, etc.), but may also not correspond to that (e.g. if the title bar is made + /// transparent using [`with_titlebar_transparent`] on macOS, or your are drawing window + /// decorations yourself). /// /// 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)`. + /// + /// [`outer_position`]: Self::outer_position + #[cfg_attr( + any(macos_platform, docsrs), + doc = "[`with_titlebar_transparent`]: WindowAttributesExtMacOS::with_titlebar_transparent" + )] + #[cfg_attr( + not(any(macos_platform, docsrs)), + doc = "[`with_titlebar_transparent`]: #only-available-on-macos" + )] fn surface_position(&self) -> PhysicalPosition; /// The position of the top-left hand corner of the window relative to the top-left hand corner @@ -650,9 +665,9 @@ pub trait Window: AsAny + Send + Sync { /// 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. + /// Note that to ensure that your content is not obscured by things such as notches or the title + /// bar, you will likely want to only draw important content inside a specific area of the + /// surface, see [`safe_area`] for details. /// /// ## Platform-specific /// @@ -660,6 +675,7 @@ pub trait Window: AsAny + Send + Sync { /// /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform /// [`WindowEvent::SurfaceResized`]: crate::event::WindowEvent::SurfaceResized + /// [`safe_area`]: Window::safe_area fn surface_size(&self) -> PhysicalSize; /// Request the new size for the surface. @@ -722,6 +738,8 @@ pub trait Window: AsAny + Send + Sync { /// 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]. /// + /// If the entire content of the surface is visible, this returns `((0, 0), surface_size)`. + /// /// ## Platform-specific /// /// - **Wayland / Android / Orbital:** Unimplemented, returns `((0, 0), surface_size)`. From bec2ed1690b38d850a1047079ebab95a8483251e Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 09:06:44 +0200 Subject: [PATCH 05/42] Use possibly negative values for surface position Dunno, I can imagine that it will happen on some platforms --- src/platform_impl/apple/appkit/window.rs | 2 +- src/platform_impl/apple/appkit/window_delegate.rs | 6 +++--- src/platform_impl/apple/uikit/window.rs | 6 +++--- src/window.rs | 5 ++++- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/platform_impl/apple/appkit/window.rs b/src/platform_impl/apple/appkit/window.rs index 183cd2223d..b131c1a1d7 100644 --- a/src/platform_impl/apple/appkit/window.rs +++ b/src/platform_impl/apple/appkit/window.rs @@ -111,7 +111,7 @@ impl CoreWindow for Window { self.maybe_wait_on_main(|delegate| delegate.reset_dead_keys()); } - fn surface_position(&self) -> dpi::PhysicalPosition { + fn surface_position(&self) -> dpi::PhysicalPosition { self.maybe_wait_on_main(|delegate| delegate.surface_position()) } diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index 81677588e4..5a9f3992de 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -931,12 +931,12 @@ 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 surface_position(&self) -> Result, RequestError> { + pub fn surface_position(&self) -> PhysicalPosition { let content_rect = self.window().contentRectForFrameRect(self.window().frame()); let logical = LogicalPosition::new(content_rect.origin.x, content_rect.origin.y); logical.to_physical(self.scale_factor()) diff --git a/src/platform_impl/apple/uikit/window.rs b/src/platform_impl/apple/uikit/window.rs index 714fda1515..19d4d11a3e 100644 --- a/src/platform_impl/apple/uikit/window.rs +++ b/src/platform_impl/apple/uikit/window.rs @@ -159,7 +159,7 @@ impl Inner { pub fn pre_present_notify(&self) {} - pub fn surface_position(&self) -> PhysicalPosition { + pub fn surface_position(&self) -> PhysicalPosition { let view_position = self.view.frame().origin; let position = unsafe { self.window.convertPoint_fromView(view_position, Some(&self.view)) }; @@ -167,7 +167,7 @@ impl Inner { position.to_physical(self.scale_factor()) } - pub fn outer_position(&self) -> Result, NotSupportedError> { + 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 }; @@ -617,7 +617,7 @@ impl CoreWindow for Window { self.maybe_wait_on_main(|delegate| delegate.reset_dead_keys()); } - fn surface_position(&self) -> PhysicalPosition { + fn surface_position(&self) -> PhysicalPosition { self.maybe_wait_on_main(|delegate| delegate.surface_position()) } diff --git a/src/window.rs b/src/window.rs index 12908e2bfc..25063bd671 100644 --- a/src/window.rs +++ b/src/window.rs @@ -601,6 +601,9 @@ pub trait Window: AsAny + Send + Sync { /// transparent using [`with_titlebar_transparent`] on macOS, or your are drawing window /// decorations yourself). /// + /// This _may_ be negative in the off case that the windowing system decided to make a surface + /// that is larger than your window. + /// /// 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)`. /// @@ -613,7 +616,7 @@ pub trait Window: AsAny + Send + Sync { not(any(macos_platform, docsrs)), doc = "[`with_titlebar_transparent`]: #only-available-on-macos" )] - fn surface_position(&self) -> PhysicalPosition; + fn surface_position(&self) -> PhysicalPosition; /// The position of the top-left hand corner of the window relative to the top-left hand corner /// of the desktop. From 81c92ebb4b821470f87f63cec0391297231473ff Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 09:27:40 +0200 Subject: [PATCH 06/42] Fix safe_area on macOS < 11 --- src/platform_impl/apple/appkit/window_delegate.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index 5a9f3992de..69c1b1a5fc 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -966,7 +966,16 @@ impl WindowDelegate { } pub fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { - let safe_rect = unsafe { self.view().safeAreaRect() }; + // Only available on macOS 11.0 + let safe_rect = if self.view().respondsToSelector(sel!(safeAreaRect)) { + // Includes NSWindowStyleMask::FullSizeContentView by default, and the notch because + // we've set it up with `additionalSafeAreaInsets`. + unsafe { self.view().safeAreaRect() } + } else { + // Includes NSWindowStyleMask::FullSizeContentView + // Convert from window coordinates to view coordinates + unsafe { self.view().convertRect_fromView(self.window().contentLayoutRect(), None) } + }; 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())) From d5d8e30e8c1d7548c25399daaf2859976511acd4 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 09:37:29 +0200 Subject: [PATCH 07/42] Fix outer_size on iOS --- src/platform_impl/apple/uikit/window.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/platform_impl/apple/uikit/window.rs b/src/platform_impl/apple/uikit/window.rs index 19d4d11a3e..e859ae561a 100644 --- a/src/platform_impl/apple/uikit/window.rs +++ b/src/platform_impl/apple/uikit/window.rs @@ -193,8 +193,8 @@ impl Inner { } pub fn outer_size(&self) -> PhysicalSize { - let screen_frame = self.screen_frame(); - let size = LogicalSize::new(screen_frame.size.width, screen_frame.size.height); + let frame = self.window.frame(); + let size = LogicalSize::new(frame.size.width, frame.size.height); size.to_physical(self.scale_factor()) } @@ -204,7 +204,8 @@ impl Inner { pub fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { let frame = self.view.frame(); - let safe_area = if app_state::os_capabilities().safe_area { + // 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 @@ -213,10 +214,10 @@ impl Inner { 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 position = LogicalPosition::new(insets.left, insets.top); let size = LogicalSize::new( - frame.size.width - safe_area.left - safe_area.right, - frame.size.height - safe_area.top - safe_area.bottom, + frame.size.width - insets.left - insets.right, + frame.size.height - insets.top - insets.bottom, ); let scale_factor = self.scale_factor(); (position.to_physical(scale_factor), size.to_physical(scale_factor)) From b1fee883b87ba18e1edf9ca90032a954cb7c0346 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 09:40:01 +0200 Subject: [PATCH 08/42] Remove draw.io files from typos check --- typos.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/typos.toml b/typos.toml index c0527730e9..7a32f53230 100644 --- a/typos.toml +++ b/typos.toml @@ -5,3 +5,6 @@ TME_LEAVE = "TME_LEAVE" # From windows_sys::Win32::UI::Input::Keyboa XF86_Calculater = "XF86_Calculater" # From xkbcommon_dl::keysyms::XF86_Calculater ptd = "ptd" # From windows_sys::Win32::System::Com::FORMATETC { ptd, ..} requestor = "requestor" # From x11_dl::xlib::XSelectionEvent { requestor ..} + +[files] +extend-exclude = ["docs/*.drawio"] From 5df9eb85f1742630cb793e8f7994491ea7b2f5e1 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 09:40:45 +0200 Subject: [PATCH 09/42] Fix doc link --- src/window.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/window.rs b/src/window.rs index 25063bd671..e38a17f957 100644 --- a/src/window.rs +++ b/src/window.rs @@ -610,7 +610,8 @@ pub trait Window: AsAny + Send + Sync { /// [`outer_position`]: Self::outer_position #[cfg_attr( any(macos_platform, docsrs), - doc = "[`with_titlebar_transparent`]: WindowAttributesExtMacOS::with_titlebar_transparent" + doc = "[`with_titlebar_transparent`]: \ + crate::platform::macos::WindowAttributesExtMacOS::with_titlebar_transparent" )] #[cfg_attr( not(any(macos_platform, docsrs)), From da7942e72057bf904a32d3162f3704915fa626a4 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 10:13:48 +0200 Subject: [PATCH 10/42] Fix SurfaceResized on iOS --- src/changelog/unreleased.md | 1 + src/platform_impl/apple/uikit/view.rs | 40 ++++++++----------------- src/platform_impl/apple/uikit/window.rs | 11 ++----- 3 files changed, 17 insertions(+), 35 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 2f1dfa9673..7e69b14ca6 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -157,3 +157,4 @@ changelog entry. ### Fixed - On Orbital, `MonitorHandle::name()` now returns `None` instead of a dummy name. +- On iOS, fixed `SurfaceResized` and `Window::surface_size` not reporting the size of the actual surface. diff --git a/src/platform_impl/apple/uikit/view.rs b/src/platform_impl/apple/uikit/view.rs index 20a185b957..b0b97b1eb4 100644 --- a/src/platform_impl/apple/uikit/view.rs +++ b/src/platform_impl/apple/uikit/view.rs @@ -6,10 +6,10 @@ 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 super::app_state::{self, EventWrapper}; @@ -69,26 +69,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 { @@ -123,13 +112,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 = RootWindowId(window.id()); app_state::handle_nonuser_events( diff --git a/src/platform_impl/apple/uikit/window.rs b/src/platform_impl/apple/uikit/window.rs index e859ae561a..234bb01b73 100644 --- a/src/platform_impl/apple/uikit/window.rs +++ b/src/platform_impl/apple/uikit/window.rs @@ -527,14 +527,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 }; let window_id = CoreWindowId(window.id()); app_state::handle_nonuser_events( mtm, From 9f080a5d2fe30e8d323ded0f825faac8f499b88f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 10:17:22 +0200 Subject: [PATCH 11/42] Always redraw when the safe area changes --- src/platform_impl/apple/uikit/view.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/platform_impl/apple/uikit/view.rs b/src/platform_impl/apple/uikit/view.rs index b0b97b1eb4..a5d3d45e44 100644 --- a/src/platform_impl/apple/uikit/view.rs +++ b/src/platform_impl/apple/uikit/view.rs @@ -11,6 +11,7 @@ use objc2_ui_kit::{ UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer, UITextInputTraits, UITouch, UITouchPhase, UITouchType, UITraitEnvironment, UIView, }; +use tracing::debug; use super::app_state::{self, EventWrapper}; use super::window::WinitUIWindow; @@ -136,6 +137,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) From bc12e13e0b804752e56695c85fc0af7c1643b54f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 10:22:46 +0200 Subject: [PATCH 12/42] Properly set up typos check --- typos.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typos.toml b/typos.toml index 7a32f53230..c454f23b9b 100644 --- a/typos.toml +++ b/typos.toml @@ -7,4 +7,4 @@ ptd = "ptd" # From windows_sys::Win32::System::Com::FORM requestor = "requestor" # From x11_dl::xlib::XSelectionEvent { requestor ..} [files] -extend-exclude = ["docs/*.drawio"] +extend-exclude = ["*.drawio"] From 287fd8d04206c4b938960c52f91e9c48e1073b6a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 10:51:04 +0200 Subject: [PATCH 13/42] Add Android, Orbital, Web, Windows, Wayland and X11 implementations Very bare-bones, probably not correct --- src/platform_impl/android/mod.rs | 10 ++++-- src/platform_impl/linux/wayland/window/mod.rs | 10 ++++-- src/platform_impl/linux/x11/util/geometry.rs | 11 +++++-- src/platform_impl/linux/x11/window.rs | 31 ++++++++++++++----- src/platform_impl/orbital/window.rs | 19 +++++++----- src/platform_impl/web/window.rs | 11 +++++-- src/platform_impl/windows/window.rs | 15 ++++++--- src/window.rs | 3 +- 8 files changed, 78 insertions(+), 32 deletions(-) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 426dd0fb35..157211c026 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -796,8 +796,9 @@ 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 inner_position(&self) -> PhysicalPosition { + // FIXME: Complete this implementation + (0, 0).into() } fn outer_position(&self) -> Result, RequestError> { @@ -820,6 +821,11 @@ impl CoreWindow for Window { screen_size(&self.app) } + fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { + // FIXME: Complete this implementation + ((0, 0).into(), self.surface_size()) + } + fn set_min_surface_size(&self, _: Option) {} fn set_max_surface_size(&self, _: Option) {} diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 42f37a0963..f79b5a032a 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -305,9 +305,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> { @@ -338,6 +337,11 @@ impl CoreWindow for Window { super::logical_to_physical_rounded(window_state.outer_size(), scale_factor) } + fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { + // FIXME: Include CSDs drawn by Winit + ((0, 0).into(), self.surface_size()) + } + 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 ac5a0bc0d9..6ab0aaa80c 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -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) -> (PhysicalPosition, PhysicalSize) { + 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,11 @@ impl UnownedWindow { } } + fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { + // FIXME: Complete this implementation + ((0, 0).into(), self.surface_size()) + } + pub(crate) fn request_surface_size_physical(&self, width: u32, height: u32) { self.xconn .xcb_connection() @@ -1989,7 +2004,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 +2031,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 25f011fa11..ae30ec6473 100644 --- a/src/platform_impl/orbital/window.rs +++ b/src/platform_impl/orbital/window.rs @@ -199,17 +199,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] @@ -240,6 +240,11 @@ impl CoreWindow for Window { self.surface_size() } + fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { + // FIXME: Complete this implementation + ((0, 0).into(), self.surface_size()) + } + #[inline] fn set_min_surface_size(&self, _: Option) {} diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index d979310a6e..8c6f0a25ea 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -109,9 +109,9 @@ impl RootWindow for Window { // Not supported } - fn inner_position(&self) -> Result, RequestError> { - // Note: the canvas element has no window decorations, so this is equal to `outer_position`. - self.outer_position() + fn surface_position(&self) -> PhysicalPosition { + // Note: the canvas element has no window decorations. + (0, 0).into() } fn outer_position(&self) -> Result, RequestError> { @@ -152,6 +152,11 @@ impl RootWindow for Window { self.surface_size() } + fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { + // FIXME: Complete this implementation + ((0, 0).into(), self.surface_size()) + } + fn set_min_surface_size(&self, min_size: Option) { self.inner.dispatch(move |inner| { let dimensions = min_size.map(|min_size| min_size.to_logical(inner.scale_factor())); diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index db0dd9ba7c..ee5708f897 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -421,15 +421,15 @@ impl CoreWindow for Window { ) } - fn inner_position(&self) -> Result, RequestError> { - let mut position: POINT = unsafe { mem::zeroed() }; - if unsafe { ClientToScreen(self.hwnd(), &mut position) } == false.into() { + fn surface_position(&self) -> PhysicalPosition { + let mut rect: RECT = unsafe { mem::zeroed() }; + if unsafe { GetClientRect(self.hwnd(), &mut rect) } == false.into() { panic!( - "Unexpected ClientToScreen failure: please report this error to \ + "Unexpected GetClientRect failure: please report this error to \ rust-windowing/winit" ) } - Ok(PhysicalPosition::new(position.x, position.y)) + PhysicalPosition::new(rect.left as i32, rect.top as i32) } fn set_outer_position(&self, position: Position) { @@ -499,6 +499,11 @@ impl CoreWindow for Window { None } + fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { + // FIXME: Complete this implementation + ((0, 0).into(), self.surface_size()) + } + fn set_min_surface_size(&self, size: Option) { self.window_state_lock().min_size = size; // Make windows re-check the window size bounds. diff --git a/src/window.rs b/src/window.rs index e38a17f957..28227d40d9 100644 --- a/src/window.rs +++ b/src/window.rs @@ -746,7 +746,8 @@ pub trait Window: AsAny + Send + Sync { /// /// ## Platform-specific /// - /// - **Wayland / Android / Orbital:** Unimplemented, returns `((0, 0), surface_size)`. + /// - **Android / Orbital / Wayland / Web / Windows / X11:** Unimplemented, returns `((0, 0), + /// surface_size)`. fn safe_area(&self) -> (PhysicalPosition, PhysicalSize); /// Sets a minimum dimensions of the window's surface. From ba76cc61bb6fa79e63de608e0848aba82e9ef304 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 10:53:04 +0200 Subject: [PATCH 14/42] Fix Android inner_position/surface_position name --- src/platform_impl/android/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 157211c026..04918b3cf4 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -796,7 +796,7 @@ impl CoreWindow for Window { fn pre_present_notify(&self) {} - fn inner_position(&self) -> PhysicalPosition { + fn surface_position(&self) -> PhysicalPosition { // FIXME: Complete this implementation (0, 0).into() } From 8561d3b64a85f4bab25b8b0306c326b8b0874fb1 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 10:59:23 +0200 Subject: [PATCH 15/42] Fix clippy warning on Windows --- src/platform_impl/windows/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index ee5708f897..11a55a7dc7 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -429,7 +429,7 @@ impl CoreWindow for Window { rust-windowing/winit" ) } - PhysicalPosition::new(rect.left as i32, rect.top as i32) + PhysicalPosition::new(rect.left, rect.top) } fn set_outer_position(&self, position: Position) { From feb686a573a077a00b49755d522962eb1d5f4d43 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 11:11:00 +0200 Subject: [PATCH 16/42] Fix size of desktop coordinate systems image --- docs/res/coordinate-systems-desktop.svg | 2 +- docs/res/coordinate-systems.drawio | 76 ++++++++++++------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/docs/res/coordinate-systems-desktop.svg b/docs/res/coordinate-systems-desktop.svg index 269d50d6ed..6f72aec5c0 100644 --- a/docs/res/coordinate-systems-desktop.svg +++ b/docs/res/coordinate-systems-desktop.svg @@ -1 +1 @@ -
outer_position
outer...
outer_size
outer...
surface_size
surfa...
surface_position
surfa...
+
outer_position
outer...
outer_size
outer...
surface_size
surfa...
surface_position
surfa...
diff --git a/docs/res/coordinate-systems.drawio b/docs/res/coordinate-systems.drawio index 34a9c558ec..eb39445e4e 100644 --- a/docs/res/coordinate-systems.drawio +++ b/docs/res/coordinate-systems.drawio @@ -1,77 +1,77 @@ - + - + - - + + - - + + - - + + - - + + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - - + + - + - - + + - - + + - + - - + + - + From e878c5999a36547da41da836faa91e7a54338361 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 11:21:42 +0200 Subject: [PATCH 17/42] Note on android that there's events for safe area insets --- src/platform_impl/android/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 04918b3cf4..61821a0991 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -823,6 +823,7 @@ impl CoreWindow for Window { fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { // FIXME: Complete this implementation + // Also wire up so that `Resized` events are emitted when the safe area insets changes. ((0, 0).into(), self.surface_size()) } From c7523d4d07f8d36c28a014ac16b0c06bdf79020a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 10 Sep 2024 11:24:54 +0200 Subject: [PATCH 18/42] Add docs on also emitting RedrawRequested when safe area changes --- src/event.rs | 6 ++++-- src/platform_impl/android/mod.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/event.rs b/src/event.rs index ac945ccb0d..ccca865e5e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -423,13 +423,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/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 61821a0991..0d225f30c2 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -823,7 +823,7 @@ impl CoreWindow for Window { fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { // FIXME: Complete this implementation - // Also wire up so that `Resized` events are emitted when the safe area insets changes. + // Also wire up so that `RedrawRequested` events are emitted when the safe insets change. ((0, 0).into(), self.surface_size()) } From 01552d5cae2c541049d022cfb6a97ea0d860051f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 11 Sep 2024 12:49:53 +0200 Subject: [PATCH 19/42] Move docs on safe area to # Drawing on the window --- src/lib.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9d7d44f44c..1fe592052e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,6 +115,14 @@ //! [`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; @@ -138,17 +146,13 @@ //! 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. +//! Note that the safe area (discussed above) is especially important here, since windows on mobile +//! are often full screen, and often the device has notches (as illustrated in the image below). #![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 //! From 45326fb0ba9b081efc6adcda693d8b65dbdbe4df Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 11 Sep 2024 12:50:56 +0200 Subject: [PATCH 20/42] Remove comment about CSD in safe_area on wayland --- src/platform_impl/linux/wayland/window/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index f79b5a032a..6e2cc3d210 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -338,7 +338,6 @@ impl CoreWindow for Window { } fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { - // FIXME: Include CSDs drawn by Winit ((0, 0).into(), self.surface_size()) } From c65149055259065b6ef8206862071e3e31a28fae Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 11 Sep 2024 12:56:30 +0200 Subject: [PATCH 21/42] Remove fixmes, use issue to track instead --- src/platform_impl/android/mod.rs | 3 --- src/platform_impl/linux/x11/window.rs | 1 - src/platform_impl/orbital/window.rs | 1 - src/platform_impl/web/window.rs | 1 - src/platform_impl/windows/window.rs | 1 - 5 files changed, 7 deletions(-) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 0d225f30c2..425e45c96a 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -797,7 +797,6 @@ impl CoreWindow for Window { fn pre_present_notify(&self) {} fn surface_position(&self) -> PhysicalPosition { - // FIXME: Complete this implementation (0, 0).into() } @@ -822,8 +821,6 @@ impl CoreWindow for Window { } fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { - // FIXME: Complete this implementation - // Also wire up so that `RedrawRequested` events are emitted when the safe insets change. ((0, 0).into(), self.surface_size()) } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 6ab0aaa80c..19e6e06f5b 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -1593,7 +1593,6 @@ impl UnownedWindow { } fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { - // FIXME: Complete this implementation ((0, 0).into(), self.surface_size()) } diff --git a/src/platform_impl/orbital/window.rs b/src/platform_impl/orbital/window.rs index ae30ec6473..f80d956029 100644 --- a/src/platform_impl/orbital/window.rs +++ b/src/platform_impl/orbital/window.rs @@ -241,7 +241,6 @@ impl CoreWindow for Window { } fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { - // FIXME: Complete this implementation ((0, 0).into(), self.surface_size()) } diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 8c6f0a25ea..3de488318c 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -153,7 +153,6 @@ impl RootWindow for Window { } fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { - // FIXME: Complete this implementation ((0, 0).into(), self.surface_size()) } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 11a55a7dc7..1a34626206 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -500,7 +500,6 @@ impl CoreWindow for Window { } fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { - // FIXME: Complete this implementation ((0, 0).into(), self.surface_size()) } From c1ea82ae56fa01ed2f422200f369304d235c1d76 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 11 Sep 2024 12:59:41 +0200 Subject: [PATCH 22/42] Change note on negative surface position --- src/window.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/window.rs b/src/window.rs index 28227d40d9..afffd6c81a 100644 --- a/src/window.rs +++ b/src/window.rs @@ -601,8 +601,7 @@ pub trait Window: AsAny + Send + Sync { /// transparent using [`with_titlebar_transparent`] on macOS, or your are drawing window /// decorations yourself). /// - /// This _may_ be negative in the off case that the windowing system decided to make a surface - /// that is larger than your window. + /// This may be negative. /// /// 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)`. From 39f40c776fe08c51502d44dbf473d8ffbf7591b9 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 11 Sep 2024 13:08:26 +0200 Subject: [PATCH 23/42] Make safe area position vs. size a bit clearer --- docs/res/coordinate-systems-mobile.svg | 2 +- docs/res/coordinate-systems.drawio | 47 +++++++++++++++----------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/docs/res/coordinate-systems-mobile.svg b/docs/res/coordinate-systems-mobile.svg index 259ee59ce8..9c8d1fe5d8 100644 --- a/docs/res/coordinate-systems-mobile.svg +++ b/docs/res/coordinate-systems-mobile.svg @@ -1 +1 @@ -
safe_area
safe_...
surface_size
surfa...
+
safe_area
(size)
safe_...
surface_size
surfa...
safe_area
(position)
safe_...
diff --git a/docs/res/coordinate-systems.drawio b/docs/res/coordinate-systems.drawio index eb39445e4e..fab30897d9 100644 --- a/docs/res/coordinate-systems.drawio +++ b/docs/res/coordinate-systems.drawio @@ -1,4 +1,4 @@ - + @@ -80,46 +80,53 @@ - + - + - + - + - + + + + + + + - - + + - - + + - + - - + + - - + + - - - - - + + + + + + From 131eaa1737d9c3f82260598961ed2eadb52cf9a2 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 11 Sep 2024 13:15:32 +0200 Subject: [PATCH 24/42] Add dot and arrow to the start and end of position indicators --- docs/res/coordinate-systems-desktop.svg | 2 +- docs/res/coordinate-systems-mobile.svg | 2 +- docs/res/coordinate-systems.drawio | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/res/coordinate-systems-desktop.svg b/docs/res/coordinate-systems-desktop.svg index 6f72aec5c0..14d33a759a 100644 --- a/docs/res/coordinate-systems-desktop.svg +++ b/docs/res/coordinate-systems-desktop.svg @@ -1 +1 @@ -
outer_position
outer...
outer_size
outer...
surface_size
surfa...
surface_position
surfa...
+
outer_position
outer...
outer_size
outer...
surface_size
surfa...
surface_position
surfa...
diff --git a/docs/res/coordinate-systems-mobile.svg b/docs/res/coordinate-systems-mobile.svg index 9c8d1fe5d8..d18053e649 100644 --- a/docs/res/coordinate-systems-mobile.svg +++ b/docs/res/coordinate-systems-mobile.svg @@ -1 +1 @@ -
safe_area
(size)
safe_...
surface_size
surfa...
safe_area
(position)
safe_...
+
safe_area
(size)
safe_...
surface_size
surfa...
safe_area
(position)
safe_...
diff --git a/docs/res/coordinate-systems.drawio b/docs/res/coordinate-systems.drawio index fab30897d9..0f82751c7d 100644 --- a/docs/res/coordinate-systems.drawio +++ b/docs/res/coordinate-systems.drawio @@ -1,6 +1,6 @@ - + @@ -37,8 +37,8 @@
- - + + @@ -66,7 +66,7 @@ - + @@ -80,7 +80,7 @@
- + @@ -121,9 +121,9 @@ - + - + From 62a95a5a946420082655d64dcdc2b888dbb13bfe Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 11 Sep 2024 13:44:49 +0200 Subject: [PATCH 25/42] macOS: Guarantee that SurfaceResized is emitted on safe area change To support on Alacritty --- src/platform_impl/apple/appkit/window_delegate.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index 69c1b1a5fc..173ed4b935 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -564,6 +564,11 @@ 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 Alacritty 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`. masks |= NSWindowStyleMask::FullSizeContentView; } From 3b3928e941d7617e7b36934b9abf05f7e774ea8b Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 11 Sep 2024 14:55:42 +0200 Subject: [PATCH 26/42] Don't mention Alacritty --- src/platform_impl/apple/appkit/window_delegate.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index 173ed4b935..58da9b05bb 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -565,10 +565,11 @@ 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 Alacritty know that the safe area changed. + // `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`. + // done with an observer on `safeAreaRect` / `contentLayoutRect`, see: + // masks |= NSWindowStyleMask::FullSizeContentView; } From 992778af4802f28da4be42ae6ca776dd6e9d670a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 11 Sep 2024 15:56:05 +0200 Subject: [PATCH 27/42] Comment before mobile picture --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1fe592052e..f016a44984 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -146,8 +146,8 @@ //! 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. //! -//! Note that the safe area (discussed above) is especially important here, since windows on mobile -//! are often full screen, and often the device has notches (as illustrated in the image below). +//! 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, re-add them //! [`Window::surface_position`]: crate::window::Window::surface_position //! [`Window::surface_size`]: crate::window::Window::surface_size From 1ffa29bfa4b171c1f051288f8b66f4086c711f39 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Fri, 11 Oct 2024 14:15:47 +0200 Subject: [PATCH 28/42] Implement Web --- Cargo.toml | 1 + src/platform_impl/web/event_loop/runner.rs | 10 +++- src/platform_impl/web/web_sys/canvas.rs | 4 +- src/platform_impl/web/web_sys/mod.rs | 2 + src/platform_impl/web/web_sys/safe_area.rs | 56 ++++++++++++++++++++++ src/platform_impl/web/window.rs | 35 +++++++++++++- 6 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 src/platform_impl/web/web_sys/safe_area.rs diff --git a/Cargo.toml b/Cargo.toml index 2e67b79f31..52a30aaba3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -297,6 +297,7 @@ web_sys = { package = "web-sys", version = "0.3.70", features = [ "FocusEvent", "HtmlCanvasElement", "HtmlElement", + "HtmlHtmlElement", "HtmlImageElement", "ImageBitmap", "ImageBitmapOptions", diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 64c65a34eb..e7bac50bca 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -21,7 +21,7 @@ use crate::event::{ }; 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, Waker, WakerSpawner}; use crate::platform_impl::platform::window::Inner; use crate::window::WindowId; @@ -58,6 +58,7 @@ struct Execution { redraw_pending: RefCell>, destroy_pending: RefCell>, pub(crate) monitor: Rc, + safe_area: Rc, page_transition_event_handle: RefCell>, device_events: Cell, on_mouse_move: OnEventHandle, @@ -157,6 +158,8 @@ impl Shared { WeakShared(weak.clone()), ); + let safe_area = SafeAreaHandle::new(&window, &document); + Execution { main_thread, proxy_spawner, @@ -176,6 +179,7 @@ impl Shared { redraw_pending: RefCell::new(HashSet::new()), destroy_pending: RefCell::new(VecDeque::new()), monitor: Rc::new(monitor), + safe_area: Rc::new(safe_area), page_transition_event_handle: RefCell::new(None), device_events: Cell::default(), on_mouse_move: RefCell::new(None), @@ -832,6 +836,10 @@ impl Shared { pub(crate) fn monitor(&self) -> &Rc { &self.0.monitor } + + pub(crate) fn safe_area(&self) -> &Rc { + &self.0.safe_area + } } #[derive(Clone, Debug)] diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index a1e5096e00..865a40b687 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -71,8 +71,8 @@ pub struct Common { #[derive(Clone, Debug)] pub struct Style { - read: CssStyleDeclaration, - write: CssStyleDeclaration, + pub(super) read: CssStyleDeclaration, + pub(super) write: CssStyleDeclaration, } impl Canvas { diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index e6d19077a9..847f096b02 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -7,6 +7,7 @@ mod intersection_handle; mod media_query_handle; mod pointer; mod resize_scaling; +mod safe_area; mod schedule; use std::cell::OnceCell; @@ -21,6 +22,7 @@ pub use self::canvas::{Canvas, Style}; pub use self::event::ButtonsState; pub use self::event_handle::EventListenerHandle; pub use self::resize_scaling::ResizeScaleHandle; +pub use self::safe_area::SafeAreaHandle; pub use self::schedule::Schedule; use crate::dpi::{LogicalPosition, LogicalSize}; diff --git a/src/platform_impl/web/web_sys/safe_area.rs b/src/platform_impl/web/web_sys/safe_area.rs new file mode 100644 index 0000000000..7ecdfc3970 --- /dev/null +++ b/src/platform_impl/web/web_sys/safe_area.rs @@ -0,0 +1,56 @@ +use dpi::{LogicalPosition, LogicalSize}; +use wasm_bindgen::JsCast; +use web_sys::{Document, HtmlHtmlElement, Window}; + +use super::Style; + +pub struct SafeAreaHandle { + style: Style, +} + +impl SafeAreaHandle { + pub fn new(window: &Window, document: &Document) -> Self { + let document: HtmlHtmlElement = document.document_element().unwrap().unchecked_into(); + #[allow(clippy::disallowed_methods)] + let write = document.style(); + write + .set_property( + "--__winit_safe_area", + "env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) \ + env(safe-area-inset-left)", + ) + .expect("unexpected read-only declaration block"); + #[allow(clippy::disallowed_methods)] + let read = window + .get_computed_style(&document) + .expect("failed to obtain computed style") + // this can't fail: we aren't using a pseudo-element + .expect("invalid pseudo-element"); + + SafeAreaHandle { style: Style { read, write } } + } + + pub fn get(&self) -> (LogicalPosition, LogicalSize) { + let value = self.style.get("--__winit_safe_area"); + + let mut values = value + .split(' ') + .map(|value| value.strip_suffix("px").expect("unexpected unit other then `px` found")); + let top: f64 = values.next().unwrap().parse().unwrap(); + let right: f64 = values.next().unwrap().parse().unwrap(); + let bottom: f64 = values.next().unwrap().parse().unwrap(); + let left: f64 = values.next().unwrap().parse().unwrap(); + assert_eq!(values.next(), None, "unexpected fifth value"); + + let width = super::style_size_property(&self.style, "width") - left - right; + let height = super::style_size_property(&self.style, "height") - top - bottom; + + (LogicalPosition::new(left, top), LogicalSize::new(width, height)) + } +} + +impl Drop for SafeAreaHandle { + fn drop(&mut self) { + self.style.remove("--__winit_safe_area"); + } +} diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 3de488318c..189b6ca272 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -2,6 +2,7 @@ use std::cell::Ref; use std::rc::Rc; use std::sync::Arc; +use dpi::{LogicalPosition, LogicalSize}; use web_sys::HtmlCanvasElement; use super::main_thread::{MainThreadMarker, MainThreadSafe}; @@ -26,6 +27,7 @@ pub struct Inner { id: WindowId, pub window: web_sys::Window, monitor: Rc, + safe_area: Rc, canvas: Rc, destroy_fn: Option>, } @@ -59,6 +61,7 @@ impl Window { id, window: window.clone(), monitor: Rc::clone(target.runner.monitor()), + safe_area: Rc::clone(target.runner.safe_area()), canvas, destroy_fn: Some(destroy_fn), }; @@ -153,7 +156,37 @@ impl RootWindow for Window { } fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { - ((0, 0).into(), self.surface_size()) + self.inner.queue(|inner| { + let (safe_start_pos, safe_size) = inner.safe_area.get(); + let safe_end_pos = LogicalPosition::new( + safe_start_pos.x + safe_size.width, + safe_start_pos.y + safe_size.height, + ); + + let surface_start_pos = inner.canvas.position(); + let surface_size = LogicalSize::new( + backend::style_size_property(inner.canvas.style(), "width"), + backend::style_size_property(inner.canvas.style(), "height"), + ); + let surface_end_pos = LogicalPosition::new( + surface_start_pos.x + surface_size.width, + surface_start_pos.y + surface_size.height, + ); + + let pos = LogicalPosition::new( + f64::max(safe_start_pos.x - surface_start_pos.x, 0.), + f64::max(safe_start_pos.y - surface_start_pos.y, 0.), + ); + let width = safe_size.width + - (f64::max(surface_start_pos.x - safe_start_pos.x, 0.) + + f64::max(safe_end_pos.x - surface_end_pos.x, 0.)); + let height = safe_size.height + - (f64::max(surface_start_pos.y - safe_start_pos.y, 0.) + + f64::max(safe_end_pos.y - surface_end_pos.y, 0.)); + let size = LogicalSize::new(width, height); + + (pos.to_physical(inner.scale_factor()), size.to_physical(inner.scale_factor())) + }) } fn set_min_surface_size(&self, min_size: Option) { From 025bb67c9d42c731c3551851b5d3b7f3fd663441 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 4 Nov 2024 14:01:57 +0100 Subject: [PATCH 29/42] Improve rustfmt note --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f016a44984..3dc93d5233 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,7 +140,7 @@ //! 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 +#![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 @@ -148,7 +148,7 @@ //! //! 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, re-add them +#![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 From 2caf76193c2b9f7a2f4751f3f8246c74de3d0879 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 4 Nov 2024 14:02:27 +0100 Subject: [PATCH 30/42] doc function --- src/window.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/window.rs b/src/window.rs index afffd6c81a..a9cc9fa060 100644 --- a/src/window.rs +++ b/src/window.rs @@ -670,7 +670,7 @@ pub trait Window: AsAny + Send + Sync { /// /// Note that to ensure that your content is not obscured by things such as notches or the title /// bar, you will likely want to only draw important content inside a specific area of the - /// surface, see [`safe_area`] for details. + /// surface, see [`safe_area()`] for details. /// /// ## Platform-specific /// @@ -678,7 +678,7 @@ pub trait Window: AsAny + Send + Sync { /// /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform /// [`WindowEvent::SurfaceResized`]: crate::event::WindowEvent::SurfaceResized - /// [`safe_area`]: Window::safe_area + /// [`safe_area()`]: Window::safe_area fn surface_size(&self) -> PhysicalSize; /// Request the new size for the surface. From 9a4c51c7707f23d351fce425e3199ccc700bece2 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 4 Nov 2024 14:06:21 +0100 Subject: [PATCH 31/42] Document that safe area != occlusion --- src/window.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/window.rs b/src/window.rs index a9cc9fa060..2a3c1de896 100644 --- a/src/window.rs +++ b/src/window.rs @@ -741,6 +741,9 @@ pub trait Window: AsAny + Send + Sync { /// 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]. /// + /// Note that the safe area does not take occlusion from other windows into account; in a way, + /// it is only a "hardware"-level occlusion. + /// /// If the entire content of the surface is visible, this returns `((0, 0), surface_size)`. /// /// ## Platform-specific From 51269ced15ef411fc8dc4654b08e9bccd2f888a6 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 4 Nov 2024 14:54:25 +0100 Subject: [PATCH 32/42] Add inset types --- dpi/CHANGELOG.md | 2 + dpi/src/lib.rs | 144 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) 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

LogicalInsets

{ + #[inline] + pub const fn new(top: P, left: P, bottom: P, right: P) -> Self { + Self { top, left, bottom, right } + } +} + +impl LogicalInsets

{ + #[inline] + pub fn from_physical>, X: Pixel>( + physical: T, + scale_factor: f64, + ) -> Self { + physical.into().to_logical(scale_factor) + } + + #[inline] + pub fn to_physical(&self, scale_factor: f64) -> PhysicalInsets { + assert!(validate_scale_factor(scale_factor)); + let top = self.top.into() * scale_factor; + let left = self.left.into() * scale_factor; + let bottom = self.bottom.into() * scale_factor; + let right = self.right.into() * scale_factor; + PhysicalInsets::new(top, left, bottom, right).cast() + } + + #[inline] + pub fn cast(&self) -> LogicalInsets { + LogicalInsets { + top: self.top.cast(), + left: self.left.cast(), + bottom: self.bottom.cast(), + right: self.right.cast(), + } + } +} + +/// The physical 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 PhysicalInsets

{ + /// 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

PhysicalInsets

{ + #[inline] + pub const fn new(top: P, left: P, bottom: P, right: P) -> Self { + Self { top, left, bottom, right } + } +} + +impl PhysicalInsets

{ + #[inline] + pub fn from_logical>, X: Pixel>( + logical: T, + scale_factor: f64, + ) -> Self { + logical.into().to_physical(scale_factor) + } + + #[inline] + pub fn to_logical(&self, scale_factor: f64) -> LogicalInsets { + assert!(validate_scale_factor(scale_factor)); + let top = self.top.into() / scale_factor; + let left = self.left.into() / scale_factor; + let bottom = self.bottom.into() / scale_factor; + let right = self.right.into() / scale_factor; + LogicalInsets::new(top, left, bottom, right).cast() + } + + #[inline] + pub fn cast(&self) -> PhysicalInsets { + PhysicalInsets { + top: self.top.cast(), + left: self.left.cast(), + bottom: self.bottom.cast(), + right: self.right.cast(), + } + } +} + +/// Insets that are either physical or logical. +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Insets { + Physical(PhysicalInsets), + Logical(LogicalInsets), +} + +impl Insets { + pub fn new>(insets: S) -> Self { + insets.into() + } + + pub fn to_logical(&self, scale_factor: f64) -> LogicalInsets

{ + match *self { + Self::Physical(insets) => insets.to_logical(scale_factor), + Self::Logical(insets) => insets.cast(), + } + } + + pub fn to_physical(&self, scale_factor: f64) -> PhysicalInsets

{ + match *self { + Self::Physical(insets) => insets.cast(), + Self::Logical(insets) => insets.to_physical(scale_factor), + } + } +} + +impl From> for Insets { + #[inline] + fn from(insets: PhysicalInsets

) -> Self { + Self::Physical(insets.cast()) + } +} + +impl From> for Insets { + #[inline] + fn from(insets: LogicalInsets

) -> Self { + Self::Logical(insets.cast()) + } +} + #[cfg(test)] mod tests { use std::collections::HashSet; From 12f3666ff5ef6b48ced55532a85d31852963b454 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 4 Nov 2024 14:53:07 +0100 Subject: [PATCH 33/42] Use insets instead of rectangle --- examples/window.rs | 10 ++++++- src/platform_impl/android/mod.rs | 6 ++-- src/platform_impl/apple/appkit/window.rs | 2 +- .../apple/appkit/window_delegate.rs | 29 ++++++++++++++----- src/platform_impl/apple/uikit/window.rs | 19 +++++------- src/platform_impl/linux/wayland/window/mod.rs | 6 ++-- src/platform_impl/linux/x11/window.rs | 8 ++--- src/platform_impl/orbital/window.rs | 6 ++-- src/platform_impl/web/window.rs | 24 ++++++--------- src/platform_impl/windows/window.rs | 6 ++-- src/window.rs | 27 +++++++++++++++-- 11 files changed, 88 insertions(+), 55 deletions(-) diff --git a/examples/window.rs b/examples/window.rs index 9a8a305441..d3a4c90c77 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -910,7 +910,15 @@ impl WindowState { // 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 insets = self.window.safe_area(); + // Compute the safe rectangle. + // Winit's coordinate system has (0, 0) in the top-left corner. + let origin = PhysicalPosition::new(insets.left, insets.top); + let size = PhysicalSize::new( + surface_size.width - insets.left - insets.right, + surface_size.height - insets.top - insets.bottom, + ); + 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 { diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 425e45c96a..7a3db80c77 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, Force, StartCause, SurfaceSizeWriter}; use crate::event_loop::{ @@ -820,8 +820,8 @@ impl CoreWindow for Window { screen_size(&self.app) } - fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { - ((0, 0).into(), self.surface_size()) + fn safe_area(&self) -> PhysicalInsets { + PhysicalInsets::new(0, 0, 0, 0) } fn set_min_surface_size(&self, _: Option) {} diff --git a/src/platform_impl/apple/appkit/window.rs b/src/platform_impl/apple/appkit/window.rs index b131c1a1d7..409763ce03 100644 --- a/src/platform_impl/apple/appkit/window.rs +++ b/src/platform_impl/apple/appkit/window.rs @@ -135,7 +135,7 @@ impl CoreWindow for Window { self.maybe_wait_on_main(|delegate| delegate.outer_size()) } - fn safe_area(&self) -> (dpi::PhysicalPosition, dpi::PhysicalSize) { + fn safe_area(&self) -> dpi::PhysicalInsets { self.maybe_wait_on_main(|delegate| delegate.safe_area()) } diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index 58da9b05bb..ed057302d4 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -34,7 +34,10 @@ use super::observer::RunLoop; use super::view::WinitView; use super::window::WinitWindow; use super::{ffi, Fullscreen, MonitorHandle, WindowId}; -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}; @@ -971,20 +974,30 @@ impl WindowDelegate { logical.to_physical(self.scale_factor()) } - pub fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { + pub fn safe_area(&self) -> PhysicalInsets { // Only available on macOS 11.0 - let safe_rect = if self.view().respondsToSelector(sel!(safeAreaRect)) { + let insets = if self.view().respondsToSelector(sel!(safeAreaInsets)) || false { // Includes NSWindowStyleMask::FullSizeContentView by default, and the notch because // we've set it up with `additionalSafeAreaInsets`. - unsafe { self.view().safeAreaRect() } + unsafe { self.view().safeAreaInsets() } } else { + let content_rect = self.window().contentRectForFrameRect(self.window().frame()); // Includes NSWindowStyleMask::FullSizeContentView // Convert from window coordinates to view coordinates - unsafe { self.view().convertRect_fromView(self.window().contentLayoutRect(), None) } + 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 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())) + let insets = LogicalInsets::new(insets.top, insets.left, insets.bottom, insets.right); + insets.to_physical(self.scale_factor()) } #[inline] diff --git a/src/platform_impl/apple/uikit/window.rs b/src/platform_impl/apple/uikit/window.rs index 234bb01b73..25721e7160 100644 --- a/src/platform_impl/apple/uikit/window.rs +++ b/src/platform_impl/apple/uikit/window.rs @@ -19,7 +19,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; @@ -202,8 +205,7 @@ impl Inner { Some(self.surface_size()) } - pub fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { - let frame = self.view.frame(); + pub fn safe_area(&self) -> PhysicalInsets { // Only available on iOS 11.0 let insets = if app_state::os_capabilities().safe_area { self.view.safeAreaInsets() @@ -214,13 +216,8 @@ impl Inner { 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(insets.left, insets.top); - let size = LogicalSize::new( - frame.size.width - insets.left - insets.right, - frame.size.height - insets.top - insets.bottom, - ); - let scale_factor = self.scale_factor(); - (position.to_physical(scale_factor), size.to_physical(scale_factor)) + 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) { @@ -637,7 +634,7 @@ impl CoreWindow for Window { self.maybe_wait_on_main(|delegate| delegate.outer_size()) } - fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { + fn safe_area(&self) -> PhysicalInsets { self.maybe_wait_on_main(|delegate| delegate.safe_area()) } diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 6e2cc3d210..adf5ef66ff 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, WindowId}; -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; @@ -337,8 +337,8 @@ impl CoreWindow for Window { super::logical_to_physical_rounded(window_state.outer_size(), scale_factor) } - fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { - ((0, 0).into(), self.surface_size()) + fn safe_area(&self) -> PhysicalInsets { + PhysicalInsets::new(0, 0, 0, 0) } fn set_min_surface_size(&self, min_size: Option) { diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 19e6e06f5b..e96fbbfa9a 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, WindowId, 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; @@ -106,7 +106,7 @@ impl CoreWindow for Window { self.0.outer_size() } - fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { + fn safe_area(&self) -> PhysicalInsets { self.0.safe_area() } @@ -1592,8 +1592,8 @@ impl UnownedWindow { } } - fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { - ((0, 0).into(), self.surface_size()) + fn safe_area(&self) -> PhysicalInsets { + PhysicalInsets::new(0, 0, 0, 0) } pub(crate) fn request_surface_size_physical(&self, width: u32, height: u32) { diff --git a/src/platform_impl/orbital/window.rs b/src/platform_impl/orbital/window.rs index f80d956029..ea0d1a3311 100644 --- a/src/platform_impl/orbital/window.rs +++ b/src/platform_impl/orbital/window.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex}; use super::{ActiveEventLoop, MonitorHandle, RedoxSocket, TimeSocket, WindowId, 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 as CoreWindowId}; @@ -240,8 +240,8 @@ impl CoreWindow for Window { self.surface_size() } - fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { - ((0, 0).into(), self.surface_size()) + fn safe_area(&self) -> PhysicalInsets { + PhysicalInsets::new(0, 0, 0, 0) } #[inline] diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 189b6ca272..44a03ffec1 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -9,7 +9,7 @@ use super::main_thread::{MainThreadMarker, MainThreadSafe}; use super::monitor::MonitorHandler; use super::r#async::Dispatcher; use super::{backend, lock, ActiveEventLoop}; -use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; +use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{NotSupportedError, RequestError}; use crate::icon::Icon; use crate::monitor::MonitorHandle as RootMonitorHandle; @@ -155,7 +155,7 @@ impl RootWindow for Window { self.surface_size() } - fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { + fn safe_area(&self) -> PhysicalInsets { self.inner.queue(|inner| { let (safe_start_pos, safe_size) = inner.safe_area.get(); let safe_end_pos = LogicalPosition::new( @@ -173,19 +173,13 @@ impl RootWindow for Window { surface_start_pos.y + surface_size.height, ); - let pos = LogicalPosition::new( - f64::max(safe_start_pos.x - surface_start_pos.x, 0.), - f64::max(safe_start_pos.y - surface_start_pos.y, 0.), - ); - let width = safe_size.width - - (f64::max(surface_start_pos.x - safe_start_pos.x, 0.) - + f64::max(safe_end_pos.x - surface_end_pos.x, 0.)); - let height = safe_size.height - - (f64::max(surface_start_pos.y - safe_start_pos.y, 0.) - + f64::max(safe_end_pos.y - surface_end_pos.y, 0.)); - let size = LogicalSize::new(width, height); - - (pos.to_physical(inner.scale_factor()), size.to_physical(inner.scale_factor())) + let top = f64::max(safe_start_pos.y - surface_start_pos.y, 0.); + let left = f64::max(safe_start_pos.x - surface_start_pos.x, 0.); + let bottom = f64::max(surface_end_pos.y - safe_end_pos.y, 0.); + let right = f64::max(surface_end_pos.x - safe_end_pos.x, 0.); + + let insets = LogicalInsets::new(top, left, bottom, right); + insets.to_physical(inner.scale_factor()) }) } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 1a34626206..9d027d48a1 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -46,7 +46,7 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{ }; 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::icon::Icon; use crate::monitor::MonitorHandle as CoreMonitorHandle; @@ -499,8 +499,8 @@ impl CoreWindow for Window { None } - fn safe_area(&self) -> (PhysicalPosition, PhysicalSize) { - ((0, 0).into(), self.surface_size()) + fn safe_area(&self) -> PhysicalInsets { + PhysicalInsets::new(0, 0, 0, 0) } fn set_min_surface_size(&self, size: Option) { diff --git a/src/window.rs b/src/window.rs index 2a3c1de896..1dc7d588f6 100644 --- a/src/window.rs +++ b/src/window.rs @@ -7,7 +7,7 @@ pub use cursor_icon::{CursorIcon, ParseError as CursorIconParseError}; use serde::{Deserialize, Serialize}; pub use crate::cursor::{BadImage, Cursor, CustomCursor, CustomCursorSource, MAX_CURSOR_SIZE}; -use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; +use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::RequestError; pub use crate::icon::{BadIcon, Icon}; use crate::monitor::{MonitorHandle, VideoModeHandle}; @@ -729,7 +729,7 @@ pub trait Window: AsAny + Send + Sync { /// [`Window::surface_size`]._ fn outer_size(&self) -> PhysicalSize; - /// The area of the surface that is unobstructed. + /// The inset 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 @@ -750,7 +750,28 @@ pub trait Window: AsAny + Send + Sync { /// /// - **Android / Orbital / Wayland / Web / Windows / X11:** Unimplemented, returns `((0, 0), /// surface_size)`. - fn safe_area(&self) -> (PhysicalPosition, PhysicalSize); + /// + /// ## Examples + /// + /// Convert safe area insets to a size and a position. + /// + /// ``` + /// use winit::dpi::{PhysicalPosition, PhysicalSize}; + /// + /// # let surface_size = dpi::PhysicalSize::new(0, 0); + /// # #[cfg(requires_window)] + /// let surface_size = window.surface_size(); + /// # let insets = dpi::PhysicalInsets::new(0, 0, 0, 0); + /// # #[cfg(requires_window)] + /// let insets = window.safe_area(); + /// + /// let origin = PhysicalPosition::new(insets.left, insets.top); + /// let size = PhysicalSize::new( + /// surface_size.width - insets.left - insets.right, + /// surface_size.height - insets.top - insets.bottom, + /// ); + /// ``` + fn safe_area(&self) -> PhysicalInsets; /// Sets a minimum dimensions of the window's surface. /// From c288aeb78061c4e6ae7f6b6f583373a64f3348c6 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 4 Nov 2024 15:06:06 +0100 Subject: [PATCH 34/42] Fix web --- src/platform_impl/web/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 2dc44312ff..352a231a6a 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -9,7 +9,7 @@ use super::main_thread::{MainThreadMarker, MainThreadSafe}; use super::monitor::MonitorHandler; use super::r#async::Dispatcher; use super::{backend, lock, ActiveEventLoop}; -use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size}; +use crate::dpi::{LogicalInsets, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{NotSupportedError, RequestError}; use crate::icon::Icon; use crate::monitor::MonitorHandle as RootMonitorHandle; From 566b7900356898f8349438563e1bdbaba4196913 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 4 Nov 2024 15:08:10 +0100 Subject: [PATCH 35/42] Remove test code --- src/platform_impl/apple/appkit/window_delegate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index c8d62d9629..62707277e8 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -985,7 +985,7 @@ impl WindowDelegate { pub fn safe_area(&self) -> PhysicalInsets { // Only available on macOS 11.0 - let insets = if self.view().respondsToSelector(sel!(safeAreaInsets)) || false { + 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() } From 8143515b87320da7356e8d6b61721723391eb10b Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 4 Nov 2024 15:26:31 +0100 Subject: [PATCH 36/42] Update mobile diagram --- docs/res/coordinate-systems-mobile.svg | 2 +- docs/res/coordinate-systems.drawio | 42 ++++++++++++-------------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/docs/res/coordinate-systems-mobile.svg b/docs/res/coordinate-systems-mobile.svg index d18053e649..d045da31cb 100644 --- a/docs/res/coordinate-systems-mobile.svg +++ b/docs/res/coordinate-systems-mobile.svg @@ -1 +1 @@ -
safe_area
(size)
safe_...
surface_size
surfa...
safe_area
(position)
safe_...
+
surface_size
surfa...
safe_area.top
safe_...
safe_area.bottom
safe_...
\ No newline at end of file diff --git a/docs/res/coordinate-systems.drawio b/docs/res/coordinate-systems.drawio index 0f82751c7d..6e884658a4 100644 --- a/docs/res/coordinate-systems.drawio +++ b/docs/res/coordinate-systems.drawio @@ -1,4 +1,4 @@ - + @@ -80,51 +80,47 @@ - + - + - + - + - + - + - + - - + + - - + + - + - - - - - - + + - - - - + + + + From 773b002a5fb80ea2507cdfb4e4bc36b528e766f0 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 4 Nov 2024 15:31:07 +0100 Subject: [PATCH 37/42] Fix broken doc --- docs/res/coordinate-systems-mobile.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/res/coordinate-systems-mobile.svg b/docs/res/coordinate-systems-mobile.svg index d045da31cb..b1ed4c8274 100644 --- a/docs/res/coordinate-systems-mobile.svg +++ b/docs/res/coordinate-systems-mobile.svg @@ -1 +1 @@ -
surface_size
surfa...
safe_area.top
safe_...
safe_area.bottom
safe_...
\ No newline at end of file +
surface_size
surfa...
safe_area.top
safe_...
safe_area.bottom
safe_...
From 20c95c24dd5995ee3c54584cc974992d4a6e7dce Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 21 Nov 2024 14:18:15 +0100 Subject: [PATCH 38/42] Draw a rectangle instead of a star --- examples/window.rs | 59 +++++++++++++--------------------------------- 1 file changed, 17 insertions(+), 42 deletions(-) diff --git a/examples/window.rs b/examples/window.rs index 7ec169df91..827b72a1e8 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -923,53 +923,28 @@ impl WindowState { 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 + // Draw a different color inside the safe area let surface_size = self.window.surface_size(); let insets = self.window.safe_area(); - // Compute the safe rectangle. - // Winit's coordinate system has (0, 0) in the top-left corner. - let origin = PhysicalPosition::new(insets.left, insets.top); - let size = PhysicalSize::new( - surface_size.width - insets.left - insets.right, - surface_size.height - insets.top - insets.bottom, - ); - - 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); - - !(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 { + 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 + }; } } } From bea232c9c14e595267034d62d1601daaede4c42e Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 21 Nov 2024 15:56:01 +0100 Subject: [PATCH 39/42] Fix text on devices with a larger monospace font --- docs/res/coordinate-systems-desktop.svg | 2 +- docs/res/coordinate-systems.drawio | 64 ++++++++++++------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/res/coordinate-systems-desktop.svg b/docs/res/coordinate-systems-desktop.svg index 14d33a759a..112b65ba9b 100644 --- a/docs/res/coordinate-systems-desktop.svg +++ b/docs/res/coordinate-systems-desktop.svg @@ -1 +1 @@ -
outer_position
outer...
outer_size
outer...
surface_size
surfa...
surface_position
surfa...
+
outer_position
outer...
outer_size
outer...
surface_size
surfa...
surface_position
surfa...
\ No newline at end of file diff --git a/docs/res/coordinate-systems.drawio b/docs/res/coordinate-systems.drawio index 6e884658a4..2bfb009883 100644 --- a/docs/res/coordinate-systems.drawio +++ b/docs/res/coordinate-systems.drawio @@ -1,79 +1,79 @@ - + - + - + + + + - + - - - - + - + - - + + - - + + - - + + - - + + - + - - + + - - + + - - - + + + - - + + - - - + + + - + - + From 1594cea17c3218baa50d81720b02a4e04312215f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 21 Nov 2024 15:56:25 +0100 Subject: [PATCH 40/42] Move source files out of docs/res, into docs --- docs/{res => }/ATTRIBUTION.md | 0 docs/{res => }/coordinate-systems.drawio | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename docs/{res => }/ATTRIBUTION.md (100%) rename docs/{res => }/coordinate-systems.drawio (100%) diff --git a/docs/res/ATTRIBUTION.md b/docs/ATTRIBUTION.md similarity index 100% rename from docs/res/ATTRIBUTION.md rename to docs/ATTRIBUTION.md diff --git a/docs/res/coordinate-systems.drawio b/docs/coordinate-systems.drawio similarity index 100% rename from docs/res/coordinate-systems.drawio rename to docs/coordinate-systems.drawio From 5cf650a5d805cbee23f699b0924692543c691b42 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 21 Nov 2024 17:18:59 +0100 Subject: [PATCH 41/42] Fix missing newline --- docs/res/coordinate-systems-desktop.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/res/coordinate-systems-desktop.svg b/docs/res/coordinate-systems-desktop.svg index 112b65ba9b..1171c53a39 100644 --- a/docs/res/coordinate-systems-desktop.svg +++ b/docs/res/coordinate-systems-desktop.svg @@ -1 +1 @@ -
outer_position
outer...
outer_size
outer...
surface_size
surfa...
surface_position
surfa...
\ No newline at end of file +
outer_position
outer...
outer_size
outer...
surface_size
surfa...
surface_position
surfa...
From de2414f1a201403fb4e9bfa952dcdd7ef53fe1a0 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 21 Nov 2024 17:24:29 +0100 Subject: [PATCH 42/42] Update outdated comment --- src/window.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/window.rs b/src/window.rs index 09d964ed1e..cd16d3878a 100644 --- a/src/window.rs +++ b/src/window.rs @@ -728,12 +728,11 @@ pub trait Window: AsAny + Send + Sync { /// Note that the safe area does not take occlusion from other windows into account; in a way, /// it is only a "hardware"-level occlusion. /// - /// If the entire content of the surface is visible, this returns `((0, 0), surface_size)`. + /// If the entire content of the surface is visible, this returns `(0, 0, 0, 0)`. /// /// ## Platform-specific /// - /// - **Android / Orbital / Wayland / Web / Windows / X11:** Unimplemented, returns `((0, 0), - /// surface_size)`. + /// - **Android / Orbital / Wayland / Windows / X11:** Unimplemented, returns `(0, 0, 0, 0)`. /// /// ## Examples ///