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 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#[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 pub const fn new(dip: i32) -> Self {
284 Dip(dip * DIP_TO_PX)
285 }
286
287 pub fn new_f32(dip: f32) -> Self {
289 Dip((dip * DIP_TO_PX as f32).round() as i32)
290 }
291
292 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 pub fn to_f32(self) -> f32 {
299 self.0 as f32 / DIP_TO_PX as f32
300 }
301
302 pub fn to_i32(self) -> i32 {
304 self.0 / DIP_TO_PX
305 }
306
307 pub fn max(self, other: Dip) -> Dip {
309 Dip(self.0.max(other.0))
310 }
311
312 pub fn min(self, other: Dip) -> Dip {
314 Dip(self.0.min(other.0))
315 }
316
317 pub fn abs(self) -> Dip {
319 Dip(self.0.saturating_abs())
320 }
321
322 pub const MAX: Dip = Dip(i32::MAX / DIP_TO_PX);
324 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}
338impl 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
563pub type PxPoint = euclid::Point2D<Px, Px>;
565
566pub type DipPoint = euclid::Point2D<Dip, Dip>;
568
569pub type PxVector = euclid::Vector2D<Px, Px>;
571
572pub type DipVector = euclid::Vector2D<Dip, Dip>;
574
575pub type PxSize = euclid::Size2D<Px, Px>;
577
578pub type DipSize = euclid::Size2D<Dip, Dip>;
580
581pub type PxRect = euclid::Rect<Px, Px>;
583
584pub type PxBox = euclid::Box2D<Px, Px>;
586
587pub type DipRect = euclid::Rect<Dip, Dip>;
589
590pub type DipBox = euclid::Box2D<Dip, Dip>;
592
593pub type PxSideOffsets = SideOffsets2D<Px, Px>;
595pub type DipSideOffsets = SideOffsets2D<Dip, Dip>;
597
598pub type PxCornerRadius = CornerRadius2D<Px, Px>;
600
601pub type DipCornerRadius = CornerRadius2D<Dip, Dip>;
603
604pub trait PxToDip {
606 type AsDip;
608
609 fn to_dip(self, scale_factor: Factor) -> Self::AsDip;
611}
612
613pub trait DipToPx {
615 type AsPx;
617
618 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}