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(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(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
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(), 5);
188    /// assert_eq!(Px::new(0).abs(), 0);
189    /// ```
190    pub fn abs(self) -> u32 {
191        self.0.unsigned_abs()
192    }
193
194    /// Returns only the positive value, or zero if negative.
195    ///
196    /// This is useful for ensuring that pixel values are always non-negative,
197    /// especially when dealing with rendering or layout calculations.
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// use tessera_ui::px::Px;
203    ///
204    /// assert_eq!(Px::new(10).positive(), 10);
205    /// assert_eq!(Px::new(-5).positive(), 0);
206    /// assert_eq!(Px::new(0).positive(), 0);
207    /// ```
208    pub fn positive(self) -> u32 {
209        if self.0 < 0 { 0 } else { self.0 as u32 }
210    }
211
212    /// Returns the negative value, or zero if positive.
213    ///
214    /// This is useful for ensuring that pixel values are always non-positive,
215    /// especially when dealing with rendering or layout calculations.
216    ///
217    /// # Examples
218    ///
219    /// ```
220    /// use tessera_ui::px::Px;
221    ///
222    /// assert_eq!(Px::new(10).negative(), 0);
223    /// assert_eq!(Px::new(-5).negative(), -5);
224    /// assert_eq!(Px::new(0).negative(), 0);
225    pub fn negative(self) -> i32 {
226        if self.0 > 0 { 0 } else { self.0 }
227    }
228
229    /// Converts the pixel value to f32.
230    ///
231    /// # Examples
232    ///
233    /// ```
234    /// use tessera_ui::px::Px;
235    ///
236    /// let px = Px::new(42);
237    /// assert_eq!(px.to_f32(), 42.0);
238    /// ```
239    pub fn to_f32(self) -> f32 {
240        self.0 as f32
241    }
242
243    /// Creates a `Px` from an f32 value.
244    ///
245    /// # Panics
246    ///
247    /// This function may panic on overflow in debug builds when the f32 value
248    /// cannot be represented as an i32.
249    ///
250    /// # Examples
251    ///
252    /// ```
253    /// use tessera_ui::px::Px;
254    ///
255    /// let px = Px::from_f32(42.7);
256    /// assert_eq!(px.raw(), 42);
257    /// ```
258    pub fn from_f32(value: f32) -> Self {
259        Px(value as i32)
260    }
261
262    /// Creates a `Px` from an f32 value, saturating at the numeric bounds instead of overflowing.
263    ///
264    /// This is the safe alternative to [`from_f32`](Self::from_f32) that handles overflow
265    /// by clamping the value to the valid i32 range.
266    ///
267    /// # Examples
268    ///
269    /// ```
270    /// use tessera_ui::px::Px;
271    ///
272    /// let normal = Px::saturating_from_f32(42.7);
273    /// assert_eq!(normal.raw(), 42);
274    ///
275    /// let max_val = Px::saturating_from_f32(f32::MAX);
276    /// assert_eq!(max_val.raw(), i32::MAX);
277    ///
278    /// let min_val = Px::saturating_from_f32(f32::MIN);
279    /// assert_eq!(min_val.raw(), i32::MIN);
280    /// ```
281    pub fn saturating_from_f32(value: f32) -> Self {
282        let clamped_value = value.clamp(i32::MIN as f32, i32::MAX as f32);
283        Px(clamped_value as i32)
284    }
285
286    /// Saturating integer addition.
287    ///
288    /// Computes `self + rhs`, saturating at the numeric bounds instead of overflowing.
289    /// This prevents integer overflow by clamping the result to the valid i32 range.
290    ///
291    /// # Arguments
292    ///
293    /// * `rhs` - The right-hand side value to add
294    ///
295    /// # Examples
296    ///
297    /// ```
298    /// use tessera_ui::px::Px;
299    ///
300    /// let a = Px::new(10);
301    /// let b = Px::new(5);
302    /// assert_eq!(a.saturating_add(b), Px::new(15));
303    ///
304    /// // Prevents overflow
305    /// let max = Px::new(i32::MAX);
306    /// assert_eq!(max.saturating_add(Px::new(1)), max);
307    /// ```
308    pub fn saturating_add(self, rhs: Self) -> Self {
309        Px(self.0.saturating_add(rhs.0))
310    }
311
312    /// Saturating integer subtraction.
313    ///
314    /// Computes `self - rhs`, saturating at the numeric bounds instead of overflowing.
315    /// This prevents integer underflow by clamping the result to the valid i32 range.
316    ///
317    /// # Arguments
318    ///
319    /// * `rhs` - The right-hand side value to subtract
320    ///
321    /// # Examples
322    ///
323    /// ```
324    /// use tessera_ui::px::Px;
325    ///
326    /// let a = Px::new(10);
327    /// let b = Px::new(5);
328    /// assert_eq!(a.saturating_sub(b), Px::new(5));
329    ///
330    /// // Prevents underflow
331    /// let min = Px::new(i32::MIN);
332    /// assert_eq!(min.saturating_sub(Px::new(1)), min);
333    /// ```
334    pub fn saturating_sub(self, rhs: Self) -> Self {
335        Px(self.0.saturating_sub(rhs.0))
336    }
337
338    /// Multiplies the pixel value by a scalar f32.
339    ///
340    /// # Arguments
341    ///
342    /// * `rhs` - The scalar value to multiply by
343    ///
344    /// # Returns
345    ///
346    /// A new `Px` instance with the result of the multiplication.
347    ///
348    /// # Examples
349    ///
350    /// ```
351    /// use tessera_ui::px::Px;
352    ///
353    /// let px = Px::new(10);
354    /// let result = px.mul_f32(2.0);
355    /// assert_eq!(result, Px::new(20));
356    /// ```
357    pub fn mul_f32(self, rhs: f32) -> Self {
358        Px((self.0 as f32 * rhs) as i32)
359    }
360
361    /// Divides the pixel value by a scalar f32.
362    ///
363    /// # Arguments
364    ///
365    /// * `rhs` - The scalar value to divide by
366    ///
367    /// # Returns
368    ///
369    /// A new `Px` instance with the result of the division.
370    ///
371    /// # Panics
372    ///
373    /// This function may panic if `rhs` is zero, as division by zero is undefined.
374    ///
375    /// # Examples
376    ///
377    /// ```
378    /// use tessera_ui::px::Px;
379    ///
380    /// let px = Px::new(20);
381    /// let result = px.div_f32(2.0);
382    /// assert_eq!(result, Px::new(10));
383    /// ```
384    pub fn div_f32(self, rhs: f32) -> Self {
385        Px::from_f32(self.to_f32() / rhs)
386    }
387}
388
389/// A 2D position in physical pixel space.
390///
391/// This type represents a position with x and y coordinates in physical pixel space.
392/// Physical pixels correspond directly to screen pixels and are used internally
393/// by the rendering system.
394///
395/// # Coordinate System
396///
397/// - Origin (0, 0) is at the top-left corner
398/// - X-axis increases to the right
399/// - Y-axis increases downward
400/// - Negative coordinates are supported for scrolling and off-screen positioning
401///
402/// # Examples
403///
404/// ```
405/// use tessera_ui::px::{Px, PxPosition};
406///
407/// // Create a position
408/// let position = PxPosition::new(Px::new(100), Px::new(200));
409///
410/// // Offset the position
411/// let offset_position = position.offset(Px::new(10), Px::new(-5));
412///
413/// // Calculate distance between positions
414/// let other_position = PxPosition::new(Px::new(103), Px::new(196));
415/// let distance = position.distance_to(other_position);
416///
417/// // Arithmetic operations
418/// let sum = position + other_position;
419/// let diff = position - other_position;
420/// ```
421#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
422pub struct PxPosition {
423    /// The x-coordinate in physical pixels
424    pub x: Px,
425    /// The y-coordinate in physical pixels
426    pub y: Px,
427}
428
429impl PxPosition {
430    /// A constant representing the zero position (0, 0).
431    pub const ZERO: Self = Self { x: Px(0), y: Px(0) };
432
433    /// Creates a new position from x and y coordinates.
434    ///
435    /// # Arguments
436    ///
437    /// * `x` - The x-coordinate in physical pixels
438    /// * `y` - The y-coordinate in physical pixels
439    ///
440    /// # Examples
441    ///
442    /// ```
443    /// use tessera_ui::px::{Px, PxPosition};
444    ///
445    /// let position = PxPosition::new(Px::new(100), Px::new(200));
446    /// assert_eq!(position.x, Px::new(100));
447    /// assert_eq!(position.y, Px::new(200));
448    /// ```
449    pub const fn new(x: Px, y: Px) -> Self {
450        Self { x, y }
451    }
452
453    /// Offsets the position by the given deltas.
454    ///
455    /// # Panics
456    ///
457    /// This function may panic on overflow in debug builds.
458    ///
459    /// # Arguments
460    ///
461    /// * `dx` - The x-axis offset in physical pixels
462    /// * `dy` - The y-axis offset in physical pixels
463    ///
464    /// # Examples
465    ///
466    /// ```
467    /// use tessera_ui::px::{Px, PxPosition};
468    ///
469    /// let position = PxPosition::new(Px::new(10), Px::new(20));
470    /// let offset_position = position.offset(Px::new(5), Px::new(-3));
471    /// assert_eq!(offset_position, PxPosition::new(Px::new(15), Px::new(17)));
472    /// ```
473    pub fn offset(self, dx: Px, dy: Px) -> Self {
474        Self {
475            x: self.x + dx,
476            y: self.y + dy,
477        }
478    }
479
480    /// Offsets the position with saturating arithmetic.
481    ///
482    /// This prevents overflow by clamping the result to the valid coordinate range.
483    ///
484    /// # Arguments
485    ///
486    /// * `dx` - The x-axis offset in physical pixels
487    /// * `dy` - The y-axis offset in physical pixels
488    ///
489    /// # Examples
490    ///
491    /// ```
492    /// use tessera_ui::px::{Px, PxPosition};
493    ///
494    /// let position = PxPosition::new(Px::new(10), Px::new(20));
495    /// let offset_position = position.saturating_offset(Px::new(5), Px::new(-3));
496    /// assert_eq!(offset_position, PxPosition::new(Px::new(15), Px::new(17)));
497    ///
498    /// // Prevents overflow
499    /// let max_position = PxPosition::new(Px::new(i32::MAX), Px::new(i32::MAX));
500    /// let safe_offset = max_position.saturating_offset(Px::new(1), Px::new(1));
501    /// assert_eq!(safe_offset, max_position);
502    /// ```
503    pub fn saturating_offset(self, dx: Px, dy: Px) -> Self {
504        Self {
505            x: self.x.saturating_add(dx),
506            y: self.y.saturating_add(dy),
507        }
508    }
509
510    /// Calculates the Euclidean distance to another position.
511    ///
512    /// # Arguments
513    ///
514    /// * `other` - The other position to calculate distance to
515    ///
516    /// # Returns
517    ///
518    /// The distance as a floating-point value
519    ///
520    /// # Examples
521    ///
522    /// ```
523    /// use tessera_ui::px::{Px, PxPosition};
524    ///
525    /// let pos1 = PxPosition::new(Px::new(0), Px::new(0));
526    /// let pos2 = PxPosition::new(Px::new(3), Px::new(4));
527    /// assert_eq!(pos1.distance_to(pos2), 5.0);
528    /// ```
529    pub fn distance_to(self, other: Self) -> f32 {
530        let dx = (self.x.0 - other.x.0) as f32;
531        let dy = (self.y.0 - other.y.0) as f32;
532        (dx * dx + dy * dy).sqrt()
533    }
534
535    /// Converts the position to a 2D f32 array.
536    ///
537    /// # Returns
538    ///
539    /// An array `[x, y]` where both coordinates are converted to f32
540    ///
541    /// # Examples
542    ///
543    /// ```
544    /// use tessera_ui::px::{Px, PxPosition};
545    ///
546    /// let position = PxPosition::new(Px::new(10), Px::new(20));
547    /// assert_eq!(position.to_f32_arr2(), [10.0, 20.0]);
548    /// ```
549    pub fn to_f32_arr2(self) -> [f32; 2] {
550        [self.x.0 as f32, self.y.0 as f32]
551    }
552
553    /// Converts the position to a 3D f32 array with z=0.
554    ///
555    /// # Returns
556    ///
557    /// An array `[x, y, 0.0]` where x and y are converted to f32 and z is 0.0
558    ///
559    /// # Examples
560    ///
561    /// ```
562    /// use tessera_ui::px::{Px, PxPosition};
563    ///
564    /// let position = PxPosition::new(Px::new(10), Px::new(20));
565    /// assert_eq!(position.to_f32_arr3(), [10.0, 20.0, 0.0]);
566    /// ```
567    pub fn to_f32_arr3(self) -> [f32; 3] {
568        [self.x.0 as f32, self.y.0 as f32, 0.0]
569    }
570
571    /// Creates a position from a 2D f32 array.
572    ///
573    /// # Arguments
574    ///
575    /// * `arr` - An array `[x, y]` where both values will be converted to i32
576    ///
577    /// # Examples
578    ///
579    /// ```
580    /// use tessera_ui::px::{Px, PxPosition};
581    ///
582    /// let position = PxPosition::from_f32_arr2([10.5, 20.7]);
583    /// assert_eq!(position, PxPosition::new(Px::new(10), Px::new(20)));
584    /// ```
585    pub fn from_f32_arr2(arr: [f32; 2]) -> Self {
586        Self {
587            x: Px::new(arr[0] as i32),
588            y: Px::new(arr[1] as i32),
589        }
590    }
591
592    /// Creates a position from a 3D f32 array, ignoring the z component.
593    ///
594    /// # Arguments
595    ///
596    /// * `arr` - An array `[x, y, z]` where only x and y are used
597    ///
598    /// # Examples
599    ///
600    /// ```
601    /// use tessera_ui::px::{Px, PxPosition};
602    ///
603    /// let position = PxPosition::from_f32_arr3([10.5, 20.7, 30.9]);
604    /// assert_eq!(position, PxPosition::new(Px::new(10), Px::new(20)));
605    /// ```
606    pub fn from_f32_arr3(arr: [f32; 3]) -> Self {
607        Self {
608            x: Px::new(arr[0] as i32),
609            y: Px::new(arr[1] as i32),
610        }
611    }
612
613    /// Converts the position to a 2D f64 array.
614    ///
615    /// # Returns
616    ///
617    /// An array `[x, y]` where both coordinates are converted to f64
618    ///
619    /// # Examples
620    ///
621    /// ```
622    /// use tessera_ui::px::{Px, PxPosition};
623    ///
624    /// let position = PxPosition::new(Px::new(10), Px::new(20));
625    /// assert_eq!(position.to_f64_arr2(), [10.0, 20.0]);
626    /// ```
627    pub fn to_f64_arr2(self) -> [f64; 2] {
628        [self.x.0 as f64, self.y.0 as f64]
629    }
630
631    /// Converts the position to a 3D f64 array with z=0.
632    ///
633    /// # Returns
634    ///
635    /// An array `[x, y, 0.0]` where x and y are converted to f64 and z is 0.0
636    ///
637    /// # Examples
638    ///
639    /// ```
640    /// use tessera_ui::px::{Px, PxPosition};
641    ///
642    /// let position = PxPosition::new(Px::new(10), Px::new(20));
643    /// assert_eq!(position.to_f64_arr3(), [10.0, 20.0, 0.0]);
644    /// ```
645    pub fn to_f64_arr3(self) -> [f64; 3] {
646        [self.x.0 as f64, self.y.0 as f64, 0.0]
647    }
648
649    /// Creates a position from a 2D f64 array.
650    ///
651    /// # Arguments
652    ///
653    /// * `arr` - An array `[x, y]` where both values will be converted to i32
654    ///
655    /// # Examples
656    ///
657    /// ```
658    /// use tessera_ui::px::{Px, PxPosition};
659    ///
660    /// let position = PxPosition::from_f64_arr2([10.5, 20.7]);
661    /// assert_eq!(position, PxPosition::new(Px::new(10), Px::new(20)));
662    /// ```
663    pub fn from_f64_arr2(arr: [f64; 2]) -> Self {
664        Self {
665            x: Px::new(arr[0] as i32),
666            y: Px::new(arr[1] as i32),
667        }
668    }
669
670    /// Creates a position from a 3D f64 array, ignoring the z component.
671    ///
672    /// # Arguments
673    ///
674    /// * `arr` - An array `[x, y, z]` where only x and y are used
675    ///
676    /// # Examples
677    ///
678    /// ```
679    /// use tessera_ui::px::{Px, PxPosition};
680    ///
681    /// let position = PxPosition::from_f64_arr3([10.5, 20.7, 30.9]);
682    /// assert_eq!(position, PxPosition::new(Px::new(10), Px::new(20)));
683    /// ```
684    pub fn from_f64_arr3(arr: [f64; 3]) -> Self {
685        Self {
686            x: Px::new(arr[0] as i32),
687            y: Px::new(arr[1] as i32),
688        }
689    }
690}
691
692/// A 2D size in physical pixel space.
693///
694/// This type represents dimensions (width and height) in physical pixel space.
695/// Physical pixels correspond directly to screen pixels and are used internally
696/// by the rendering system.
697///
698/// # Examples
699///
700/// ```
701/// use tessera_ui::px::{Px, PxSize};
702///
703/// // Create a size
704/// let size = PxSize::new(Px::new(300), Px::new(200));
705///
706/// // Convert to array formats for graphics APIs
707/// let f32_array = size.to_f32_arr2();
708/// assert_eq!(f32_array, [300.0, 200.0]);
709///
710/// // Create from array
711/// let from_array = PxSize::from([Px::new(400), Px::new(300)]);
712/// ```
713#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
714pub struct PxSize {
715    /// The width in physical pixels
716    pub width: Px,
717    /// The height in physical pixels
718    pub height: Px,
719}
720
721impl PxSize {
722    /// A constant representing zero size (0×0).
723    pub const ZERO: Self = Self {
724        width: Px(0),
725        height: Px(0),
726    };
727
728    /// Creates a new size from width and height.
729    ///
730    /// # Arguments
731    ///
732    /// * `width` - The width in physical pixels
733    /// * `height` - The height in physical pixels
734    ///
735    /// # Examples
736    ///
737    /// ```
738    /// use tessera_ui::px::{Px, PxSize};
739    ///
740    /// let size = PxSize::new(Px::new(300), Px::new(200));
741    /// assert_eq!(size.width, Px::new(300));
742    /// assert_eq!(size.height, Px::new(200));
743    /// ```
744    pub const fn new(width: Px, height: Px) -> Self {
745        Self { width, height }
746    }
747
748    /// Converts the size to a 2D f32 array.
749    ///
750    /// This is useful for interfacing with graphics APIs that expect
751    /// floating-point size values.
752    ///
753    /// # Returns
754    ///
755    /// An array `[width, height]` where both dimensions are converted to f32
756    ///
757    /// # Examples
758    ///
759    /// ```
760    /// use tessera_ui::px::{Px, PxSize};
761    ///
762    /// let size = PxSize::new(Px::new(300), Px::new(200));
763    /// assert_eq!(size.to_f32_arr2(), [300.0, 200.0]);
764    /// ```
765    pub fn to_f32_arr2(self) -> [f32; 2] {
766        [self.width.0 as f32, self.height.0 as f32]
767    }
768}
769
770/// A 2D rectangle in physical pixel space.
771///
772/// This type represents a rectangle with a position (top-left corner) and dimensions
773/// in physical pixel space.
774#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
775pub struct PxRect {
776    /// The x-coordinate of the top-left corner
777    pub x: Px,
778    /// The y-coordinate of the top-left corner
779    pub y: Px,
780    /// The width of the rectangle
781    pub width: Px,
782    /// The height of the rectangle
783    pub height: Px,
784}
785
786impl PxRect {
787    /// A constant representing a zero rectangle (0×0 at position (0, 0)).
788    pub const ZERO: Self = Self {
789        x: Px::ZERO,
790        y: Px::ZERO,
791        width: Px::ZERO,
792        height: Px::ZERO,
793    };
794
795    /// Creates a new rectangle from position and size.
796    pub const fn new(x: Px, y: Px, width: Px, height: Px) -> Self {
797        Self {
798            x,
799            y,
800            width,
801            height,
802        }
803    }
804
805    /// Checks if this rectangle is orthogonal (non-overlapping) with another rectangle.
806    ///
807    /// Two rectangles are orthogonal if they do not overlap in either the x or y axis.
808    /// This is useful for barrier batching optimization where non-overlapping rectangles
809    /// can be processed together without requiring barriers.
810    ///
811    /// # Arguments
812    ///
813    /// * `other` - The other rectangle to check orthogonality against
814    ///
815    /// # Returns
816    ///
817    /// `true` if the rectangles are orthogonal (non-overlapping), `false` otherwise
818    ///
819    /// # Examples
820    ///
821    /// ```
822    /// use tessera_ui::px::{Px, PxRect};
823    ///
824    /// let rect1 = PxRect::new(Px::new(0), Px::new(0), Px::new(100), Px::new(100));
825    /// let rect2 = PxRect::new(Px::new(150), Px::new(0), Px::new(100), Px::new(100));
826    /// assert!(rect1.is_orthogonal(&rect2));
827    ///
828    /// let rect3 = PxRect::new(Px::new(50), Px::new(50), Px::new(100), Px::new(100));
829    /// assert!(!rect1.is_orthogonal(&rect3));
830    /// ```
831    pub fn is_orthogonal(&self, other: &Self) -> bool {
832        // Check if rectangles overlap on x-axis
833        let x_overlap = self.x.0 < other.x.0 + other.width.0 && other.x.0 < self.x.0 + self.width.0;
834
835        // Check if rectangles overlap on y-axis
836        let y_overlap =
837            self.y.0 < other.y.0 + other.height.0 && other.y.0 < self.y.0 + self.height.0;
838
839        // Rectangles are orthogonal if they don't overlap on either axis
840        !x_overlap || !y_overlap
841    }
842
843    /// Creates a new rectangle that is the union of this rectangle and another rectangle.
844    /// Which is the smallest rectangle that contains both rectangles.
845    ///
846    /// # Arguments
847    ///
848    /// * `other` - The other rectangle to union with
849    ///
850    /// # Returns
851    ///
852    /// A new `PxRect` that is the union of this rectangle and the other rectangle
853    ///
854    /// # Examples
855    ///
856    /// ```
857    /// use tessera_ui::px::{Px, PxRect};
858    ///
859    /// let rect1 = PxRect::new(Px::new(0), Px::new(0), Px::new(100), Px::new(100));
860    /// let rect2 = PxRect::new(Px::new(50), Px::new(50), Px::new(100), Px::new(100));
861    /// let union_rect = rect1.union(&rect2);
862    /// assert_eq!(union_rect, PxRect::new(Px::new(0), Px::new(0), Px::new(150), Px::new(150)));
863    /// ```
864    pub fn union(&self, other: &Self) -> Self {
865        let x = self.x.0.min(other.x.0);
866        let y = self.y.0.min(other.y.0);
867        let width = (self.x.0 + self.width.0).max(other.x.0 + other.width.0) - x;
868        let height = (self.y.0 + self.height.0).max(other.y.0 + other.height.0) - y;
869
870        Self {
871            x: Px(x),
872            y: Px(y),
873            width: Px(width),
874            height: Px(height),
875        }
876    }
877
878    /// Returns the area of this rectangle.
879    ///
880    /// # Returns
881    ///
882    /// The area as a positive integer, or 0 if width or height is negative
883    pub fn area(&self) -> u32 {
884        let width = self.width.0.max(0) as u32;
885        let height = self.height.0.max(0) as u32;
886        width * height
887    }
888
889    /// Gets the intersection of this rectangle with another rectangle.
890    ///
891    /// If the rectangles do not intersect, returns `None`.
892    ///
893    /// # Arguments
894    ///
895    /// * `other` - The other rectangle to intersect with
896    ///
897    /// # Returns
898    ///
899    /// An `Option<PxRect>` that is `Some` if the rectangles intersect,
900    /// or `None` if they do not.
901    ///
902    /// # Examples
903    ///
904    /// ```
905    /// use tessera_ui::px::{Px, PxRect};
906    ///
907    /// let rect1 = PxRect::new(Px::new(0), Px::new(0), Px::new(100), Px::new(100));
908    /// let rect2 = PxRect::new(Px::new(50), Px::new(50), Px::new(100), Px::new(100));
909    /// let intersection = rect1.intersection(&rect2);
910    /// assert_eq!(intersection, Some(PxRect::new(Px::new(50), Px::new(50), Px::new(50), Px::new(50))));
911    /// ```
912    pub fn intersection(&self, other: &Self) -> Option<Self> {
913        let x1 = self.x.0.max(other.x.0);
914        let y1 = self.y.0.max(other.y.0);
915        let x2 = (self.x.0 + self.width.0).min(other.x.0 + other.width.0);
916        let y2 = (self.y.0 + self.height.0).min(other.y.0 + other.height.0);
917
918        if x1 < x2 && y1 < y2 {
919            Some(Self {
920                x: Px(x1),
921                y: Px(y1),
922                width: Px(x2 - x1),
923                height: Px(y2 - y1),
924            })
925        } else {
926            None
927        }
928    }
929
930    /// Check if a point is inside the rectangle.
931    ///
932    /// # Arguments
933    ///
934    /// * `point` - The point to check
935    ///
936    /// # Returns
937    ///
938    /// An bool shows that whether the point is inside rectangle.
939    pub fn contains(&self, point: PxPosition) -> bool {
940        point.x.0 >= self.x.0
941            && point.x.0 < self.x.0 + self.width.0
942            && point.y.0 >= self.y.0
943            && point.y.0 < self.y.0 + self.height.0
944    }
945}
946
947impl From<[Px; 2]> for PxSize {
948    fn from(size: [Px; 2]) -> Self {
949        Self {
950            width: size[0],
951            height: size[1],
952        }
953    }
954}
955
956impl From<PxSize> for winit::dpi::PhysicalSize<i32> {
957    fn from(size: PxSize) -> Self {
958        winit::dpi::PhysicalSize {
959            width: size.width.raw(),
960            height: size.height.raw(),
961        }
962    }
963}
964
965impl From<winit::dpi::PhysicalSize<u32>> for PxSize {
966    fn from(size: winit::dpi::PhysicalSize<u32>) -> Self {
967        Self {
968            width: Px(size.width as i32),
969            height: Px(size.height as i32),
970        }
971    }
972}
973
974impl From<crate::component_tree::ComputedData> for PxSize {
975    fn from(data: crate::component_tree::ComputedData) -> Self {
976        Self {
977            width: data.width,
978            height: data.height,
979        }
980    }
981}
982
983impl From<PxSize> for winit::dpi::Size {
984    fn from(size: PxSize) -> Self {
985        winit::dpi::PhysicalSize::from(size).into()
986    }
987}
988
989impl std::ops::Add for Px {
990    type Output = Self;
991
992    fn add(self, rhs: Self) -> Self::Output {
993        Px(self.0 + rhs.0)
994    }
995}
996
997impl Neg for Px {
998    type Output = Self;
999
1000    fn neg(self) -> Self::Output {
1001        Px::new(-self.0)
1002    }
1003}
1004
1005impl std::ops::Sub for Px {
1006    type Output = Self;
1007
1008    fn sub(self, rhs: Self) -> Self::Output {
1009        Px(self.0 - rhs.0)
1010    }
1011}
1012
1013impl std::ops::Mul for Px {
1014    type Output = Self;
1015
1016    fn mul(self, rhs: Self) -> Self::Output {
1017        Px(self.0 * rhs.0)
1018    }
1019}
1020
1021impl std::ops::Div for Px {
1022    type Output = Self;
1023
1024    fn div(self, rhs: Self) -> Self::Output {
1025        Px(self.0 / rhs.0)
1026    }
1027}
1028
1029impl std::ops::Mul<i32> for Px {
1030    type Output = Self;
1031
1032    fn mul(self, rhs: i32) -> Self::Output {
1033        Px(self.0 * rhs)
1034    }
1035}
1036
1037impl std::ops::Div<i32> for Px {
1038    type Output = Self;
1039
1040    fn div(self, rhs: i32) -> Self::Output {
1041        Px(self.0 / rhs)
1042    }
1043}
1044
1045impl From<i32> for Px {
1046    fn from(value: i32) -> Self {
1047        Px(value)
1048    }
1049}
1050
1051impl From<u32> for Px {
1052    fn from(value: u32) -> Self {
1053        Px(value as i32)
1054    }
1055}
1056
1057impl From<Dp> for Px {
1058    fn from(dp: Dp) -> Self {
1059        Px::from_dp(dp)
1060    }
1061}
1062
1063impl From<PxPosition> for winit::dpi::PhysicalPosition<i32> {
1064    fn from(pos: PxPosition) -> Self {
1065        winit::dpi::PhysicalPosition {
1066            x: pos.x.0,
1067            y: pos.y.0,
1068        }
1069    }
1070}
1071
1072impl From<PxPosition> for winit::dpi::Position {
1073    fn from(pos: PxPosition) -> Self {
1074        winit::dpi::PhysicalPosition::from(pos).into()
1075    }
1076}
1077
1078impl AddAssign for Px {
1079    fn add_assign(&mut self, rhs: Self) {
1080        self.0 += rhs.0;
1081    }
1082}
1083
1084// Arithmetic operations support - PxPosition
1085impl std::ops::Add for PxPosition {
1086    type Output = Self;
1087
1088    fn add(self, rhs: Self) -> Self::Output {
1089        PxPosition {
1090            x: self.x + rhs.x,
1091            y: self.y + rhs.y,
1092        }
1093    }
1094}
1095
1096impl std::ops::Sub for PxPosition {
1097    type Output = Self;
1098
1099    fn sub(self, rhs: Self) -> Self::Output {
1100        PxPosition {
1101            x: self.x - rhs.x,
1102            y: self.y - rhs.y,
1103        }
1104    }
1105}
1106
1107// Type conversion implementations
1108impl From<[i32; 2]> for PxPosition {
1109    fn from(pos: [i32; 2]) -> Self {
1110        PxPosition {
1111            x: Px(pos[0]),
1112            y: Px(pos[1]),
1113        }
1114    }
1115}
1116
1117impl From<PxPosition> for [i32; 2] {
1118    fn from(pos: PxPosition) -> Self {
1119        [pos.x.0, pos.y.0]
1120    }
1121}
1122
1123impl From<[u32; 2]> for PxPosition {
1124    fn from(pos: [u32; 2]) -> Self {
1125        PxPosition {
1126            x: Px(pos[0] as i32),
1127            y: Px(pos[1] as i32),
1128        }
1129    }
1130}
1131
1132impl From<PxPosition> for [u32; 2] {
1133    fn from(pos: PxPosition) -> Self {
1134        [pos.x.positive(), pos.y.positive()]
1135    }
1136}
1137
1138impl From<[Px; 2]> for PxPosition {
1139    fn from(pos: [Px; 2]) -> Self {
1140        PxPosition {
1141            x: pos[0],
1142            y: pos[1],
1143        }
1144    }
1145}
1146
1147impl From<PxPosition> for [Px; 2] {
1148    fn from(pos: PxPosition) -> Self {
1149        [pos.x, pos.y]
1150    }
1151}
1152
1153#[cfg(test)]
1154mod tests {
1155    use super::*;
1156
1157    #[test]
1158    fn test_px_creation() {
1159        let px = Px::new(42);
1160        assert_eq!(px.0, 42);
1161
1162        let px_neg = Px::new(-10);
1163        assert_eq!(px_neg.0, -10);
1164    }
1165
1166    #[test]
1167    fn test_px_arithmetic() {
1168        let a = Px(10);
1169        let b = Px(5);
1170
1171        assert_eq!(a + b, Px(15));
1172        assert_eq!(a - b, Px(5));
1173        assert_eq!(a * 2, Px(20));
1174        assert_eq!(a / 2, Px(5));
1175        assert_eq!(a * b, Px(50));
1176        assert_eq!(a / b, Px(2));
1177    }
1178
1179    #[test]
1180    fn test_px_saturating_arithmetic() {
1181        let max = Px(i32::MAX);
1182        let min = Px(i32::MIN);
1183        assert_eq!(max.saturating_add(Px(1)), max);
1184        assert_eq!(min.saturating_sub(Px(1)), min);
1185    }
1186
1187    #[test]
1188    fn test_saturating_from_f32() {
1189        assert_eq!(Px::saturating_from_f32(f32::MAX), Px(i32::MAX));
1190        assert_eq!(Px::saturating_from_f32(f32::MIN), Px(i32::MIN));
1191        assert_eq!(Px::saturating_from_f32(100.5), Px(100));
1192        assert_eq!(Px::saturating_from_f32(-100.5), Px(-100));
1193    }
1194
1195    #[test]
1196    fn test_px_abs() {
1197        assert_eq!(Px(10).abs(), 10);
1198        assert_eq!(Px(-5).abs(), 5);
1199        assert_eq!(Px(0).abs(), 0);
1200    }
1201
1202    #[test]
1203    fn test_px_position() {
1204        let pos = PxPosition::new(Px(10), Px(-5));
1205        assert_eq!(pos.x, Px(10));
1206        assert_eq!(pos.y, Px(-5));
1207
1208        let offset_pos = pos.offset(Px(2), Px(3));
1209        assert_eq!(offset_pos, PxPosition::new(Px(12), Px(-2)));
1210    }
1211
1212    #[test]
1213    fn test_px_position_arithmetic() {
1214        let pos1 = PxPosition::new(Px(10), Px(20));
1215        let pos2 = PxPosition::new(Px(5), Px(15));
1216
1217        let sum = pos1 + pos2;
1218        assert_eq!(sum, PxPosition::new(Px(15), Px(35)));
1219
1220        let diff = pos1 - pos2;
1221        assert_eq!(diff, PxPosition::new(Px(5), Px(5)));
1222    }
1223
1224    #[test]
1225    fn test_px_position_conversions() {
1226        let i32_pos: [i32; 2] = [10, -5];
1227        let px_pos: PxPosition = i32_pos.into();
1228        let back_to_i32: [i32; 2] = px_pos.into();
1229        assert_eq!(i32_pos, back_to_i32);
1230
1231        let u32_pos: [u32; 2] = [10, 5];
1232        let px_from_u32: PxPosition = u32_pos.into();
1233        let back_to_u32: [u32; 2] = px_from_u32.into();
1234        assert_eq!(u32_pos, back_to_u32);
1235    }
1236
1237    #[test]
1238    fn test_distance() {
1239        let pos1 = PxPosition::new(Px(0), Px(0));
1240        let pos2 = PxPosition::new(Px(3), Px(4));
1241        assert_eq!(pos1.distance_to(pos2), 5.0);
1242    }
1243}