zng_unit/
px_dip.rs

1use std::{cmp, fmt, ops};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{CornerRadius2D, Factor, side_offsets::SideOffsets2D};
6
7/// Same value used in `60`.
8const DIP_TO_PX: i32 = 60;
9
10/// Device pixel.
11///
12/// Represents an actual device pixel, not descaled by the pixel scale factor.
13#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, bytemuck::Zeroable, bytemuck::Pod)]
14#[repr(transparent)]
15#[serde(transparent)]
16pub struct Px(pub i32);
17impl Px {
18    /// See [`DipToPx`].
19    pub fn from_dip(dip: Dip, scale_factor: Factor) -> Px {
20        Px((dip.0 as f32 / DIP_TO_PX as f32 * scale_factor.0).round() as i32)
21    }
22
23    /// Compares and returns the maximum of two pixel values.
24    pub fn max(self, other: Px) -> Px {
25        Px(self.0.max(other.0))
26    }
27
28    /// Compares and returns the minimum of two pixel values.
29    pub fn min(self, other: Px) -> Px {
30        Px(self.0.min(other.0))
31    }
32
33    /// Computes the saturating absolute value of `self`.
34    pub fn abs(self) -> Px {
35        Px(self.0.saturating_abs())
36    }
37
38    /// [`i32::MAX`].
39    pub const MAX: Px = Px(i32::MAX);
40
41    /// [`i32::MIN`].
42    pub const MIN: Px = Px(i32::MIN);
43}
44impl fmt::Debug for Px {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        write!(f, "{}px", self.0)
47    }
48}
49impl fmt::Display for Px {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        write!(f, "{}px", self.0)
52    }
53}
54/// Parses `"##"` and `"##px"` where `##` is an `i32`.
55impl std::str::FromStr for Px {
56    type Err = std::num::ParseIntError;
57
58    fn from_str(s: &str) -> Result<Self, Self::Err> {
59        crate::parse_suffix(s, &["px"]).map(Px)
60    }
61}
62impl num_traits::ToPrimitive for Px {
63    fn to_i32(&self) -> Option<i32> {
64        Some(self.0)
65    }
66    fn to_i64(&self) -> Option<i64> {
67        Some(self.0 as i64)
68    }
69
70    fn to_u64(&self) -> Option<u64> {
71        Some(self.0 as u64)
72    }
73}
74impl num_traits::NumCast for Px {
75    fn from<T: num_traits::ToPrimitive>(n: T) -> Option<Self> {
76        if let Some(p) = n.to_i32() {
77            Some(Px(p))
78        } else {
79            n.to_f32().map(|p| Px(p as i32))
80        }
81    }
82}
83impl num_traits::Zero for Px {
84    fn zero() -> Self {
85        Px(0)
86    }
87
88    fn is_zero(&self) -> bool {
89        self.0 == 0
90    }
91}
92impl num_traits::One for Px {
93    fn one() -> Self {
94        Px(1)
95    }
96}
97impl euclid::num::Round for Px {
98    fn round(self) -> Self {
99        self
100    }
101}
102impl euclid::num::Ceil for Px {
103    fn ceil(self) -> Self {
104        self
105    }
106}
107impl euclid::num::Floor for Px {
108    fn floor(self) -> Self {
109        self
110    }
111}
112impl num_traits::Num for Px {
113    type FromStrRadixErr = <i32 as num_traits::Num>::FromStrRadixErr;
114
115    fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
116        num_traits::Num::from_str_radix(str, radix).map(Px)
117    }
118}
119impl num_traits::Signed for Px {
120    fn abs(&self) -> Self {
121        Px(self.0.abs())
122    }
123
124    fn abs_sub(&self, other: &Self) -> Self {
125        Px(num_traits::Signed::abs_sub(&self.0, &other.0))
126    }
127
128    fn signum(&self) -> Self {
129        Px(num_traits::Signed::signum(&self.0))
130    }
131
132    fn is_positive(&self) -> bool {
133        self.0 > 0
134    }
135
136    fn is_negative(&self) -> bool {
137        self.0 < 0
138    }
139}
140impl From<i32> for Px {
141    fn from(px: i32) -> Self {
142        Px(px)
143    }
144}
145impl ops::Add for Px {
146    type Output = Self;
147
148    fn add(self, rhs: Self) -> Self::Output {
149        Px(self.0.saturating_add(rhs.0))
150    }
151}
152impl ops::AddAssign for Px {
153    fn add_assign(&mut self, rhs: Self) {
154        *self = *self + rhs;
155    }
156}
157impl ops::Sub for Px {
158    type Output = Self;
159
160    fn sub(self, rhs: Self) -> Self::Output {
161        Px(self.0.saturating_sub(rhs.0))
162    }
163}
164impl ops::SubAssign for Px {
165    fn sub_assign(&mut self, rhs: Self) {
166        *self = *self - rhs;
167    }
168}
169impl ops::Mul<f32> for Px {
170    type Output = Px;
171
172    fn mul(self, rhs: f32) -> Self::Output {
173        Px((self.0 as f32 * rhs).round() as i32)
174    }
175}
176impl ops::MulAssign<f32> for Px {
177    fn mul_assign(&mut self, rhs: f32) {
178        *self = *self * rhs;
179    }
180}
181impl ops::Mul<i32> for Px {
182    type Output = Px;
183
184    fn mul(self, rhs: i32) -> Self::Output {
185        Px(self.0 * rhs)
186    }
187}
188impl ops::MulAssign<i32> for Px {
189    fn mul_assign(&mut self, rhs: i32) {
190        *self = *self * rhs;
191    }
192}
193impl ops::Mul<Px> for Px {
194    type Output = Px;
195
196    fn mul(self, rhs: Px) -> Self::Output {
197        Px(self.0.saturating_mul(rhs.0))
198    }
199}
200impl ops::MulAssign<Px> for Px {
201    fn mul_assign(&mut self, rhs: Px) {
202        *self = *self * rhs;
203    }
204}
205impl ops::Div<f32> for Px {
206    type Output = Px;
207
208    fn div(self, rhs: f32) -> Self::Output {
209        Px((self.0 as f32 / rhs).round() as i32)
210    }
211}
212impl ops::Div<i32> for Px {
213    type Output = Px;
214
215    fn div(self, rhs: i32) -> Self::Output {
216        Px(self.0 / rhs)
217    }
218}
219impl ops::Div<Px> for Px {
220    type Output = Px;
221
222    fn div(self, rhs: Px) -> Self::Output {
223        Px(self.0 / rhs.0)
224    }
225}
226impl ops::DivAssign<f32> for Px {
227    fn div_assign(&mut self, rhs: f32) {
228        *self = *self / rhs;
229    }
230}
231impl ops::DivAssign<i32> for Px {
232    fn div_assign(&mut self, rhs: i32) {
233        *self = *self / rhs;
234    }
235}
236impl ops::DivAssign<Px> for Px {
237    fn div_assign(&mut self, rhs: Px) {
238        *self = *self / rhs;
239    }
240}
241impl ops::Neg for Px {
242    type Output = Self;
243
244    fn neg(self) -> Self::Output {
245        Px(self.0.saturating_neg())
246    }
247}
248impl ops::Rem for Px {
249    type Output = Self;
250
251    fn rem(self, rhs: Self) -> Self::Output {
252        Px(self.0 % rhs.0)
253    }
254}
255impl std::iter::Sum for Px {
256    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
257        iter.fold(Px(0), |a, b| a + b)
258    }
259}
260impl PartialEq<i32> for Px {
261    fn eq(&self, other: &i32) -> bool {
262        *self == Px(*other)
263    }
264}
265impl PartialOrd<i32> for Px {
266    fn partial_cmp(&self, other: &i32) -> Option<cmp::Ordering> {
267        self.partial_cmp(&Px(*other))
268    }
269}
270
271/// Device independent pixel.
272///
273/// Represent a device pixel descaled by the pixel scale factor.
274///
275/// Internally this is an `i32` that represents 1/60th of a pixel.
276#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, bytemuck::Zeroable, bytemuck::Pod)]
277#[serde(from = "f32")]
278#[serde(into = "f32")]
279#[repr(transparent)]
280pub struct Dip(i32);
281impl Dip {
282    /// New from round integer value.
283    pub const fn new(dip: i32) -> Self {
284        Dip(dip * DIP_TO_PX)
285    }
286
287    /// new from floating point.
288    pub fn new_f32(dip: f32) -> Self {
289        Dip((dip * DIP_TO_PX as f32).round() as i32)
290    }
291
292    /// See [`PxToDip`].
293    pub fn from_px(px: Px, scale_factor: Factor) -> Dip {
294        Dip((px.0 as f32 / scale_factor.0 * DIP_TO_PX as f32).round() as i32)
295    }
296
297    /// Returns `self` as [`f32`].
298    pub fn to_f32(self) -> f32 {
299        self.0 as f32 / DIP_TO_PX as f32
300    }
301
302    /// Returns `self` as [`i32`].
303    pub fn to_i32(self) -> i32 {
304        self.0 / DIP_TO_PX
305    }
306
307    /// Compares and returns the maximum of two pixel values.
308    pub fn max(self, other: Dip) -> Dip {
309        Dip(self.0.max(other.0))
310    }
311
312    /// Compares and returns the minimum of two pixel values.
313    pub fn min(self, other: Dip) -> Dip {
314        Dip(self.0.min(other.0))
315    }
316
317    /// Computes the saturating absolute value of `self`.
318    pub fn abs(self) -> Dip {
319        Dip(self.0.saturating_abs())
320    }
321
322    /// Maximum DIP value.
323    pub const MAX: Dip = Dip(i32::MAX / DIP_TO_PX);
324    /// Minimum DIP value.
325    pub const MIN: Dip = Dip(i32::MIN / DIP_TO_PX);
326}
327impl fmt::Debug for Dip {
328    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329        fmt::Display::fmt(self, f)
330    }
331}
332impl fmt::Display for Dip {
333    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334        fmt::Display::fmt(&self.to_f32(), f)?;
335        write!(f, "dip")
336    }
337}
338/// Parses `"##"` and `"##dip"` where `##` is an `f32`.
339impl std::str::FromStr for Dip {
340    type Err = std::num::ParseFloatError;
341
342    fn from_str(s: &str) -> Result<Self, Self::Err> {
343        crate::parse_suffix(s, &["dip"]).map(Dip::new_f32)
344    }
345}
346impl From<i32> for Dip {
347    fn from(dip: i32) -> Self {
348        Dip::new(dip)
349    }
350}
351impl From<f32> for Dip {
352    fn from(dip: f32) -> Self {
353        Dip::new_f32(dip)
354    }
355}
356impl From<Dip> for f32 {
357    fn from(value: Dip) -> Self {
358        value.to_f32()
359    }
360}
361impl ops::Add for Dip {
362    type Output = Self;
363
364    fn add(self, rhs: Self) -> Self::Output {
365        Dip(self.0.saturating_add(rhs.0))
366    }
367}
368impl ops::AddAssign for Dip {
369    fn add_assign(&mut self, rhs: Self) {
370        *self = *self + rhs;
371    }
372}
373impl ops::Sub for Dip {
374    type Output = Self;
375
376    fn sub(self, rhs: Self) -> Self::Output {
377        Dip(self.0.saturating_sub(rhs.0))
378    }
379}
380impl ops::SubAssign for Dip {
381    fn sub_assign(&mut self, rhs: Self) {
382        *self = *self - rhs;
383    }
384}
385impl ops::Neg for Dip {
386    type Output = Self;
387
388    fn neg(self) -> Self::Output {
389        Dip(self.0.saturating_neg())
390    }
391}
392impl ops::Mul<f32> for Dip {
393    type Output = Dip;
394
395    fn mul(self, rhs: f32) -> Self::Output {
396        Dip((self.0 as f32 * rhs).round() as i32)
397    }
398}
399impl ops::MulAssign<f32> for Dip {
400    fn mul_assign(&mut self, rhs: f32) {
401        *self = *self * rhs;
402    }
403}
404impl ops::Mul<Dip> for Dip {
405    type Output = Dip;
406
407    fn mul(self, rhs: Dip) -> Self::Output {
408        Dip(self.0.saturating_mul(rhs.to_i32()))
409    }
410}
411impl ops::MulAssign<Dip> for Dip {
412    fn mul_assign(&mut self, rhs: Dip) {
413        *self = *self * rhs;
414    }
415}
416impl ops::Div<f32> for Dip {
417    type Output = Dip;
418
419    fn div(self, rhs: f32) -> Self::Output {
420        Dip((self.0 as f32 / rhs).round() as i32)
421    }
422}
423impl ops::DivAssign<f32> for Dip {
424    fn div_assign(&mut self, rhs: f32) {
425        *self = *self / rhs;
426    }
427}
428impl ops::Div<Dip> for Dip {
429    type Output = Dip;
430
431    fn div(self, rhs: Dip) -> Self::Output {
432        Dip::new(self.0 / rhs.0)
433    }
434}
435impl ops::DivAssign<Dip> for Dip {
436    fn div_assign(&mut self, rhs: Dip) {
437        *self = *self / rhs;
438    }
439}
440impl ops::Rem for Dip {
441    type Output = Self;
442
443    fn rem(self, rhs: Self) -> Self::Output {
444        Dip(self.0 % rhs.0)
445    }
446}
447impl ops::RemAssign for Dip {
448    fn rem_assign(&mut self, rhs: Self) {
449        *self = *self % rhs;
450    }
451}
452impl num_traits::ToPrimitive for Dip {
453    fn to_i64(&self) -> Option<i64> {
454        Some(Dip::to_i32(*self) as i64)
455    }
456
457    fn to_u64(&self) -> Option<u64> {
458        if self.0 >= 0 { Some(Dip::to_i32(*self) as u64) } else { None }
459    }
460
461    fn to_f32(&self) -> Option<f32> {
462        Some(Dip::to_f32(*self))
463    }
464
465    fn to_f64(&self) -> Option<f64> {
466        Some(Dip::to_f32(*self) as f64)
467    }
468}
469impl num_traits::NumCast for Dip {
470    fn from<T: num_traits::ToPrimitive>(n: T) -> Option<Self> {
471        #[expect(clippy::manual_map)]
472        if let Some(n) = n.to_f32() {
473            Some(Dip::new_f32(n))
474        } else if let Some(n) = n.to_i32() {
475            Some(Dip::new(n))
476        } else {
477            None
478        }
479    }
480}
481impl num_traits::Zero for Dip {
482    fn zero() -> Self {
483        Dip(0)
484    }
485
486    fn is_zero(&self) -> bool {
487        self.0 == 0
488    }
489}
490impl num_traits::One for Dip {
491    fn one() -> Self {
492        Dip::new(1)
493    }
494}
495impl euclid::num::Round for Dip {
496    fn round(self) -> Self {
497        Dip::new_f32(self.to_f32().round())
498    }
499}
500impl euclid::num::Ceil for Dip {
501    fn ceil(self) -> Self {
502        Dip::new_f32(self.to_f32().ceil())
503    }
504}
505impl euclid::num::Floor for Dip {
506    fn floor(self) -> Self {
507        Dip::new_f32(self.to_f32().floor())
508    }
509}
510impl num_traits::Signed for Dip {
511    fn abs(&self) -> Self {
512        Dip(self.0.abs())
513    }
514
515    fn abs_sub(&self, other: &Self) -> Self {
516        Dip(num_traits::Signed::abs_sub(&self.0, &other.0))
517    }
518
519    fn signum(&self) -> Self {
520        match self.0.cmp(&0) {
521            cmp::Ordering::Less => Dip::new(-1),
522            cmp::Ordering::Equal => Dip(0),
523            cmp::Ordering::Greater => Dip::new(1),
524        }
525    }
526
527    fn is_positive(&self) -> bool {
528        self.0 > 0
529    }
530
531    fn is_negative(&self) -> bool {
532        self.0 < 0
533    }
534}
535impl num_traits::Num for Dip {
536    type FromStrRadixErr = <i32 as num_traits::Num>::FromStrRadixErr;
537
538    fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
539        num_traits::Num::from_str_radix(str, radix).map(Dip::new)
540    }
541}
542impl PartialEq<i32> for Dip {
543    fn eq(&self, other: &i32) -> bool {
544        *self == Dip::new(*other)
545    }
546}
547impl PartialOrd<i32> for Dip {
548    fn partial_cmp(&self, other: &i32) -> Option<cmp::Ordering> {
549        self.partial_cmp(&Dip::new(*other))
550    }
551}
552impl PartialEq<f32> for Dip {
553    fn eq(&self, other: &f32) -> bool {
554        *self == Dip::new_f32(*other)
555    }
556}
557impl PartialOrd<f32> for Dip {
558    fn partial_cmp(&self, other: &f32) -> Option<cmp::Ordering> {
559        self.partial_cmp(&Dip::new_f32(*other))
560    }
561}
562
563/// A point in device pixels.
564pub type PxPoint = euclid::Point2D<Px, Px>;
565
566/// A point in device independent pixels.
567pub type DipPoint = euclid::Point2D<Dip, Dip>;
568
569/// A vector in device pixels.
570pub type PxVector = euclid::Vector2D<Px, Px>;
571
572/// A vector in device independent pixels.
573pub type DipVector = euclid::Vector2D<Dip, Dip>;
574
575/// A size in device pixels.
576pub type PxSize = euclid::Size2D<Px, Px>;
577
578/// A size in device pixels.
579pub type DipSize = euclid::Size2D<Dip, Dip>;
580
581/// A rectangle in device pixels.
582pub type PxRect = euclid::Rect<Px, Px>;
583
584/// A rectangle box in device pixels.
585pub type PxBox = euclid::Box2D<Px, Px>;
586
587/// A rectangle in device independent pixels.
588pub type DipRect = euclid::Rect<Dip, Dip>;
589
590/// A rectangle box in device independent pixels.
591pub type DipBox = euclid::Box2D<Dip, Dip>;
592
593/// Side-offsets in device pixels.
594pub type PxSideOffsets = SideOffsets2D<Px, Px>;
595/// Side-offsets in device independent pixels.
596pub type DipSideOffsets = SideOffsets2D<Dip, Dip>;
597
598/// Corner-radius in device pixels.
599pub type PxCornerRadius = CornerRadius2D<Px, Px>;
600
601/// Corner-radius in device independent pixels.
602pub type DipCornerRadius = CornerRadius2D<Dip, Dip>;
603
604/// Conversion from [`Px`] to [`Dip`] units.
605pub trait PxToDip {
606    /// `Self` equivalent in [`Dip`] units.
607    type AsDip;
608
609    /// Divide the [`Px`] self by the scale.
610    fn to_dip(self, scale_factor: Factor) -> Self::AsDip;
611}
612
613/// Conversion from [`Dip`] to [`Px`] units.
614pub trait DipToPx {
615    /// `Self` equivalent in [`Px`] units.
616    type AsPx;
617
618    /// Multiply the [`Dip`] self by the scale.
619    fn to_px(self, scale_factor: Factor) -> Self::AsPx;
620}
621
622impl PxToDip for Px {
623    type AsDip = Dip;
624
625    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
626        Dip::from_px(self, scale_factor)
627    }
628}
629
630impl DipToPx for Dip {
631    type AsPx = Px;
632
633    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
634        Px::from_dip(self, scale_factor)
635    }
636}
637
638impl PxToDip for PxPoint {
639    type AsDip = DipPoint;
640
641    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
642        DipPoint::new(self.x.to_dip(scale_factor), self.y.to_dip(scale_factor))
643    }
644}
645
646impl DipToPx for DipPoint {
647    type AsPx = PxPoint;
648
649    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
650        PxPoint::new(self.x.to_px(scale_factor), self.y.to_px(scale_factor))
651    }
652}
653impl DipToPx for euclid::Point2D<f32, Dip> {
654    type AsPx = euclid::Point2D<f32, Px>;
655
656    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
657        euclid::point2(self.x * scale_factor.0, self.y * scale_factor.0)
658    }
659}
660
661impl PxToDip for PxSize {
662    type AsDip = DipSize;
663
664    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
665        DipSize::new(self.width.to_dip(scale_factor), self.height.to_dip(scale_factor))
666    }
667}
668
669impl DipToPx for DipSize {
670    type AsPx = PxSize;
671
672    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
673        PxSize::new(self.width.to_px(scale_factor), self.height.to_px(scale_factor))
674    }
675}
676
677impl DipToPx for DipVector {
678    type AsPx = PxVector;
679
680    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
681        PxVector::new(self.x.to_px(scale_factor), self.y.to_px(scale_factor))
682    }
683}
684impl PxToDip for PxVector {
685    type AsDip = DipVector;
686
687    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
688        DipVector::new(self.x.to_dip(scale_factor), self.y.to_dip(scale_factor))
689    }
690}
691
692impl PxToDip for PxRect {
693    type AsDip = DipRect;
694
695    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
696        DipRect::new(self.origin.to_dip(scale_factor), self.size.to_dip(scale_factor))
697    }
698}
699
700impl DipToPx for DipRect {
701    type AsPx = PxRect;
702
703    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
704        PxRect::new(self.origin.to_px(scale_factor), self.size.to_px(scale_factor))
705    }
706}
707
708impl PxToDip for PxBox {
709    type AsDip = DipBox;
710
711    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
712        DipBox::new(self.min.to_dip(scale_factor), self.max.to_dip(scale_factor))
713    }
714}
715
716impl DipToPx for DipBox {
717    type AsPx = PxBox;
718
719    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
720        PxBox::new(self.min.to_px(scale_factor), self.max.to_px(scale_factor))
721    }
722}
723
724impl DipToPx for DipSideOffsets {
725    type AsPx = PxSideOffsets;
726
727    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
728        PxSideOffsets::new(
729            self.top.to_px(scale_factor),
730            self.right.to_px(scale_factor),
731            self.bottom.to_px(scale_factor),
732            self.left.to_px(scale_factor),
733        )
734    }
735}
736impl PxToDip for PxSideOffsets {
737    type AsDip = DipSideOffsets;
738
739    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
740        DipSideOffsets::new(
741            self.top.to_dip(scale_factor),
742            self.right.to_dip(scale_factor),
743            self.bottom.to_dip(scale_factor),
744            self.left.to_dip(scale_factor),
745        )
746    }
747}
748
749impl DipToPx for DipCornerRadius {
750    type AsPx = PxCornerRadius;
751
752    fn to_px(self, scale_factor: Factor) -> Self::AsPx {
753        PxCornerRadius::new(
754            self.top_left.to_px(scale_factor),
755            self.top_right.to_px(scale_factor),
756            self.bottom_left.to_px(scale_factor),
757            self.bottom_right.to_px(scale_factor),
758        )
759    }
760}
761impl PxToDip for PxCornerRadius {
762    type AsDip = DipCornerRadius;
763
764    fn to_dip(self, scale_factor: Factor) -> Self::AsDip {
765        DipCornerRadius::new(
766            self.top_left.to_dip(scale_factor),
767            self.top_right.to_dip(scale_factor),
768            self.bottom_left.to_dip(scale_factor),
769            self.bottom_right.to_dip(scale_factor),
770        )
771    }
772}
773
774#[cfg(test)]
775mod tests {
776    use super::*;
777
778    #[test]
779    fn dip_px_1_1_conversion() {
780        let px = Dip::new(100).to_px(Factor(1.0));
781        assert_eq!(px, Px(100));
782    }
783
784    #[test]
785    fn px_dip_1_1_conversion() {
786        let dip = Px(100).to_dip(Factor(1.0));
787        assert_eq!(dip, Dip::new(100));
788    }
789
790    #[test]
791    fn dip_px_1_15_conversion() {
792        let px = Dip::new(100).to_px(Factor(1.5));
793        assert_eq!(px, Px(150));
794    }
795
796    #[test]
797    fn px_dip_1_15_conversion() {
798        let dip = Px(150).to_dip(Factor(1.5));
799        assert_eq!(dip, Dip::new(100));
800    }
801}