floem_winit/
dpi.rs

1//! UI scaling is important, so read the docs for this module if you don't want to be confused.
2//!
3//! ## Why should I care about UI scaling?
4//!
5//! Modern computer screens don't have a consistent relationship between resolution and size.
6//! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens
7//! typically being less than a quarter the size of their desktop counterparts. Moreover, neither
8//! desktop nor mobile screens have consistent resolutions within their own size classes - common
9//! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K
10//! and beyond.
11//!
12//! Given that, it's a mistake to assume that 2D content will only be displayed on screens with
13//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen and
14//! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up
15//! about a quarter of the physical space as it did on the 1080p screen. That issue is especially
16//! problematic with text rendering, where quarter-sized text becomes a significant legibility
17//! problem.
18//!
19//! Failure to account for the scale factor can create a significantly degraded user experience.
20//! Most notably, it can make users feel like they have bad eyesight, which will potentially cause
21//! them to think about growing elderly, resulting in them having an existential crisis. Once users
22//! enter that state, they will no longer be focused on your application.
23//!
24//! ## How should I handle it?
25//!
26//! The solution to this problem is to account for the device's *scale factor*. The scale factor is
27//! the factor UI elements should be scaled by to be consistent with the rest of the user's system -
28//! for example, a button that's usually 50 pixels across would be 100 pixels across on a device
29//! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`.
30//!
31//! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's
32//! usually a mistake since there's no consistent mapping between the scale factor and the screen's
33//! actual DPI. Unless printing to a physical medium, you should work in scaled pixels rather
34//! than any DPI-dependent units.
35//!
36//! ### Position and Size types
37//!
38//! Winit's [`PhysicalPosition`] / [`PhysicalSize`] types correspond with the actual pixels on the
39//! device, and the [`LogicalPosition`] / [`LogicalSize`] types correspond to the physical pixels
40//! divided by the scale factor.
41//! All of Winit's functions return physical types, but can take either logical or physical
42//! coordinates as input, allowing you to use the most convenient coordinate system for your
43//! particular application.
44//!
45//! Winit's position and size types are generic over their exact pixel type, `P`, to allow the
46//! API to have integer precision where appropriate (e.g. most window manipulation functions) and
47//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch
48//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so
49//! will truncate the fractional part of the float rather than properly round to the nearest
50//! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the
51//! rounding properly. Note that precision loss will still occur when rounding from a float to an
52//! int, although rounding lessens the problem.
53//!
54//! ### Events
55//!
56//! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed.
57//! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI
58//! monitor or if the user changes their DPI settings. This allows you to rescale your application's
59//! UI elements and adjust how the platform changes the window's size to reflect the new scale
60//! factor. If a window hasn't received a [`ScaleFactorChanged`] event, its scale factor
61//! can be found by calling [`window.scale_factor()`].
62//!
63//! ## How is the scale factor calculated?
64//!
65//! The scale factor is calculated differently on different platforms:
66//!
67//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the
68//!   display settings. While users are free to select any option they want, they're only given a
69//!   selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7. The scale factor is
70//!   global and changing it requires logging out. See [this article][windows_1] for technical
71//!   details.
72//! - **macOS:** Recent macOS versions allow the user to change the scaling factor for specific
73//!   displays. When available, the user may pick a per-monitor scaling factor from a set of
74//!   pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default,
75//!   but the specific value varies across devices.
76//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
77//!   currently uses a three-pronged approach:
78//!   + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable if present.
79//!   + If not present, use the value set in `Xft.dpi` in Xresources.
80//!   + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR.
81//!
82//!   If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
83//!   XRandR scaling method. Generally speaking, you should try to configure the standard system
84//!   variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
85//! - **Wayland:** The scale factor is suggested by the compositor for each window individually. The
86//!   monitor scale factor may differ from the window scale factor.
87//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range
88//!   from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
89//!   information.
90//! - **Android:** Scale factors are set by the manufacturer to the value that best suits the
91//!   device, and range from `1.0` to `4.0`. See [this article][android_1] for more information.
92//! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels.
93//!   In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by
94//!   both the screen scaling and the browser zoom level and can go below `1.0`.
95//!
96//!
97//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
98//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
99//! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged
100//! [`window.scale_factor()`]: crate::window::Window::scale_factor
101//! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
102//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
103//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/
104//! [android_1]: https://developer.android.com/training/multiscreen/screendensities
105//! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
106
107pub trait Pixel: Copy + Into<f64> {
108    fn from_f64(f: f64) -> Self;
109    fn cast<P: Pixel>(self) -> P {
110        P::from_f64(self.into())
111    }
112}
113
114impl Pixel for u8 {
115    fn from_f64(f: f64) -> Self {
116        f.round() as u8
117    }
118}
119impl Pixel for u16 {
120    fn from_f64(f: f64) -> Self {
121        f.round() as u16
122    }
123}
124impl Pixel for u32 {
125    fn from_f64(f: f64) -> Self {
126        f.round() as u32
127    }
128}
129impl Pixel for i8 {
130    fn from_f64(f: f64) -> Self {
131        f.round() as i8
132    }
133}
134impl Pixel for i16 {
135    fn from_f64(f: f64) -> Self {
136        f.round() as i16
137    }
138}
139impl Pixel for i32 {
140    fn from_f64(f: f64) -> Self {
141        f.round() as i32
142    }
143}
144impl Pixel for f32 {
145    fn from_f64(f: f64) -> Self {
146        f as f32
147    }
148}
149impl Pixel for f64 {
150    fn from_f64(f: f64) -> Self {
151        f
152    }
153}
154
155/// Checks that the scale factor is a normal positive `f64`.
156///
157/// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from
158/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit;
159/// otherwise, you risk panics.
160#[inline]
161pub fn validate_scale_factor(scale_factor: f64) -> bool {
162    scale_factor.is_sign_positive() && scale_factor.is_normal()
163}
164
165/// A position represented in logical pixels.
166///
167/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
168/// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>`
169/// implementation is provided which does the rounding for you.
170#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
171#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
172pub struct LogicalPosition<P> {
173    pub x: P,
174    pub y: P,
175}
176
177impl<P> LogicalPosition<P> {
178    #[inline]
179    pub const fn new(x: P, y: P) -> Self {
180        LogicalPosition { x, y }
181    }
182}
183
184impl<P: Pixel> LogicalPosition<P> {
185    #[inline]
186    pub fn from_physical<T: Into<PhysicalPosition<X>>, X: Pixel>(
187        physical: T,
188        scale_factor: f64,
189    ) -> Self {
190        physical.into().to_logical(scale_factor)
191    }
192
193    #[inline]
194    pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalPosition<X> {
195        assert!(validate_scale_factor(scale_factor));
196        let x = self.x.into() * scale_factor;
197        let y = self.y.into() * scale_factor;
198        PhysicalPosition::new(x, y).cast()
199    }
200
201    #[inline]
202    pub fn cast<X: Pixel>(&self) -> LogicalPosition<X> {
203        LogicalPosition {
204            x: self.x.cast(),
205            y: self.y.cast(),
206        }
207    }
208}
209
210impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalPosition<P> {
211    fn from((x, y): (X, X)) -> LogicalPosition<P> {
212        LogicalPosition::new(x.cast(), y.cast())
213    }
214}
215
216impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for (X, X) {
217    fn from(p: LogicalPosition<P>) -> (X, X) {
218        (p.x.cast(), p.y.cast())
219    }
220}
221
222impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalPosition<P> {
223    fn from([x, y]: [X; 2]) -> LogicalPosition<P> {
224        LogicalPosition::new(x.cast(), y.cast())
225    }
226}
227
228impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for [X; 2] {
229    fn from(p: LogicalPosition<P>) -> [X; 2] {
230        [p.x.cast(), p.y.cast()]
231    }
232}
233
234#[cfg(feature = "mint")]
235impl<P: Pixel> From<mint::Point2<P>> for LogicalPosition<P> {
236    fn from(p: mint::Point2<P>) -> Self {
237        Self::new(p.x, p.y)
238    }
239}
240
241#[cfg(feature = "mint")]
242impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
243    fn from(p: LogicalPosition<P>) -> Self {
244        mint::Point2 { x: p.x, y: p.y }
245    }
246}
247
248/// A position represented in physical pixels.
249#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
250#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
251pub struct PhysicalPosition<P> {
252    pub x: P,
253    pub y: P,
254}
255
256impl<P> PhysicalPosition<P> {
257    #[inline]
258    pub const fn new(x: P, y: P) -> Self {
259        PhysicalPosition { x, y }
260    }
261}
262
263impl<P: Pixel> PhysicalPosition<P> {
264    #[inline]
265    pub fn from_logical<T: Into<LogicalPosition<X>>, X: Pixel>(
266        logical: T,
267        scale_factor: f64,
268    ) -> Self {
269        logical.into().to_physical(scale_factor)
270    }
271
272    #[inline]
273    pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalPosition<X> {
274        assert!(validate_scale_factor(scale_factor));
275        let x = self.x.into() / scale_factor;
276        let y = self.y.into() / scale_factor;
277        LogicalPosition::new(x, y).cast()
278    }
279
280    #[inline]
281    pub fn cast<X: Pixel>(&self) -> PhysicalPosition<X> {
282        PhysicalPosition {
283            x: self.x.cast(),
284            y: self.y.cast(),
285        }
286    }
287}
288
289impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalPosition<P> {
290    fn from((x, y): (X, X)) -> PhysicalPosition<P> {
291        PhysicalPosition::new(x.cast(), y.cast())
292    }
293}
294
295impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for (X, X) {
296    fn from(p: PhysicalPosition<P>) -> (X, X) {
297        (p.x.cast(), p.y.cast())
298    }
299}
300
301impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalPosition<P> {
302    fn from([x, y]: [X; 2]) -> PhysicalPosition<P> {
303        PhysicalPosition::new(x.cast(), y.cast())
304    }
305}
306
307impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for [X; 2] {
308    fn from(p: PhysicalPosition<P>) -> [X; 2] {
309        [p.x.cast(), p.y.cast()]
310    }
311}
312
313#[cfg(feature = "mint")]
314impl<P: Pixel> From<mint::Point2<P>> for PhysicalPosition<P> {
315    fn from(p: mint::Point2<P>) -> Self {
316        Self::new(p.x, p.y)
317    }
318}
319
320#[cfg(feature = "mint")]
321impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
322    fn from(p: PhysicalPosition<P>) -> Self {
323        mint::Point2 { x: p.x, y: p.y }
324    }
325}
326
327/// A size represented in logical pixels.
328#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
329#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
330pub struct LogicalSize<P> {
331    pub width: P,
332    pub height: P,
333}
334
335impl<P> LogicalSize<P> {
336    #[inline]
337    pub const fn new(width: P, height: P) -> Self {
338        LogicalSize { width, height }
339    }
340}
341
342impl<P: Pixel> LogicalSize<P> {
343    #[inline]
344    pub fn from_physical<T: Into<PhysicalSize<X>>, X: Pixel>(
345        physical: T,
346        scale_factor: f64,
347    ) -> Self {
348        physical.into().to_logical(scale_factor)
349    }
350
351    #[inline]
352    pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalSize<X> {
353        assert!(validate_scale_factor(scale_factor));
354        let width = self.width.into() * scale_factor;
355        let height = self.height.into() * scale_factor;
356        PhysicalSize::new(width, height).cast()
357    }
358
359    #[inline]
360    pub fn cast<X: Pixel>(&self) -> LogicalSize<X> {
361        LogicalSize {
362            width: self.width.cast(),
363            height: self.height.cast(),
364        }
365    }
366}
367
368impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalSize<P> {
369    fn from((x, y): (X, X)) -> LogicalSize<P> {
370        LogicalSize::new(x.cast(), y.cast())
371    }
372}
373
374impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for (X, X) {
375    fn from(s: LogicalSize<P>) -> (X, X) {
376        (s.width.cast(), s.height.cast())
377    }
378}
379
380impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalSize<P> {
381    fn from([x, y]: [X; 2]) -> LogicalSize<P> {
382        LogicalSize::new(x.cast(), y.cast())
383    }
384}
385
386impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for [X; 2] {
387    fn from(s: LogicalSize<P>) -> [X; 2] {
388        [s.width.cast(), s.height.cast()]
389    }
390}
391
392#[cfg(feature = "mint")]
393impl<P: Pixel> From<mint::Vector2<P>> for LogicalSize<P> {
394    fn from(v: mint::Vector2<P>) -> Self {
395        Self::new(v.x, v.y)
396    }
397}
398
399#[cfg(feature = "mint")]
400impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
401    fn from(s: LogicalSize<P>) -> Self {
402        mint::Vector2 {
403            x: s.width,
404            y: s.height,
405        }
406    }
407}
408
409/// A size represented in physical pixels.
410#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
411#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
412pub struct PhysicalSize<P> {
413    pub width: P,
414    pub height: P,
415}
416
417impl<P> PhysicalSize<P> {
418    #[inline]
419    pub const fn new(width: P, height: P) -> Self {
420        PhysicalSize { width, height }
421    }
422}
423
424impl<P: Pixel> PhysicalSize<P> {
425    #[inline]
426    pub fn from_logical<T: Into<LogicalSize<X>>, X: Pixel>(logical: T, scale_factor: f64) -> Self {
427        logical.into().to_physical(scale_factor)
428    }
429
430    #[inline]
431    pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalSize<X> {
432        assert!(validate_scale_factor(scale_factor));
433        let width = self.width.into() / scale_factor;
434        let height = self.height.into() / scale_factor;
435        LogicalSize::new(width, height).cast()
436    }
437
438    #[inline]
439    pub fn cast<X: Pixel>(&self) -> PhysicalSize<X> {
440        PhysicalSize {
441            width: self.width.cast(),
442            height: self.height.cast(),
443        }
444    }
445}
446
447impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalSize<P> {
448    fn from((x, y): (X, X)) -> PhysicalSize<P> {
449        PhysicalSize::new(x.cast(), y.cast())
450    }
451}
452
453impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for (X, X) {
454    fn from(s: PhysicalSize<P>) -> (X, X) {
455        (s.width.cast(), s.height.cast())
456    }
457}
458
459impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalSize<P> {
460    fn from([x, y]: [X; 2]) -> PhysicalSize<P> {
461        PhysicalSize::new(x.cast(), y.cast())
462    }
463}
464
465impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for [X; 2] {
466    fn from(s: PhysicalSize<P>) -> [X; 2] {
467        [s.width.cast(), s.height.cast()]
468    }
469}
470
471#[cfg(feature = "mint")]
472impl<P: Pixel> From<mint::Vector2<P>> for PhysicalSize<P> {
473    fn from(v: mint::Vector2<P>) -> Self {
474        Self::new(v.x, v.y)
475    }
476}
477
478#[cfg(feature = "mint")]
479impl<P: Pixel> From<PhysicalSize<P>> for mint::Vector2<P> {
480    fn from(s: PhysicalSize<P>) -> Self {
481        mint::Vector2 {
482            x: s.width,
483            y: s.height,
484        }
485    }
486}
487
488/// A size that's either physical or logical.
489#[derive(Debug, Copy, Clone, PartialEq)]
490#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
491pub enum Size {
492    Physical(PhysicalSize<u32>),
493    Logical(LogicalSize<f64>),
494}
495
496impl Size {
497    pub fn new<S: Into<Size>>(size: S) -> Size {
498        size.into()
499    }
500
501    pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalSize<P> {
502        match *self {
503            Size::Physical(size) => size.to_logical(scale_factor),
504            Size::Logical(size) => size.cast(),
505        }
506    }
507
508    pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalSize<P> {
509        match *self {
510            Size::Physical(size) => size.cast(),
511            Size::Logical(size) => size.to_physical(scale_factor),
512        }
513    }
514
515    pub fn clamp<S: Into<Size>>(input: S, min: S, max: S, scale_factor: f64) -> Size {
516        let (input, min, max) = (
517            input.into().to_physical::<f64>(scale_factor),
518            min.into().to_physical::<f64>(scale_factor),
519            max.into().to_physical::<f64>(scale_factor),
520        );
521
522        let width = input.width.clamp(min.width, max.width);
523        let height = input.height.clamp(min.height, max.height);
524
525        PhysicalSize::new(width, height).into()
526    }
527}
528
529impl<P: Pixel> From<PhysicalSize<P>> for Size {
530    #[inline]
531    fn from(size: PhysicalSize<P>) -> Size {
532        Size::Physical(size.cast())
533    }
534}
535
536impl<P: Pixel> From<LogicalSize<P>> for Size {
537    #[inline]
538    fn from(size: LogicalSize<P>) -> Size {
539        Size::Logical(size.cast())
540    }
541}
542
543/// A position that's either physical or logical.
544#[derive(Debug, Copy, Clone, PartialEq)]
545#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
546pub enum Position {
547    Physical(PhysicalPosition<i32>),
548    Logical(LogicalPosition<f64>),
549}
550
551impl Position {
552    pub fn new<S: Into<Position>>(position: S) -> Position {
553        position.into()
554    }
555
556    pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalPosition<P> {
557        match *self {
558            Position::Physical(position) => position.to_logical(scale_factor),
559            Position::Logical(position) => position.cast(),
560        }
561    }
562
563    pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalPosition<P> {
564        match *self {
565            Position::Physical(position) => position.cast(),
566            Position::Logical(position) => position.to_physical(scale_factor),
567        }
568    }
569}
570
571impl<P: Pixel> From<PhysicalPosition<P>> for Position {
572    #[inline]
573    fn from(position: PhysicalPosition<P>) -> Position {
574        Position::Physical(position.cast())
575    }
576}
577
578impl<P: Pixel> From<LogicalPosition<P>> for Position {
579    #[inline]
580    fn from(position: LogicalPosition<P>) -> Position {
581        Position::Logical(position.cast())
582    }
583}
584
585#[cfg(test)]
586mod tests {
587    use crate::dpi;
588    use std::collections::HashSet;
589
590    macro_rules! test_pixel_int_impl {
591        ($($name:ident => $ty:ty),*) => {$(
592            #[test]
593            fn $name() {
594                use dpi::Pixel;
595
596                assert_eq!(
597                    <$ty as Pixel>::from_f64(37.0),
598                    37,
599                );
600                assert_eq!(
601                    <$ty as Pixel>::from_f64(37.4),
602                    37,
603                );
604                assert_eq!(
605                    <$ty as Pixel>::from_f64(37.5),
606                    38,
607                );
608                assert_eq!(
609                    <$ty as Pixel>::from_f64(37.9),
610                    38,
611                );
612
613                assert_eq!(
614                    <$ty as Pixel>::cast::<u8>(37),
615                    37,
616                );
617                assert_eq!(
618                    <$ty as Pixel>::cast::<u16>(37),
619                    37,
620                );
621                assert_eq!(
622                    <$ty as Pixel>::cast::<u32>(37),
623                    37,
624                );
625                assert_eq!(
626                    <$ty as Pixel>::cast::<i8>(37),
627                    37,
628                );
629                assert_eq!(
630                    <$ty as Pixel>::cast::<i16>(37),
631                    37,
632                );
633                assert_eq!(
634                    <$ty as Pixel>::cast::<i32>(37),
635                    37,
636                );
637            }
638        )*};
639    }
640
641    test_pixel_int_impl! {
642        test_pixel_int_u8 => u8,
643        test_pixel_int_u16 => u16,
644        test_pixel_int_u32 => u32,
645        test_pixel_int_i8 => i8,
646        test_pixel_int_i16 => i16
647    }
648
649    macro_rules! assert_approx_eq {
650        ($a:expr, $b:expr $(,)?) => {
651            assert!(
652                ($a - $b).abs() < 0.001,
653                "{} is not approximately equal to {}",
654                $a,
655                $b
656            );
657        };
658    }
659
660    macro_rules! test_pixel_float_impl {
661    ($($name:ident => $ty:ty),*) => {$(
662        #[test]
663        fn $name() {
664            use dpi::Pixel;
665
666            assert_approx_eq!(
667                <$ty as Pixel>::from_f64(37.0),
668                37.0,
669            );
670            assert_approx_eq!(
671                <$ty as Pixel>::from_f64(37.4),
672                37.4,
673            );
674            assert_approx_eq!(
675                <$ty as Pixel>::from_f64(37.5),
676                37.5,
677            );
678            assert_approx_eq!(
679                <$ty as Pixel>::from_f64(37.9),
680                37.9,
681            );
682
683            assert_eq!(
684                <$ty as Pixel>::cast::<u8>(37.0),
685                37,
686            );
687            assert_eq!(
688                <$ty as Pixel>::cast::<u8>(37.4),
689                37,
690            );
691            assert_eq!(
692                <$ty as Pixel>::cast::<u8>(37.5),
693                38,
694            );
695
696            assert_eq!(
697                <$ty as Pixel>::cast::<u16>(37.0),
698                37,
699            );
700            assert_eq!(
701                <$ty as Pixel>::cast::<u16>(37.4),
702                37,
703            );
704            assert_eq!(
705                <$ty as Pixel>::cast::<u16>(37.5),
706                38,
707            );
708
709            assert_eq!(
710                <$ty as Pixel>::cast::<u32>(37.0),
711                37,
712            );
713            assert_eq!(
714                <$ty as Pixel>::cast::<u32>(37.4),
715                37,
716            );
717            assert_eq!(
718                <$ty as Pixel>::cast::<u32>(37.5),
719                38,
720            );
721
722            assert_eq!(
723                <$ty as Pixel>::cast::<i8>(37.0),
724                37,
725            );
726            assert_eq!(
727                <$ty as Pixel>::cast::<i8>(37.4),
728                37,
729            );
730            assert_eq!(
731                <$ty as Pixel>::cast::<i8>(37.5),
732                38,
733            );
734
735            assert_eq!(
736                <$ty as Pixel>::cast::<i16>(37.0),
737                37,
738            );
739            assert_eq!(
740                <$ty as Pixel>::cast::<i16>(37.4),
741                37,
742            );
743            assert_eq!(
744                <$ty as Pixel>::cast::<i16>(37.5),
745                38,
746            );
747        }
748    )*};
749}
750
751    test_pixel_float_impl! {
752        test_pixel_float_f32 => f32,
753        test_pixel_float_f64 => f64
754    }
755
756    #[test]
757    fn test_validate_scale_factor() {
758        assert!(dpi::validate_scale_factor(1.0));
759        assert!(dpi::validate_scale_factor(2.0));
760        assert!(dpi::validate_scale_factor(3.0));
761        assert!(dpi::validate_scale_factor(1.5));
762        assert!(dpi::validate_scale_factor(0.5));
763
764        assert!(!dpi::validate_scale_factor(0.0));
765        assert!(!dpi::validate_scale_factor(-1.0));
766        assert!(!dpi::validate_scale_factor(f64::INFINITY));
767        assert!(!dpi::validate_scale_factor(f64::NAN));
768        assert!(!dpi::validate_scale_factor(f64::NEG_INFINITY));
769    }
770
771    #[test]
772    fn test_logical_position() {
773        let log_pos = dpi::LogicalPosition::new(1.0, 2.0);
774        assert_eq!(
775            log_pos.to_physical::<u32>(1.0),
776            dpi::PhysicalPosition::new(1, 2)
777        );
778        assert_eq!(
779            log_pos.to_physical::<u32>(2.0),
780            dpi::PhysicalPosition::new(2, 4)
781        );
782        assert_eq!(log_pos.cast::<u32>(), dpi::LogicalPosition::new(1, 2));
783        assert_eq!(
784            log_pos,
785            dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(1.0, 2.0), 1.0)
786        );
787        assert_eq!(
788            log_pos,
789            dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(2.0, 4.0), 2.0)
790        );
791        assert_eq!(
792            dpi::LogicalPosition::from((2.0, 2.0)),
793            dpi::LogicalPosition::new(2.0, 2.0)
794        );
795        assert_eq!(
796            dpi::LogicalPosition::from([2.0, 3.0]),
797            dpi::LogicalPosition::new(2.0, 3.0)
798        );
799
800        let x: (f64, f64) = log_pos.into();
801        assert_eq!(x, (1.0, 2.0));
802        let x: [f64; 2] = log_pos.into();
803        assert_eq!(x, [1.0, 2.0]);
804    }
805
806    #[test]
807    fn test_physical_position() {
808        assert_eq!(
809            dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(1.0, 2.0), 1.0),
810            dpi::PhysicalPosition::new(1, 2)
811        );
812        assert_eq!(
813            dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(2.0, 4.0), 0.5),
814            dpi::PhysicalPosition::new(1, 2)
815        );
816        assert_eq!(
817            dpi::PhysicalPosition::from((2.0, 2.0)),
818            dpi::PhysicalPosition::new(2.0, 2.0)
819        );
820        assert_eq!(
821            dpi::PhysicalPosition::from([2.0, 3.0]),
822            dpi::PhysicalPosition::new(2.0, 3.0)
823        );
824
825        let x: (f64, f64) = dpi::PhysicalPosition::new(1, 2).into();
826        assert_eq!(x, (1.0, 2.0));
827        let x: [f64; 2] = dpi::PhysicalPosition::new(1, 2).into();
828        assert_eq!(x, [1.0, 2.0]);
829    }
830
831    #[test]
832    fn test_logical_size() {
833        let log_size = dpi::LogicalSize::new(1.0, 2.0);
834        assert_eq!(
835            log_size.to_physical::<u32>(1.0),
836            dpi::PhysicalSize::new(1, 2)
837        );
838        assert_eq!(
839            log_size.to_physical::<u32>(2.0),
840            dpi::PhysicalSize::new(2, 4)
841        );
842        assert_eq!(log_size.cast::<u32>(), dpi::LogicalSize::new(1, 2));
843        assert_eq!(
844            log_size,
845            dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(1.0, 2.0), 1.0)
846        );
847        assert_eq!(
848            log_size,
849            dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(2.0, 4.0), 2.0)
850        );
851        assert_eq!(
852            dpi::LogicalSize::from((2.0, 2.0)),
853            dpi::LogicalSize::new(2.0, 2.0)
854        );
855        assert_eq!(
856            dpi::LogicalSize::from([2.0, 3.0]),
857            dpi::LogicalSize::new(2.0, 3.0)
858        );
859
860        let x: (f64, f64) = log_size.into();
861        assert_eq!(x, (1.0, 2.0));
862        let x: [f64; 2] = log_size.into();
863        assert_eq!(x, [1.0, 2.0]);
864    }
865
866    #[test]
867    fn test_physical_size() {
868        assert_eq!(
869            dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(1.0, 2.0), 1.0),
870            dpi::PhysicalSize::new(1, 2)
871        );
872        assert_eq!(
873            dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(2.0, 4.0), 0.5),
874            dpi::PhysicalSize::new(1, 2)
875        );
876        assert_eq!(
877            dpi::PhysicalSize::from((2.0, 2.0)),
878            dpi::PhysicalSize::new(2.0, 2.0)
879        );
880        assert_eq!(
881            dpi::PhysicalSize::from([2.0, 3.0]),
882            dpi::PhysicalSize::new(2.0, 3.0)
883        );
884
885        let x: (f64, f64) = dpi::PhysicalSize::new(1, 2).into();
886        assert_eq!(x, (1.0, 2.0));
887        let x: [f64; 2] = dpi::PhysicalSize::new(1, 2).into();
888        assert_eq!(x, [1.0, 2.0]);
889    }
890
891    #[test]
892    fn test_size() {
893        assert_eq!(
894            dpi::Size::new(dpi::PhysicalSize::new(1, 2)),
895            dpi::Size::Physical(dpi::PhysicalSize::new(1, 2))
896        );
897        assert_eq!(
898            dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)),
899            dpi::Size::Logical(dpi::LogicalSize::new(1.0, 2.0))
900        );
901
902        assert_eq!(
903            dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(1.0),
904            dpi::LogicalSize::new(1.0, 2.0)
905        );
906        assert_eq!(
907            dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(2.0),
908            dpi::LogicalSize::new(0.5, 1.0)
909        );
910        assert_eq!(
911            dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_logical::<f64>(1.0),
912            dpi::LogicalSize::new(1.0, 2.0)
913        );
914
915        assert_eq!(
916            dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(1.0),
917            dpi::PhysicalSize::new(1, 2)
918        );
919        assert_eq!(
920            dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(2.0),
921            dpi::PhysicalSize::new(1, 2)
922        );
923        assert_eq!(
924            dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(1.0),
925            dpi::PhysicalSize::new(1, 2)
926        );
927        assert_eq!(
928            dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(2.0),
929            dpi::PhysicalSize::new(2, 4)
930        );
931
932        let small = dpi::Size::Physical((1, 2).into());
933        let medium = dpi::Size::Logical((3, 4).into());
934        let medium_physical = dpi::Size::new(medium.to_physical::<u32>(1.0));
935        let large = dpi::Size::Physical((5, 6).into());
936        assert_eq!(dpi::Size::clamp(medium, small, large, 1.0), medium_physical);
937        assert_eq!(dpi::Size::clamp(small, medium, large, 1.0), medium_physical);
938        assert_eq!(dpi::Size::clamp(large, small, medium, 1.0), medium_physical);
939    }
940
941    #[test]
942    fn test_position() {
943        assert_eq!(
944            dpi::Position::new(dpi::PhysicalPosition::new(1, 2)),
945            dpi::Position::Physical(dpi::PhysicalPosition::new(1, 2))
946        );
947        assert_eq!(
948            dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)),
949            dpi::Position::Logical(dpi::LogicalPosition::new(1.0, 2.0))
950        );
951
952        assert_eq!(
953            dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(1.0),
954            dpi::LogicalPosition::new(1.0, 2.0)
955        );
956        assert_eq!(
957            dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(2.0),
958            dpi::LogicalPosition::new(0.5, 1.0)
959        );
960        assert_eq!(
961            dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_logical::<f64>(1.0),
962            dpi::LogicalPosition::new(1.0, 2.0)
963        );
964
965        assert_eq!(
966            dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(1.0),
967            dpi::PhysicalPosition::new(1, 2)
968        );
969        assert_eq!(
970            dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(2.0),
971            dpi::PhysicalPosition::new(1, 2)
972        );
973        assert_eq!(
974            dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(1.0),
975            dpi::PhysicalPosition::new(1, 2)
976        );
977        assert_eq!(
978            dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(2.0),
979            dpi::PhysicalPosition::new(2, 4)
980        );
981    }
982
983    // Eat coverage for the Debug impls et al
984    #[test]
985    fn ensure_attrs_do_not_panic() {
986        let _ = format!("{:?}", dpi::LogicalPosition::<u32>::default().clone());
987        HashSet::new().insert(dpi::LogicalPosition::<u32>::default());
988
989        let _ = format!("{:?}", dpi::PhysicalPosition::<u32>::default().clone());
990        HashSet::new().insert(dpi::PhysicalPosition::<u32>::default());
991
992        let _ = format!("{:?}", dpi::LogicalSize::<u32>::default().clone());
993        HashSet::new().insert(dpi::LogicalSize::<u32>::default());
994
995        let _ = format!("{:?}", dpi::PhysicalSize::<u32>::default().clone());
996        HashSet::new().insert(dpi::PhysicalSize::<u32>::default());
997
998        let _ = format!("{:?}", dpi::Size::Physical((1, 2).into()).clone());
999        let _ = format!("{:?}", dpi::Position::Physical((1, 2).into()).clone());
1000    }
1001}