Skip to main content

wingy_dpi/
lib.rs

1//! # DPI
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//! The [`PhysicalPosition`] / [`PhysicalSize`] / [`PhysicalUnit`] types correspond with the actual
39//! pixels on the device, and the [`LogicalPosition`] / [`LogicalSize`] / [`LogicalUnit`] types
40//! correspond to the physical pixels divided by the scale factor.
41//!
42//! The position and size types are generic over their exact pixel type, `P`, to allow the
43//! API to have integer precision where appropriate (e.g. most window manipulation functions) and
44//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch
45//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so
46//! will truncate the fractional part of the float rather than properly round to the nearest
47//! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the
48//! rounding properly. Note that precision loss will still occur when rounding from a float to an
49//! int, although rounding lessens the problem.
50//!
51//! ## Cargo Features
52//!
53//! This crate provides the following Cargo features:
54//!
55//! * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
56//! * `mint`: Enables mint (math interoperability standard types) conversions.
57//! * `std` (enabled by default): Uses the standard library mathematical functions (normally through
58//!   your target platform's libm). This feature also changes the library's license from `Apache-2.0
59//!   AND MIT` to `APACHE-2.0` (only). For full details, see the package README.
60//!
61//! To use this library on a target without the standard library available, you should disable
62//! default features (thus disabling the `std` feature, with the license consequences thereof).
63//!
64//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
65//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
66
67#![cfg_attr(docsrs, feature(doc_cfg), doc(auto_cfg(hide(doc, docsrs))))]
68#![cfg_attr(feature = "std", forbid(unsafe_code))]
69#![no_std]
70
71#[cfg(not(feature = "std"))]
72mod libm;
73
74#[cfg(any(feature = "std", test))]
75extern crate std;
76
77#[cfg(feature = "serde")]
78use serde::{Deserialize, Serialize};
79
80pub trait Pixel: Copy + Into<f64> {
81    fn from_f64(f: f64) -> Self;
82    fn cast<P: Pixel>(self) -> P {
83        P::from_f64(self.into())
84    }
85}
86
87macro_rules! pixel_int_impl {
88    ($($t:ty),*) => {$(
89        impl Pixel for $t {
90            fn from_f64(f: f64) -> Self {
91                round(f) as $t
92            }
93        }
94    )*}
95}
96
97pixel_int_impl!(u8, u16, u32, i8, i16, i32);
98
99impl Pixel for f32 {
100    fn from_f64(f: f64) -> Self {
101        f as f32
102    }
103}
104impl Pixel for f64 {
105    fn from_f64(f: f64) -> Self {
106        f
107    }
108}
109
110/// Round f to the closest integer, rounding away from `0.0`
111#[inline]
112fn round(f: f64) -> f64 {
113    #[cfg(feature = "std")]
114    return f.round();
115    #[cfg(not(feature = "std"))]
116    return libm::round(f);
117}
118
119/// Checks that the scale factor is a normal positive `f64`.
120///
121/// All functions that take a scale factor assert that this will return `true`. If you're sourcing
122/// scale factors from anywhere other than wingy, it's recommended to validate them using this
123/// function before passing them to wingy; otherwise, you risk panics.
124#[inline]
125pub fn validate_scale_factor(scale_factor: f64) -> bool {
126    scale_factor.is_sign_positive() && scale_factor.is_normal()
127}
128
129/// A logical pixel unit.
130#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
131#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
132pub struct LogicalUnit<P>(pub P);
133
134impl<P> LogicalUnit<P> {
135    /// Represents a maximum logical unit that is equal to [`f64::MAX`].
136    pub const MAX: LogicalUnit<f64> = LogicalUnit::new(f64::MAX);
137    /// Represents a minimum logical unit of [`f64::MAX`].
138    pub const MIN: LogicalUnit<f64> = LogicalUnit::new(f64::MIN);
139    /// Represents a logical unit of `0_f64`.
140    pub const ZERO: LogicalUnit<f64> = LogicalUnit::new(0.0);
141
142    #[inline]
143    pub const fn new(v: P) -> Self {
144        LogicalUnit(v)
145    }
146}
147
148impl<P: Pixel> LogicalUnit<P> {
149    #[inline]
150    pub fn from_physical<T: Into<PhysicalUnit<X>>, X: Pixel>(
151        physical: T,
152        scale_factor: f64,
153    ) -> Self {
154        physical.into().to_logical(scale_factor)
155    }
156
157    #[inline]
158    pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalUnit<X> {
159        assert!(validate_scale_factor(scale_factor));
160        PhysicalUnit::new(self.0.into() * scale_factor).cast()
161    }
162
163    #[inline]
164    pub fn cast<X: Pixel>(&self) -> LogicalUnit<X> {
165        LogicalUnit(self.0.cast())
166    }
167}
168
169impl<P: Pixel, X: Pixel> From<X> for LogicalUnit<P> {
170    fn from(v: X) -> LogicalUnit<P> {
171        LogicalUnit::new(v.cast())
172    }
173}
174
175impl<P: Pixel> From<LogicalUnit<P>> for u8 {
176    fn from(v: LogicalUnit<P>) -> u8 {
177        v.0.cast()
178    }
179}
180
181impl<P: Pixel> From<LogicalUnit<P>> for u16 {
182    fn from(v: LogicalUnit<P>) -> u16 {
183        v.0.cast()
184    }
185}
186
187impl<P: Pixel> From<LogicalUnit<P>> for u32 {
188    fn from(v: LogicalUnit<P>) -> u32 {
189        v.0.cast()
190    }
191}
192
193impl<P: Pixel> From<LogicalUnit<P>> for i8 {
194    fn from(v: LogicalUnit<P>) -> i8 {
195        v.0.cast()
196    }
197}
198
199impl<P: Pixel> From<LogicalUnit<P>> for i16 {
200    fn from(v: LogicalUnit<P>) -> i16 {
201        v.0.cast()
202    }
203}
204
205impl<P: Pixel> From<LogicalUnit<P>> for i32 {
206    fn from(v: LogicalUnit<P>) -> i32 {
207        v.0.cast()
208    }
209}
210
211impl<P: Pixel> From<LogicalUnit<P>> for f32 {
212    fn from(v: LogicalUnit<P>) -> f32 {
213        v.0.cast()
214    }
215}
216
217impl<P: Pixel> From<LogicalUnit<P>> for f64 {
218    fn from(v: LogicalUnit<P>) -> f64 {
219        v.0.cast()
220    }
221}
222
223/// A physical pixel unit.
224#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
225#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
226pub struct PhysicalUnit<P>(pub P);
227
228impl<P> PhysicalUnit<P> {
229    /// Represents a maximum physical unit that is equal to [`f64::MAX`].
230    pub const MAX: LogicalUnit<f64> = LogicalUnit::new(f64::MAX);
231    /// Represents a minimum physical unit of [`f64::MAX`].
232    pub const MIN: LogicalUnit<f64> = LogicalUnit::new(f64::MIN);
233    /// Represents a physical unit of `0_f64`.
234    pub const ZERO: LogicalUnit<f64> = LogicalUnit::new(0.0);
235
236    #[inline]
237    pub const fn new(v: P) -> Self {
238        PhysicalUnit(v)
239    }
240}
241
242impl<P: Pixel> PhysicalUnit<P> {
243    #[inline]
244    pub fn from_logical<T: Into<LogicalUnit<X>>, X: Pixel>(logical: T, scale_factor: f64) -> Self {
245        logical.into().to_physical(scale_factor)
246    }
247
248    #[inline]
249    pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalUnit<X> {
250        assert!(validate_scale_factor(scale_factor));
251        LogicalUnit::new(self.0.into() / scale_factor).cast()
252    }
253
254    #[inline]
255    pub fn cast<X: Pixel>(&self) -> PhysicalUnit<X> {
256        PhysicalUnit(self.0.cast())
257    }
258}
259
260impl<P: Pixel, X: Pixel> From<X> for PhysicalUnit<P> {
261    fn from(v: X) -> PhysicalUnit<P> {
262        PhysicalUnit::new(v.cast())
263    }
264}
265
266impl<P: Pixel> From<PhysicalUnit<P>> for u8 {
267    fn from(v: PhysicalUnit<P>) -> u8 {
268        v.0.cast()
269    }
270}
271
272impl<P: Pixel> From<PhysicalUnit<P>> for u16 {
273    fn from(v: PhysicalUnit<P>) -> u16 {
274        v.0.cast()
275    }
276}
277
278impl<P: Pixel> From<PhysicalUnit<P>> for u32 {
279    fn from(v: PhysicalUnit<P>) -> u32 {
280        v.0.cast()
281    }
282}
283
284impl<P: Pixel> From<PhysicalUnit<P>> for i8 {
285    fn from(v: PhysicalUnit<P>) -> i8 {
286        v.0.cast()
287    }
288}
289
290impl<P: Pixel> From<PhysicalUnit<P>> for i16 {
291    fn from(v: PhysicalUnit<P>) -> i16 {
292        v.0.cast()
293    }
294}
295
296impl<P: Pixel> From<PhysicalUnit<P>> for i32 {
297    fn from(v: PhysicalUnit<P>) -> i32 {
298        v.0.cast()
299    }
300}
301
302impl<P: Pixel> From<PhysicalUnit<P>> for f32 {
303    fn from(v: PhysicalUnit<P>) -> f32 {
304        v.0.cast()
305    }
306}
307
308impl<P: Pixel> From<PhysicalUnit<P>> for f64 {
309    fn from(v: PhysicalUnit<P>) -> f64 {
310        v.0.cast()
311    }
312}
313
314/// A pixel unit that's either physical or logical.
315#[derive(Debug, Copy, Clone, PartialEq)]
316#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
317pub enum PixelUnit {
318    Physical(PhysicalUnit<i32>),
319    Logical(LogicalUnit<f64>),
320}
321
322impl PixelUnit {
323    /// Represents a maximum logical unit that is equal to [`f64::MAX`].
324    pub const MAX: PixelUnit = PixelUnit::Logical(LogicalUnit::new(f64::MAX));
325    /// Represents a minimum logical unit of [`f64::MAX`].
326    pub const MIN: PixelUnit = PixelUnit::Logical(LogicalUnit::new(f64::MIN));
327    /// Represents a logical unit of `0_f64`.
328    pub const ZERO: PixelUnit = PixelUnit::Logical(LogicalUnit::new(0.0));
329
330    pub fn new<S: Into<PixelUnit>>(unit: S) -> PixelUnit {
331        unit.into()
332    }
333
334    pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalUnit<P> {
335        match *self {
336            PixelUnit::Physical(unit) => unit.to_logical(scale_factor),
337            PixelUnit::Logical(unit) => unit.cast(),
338        }
339    }
340
341    pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalUnit<P> {
342        match *self {
343            PixelUnit::Physical(unit) => unit.cast(),
344            PixelUnit::Logical(unit) => unit.to_physical(scale_factor),
345        }
346    }
347}
348
349impl<P: Pixel> From<PhysicalUnit<P>> for PixelUnit {
350    #[inline]
351    fn from(unit: PhysicalUnit<P>) -> PixelUnit {
352        PixelUnit::Physical(unit.cast())
353    }
354}
355
356impl<P: Pixel> From<LogicalUnit<P>> for PixelUnit {
357    #[inline]
358    fn from(unit: LogicalUnit<P>) -> PixelUnit {
359        PixelUnit::Logical(unit.cast())
360    }
361}
362
363macro_rules! vec2_from_impls {
364    ($t:ident, $a:ident, $b:ident, $mint_ty:ident) => {
365        impl<P: Pixel, X: Pixel> From<(X, X)> for $t<P> {
366            fn from(($a, $b): (X, X)) -> Self {
367                Self::new($a.cast(), $b.cast())
368            }
369        }
370
371        impl<P: Pixel, X: Pixel> From<$t<P>> for (X, X) {
372            fn from(p: $t<P>) -> Self {
373                (p.$a.cast(), p.$b.cast())
374            }
375        }
376
377        impl<P: Pixel, X: Pixel> From<[X; 2]> for $t<P> {
378            fn from([$a, $b]: [X; 2]) -> Self {
379                Self::new($a.cast(), $b.cast())
380            }
381        }
382
383        impl<P: Pixel, X: Pixel> From<$t<P>> for [X; 2] {
384            fn from(p: $t<P>) -> Self {
385                [p.$a.cast(), p.$b.cast()]
386            }
387        }
388
389        #[cfg(feature = "mint")]
390        impl<P: Pixel> From<mint::$mint_ty<P>> for $t<P> {
391            fn from(p: mint::$mint_ty<P>) -> Self {
392                Self::new(p.x, p.y)
393            }
394        }
395
396        #[cfg(feature = "mint")]
397        impl<P: Pixel> From<$t<P>> for mint::$mint_ty<P> {
398            fn from(p: $t<P>) -> Self {
399                Self { x: p.$a, y: p.$b }
400            }
401        }
402    };
403}
404
405/// A position represented in logical pixels.
406///
407/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
408/// fractional part, which can cause noticeable issues. To help with that, an `Into<(i32, i32)>`
409/// implementation is provided which does the rounding for you.
410#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
411#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
412pub struct LogicalPosition<P> {
413    pub x: P,
414    pub y: P,
415}
416
417impl<P> LogicalPosition<P> {
418    #[inline]
419    pub const fn new(x: P, y: P) -> Self {
420        LogicalPosition { x, y }
421    }
422}
423
424impl<P: Pixel> LogicalPosition<P> {
425    #[inline]
426    pub fn from_physical<T: Into<PhysicalPosition<X>>, X: Pixel>(
427        physical: T,
428        scale_factor: f64,
429    ) -> Self {
430        physical.into().to_logical(scale_factor)
431    }
432
433    #[inline]
434    pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalPosition<X> {
435        assert!(validate_scale_factor(scale_factor));
436        let x = self.x.into() * scale_factor;
437        let y = self.y.into() * scale_factor;
438        PhysicalPosition::new(x, y).cast()
439    }
440
441    #[inline]
442    pub fn cast<X: Pixel>(&self) -> LogicalPosition<X> {
443        LogicalPosition { x: self.x.cast(), y: self.y.cast() }
444    }
445}
446
447vec2_from_impls!(LogicalPosition, x, y, Point2);
448
449/// A position represented in physical pixels.
450#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
451#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
452pub struct PhysicalPosition<P> {
453    pub x: P,
454    pub y: P,
455}
456
457impl<P> PhysicalPosition<P> {
458    #[inline]
459    pub const fn new(x: P, y: P) -> Self {
460        PhysicalPosition { x, y }
461    }
462}
463
464impl<P: Pixel> PhysicalPosition<P> {
465    #[inline]
466    pub fn from_logical<T: Into<LogicalPosition<X>>, X: Pixel>(
467        logical: T,
468        scale_factor: f64,
469    ) -> Self {
470        logical.into().to_physical(scale_factor)
471    }
472
473    #[inline]
474    pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalPosition<X> {
475        assert!(validate_scale_factor(scale_factor));
476        let x = self.x.into() / scale_factor;
477        let y = self.y.into() / scale_factor;
478        LogicalPosition::new(x, y).cast()
479    }
480
481    #[inline]
482    pub fn cast<X: Pixel>(&self) -> PhysicalPosition<X> {
483        PhysicalPosition { x: self.x.cast(), y: self.y.cast() }
484    }
485}
486
487vec2_from_impls!(PhysicalPosition, x, y, Point2);
488
489/// A size represented in logical pixels.
490#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
491#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
492pub struct LogicalSize<P> {
493    pub width: P,
494    pub height: P,
495}
496
497impl<P> LogicalSize<P> {
498    #[inline]
499    pub const fn new(width: P, height: P) -> Self {
500        LogicalSize { width, height }
501    }
502}
503
504impl<P: Pixel> LogicalSize<P> {
505    #[inline]
506    pub fn from_physical<T: Into<PhysicalSize<X>>, X: Pixel>(
507        physical: T,
508        scale_factor: f64,
509    ) -> Self {
510        physical.into().to_logical(scale_factor)
511    }
512
513    #[inline]
514    pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalSize<X> {
515        assert!(validate_scale_factor(scale_factor));
516        let width = self.width.into() * scale_factor;
517        let height = self.height.into() * scale_factor;
518        PhysicalSize::new(width, height).cast()
519    }
520
521    #[inline]
522    pub fn cast<X: Pixel>(&self) -> LogicalSize<X> {
523        LogicalSize { width: self.width.cast(), height: self.height.cast() }
524    }
525}
526
527vec2_from_impls!(LogicalSize, width, height, Vector2);
528
529/// A size represented in physical pixels.
530#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
531#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
532pub struct PhysicalSize<P> {
533    pub width: P,
534    pub height: P,
535}
536
537impl<P> PhysicalSize<P> {
538    #[inline]
539    pub const fn new(width: P, height: P) -> Self {
540        PhysicalSize { width, height }
541    }
542}
543
544impl<P: Pixel> PhysicalSize<P> {
545    #[inline]
546    pub fn from_logical<T: Into<LogicalSize<X>>, X: Pixel>(logical: T, scale_factor: f64) -> Self {
547        logical.into().to_physical(scale_factor)
548    }
549
550    #[inline]
551    pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalSize<X> {
552        assert!(validate_scale_factor(scale_factor));
553        let width = self.width.into() / scale_factor;
554        let height = self.height.into() / scale_factor;
555        LogicalSize::new(width, height).cast()
556    }
557
558    #[inline]
559    pub fn cast<X: Pixel>(&self) -> PhysicalSize<X> {
560        PhysicalSize { width: self.width.cast(), height: self.height.cast() }
561    }
562}
563
564vec2_from_impls!(PhysicalSize, width, height, Vector2);
565
566/// A size that's either physical or logical.
567#[derive(Debug, Copy, Clone, PartialEq)]
568#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
569pub enum Size {
570    Physical(PhysicalSize<u32>),
571    Logical(LogicalSize<f64>),
572}
573
574impl Size {
575    pub fn new<S: Into<Size>>(size: S) -> Size {
576        size.into()
577    }
578
579    pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalSize<P> {
580        match *self {
581            Size::Physical(size) => size.to_logical(scale_factor),
582            Size::Logical(size) => size.cast(),
583        }
584    }
585
586    pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalSize<P> {
587        match *self {
588            Size::Physical(size) => size.cast(),
589            Size::Logical(size) => size.to_physical(scale_factor),
590        }
591    }
592
593    pub fn clamp<S: Into<Size>>(input: S, min: S, max: S, scale_factor: f64) -> Size {
594        let (input, min, max) = (
595            input.into().to_physical::<f64>(scale_factor),
596            min.into().to_physical::<f64>(scale_factor),
597            max.into().to_physical::<f64>(scale_factor),
598        );
599
600        let width = input.width.clamp(min.width, max.width);
601        let height = input.height.clamp(min.height, max.height);
602
603        PhysicalSize::new(width, height).into()
604    }
605}
606
607impl<P: Pixel> From<PhysicalSize<P>> for Size {
608    #[inline]
609    fn from(size: PhysicalSize<P>) -> Size {
610        Size::Physical(size.cast())
611    }
612}
613
614impl<P: Pixel> From<LogicalSize<P>> for Size {
615    #[inline]
616    fn from(size: LogicalSize<P>) -> Size {
617        Size::Logical(size.cast())
618    }
619}
620
621/// A position that's either physical or logical.
622#[derive(Debug, Copy, Clone, PartialEq)]
623#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
624pub enum Position {
625    Physical(PhysicalPosition<i32>),
626    Logical(LogicalPosition<f64>),
627}
628
629impl Position {
630    pub fn new<S: Into<Position>>(position: S) -> Position {
631        position.into()
632    }
633
634    pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalPosition<P> {
635        match *self {
636            Position::Physical(position) => position.to_logical(scale_factor),
637            Position::Logical(position) => position.cast(),
638        }
639    }
640
641    pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalPosition<P> {
642        match *self {
643            Position::Physical(position) => position.cast(),
644            Position::Logical(position) => position.to_physical(scale_factor),
645        }
646    }
647}
648
649impl<P: Pixel> From<PhysicalPosition<P>> for Position {
650    #[inline]
651    fn from(position: PhysicalPosition<P>) -> Position {
652        Position::Physical(position.cast())
653    }
654}
655
656impl<P: Pixel> From<LogicalPosition<P>> for Position {
657    #[inline]
658    fn from(position: LogicalPosition<P>) -> Position {
659        Position::Logical(position.cast())
660    }
661}
662
663/// The logical distance between the edges of two rectangles.
664#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
665#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
666pub struct LogicalInsets<P> {
667    /// The distance to the top edge.
668    pub top: P,
669    /// The distance to the left edge.
670    pub left: P,
671    /// The distance to the bottom edge.
672    pub bottom: P,
673    /// The distance to the right edge.
674    pub right: P,
675}
676
677impl<P> LogicalInsets<P> {
678    #[inline]
679    pub const fn new(top: P, left: P, bottom: P, right: P) -> Self {
680        Self { top, left, bottom, right }
681    }
682}
683
684impl<P: Pixel> LogicalInsets<P> {
685    #[inline]
686    pub fn from_physical<T: Into<PhysicalInsets<X>>, X: Pixel>(
687        physical: T,
688        scale_factor: f64,
689    ) -> Self {
690        physical.into().to_logical(scale_factor)
691    }
692
693    #[inline]
694    pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalInsets<X> {
695        assert!(validate_scale_factor(scale_factor));
696        let top = self.top.into() * scale_factor;
697        let left = self.left.into() * scale_factor;
698        let bottom = self.bottom.into() * scale_factor;
699        let right = self.right.into() * scale_factor;
700        PhysicalInsets::new(top, left, bottom, right).cast()
701    }
702
703    #[inline]
704    pub fn cast<X: Pixel>(&self) -> LogicalInsets<X> {
705        LogicalInsets {
706            top: self.top.cast(),
707            left: self.left.cast(),
708            bottom: self.bottom.cast(),
709            right: self.right.cast(),
710        }
711    }
712}
713
714/// The physical distance between the edges of two rectangles.
715#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
716#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
717pub struct PhysicalInsets<P> {
718    /// The distance to the top edge.
719    pub top: P,
720    /// The distance to the left edge.
721    pub left: P,
722    /// The distance to the bottom edge.
723    pub bottom: P,
724    /// The distance to the right edge.
725    pub right: P,
726}
727
728impl<P> PhysicalInsets<P> {
729    #[inline]
730    pub const fn new(top: P, left: P, bottom: P, right: P) -> Self {
731        Self { top, left, bottom, right }
732    }
733}
734
735impl<P: Pixel> PhysicalInsets<P> {
736    #[inline]
737    pub fn from_logical<T: Into<LogicalInsets<X>>, X: Pixel>(
738        logical: T,
739        scale_factor: f64,
740    ) -> Self {
741        logical.into().to_physical(scale_factor)
742    }
743
744    #[inline]
745    pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalInsets<X> {
746        assert!(validate_scale_factor(scale_factor));
747        let top = self.top.into() / scale_factor;
748        let left = self.left.into() / scale_factor;
749        let bottom = self.bottom.into() / scale_factor;
750        let right = self.right.into() / scale_factor;
751        LogicalInsets::new(top, left, bottom, right).cast()
752    }
753
754    #[inline]
755    pub fn cast<X: Pixel>(&self) -> PhysicalInsets<X> {
756        PhysicalInsets {
757            top: self.top.cast(),
758            left: self.left.cast(),
759            bottom: self.bottom.cast(),
760            right: self.right.cast(),
761        }
762    }
763}
764
765/// Insets that are either physical or logical.
766#[derive(Debug, Copy, Clone, PartialEq)]
767#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
768pub enum Insets {
769    Physical(PhysicalInsets<u32>),
770    Logical(LogicalInsets<f64>),
771}
772
773impl Insets {
774    pub fn new<S: Into<Self>>(insets: S) -> Self {
775        insets.into()
776    }
777
778    pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalInsets<P> {
779        match *self {
780            Self::Physical(insets) => insets.to_logical(scale_factor),
781            Self::Logical(insets) => insets.cast(),
782        }
783    }
784
785    pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalInsets<P> {
786        match *self {
787            Self::Physical(insets) => insets.cast(),
788            Self::Logical(insets) => insets.to_physical(scale_factor),
789        }
790    }
791}
792
793impl<P: Pixel> From<PhysicalInsets<P>> for Insets {
794    #[inline]
795    fn from(insets: PhysicalInsets<P>) -> Self {
796        Self::Physical(insets.cast())
797    }
798}
799
800impl<P: Pixel> From<LogicalInsets<P>> for Insets {
801    #[inline]
802    fn from(insets: LogicalInsets<P>) -> Self {
803        Self::Logical(insets.cast())
804    }
805}
806
807#[cfg(test)]
808mod tests {
809    use std::collections::HashSet;
810
811    use super::*;
812
813    macro_rules! test_pixel_int_impl {
814        ($($name:ident => $ty:ty),*) => {$(
815            #[test]
816            fn $name() {
817                assert_eq!(
818                    <$ty as Pixel>::from_f64(37.0),
819                    37,
820                );
821                assert_eq!(
822                    <$ty as Pixel>::from_f64(37.4),
823                    37,
824                );
825                assert_eq!(
826                    <$ty as Pixel>::from_f64(37.5),
827                    38,
828                );
829                assert_eq!(
830                    <$ty as Pixel>::from_f64(37.9),
831                    38,
832                );
833
834                assert_eq!(
835                    <$ty as Pixel>::cast::<u8>(37),
836                    37,
837                );
838                assert_eq!(
839                    <$ty as Pixel>::cast::<u16>(37),
840                    37,
841                );
842                assert_eq!(
843                    <$ty as Pixel>::cast::<u32>(37),
844                    37,
845                );
846                assert_eq!(
847                    <$ty as Pixel>::cast::<i8>(37),
848                    37,
849                );
850                assert_eq!(
851                    <$ty as Pixel>::cast::<i16>(37),
852                    37,
853                );
854                assert_eq!(
855                    <$ty as Pixel>::cast::<i32>(37),
856                    37,
857                );
858            }
859        )*};
860    }
861
862    test_pixel_int_impl! {
863        test_pixel_int_u8 => u8,
864        test_pixel_int_u16 => u16,
865        test_pixel_int_u32 => u32,
866        test_pixel_int_i8 => i8,
867        test_pixel_int_i16 => i16
868    }
869
870    macro_rules! assert_approx_eq {
871        ($a:expr, $b:expr $(,)?) => {
872            assert!(($a - $b).abs() < 0.001, "{} is not approximately equal to {}", $a, $b);
873        };
874    }
875
876    macro_rules! test_pixel_float_impl {
877    ($($name:ident => $ty:ty),*) => {$(
878        #[test]
879        fn $name() {
880            assert_approx_eq!(
881                <$ty as Pixel>::from_f64(37.0),
882                37.0,
883            );
884            assert_approx_eq!(
885                <$ty as Pixel>::from_f64(37.4),
886                37.4,
887            );
888            assert_approx_eq!(
889                <$ty as Pixel>::from_f64(37.5),
890                37.5,
891            );
892            assert_approx_eq!(
893                <$ty as Pixel>::from_f64(37.9),
894                37.9,
895            );
896
897            assert_eq!(
898                <$ty as Pixel>::cast::<u8>(37.0),
899                37,
900            );
901            assert_eq!(
902                <$ty as Pixel>::cast::<u8>(37.4),
903                37,
904            );
905            assert_eq!(
906                <$ty as Pixel>::cast::<u8>(37.5),
907                38,
908            );
909
910            assert_eq!(
911                <$ty as Pixel>::cast::<u16>(37.0),
912                37,
913            );
914            assert_eq!(
915                <$ty as Pixel>::cast::<u16>(37.4),
916                37,
917            );
918            assert_eq!(
919                <$ty as Pixel>::cast::<u16>(37.5),
920                38,
921            );
922
923            assert_eq!(
924                <$ty as Pixel>::cast::<u32>(37.0),
925                37,
926            );
927            assert_eq!(
928                <$ty as Pixel>::cast::<u32>(37.4),
929                37,
930            );
931            assert_eq!(
932                <$ty as Pixel>::cast::<u32>(37.5),
933                38,
934            );
935
936            assert_eq!(
937                <$ty as Pixel>::cast::<i8>(37.0),
938                37,
939            );
940            assert_eq!(
941                <$ty as Pixel>::cast::<i8>(37.4),
942                37,
943            );
944            assert_eq!(
945                <$ty as Pixel>::cast::<i8>(37.5),
946                38,
947            );
948
949            assert_eq!(
950                <$ty as Pixel>::cast::<i16>(37.0),
951                37,
952            );
953            assert_eq!(
954                <$ty as Pixel>::cast::<i16>(37.4),
955                37,
956            );
957            assert_eq!(
958                <$ty as Pixel>::cast::<i16>(37.5),
959                38,
960            );
961        }
962    )*};
963}
964
965    test_pixel_float_impl! {
966        test_pixel_float_f32 => f32,
967        test_pixel_float_f64 => f64
968    }
969
970    #[test]
971    fn test_validate_scale_factor() {
972        assert!(validate_scale_factor(1.0));
973        assert!(validate_scale_factor(2.0));
974        assert!(validate_scale_factor(3.0));
975        assert!(validate_scale_factor(1.5));
976        assert!(validate_scale_factor(0.5));
977
978        assert!(!validate_scale_factor(0.0));
979        assert!(!validate_scale_factor(-1.0));
980        assert!(!validate_scale_factor(f64::INFINITY));
981        assert!(!validate_scale_factor(f64::NAN));
982        assert!(!validate_scale_factor(f64::NEG_INFINITY));
983    }
984
985    #[test]
986    fn test_logical_unity() {
987        let log_unit = LogicalUnit::new(1.0);
988        assert_eq!(log_unit.to_physical::<u32>(1.0), PhysicalUnit::new(1));
989        assert_eq!(log_unit.to_physical::<u32>(2.0), PhysicalUnit::new(2));
990        assert_eq!(log_unit.cast::<u32>(), LogicalUnit::new(1));
991        assert_eq!(log_unit, LogicalUnit::from_physical(PhysicalUnit::new(1.0), 1.0));
992        assert_eq!(log_unit, LogicalUnit::from_physical(PhysicalUnit::new(2.0), 2.0));
993        assert_eq!(LogicalUnit::from(2.0), LogicalUnit::new(2.0));
994
995        let x: f64 = log_unit.into();
996        assert_eq!(x, 1.0);
997    }
998
999    #[test]
1000    fn test_physical_unit() {
1001        assert_eq!(PhysicalUnit::from_logical(LogicalUnit::new(1.0), 1.0), PhysicalUnit::new(1));
1002        assert_eq!(PhysicalUnit::from_logical(LogicalUnit::new(2.0), 0.5), PhysicalUnit::new(1));
1003        assert_eq!(PhysicalUnit::from(2.0), PhysicalUnit::new(2.0,));
1004        assert_eq!(PhysicalUnit::from(2.0), PhysicalUnit::new(2.0));
1005
1006        let x: f64 = PhysicalUnit::new(1).into();
1007        assert_eq!(x, 1.0);
1008    }
1009
1010    #[test]
1011    fn test_logical_position() {
1012        let log_pos = LogicalPosition::new(1.0, 2.0);
1013        assert_eq!(log_pos.to_physical::<u32>(1.0), PhysicalPosition::new(1, 2));
1014        assert_eq!(log_pos.to_physical::<u32>(2.0), PhysicalPosition::new(2, 4));
1015        assert_eq!(log_pos.cast::<u32>(), LogicalPosition::new(1, 2));
1016        assert_eq!(log_pos, LogicalPosition::from_physical(PhysicalPosition::new(1.0, 2.0), 1.0));
1017        assert_eq!(log_pos, LogicalPosition::from_physical(PhysicalPosition::new(2.0, 4.0), 2.0));
1018        assert_eq!(LogicalPosition::from((2.0, 2.0)), LogicalPosition::new(2.0, 2.0));
1019        assert_eq!(LogicalPosition::from([2.0, 3.0]), LogicalPosition::new(2.0, 3.0));
1020
1021        let x: (f64, f64) = log_pos.into();
1022        assert_eq!(x, (1.0, 2.0));
1023        let x: [f64; 2] = log_pos.into();
1024        assert_eq!(x, [1.0, 2.0]);
1025    }
1026
1027    #[test]
1028    fn test_physical_position() {
1029        assert_eq!(
1030            PhysicalPosition::from_logical(LogicalPosition::new(1.0, 2.0), 1.0),
1031            PhysicalPosition::new(1, 2)
1032        );
1033        assert_eq!(
1034            PhysicalPosition::from_logical(LogicalPosition::new(2.0, 4.0), 0.5),
1035            PhysicalPosition::new(1, 2)
1036        );
1037        assert_eq!(PhysicalPosition::from((2.0, 2.0)), PhysicalPosition::new(2.0, 2.0));
1038        assert_eq!(PhysicalPosition::from([2.0, 3.0]), PhysicalPosition::new(2.0, 3.0));
1039
1040        let x: (f64, f64) = PhysicalPosition::new(1, 2).into();
1041        assert_eq!(x, (1.0, 2.0));
1042        let x: [f64; 2] = PhysicalPosition::new(1, 2).into();
1043        assert_eq!(x, [1.0, 2.0]);
1044    }
1045
1046    #[test]
1047    fn test_logical_size() {
1048        let log_size = LogicalSize::new(1.0, 2.0);
1049        assert_eq!(log_size.to_physical::<u32>(1.0), PhysicalSize::new(1, 2));
1050        assert_eq!(log_size.to_physical::<u32>(2.0), PhysicalSize::new(2, 4));
1051        assert_eq!(log_size.cast::<u32>(), LogicalSize::new(1, 2));
1052        assert_eq!(log_size, LogicalSize::from_physical(PhysicalSize::new(1.0, 2.0), 1.0));
1053        assert_eq!(log_size, LogicalSize::from_physical(PhysicalSize::new(2.0, 4.0), 2.0));
1054        assert_eq!(LogicalSize::from((2.0, 2.0)), LogicalSize::new(2.0, 2.0));
1055        assert_eq!(LogicalSize::from([2.0, 3.0]), LogicalSize::new(2.0, 3.0));
1056
1057        let x: (f64, f64) = log_size.into();
1058        assert_eq!(x, (1.0, 2.0));
1059        let x: [f64; 2] = log_size.into();
1060        assert_eq!(x, [1.0, 2.0]);
1061    }
1062
1063    #[test]
1064    fn test_physical_size() {
1065        assert_eq!(
1066            PhysicalSize::from_logical(LogicalSize::new(1.0, 2.0), 1.0),
1067            PhysicalSize::new(1, 2)
1068        );
1069        assert_eq!(
1070            PhysicalSize::from_logical(LogicalSize::new(2.0, 4.0), 0.5),
1071            PhysicalSize::new(1, 2)
1072        );
1073        assert_eq!(PhysicalSize::from((2.0, 2.0)), PhysicalSize::new(2.0, 2.0));
1074        assert_eq!(PhysicalSize::from([2.0, 3.0]), PhysicalSize::new(2.0, 3.0));
1075
1076        let x: (f64, f64) = PhysicalSize::new(1, 2).into();
1077        assert_eq!(x, (1.0, 2.0));
1078        let x: [f64; 2] = PhysicalSize::new(1, 2).into();
1079        assert_eq!(x, [1.0, 2.0]);
1080    }
1081
1082    #[test]
1083    fn test_size() {
1084        assert_eq!(Size::new(PhysicalSize::new(1, 2)), Size::Physical(PhysicalSize::new(1, 2)));
1085        assert_eq!(
1086            Size::new(LogicalSize::new(1.0, 2.0)),
1087            Size::Logical(LogicalSize::new(1.0, 2.0))
1088        );
1089
1090        assert_eq!(
1091            Size::new(PhysicalSize::new(1, 2)).to_logical::<f64>(1.0),
1092            LogicalSize::new(1.0, 2.0)
1093        );
1094        assert_eq!(
1095            Size::new(PhysicalSize::new(1, 2)).to_logical::<f64>(2.0),
1096            LogicalSize::new(0.5, 1.0)
1097        );
1098        assert_eq!(
1099            Size::new(LogicalSize::new(1.0, 2.0)).to_logical::<f64>(1.0),
1100            LogicalSize::new(1.0, 2.0)
1101        );
1102
1103        assert_eq!(
1104            Size::new(PhysicalSize::new(1, 2)).to_physical::<u32>(1.0),
1105            PhysicalSize::new(1, 2)
1106        );
1107        assert_eq!(
1108            Size::new(PhysicalSize::new(1, 2)).to_physical::<u32>(2.0),
1109            PhysicalSize::new(1, 2)
1110        );
1111        assert_eq!(
1112            Size::new(LogicalSize::new(1.0, 2.0)).to_physical::<u32>(1.0),
1113            PhysicalSize::new(1, 2)
1114        );
1115        assert_eq!(
1116            Size::new(LogicalSize::new(1.0, 2.0)).to_physical::<u32>(2.0),
1117            PhysicalSize::new(2, 4)
1118        );
1119
1120        let small = Size::Physical((1, 2).into());
1121        let medium = Size::Logical((3, 4).into());
1122        let medium_physical = Size::new(medium.to_physical::<u32>(1.0));
1123        let large = Size::Physical((5, 6).into());
1124        assert_eq!(Size::clamp(medium, small, large, 1.0), medium_physical);
1125        assert_eq!(Size::clamp(small, medium, large, 1.0), medium_physical);
1126        assert_eq!(Size::clamp(large, small, medium, 1.0), medium_physical);
1127    }
1128
1129    #[test]
1130    fn test_position() {
1131        assert_eq!(
1132            Position::new(PhysicalPosition::new(1, 2)),
1133            Position::Physical(PhysicalPosition::new(1, 2))
1134        );
1135        assert_eq!(
1136            Position::new(LogicalPosition::new(1.0, 2.0)),
1137            Position::Logical(LogicalPosition::new(1.0, 2.0))
1138        );
1139
1140        assert_eq!(
1141            Position::new(PhysicalPosition::new(1, 2)).to_logical::<f64>(1.0),
1142            LogicalPosition::new(1.0, 2.0)
1143        );
1144        assert_eq!(
1145            Position::new(PhysicalPosition::new(1, 2)).to_logical::<f64>(2.0),
1146            LogicalPosition::new(0.5, 1.0)
1147        );
1148        assert_eq!(
1149            Position::new(LogicalPosition::new(1.0, 2.0)).to_logical::<f64>(1.0),
1150            LogicalPosition::new(1.0, 2.0)
1151        );
1152
1153        assert_eq!(
1154            Position::new(PhysicalPosition::new(1, 2)).to_physical::<u32>(1.0),
1155            PhysicalPosition::new(1, 2)
1156        );
1157        assert_eq!(
1158            Position::new(PhysicalPosition::new(1, 2)).to_physical::<u32>(2.0),
1159            PhysicalPosition::new(1, 2)
1160        );
1161        assert_eq!(
1162            Position::new(LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(1.0),
1163            PhysicalPosition::new(1, 2)
1164        );
1165        assert_eq!(
1166            Position::new(LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(2.0),
1167            PhysicalPosition::new(2, 4)
1168        );
1169    }
1170
1171    // Eat coverage for the Debug impls et al
1172    #[test]
1173    fn ensure_attrs_do_not_panic() {
1174        let _ = std::format!("{:?}", LogicalPosition::<u32>::default().clone());
1175        HashSet::new().insert(LogicalPosition::<u32>::default());
1176
1177        let _ = std::format!("{:?}", PhysicalPosition::<u32>::default().clone());
1178        HashSet::new().insert(PhysicalPosition::<u32>::default());
1179
1180        let _ = std::format!("{:?}", LogicalSize::<u32>::default().clone());
1181        HashSet::new().insert(LogicalSize::<u32>::default());
1182
1183        let _ = std::format!("{:?}", PhysicalSize::<u32>::default().clone());
1184        HashSet::new().insert(PhysicalSize::<u32>::default());
1185
1186        let _ = std::format!("{:?}", Size::Physical((1, 2).into()).clone());
1187        let _ = std::format!("{:?}", Position::Physical((1, 2).into()).clone());
1188    }
1189
1190    #[test]
1191    fn ensure_copy_trait() {
1192        fn is_copy<T: Copy>() {}
1193
1194        is_copy::<LogicalUnit<i32>>();
1195        is_copy::<PhysicalUnit<f64>>();
1196        is_copy::<PixelUnit>();
1197
1198        is_copy::<LogicalSize<i32>>();
1199        is_copy::<PhysicalSize<f64>>();
1200        is_copy::<Size>();
1201
1202        is_copy::<LogicalPosition<i32>>();
1203        is_copy::<PhysicalPosition<f64>>();
1204        is_copy::<Position>();
1205    }
1206
1207    #[test]
1208    fn ensure_partial_eq_trait() {
1209        fn is_partial_eq<T: PartialEq>() {}
1210
1211        is_partial_eq::<LogicalUnit<i32>>();
1212        is_partial_eq::<PhysicalUnit<f64>>();
1213        is_partial_eq::<PixelUnit>();
1214
1215        is_partial_eq::<LogicalSize<i32>>();
1216        is_partial_eq::<PhysicalSize<f64>>();
1217        is_partial_eq::<Size>();
1218
1219        is_partial_eq::<LogicalPosition<i32>>();
1220        is_partial_eq::<PhysicalPosition<f64>>();
1221        is_partial_eq::<Position>();
1222    }
1223}