diff --git a/examples/window.rs b/examples/window.rs index 3d87e2f8d9..469f235653 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -33,6 +33,8 @@ use winit::platform::startup_notify::{ use winit::platform::web::{ActiveEventLoopExtWeb, CustomCursorExtWeb, WindowAttributesExtWeb}; #[cfg(x11_platform)] use winit::platform::x11::WindowAttributesExtX11; +#[cfg(not(android_platform))] +use winit::window::InsetKind; use winit::window::{ Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, Icon, ResizeDirection, Theme, Window, WindowAttributes, WindowId, @@ -949,7 +951,7 @@ impl WindowState { // Draw a different color inside the safe area let surface_size = self.window.surface_size(); - let insets = self.window.safe_area(); + let insets = self.window.insets(InsetKind::SafeArea); 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; diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 4e91f91773..dc1977456a 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -74,7 +74,8 @@ changelog entry. - On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env variables to test the respective modifiers of window creation. - Added `Window::surface_position`, which is the position of the surface inside the window. -- Added `Window::safe_area`, which describes the area of the surface that is unobstructed. +- Added `Window::insets`, which lets you read the distance from the edge of the window taken by a certain `InsetKind`. +- Added `InsetKind::SafeArea`, used with `Window::insets`, which describes the area of the surface that is unobstructed. ### Changed diff --git a/src/event.rs b/src/event.rs index 94ed8a2584..2c01cc585e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -480,7 +480,7 @@ pub enum WindowEvent { /// Winit will aggregate duplicate redraw requests into a single event, to /// help avoid duplicating rendering work. /// - /// [the safe area]: crate::window::Window::safe_area + /// [the safe area]: crate::window::InsetKind::SafeArea RedrawRequested, } diff --git a/src/lib.rs b/src/lib.rs index 3dadb80d1b..b2fe0edbe1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,12 +124,14 @@ //! 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 +//! can be accessed by querying [`Window::insets`] with [`InsetKind::SafeArea`], 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::insets`]: crate::window::Window::insets +//! [`InsetKind::SafeArea`]: crate::window::InsetKind::SafeArea //! //! # Coordinate systems //! diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 79c0d57c8b..46294f2b37 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -94,7 +94,7 @@ pub trait WindowExtMacOS { /// 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. + /// overlap with the notch on newer devices; see [`Window::insets`] for details. fn set_simple_fullscreen(&self, fullscreen: bool) -> bool; /// Returns whether or not the window has shadow. diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 0d07506a02..3fd0afe594 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -24,7 +24,7 @@ use crate::event_loop::{ use crate::monitor::MonitorHandle as RootMonitorHandle; use crate::platform::pump_events::PumpStatus; use crate::window::{ - self, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, ImePurpose, + self, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, ImePurpose, InsetKind, ResizeDirection, Theme, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel, }; @@ -857,7 +857,7 @@ impl CoreWindow for Window { screen_size(&self.app) } - fn safe_area(&self) -> PhysicalInsets { + fn insets(&self, _kind: InsetKind) -> PhysicalInsets { PhysicalInsets::new(0, 0, 0, 0) } diff --git a/src/platform_impl/apple/appkit/window.rs b/src/platform_impl/apple/appkit/window.rs index abc3bb2f48..999159c9ac 100644 --- a/src/platform_impl/apple/appkit/window.rs +++ b/src/platform_impl/apple/appkit/window.rs @@ -11,8 +11,8 @@ use super::window_delegate::WindowDelegate; use crate::error::RequestError; use crate::monitor::MonitorHandle as CoreMonitorHandle; use crate::window::{ - Cursor, Fullscreen, Icon, ImePurpose, Theme, UserAttentionType, Window as CoreWindow, - WindowAttributes, WindowButtons, WindowId, WindowLevel, + Cursor, Fullscreen, Icon, ImePurpose, InsetKind, Theme, UserAttentionType, + Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel, }; pub(crate) struct Window { @@ -131,8 +131,8 @@ impl CoreWindow for Window { self.maybe_wait_on_main(|delegate| delegate.outer_size()) } - fn safe_area(&self) -> dpi::PhysicalInsets { - self.maybe_wait_on_main(|delegate| delegate.safe_area()) + fn insets(&self, kind: InsetKind) -> dpi::PhysicalInsets { + self.maybe_wait_on_main(|delegate| delegate.insets(kind)) } fn set_min_surface_size(&self, min_size: Option) { diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index 62707277e8..fd2c64014a 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -43,7 +43,7 @@ use crate::error::{NotSupportedError, RequestError}; use crate::event::{SurfaceSizeWriter, WindowEvent}; use crate::platform::macos::{OptionAsAlt, WindowExtMacOS}; use crate::window::{ - Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType, + Cursor, CursorGrabMode, Icon, ImePurpose, InsetKind, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowId, WindowLevel, }; @@ -983,7 +983,13 @@ impl WindowDelegate { logical.to_physical(self.scale_factor()) } - pub fn safe_area(&self) -> PhysicalInsets { + pub fn insets(&self, kind: InsetKind) -> PhysicalInsets { + match kind { + InsetKind::SafeArea => self.safe_area(), + } + } + + fn safe_area(&self) -> PhysicalInsets { // Only available on macOS 11.0 let insets = if self.view().respondsToSelector(sel!(safeAreaInsets)) { // Includes NSWindowStyleMask::FullSizeContentView by default, and the notch because diff --git a/src/platform_impl/apple/uikit/window.rs b/src/platform_impl/apple/uikit/window.rs index e992e8072e..85a5ef712d 100644 --- a/src/platform_impl/apple/uikit/window.rs +++ b/src/platform_impl/apple/uikit/window.rs @@ -28,8 +28,8 @@ use crate::icon::Icon; use crate::monitor::MonitorHandle as CoreMonitorHandle; use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations}; use crate::window::{ - CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow, - WindowAttributes, WindowButtons, WindowId, WindowLevel, + CursorGrabMode, ImePurpose, InsetKind, ResizeDirection, Theme, UserAttentionType, + Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel, }; declare_class!( @@ -204,7 +204,13 @@ impl Inner { Some(self.surface_size()) } - pub fn safe_area(&self) -> PhysicalInsets { + pub fn insets(&self, kind: InsetKind) -> PhysicalInsets { + match kind { + InsetKind::SafeArea => self.safe_area(), + } + } + + fn safe_area(&self) -> PhysicalInsets { // Only available on iOS 11.0 let insets = if app_state::os_capabilities().safe_area { self.view.safeAreaInsets() @@ -627,8 +633,8 @@ impl CoreWindow for Window { self.maybe_wait_on_main(|delegate| delegate.outer_size()) } - fn safe_area(&self) -> PhysicalInsets { - self.maybe_wait_on_main(|delegate| delegate.safe_area()) + fn insets(&self, kind: InsetKind) -> PhysicalInsets { + self.maybe_wait_on_main(|delegate| delegate.insets(kind)) } fn set_min_surface_size(&self, min_size: Option) { diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index cb7317737a..c02c9d9317 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -24,8 +24,8 @@ use crate::event_loop::AsyncRequestSerial; use crate::monitor::MonitorHandle as CoreMonitorHandle; use crate::platform_impl::{Fullscreen, MonitorHandle as PlatformMonitorHandle}; use crate::window::{ - Cursor, CursorGrabMode, Fullscreen as CoreFullscreen, ImePurpose, ResizeDirection, Theme, - UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, + Cursor, CursorGrabMode, Fullscreen as CoreFullscreen, ImePurpose, InsetKind, ResizeDirection, + Theme, UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel, }; @@ -335,7 +335,7 @@ impl CoreWindow for Window { super::logical_to_physical_rounded(window_state.outer_size(), scale_factor) } - fn safe_area(&self) -> PhysicalInsets { + fn insets(&self, _kind: InsetKind) -> PhysicalInsets { PhysicalInsets::new(0, 0, 0, 0) } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index e3a72ad228..f746278988 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -35,8 +35,8 @@ use crate::platform_impl::{ VideoModeHandle as PlatformVideoModeHandle, }; use crate::window::{ - CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow, - WindowAttributes, WindowButtons, WindowId, WindowLevel, + CursorGrabMode, ImePurpose, InsetKind, ResizeDirection, Theme, UserAttentionType, + Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel, }; pub(crate) struct Window(Arc); @@ -106,8 +106,8 @@ impl CoreWindow for Window { self.0.outer_size() } - fn safe_area(&self) -> PhysicalInsets { - self.0.safe_area() + fn insets(&self, kind: InsetKind) -> PhysicalInsets { + self.0.insets(kind) } fn set_min_surface_size(&self, min_size: Option) { @@ -1592,7 +1592,7 @@ impl UnownedWindow { } } - fn safe_area(&self) -> PhysicalInsets { + fn insets(&self, _kind: InsetKind) -> PhysicalInsets { PhysicalInsets::new(0, 0, 0, 0) } diff --git a/src/platform_impl/orbital/window.rs b/src/platform_impl/orbital/window.rs index 0699b1fe10..223ff48a70 100644 --- a/src/platform_impl/orbital/window.rs +++ b/src/platform_impl/orbital/window.rs @@ -7,7 +7,7 @@ use crate::cursor::Cursor; 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}; +use crate::window::{self, Fullscreen, ImePurpose, InsetKind, Window as CoreWindow, WindowId}; // These values match the values uses in the `window_new` function in orbital: // https://gitlab.redox-os.org/redox-os/orbital/-/blob/master/src/scheme.rs @@ -239,7 +239,7 @@ impl CoreWindow for Window { self.surface_size() } - fn safe_area(&self) -> PhysicalInsets { + fn insets(&self, _kind: InsetKind) -> PhysicalInsets { PhysicalInsets::new(0, 0, 0, 0) } diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 352a231a6a..59a4860ee1 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -14,8 +14,8 @@ use crate::error::{NotSupportedError, RequestError}; use crate::icon::Icon; use crate::monitor::MonitorHandle as RootMonitorHandle; use crate::window::{ - Cursor, CursorGrabMode, Fullscreen as RootFullscreen, ImePurpose, ResizeDirection, Theme, - UserAttentionType, Window as RootWindow, WindowAttributes, WindowButtons, WindowId, + Cursor, CursorGrabMode, Fullscreen as RootFullscreen, ImePurpose, InsetKind, ResizeDirection, + Theme, UserAttentionType, Window as RootWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel, }; @@ -91,6 +91,34 @@ impl Window { lock::is_cursor_lock_raw(inner.canvas.navigator(), inner.canvas.document()) }) } + + 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( + 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 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()) + }) + } } impl RootWindow for Window { @@ -155,32 +183,10 @@ impl RootWindow for Window { self.surface_size() } - 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( - 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 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()) - }) + fn insets(&self, kind: InsetKind) -> PhysicalInsets { + match kind { + InsetKind::SafeArea => self.safe_area(), + } } fn set_min_surface_size(&self, min_size: Option) { diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index aee882c847..f2876cb5ad 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -68,7 +68,7 @@ use crate::platform_impl::platform::window_state::{ }; use crate::platform_impl::platform::{monitor, util, Fullscreen, SelectedCursor}; use crate::window::{ - CursorGrabMode, Fullscreen as CoreFullscreen, ImePurpose, ResizeDirection, Theme, + CursorGrabMode, Fullscreen as CoreFullscreen, ImePurpose, InsetKind, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel, }; @@ -494,7 +494,7 @@ impl CoreWindow for Window { None } - fn safe_area(&self) -> PhysicalInsets { + fn insets(&self, _kind: InsetKind) -> PhysicalInsets { PhysicalInsets::new(0, 0, 0, 0) } diff --git a/src/window.rs b/src/window.rs index cd16d3878a..5868f71782 100644 --- a/src/window.rs +++ b/src/window.rs @@ -654,7 +654,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 [`insets()`] and [`InsetKind::SafeArea`] for details. /// /// ## Platform-specific /// @@ -662,7 +662,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 + /// [`insets()`]: Window::insets fn surface_size(&self) -> PhysicalSize; /// Request the new size for the surface. @@ -713,26 +713,19 @@ pub trait Window: AsAny + Send + Sync { /// [`Window::surface_size`]._ fn outer_size(&self) -> PhysicalSize; - /// The inset area of the surface that is unobstructed. + /// Request the size of a specific [`InsetKind`]. /// - /// 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. + /// 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 insets to compute the area within which you can draw your + /// important content. /// - /// 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]. - /// - /// 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, 0, 0)`. - /// - /// ## Platform-specific - /// - /// - **Android / Orbital / Wayland / Windows / X11:** Unimplemented, returns `(0, 0, 0, 0)`. + /// On some devices, especially mobile devices, the screen is not a perfect + /// rectangle, and may have rounded corners, notches, bezels, and so on. + /// Some areas of the window may also be used by the platform itself for + /// drawing its own elements. /// /// ## Examples /// @@ -746,7 +739,7 @@ pub trait Window: AsAny + Send + Sync { /// let surface_size = window.surface_size(); /// # let insets = dpi::PhysicalInsets::new(0, 0, 0, 0); /// # #[cfg(requires_window)] - /// let insets = window.safe_area(); + /// let insets = window.insets(InsetKind::SafeArea); /// /// let origin = PhysicalPosition::new(insets.left, insets.top); /// let size = PhysicalSize::new( @@ -754,7 +747,7 @@ pub trait Window: AsAny + Send + Sync { /// surface_size.height - insets.top - insets.bottom, /// ); /// ``` - fn safe_area(&self) -> PhysicalInsets; + fn insets(&self, kind: InsetKind) -> PhysicalInsets; /// Sets a minimum dimensions of the window's surface. /// @@ -1549,3 +1542,36 @@ impl ActivationToken { Self { _token } } } + +/// Kind of window inset that a [`Window`] may have. +/// +/// Insets are a set of distances from the edges of a window which are used by +/// the platform for a specific purpose. They may be "hardware" insets, such as +/// the camera notch on a mobile device, or "software" insets, such as the space +/// which the native window control buttons take up. +/// +/// Use [`Window::insets`] to get the inset distances for a specific kind of +/// inset. +#[non_exhaustive] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum InsetKind { + /// The inset area of the surface that is guaranteed to be unobstructed by + /// any other inset - the "safest" inset. + /// + /// 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]. + /// + /// 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, 0, 0)`. + /// + /// ## Platform-specific + /// + /// - **Android / Orbital / Wayland / Windows / X11:** Unimplemented, returns `(0, 0, 0, 0)`. + SafeArea, +}