1use std::marker::PhantomData;
25use std::ops::{Add, Mul, Sub};
26
27pub trait CoordinateSpace: Copy + Clone + private::Sealed {}
35
36mod private {
37 pub trait Sealed {}
38 impl Sealed for super::Logical {}
39 impl Sealed for super::Physical {}
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
48pub struct Logical;
49impl CoordinateSpace for Logical {}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
56pub struct Physical;
57impl CoordinateSpace for Physical {}
58
59#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
89pub struct ScaleFactor(pub f64);
90
91impl ScaleFactor {
92 #[inline]
99 pub const fn new(scale: f64) -> Self {
100 Self(scale)
102 }
103
104 #[inline]
121 pub fn try_new(scale: f64) -> Option<Self> {
122 if scale.is_finite() && scale > 0.0 {
123 Some(Self(scale))
124 } else {
125 None
126 }
127 }
128
129 #[inline]
131 pub fn is_valid(self) -> bool {
132 self.0.is_finite() && self.0 > 0.0
133 }
134
135 #[inline]
142 pub fn inverse(self) -> Self {
143 debug_assert!(
144 self.is_valid(),
145 "ScaleFactor::inverse() called on invalid scale factor: {}",
146 self.0
147 );
148 Self(1.0 / self.0)
149 }
150
151 #[inline]
153 pub fn as_f32(self) -> f32 {
154 self.0 as f32
155 }
156
157 #[inline]
159 pub fn as_f64(self) -> f64 {
160 self.0
161 }
162}
163
164impl Default for ScaleFactor {
165 fn default() -> Self {
166 Self(1.0)
167 }
168}
169
170impl From<f64> for ScaleFactor {
171 fn from(scale: f64) -> Self {
172 Self(scale)
173 }
174}
175
176impl From<f32> for ScaleFactor {
177 fn from(scale: f32) -> Self {
178 Self(scale as f64)
179 }
180}
181
182impl From<ScaleFactor> for f64 {
183 fn from(scale: ScaleFactor) -> Self {
184 scale.0
185 }
186}
187
188impl From<ScaleFactor> for f32 {
189 fn from(scale: ScaleFactor) -> Self {
190 scale.0 as f32
191 }
192}
193
194#[derive(Debug, Clone, Copy)]
212pub struct Size2D<T, S: CoordinateSpace> {
213 pub width: T,
214 pub height: T,
215 _marker: PhantomData<S>,
216}
217
218impl<T: PartialEq, S: CoordinateSpace> PartialEq for Size2D<T, S> {
219 fn eq(&self, other: &Self) -> bool {
220 self.width == other.width && self.height == other.height
221 }
222}
223
224impl<T: Eq, S: CoordinateSpace> Eq for Size2D<T, S> {}
225
226impl<T: Default, S: CoordinateSpace> Default for Size2D<T, S> {
227 fn default() -> Self {
228 Self {
229 width: T::default(),
230 height: T::default(),
231 _marker: PhantomData,
232 }
233 }
234}
235
236impl<T: std::hash::Hash, S: CoordinateSpace> std::hash::Hash for Size2D<T, S> {
237 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
238 self.width.hash(state);
239 self.height.hash(state);
240 }
241}
242
243impl<T, S: CoordinateSpace> Size2D<T, S> {
244 #[inline]
246 pub const fn new(width: T, height: T) -> Self {
247 Self {
248 width,
249 height,
250 _marker: PhantomData,
251 }
252 }
253
254 #[inline]
256 pub fn into_tuple(self) -> (T, T) {
257 (self.width, self.height)
258 }
259
260 #[inline]
262 pub fn from_tuple(tuple: (T, T)) -> Self {
263 Self::new(tuple.0, tuple.1)
264 }
265}
266
267impl<T: Copy, S: CoordinateSpace> Size2D<T, S> {
268 #[inline]
270 pub fn width(&self) -> T {
271 self.width
272 }
273
274 #[inline]
276 pub fn height(&self) -> T {
277 self.height
278 }
279}
280
281impl<T: Copy, S: CoordinateSpace> Size2D<T, S> {
282 #[inline]
284 pub fn cast<U>(self) -> Size2D<U, S>
285 where
286 T: Into<U>,
287 {
288 Size2D::new(self.width.into(), self.height.into())
289 }
290}
291
292impl<T, S: CoordinateSpace> Size2D<T, S>
293where
294 T: Copy + Into<f64>,
295{
296 #[inline]
298 pub fn to_f32(self) -> Size2D<f32, S> {
299 Size2D::new(self.width.into() as f32, self.height.into() as f32)
300 }
301
302 #[inline]
304 pub fn to_f64(self) -> Size2D<f64, S> {
305 Size2D::new(self.width.into(), self.height.into())
306 }
307}
308
309impl<T: Mul<Output = T> + Copy, S: CoordinateSpace> Mul<T> for Size2D<T, S> {
310 type Output = Self;
311
312 fn mul(self, rhs: T) -> Self::Output {
313 Self::new(self.width * rhs, self.height * rhs)
314 }
315}
316
317impl<T> Size2D<T, Logical>
319where
320 T: Copy + Into<f64>,
321{
322 #[inline]
324 pub fn to_physical(self, scale: ScaleFactor) -> PhysicalSize<u32> {
325 PhysicalSize::new(
326 (self.width.into() * scale.0).round() as u32,
327 (self.height.into() * scale.0).round() as u32,
328 )
329 }
330
331 #[inline]
333 pub fn to_physical_f32(self, scale: ScaleFactor) -> PhysicalSize<f32> {
334 PhysicalSize::new(
335 (self.width.into() * scale.0) as f32,
336 (self.height.into() * scale.0) as f32,
337 )
338 }
339
340 #[inline]
342 pub fn to_physical_f64(self, scale: ScaleFactor) -> PhysicalSize<f64> {
343 PhysicalSize::new(self.width.into() * scale.0, self.height.into() * scale.0)
344 }
345}
346
347impl<T> Size2D<T, Physical>
349where
350 T: Copy + Into<f64>,
351{
352 #[inline]
354 pub fn to_logical(self, scale: ScaleFactor) -> LogicalSize<f32> {
355 LogicalSize::new(
356 (self.width.into() / scale.0) as f32,
357 (self.height.into() / scale.0) as f32,
358 )
359 }
360
361 #[inline]
363 pub fn to_logical_f64(self, scale: ScaleFactor) -> LogicalSize<f64> {
364 LogicalSize::new(self.width.into() / scale.0, self.height.into() / scale.0)
365 }
366}
367
368pub type LogicalSize<T> = Size2D<T, Logical>;
370
371pub type PhysicalSize<T> = Size2D<T, Physical>;
373
374#[derive(Debug, Clone, Copy)]
382pub struct Position2D<T, S: CoordinateSpace> {
383 pub x: T,
384 pub y: T,
385 _marker: PhantomData<S>,
386}
387
388impl<T: PartialEq, S: CoordinateSpace> PartialEq for Position2D<T, S> {
389 fn eq(&self, other: &Self) -> bool {
390 self.x == other.x && self.y == other.y
391 }
392}
393
394impl<T: Eq, S: CoordinateSpace> Eq for Position2D<T, S> {}
395
396impl<T: Default, S: CoordinateSpace> Default for Position2D<T, S> {
397 fn default() -> Self {
398 Self {
399 x: T::default(),
400 y: T::default(),
401 _marker: PhantomData,
402 }
403 }
404}
405
406impl<T: std::hash::Hash, S: CoordinateSpace> std::hash::Hash for Position2D<T, S> {
407 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
408 self.x.hash(state);
409 self.y.hash(state);
410 }
411}
412
413impl<T, S: CoordinateSpace> Position2D<T, S> {
414 #[inline]
416 pub const fn new(x: T, y: T) -> Self {
417 Self {
418 x,
419 y,
420 _marker: PhantomData,
421 }
422 }
423
424 #[inline]
426 pub fn origin() -> Self
427 where
428 T: Default,
429 {
430 Self::default()
431 }
432
433 #[inline]
435 pub fn into_tuple(self) -> (T, T) {
436 (self.x, self.y)
437 }
438
439 #[inline]
441 pub fn from_tuple(tuple: (T, T)) -> Self {
442 Self::new(tuple.0, tuple.1)
443 }
444}
445
446impl<T: Copy, S: CoordinateSpace> Position2D<T, S> {
447 #[inline]
449 pub fn x(&self) -> T {
450 self.x
451 }
452
453 #[inline]
455 pub fn y(&self) -> T {
456 self.y
457 }
458}
459
460impl<T, S: CoordinateSpace> Position2D<T, S>
461where
462 T: Copy + Into<f64>,
463{
464 #[inline]
466 pub fn to_f32(self) -> Position2D<f32, S> {
467 Position2D::new(self.x.into() as f32, self.y.into() as f32)
468 }
469
470 #[inline]
472 pub fn to_f64(self) -> Position2D<f64, S> {
473 Position2D::new(self.x.into(), self.y.into())
474 }
475}
476
477impl<T: Mul<Output = T> + Copy, S: CoordinateSpace> Mul<T> for Position2D<T, S> {
478 type Output = Self;
479
480 fn mul(self, rhs: T) -> Self::Output {
481 Self::new(self.x * rhs, self.y * rhs)
482 }
483}
484
485impl<T: Add<Output = T>, S: CoordinateSpace> Add for Position2D<T, S> {
486 type Output = Self;
487
488 fn add(self, rhs: Self) -> Self::Output {
489 Self::new(self.x + rhs.x, self.y + rhs.y)
490 }
491}
492
493impl<T: Sub<Output = T>, S: CoordinateSpace> Sub for Position2D<T, S> {
494 type Output = Self;
495
496 fn sub(self, rhs: Self) -> Self::Output {
497 Self::new(self.x - rhs.x, self.y - rhs.y)
498 }
499}
500
501impl<T> Position2D<T, Logical>
503where
504 T: Copy + Into<f64>,
505{
506 #[inline]
508 pub fn to_physical(self, scale: ScaleFactor) -> PhysicalPosition<i32> {
509 PhysicalPosition::new(
510 (self.x.into() * scale.0).round() as i32,
511 (self.y.into() * scale.0).round() as i32,
512 )
513 }
514
515 #[inline]
517 pub fn to_physical_f32(self, scale: ScaleFactor) -> PhysicalPosition<f32> {
518 PhysicalPosition::new(
519 (self.x.into() * scale.0) as f32,
520 (self.y.into() * scale.0) as f32,
521 )
522 }
523
524 #[inline]
526 pub fn to_physical_f64(self, scale: ScaleFactor) -> PhysicalPosition<f64> {
527 PhysicalPosition::new(self.x.into() * scale.0, self.y.into() * scale.0)
528 }
529}
530
531impl<T> Position2D<T, Physical>
533where
534 T: Copy + Into<f64>,
535{
536 #[inline]
538 pub fn to_logical(self, scale: ScaleFactor) -> LogicalPosition<f32> {
539 LogicalPosition::new(
540 (self.x.into() / scale.0) as f32,
541 (self.y.into() / scale.0) as f32,
542 )
543 }
544
545 #[inline]
547 pub fn to_logical_f64(self, scale: ScaleFactor) -> LogicalPosition<f64> {
548 LogicalPosition::new(self.x.into() / scale.0, self.y.into() / scale.0)
549 }
550}
551
552pub type LogicalPosition<T> = Position2D<T, Logical>;
554
555pub type PhysicalPosition<T> = Position2D<T, Physical>;
557
558#[derive(Debug, Clone, Copy)]
566pub struct Rect2D<T, S: CoordinateSpace> {
567 pub x: T,
568 pub y: T,
569 pub width: T,
570 pub height: T,
571 _marker: PhantomData<S>,
572}
573
574impl<T: PartialEq, S: CoordinateSpace> PartialEq for Rect2D<T, S> {
575 fn eq(&self, other: &Self) -> bool {
576 self.x == other.x
577 && self.y == other.y
578 && self.width == other.width
579 && self.height == other.height
580 }
581}
582
583impl<T: Eq, S: CoordinateSpace> Eq for Rect2D<T, S> {}
584
585impl<T: Default, S: CoordinateSpace> Default for Rect2D<T, S> {
586 fn default() -> Self {
587 Self {
588 x: T::default(),
589 y: T::default(),
590 width: T::default(),
591 height: T::default(),
592 _marker: PhantomData,
593 }
594 }
595}
596
597impl<T: std::hash::Hash, S: CoordinateSpace> std::hash::Hash for Rect2D<T, S> {
598 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
599 self.x.hash(state);
600 self.y.hash(state);
601 self.width.hash(state);
602 self.height.hash(state);
603 }
604}
605
606impl<T, S: CoordinateSpace> Rect2D<T, S> {
607 #[inline]
609 pub const fn new(x: T, y: T, width: T, height: T) -> Self {
610 Self {
611 x,
612 y,
613 width,
614 height,
615 _marker: PhantomData,
616 }
617 }
618
619 #[inline]
621 pub fn from_position_size(position: Position2D<T, S>, size: Size2D<T, S>) -> Self {
622 Self::new(position.x, position.y, size.width, size.height)
623 }
624}
625
626impl<T: Copy, S: CoordinateSpace> Rect2D<T, S> {
627 #[inline]
629 pub fn position(&self) -> Position2D<T, S> {
630 Position2D::new(self.x, self.y)
631 }
632
633 #[inline]
635 pub fn size(&self) -> Size2D<T, S> {
636 Size2D::new(self.width, self.height)
637 }
638
639 #[inline]
641 pub fn x(&self) -> T {
642 self.x
643 }
644
645 #[inline]
647 pub fn y(&self) -> T {
648 self.y
649 }
650
651 #[inline]
653 pub fn width(&self) -> T {
654 self.width
655 }
656
657 #[inline]
659 pub fn height(&self) -> T {
660 self.height
661 }
662}
663
664impl<T, S: CoordinateSpace> Rect2D<T, S>
665where
666 T: Copy + Add<Output = T>,
667{
668 #[inline]
670 pub fn right(&self) -> T {
671 self.x + self.width
672 }
673
674 #[inline]
676 pub fn bottom(&self) -> T {
677 self.y + self.height
678 }
679}
680
681impl<T, S: CoordinateSpace> Rect2D<T, S>
682where
683 T: Copy + Into<f64>,
684{
685 #[inline]
687 pub fn to_f32(self) -> Rect2D<f32, S> {
688 Rect2D::new(
689 self.x.into() as f32,
690 self.y.into() as f32,
691 self.width.into() as f32,
692 self.height.into() as f32,
693 )
694 }
695
696 #[inline]
698 pub fn to_f64(self) -> Rect2D<f64, S> {
699 Rect2D::new(
700 self.x.into(),
701 self.y.into(),
702 self.width.into(),
703 self.height.into(),
704 )
705 }
706}
707
708impl<T, S: CoordinateSpace> Rect2D<T, S>
709where
710 T: Copy + PartialOrd + Add<Output = T>,
711{
712 #[inline]
714 pub fn contains(&self, point: Position2D<T, S>) -> bool {
715 point.x >= self.x
716 && point.x < self.x + self.width
717 && point.y >= self.y
718 && point.y < self.y + self.height
719 }
720}
721
722impl<T> Rect2D<T, Logical>
724where
725 T: Copy + Into<f64>,
726{
727 #[inline]
729 pub fn to_physical(self, scale: ScaleFactor) -> PhysicalRect<u32> {
730 PhysicalRect::new(
731 (self.x.into() * scale.0).round() as u32,
732 (self.y.into() * scale.0).round() as u32,
733 (self.width.into() * scale.0).round() as u32,
734 (self.height.into() * scale.0).round() as u32,
735 )
736 }
737
738 #[inline]
740 pub fn to_physical_f32(self, scale: ScaleFactor) -> PhysicalRect<f32> {
741 PhysicalRect::new(
742 (self.x.into() * scale.0) as f32,
743 (self.y.into() * scale.0) as f32,
744 (self.width.into() * scale.0) as f32,
745 (self.height.into() * scale.0) as f32,
746 )
747 }
748}
749
750impl<T> Rect2D<T, Physical>
752where
753 T: Copy + Into<f64>,
754{
755 #[inline]
757 pub fn to_logical(self, scale: ScaleFactor) -> LogicalRect<f32> {
758 LogicalRect::new(
759 (self.x.into() / scale.0) as f32,
760 (self.y.into() / scale.0) as f32,
761 (self.width.into() / scale.0) as f32,
762 (self.height.into() / scale.0) as f32,
763 )
764 }
765
766 #[inline]
768 pub fn to_logical_f64(self, scale: ScaleFactor) -> LogicalRect<f64> {
769 LogicalRect::new(
770 self.x.into() / scale.0,
771 self.y.into() / scale.0,
772 self.width.into() / scale.0,
773 self.height.into() / scale.0,
774 )
775 }
776}
777
778pub type LogicalRect<T> = Rect2D<T, Logical>;
780
781pub type PhysicalRect<T> = Rect2D<T, Physical>;
783
784#[cfg(feature = "winit")]
789mod winit_interop {
790 use super::*;
791
792 impl From<winit::dpi::PhysicalSize<u32>> for PhysicalSize<u32> {
793 fn from(size: winit::dpi::PhysicalSize<u32>) -> Self {
794 Self::new(size.width, size.height)
795 }
796 }
797
798 impl From<PhysicalSize<u32>> for winit::dpi::PhysicalSize<u32> {
799 fn from(size: PhysicalSize<u32>) -> Self {
800 Self::new(size.width, size.height)
801 }
802 }
803
804 impl From<winit::dpi::PhysicalSize<f32>> for PhysicalSize<f32> {
805 fn from(size: winit::dpi::PhysicalSize<f32>) -> Self {
806 Self::new(size.width, size.height)
807 }
808 }
809
810 impl From<PhysicalSize<f32>> for winit::dpi::PhysicalSize<f32> {
811 fn from(size: PhysicalSize<f32>) -> Self {
812 Self::new(size.width, size.height)
813 }
814 }
815
816 impl From<winit::dpi::LogicalSize<u32>> for LogicalSize<u32> {
817 fn from(size: winit::dpi::LogicalSize<u32>) -> Self {
818 Self::new(size.width, size.height)
819 }
820 }
821
822 impl From<LogicalSize<u32>> for winit::dpi::LogicalSize<u32> {
823 fn from(size: LogicalSize<u32>) -> Self {
824 Self::new(size.width, size.height)
825 }
826 }
827
828 impl From<winit::dpi::LogicalSize<f32>> for LogicalSize<f32> {
829 fn from(size: winit::dpi::LogicalSize<f32>) -> Self {
830 Self::new(size.width, size.height)
831 }
832 }
833
834 impl From<LogicalSize<f32>> for winit::dpi::LogicalSize<f32> {
835 fn from(size: LogicalSize<f32>) -> Self {
836 Self::new(size.width, size.height)
837 }
838 }
839
840 impl From<winit::dpi::PhysicalPosition<i32>> for PhysicalPosition<i32> {
841 fn from(pos: winit::dpi::PhysicalPosition<i32>) -> Self {
842 Self::new(pos.x, pos.y)
843 }
844 }
845
846 impl From<PhysicalPosition<i32>> for winit::dpi::PhysicalPosition<i32> {
847 fn from(pos: PhysicalPosition<i32>) -> Self {
848 Self::new(pos.x, pos.y)
849 }
850 }
851
852 impl From<winit::dpi::PhysicalPosition<f64>> for PhysicalPosition<f64> {
853 fn from(pos: winit::dpi::PhysicalPosition<f64>) -> Self {
854 Self::new(pos.x, pos.y)
855 }
856 }
857
858 impl From<PhysicalPosition<f64>> for winit::dpi::PhysicalPosition<f64> {
859 fn from(pos: PhysicalPosition<f64>) -> Self {
860 Self::new(pos.x, pos.y)
861 }
862 }
863
864 impl From<winit::dpi::LogicalPosition<f64>> for LogicalPosition<f64> {
865 fn from(pos: winit::dpi::LogicalPosition<f64>) -> Self {
866 Self::new(pos.x, pos.y)
867 }
868 }
869
870 impl From<LogicalPosition<f64>> for winit::dpi::LogicalPosition<f64> {
871 fn from(pos: LogicalPosition<f64>) -> Self {
872 Self::new(pos.x, pos.y)
873 }
874 }
875}
876
877#[derive(Debug, Clone, Copy, PartialEq, Default)]
888pub struct Size<T> {
889 pub width: T,
890 pub height: T,
891}
892
893impl<T> Size<T> {
894 #[inline]
896 pub const fn new(width: T, height: T) -> Self {
897 Self { width, height }
898 }
899}
900
901impl<T: Copy> Size<T> {
902 #[inline]
904 pub fn into_tuple(self) -> (T, T) {
905 (self.width, self.height)
906 }
907}
908
909impl<T: Mul<Output = T> + Copy> Mul<T> for Size<T> {
910 type Output = Self;
911
912 fn mul(self, rhs: T) -> Self::Output {
913 Self::new(self.width * rhs, self.height * rhs)
914 }
915}
916
917impl<T: Add<Output = T>> Add for Size<T> {
918 type Output = Self;
919
920 fn add(self, rhs: Self) -> Self::Output {
921 Self::new(self.width + rhs.width, self.height + rhs.height)
922 }
923}
924
925impl<T: Sub<Output = T>> Sub for Size<T> {
926 type Output = Self;
927
928 fn sub(self, rhs: Self) -> Self::Output {
929 Self::new(self.width - rhs.width, self.height - rhs.height)
930 }
931}
932
933impl<T, S: CoordinateSpace> From<Size2D<T, S>> for Size<T> {
935 fn from(size: Size2D<T, S>) -> Self {
936 Self::new(size.width, size.height)
937 }
938}
939
940impl<T, S: CoordinateSpace> From<Position2D<T, S>> for Pos<T> {
941 fn from(pos: Position2D<T, S>) -> Self {
942 Self::new(pos.x, pos.y)
943 }
944}
945
946impl<T, S: CoordinateSpace> From<Rect2D<T, S>> for Rect<T> {
947 fn from(rect: Rect2D<T, S>) -> Self {
948 Self::new(rect.x, rect.y, rect.width, rect.height)
949 }
950}
951
952#[derive(Debug, Clone, Copy, PartialEq, Default)]
957pub struct Pos<T> {
958 pub x: T,
959 pub y: T,
960}
961
962impl<T> Pos<T> {
963 #[inline]
965 pub const fn new(x: T, y: T) -> Self {
966 Self { x, y }
967 }
968}
969
970impl<T: Copy> Pos<T> {
971 #[inline]
973 pub fn into_tuple(self) -> (T, T) {
974 (self.x, self.y)
975 }
976}
977
978impl<T: Mul<Output = T> + Copy> Mul<T> for Pos<T> {
979 type Output = Self;
980
981 fn mul(self, rhs: T) -> Self::Output {
982 Self::new(self.x * rhs, self.y * rhs)
983 }
984}
985
986impl<T: Add<Output = T>> Add for Pos<T> {
987 type Output = Self;
988
989 fn add(self, rhs: Self) -> Self::Output {
990 Self::new(self.x + rhs.x, self.y + rhs.y)
991 }
992}
993
994impl<T: Sub<Output = T>> Sub for Pos<T> {
995 type Output = Self;
996
997 fn sub(self, rhs: Self) -> Self::Output {
998 Self::new(self.x - rhs.x, self.y - rhs.y)
999 }
1000}
1001
1002#[derive(Debug, Clone, Copy, PartialEq, Default)]
1007pub struct Rect<T> {
1008 pub x: T,
1009 pub y: T,
1010 pub width: T,
1011 pub height: T,
1012}
1013
1014impl<T> Rect<T> {
1015 #[inline]
1017 pub const fn new(x: T, y: T, width: T, height: T) -> Self {
1018 Self {
1019 x,
1020 y,
1021 width,
1022 height,
1023 }
1024 }
1025
1026 #[inline]
1028 pub fn from_position_size(pos: Pos<T>, size: Size<T>) -> Self {
1029 Self::new(pos.x, pos.y, size.width, size.height)
1030 }
1031
1032 #[inline]
1034 pub fn position(&self) -> Pos<T>
1035 where
1036 T: Copy,
1037 {
1038 Pos::new(self.x, self.y)
1039 }
1040
1041 #[inline]
1043 pub fn size(&self) -> Size<T>
1044 where
1045 T: Copy,
1046 {
1047 Size::new(self.width, self.height)
1048 }
1049}
1050
1051impl<T> Rect<T>
1052where
1053 T: Copy + PartialOrd + Add<Output = T>,
1054{
1055 #[inline]
1057 pub fn contains(&self, point: Pos<T>) -> bool {
1058 point.x >= self.x
1059 && point.x < self.x + self.width
1060 && point.y >= self.y
1061 && point.y < self.y + self.height
1062 }
1063}
1064
1065#[cfg(test)]
1070mod tests {
1071 use super::*;
1072
1073 #[test]
1074 fn test_logical_to_physical_size() {
1075 let logical = LogicalSize::new(100.0_f64, 50.0);
1076 let scale = ScaleFactor(2.0);
1077 let physical = logical.to_physical(scale);
1078 assert_eq!(physical.width, 200);
1079 assert_eq!(physical.height, 100);
1080 }
1081
1082 #[test]
1083 fn test_physical_to_logical_size() {
1084 let physical = PhysicalSize::new(200_u32, 100);
1085 let scale = ScaleFactor(2.0);
1086 let logical = physical.to_logical(scale);
1087 assert_eq!(logical.width, 100.0);
1088 assert_eq!(logical.height, 50.0);
1089 }
1090
1091 #[test]
1092 fn test_logical_to_physical_position() {
1093 let logical = LogicalPosition::new(50.0_f64, 25.0);
1094 let scale = ScaleFactor(2.0);
1095 let physical = logical.to_physical(scale);
1096 assert_eq!(physical.x, 100);
1097 assert_eq!(physical.y, 50);
1098 }
1099
1100 #[test]
1101 fn test_rect_contains() {
1102 let rect = LogicalRect::new(10.0_f64, 10.0, 100.0, 50.0);
1103 assert!(rect.contains(LogicalPosition::new(50.0, 30.0)));
1104 assert!(!rect.contains(LogicalPosition::new(5.0, 30.0)));
1105 assert!(!rect.contains(LogicalPosition::new(50.0, 5.0)));
1106 }
1107
1108 #[test]
1109 fn test_scale_factor_inverse() {
1110 let scale = ScaleFactor(2.0);
1111 let inv = scale.inverse();
1112 assert_eq!(inv.0, 0.5);
1113 }
1114
1115 #[test]
1116 fn test_size_multiplication() {
1117 let size = LogicalSize::new(10.0_f32, 20.0);
1118 let scaled = size * 2.0;
1119 assert_eq!(scaled.width, 20.0);
1120 assert_eq!(scaled.height, 40.0);
1121 }
1122
1123 #[test]
1124 fn test_position_arithmetic() {
1125 let a = LogicalPosition::new(10.0_f32, 20.0);
1126 let b = LogicalPosition::new(5.0, 10.0);
1127 let sum = a + b;
1128 let diff = a - b;
1129 assert_eq!(sum.x, 15.0);
1130 assert_eq!(sum.y, 30.0);
1131 assert_eq!(diff.x, 5.0);
1132 assert_eq!(diff.y, 10.0);
1133 }
1134
1135 #[test]
1136 fn test_rect_from_position_size() {
1137 let pos = LogicalPosition::new(10.0_f32, 20.0);
1138 let size = LogicalSize::new(100.0_f32, 50.0);
1139 let rect = LogicalRect::from_position_size(pos, size);
1140 assert_eq!(rect.x, 10.0);
1141 assert_eq!(rect.y, 20.0);
1142 assert_eq!(rect.width, 100.0);
1143 assert_eq!(rect.height, 50.0);
1144 }
1145}