//! UI scaling is important, so read the docs for this module if you don't want to be confused. //! //! ## Why should I care about UI scaling? //! //! Modern computer screens don't have a consistent relationship between resolution and size. //! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens //! normally being less than a quarter the size of their desktop counterparts. What's more, neither //! desktop nor mobile screens are consistent resolutions within their own size classes - common //! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K //! and beyond. //! //! Given that, it's a mistake to assume that 2D content will only be displayed on screens with //! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen, //! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up //! about a quarter of the physical space as it did on the 1080p screen. That issue is especially //! problematic with text rendering, where quarter-sized text becomes a significant legibility //! problem. //! //! Failure to account for the scale factor can create a significantly degraded user experience. //! Most notably, it can make users feel like they have bad eyesight, which will potentially cause //! them to think about growing elderly, resulting in them having an existential crisis. Once users //! enter that state, they will no longer be focused on your application. //! //! ## How should I handle it? //! //! The solution to this problem is to account for the device's *scale factor*. The scale factor is //! the factor UI elements should be scaled by to be consistent with the rest of the user's system - //! for example, a button that's normally 50 pixels across would be 100 pixels across on a device //! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`. //! //! The scale factor correlates with, but no has direct relationship to, the screen's actual DPI //! (dots per inch). Operating systems used to define the scale factor in terms of the screen's //! approximate DPI (at the time, 72 pixels per inch), but [Microsoft decided to report that the DPI //! was roughly 1/3 bigger than the screen's actual DPI (so, 96 pixels per inch) in order to make //! text more legible][microsoft_dpi]. As a result, the exact DPI as defined by the OS doesn't carry //! a whole lot of weight when designing cross-platform UIs. Scaled pixels should generally be used //! as the base unit for on-screen UI measurement, instead of DPI-dependent units such as //! [points][points] or [picas][picas]. //! //! ### Position and Size types //! //! Winit's `Physical(Position|Size)` types correspond with the actual pixels on the device, and the //! `Logical(Position|Size)` types correspond to the physical pixels divided by the scale factor. //! All of Winit's functions return physical types, but can take either logical or physical //! coordinates as input, allowing you to use the most convenient coordinate system for your //! particular application. //! //! Winit's position and size types types are generic over their exact pixel type, `P`, to allow the //! API to have integer precision where appropriate (e.g. most window manipulation functions) and //! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch //! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so //! will truncate the fractional part of the float, rather than properly round to the nearest //! integer. Use the provided `cast` function or `From`/`Into` conversions, which handle the //! rounding properly. Note that precision loss will still occur when rounding from a float to an //! int, although rounding lessens the problem. //! //! ### Events //! //! Winit will dispatch a [`DpiChanged`](crate::event::WindowEvent::DpiChanged) //! event whenever a window's scale factor has changed. This can happen if the user drags their //! window from a standard-resolution monitor to a high-DPI monitor, or if the user changes their //! DPI settings. This gives you a chance to rescale your application's UI elements and adjust how //! the platform changes the window's size to reflect the new scale factor. If a window hasn't //! received a [`DpiChanged`](crate::event::WindowEvent::DpiChanged) event, //! then its scale factor is `1.0`. //! //! ## How is the scale factor calculated? //! //! Scale factor is calculated differently on different platforms: //! //! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the //! display settings. While users are free to select any option they want, they're only given a //! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is //! global and changing it requires logging out. See [this article][windows_1] for technical //! details. //! - **macOS:** "retina displays" have a scale factor of 2.0. Otherwise, the scale factor is 1.0. //! Intermediate scale factors are never used. It's possible for any display to use that 2.0 scale //! factor, given the use of the command line. //! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit //! currently uses a three-pronged approach: //! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present. //! + If not present, use the value set in `Xft.dpi` in Xresources. //! + Otherwise, calcuate the scale factor based on the millimeter monitor dimensions provided by XRandR. //! //! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the //! XRandR scaling method. Generally speaking, you should try to configure the standard system //! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`. //! - **Wayland:** On Wayland, scale factors are set per-screen by the server, and are always //! integers (most often 1 or 2). //! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range //! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more //! information. //! - **Android:** Scale factors are set by the manufacturer to the value that best suits the //! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information. //! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels. //! //! [microsoft_dpi]: https://blogs.msdn.microsoft.com/fontblog/2005/11/08/where-does-96-dpi-come-from-in-windows/ //! [points]: https://en.wikipedia.org/wiki/Point_(typography) //! [picas]: https://en.wikipedia.org/wiki/Pica_(typography) //! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows //! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html //! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/ //! [android_1]: https://developer.android.com/training/multiscreen/screendensities pub trait Pixel: Copy + Into { fn from_f64(f: f64) -> Self; fn cast(self) -> P { P::from_f64(self.into()) } } impl Pixel for u8 { fn from_f64(f: f64) -> Self { f.round() as u8 } } impl Pixel for u16 { fn from_f64(f: f64) -> Self { f.round() as u16 } } impl Pixel for u32 { fn from_f64(f: f64) -> Self { f.round() as u32 } } impl Pixel for i8 { fn from_f64(f: f64) -> Self { f.round() as i8 } } impl Pixel for i16 { fn from_f64(f: f64) -> Self { f.round() as i16 } } impl Pixel for i32 { fn from_f64(f: f64) -> Self { f.round() as i32 } } impl Pixel for f32 { fn from_f64(f: f64) -> Self { f as f32 } } impl Pixel for f64 { fn from_f64(f: f64) -> Self { f } } /// Checks that the scale factor is a normal positive `f64`. /// /// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from /// anywhere other than winit, it's recommended to validate them using this function before passing them to winit; /// otherwise, you risk panics. #[inline] pub fn validate_scale_factor(dpi_factor: f64) -> bool { dpi_factor.is_sign_positive() && dpi_factor.is_normal() } /// A position represented in logical pixels. /// /// The position is stored as floats, so please be careful. Casting floats to integers truncates the /// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>` /// implementation is provided which does the rounding for you. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct LogicalPosition

