Skip to main content

simple_render/ui/types/
geometry.rs

1use super::*;
2
3#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
4pub struct Bounds {
5    pub x: u32,
6    pub y: u32,
7    pub width: u32,
8    pub height: u32,
9}
10
11impl Bounds {
12    pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
13        Self {
14            x,
15            y,
16            width,
17            height,
18        }
19    }
20
21    pub(in crate::ui) fn inset(self, spacing: Spacing) -> Self {
22        let horizontal = spacing.left.saturating_add(spacing.right);
23        let vertical = spacing.top.saturating_add(spacing.bottom);
24        Self {
25            x: self.x.saturating_add(spacing.left),
26            y: self.y.saturating_add(spacing.top),
27            width: self.width.saturating_sub(horizontal),
28            height: self.height.saturating_sub(vertical),
29        }
30    }
31
32    pub(in crate::ui) fn right(self) -> u32 {
33        self.x.saturating_add(self.width)
34    }
35
36    pub(in crate::ui) fn bottom(self) -> u32 {
37        self.y.saturating_add(self.height)
38    }
39
40    pub(in crate::ui) fn intersect(self, other: Self) -> Option<Self> {
41        let x = self.x.max(other.x);
42        let y = self.y.max(other.y);
43        let right = self.right().min(other.right());
44        let bottom = self.bottom().min(other.bottom());
45        let width = right.saturating_sub(x);
46        let height = bottom.saturating_sub(y);
47        (width > 0 && height > 0).then_some(Self {
48            x,
49            y,
50            width,
51            height,
52        })
53    }
54}
55
56#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
57pub struct CornerRadius {
58    pub top_left: u32,
59    pub top_right: u32,
60    pub bottom_right: u32,
61    pub bottom_left: u32,
62}
63
64impl CornerRadius {
65    pub const ZERO: Self = Self::all(0);
66
67    pub const fn all(radius: u32) -> Self {
68        Self {
69            top_left: radius,
70            top_right: radius,
71            bottom_right: radius,
72            bottom_left: radius,
73        }
74    }
75
76    pub(in crate::ui) fn is_zero(self) -> bool {
77        self.top_left == 0 && self.top_right == 0 && self.bottom_right == 0 && self.bottom_left == 0
78    }
79
80    pub(in crate::ui) fn inset(self, spacing: Spacing) -> Self {
81        Self {
82            top_left: self.top_left.saturating_sub(spacing.top.max(spacing.left)),
83            top_right: self
84                .top_right
85                .saturating_sub(spacing.top.max(spacing.right)),
86            bottom_right: self
87                .bottom_right
88                .saturating_sub(spacing.bottom.max(spacing.right)),
89            bottom_left: self
90                .bottom_left
91                .saturating_sub(spacing.bottom.max(spacing.left)),
92        }
93    }
94
95    pub(in crate::ui) fn scaled(self, scale: u32) -> Self {
96        Self {
97            top_left: self.top_left.saturating_mul(scale),
98            top_right: self.top_right.saturating_mul(scale),
99            bottom_right: self.bottom_right.saturating_mul(scale),
100            bottom_left: self.bottom_left.saturating_mul(scale),
101        }
102    }
103
104    pub(in crate::ui) fn clamp_to(self, width: u32, height: u32) -> Self {
105        let max = width.min(height) / 2;
106        Self {
107            top_left: self.top_left.min(max),
108            top_right: self.top_right.min(max),
109            bottom_right: self.bottom_right.min(max),
110            bottom_left: self.bottom_left.min(max),
111        }
112    }
113}
114
115#[derive(Clone, Copy, Debug, PartialEq, Eq)]
116pub struct RoundedClip {
117    pub rect: Bounds,
118    pub radii: CornerRadius,
119}
120
121impl RoundedClip {
122    pub(in crate::ui) const EMPTY: Self = Self {
123        rect: Bounds {
124            x: 0,
125            y: 0,
126            width: 0,
127            height: 0,
128        },
129        radii: CornerRadius::ZERO,
130    };
131}
132
133const MAX_ROUNDED_CLIPS: usize = 4;
134
135#[derive(Clone, Copy, Debug, PartialEq, Eq)]
136pub struct Clip {
137    pub(in crate::ui) bounds: Bounds,
138    pub(in crate::ui) rounded: [RoundedClip; MAX_ROUNDED_CLIPS],
139    pub(in crate::ui) rounded_len: u8,
140}
141
142impl Clip {
143    pub const fn rect(bounds: Bounds) -> Self {
144        Self {
145            bounds,
146            rounded: [RoundedClip::EMPTY; MAX_ROUNDED_CLIPS],
147            rounded_len: 0,
148        }
149    }
150
151    pub fn rounded_rect(rect: Bounds, radius: u32) -> Self {
152        Self::rounded_rect_corners(rect, CornerRadius::all(radius))
153    }
154
155    pub fn rounded_rect_corners(rect: Bounds, radii: CornerRadius) -> Self {
156        Self::rect(rect)
157            .with_rounded_rect(rect, radii)
158            .unwrap_or_else(|| Self::rect(rect))
159    }
160
161    pub fn bounds(self) -> Bounds {
162        self.bounds
163    }
164
165    pub(in crate::ui) fn intersect_bounds(mut self, bounds: Bounds) -> Option<Self> {
166        self.bounds = self.bounds.intersect(bounds)?;
167        Some(self)
168    }
169
170    pub(in crate::ui) fn with_rounded_rect(
171        self,
172        rect: Bounds,
173        radii: CornerRadius,
174    ) -> Option<Self> {
175        let mut clip = self.intersect_bounds(rect)?;
176        if radii.is_zero() || rect.width == 0 || rect.height == 0 {
177            return Some(clip);
178        }
179
180        let index = usize::from(clip.rounded_len).min(MAX_ROUNDED_CLIPS - 1);
181        clip.rounded[index] = RoundedClip { rect, radii };
182        clip.rounded_len = clip
183            .rounded_len
184            .saturating_add(1)
185            .min(MAX_ROUNDED_CLIPS as u8);
186        Some(clip)
187    }
188
189    pub fn contains(self, x: u32, y: u32) -> bool {
190        if x < self.bounds.x
191            || y < self.bounds.y
192            || x >= self.bounds.right()
193            || y >= self.bounds.bottom()
194        {
195            return false;
196        }
197
198        self.rounded[..usize::from(self.rounded_len)]
199            .iter()
200            .all(|clip| {
201                x >= clip.rect.x
202                    && y >= clip.rect.y
203                    && super::render::rounded_rect_contains(
204                        x - clip.rect.x,
205                        y - clip.rect.y,
206                        clip.rect.width,
207                        clip.rect.height,
208                        clip.radii,
209                    )
210            })
211    }
212
213    pub(in crate::ui) fn coverage(self, x: u32, y: u32) -> u8 {
214        if x < self.bounds.x
215            || y < self.bounds.y
216            || x >= self.bounds.right()
217            || y >= self.bounds.bottom()
218        {
219            return 0;
220        }
221
222        let mut coverage = 255_u16;
223        for clip in self.rounded[..usize::from(self.rounded_len)].iter() {
224            if x < clip.rect.x
225                || y < clip.rect.y
226                || x >= clip.rect.right()
227                || y >= clip.rect.bottom()
228            {
229                return 0;
230            }
231
232            let rounded_coverage = super::render::rounded_rect_coverage(
233                x - clip.rect.x,
234                y - clip.rect.y,
235                clip.rect.width,
236                clip.rect.height,
237                clip.radii,
238            );
239            coverage = coverage * u16::from(rounded_coverage) / 255;
240            if coverage == 0 {
241                return 0;
242            }
243        }
244
245        coverage as u8
246    }
247}