tessera_ui/
px.rs

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