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}