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}