Skip to main content

matrix_gui/helper/
lw_primitives.rs

1//! Lightweight primitive types for the matrix_gui framework.
2//!
3//! This module provides memory-efficient primitive types that are smaller than
4//! standard `embedded_graphics` primitive types. These types use smaller
5//! integer types to reduce memory footprint in embedded systems.
6//!
7//! # Types
8//!
9//! - [`LwRectangle<T, U>`]: Lightweight rectangle type with configurable coordinate and dimension types
10//!
11//! # Conversions
12//!
13//! All lightweight types support bidirectional conversion with their
14//! `embedded_graphics` counterparts using `From` and `Into` traits.
15//! Conversions use saturating casts to prevent overflow.
16//!
17//! # Example
18//!
19//! ```rust
20//! use matrix_gui::prelude::*;
21//! use embedded_graphics::primitives::Rectangle;
22//!
23//! // Convert from embedded_graphics Rectangle to LwRectangle
24//! let rect = Rectangle::new(Point::new(10, 20), Size::new(100, 200));
25//! let lw_rect: LwRectangle<i16, u16> = rect.into();
26//!
27//! // Convert back to Rectangle
28//! let rect2: Rectangle = lw_rect.into();
29//! ```
30
31use core::fmt::Debug;
32use embedded_graphics::{geometry::AnchorPoint, prelude::Size, primitives::Rectangle};
33
34use crate::prelude::DeltaResize;
35
36use super::lw_geometry::{LwPoint, LwSize};
37
38/// Lightweight rectangle type with configurable coordinate and dimension types.
39///
40/// This struct represents a 2D rectangle with a top-left point and size,
41/// using configurable integer types for coordinates and dimensions. It's designed
42/// to be more memory-efficient than standard `embedded_graphics::Rectangle`
43/// for embedded systems.
44///
45/// # Type Parameters
46///
47/// * `T` - The coordinate type for the top-left point (typically i8 or i16)
48/// * `U` - The dimension type for the size (typically u8 or u16)
49///
50/// # Example
51///
52/// ```rust
53/// use matrix_gui::prelude::*;
54///
55/// // Create a rectangle
56/// let rect = LwRectangle::new(
57///     LwPoint::new(10, 20),
58///     LwSize::new(100, 200)
59/// );
60///
61/// // Convert from embedded_graphics Rectangle
62/// let eg_rect = embedded_graphics::primitives::Rectangle::new(
63///     embedded_graphics::geometry::Point::new(10, 20),
64///     embedded_graphics::geometry::Size::new(100, 200)
65/// );
66/// let lw_rect: LwRectangle<i16, u16> = eg_rect.into();
67/// ```
68#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
69pub struct LwRectangle<T, U> {
70    /// The top-left corner of the rectangle.
71    pub top_left: LwPoint<T>,
72    /// The size of the rectangle.
73    pub size: LwSize<U>,
74}
75
76impl<T, U> LwRectangle<T, U>
77where
78    T: Copy + Clone + Eq + PartialEq + Debug + Default,
79    U: Copy + Clone + Eq + PartialEq + Debug + Default,
80{
81    /// Creates a new `LwRectangle` with the specified top-left point and size.
82    ///
83    /// # Arguments
84    ///
85    /// * `top_left` - The top-left corner point.
86    /// * `size` - The size of the rectangle.
87    ///
88    /// # Returns
89    ///
90    /// A new `LwRectangle` instance.
91    pub const fn new(top_left: LwPoint<T>, size: LwSize<U>) -> Self {
92        Self { top_left, size }
93    }
94}
95
96impl LwRectangle<i16, u16> {
97    /// Scales the rectangle around its center by the given percentage.
98    ///
99    /// # Arguments
100    ///
101    /// * `percent` - The percentage to scale by (100% means no change).
102    ///               200% means double the size, 50% means half the size, etc.
103    pub fn center_scale(&self, horizontal_percent: u16, vertical_percent: u16) -> Self {
104        let scale_width_factor = horizontal_percent as i16 - 100;
105        let scale_height_factor = vertical_percent as i16 - 100;
106
107        let delta_width = (self.size.width as i32 * scale_width_factor as i32 / 100) as i16;
108        let delta_height = (self.size.height as i32 * scale_height_factor as i32 / 100) as i16;
109
110        let mut area = self.rectangle();
111        let (size, ap) = DeltaResize::Center(delta_width, delta_height).transform(&area.size);
112        area = area.resized(size, ap);
113
114        area.into()
115    }
116
117    /// Resizes the rectangle using the specified resize delta.
118    pub fn delta_resize(&self, delta: DeltaResize) -> Self {
119        let area = self.rectangle();
120        let (size, ap) = delta.transform(&area.size);
121        area.resized(size, ap).into()
122    }
123
124    /// Moves the rectangle by the specified amount.
125    pub fn move_by(&self, dx: i16, dy: i16) -> Self {
126        Self {
127            top_left: self.top_left.offset(dx, dy),
128            size: self.size,
129        }
130    }
131
132    /// Converts the `LwRectangle` to an `embedded_graphics::Rectangle`.
133    pub fn rectangle(&self) -> Rectangle {
134        (*self).into()
135    }
136
137    /// Resizes the rectangle to the specified width and height, using the given anchor point.
138    pub fn resized(&self, width: u16, height: u16, anchor_point: AnchorPoint) -> Self {
139        let size = Size::new(width as u32, height as u32);
140
141        self.rectangle().resized(size, anchor_point).into()
142    }
143}
144
145/// Converts an `embedded_graphics::Rectangle` to `LwRectangle<i8, u8>` using saturating cast.
146impl From<Rectangle> for LwRectangle<i8, u8> {
147    fn from(rect: Rectangle) -> Self {
148        Self {
149            top_left: rect.top_left.into(),
150            size: rect.size.into(),
151        }
152    }
153}
154
155/// Converts an `embedded_graphics::Rectangle` to `LwRectangle<i16, u16>` using saturating cast.
156impl From<Rectangle> for LwRectangle<i16, u16> {
157    fn from(rect: Rectangle) -> Self {
158        Self {
159            top_left: rect.top_left.into(),
160            size: rect.size.into(),
161        }
162    }
163}
164
165/// Converts `LwRectangle<i8, u8>` to `embedded_graphics::Rectangle`.
166impl From<LwRectangle<i8, u8>> for Rectangle {
167    fn from(lw_rect: LwRectangle<i8, u8>) -> Self {
168        Rectangle::new(lw_rect.top_left.into(), lw_rect.size.into())
169    }
170}
171
172/// Converts `LwRectangle<i16, u16>` to `embedded_graphics::Rectangle`.
173impl From<LwRectangle<i16, u16>> for Rectangle {
174    fn from(lw_rect: LwRectangle<i16, u16>) -> Self {
175        Rectangle::new(lw_rect.top_left.into(), lw_rect.size.into())
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use embedded_graphics::geometry::Point;
183    use embedded_graphics::geometry::Size;
184
185    #[test]
186    fn test_lw_rect_i8_u8_from_rectangle() {
187        let rect = Rectangle::new(Point::new(10, -20), Size::new(100, 200));
188        let lw_rect: LwRectangle<i8, u8> = rect.into();
189        assert_eq!(lw_rect.top_left.x, 10);
190        assert_eq!(lw_rect.top_left.y, -20);
191        assert_eq!(lw_rect.size.width, 100);
192        assert_eq!(lw_rect.size.height, 200);
193    }
194
195    #[test]
196    fn test_lw_rect_i8_u8_to_rectangle() {
197        let lw_rect = LwRectangle {
198            top_left: LwPoint { x: 5i8, y: -10i8 },
199            size: LwSize {
200                width: 50u8,
201                height: 100u8,
202            },
203        };
204        let rect: Rectangle = lw_rect.into();
205        assert_eq!(rect.top_left, Point::new(5, -10));
206        assert_eq!(rect.size, Size::new(50, 100));
207    }
208
209    #[test]
210    fn test_lw_rect_i16_u16_from_rectangle() {
211        let rect = Rectangle::new(Point::new(1000, -2000), Size::new(5000, 10000));
212        let lw_rect: LwRectangle<i16, u16> = rect.into();
213        assert_eq!(lw_rect.top_left.x, 1000);
214        assert_eq!(lw_rect.top_left.y, -2000);
215        assert_eq!(lw_rect.size.width, 5000);
216        assert_eq!(lw_rect.size.height, 10000);
217    }
218
219    #[test]
220    fn test_lw_rect_i16_u16_to_rectangle() {
221        let lw_rect = LwRectangle {
222            top_left: LwPoint {
223                x: 500i16,
224                y: -1000i16,
225            },
226            size: LwSize {
227                width: 500u16,
228                height: 1000u16,
229            },
230        };
231        let rect: Rectangle = lw_rect.into();
232        assert_eq!(rect.top_left, Point::new(500, -1000));
233        assert_eq!(rect.size, Size::new(500, 1000));
234    }
235
236    #[test]
237    fn test_lw_rect_i16_u16_large_value() {
238        let rect = Rectangle::new(
239            Point::new(i32::MAX, i32::MIN),
240            Size::new(u32::MAX, u32::MAX),
241        );
242        let lw_rect: LwRectangle<i16, u16> = rect.into();
243        assert_eq!(lw_rect.top_left.x, i16::MAX);
244        assert_eq!(lw_rect.top_left.y, i16::MIN);
245        assert_eq!(lw_rect.size.width, u16::MAX);
246        assert_eq!(lw_rect.size.height, u16::MAX);
247    }
248
249    #[test]
250    fn test_lw_rect_i8_u8_overflow() {
251        let rect = Rectangle::new(Point::new(200, -200), Size::new(300, 300));
252        let lw_rect: LwRectangle<i8, u8> = rect.into();
253        assert_eq!(lw_rect.top_left.x, 127);
254        assert_eq!(lw_rect.top_left.y, -128);
255        assert_eq!(lw_rect.size.width, u8::MAX);
256        assert_eq!(lw_rect.size.height, u8::MAX);
257    }
258
259    #[test]
260    fn test_lw_rect_zero() {
261        let rect = Rectangle::new(Point::new(0, 0), Size::new(0, 0));
262        let lw_rect_i8_u8: LwRectangle<i8, u8> = rect.into();
263        let lw_rect_i16_u16: LwRectangle<i16, u16> = rect.into();
264        assert_eq!(lw_rect_i8_u8.top_left.x, 0);
265        assert_eq!(lw_rect_i16_u16.size.width, 0);
266    }
267}