Skip to main content

embedded_gui/
round.rs

1//! Circular / Round Screen Geometry and Unobstructed Area adaptation.
2//!
3//! Provides line chord width calculations for circular displays (e.g. 180×180 /
4//! 240×240 GC9A01 LCDs) and reactive unobstructed area layout management.
5
6use crate::geometry::Rect;
7#[cfg(not(feature = "std"))]
8use crate::math::F32Ext as _;
9
10/// Calculates the horizontal chord width across a circle at a given vertical distance from center.
11///
12/// Formula: $w = 2 \times \sqrt{R^2 - y^2}$
13#[inline]
14pub fn circle_chord_width(radius: u32, y_offset_from_center: i32) -> u32 {
15    let r = radius as i32;
16    let y = y_offset_from_center.abs();
17    if y >= r {
18        return 0;
19    }
20    let r2 = r * r;
21    let y2 = y * y;
22    let half_w = ((r2 - y2) as f32).sqrt() as u32;
23    half_w * 2
24}
25
26/// Calculates the safe bounding rectangle for text or widget lines on a circular display.
27///
28/// Ensures elements remain fully within the circular screen perimeter at the given vertical position.
29pub fn round_screen_line_bounds(diameter: u32, line_y: i32, line_height: u32) -> Rect {
30    let radius = (diameter / 2) as i32;
31    let center_y = radius;
32
33    // Check top and bottom edges of the line
34    let y_top = line_y - center_y;
35    let y_bottom = (line_y + line_height as i32) - center_y;
36
37    let w_top = circle_chord_width(radius as u32, y_top);
38    let w_bottom = circle_chord_width(radius as u32, y_bottom);
39    let min_width = w_top.min(w_bottom);
40
41    let offset_x = (radius - (min_width as i32 / 2)).max(0);
42    Rect::new(offset_x, line_y, min_width, line_height)
43}
44
45/// Manages dynamic unobstructed screen bounds when system overlays or banners
46/// (e.g., status bars, timeline peek, heads-up notifications) cover portions of the display.
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub struct UnobstructedArea {
49    pub screen_size: Rect,
50    pub inset_top: u16,
51    pub inset_bottom: u16,
52    pub inset_left: u16,
53    pub inset_right: u16,
54}
55
56impl UnobstructedArea {
57    pub const fn new(screen_size: Rect) -> Self {
58        Self {
59            screen_size,
60            inset_top: 0,
61            inset_bottom: 0,
62            inset_left: 0,
63            inset_right: 0,
64        }
65    }
66
67    pub fn set_insets(&mut self, top: u16, bottom: u16, left: u16, right: u16) {
68        self.inset_top = top;
69        self.inset_bottom = bottom;
70        self.inset_left = left;
71        self.inset_right = right;
72    }
73
74    /// Returns the currently visible and unobstructed rectangle.
75    pub fn visible_rect(&self) -> Rect {
76        let x = self.screen_size.x + self.inset_left as i32;
77        let y = self.screen_size.y + self.inset_top as i32;
78        let w = self
79            .screen_size
80            .w
81            .saturating_sub((self.inset_left + self.inset_right) as u32);
82        let h = self
83            .screen_size
84            .h
85            .saturating_sub((self.inset_top + self.inset_bottom) as u32);
86        Rect::new(x, y, w, h)
87    }
88
89    /// Returns whether the display is partially obstructed.
90    pub const fn is_obstructed(&self) -> bool {
91        self.inset_top > 0 || self.inset_bottom > 0 || self.inset_left > 0 || self.inset_right > 0
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_circle_chord_calculations() {
101        let radius = 90; // 180px diameter circle
102        // At center (y = 0), chord width equals full diameter
103        assert_eq!(circle_chord_width(radius, 0), 180);
104
105        // Near top/bottom (y = 80)
106        let w = circle_chord_width(radius, 80);
107        assert!(w > 0 && w < 180);
108
109        // Out of bounds
110        assert_eq!(circle_chord_width(radius, 95), 0);
111    }
112
113    #[test]
114    fn test_round_screen_line_bounds() {
115        let diameter = 180;
116        let center_line = round_screen_line_bounds(diameter, 80, 20);
117        assert!(center_line.w > 160);
118        assert_eq!(center_line.y, 80);
119    }
120
121    #[test]
122    fn test_unobstructed_area_rect() {
123        let mut area = UnobstructedArea::new(Rect::new(0, 0, 144, 168));
124        assert_eq!(area.visible_rect(), Rect::new(0, 0, 144, 168));
125        assert!(!area.is_obstructed());
126
127        area.set_insets(16, 24, 0, 0);
128        assert!(area.is_obstructed());
129        assert_eq!(area.visible_rect(), Rect::new(0, 16, 144, 128));
130    }
131}