Skip to main content

matrix_gui/helper/
lw_geometry.rs

1//! Lightweight geometry types for the matrix_gui framework.
2//!
3//! This module provides memory-efficient geometry types that are smaller than
4//! the standard `embedded_graphics` types. These types use smaller integer
5//! types (i8, i16, u8, u16) to reduce memory footprint in embedded systems.
6//!
7//! # Types
8//!
9//! - [`LwPoint<T>`]: Lightweight point type with configurable coordinate type
10//! - [`LwSize<T>`]: Lightweight size type with configurable dimension type
11//! - [`DeltaResize`]: Enum for specifying resize operations with anchor points
12//!
13//! # Conversions
14//!
15//! All lightweight types support bidirectional conversion with their
16//! `embedded_graphics` counterparts using `From` and `Into` traits.
17//! Conversions use saturating casts to prevent overflow.
18//!
19//! # Example
20//!
21//! ```rust
22//! use matrix_gui::prelude::*;
23//! use embedded_graphics::prelude::*;
24//!
25//! // Convert from embedded_graphics Point to LwPoint
26//! let point = Point::new(100, 200);
27//! let lw_point: LwPoint<i16> = point.into();
28//!
29//! // Convert back to Point
30//! let point2: Point = lw_point.into();
31//! ```
32
33use embedded_graphics::geometry::AnchorPoint;
34use embedded_graphics::geometry::Point;
35use embedded_graphics::geometry::Size;
36use saturating_cast::SaturatingCast;
37
38/// Lightweight point type with configurable coordinate type.
39///
40/// This struct represents a 2D point with x and y coordinates using a
41/// configurable integer type. It's designed to be more memory-efficient
42/// than the standard `embedded_graphics::Point` for embedded systems.
43///
44/// # Type Parameters
45///
46/// * `T` - The coordinate type (typically i8 or i16)
47///
48/// # Example
49///
50/// ```rust
51/// use matrix_gui::prelude::*;
52///
53/// // Create a point
54/// let point = LwPoint::new(10, 20);
55///
56/// // Convert from embedded_graphics Point
57/// let eg_point = embedded_graphics::prelude::Point::new(100, 200);
58/// let lw_point: LwPoint<i16> = eg_point.into();
59/// ```
60#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
61pub struct LwPoint<T> {
62    /// The x-coordinate.
63    pub x: T,
64    /// The y-coordinate.
65    pub y: T,
66}
67
68impl<T> LwPoint<T> {
69    /// Creates a new `LwPoint` with the specified coordinates.
70    ///
71    /// # Arguments
72    ///
73    /// * `x` - The x-coordinate.
74    /// * `y` - The y-coordinate.
75    ///
76    /// # Returns
77    ///
78    /// A new `LwPoint` instance.
79    pub const fn new(x: T, y: T) -> Self {
80        Self { x, y }
81    }
82}
83/// Offset the point by the given amount.
84impl LwPoint<i8> {
85    pub const fn offset(&self, dx: i8, dy: i8) -> Self {
86        Self {
87            x: self.x + dx,
88            y: self.y + dy,
89        }
90    }
91}
92
93/// Offset the point by the given amount.
94impl LwPoint<i16> {
95    pub const fn offset(&self, dx: i16, dy: i16) -> Self {
96        Self {
97            x: self.x + dx,
98            y: self.y + dy,
99        }
100    }
101}
102
103/// Converts an `embedded_graphics::Point` to `LwPoint<i8>` using saturating cast.
104impl From<Point> for LwPoint<i8> {
105    fn from(point: Point) -> Self {
106        Self {
107            x: point.x.saturating_cast(),
108            y: point.y.saturating_cast(),
109        }
110    }
111}
112
113/// Converts an `embedded_graphics::Point` to `LwPoint<i16>` using saturating cast.
114impl From<Point> for LwPoint<i16> {
115    fn from(point: Point) -> Self {
116        Self {
117            x: point.x.saturating_cast(),
118            y: point.y.saturating_cast(),
119        }
120    }
121}
122
123/// Converts `LwPoint<i8>` to `embedded_graphics::Point`.
124impl From<LwPoint<i8>> for Point {
125    fn from(lw_point: LwPoint<i8>) -> Self {
126        Point::new(lw_point.x as i32, lw_point.y as i32)
127    }
128}
129
130/// Converts `LwPoint<i16>` to `embedded_graphics::Point`.
131impl From<LwPoint<i16>> for Point {
132    fn from(lw_point: LwPoint<i16>) -> Self {
133        Point::new(lw_point.x as i32, lw_point.y as i32)
134    }
135}
136
137/// Lightweight size type with configurable dimension type.
138///
139/// This struct represents a 2D size with width and height dimensions using a
140/// configurable integer type. It's designed to be more memory-efficient
141/// than standard `embedded_graphics::Size` for embedded systems.
142///
143/// # Type Parameters
144///
145/// * `T` - The dimension type (typically u8 or u16)
146///
147/// # Example
148///
149/// ```rust
150/// use matrix_gui::prelude::*;
151///
152/// // Create a size
153/// let size = LwSize::new(100, 200);
154///
155/// // Convert from embedded_graphics Size
156/// let eg_size = embedded_graphics::prelude::Size::new(100, 200);
157/// let lw_size: LwSize<u16> = eg_size.into();
158/// ```
159#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
160pub struct LwSize<T> {
161    /// The width dimension.
162    pub width: T,
163    /// The height dimension.
164    pub height: T,
165}
166
167impl<T> LwSize<T> {
168    /// Creates a new `LwSize` with specified dimensions.
169    ///
170    /// # Arguments
171    ///
172    /// * `width` - The width dimension.
173    /// * `height` - The height dimension.
174    ///
175    /// # Returns
176    ///
177    /// A new `LwSize` instance.
178    pub const fn new(width: T, height: T) -> Self {
179        Self { width, height }
180    }
181}
182
183/// Converts an `embedded_graphics::Size` to `LwSize<u8>` using saturating cast.
184impl From<Size> for LwSize<u8> {
185    fn from(size: Size) -> Self {
186        Self {
187            width: size.width.saturating_cast(),
188            height: size.height.saturating_cast(),
189        }
190    }
191}
192
193/// Converts an `embedded_graphics::Size` to `LwSize<u16>` using saturating cast.
194impl From<Size> for LwSize<u16> {
195    fn from(size: Size) -> Self {
196        Self {
197            width: size.width.saturating_cast(),
198            height: size.height.saturating_cast(),
199        }
200    }
201}
202
203/// Converts `LwSize<u8>` to `embedded_graphics::Size`.
204impl From<LwSize<u8>> for Size {
205    fn from(lw_size: LwSize<u8>) -> Self {
206        Size::new(lw_size.width as u32, lw_size.height as u32)
207    }
208}
209
210/// Converts `LwSize<u16>` to `embedded_graphics::Size`.
211impl From<LwSize<u16>> for Size {
212    fn from(lw_size: LwSize<u16>) -> Self {
213        Size::new(lw_size.width as u32, lw_size.height as u32)
214    }
215}
216
217/// Delta resize handle with anchor point specification.
218///
219/// This enum represents different resize operations for widgets, specifying
220/// both the size delta and the anchor point to use during resizing.
221/// Each variant represents a different corner or edge of a widget.
222///
223/// # Variants
224///
225/// - `TopLeft(w, h)`: Resize from top-left corner
226/// - `TopCenter(w, h)`: Resize from top edge (centered)
227/// - `TopRight(w, h)`: Resize from top-right corner
228/// - `CenterLeft(w, h)`: Resize from left edge (centered)
229/// - `Center(w, h)`: Resize from center
230/// - `CenterRight(w, h)`: Resize from right edge (centered)
231/// - `BottomLeft(w, h)`: Resize from bottom-left corner
232/// - `BottomCenter(w, h)`: Resize from bottom edge (centered)
233/// - `BottomRight(w, h)`: Resize from bottom-right corner
234#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone)]
235pub enum DeltaResize {
236    /// Resize from top-left corner with width and height deltas.
237    TopLeft(i16, i16),
238    /// Resize from top edge (centered) with width and height deltas.
239    TopCenter(i16, i16),
240    /// Resize from top-right corner with width and height deltas.
241    TopRight(i16, i16),
242    /// Resize from left edge (centered) with width and height deltas.
243    CenterLeft(i16, i16),
244    /// Resize from center with width and height deltas.
245    Center(i16, i16),
246    /// Resize from right edge (centered) with width and height deltas.
247    CenterRight(i16, i16),
248    /// Resize from bottom-left corner with width and height deltas.
249    BottomLeft(i16, i16),
250    /// Resize from bottom edge (centered) with width and height deltas.
251    BottomCenter(i16, i16),
252    /// Resize from bottom-right corner with width and height deltas.
253    BottomRight(i16, i16),
254}
255
256impl DeltaResize {
257    pub const fn unwrap(&self) -> (i32, i32, AnchorPoint) {
258        let (delta_w, delta_h, ap) = match *self {
259            DeltaResize::TopLeft(w, h) => (w, h, AnchorPoint::TopLeft),
260            DeltaResize::TopCenter(w, h) => (w, h, AnchorPoint::TopCenter),
261            DeltaResize::TopRight(w, h) => (w, h, AnchorPoint::TopRight),
262            DeltaResize::CenterLeft(w, h) => (w, h, AnchorPoint::CenterLeft),
263            DeltaResize::Center(w, h) => (w, h, AnchorPoint::Center),
264            DeltaResize::CenterRight(w, h) => (w, h, AnchorPoint::CenterRight),
265            DeltaResize::BottomLeft(w, h) => (w, h, AnchorPoint::BottomLeft),
266            DeltaResize::BottomCenter(w, h) => (w, h, AnchorPoint::BottomCenter),
267            DeltaResize::BottomRight(w, h) => (w, h, AnchorPoint::BottomRight),
268        };
269
270        (delta_w as i32, delta_h as i32, ap)
271    }
272
273    /// Applies the resize deltas to the given size and returns
274    /// the new size along with the appropriate anchor point.
275    ///
276    /// # Arguments
277    ///
278    /// * `size` - The original size to resize.
279    ///
280    /// # Returns
281    ///
282    /// A tuple containing:
283    /// - The new size as `Size`
284    /// - The anchor point as `AnchorPoint`
285    pub const fn transform(&self, size: &Size) -> (Size, AnchorPoint) {
286        let (delta_w, delta_h, anchor_point) = self.unwrap();
287        let width = size.width as i32 + delta_w;
288        let height = size.height as i32 + delta_h;
289
290        (Size::new(width as u32, height as u32), anchor_point)
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297
298    #[test]
299    fn test_lw_point_i8_from_point() {
300        let point = Point::new(10, -20);
301        let lw_point: LwPoint<i8> = point.into();
302        assert_eq!(lw_point.x, 10);
303        assert_eq!(lw_point.y, -20);
304    }
305
306    #[test]
307    fn test_lw_point_i8_to_point() {
308        let lw_point = LwPoint { x: 5i8, y: -10i8 };
309        let point: Point = lw_point.into();
310        assert_eq!(point, Point::new(5, -10));
311    }
312
313    #[test]
314    fn test_lw_point_i8_overflow() {
315        let point = Point::new(200, -200);
316        let lw_point: LwPoint<i8> = point.into();
317        assert_eq!(lw_point.x, 127);
318        assert_eq!(lw_point.y, -128);
319    }
320
321    #[test]
322    fn test_lw_point_i16_from_point() {
323        let point = Point::new(1000, -2000);
324        let lw_point: LwPoint<i16> = point.into();
325        assert_eq!(lw_point.x, 1000);
326        assert_eq!(lw_point.y, -2000);
327    }
328
329    #[test]
330    fn test_lw_point_i16_to_point() {
331        let lw_point = LwPoint {
332            x: 500i16,
333            y: -1000i16,
334        };
335        let point: Point = lw_point.into();
336        assert_eq!(point, Point::new(500, -1000));
337    }
338
339    #[test]
340    fn test_lw_point_i16_large_value() {
341        let point = Point::new(i32::MAX, i32::MIN);
342        let lw_point: LwPoint<i16> = point.into();
343        assert_eq!(lw_point.x, i16::MAX);
344        assert_eq!(lw_point.y, i16::MIN);
345    }
346
347    #[test]
348    fn test_lw_point_zero() {
349        let point = Point::new(0, 0);
350        let lw_point_i8: LwPoint<i8> = point.into();
351        let lw_point_i16: LwPoint<i16> = point.into();
352        assert_eq!(lw_point_i8.x, 0);
353        assert_eq!(lw_point_i16.y, 0);
354    }
355
356    #[test]
357    fn test_lw_point_into_i8() {
358        let point = Point::new(10, -20);
359        let lw_point: LwPoint<i8> = point.into();
360        assert_eq!(lw_point.x, 10);
361        assert_eq!(lw_point.y, -20);
362    }
363
364    #[test]
365    fn test_lw_point_into_i16() {
366        let point = Point::new(1000, -2000);
367        let lw_point: LwPoint<i16> = point.into();
368        assert_eq!(lw_point.x, 1000);
369        assert_eq!(lw_point.y, -2000);
370    }
371
372    #[test]
373    fn test_lw_point_from_i8() {
374        let lw_point = LwPoint { x: 5i8, y: -10i8 };
375        let point: Point = lw_point.into();
376        assert_eq!(point, Point::new(5, -10));
377    }
378
379    #[test]
380    fn test_lw_point_from_i16() {
381        let lw_point = LwPoint {
382            x: 500i16,
383            y: -1000i16,
384        };
385        let point: Point = lw_point.into();
386        assert_eq!(point, Point::new(500, -1000));
387    }
388
389    #[test]
390    fn test_lw_size_u8_from_size() {
391        let size = Size::new(100, 200);
392        let lw_size: LwSize<u8> = size.into();
393        assert_eq!(lw_size.width, 100);
394        assert_eq!(lw_size.height, 200);
395    }
396
397    #[test]
398    fn test_lw_size_u8_to_size() {
399        let lw_size = LwSize {
400            width: 50u8,
401            height: 100u8,
402        };
403        let size: Size = lw_size.into();
404        assert_eq!(size, Size::new(50, 100));
405    }
406
407    #[test]
408    fn test_lw_size_u8_overflow() {
409        let size = Size::new(300, 300);
410        let lw_size: LwSize<u8> = size.into();
411        assert_eq!(lw_size.width, u8::MAX);
412        assert_eq!(lw_size.height, u8::MAX);
413    }
414
415    #[test]
416    fn test_lw_size_u16_from_size() {
417        let size = Size::new(1000, 2000);
418        let lw_size: LwSize<u16> = size.into();
419        assert_eq!(lw_size.width, 1000);
420        assert_eq!(lw_size.height, 2000);
421    }
422
423    #[test]
424    fn test_lw_size_u16_to_size() {
425        let lw_size = LwSize {
426            width: 500u16,
427            height: 1000u16,
428        };
429        let size: Size = lw_size.into();
430        assert_eq!(size, Size::new(500, 1000));
431    }
432
433    #[test]
434    fn test_lw_size_u16_large_value() {
435        let size = Size::new(u32::MAX, u32::MAX);
436        let lw_size: LwSize<u16> = size.into();
437        assert_eq!(lw_size.width, u16::MAX);
438        assert_eq!(lw_size.height, u16::MAX);
439    }
440
441    #[test]
442    fn test_lw_size_zero() {
443        let size = Size::new(0, 0);
444        let lw_size_u8: LwSize<u8> = size.into();
445        let lw_size_u16: LwSize<u16> = size.into();
446        assert_eq!(lw_size_u8.width, 0);
447        assert_eq!(lw_size_u16.height, 0);
448    }
449}