1use std::fmt;
31use std::ops::{Add, Div, Mul, Neg, Sub};
32
33use crate::geometry::{Rect, Size, Vec2};
34
35#[derive(Clone, Copy, Default, PartialEq)]
40#[repr(transparent)]
41pub struct Dp(pub f32);
42
43impl Dp {
44 pub const ZERO: Dp = Dp(0.0);
45 pub const HAIRLINE: Dp = Dp(0.0);
47 pub const INFINITY: Dp = Dp(f32::INFINITY);
48 pub const UNSPECIFIED: Dp = Dp(f32::NAN);
50
51 #[inline]
52 pub const fn from_px_raw(v: f32) -> Dp {
53 Dp(v)
54 }
55
56 #[inline]
58 pub const fn value(self) -> f32 {
59 self.0
60 }
61
62 #[inline]
64 pub fn is_specified(self) -> bool {
65 !self.0.is_nan()
66 }
67
68 #[inline]
70 pub fn is_unspecified(self) -> bool {
71 self.0.is_nan()
72 }
73
74 #[inline]
76 pub fn take_or_else(self, block: impl FnOnce() -> Dp) -> Dp {
77 if self.is_specified() { self } else { block() }
78 }
79
80 #[inline]
81 pub fn is_finite(self) -> bool {
82 self.0.is_finite()
83 }
84
85 #[inline]
87 pub fn abs(self) -> Dp {
88 Dp(self.0.abs())
89 }
90
91 #[inline]
93 pub fn min(self, other: Dp) -> Dp {
94 Dp(self.0.min(other.0))
95 }
96
97 #[inline]
99 pub fn max(self, other: Dp) -> Dp {
100 Dp(self.0.max(other.0))
101 }
102
103 #[inline]
105 pub fn clamp(self, min: Dp, max: Dp) -> Dp {
106 Dp(self.0.clamp(min.0, max.0))
107 }
108
109 #[inline]
110 pub fn coerce_in(self, min: Dp, max: Dp) -> Dp {
111 Dp(self.0.clamp(min.0, max.0))
112 }
113
114 #[inline]
115 pub fn coerce_at_least(self, min: Dp) -> Dp {
116 Dp(self.0.max(min.0))
117 }
118
119 #[inline]
120 pub fn coerce_at_most(self, max: Dp) -> Dp {
121 Dp(self.0.min(max.0))
122 }
123
124 #[inline]
126 pub fn to_px(self) -> Px {
127 Px(self.0 * crate::locals::effective_density_scale())
128 }
129
130 #[inline]
132 pub fn to_px_with_scale(self, scale: f32) -> Px {
133 Px(self.0 * scale)
134 }
135
136 #[inline]
138 pub fn round_to_px(self) -> i32 {
139 self.to_px().0.round() as i32
140 }
141
142 #[inline]
144 pub fn to_sp(self) -> Sp {
145 let fs = crate::locals::text_scale().0.max(0.0001);
146 Sp(self.0 / fs)
147 }
148
149 #[inline]
152 pub fn to_bits(self) -> u32 {
153 hash_f32_bits(self.0)
154 }
155}
156
157impl fmt::Debug for Dp {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 if self.is_unspecified() {
160 write!(f, "Dp.Unspecified")
161 } else {
162 write!(f, "{}dp", self.0)
163 }
164 }
165}
166
167impl fmt::Display for Dp {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 if self.is_unspecified() {
170 write!(f, "Dp.Unspecified")
171 } else {
172 write!(f, "{}dp", self.0)
173 }
174 }
175}
176
177impl std::hash::Hash for Dp {
178 #[inline]
179 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
180 hash_f32_bits(self.0).hash(state);
183 }
184}
185
186#[inline]
189fn hash_f32_bits(v: f32) -> u32 {
190 let mut bits = v.to_bits();
191 if bits == 0x8000_0000 {
192 bits = 0;
193 }
194 if v.is_nan() {
195 bits = 0x7FC0_0000;
196 }
197 bits
198}
199
200impl Add for Dp {
201 type Output = Dp;
202 #[inline]
203 fn add(self, other: Dp) -> Dp {
204 Dp(self.0 + other.0)
205 }
206}
207
208impl Sub for Dp {
209 type Output = Dp;
210 #[inline]
211 fn sub(self, other: Dp) -> Dp {
212 Dp(self.0 - other.0)
213 }
214}
215
216impl Neg for Dp {
217 type Output = Dp;
218 #[inline]
219 fn neg(self) -> Dp {
220 Dp(-self.0)
221 }
222}
223
224impl Mul<f32> for Dp {
225 type Output = Dp;
226 #[inline]
227 fn mul(self, other: f32) -> Dp {
228 Dp(self.0 * other)
229 }
230}
231
232impl Mul<Dp> for f32 {
233 type Output = Dp;
234 #[inline]
235 fn mul(self, other: Dp) -> Dp {
236 Dp(self * other.0)
237 }
238}
239
240impl Div<f32> for Dp {
241 type Output = Dp;
242 #[inline]
243 fn div(self, other: f32) -> Dp {
244 Dp(self.0 / other)
245 }
246}
247
248impl Div<Dp> for Dp {
250 type Output = f32;
251 #[inline]
252 fn div(self, other: Dp) -> f32 {
253 self.0 / other.0
254 }
255}
256
257impl PartialOrd for Dp {
258 #[inline]
259 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
260 if self.0.is_nan() || other.0.is_nan() {
262 Some(std::cmp::Ordering::Equal)
263 } else {
264 self.0.partial_cmp(&other.0)
265 }
266 }
267}
268
269#[inline]
271pub fn lerp_dp(start: Dp, stop: Dp, fraction: f32) -> Dp {
272 Dp(start.0 + (stop.0 - start.0) * fraction)
273}
274
275#[inline]
276pub fn min_dp(a: Dp, b: Dp) -> Dp {
277 Dp(a.0.min(b.0))
278}
279
280#[inline]
281pub fn max_dp(a: Dp, b: Dp) -> Dp {
282 Dp(a.0.max(b.0))
283}
284
285#[derive(Clone, Copy, Default, PartialEq)]
290#[repr(transparent)]
291pub struct Px(pub f32);
292
293impl Px {
294 pub const ZERO: Px = Px(0.0);
295 pub const INFINITY: Px = Px(f32::INFINITY);
296 pub const UNSPECIFIED: Px = Px(f32::NAN);
297
298 #[inline]
299 pub const fn value(self) -> f32 {
300 self.0
301 }
302
303 #[inline]
304 pub fn is_specified(self) -> bool {
305 !self.0.is_nan()
306 }
307
308 #[inline]
309 pub fn is_finite(self) -> bool {
310 self.0.is_finite()
311 }
312
313 #[inline]
315 pub fn abs(self) -> Px {
316 Px(self.0.abs())
317 }
318
319 #[inline]
321 pub fn min(self, other: Px) -> Px {
322 Px(self.0.min(other.0))
323 }
324
325 #[inline]
327 pub fn max(self, other: Px) -> Px {
328 Px(self.0.max(other.0))
329 }
330
331 #[inline]
333 pub fn to_dp(self) -> Dp {
334 let scale = crate::locals::effective_density_scale();
335 if scale <= 0.0001 {
336 Dp(0.0)
337 } else {
338 Dp(self.0 / scale)
339 }
340 }
341
342 #[inline]
344 pub fn to_dp_with_scale(self, scale: f32) -> Dp {
345 if scale <= 0.0001 {
346 Dp(0.0)
347 } else {
348 Dp(self.0 / scale)
349 }
350 }
351
352 #[inline]
354 pub fn to_sp(self) -> Sp {
355 self.to_dp().to_sp()
356 }
357}
358
359impl fmt::Debug for Px {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 write!(f, "{}px", self.0)
362 }
363}
364
365impl fmt::Display for Px {
366 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367 write!(f, "{}px", self.0)
368 }
369}
370
371impl std::hash::Hash for Px {
372 #[inline]
373 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
374 hash_f32_bits(self.0).hash(state);
375 }
376}
377
378impl Add for Px {
379 type Output = Px;
380 #[inline]
381 fn add(self, other: Px) -> Px {
382 Px(self.0 + other.0)
383 }
384}
385
386impl Sub for Px {
387 type Output = Px;
388 #[inline]
389 fn sub(self, other: Px) -> Px {
390 Px(self.0 - other.0)
391 }
392}
393
394impl Neg for Px {
395 type Output = Px;
396 #[inline]
397 fn neg(self) -> Px {
398 Px(-self.0)
399 }
400}
401
402impl Mul<f32> for Px {
403 type Output = Px;
404 #[inline]
405 fn mul(self, other: f32) -> Px {
406 Px(self.0 * other)
407 }
408}
409
410impl Mul<Px> for f32 {
411 type Output = Px;
412 #[inline]
413 fn mul(self, other: Px) -> Px {
414 Px(self * other.0)
415 }
416}
417
418impl Div<f32> for Px {
419 type Output = Px;
420 #[inline]
421 fn div(self, other: f32) -> Px {
422 Px(self.0 / other)
423 }
424}
425
426impl Div<Px> for Px {
427 type Output = f32;
428 #[inline]
429 fn div(self, other: Px) -> f32 {
430 self.0 / other.0
431 }
432}
433
434impl PartialOrd for Px {
435 #[inline]
436 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
437 self.0.partial_cmp(&other.0)
438 }
439}
440
441#[inline]
442pub fn lerp_px(start: Px, stop: Px, fraction: f32) -> Px {
443 Px(start.0 + (stop.0 - start.0) * fraction)
444}
445
446#[derive(Clone, Copy, Default, PartialEq)]
451#[repr(transparent)]
452pub struct Sp(pub f32);
453
454impl Sp {
455 pub const ZERO: Sp = Sp(0.0);
456 pub const UNSPECIFIED: Sp = Sp(f32::NAN);
457
458 #[inline]
459 pub const fn value(self) -> f32 {
460 self.0
461 }
462
463 #[inline]
464 pub fn is_specified(self) -> bool {
465 !self.0.is_nan()
466 }
467
468 #[inline]
470 pub fn abs(self) -> Sp {
471 Sp(self.0.abs())
472 }
473
474 #[inline]
476 pub fn min(self, other: Sp) -> Sp {
477 Sp(self.0.min(other.0))
478 }
479
480 #[inline]
482 pub fn max(self, other: Sp) -> Sp {
483 Sp(self.0.max(other.0))
484 }
485
486 #[inline]
488 pub fn to_px(self) -> Px {
489 Px(self.0
490 * crate::locals::effective_density_scale()
491 * crate::locals::text_scale().0.max(0.0))
492 }
493
494 #[inline]
496 pub fn to_dp(self) -> Dp {
497 Dp(self.0 * crate::locals::text_scale().0.max(0.0))
498 }
499
500 pub fn take_or_else(self, block: impl FnOnce() -> Sp) -> Sp {
501 if self.is_specified() { self } else { block() }
502 }
503}
504
505impl fmt::Debug for Sp {
506 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
507 if self.is_specified() {
508 write!(f, "{}sp", self.0)
509 } else {
510 write!(f, "Sp.Unspecified")
511 }
512 }
513}
514
515impl fmt::Display for Sp {
516 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
517 if self.is_specified() {
518 write!(f, "{}sp", self.0)
519 } else {
520 write!(f, "Sp.Unspecified")
521 }
522 }
523}
524
525impl std::hash::Hash for Sp {
526 #[inline]
527 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
528 hash_f32_bits(self.0).hash(state);
529 }
530}
531
532impl Add for Sp {
533 type Output = Sp;
534 #[inline]
535 fn add(self, other: Sp) -> Sp {
536 Sp(self.0 + other.0)
537 }
538}
539
540impl Sub for Sp {
541 type Output = Sp;
542 #[inline]
543 fn sub(self, other: Sp) -> Sp {
544 Sp(self.0 - other.0)
545 }
546}
547
548impl Neg for Sp {
549 type Output = Sp;
550 #[inline]
551 fn neg(self) -> Sp {
552 Sp(-self.0)
553 }
554}
555
556impl Mul<f32> for Sp {
557 type Output = Sp;
558 #[inline]
559 fn mul(self, other: f32) -> Sp {
560 Sp(self.0 * other)
561 }
562}
563
564impl Div<f32> for Sp {
565 type Output = Sp;
566 #[inline]
567 fn div(self, other: f32) -> Sp {
568 Sp(self.0 / other)
569 }
570}
571
572impl PartialOrd for Sp {
573 #[inline]
574 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
575 self.0.partial_cmp(&other.0)
576 }
577}
578
579#[inline]
580pub fn lerp_sp(start: Sp, stop: Sp, fraction: f32) -> Sp {
581 Sp(start.0 + (stop.0 - start.0) * fraction)
582}
583
584pub trait UnitExt {
586 fn dp(self) -> Dp;
587 fn px(self) -> Px;
588 fn sp(self) -> Sp;
589}
590
591impl UnitExt for f32 {
592 #[inline]
593 fn dp(self) -> Dp {
594 Dp(self)
595 }
596 #[inline]
597 fn px(self) -> Px {
598 Px(self)
599 }
600 #[inline]
601 fn sp(self) -> Sp {
602 Sp(self)
603 }
604}
605
606impl UnitExt for f64 {
607 #[inline]
608 fn dp(self) -> Dp {
609 Dp(self as f32)
610 }
611 #[inline]
612 fn px(self) -> Px {
613 Px(self as f32)
614 }
615 #[inline]
616 fn sp(self) -> Sp {
617 Sp(self as f32)
618 }
619}
620
621impl UnitExt for i32 {
622 #[inline]
623 fn dp(self) -> Dp {
624 Dp(self as f32)
625 }
626 #[inline]
627 fn px(self) -> Px {
628 Px(self as f32)
629 }
630 #[inline]
631 fn sp(self) -> Sp {
632 Sp(self as f32)
633 }
634}
635
636impl UnitExt for u32 {
637 #[inline]
638 fn dp(self) -> Dp {
639 Dp(self as f32)
640 }
641 #[inline]
642 fn px(self) -> Px {
643 Px(self as f32)
644 }
645 #[inline]
646 fn sp(self) -> Sp {
647 Sp(self as f32)
648 }
649}
650
651impl From<f32> for Dp {
655 #[inline]
656 fn from(v: f32) -> Self {
657 Dp(v)
658 }
659}
660impl From<f64> for Dp {
661 #[inline]
662 fn from(v: f64) -> Self {
663 Dp(v as f32)
664 }
665}
666impl From<i32> for Dp {
667 #[inline]
668 fn from(v: i32) -> Self {
669 Dp(v as f32)
670 }
671}
672impl From<u32> for Dp {
673 #[inline]
674 fn from(v: u32) -> Self {
675 Dp(v as f32)
676 }
677}
678impl From<Dp> for f32 {
679 #[inline]
680 fn from(v: Dp) -> Self {
681 v.0
682 }
683}
684
685impl From<f32> for Sp {
686 #[inline]
687 fn from(v: f32) -> Self {
688 Sp(v)
689 }
690}
691impl From<f64> for Sp {
692 #[inline]
693 fn from(v: f64) -> Self {
694 Sp(v as f32)
695 }
696}
697impl From<i32> for Sp {
698 #[inline]
699 fn from(v: i32) -> Self {
700 Sp(v as f32)
701 }
702}
703impl From<u32> for Sp {
704 #[inline]
705 fn from(v: u32) -> Self {
706 Sp(v as f32)
707 }
708}
709impl From<Sp> for f32 {
710 #[inline]
711 fn from(v: Sp) -> Self {
712 v.0
713 }
714}
715
716impl From<f32> for Px {
717 #[inline]
718 fn from(v: f32) -> Self {
719 Px(v)
720 }
721}
722impl From<f64> for Px {
723 #[inline]
724 fn from(v: f64) -> Self {
725 Px(v as f32)
726 }
727}
728impl From<i32> for Px {
729 #[inline]
730 fn from(v: i32) -> Self {
731 Px(v as f32)
732 }
733}
734impl From<u32> for Px {
735 #[inline]
736 fn from(v: u32) -> Self {
737 Px(v as f32)
738 }
739}
740impl From<Px> for f32 {
741 #[inline]
742 fn from(v: Px) -> Self {
743 v.0
744 }
745}
746
747#[derive(Clone, Copy, Debug, Default, PartialEq)]
749pub struct DpOffset {
750 pub x: Dp,
751 pub y: Dp,
752}
753
754impl DpOffset {
755 pub const ZERO: DpOffset = DpOffset {
756 x: Dp::ZERO,
757 y: Dp::ZERO,
758 };
759 pub const UNSPECIFIED: DpOffset = DpOffset {
760 x: Dp::UNSPECIFIED,
761 y: Dp::UNSPECIFIED,
762 };
763
764 #[inline]
765 pub fn new(x: Dp, y: Dp) -> Self {
766 Self { x, y }
767 }
768
769 #[inline]
770 pub fn is_specified(self) -> bool {
771 self.x.is_specified() && self.y.is_specified()
772 }
773
774 #[inline]
776 pub fn to_px(self) -> Vec2 {
777 Vec2 {
778 x: self.x.to_px().0,
779 y: self.y.to_px().0,
780 }
781 }
782}
783
784impl Add for DpOffset {
785 type Output = DpOffset;
786 #[inline]
787 fn add(self, other: DpOffset) -> DpOffset {
788 DpOffset::new(self.x + other.x, self.y + other.y)
789 }
790}
791
792impl Sub for DpOffset {
793 type Output = DpOffset;
794 #[inline]
795 fn sub(self, other: DpOffset) -> DpOffset {
796 DpOffset::new(self.x - other.x, self.y - other.y)
797 }
798}
799
800#[derive(Clone, Copy, Debug, Default, PartialEq)]
802pub struct DpSize {
803 pub width: Dp,
804 pub height: Dp,
805}
806
807impl DpSize {
808 pub const ZERO: DpSize = DpSize {
809 width: Dp::ZERO,
810 height: Dp::ZERO,
811 };
812
813 #[inline]
814 pub fn new(width: Dp, height: Dp) -> Self {
815 Self { width, height }
816 }
817
818 #[inline]
820 pub fn to_px(self) -> Size {
821 Size {
822 width: self.width.to_px().0,
823 height: self.height.to_px().0,
824 }
825 }
826}
827
828#[derive(Clone, Copy, Debug, Default, PartialEq)]
830pub struct DpRect {
831 pub left: Dp,
832 pub top: Dp,
833 pub right: Dp,
834 pub bottom: Dp,
835}
836
837impl DpRect {
838 #[inline]
839 pub fn new(left: Dp, top: Dp, right: Dp, bottom: Dp) -> Self {
840 Self {
841 left,
842 top,
843 right,
844 bottom,
845 }
846 }
847
848 #[inline]
849 pub fn width(self) -> Dp {
850 self.right - self.left
851 }
852
853 #[inline]
854 pub fn height(self) -> Dp {
855 self.bottom - self.top
856 }
857
858 #[inline]
860 pub fn to_px(self) -> Rect {
861 Rect {
862 x: self.left.to_px().0,
863 y: self.top.to_px().0,
864 w: (self.right - self.left).to_px().0,
865 h: (self.bottom - self.top).to_px().0,
866 }
867 }
868}
869
870#[inline]
872pub fn size_px_to_dp(size: Size) -> DpSize {
873 DpSize::new(Px(size.width).to_dp(), Px(size.height).to_dp())
874}
875
876#[cfg(test)]
877mod tests {
878 use super::*;
879
880 #[test]
881 fn dp_arithmetic_matches_compose() {
882 assert_eq!(Dp(2.0) + Dp(3.0), Dp(5.0));
883 assert_eq!(Dp(5.0) - Dp(3.0), Dp(2.0));
884 assert_eq!(-Dp(2.0), Dp(-2.0));
885 assert_eq!(Dp(2.0) * 3.0, Dp(6.0));
886 assert_eq!(Dp(6.0) / 3.0, Dp(2.0));
887 assert_eq!(Dp(6.0) / Dp(3.0), 2.0);
888 }
889
890 #[test]
891 fn dp_unspecified_semantics() {
892 assert!(!Dp::UNSPECIFIED.is_specified());
893 assert!(Dp(1.0).is_specified());
894 assert_eq!(Dp::UNSPECIFIED.take_or_else(|| Dp(4.0)), Dp(4.0));
895 assert!(Dp::UNSPECIFIED != Dp::UNSPECIFIED);
897 assert_eq!(
898 Dp::UNSPECIFIED.partial_cmp(&Dp(1.0)),
899 Some(std::cmp::Ordering::Equal)
900 );
901 }
902
903 #[test]
904 fn unit_ext_constructors() {
905 assert_eq!(16.0f32.dp(), Dp(16.0));
906 assert_eq!(10i32.dp(), Dp(10.0));
907 assert_eq!(14.0f32.sp(), Sp(14.0));
908 assert_eq!(2.0f32.px(), Px(2.0));
909 }
910
911 #[test]
912 fn std_from_conversions() {
913 assert_eq!(Dp::from(16.0f32), Dp(16.0));
914 assert_eq!(Dp::from(10i32), Dp(10.0));
915 assert_eq!(Sp::from(14.0f32), Sp(14.0));
916 assert_eq!(Px::from(2.0f32), Px(2.0));
917 assert_eq!(f32::from(Dp(3.0)), 3.0);
918 let v: Dp = 8.0f32.into();
919 assert_eq!(v, Dp(8.0));
920 }
921
922 #[test]
923 fn dp_rect_dimensions() {
924 let r = DpRect::new(Dp(0.0), Dp(0.0), Dp(10.0), Dp(20.0));
925 assert_eq!(r.width(), Dp(10.0));
926 assert_eq!(r.height(), Dp(20.0));
927 }
928
929 #[test]
930 fn lerp_midpoint() {
931 assert_eq!(lerp_dp(Dp(0.0), Dp(10.0), 0.5), Dp(5.0));
932 assert_eq!(lerp_sp(Sp(0.0), Sp(10.0), 0.5), Sp(10.0 * 0.5));
933 }
934}