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