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}