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