{ pub x: P, pub y: P, } impl

LogicalPosition

{ #[inline] pub const fn new(x: P, y: P) -> Self { LogicalPosition { x, y } } } impl LogicalPosition

{ #[inline] pub fn from_physical>, X: Pixel>( physical: T, dpi_factor: f64, ) -> Self { physical.into().to_logical(dpi_factor) } #[inline] pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition { assert!(validate_scale_factor(dpi_factor)); let x = self.x.into() * dpi_factor; let y = self.y.into() * dpi_factor; PhysicalPosition::new(x, y).cast() } #[inline] pub fn cast(&self) -> LogicalPosition { LogicalPosition { x: self.x.cast(), y: self.y.cast(), } } } impl From<(X, X)> for LogicalPosition

{ fn from((x, y): (X, X)) -> LogicalPosition

{ LogicalPosition::new(x.cast(), y.cast()) } } impl Into<(X, X)> for LogicalPosition

{ fn into(self: Self) -> (X, X) { (self.x.cast(), self.y.cast()) } } impl From<[X; 2]> for LogicalPosition

{ fn from([x, y]: [X; 2]) -> LogicalPosition

{ LogicalPosition::new(x.cast(), y.cast()) } } impl Into<[X; 2]> for LogicalPosition

{ fn into(self: Self) -> [X; 2] { [self.x.cast(), self.y.cast()] } } /// A position represented in physical pixels. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PhysicalPosition

{ pub x: P, pub y: P, } impl

PhysicalPosition

{ #[inline] pub const fn new(x: P, y: P) -> Self { PhysicalPosition { x, y } } } impl PhysicalPosition

{ #[inline] pub fn from_logical>, X: Pixel>( logical: T, dpi_factor: f64, ) -> Self { logical.into().to_physical(dpi_factor) } #[inline] pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition { assert!(validate_scale_factor(dpi_factor)); let x = self.x.into() / dpi_factor; let y = self.y.into() / dpi_factor; LogicalPosition::new(x, y).cast() } #[inline] pub fn cast(&self) -> PhysicalPosition { PhysicalPosition { x: self.x.cast(), y: self.y.cast(), } } } impl From<(X, X)> for PhysicalPosition

{ fn from((x, y): (X, X)) -> PhysicalPosition

{ PhysicalPosition::new(x.cast(), y.cast()) } } impl Into<(X, X)> for PhysicalPosition

{ fn into(self: Self) -> (X, X) { (self.x.cast(), self.y.cast()) } } impl From<[X; 2]> for PhysicalPosition

{ fn from([x, y]: [X; 2]) -> PhysicalPosition

{ PhysicalPosition::new(x.cast(), y.cast()) } } impl Into<[X; 2]> for PhysicalPosition

{ fn into(self: Self) -> [X; 2] { [self.x.cast(), self.y.cast()] } } /// A size represented in logical pixels. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct LogicalSize

{ pub width: P, pub height: P, } impl

LogicalSize

{ #[inline] pub const fn new(width: P, height: P) -> Self { LogicalSize { width, height } } } impl LogicalSize

{ #[inline] pub fn from_physical>, X: Pixel>(physical: T, dpi_factor: f64) -> Self { physical.into().to_logical(dpi_factor) } #[inline] pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize { assert!(validate_scale_factor(dpi_factor)); let width = self.width.into() * dpi_factor; let height = self.height.into() * dpi_factor; PhysicalSize::new(width, height).cast() } #[inline] pub fn cast(&self) -> LogicalSize { LogicalSize { width: self.width.cast(), height: self.height.cast(), } } } impl From<(X, X)> for LogicalSize

{ fn from((x, y): (X, X)) -> LogicalSize

{ LogicalSize::new(x.cast(), y.cast()) } } impl Into<(X, X)> for LogicalSize

{ fn into(self: LogicalSize

) -> (X, X) { (self.width.cast(), self.height.cast()) } } impl From<[X; 2]> for LogicalSize

{ fn from([x, y]: [X; 2]) -> LogicalSize

{ LogicalSize::new(x.cast(), y.cast()) } } impl Into<[X; 2]> for LogicalSize

{ fn into(self: Self) -> [X; 2] { [self.width.cast(), self.height.cast()] } } /// A size represented in physical pixels. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PhysicalSize

{ pub width: P, pub height: P, } impl

PhysicalSize

{ #[inline] pub const fn new(width: P, height: P) -> Self { PhysicalSize { width, height } } } impl PhysicalSize

{ #[inline] pub fn from_logical>, X: Pixel>(logical: T, dpi_factor: f64) -> Self { logical.into().to_physical(dpi_factor) } #[inline] pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize { assert!(validate_scale_factor(dpi_factor)); let width = self.width.into() / dpi_factor; let height = self.height.into() / dpi_factor; LogicalSize::new(width, height).cast() } #[inline] pub fn cast(&self) -> PhysicalSize { PhysicalSize { width: self.width.cast(), height: self.height.cast(), } } } impl From<(X, X)> for PhysicalSize

{ fn from((x, y): (X, X)) -> PhysicalSize

{ PhysicalSize::new(x.cast(), y.cast()) } } impl Into<(X, X)> for PhysicalSize

{ fn into(self: Self) -> (X, X) { (self.width.cast(), self.height.cast()) } } impl From<[X; 2]> for PhysicalSize

{ fn from([x, y]: [X; 2]) -> PhysicalSize

{ PhysicalSize::new(x.cast(), y.cast()) } } impl Into<[X; 2]> for PhysicalSize

{ fn into(self: Self) -> [X; 2] { [self.width.cast(), self.height.cast()] } } /// A size that's either physical or logical. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Size { Physical(PhysicalSize), Logical(LogicalSize), } impl Size { pub fn new>(size: S) -> Size { size.into() } pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize

{ match *self { Size::Physical(size) => size.to_logical(dpi_factor), Size::Logical(size) => size.cast(), } } pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize

{ match *self { Size::Physical(size) => size.cast(), Size::Logical(size) => size.to_physical(dpi_factor), } } } impl From> for Size { #[inline] fn from(size: PhysicalSize

) -> Size { Size::Physical(size.cast()) } } impl From> for Size { #[inline] fn from(size: LogicalSize

) -> Size { Size::Logical(size.cast()) } } /// A position that's either physical or logical. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Position { Physical(PhysicalPosition), Logical(LogicalPosition), } impl Position { pub fn new>(position: S) -> Position { position.into() } pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition

{ match *self { Position::Physical(position) => position.to_logical(dpi_factor), Position::Logical(position) => position.cast(), } } pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition

{ match *self { Position::Physical(position) => position.cast(), Position::Logical(position) => position.to_physical(dpi_factor), } } } impl From> for Position { #[inline] fn from(position: PhysicalPosition

) -> Position { Position::Physical(position.cast()) } } impl From> for Position { #[inline] fn from(position: LogicalPosition

) -> Position { Position::Logical(position.cast()) } }