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