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