1use crate::widget::Rect;
8
9pub trait AlphaMask {
19 fn row(&self, x: i32, y: i32, coverage: &mut [u8]);
21
22 fn alpha_at(&self, x: i32, y: i32) -> u8 {
28 let mut coverage = [0u8; 1];
29 self.row(x, y, &mut coverage);
30 coverage[0]
31 }
32}
33
34impl<T: AlphaMask + ?Sized> AlphaMask for &T {
35 fn row(&self, x: i32, y: i32, coverage: &mut [u8]) {
36 (**self).row(x, y, coverage);
37 }
38
39 fn alpha_at(&self, x: i32, y: i32) -> u8 {
40 (**self).alpha_at(x, y)
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct RectMask {
47 rect: Rect,
48}
49
50impl RectMask {
51 pub const fn new(rect: Rect) -> Self {
53 Self { rect }
54 }
55
56 pub const fn rect(&self) -> Rect {
58 self.rect
59 }
60}
61
62impl From<Rect> for RectMask {
63 fn from(rect: Rect) -> Self {
64 Self::new(rect)
65 }
66}
67
68impl AlphaMask for RectMask {
69 fn row(&self, x: i32, y: i32, coverage: &mut [u8]) {
70 let Some(edges) = rect_edges(self.rect) else {
71 coverage.fill(0);
72 return;
73 };
74
75 let (_, y0, _, y1) = edges;
76 let y = i64::from(y);
77 if y < y0 || y >= y1 {
78 coverage.fill(0);
79 return;
80 }
81
82 let (x0, _, x1, _) = edges;
83 for (offset, alpha) in coverage.iter_mut().enumerate() {
84 let px = absolute_x(x, offset);
85 *alpha = if px >= x0 && px < x1 { 255 } else { 0 };
86 }
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub struct RoundedRectMask {
99 rect: Rect,
100 radius: u8,
101}
102
103impl RoundedRectMask {
104 pub const fn new(rect: Rect, radius: u8) -> Self {
106 Self { rect, radius }
107 }
108
109 pub const fn rect(&self) -> Rect {
111 self.rect
112 }
113
114 pub const fn radius(&self) -> u8 {
116 self.radius
117 }
118}
119
120impl AlphaMask for RoundedRectMask {
121 fn row(&self, x: i32, y: i32, coverage: &mut [u8]) {
122 let Some(edges) = rect_edges(self.rect) else {
123 coverage.fill(0);
124 return;
125 };
126
127 let (x0, y0, x1, y1) = edges;
128 let y = i64::from(y);
129 if y < y0 || y >= y1 {
130 coverage.fill(0);
131 return;
132 }
133
134 let radius = rounded_radius(self.rect, self.radius);
135 if radius <= 0 {
136 RectMask::new(self.rect).row(x, i32::try_from(y).unwrap_or(i32::MAX), coverage);
137 return;
138 }
139
140 for (offset, alpha) in coverage.iter_mut().enumerate() {
141 let px = absolute_x(x, offset);
142 if px < x0 || px >= x1 {
143 *alpha = 0;
144 } else {
145 *alpha = rounded_rect_pixel_alpha(edges, radius, px, y);
146 }
147 }
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub enum FadeDirection {
154 LeftToRight,
156 RightToLeft,
158 TopToBottom,
160 BottomToTop,
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub struct FadeMask {
171 rect: Rect,
172 direction: FadeDirection,
173 start_opacity: u8,
174 end_opacity: u8,
175}
176
177impl FadeMask {
178 pub const fn new(
183 rect: Rect,
184 direction: FadeDirection,
185 start_opacity: u8,
186 end_opacity: u8,
187 ) -> Self {
188 Self {
189 rect,
190 direction,
191 start_opacity,
192 end_opacity,
193 }
194 }
195
196 pub const fn rect(&self) -> Rect {
198 self.rect
199 }
200
201 pub const fn direction(&self) -> FadeDirection {
203 self.direction
204 }
205
206 pub const fn start_opacity(&self) -> u8 {
208 self.start_opacity
209 }
210
211 pub const fn end_opacity(&self) -> u8 {
213 self.end_opacity
214 }
215}
216
217impl AlphaMask for FadeMask {
218 fn row(&self, x: i32, y: i32, coverage: &mut [u8]) {
219 let Some((x0, y0, x1, y1)) = rect_edges(self.rect) else {
220 coverage.fill(0);
221 return;
222 };
223
224 let y = i64::from(y);
225 if y < y0 || y >= y1 {
226 coverage.fill(0);
227 return;
228 }
229
230 let span = match self.direction {
231 FadeDirection::LeftToRight | FadeDirection::RightToLeft => {
232 i64::from(self.rect.width - 1)
233 }
234 FadeDirection::TopToBottom | FadeDirection::BottomToTop => {
235 i64::from(self.rect.height - 1)
236 }
237 };
238
239 for (offset, alpha) in coverage.iter_mut().enumerate() {
240 let px = absolute_x(x, offset);
241 if px < x0 || px >= x1 {
242 *alpha = 0;
243 continue;
244 }
245
246 let pos = match self.direction {
247 FadeDirection::LeftToRight => px - x0,
248 FadeDirection::RightToLeft => x1 - 1 - px,
249 FadeDirection::TopToBottom => y - y0,
250 FadeDirection::BottomToTop => y1 - 1 - y,
251 };
252 *alpha = lerp_opacity(self.start_opacity, self.end_opacity, pos, span);
253 }
254 }
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267pub struct ArcMask {
268 center: (i32, i32),
269 outer_radius: u16,
270 inner_radius: u16,
271 start_deg: i16,
272 end_deg: i16,
273}
274
275impl ArcMask {
276 pub const fn new(
282 center: (i32, i32),
283 outer_radius: u16,
284 inner_radius: u16,
285 start_deg: i16,
286 end_deg: i16,
287 ) -> Self {
288 Self {
289 center,
290 outer_radius,
291 inner_radius,
292 start_deg,
293 end_deg,
294 }
295 }
296
297 pub const fn center(&self) -> (i32, i32) {
299 self.center
300 }
301
302 pub const fn outer_radius(&self) -> u16 {
304 self.outer_radius
305 }
306
307 pub const fn inner_radius(&self) -> u16 {
309 self.inner_radius
310 }
311
312 pub const fn start_deg(&self) -> i16 {
314 self.start_deg
315 }
316
317 pub const fn end_deg(&self) -> i16 {
319 self.end_deg
320 }
321}
322
323impl AlphaMask for ArcMask {
324 fn row(&self, x: i32, y: i32, coverage: &mut [u8]) {
325 if self.outer_radius <= self.inner_radius || arc_sweep(self.start_deg, self.end_deg) == 0 {
326 coverage.fill(0);
327 return;
328 }
329
330 let cy = i64::from(self.center.1);
331 let outer = i64::from(self.outer_radius);
332 let y = i64::from(y);
333 if y + 1 < cy - outer || y > cy + outer {
334 coverage.fill(0);
335 return;
336 }
337
338 for (offset, alpha) in coverage.iter_mut().enumerate() {
339 *alpha = arc_pixel_alpha(self, absolute_x(x, offset), y);
340 }
341 }
342}
343
344#[derive(Debug, Clone, Copy, PartialEq, Eq)]
346pub struct IntersectMask<A, B> {
347 first: A,
348 second: B,
349}
350
351impl<A, B> IntersectMask<A, B> {
352 pub const fn new(first: A, second: B) -> Self {
354 Self { first, second }
355 }
356
357 pub fn into_inner(self) -> (A, B) {
359 (self.first, self.second)
360 }
361}
362
363impl<A: AlphaMask, B: AlphaMask> AlphaMask for IntersectMask<A, B> {
364 fn row(&self, x: i32, y: i32, coverage: &mut [u8]) {
365 for (offset, alpha) in coverage.iter_mut().enumerate() {
366 let px = absolute_x_i32(x, offset);
367 *alpha = self.first.alpha_at(px, y).min(self.second.alpha_at(px, y));
368 }
369 }
370}
371
372#[derive(Debug, Clone, Copy, PartialEq, Eq)]
374pub struct UnionMask<A, B> {
375 first: A,
376 second: B,
377}
378
379impl<A, B> UnionMask<A, B> {
380 pub const fn new(first: A, second: B) -> Self {
382 Self { first, second }
383 }
384
385 pub fn into_inner(self) -> (A, B) {
387 (self.first, self.second)
388 }
389}
390
391impl<A: AlphaMask, B: AlphaMask> AlphaMask for UnionMask<A, B> {
392 fn row(&self, x: i32, y: i32, coverage: &mut [u8]) {
393 for (offset, alpha) in coverage.iter_mut().enumerate() {
394 let px = absolute_x_i32(x, offset);
395 *alpha = self.first.alpha_at(px, y).max(self.second.alpha_at(px, y));
396 }
397 }
398}
399
400fn rect_edges(rect: Rect) -> Option<(i64, i64, i64, i64)> {
401 if rect.width <= 0 || rect.height <= 0 {
402 return None;
403 }
404
405 let x0 = i64::from(rect.x);
406 let y0 = i64::from(rect.y);
407 Some((
408 x0,
409 y0,
410 x0 + i64::from(rect.width),
411 y0 + i64::from(rect.height),
412 ))
413}
414
415fn absolute_x(x: i32, offset: usize) -> i64 {
416 let offset = i64::try_from(offset).unwrap_or(i64::MAX);
417 i64::from(x).saturating_add(offset)
418}
419
420fn absolute_x_i32(x: i32, offset: usize) -> i32 {
421 let offset = i32::try_from(offset).unwrap_or(i32::MAX);
422 x.saturating_add(offset)
423}
424
425fn lerp_opacity(start: u8, end: u8, pos: i64, span: i64) -> u8 {
426 if span <= 0 {
427 return end;
428 }
429
430 let value = i64::from(start) + (i64::from(end) - i64::from(start)) * pos / span;
431 value.clamp(0, 255) as u8
432}
433
434const SUBPIXEL_SCALE: i64 = 8;
435const SUBPIXEL_OFFSETS: [i64; 4] = [1, 3, 5, 7];
436const SUBPIXEL_SAMPLES: u16 = 16;
437
438fn rounded_radius(rect: Rect, requested: u8) -> i64 {
439 let Some((x0, y0, x1, y1)) = rect_edges(rect) else {
440 return 0;
441 };
442 i64::from(requested).min((x1 - x0) / 2).min((y1 - y0) / 2)
443}
444
445fn rounded_rect_pixel_alpha(edges: (i64, i64, i64, i64), radius: i64, x: i64, y: i64) -> u8 {
446 let mut inside = 0u16;
447 for sy in SUBPIXEL_OFFSETS {
448 let sample_y = y.saturating_mul(SUBPIXEL_SCALE).saturating_add(sy);
449 for sx in SUBPIXEL_OFFSETS {
450 let sample_x = x.saturating_mul(SUBPIXEL_SCALE).saturating_add(sx);
451 if rounded_rect_sample_inside(edges, radius, sample_x, sample_y) {
452 inside += 1;
453 }
454 }
455 }
456 coverage_from_samples(inside)
457}
458
459fn rounded_rect_sample_inside(
460 (x0, y0, x1, y1): (i64, i64, i64, i64),
461 radius: i64,
462 sample_x: i64,
463 sample_y: i64,
464) -> bool {
465 let x0 = x0.saturating_mul(SUBPIXEL_SCALE);
466 let y0 = y0.saturating_mul(SUBPIXEL_SCALE);
467 let x1 = x1.saturating_mul(SUBPIXEL_SCALE);
468 let y1 = y1.saturating_mul(SUBPIXEL_SCALE);
469 if sample_x < x0 || sample_x >= x1 || sample_y < y0 || sample_y >= y1 {
470 return false;
471 }
472
473 let radius = radius.saturating_mul(SUBPIXEL_SCALE);
474 let left_center = x0.saturating_add(radius);
475 let right_center = x1.saturating_sub(radius);
476 let top_center = y0.saturating_add(radius);
477 let bottom_center = y1.saturating_sub(radius);
478
479 if (sample_x >= left_center && sample_x < right_center)
480 || (sample_y >= top_center && sample_y < bottom_center)
481 {
482 return true;
483 }
484
485 let center_x = if sample_x < left_center {
486 left_center
487 } else {
488 right_center
489 };
490 let center_y = if sample_y < top_center {
491 top_center
492 } else {
493 bottom_center
494 };
495 let dx = sample_x - center_x;
496 let dy = sample_y - center_y;
497 dx * dx + dy * dy <= radius * radius
498}
499
500fn arc_pixel_alpha(mask: &ArcMask, x: i64, y: i64) -> u8 {
501 let mut inside = 0u16;
502 for sy in SUBPIXEL_OFFSETS {
503 let sample_y = y.saturating_mul(SUBPIXEL_SCALE).saturating_add(sy);
504 for sx in SUBPIXEL_OFFSETS {
505 let sample_x = x.saturating_mul(SUBPIXEL_SCALE).saturating_add(sx);
506 if arc_sample_inside(mask, sample_x, sample_y) {
507 inside += 1;
508 }
509 }
510 }
511 coverage_from_samples(inside)
512}
513
514fn arc_sample_inside(mask: &ArcMask, sample_x: i64, sample_y: i64) -> bool {
515 let center_x = i64::from(mask.center.0).saturating_mul(SUBPIXEL_SCALE);
516 let center_y = i64::from(mask.center.1).saturating_mul(SUBPIXEL_SCALE);
517 let dx = sample_x - center_x;
518 let dy = sample_y - center_y;
519
520 let outer = i64::from(mask.outer_radius).saturating_mul(SUBPIXEL_SCALE);
521 if dx.abs() > outer || dy.abs() > outer {
522 return false;
523 }
524
525 let distance_sq = dx * dx + dy * dy;
526 if distance_sq > outer * outer {
527 return false;
528 }
529
530 let inner = i64::from(mask.inner_radius).saturating_mul(SUBPIXEL_SCALE);
531 if inner > 0 && distance_sq < inner * inner {
532 return false;
533 }
534
535 let sweep = arc_sweep(mask.start_deg, mask.end_deg);
536 if sweep >= 360 {
537 return true;
538 }
539 if dx == 0 && dy == 0 {
540 return mask.inner_radius == 0 && sweep > 0;
541 }
542
543 let angle = atan2_deg_clockwise(dx, dy);
544 let start = normalize_degrees(i32::from(mask.start_deg));
545 let relative = normalize_degrees(angle - start);
546 relative <= sweep
547}
548
549fn arc_sweep(start_deg: i16, end_deg: i16) -> i32 {
550 let raw = i32::from(end_deg) - i32::from(start_deg);
551 if raw >= 360 || raw <= -360 {
552 360
553 } else {
554 raw.rem_euclid(360)
555 }
556}
557
558fn atan2_deg_clockwise(dx: i64, dy: i64) -> i32 {
559 if dx == 0 {
560 return if dy > 0 {
561 90
562 } else if dy < 0 {
563 270
564 } else {
565 0
566 };
567 }
568
569 let abs_y = dy.abs();
570 let mut angle = if dx >= 0 {
571 let denom = dx + abs_y;
572 if denom == 0 {
573 0
574 } else {
575 45 - ((dx - abs_y) * 45 / denom) as i32
576 }
577 } else {
578 let denom = abs_y - dx;
579 135 - ((dx + abs_y) * 45 / denom) as i32
580 };
581
582 if dy < 0 {
583 angle = 360 - angle;
584 }
585 normalize_degrees(angle)
586}
587
588fn normalize_degrees(degrees: i32) -> i32 {
589 degrees.rem_euclid(360)
590}
591
592fn coverage_from_samples(inside: u16) -> u8 {
593 match inside {
594 0 => 0,
595 SUBPIXEL_SAMPLES => 255,
596 _ => ((inside * 255 + SUBPIXEL_SAMPLES / 2) / SUBPIXEL_SAMPLES) as u8,
597 }
598}