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    /// Creates a new rectangle from a position and size.
806    pub fn from_position_size(position: PxPosition, size: PxSize) -> Self {
807        Self {
808            x: position.x,
809            y: position.y,
810            width: size.width,
811            height: size.height,
812        }
813    }
814
815    /// Checks if this rectangle is orthogonal (non-overlapping) with another rectangle.
816    ///
817    /// Two rectangles are orthogonal if they do not overlap in either the x or y axis.
818    /// This is useful for barrier batching optimization where non-overlapping rectangles
819    /// can be processed together without requiring barriers.
820    ///
821    /// # Arguments
822    ///
823    /// * `other` - The other rectangle to check orthogonality against
824    ///
825    /// # Returns
826    ///
827    /// `true` if the rectangles are orthogonal (non-overlapping), `false` otherwise
828    ///
829    /// # Examples
830    ///
831    /// ```
832    /// use tessera_ui::px::{Px, PxRect};
833    ///
834    /// let rect1 = PxRect::new(Px::new(0), Px::new(0), Px::new(100), Px::new(100));
835    /// let rect2 = PxRect::new(Px::new(150), Px::new(0), Px::new(100), Px::new(100));
836    /// assert!(rect1.is_orthogonal(&rect2));
837    ///
838    /// let rect3 = PxRect::new(Px::new(50), Px::new(50), Px::new(100), Px::new(100));
839    /// assert!(!rect1.is_orthogonal(&rect3));
840    /// ```
841    pub fn is_orthogonal(&self, other: &Self) -> bool {
842        // Check if rectangles overlap on x-axis
843        let x_overlap = self.x.0 < other.x.0 + other.width.0 && other.x.0 < self.x.0 + self.width.0;
844
845        // Check if rectangles overlap on y-axis
846        let y_overlap =
847            self.y.0 < other.y.0 + other.height.0 && other.y.0 < self.y.0 + self.height.0;
848
849        // Rectangles are orthogonal if they don't overlap on either axis
850        !x_overlap || !y_overlap
851    }
852
853    /// Creates a new rectangle that is the union of this rectangle and another rectangle.
854    /// Which is the smallest rectangle that contains both rectangles.
855    ///
856    /// # Arguments
857    ///
858    /// * `other` - The other rectangle to union with
859    ///
860    /// # Returns
861    ///
862    /// A new `PxRect` that is the union of this rectangle and the other rectangle
863    ///
864    /// # Examples
865    ///
866    /// ```
867    /// use tessera_ui::px::{Px, PxRect};
868    ///
869    /// let rect1 = PxRect::new(Px::new(0), Px::new(0), Px::new(100), Px::new(100));
870    /// let rect2 = PxRect::new(Px::new(50), Px::new(50), Px::new(100), Px::new(100));
871    /// let union_rect = rect1.union(&rect2);
872    /// assert_eq!(union_rect, PxRect::new(Px::new(0), Px::new(0), Px::new(150), Px::new(150)));
873    /// ```
874    pub fn union(&self, other: &Self) -> Self {
875        let x = self.x.0.min(other.x.0);
876        let y = self.y.0.min(other.y.0);
877        let width = (self.x.0 + self.width.0).max(other.x.0 + other.width.0) - x;
878        let height = (self.y.0 + self.height.0).max(other.y.0 + other.height.0) - y;
879
880        Self {
881            x: Px(x),
882            y: Px(y),
883            width: Px(width),
884            height: Px(height),
885        }
886    }
887
888    /// Returns the area of this rectangle.
889    ///
890    /// # Returns
891    ///
892    /// The area as a positive integer, or 0 if width or height is negative
893    pub fn area(&self) -> u32 {
894        let width = self.width.0.max(0) as u32;
895        let height = self.height.0.max(0) as u32;
896        width * height
897    }
898
899    /// Gets the intersection of this rectangle with another rectangle.
900    ///
901    /// If the rectangles do not intersect, returns `None`.
902    ///
903    /// # Arguments
904    ///
905    /// * `other` - The other rectangle to intersect with
906    ///
907    /// # Returns
908    ///
909    /// An `Option<PxRect>` that is `Some` if the rectangles intersect,
910    /// or `None` if they do not.
911    ///
912    /// # Examples
913    ///
914    /// ```
915    /// use tessera_ui::px::{Px, PxRect};
916    ///
917    /// let rect1 = PxRect::new(Px::new(0), Px::new(0), Px::new(100), Px::new(100));
918    /// let rect2 = PxRect::new(Px::new(50), Px::new(50), Px::new(100), Px::new(100));
919    /// let intersection = rect1.intersection(&rect2);
920    /// assert_eq!(intersection, Some(PxRect::new(Px::new(50), Px::new(50), Px::new(50), Px::new(50))));
921    /// ```
922    pub fn intersection(&self, other: &Self) -> Option<Self> {
923        let x1 = self.x.0.max(other.x.0);
924        let y1 = self.y.0.max(other.y.0);
925        let x2 = (self.x.0 + self.width.0).min(other.x.0 + other.width.0);
926        let y2 = (self.y.0 + self.height.0).min(other.y.0 + other.height.0);
927
928        if x1 < x2 && y1 < y2 {
929            Some(Self {
930                x: Px(x1),
931                y: Px(y1),
932                width: Px(x2 - x1),
933                height: Px(y2 - y1),
934            })
935        } else {
936            None
937        }
938    }
939
940    /// Check if a point is inside the rectangle.
941    ///
942    /// # Arguments
943    ///
944    /// * `point` - The point to check
945    ///
946    /// # Returns
947    ///
948    /// An bool shows that whether the point is inside rectangle.
949    pub fn contains(&self, point: PxPosition) -> bool {
950        point.x.0 >= self.x.0
951            && point.x.0 < self.x.0 + self.width.0
952            && point.y.0 >= self.y.0
953            && point.y.0 < self.y.0 + self.height.0
954    }
955}
956
957impl From<[Px; 2]> for PxSize {
958    fn from(size: [Px; 2]) -> Self {
959        Self {
960            width: size[0],
961            height: size[1],
962        }
963    }
964}
965
966impl From<PxSize> for winit::dpi::PhysicalSize<i32> {
967    fn from(size: PxSize) -> Self {
968        winit::dpi::PhysicalSize {
969            width: size.width.raw(),
970            height: size.height.raw(),
971        }
972    }
973}
974
975impl From<winit::dpi::PhysicalSize<u32>> for PxSize {
976    fn from(size: winit::dpi::PhysicalSize<u32>) -> Self {
977        Self {
978            width: Px(size.width as i32),
979            height: Px(size.height as i32),
980        }
981    }
982}
983
984impl From<crate::component_tree::ComputedData> for PxSize {
985    fn from(data: crate::component_tree::ComputedData) -> Self {
986        Self {
987            width: data.width,
988            height: data.height,
989        }
990    }
991}
992
993impl From<PxSize> for winit::dpi::Size {
994    fn from(size: PxSize) -> Self {
995        winit::dpi::PhysicalSize::from(size).into()
996    }
997}
998
999impl std::ops::Add for Px {
1000    type Output = Self;
1001
1002    fn add(self, rhs: Self) -> Self::Output {
1003        Px(self.0 + rhs.0)
1004    }
1005}
1006
1007impl Neg for Px {
1008    type Output = Self;
1009
1010    fn neg(self) -> Self::Output {
1011        Px::new(-self.0)
1012    }
1013}
1014
1015impl std::ops::Sub for Px {
1016    type Output = Self;
1017
1018    fn sub(self, rhs: Self) -> Self::Output {
1019        Px(self.0 - rhs.0)
1020    }
1021}
1022
1023impl std::ops::Mul for Px {
1024    type Output = Self;
1025
1026    fn mul(self, rhs: Self) -> Self::Output {
1027        Px(self.0 * rhs.0)
1028    }
1029}
1030
1031impl std::ops::Div for Px {
1032    type Output = Self;
1033
1034    fn div(self, rhs: Self) -> Self::Output {
1035        Px(self.0 / rhs.0)
1036    }
1037}
1038
1039impl std::ops::Mul<i32> for Px {
1040    type Output = Self;
1041
1042    fn mul(self, rhs: i32) -> Self::Output {
1043        Px(self.0 * rhs)
1044    }
1045}
1046
1047impl std::ops::Div<i32> for Px {
1048    type Output = Self;
1049
1050    fn div(self, rhs: i32) -> Self::Output {
1051        Px(self.0 / rhs)
1052    }
1053}
1054
1055impl From<i32> for Px {
1056    fn from(value: i32) -> Self {
1057        Px(value)
1058    }
1059}
1060
1061impl From<u32> for Px {
1062    fn from(value: u32) -> Self {
1063        Px(value as i32)
1064    }
1065}
1066
1067impl From<Dp> for Px {
1068    fn from(dp: Dp) -> Self {
1069        Px::from_dp(dp)
1070    }
1071}
1072
1073impl From<PxPosition> for winit::dpi::PhysicalPosition<i32> {
1074    fn from(pos: PxPosition) -> Self {
1075        winit::dpi::PhysicalPosition {
1076            x: pos.x.0,
1077            y: pos.y.0,
1078        }
1079    }
1080}
1081
1082impl From<PxPosition> for winit::dpi::Position {
1083    fn from(pos: PxPosition) -> Self {
1084        winit::dpi::PhysicalPosition::from(pos).into()
1085    }
1086}
1087
1088impl AddAssign for Px {
1089    fn add_assign(&mut self, rhs: Self) {
1090        self.0 += rhs.0;
1091    }
1092}
1093
1094// Arithmetic operations support - PxPosition
1095impl std::ops::Add for PxPosition {
1096    type Output = Self;
1097
1098    fn add(self, rhs: Self) -> Self::Output {
1099        PxPosition {
1100            x: self.x + rhs.x,
1101            y: self.y + rhs.y,
1102        }
1103    }
1104}
1105
1106impl std::ops::Sub for PxPosition {
1107    type Output = Self;
1108
1109    fn sub(self, rhs: Self) -> Self::Output {
1110        PxPosition {
1111            x: self.x - rhs.x,
1112            y: self.y - rhs.y,
1113        }
1114    }
1115}
1116
1117// Type conversion implementations
1118impl From<[i32; 2]> for PxPosition {
1119    fn from(pos: [i32; 2]) -> Self {
1120        PxPosition {
1121            x: Px(pos[0]),
1122            y: Px(pos[1]),
1123        }
1124    }
1125}
1126
1127impl From<PxPosition> for [i32; 2] {
1128    fn from(pos: PxPosition) -> Self {
1129        [pos.x.0, pos.y.0]
1130    }
1131}
1132
1133impl From<[u32; 2]> for PxPosition {
1134    fn from(pos: [u32; 2]) -> Self {
1135        PxPosition {
1136            x: Px(pos[0] as i32),
1137            y: Px(pos[1] as i32),
1138        }
1139    }
1140}
1141
1142impl From<PxPosition> for [u32; 2] {
1143    fn from(pos: PxPosition) -> Self {
1144        [pos.x.positive(), pos.y.positive()]
1145    }
1146}
1147
1148impl From<[Px; 2]> for PxPosition {
1149    fn from(pos: [Px; 2]) -> Self {
1150        PxPosition {
1151            x: pos[0],
1152            y: pos[1],
1153        }
1154    }
1155}
1156
1157impl From<PxPosition> for [Px; 2] {
1158    fn from(pos: PxPosition) -> Self {
1159        [pos.x, pos.y]
1160    }
1161}
1162
1163#[cfg(test)]
1164mod tests {
1165    use super::*;
1166
1167    #[test]
1168    fn test_px_creation() {
1169        let px = Px::new(42);
1170        assert_eq!(px.0, 42);
1171
1172        let px_neg = Px::new(-10);
1173        assert_eq!(px_neg.0, -10);
1174    }
1175
1176    #[test]
1177    fn test_px_arithmetic() {
1178        let a = Px(10);
1179        let b = Px(5);
1180
1181        assert_eq!(a + b, Px(15));
1182        assert_eq!(a - b, Px(5));
1183        assert_eq!(a * 2, Px(20));
1184        assert_eq!(a / 2, Px(5));
1185        assert_eq!(a * b, Px(50));
1186        assert_eq!(a / b, Px(2));
1187    }
1188
1189    #[test]
1190    fn test_px_saturating_arithmetic() {
1191        let max = Px(i32::MAX);
1192        let min = Px(i32::MIN);
1193        assert_eq!(max.saturating_add(Px(1)), max);
1194        assert_eq!(min.saturating_sub(Px(1)), min);
1195    }
1196
1197    #[test]
1198    fn test_saturating_from_f32() {
1199        assert_eq!(Px::saturating_from_f32(f32::MAX), Px(i32::MAX));
1200        assert_eq!(Px::saturating_from_f32(f32::MIN), Px(i32::MIN));
1201        assert_eq!(Px::saturating_from_f32(100.5), Px(100));
1202        assert_eq!(Px::saturating_from_f32(-100.5), Px(-100));
1203    }
1204
1205    #[test]
1206    fn test_px_abs() {
1207        assert_eq!(Px(10).abs(), 10);
1208        assert_eq!(Px(-5).abs(), 5);
1209        assert_eq!(Px(0).abs(), 0);
1210    }
1211
1212    #[test]
1213    fn test_px_position() {
1214        let pos = PxPosition::new(Px(10), Px(-5));
1215        assert_eq!(pos.x, Px(10));
1216        assert_eq!(pos.y, Px(-5));
1217
1218        let offset_pos = pos.offset(Px(2), Px(3));
1219        assert_eq!(offset_pos, PxPosition::new(Px(12), Px(-2)));
1220    }
1221
1222    #[test]
1223    fn test_px_position_arithmetic() {
1224        let pos1 = PxPosition::new(Px(10), Px(20));
1225        let pos2 = PxPosition::new(Px(5), Px(15));
1226
1227        let sum = pos1 + pos2;
1228        assert_eq!(sum, PxPosition::new(Px(15), Px(35)));
1229
1230        let diff = pos1 - pos2;
1231        assert_eq!(diff, PxPosition::new(Px(5), Px(5)));
1232    }
1233
1234    #[test]
1235    fn test_px_position_conversions() {
1236        let i32_pos: [i32; 2] = [10, -5];
1237        let px_pos: PxPosition = i32_pos.into();
1238        let back_to_i32: [i32; 2] = px_pos.into();
1239        assert_eq!(i32_pos, back_to_i32);
1240
1241        let u32_pos: [u32; 2] = [10, 5];
1242        let px_from_u32: PxPosition = u32_pos.into();
1243        let back_to_u32: [u32; 2] = px_from_u32.into();
1244        assert_eq!(u32_pos, back_to_u32);
1245    }
1246
1247    #[test]
1248    fn test_distance() {
1249        let pos1 = PxPosition::new(Px(0), Px(0));
1250        let pos2 = PxPosition::new(Px(3), Px(4));
1251        assert_eq!(pos1.distance_to(pos2), 5.0);
1252    }
1253}