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