tessera_ui/
px.rs

1//! Physical pixel coordinate system for Tessera UI framework.
2//!
3//! This module provides types and operations for working with physical pixel coordinates,
4//! positions, and sizes. Physical pixels represent actual screen pixels and are used
5//! internally by the rendering system.
6//!
7//! # Key Types
8//!
9//! - [`Px`] - A single physical pixel coordinate value that supports negative values for scrolling
10//! - [`PxPosition`] - A 2D position in physical pixel space (x, y coordinates)
11//! - [`PxSize`] - A 2D size in physical pixel space (width, height dimensions)
12//!
13//! # Coordinate System
14//!
15//! The coordinate system uses:
16//! - Origin (0, 0) at the top-left corner
17//! - X-axis increases to the right
18//! - Y-axis increases downward
19//! - Negative coordinates are supported for scrolling and off-screen positioning
20//!
21//! # Conversion
22//!
23//! Physical pixels can be converted to and from density-independent pixels ([`Dp`]):
24//! - Use [`Px::from_dp`] to convert from Dp to Px
25//! - Use [`Px::to_dp`] to convert from Px to Dp
26//!
27//! # Example
28//!
29//! ```
30//! use tessera_ui::px::{Px, PxPosition, PxSize};
31//! use tessera_ui::dp::Dp;
32//!
33//! // Create pixel values
34//! let x = Px::new(100);
35//! let y = Px::new(200);
36//!
37//! // Create a position
38//! let position = PxPosition::new(x, y);
39//!
40//! // Create a size
41//! let size = PxSize::new(Px::new(300), Px::new(400));
42//!
43//! // Arithmetic operations
44//! let offset_position = position.offset(Px::new(10), Px::new(-5));
45//!
46//! // Convert between Dp and Px
47//! let dp_value = Dp::new(16.0);
48//! let px_value = Px::from_dp(dp_value);
49//! ```
50
51use std::ops::{AddAssign, Neg};
52
53use crate::dp::{Dp, SCALE_FACTOR};
54
55/// A physical pixel coordinate value.
56///
57/// This type represents a single coordinate value in physical pixel space. Physical pixels
58/// correspond directly to screen pixels and are used internally by the rendering system.
59/// Unlike density-independent pixels ([`Dp`]), physical pixels are not scaled based on
60/// screen density.
61///
62/// # Features
63///
64/// - Supports negative values for scrolling and off-screen positioning
65/// - Provides arithmetic operations (addition, subtraction, multiplication, division)
66/// - Includes saturating arithmetic to prevent overflow
67/// - Converts to/from density-independent pixels ([`Dp`])
68/// - Converts to/from floating-point values with overflow protection
69///
70/// # Examples
71///
72/// ```
73/// use tessera_ui::px::Px;
74///
75/// // Create pixel values
76/// let px1 = Px::new(100);
77/// let px2 = Px::new(-50); // Negative values supported
78///
79/// // Arithmetic operations
80/// let sum = px1 + px2; // Px(50)
81/// let doubled = px1 * 2; // Px(200)
82///
83/// // Saturating arithmetic prevents overflow
84/// let max_px = Px::new(i32::MAX);
85/// let safe_add = max_px.saturating_add(Px::new(1)); // Still Px(i32::MAX)
86///
87/// // Convert to absolute value for rendering
88/// let abs_value = Px::new(-10).abs(); // 0 (negative becomes 0)
89/// ```
90#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
91pub struct Px(pub i32);
92
93impl Px {
94    /// A constant representing zero pixels.
95    pub const ZERO: Self = Self(0);
96
97    /// A constant representing the maximum possible pixel value.
98    pub const MAX: Self = Self(i32::MAX);
99
100    /// Returns the raw i32 value.
101    ///
102    /// This provides direct access to the underlying integer value.
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// use tessera_ui::px::Px;
108    ///
109    /// let px = Px::new(42);
110    /// assert_eq!(px.raw(), 42);
111    /// ```
112    pub fn raw(self) -> i32 {
113        self.0
114    }
115
116    /// Creates a new `Px` instance from an i32 value.
117    ///
118    /// # Arguments
119    ///
120    /// * `value` - The pixel value as an i32. Negative values are allowed.
121    ///
122    /// # Examples
123    ///
124    /// ```
125    /// use tessera_ui::px::Px;
126    ///
127    /// let positive = Px::new(100);
128    /// let negative = Px::new(-50);
129    /// let zero = Px::new(0);
130    /// ```
131    pub const fn new(value: i32) -> Self {
132        Px(value)
133    }
134
135    /// Converts from density-independent pixels ([`Dp`]) to physical pixels.
136    ///
137    /// This conversion uses the current scale factor to determine how many physical
138    /// pixels correspond to the given Dp value. The scale factor is typically
139    /// determined by the screen's pixel density.
140    ///
141    /// # Arguments
142    ///
143    /// * `dp` - The density-independent pixel value to convert
144    ///
145    /// # Examples
146    ///
147    /// ```
148    /// use tessera_ui::px::Px;
149    /// use tessera_ui::dp::Dp;
150    ///
151    /// let dp_value = Dp::new(16.0);
152    /// let px_value = Px::from_dp(dp_value);
153    /// ```
154    pub fn from_dp(dp: Dp) -> Self {
155        Px(dp.to_pixels_f64() as i32)
156    }
157
158    /// Converts from physical pixels to density-independent pixels ([`Dp`]).
159    ///
160    /// This conversion uses the current scale factor to determine the Dp value
161    /// that corresponds to this physical pixel value.
162    ///
163    /// # Examples
164    ///
165    /// ```
166    /// use tessera_ui::px::Px;
167    ///
168    /// let px_value = Px::new(32);
169    /// let dp_value = px_value.to_dp();
170    /// ```
171    pub fn to_dp(self) -> Dp {
172        let scale_factor = SCALE_FACTOR.get().map(|lock| *lock.read()).unwrap_or(1.0);
173        Dp((self.0 as f64) / scale_factor)
174    }
175
176    /// Returns the absolute value as a u32, clamping negative values to 0.
177    ///
178    /// This method is primarily used for coordinate conversion during rendering,
179    /// where negative coordinates need to be handled appropriately.
180    ///
181    /// # Examples
182    ///
183    /// ```
184    /// use tessera_ui::px::Px;
185    ///
186    /// assert_eq!(Px::new(10).abs(), 10);
187    /// assert_eq!(Px::new(-5).abs(), 0);
188    /// assert_eq!(Px::new(0).abs(), 0);
189    /// ```
190    pub fn abs(self) -> u32 {
191        self.0.max(0) as u32
192    }
193
194    /// Converts the pixel value to f32.
195    ///
196    /// # Examples
197    ///
198    /// ```
199    /// use tessera_ui::px::Px;
200    ///
201    /// let px = Px::new(42);
202    /// assert_eq!(px.to_f32(), 42.0);
203    /// ```
204    pub fn to_f32(self) -> f32 {
205        self.0 as f32
206    }
207
208    /// Creates a `Px` from an f32 value.
209    ///
210    /// # Panics
211    ///
212    /// This function may panic on overflow in debug builds when the f32 value
213    /// cannot be represented as an i32.
214    ///
215    /// # Examples
216    ///
217    /// ```
218    /// use tessera_ui::px::Px;
219    ///
220    /// let px = Px::from_f32(42.7);
221    /// assert_eq!(px.raw(), 42);
222    /// ```
223    pub fn from_f32(value: f32) -> Self {
224        Px(value as i32)
225    }
226
227    /// Creates a `Px` from an f32 value, saturating at the numeric bounds instead of overflowing.
228    ///
229    /// This is the safe alternative to [`from_f32`](Self::from_f32) that handles overflow
230    /// by clamping the value to the valid i32 range.
231    ///
232    /// # Examples
233    ///
234    /// ```
235    /// use tessera_ui::px::Px;
236    ///
237    /// let normal = Px::saturating_from_f32(42.7);
238    /// assert_eq!(normal.raw(), 42);
239    ///
240    /// let max_val = Px::saturating_from_f32(f32::MAX);
241    /// assert_eq!(max_val.raw(), i32::MAX);
242    ///
243    /// let min_val = Px::saturating_from_f32(f32::MIN);
244    /// assert_eq!(min_val.raw(), i32::MIN);
245    /// ```
246    pub fn saturating_from_f32(value: f32) -> Self {
247        let clamped_value = value.clamp(i32::MIN as f32, i32::MAX as f32);
248        Px(clamped_value as i32)
249    }
250
251    /// Saturating integer addition.
252    ///
253    /// Computes `self + rhs`, saturating at the numeric bounds instead of overflowing.
254    /// This prevents integer overflow by clamping the result to the valid i32 range.
255    ///
256    /// # Arguments
257    ///
258    /// * `rhs` - The right-hand side value to add
259    ///
260    /// # Examples
261    ///
262    /// ```
263    /// use tessera_ui::px::Px;
264    ///
265    /// let a = Px::new(10);
266    /// let b = Px::new(5);
267    /// assert_eq!(a.saturating_add(b), Px::new(15));
268    ///
269    /// // Prevents overflow
270    /// let max = Px::new(i32::MAX);
271    /// assert_eq!(max.saturating_add(Px::new(1)), max);
272    /// ```
273    pub fn saturating_add(self, rhs: Self) -> Self {
274        Px(self.0.saturating_add(rhs.0))
275    }
276
277    /// Saturating integer subtraction.
278    ///
279    /// Computes `self - rhs`, saturating at the numeric bounds instead of overflowing.
280    /// This prevents integer underflow by clamping the result to the valid i32 range.
281    ///
282    /// # Arguments
283    ///
284    /// * `rhs` - The right-hand side value to subtract
285    ///
286    /// # Examples
287    ///
288    /// ```
289    /// use tessera_ui::px::Px;
290    ///
291    /// let a = Px::new(10);
292    /// let b = Px::new(5);
293    /// assert_eq!(a.saturating_sub(b), Px::new(5));
294    ///
295    /// // Prevents underflow
296    /// let min = Px::new(i32::MIN);
297    /// assert_eq!(min.saturating_sub(Px::new(1)), min);
298    /// ```
299    pub fn saturating_sub(self, rhs: Self) -> Self {
300        Px(self.0.saturating_sub(rhs.0))
301    }
302}
303
304/// A 2D position in physical pixel space.
305///
306/// This type represents a position with x and y coordinates in physical pixel space.
307/// Physical pixels correspond directly to screen pixels and are used internally
308/// by the rendering system.
309///
310/// # Coordinate System
311///
312/// - Origin (0, 0) is at the top-left corner
313/// - X-axis increases to the right
314/// - Y-axis increases downward
315/// - Negative coordinates are supported for scrolling and off-screen positioning
316///
317/// # Examples
318///
319/// ```
320/// use tessera_ui::px::{Px, PxPosition};
321///
322/// // Create a position
323/// let position = PxPosition::new(Px::new(100), Px::new(200));
324///
325/// // Offset the position
326/// let offset_position = position.offset(Px::new(10), Px::new(-5));
327///
328/// // Calculate distance between positions
329/// let other_position = PxPosition::new(Px::new(103), Px::new(196));
330/// let distance = position.distance_to(other_position);
331///
332/// // Arithmetic operations
333/// let sum = position + other_position;
334/// let diff = position - other_position;
335/// ```
336#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
337pub struct PxPosition {
338    /// The x-coordinate in physical pixels
339    pub x: Px,
340    /// The y-coordinate in physical pixels
341    pub y: Px,
342}
343
344impl PxPosition {
345    /// A constant representing the zero position (0, 0).
346    pub const ZERO: Self = Self { x: Px(0), y: Px(0) };
347
348    /// Creates a new position from x and y coordinates.
349    ///
350    /// # Arguments
351    ///
352    /// * `x` - The x-coordinate in physical pixels
353    /// * `y` - The y-coordinate in physical pixels
354    ///
355    /// # Examples
356    ///
357    /// ```
358    /// use tessera_ui::px::{Px, PxPosition};
359    ///
360    /// let position = PxPosition::new(Px::new(100), Px::new(200));
361    /// assert_eq!(position.x, Px::new(100));
362    /// assert_eq!(position.y, Px::new(200));
363    /// ```
364    pub const fn new(x: Px, y: Px) -> Self {
365        Self { x, y }
366    }
367
368    /// Offsets the position by the given deltas.
369    ///
370    /// # Panics
371    ///
372    /// This function may panic on overflow in debug builds.
373    ///
374    /// # Arguments
375    ///
376    /// * `dx` - The x-axis offset in physical pixels
377    /// * `dy` - The y-axis offset in physical pixels
378    ///
379    /// # Examples
380    ///
381    /// ```
382    /// use tessera_ui::px::{Px, PxPosition};
383    ///
384    /// let position = PxPosition::new(Px::new(10), Px::new(20));
385    /// let offset_position = position.offset(Px::new(5), Px::new(-3));
386    /// assert_eq!(offset_position, PxPosition::new(Px::new(15), Px::new(17)));
387    /// ```
388    pub fn offset(self, dx: Px, dy: Px) -> Self {
389        Self {
390            x: self.x + dx,
391            y: self.y + dy,
392        }
393    }
394
395    /// Offsets the position with saturating arithmetic.
396    ///
397    /// This prevents overflow by clamping the result to the valid coordinate range.
398    ///
399    /// # Arguments
400    ///
401    /// * `dx` - The x-axis offset in physical pixels
402    /// * `dy` - The y-axis offset in physical pixels
403    ///
404    /// # Examples
405    ///
406    /// ```
407    /// use tessera_ui::px::{Px, PxPosition};
408    ///
409    /// let position = PxPosition::new(Px::new(10), Px::new(20));
410    /// let offset_position = position.saturating_offset(Px::new(5), Px::new(-3));
411    /// assert_eq!(offset_position, PxPosition::new(Px::new(15), Px::new(17)));
412    ///
413    /// // Prevents overflow
414    /// let max_position = PxPosition::new(Px::new(i32::MAX), Px::new(i32::MAX));
415    /// let safe_offset = max_position.saturating_offset(Px::new(1), Px::new(1));
416    /// assert_eq!(safe_offset, max_position);
417    /// ```
418    pub fn saturating_offset(self, dx: Px, dy: Px) -> Self {
419        Self {
420            x: self.x.saturating_add(dx),
421            y: self.y.saturating_add(dy),
422        }
423    }
424
425    /// Calculates the Euclidean distance to another position.
426    ///
427    /// # Arguments
428    ///
429    /// * `other` - The other position to calculate distance to
430    ///
431    /// # Returns
432    ///
433    /// The distance as a floating-point value
434    ///
435    /// # Examples
436    ///
437    /// ```
438    /// use tessera_ui::px::{Px, PxPosition};
439    ///
440    /// let pos1 = PxPosition::new(Px::new(0), Px::new(0));
441    /// let pos2 = PxPosition::new(Px::new(3), Px::new(4));
442    /// assert_eq!(pos1.distance_to(pos2), 5.0);
443    /// ```
444    pub fn distance_to(self, other: Self) -> f32 {
445        let dx = (self.x.0 - other.x.0) as f32;
446        let dy = (self.y.0 - other.y.0) as f32;
447        (dx * dx + dy * dy).sqrt()
448    }
449
450    /// Converts the position to a 2D f32 array.
451    ///
452    /// # Returns
453    ///
454    /// An array `[x, y]` where both coordinates are converted to f32
455    ///
456    /// # Examples
457    ///
458    /// ```
459    /// use tessera_ui::px::{Px, PxPosition};
460    ///
461    /// let position = PxPosition::new(Px::new(10), Px::new(20));
462    /// assert_eq!(position.to_f32_arr2(), [10.0, 20.0]);
463    /// ```
464    pub fn to_f32_arr2(self) -> [f32; 2] {
465        [self.x.0 as f32, self.y.0 as f32]
466    }
467
468    /// Converts the position to a 3D f32 array with z=0.
469    ///
470    /// # Returns
471    ///
472    /// An array `[x, y, 0.0]` where x and y are converted to f32 and z is 0.0
473    ///
474    /// # Examples
475    ///
476    /// ```
477    /// use tessera_ui::px::{Px, PxPosition};
478    ///
479    /// let position = PxPosition::new(Px::new(10), Px::new(20));
480    /// assert_eq!(position.to_f32_arr3(), [10.0, 20.0, 0.0]);
481    /// ```
482    pub fn to_f32_arr3(self) -> [f32; 3] {
483        [self.x.0 as f32, self.y.0 as f32, 0.0]
484    }
485
486    /// Creates a position from a 2D f32 array.
487    ///
488    /// # Arguments
489    ///
490    /// * `arr` - An array `[x, y]` where both values will be converted to i32
491    ///
492    /// # Examples
493    ///
494    /// ```
495    /// use tessera_ui::px::{Px, PxPosition};
496    ///
497    /// let position = PxPosition::from_f32_arr2([10.5, 20.7]);
498    /// assert_eq!(position, PxPosition::new(Px::new(10), Px::new(20)));
499    /// ```
500    pub fn from_f32_arr2(arr: [f32; 2]) -> Self {
501        Self {
502            x: Px::new(arr[0] as i32),
503            y: Px::new(arr[1] as i32),
504        }
505    }
506
507    /// Creates a position from a 3D f32 array, ignoring the z component.
508    ///
509    /// # Arguments
510    ///
511    /// * `arr` - An array `[x, y, z]` where only x and y are used
512    ///
513    /// # Examples
514    ///
515    /// ```
516    /// use tessera_ui::px::{Px, PxPosition};
517    ///
518    /// let position = PxPosition::from_f32_arr3([10.5, 20.7, 30.9]);
519    /// assert_eq!(position, PxPosition::new(Px::new(10), Px::new(20)));
520    /// ```
521    pub fn from_f32_arr3(arr: [f32; 3]) -> Self {
522        Self {
523            x: Px::new(arr[0] as i32),
524            y: Px::new(arr[1] as i32),
525        }
526    }
527
528    /// Converts the position to a 2D f64 array.
529    ///
530    /// # Returns
531    ///
532    /// An array `[x, y]` where both coordinates are converted to f64
533    ///
534    /// # Examples
535    ///
536    /// ```
537    /// use tessera_ui::px::{Px, PxPosition};
538    ///
539    /// let position = PxPosition::new(Px::new(10), Px::new(20));
540    /// assert_eq!(position.to_f64_arr2(), [10.0, 20.0]);
541    /// ```
542    pub fn to_f64_arr2(self) -> [f64; 2] {
543        [self.x.0 as f64, self.y.0 as f64]
544    }
545
546    /// Converts the position to a 3D f64 array with z=0.
547    ///
548    /// # Returns
549    ///
550    /// An array `[x, y, 0.0]` where x and y are converted to f64 and z is 0.0
551    ///
552    /// # Examples
553    ///
554    /// ```
555    /// use tessera_ui::px::{Px, PxPosition};
556    ///
557    /// let position = PxPosition::new(Px::new(10), Px::new(20));
558    /// assert_eq!(position.to_f64_arr3(), [10.0, 20.0, 0.0]);
559    /// ```
560    pub fn to_f64_arr3(self) -> [f64; 3] {
561        [self.x.0 as f64, self.y.0 as f64, 0.0]
562    }
563
564    /// Creates a position from a 2D f64 array.
565    ///
566    /// # Arguments
567    ///
568    /// * `arr` - An array `[x, y]` where both values will be converted to i32
569    ///
570    /// # Examples
571    ///
572    /// ```
573    /// use tessera_ui::px::{Px, PxPosition};
574    ///
575    /// let position = PxPosition::from_f64_arr2([10.5, 20.7]);
576    /// assert_eq!(position, PxPosition::new(Px::new(10), Px::new(20)));
577    /// ```
578    pub fn from_f64_arr2(arr: [f64; 2]) -> Self {
579        Self {
580            x: Px::new(arr[0] as i32),
581            y: Px::new(arr[1] as i32),
582        }
583    }
584
585    /// Creates a position from a 3D f64 array, ignoring the z component.
586    ///
587    /// # Arguments
588    ///
589    /// * `arr` - An array `[x, y, z]` where only x and y are used
590    ///
591    /// # Examples
592    ///
593    /// ```
594    /// use tessera_ui::px::{Px, PxPosition};
595    ///
596    /// let position = PxPosition::from_f64_arr3([10.5, 20.7, 30.9]);
597    /// assert_eq!(position, PxPosition::new(Px::new(10), Px::new(20)));
598    /// ```
599    pub fn from_f64_arr3(arr: [f64; 3]) -> Self {
600        Self {
601            x: Px::new(arr[0] as i32),
602            y: Px::new(arr[1] as i32),
603        }
604    }
605}
606
607/// A 2D size in physical pixel space.
608///
609/// This type represents dimensions (width and height) in physical pixel space.
610/// Physical pixels correspond directly to screen pixels and are used internally
611/// by the rendering system.
612///
613/// # Examples
614///
615/// ```
616/// use tessera_ui::px::{Px, PxSize};
617///
618/// // Create a size
619/// let size = PxSize::new(Px::new(300), Px::new(200));
620///
621/// // Convert to array formats for graphics APIs
622/// let f32_array = size.to_f32_arr2();
623/// assert_eq!(f32_array, [300.0, 200.0]);
624///
625/// // Create from array
626/// let from_array = PxSize::from([Px::new(400), Px::new(300)]);
627/// ```
628#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
629pub struct PxSize {
630    /// The width in physical pixels
631    pub width: Px,
632    /// The height in physical pixels
633    pub height: Px,
634}
635
636impl PxSize {
637    /// A constant representing zero size (0×0).
638    pub const ZERO: Self = Self {
639        width: Px(0),
640        height: Px(0),
641    };
642
643    /// Creates a new size from width and height.
644    ///
645    /// # Arguments
646    ///
647    /// * `width` - The width in physical pixels
648    /// * `height` - The height in physical pixels
649    ///
650    /// # Examples
651    ///
652    /// ```
653    /// use tessera_ui::px::{Px, PxSize};
654    ///
655    /// let size = PxSize::new(Px::new(300), Px::new(200));
656    /// assert_eq!(size.width, Px::new(300));
657    /// assert_eq!(size.height, Px::new(200));
658    /// ```
659    pub const fn new(width: Px, height: Px) -> Self {
660        Self { width, height }
661    }
662
663    /// Converts the size to a 2D f32 array.
664    ///
665    /// This is useful for interfacing with graphics APIs that expect
666    /// floating-point size values.
667    ///
668    /// # Returns
669    ///
670    /// An array `[width, height]` where both dimensions are converted to f32
671    ///
672    /// # Examples
673    ///
674    /// ```
675    /// use tessera_ui::px::{Px, PxSize};
676    ///
677    /// let size = PxSize::new(Px::new(300), Px::new(200));
678    /// assert_eq!(size.to_f32_arr2(), [300.0, 200.0]);
679    /// ```
680    pub fn to_f32_arr2(self) -> [f32; 2] {
681        [self.width.0 as f32, self.height.0 as f32]
682    }
683}
684
685impl From<[Px; 2]> for PxSize {
686    fn from(size: [Px; 2]) -> Self {
687        Self {
688            width: size[0],
689            height: size[1],
690        }
691    }
692}
693
694impl From<PxSize> for winit::dpi::PhysicalSize<i32> {
695    fn from(size: PxSize) -> Self {
696        winit::dpi::PhysicalSize {
697            width: size.width.raw(),
698            height: size.height.raw(),
699        }
700    }
701}
702
703impl From<winit::dpi::PhysicalSize<u32>> for PxSize {
704    fn from(size: winit::dpi::PhysicalSize<u32>) -> Self {
705        Self {
706            width: Px(size.width as i32),
707            height: Px(size.height as i32),
708        }
709    }
710}
711
712impl From<crate::component_tree::ComputedData> for PxSize {
713    fn from(data: crate::component_tree::ComputedData) -> Self {
714        Self {
715            width: data.width,
716            height: data.height,
717        }
718    }
719}
720
721impl From<PxSize> for winit::dpi::Size {
722    fn from(size: PxSize) -> Self {
723        winit::dpi::PhysicalSize::from(size).into()
724    }
725}
726
727impl std::ops::Add for Px {
728    type Output = Self;
729
730    fn add(self, rhs: Self) -> Self::Output {
731        Px(self.0 + rhs.0)
732    }
733}
734
735impl Neg for Px {
736    type Output = Self;
737
738    fn neg(self) -> Self::Output {
739        Px::new(-self.0)
740    }
741}
742
743impl std::ops::Sub for Px {
744    type Output = Self;
745
746    fn sub(self, rhs: Self) -> Self::Output {
747        Px(self.0 - rhs.0)
748    }
749}
750
751impl std::ops::Mul<i32> for Px {
752    type Output = Self;
753
754    fn mul(self, rhs: i32) -> Self::Output {
755        Px(self.0 * rhs)
756    }
757}
758
759impl std::ops::Div<i32> for Px {
760    type Output = Self;
761
762    fn div(self, rhs: i32) -> Self::Output {
763        Px(self.0 / rhs)
764    }
765}
766
767impl From<i32> for Px {
768    fn from(value: i32) -> Self {
769        Px(value)
770    }
771}
772
773impl From<u32> for Px {
774    fn from(value: u32) -> Self {
775        Px(value as i32)
776    }
777}
778
779impl From<Dp> for Px {
780    fn from(dp: Dp) -> Self {
781        Px::from_dp(dp)
782    }
783}
784
785impl From<PxPosition> for winit::dpi::PhysicalPosition<i32> {
786    fn from(pos: PxPosition) -> Self {
787        winit::dpi::PhysicalPosition {
788            x: pos.x.0,
789            y: pos.y.0,
790        }
791    }
792}
793
794impl From<PxPosition> for winit::dpi::Position {
795    fn from(pos: PxPosition) -> Self {
796        winit::dpi::PhysicalPosition::from(pos).into()
797    }
798}
799
800impl AddAssign for Px {
801    fn add_assign(&mut self, rhs: Self) {
802        self.0 += rhs.0;
803    }
804}
805
806// Arithmetic operations support - PxPosition
807impl std::ops::Add for PxPosition {
808    type Output = Self;
809
810    fn add(self, rhs: Self) -> Self::Output {
811        PxPosition {
812            x: self.x + rhs.x,
813            y: self.y + rhs.y,
814        }
815    }
816}
817
818impl std::ops::Sub for PxPosition {
819    type Output = Self;
820
821    fn sub(self, rhs: Self) -> Self::Output {
822        PxPosition {
823            x: self.x - rhs.x,
824            y: self.y - rhs.y,
825        }
826    }
827}
828
829// Type conversion implementations
830impl From<[i32; 2]> for PxPosition {
831    fn from(pos: [i32; 2]) -> Self {
832        PxPosition {
833            x: Px(pos[0]),
834            y: Px(pos[1]),
835        }
836    }
837}
838
839impl From<PxPosition> for [i32; 2] {
840    fn from(pos: PxPosition) -> Self {
841        [pos.x.0, pos.y.0]
842    }
843}
844
845impl From<[u32; 2]> for PxPosition {
846    fn from(pos: [u32; 2]) -> Self {
847        PxPosition {
848            x: Px(pos[0] as i32),
849            y: Px(pos[1] as i32),
850        }
851    }
852}
853
854impl From<PxPosition> for [u32; 2] {
855    fn from(pos: PxPosition) -> Self {
856        [pos.x.abs(), pos.y.abs()]
857    }
858}
859
860impl From<[Px; 2]> for PxPosition {
861    fn from(pos: [Px; 2]) -> Self {
862        PxPosition {
863            x: pos[0],
864            y: pos[1],
865        }
866    }
867}
868
869impl From<PxPosition> for [Px; 2] {
870    fn from(pos: PxPosition) -> Self {
871        [pos.x, pos.y]
872    }
873}
874
875#[cfg(test)]
876mod tests {
877    use super::*;
878
879    #[test]
880    fn test_px_creation() {
881        let px = Px::new(42);
882        assert_eq!(px.0, 42);
883
884        let px_neg = Px::new(-10);
885        assert_eq!(px_neg.0, -10);
886    }
887
888    #[test]
889    fn test_px_arithmetic() {
890        let a = Px(10);
891        let b = Px(5);
892
893        assert_eq!(a + b, Px(15));
894        assert_eq!(a - b, Px(5));
895        assert_eq!(a * 2, Px(20));
896        assert_eq!(a / 2, Px(5));
897    }
898
899    #[test]
900    fn test_px_saturating_arithmetic() {
901        let max = Px(i32::MAX);
902        let min = Px(i32::MIN);
903        assert_eq!(max.saturating_add(Px(1)), max);
904        assert_eq!(min.saturating_sub(Px(1)), min);
905    }
906
907    #[test]
908    fn test_saturating_from_f32() {
909        assert_eq!(Px::saturating_from_f32(f32::MAX), Px(i32::MAX));
910        assert_eq!(Px::saturating_from_f32(f32::MIN), Px(i32::MIN));
911        assert_eq!(Px::saturating_from_f32(100.5), Px(100));
912        assert_eq!(Px::saturating_from_f32(-100.5), Px(-100));
913    }
914
915    #[test]
916    fn test_px_abs() {
917        assert_eq!(Px(10).abs(), 10);
918        assert_eq!(Px(-5).abs(), 0);
919        assert_eq!(Px(0).abs(), 0);
920    }
921
922    #[test]
923    fn test_px_position() {
924        let pos = PxPosition::new(Px(10), Px(-5));
925        assert_eq!(pos.x, Px(10));
926        assert_eq!(pos.y, Px(-5));
927
928        let offset_pos = pos.offset(Px(2), Px(3));
929        assert_eq!(offset_pos, PxPosition::new(Px(12), Px(-2)));
930    }
931
932    #[test]
933    fn test_px_position_arithmetic() {
934        let pos1 = PxPosition::new(Px(10), Px(20));
935        let pos2 = PxPosition::new(Px(5), Px(15));
936
937        let sum = pos1 + pos2;
938        assert_eq!(sum, PxPosition::new(Px(15), Px(35)));
939
940        let diff = pos1 - pos2;
941        assert_eq!(diff, PxPosition::new(Px(5), Px(5)));
942    }
943
944    #[test]
945    fn test_px_position_conversions() {
946        let i32_pos: [i32; 2] = [10, -5];
947        let px_pos: PxPosition = i32_pos.into();
948        let back_to_i32: [i32; 2] = px_pos.into();
949        assert_eq!(i32_pos, back_to_i32);
950
951        let u32_pos: [u32; 2] = [10, 5];
952        let px_from_u32: PxPosition = u32_pos.into();
953        let back_to_u32: [u32; 2] = px_from_u32.into();
954        assert_eq!(u32_pos, back_to_u32);
955    }
956
957    #[test]
958    fn test_distance() {
959        let pos1 = PxPosition::new(Px(0), Px(0));
960        let pos2 = PxPosition::new(Px(3), Px(4));
961        assert_eq!(pos1.distance_to(pos2), 5.0);
962    }
963}