2020-01-05 11:19:17 +11:00
|
|
|
//! UI scaling is important, so read the docs for this module if you don't want to be confused.
|
2018-06-15 09:42:18 +10:00
|
|
|
//!
|
2020-01-05 11:19:17 +11:00
|
|
|
//! ## Why should I care about UI scaling?
|
2018-06-15 09:42:18 +10:00
|
|
|
//!
|
2020-01-05 11:19:17 +11:00
|
|
|
//! 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.
|
2018-06-15 09:42:18 +10:00
|
|
|
//!
|
2020-01-05 11:19:17 +11:00
|
|
|
//! 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.
|
2018-06-15 09:42:18 +10:00
|
|
|
//!
|
2020-01-05 11:19:17 +11:00
|
|
|
//! 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.
|
2018-06-15 09:42:18 +10:00
|
|
|
//!
|
2020-01-05 11:19:17 +11:00
|
|
|
//! ## How should I handle it?
|
2018-06-15 09:42:18 +10:00
|
|
|
//!
|
2020-01-05 11:19:17 +11:00
|
|
|
//! 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`.
|
2018-06-15 09:42:18 +10:00
|
|
|
//!
|
2020-01-05 11:19:17 +11:00
|
|
|
//! 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].
|
2018-07-17 00:44:29 +10:00
|
|
|
//!
|
2020-01-05 11:19:17 +11:00
|
|
|
//! ### Position and Size types
|
2018-06-15 09:42:18 +10:00
|
|
|
//!
|
2020-01-05 11:19:17 +11:00
|
|
|
//! 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.
|
2018-06-15 09:42:18 +10:00
|
|
|
//!
|
2020-01-05 11:19:17 +11:00
|
|
|
//! 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.
|
2018-06-15 09:42:18 +10:00
|
|
|
//!
|
2020-01-05 11:19:17 +11:00
|
|
|
//! ### Events
|
2018-10-20 07:32:57 +11:00
|
|
|
//!
|
2020-01-05 11:19:17 +11:00
|
|
|
//! 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`.
|
2018-10-20 07:32:57 +11:00
|
|
|
//!
|
2020-01-05 11:19:17 +11:00
|
|
|
//! ## 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
|
2018-06-15 09:42:18 +10:00
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
pub trait Pixel: Copy + Into<f64> {
|
|
|
|
fn from_f64(f: f64) -> Self;
|
|
|
|
fn cast<P: Pixel>(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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 06:52:27 +11:00
|
|
|
/// Checks that the scale factor is a normal positive `f64`.
|
2018-06-15 09:42:18 +10:00
|
|
|
///
|
2020-01-04 06:52:27 +11:00
|
|
|
/// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from
|
2018-06-15 09:42:18 +10:00
|
|
|
/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit;
|
|
|
|
/// otherwise, you risk panics.
|
|
|
|
#[inline]
|
2020-01-04 06:52:27 +11:00
|
|
|
pub fn validate_scale_factor(dpi_factor: f64) -> bool {
|
2018-06-15 09:42:18 +10:00
|
|
|
dpi_factor.is_sign_positive() && dpi_factor.is_normal()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A position represented in logical pixels.
|
|
|
|
///
|
2020-01-05 11:19:17 +11:00
|
|
|
/// 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.
|
2018-06-15 09:42:18 +10:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
2018-11-01 19:24:56 +11:00
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub struct LogicalPosition<P> {
|
|
|
|
pub x: P,
|
|
|
|
pub y: P,
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P> LogicalPosition<P> {
|
2018-06-15 09:42:18 +10:00
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub const fn new(x: P, y: P) -> Self {
|
2018-06-15 09:42:18 +10:00
|
|
|
LogicalPosition { x, y }
|
|
|
|
}
|
2020-01-04 17:33:07 +11:00
|
|
|
}
|
2018-06-15 09:42:18 +10:00
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P: Pixel> LogicalPosition<P> {
|
2018-06-15 09:42:18 +10:00
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn from_physical<T: Into<PhysicalPosition<X>>, X: Pixel>(
|
|
|
|
physical: T,
|
|
|
|
dpi_factor: f64,
|
|
|
|
) -> Self {
|
2018-06-15 09:42:18 +10:00
|
|
|
physical.into().to_logical(dpi_factor)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn to_physical<X: Pixel>(&self, dpi_factor: f64) -> PhysicalPosition<X> {
|
2020-01-04 06:52:27 +11:00
|
|
|
assert!(validate_scale_factor(dpi_factor));
|
2020-01-04 17:33:07 +11:00
|
|
|
let x = self.x.into() * dpi_factor;
|
|
|
|
let y = self.y.into() * dpi_factor;
|
|
|
|
PhysicalPosition::new(x, y).cast()
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn cast<X: Pixel>(&self) -> LogicalPosition<X> {
|
|
|
|
LogicalPosition {
|
|
|
|
x: self.x.cast(),
|
|
|
|
y: self.y.cast(),
|
|
|
|
}
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalPosition<P> {
|
|
|
|
fn from((x, y): (X, X)) -> LogicalPosition<P> {
|
|
|
|
LogicalPosition::new(x.cast(), y.cast())
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P: Pixel, X: Pixel> Into<(X, X)> for LogicalPosition<P> {
|
|
|
|
fn into(self: Self) -> (X, X) {
|
|
|
|
(self.x.cast(), self.y.cast())
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-27 09:56:47 +11:00
|
|
|
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalPosition<P> {
|
|
|
|
fn from([x, y]: [X; 2]) -> LogicalPosition<P> {
|
|
|
|
LogicalPosition::new(x.cast(), y.cast())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalPosition<P> {
|
|
|
|
fn into(self: Self) -> [X; 2] {
|
|
|
|
[self.x.cast(), self.y.cast()]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-15 09:42:18 +10:00
|
|
|
/// A position represented in physical pixels.
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
2018-11-01 19:24:56 +11:00
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub struct PhysicalPosition<P> {
|
|
|
|
pub x: P,
|
|
|
|
pub y: P,
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P> PhysicalPosition<P> {
|
2018-06-15 09:42:18 +10:00
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub const fn new(x: P, y: P) -> Self {
|
2018-06-15 09:42:18 +10:00
|
|
|
PhysicalPosition { x, y }
|
|
|
|
}
|
2020-01-04 17:33:07 +11:00
|
|
|
}
|
2018-06-15 09:42:18 +10:00
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P: Pixel> PhysicalPosition<P> {
|
2018-06-15 09:42:18 +10:00
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn from_logical<T: Into<LogicalPosition<X>>, X: Pixel>(
|
|
|
|
logical: T,
|
|
|
|
dpi_factor: f64,
|
|
|
|
) -> Self {
|
2018-06-15 09:42:18 +10:00
|
|
|
logical.into().to_physical(dpi_factor)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn to_logical<X: Pixel>(&self, dpi_factor: f64) -> LogicalPosition<X> {
|
2020-01-04 06:52:27 +11:00
|
|
|
assert!(validate_scale_factor(dpi_factor));
|
2020-01-04 17:33:07 +11:00
|
|
|
let x = self.x.into() / dpi_factor;
|
|
|
|
let y = self.y.into() / dpi_factor;
|
|
|
|
LogicalPosition::new(x, y).cast()
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn cast<X: Pixel>(&self) -> PhysicalPosition<X> {
|
|
|
|
PhysicalPosition {
|
|
|
|
x: self.x.cast(),
|
|
|
|
y: self.y.cast(),
|
|
|
|
}
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalPosition<P> {
|
|
|
|
fn from((x, y): (X, X)) -> PhysicalPosition<P> {
|
|
|
|
PhysicalPosition::new(x.cast(), y.cast())
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P: Pixel, X: Pixel> Into<(X, X)> for PhysicalPosition<P> {
|
|
|
|
fn into(self: Self) -> (X, X) {
|
|
|
|
(self.x.cast(), self.y.cast())
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-27 09:56:47 +11:00
|
|
|
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalPosition<P> {
|
|
|
|
fn from([x, y]: [X; 2]) -> PhysicalPosition<P> {
|
|
|
|
PhysicalPosition::new(x.cast(), y.cast())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalPosition<P> {
|
|
|
|
fn into(self: Self) -> [X; 2] {
|
|
|
|
[self.x.cast(), self.y.cast()]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-15 09:42:18 +10:00
|
|
|
/// A size represented in logical pixels.
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
2018-11-01 19:24:56 +11:00
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub struct LogicalSize<P> {
|
|
|
|
pub width: P,
|
|
|
|
pub height: P,
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P> LogicalSize<P> {
|
2018-06-15 09:42:18 +10:00
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub const fn new(width: P, height: P) -> Self {
|
2018-06-15 09:42:18 +10:00
|
|
|
LogicalSize { width, height }
|
|
|
|
}
|
2020-01-04 17:33:07 +11:00
|
|
|
}
|
2018-06-15 09:42:18 +10:00
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P: Pixel> LogicalSize<P> {
|
2018-06-15 09:42:18 +10:00
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn from_physical<T: Into<PhysicalSize<X>>, X: Pixel>(physical: T, dpi_factor: f64) -> Self {
|
2018-06-15 09:42:18 +10:00
|
|
|
physical.into().to_logical(dpi_factor)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn to_physical<X: Pixel>(&self, dpi_factor: f64) -> PhysicalSize<X> {
|
2020-01-04 06:52:27 +11:00
|
|
|
assert!(validate_scale_factor(dpi_factor));
|
2020-01-04 17:33:07 +11:00
|
|
|
let width = self.width.into() * dpi_factor;
|
|
|
|
let height = self.height.into() * dpi_factor;
|
|
|
|
PhysicalSize::new(width, height).cast()
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn cast<X: Pixel>(&self) -> LogicalSize<X> {
|
|
|
|
LogicalSize {
|
|
|
|
width: self.width.cast(),
|
|
|
|
height: self.height.cast(),
|
|
|
|
}
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalSize<P> {
|
|
|
|
fn from((x, y): (X, X)) -> LogicalSize<P> {
|
|
|
|
LogicalSize::new(x.cast(), y.cast())
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P: Pixel, X: Pixel> Into<(X, X)> for LogicalSize<P> {
|
|
|
|
fn into(self: LogicalSize<P>) -> (X, X) {
|
|
|
|
(self.width.cast(), self.height.cast())
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-27 09:56:47 +11:00
|
|
|
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalSize<P> {
|
|
|
|
fn from([x, y]: [X; 2]) -> LogicalSize<P> {
|
|
|
|
LogicalSize::new(x.cast(), y.cast())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalSize<P> {
|
|
|
|
fn into(self: Self) -> [X; 2] {
|
|
|
|
[self.width.cast(), self.height.cast()]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-15 09:42:18 +10:00
|
|
|
/// A size represented in physical pixels.
|
2019-06-20 06:49:43 +10:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
2018-11-01 19:24:56 +11:00
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub struct PhysicalSize<P> {
|
|
|
|
pub width: P,
|
|
|
|
pub height: P,
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P> PhysicalSize<P> {
|
2018-06-15 09:42:18 +10:00
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub const fn new(width: P, height: P) -> Self {
|
2018-06-15 09:42:18 +10:00
|
|
|
PhysicalSize { width, height }
|
|
|
|
}
|
2020-01-04 17:33:07 +11:00
|
|
|
}
|
2018-06-15 09:42:18 +10:00
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P: Pixel> PhysicalSize<P> {
|
2018-06-15 09:42:18 +10:00
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn from_logical<T: Into<LogicalSize<X>>, X: Pixel>(logical: T, dpi_factor: f64) -> Self {
|
2018-06-15 09:42:18 +10:00
|
|
|
logical.into().to_physical(dpi_factor)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn to_logical<X: Pixel>(&self, dpi_factor: f64) -> LogicalSize<X> {
|
2020-01-04 06:52:27 +11:00
|
|
|
assert!(validate_scale_factor(dpi_factor));
|
2020-01-04 17:33:07 +11:00
|
|
|
let width = self.width.into() / dpi_factor;
|
|
|
|
let height = self.height.into() / dpi_factor;
|
|
|
|
LogicalSize::new(width, height).cast()
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn cast<X: Pixel>(&self) -> PhysicalSize<X> {
|
|
|
|
PhysicalSize {
|
|
|
|
width: self.width.cast(),
|
|
|
|
height: self.height.cast(),
|
|
|
|
}
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalSize<P> {
|
|
|
|
fn from((x, y): (X, X)) -> PhysicalSize<P> {
|
|
|
|
PhysicalSize::new(x.cast(), y.cast())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<P: Pixel, X: Pixel> Into<(X, X)> for PhysicalSize<P> {
|
|
|
|
fn into(self: Self) -> (X, X) {
|
|
|
|
(self.width.cast(), self.height.cast())
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-27 09:56:47 +11:00
|
|
|
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalSize<P> {
|
|
|
|
fn from([x, y]: [X; 2]) -> PhysicalSize<P> {
|
|
|
|
PhysicalSize::new(x.cast(), y.cast())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalSize<P> {
|
|
|
|
fn into(self: Self) -> [X; 2] {
|
|
|
|
[self.width.cast(), self.height.cast()]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-05 11:19:17 +11:00
|
|
|
/// A size that's either physical or logical.
|
2019-06-20 06:49:43 +10:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
|
|
pub enum Size {
|
2020-01-04 17:33:07 +11:00
|
|
|
Physical(PhysicalSize<u32>),
|
|
|
|
Logical(LogicalSize<f64>),
|
2019-06-20 06:49:43 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Size {
|
|
|
|
pub fn new<S: Into<Size>>(size: S) -> Size {
|
|
|
|
size.into()
|
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn to_logical<P: Pixel>(&self, dpi_factor: f64) -> LogicalSize<P> {
|
2019-06-20 06:49:43 +10:00
|
|
|
match *self {
|
|
|
|
Size::Physical(size) => size.to_logical(dpi_factor),
|
2020-01-04 17:33:07 +11:00
|
|
|
Size::Logical(size) => size.cast(),
|
2019-06-20 06:49:43 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn to_physical<P: Pixel>(&self, dpi_factor: f64) -> PhysicalSize<P> {
|
2019-06-20 06:49:43 +10:00
|
|
|
match *self {
|
2020-01-04 17:33:07 +11:00
|
|
|
Size::Physical(size) => size.cast(),
|
2019-06-20 06:49:43 +10:00
|
|
|
Size::Logical(size) => size.to_physical(dpi_factor),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P: Pixel> From<PhysicalSize<P>> for Size {
|
2018-06-15 09:42:18 +10:00
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
fn from(size: PhysicalSize<P>) -> Size {
|
|
|
|
Size::Physical(size.cast())
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P: Pixel> From<LogicalSize<P>> for Size {
|
2018-06-15 09:42:18 +10:00
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
fn from(size: LogicalSize<P>) -> Size {
|
|
|
|
Size::Logical(size.cast())
|
2019-06-20 06:49:43 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-05 11:19:17 +11:00
|
|
|
/// A position that's either physical or logical.
|
2019-06-20 06:49:43 +10:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
|
|
pub enum Position {
|
2020-01-04 05:54:06 +11:00
|
|
|
Physical(PhysicalPosition<i32>),
|
2020-01-04 17:33:07 +11:00
|
|
|
Logical(LogicalPosition<f64>),
|
2019-06-20 06:49:43 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Position {
|
|
|
|
pub fn new<S: Into<Position>>(position: S) -> Position {
|
|
|
|
position.into()
|
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn to_logical<P: Pixel>(&self, dpi_factor: f64) -> LogicalPosition<P> {
|
2019-06-20 06:49:43 +10:00
|
|
|
match *self {
|
|
|
|
Position::Physical(position) => position.to_logical(dpi_factor),
|
2020-01-04 17:33:07 +11:00
|
|
|
Position::Logical(position) => position.cast(),
|
2019-06-20 06:49:43 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
pub fn to_physical<P: Pixel>(&self, dpi_factor: f64) -> PhysicalPosition<P> {
|
2019-06-20 06:49:43 +10:00
|
|
|
match *self {
|
2020-01-04 17:33:07 +11:00
|
|
|
Position::Physical(position) => position.cast(),
|
2019-06-20 06:49:43 +10:00
|
|
|
Position::Logical(position) => position.to_physical(dpi_factor),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P: Pixel> From<PhysicalPosition<P>> for Position {
|
2019-06-20 06:49:43 +10:00
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
fn from(position: PhysicalPosition<P>) -> Position {
|
|
|
|
Position::Physical(position.cast())
|
2019-06-20 06:49:43 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 17:33:07 +11:00
|
|
|
impl<P: Pixel> From<LogicalPosition<P>> for Position {
|
2019-06-20 06:49:43 +10:00
|
|
|
#[inline]
|
2020-01-04 17:33:07 +11:00
|
|
|
fn from(position: LogicalPosition<P>) -> Position {
|
|
|
|
Position::Logical(position.cast())
|
2018-06-15 09:42:18 +10:00
|
|
|
}
|
|
|
|
}
|