1use crate::media::{BackgroundSizing, StreamGeometryContext, SweepDirection};
6use crate::platform::IGeometryContext;
7use crate::utilities::{math_utilities, MathUtilities};
8use crate::{CornerRadius, Point, Rect, RoundedRect, Size, Thickness, Vector};
9
10const EPSILON: f64 = 0.00000153;
12
13pub struct GeometryBuilder;
15
16impl GeometryBuilder {
17 pub fn draw_rounded_corners_rectangle_keypoints(
25 context: &mut StreamGeometryContext,
26 keypoints: &RoundedRectKeypoints,
27 ) {
28 let mut radius_x;
29 let mut radius_y;
30
31 context.begin_figure(keypoints.top_left, true);
32
33 context.line_to(keypoints.top_right, true);
35
36 radius_x = keypoints.right_top.x - keypoints.top_right.x;
38 radius_y = keypoints.top_right.y - keypoints.right_top.y;
39 radius_x = if radius_x > 0.0 { radius_x } else { -radius_x };
40 radius_y = if radius_y > 0.0 { radius_y } else { -radius_y };
41
42 context.arc_to(
43 keypoints.right_top,
44 Size::new(radius_x, radius_y),
45 0.0,
46 false,
47 SweepDirection::Clockwise,
48 true,
49 );
50
51 context.line_to(keypoints.right_bottom, true);
53
54 radius_x = keypoints.right_bottom.x - keypoints.bottom_right.x;
56 radius_y = keypoints.bottom_right.y - keypoints.right_bottom.y;
57 radius_x = if radius_x > 0.0 { radius_x } else { -radius_x };
58 radius_y = if radius_y > 0.0 { radius_y } else { -radius_y };
59
60 if radius_x != 0.0 || radius_y != 0.0 {
61 context.arc_to(
62 keypoints.bottom_right,
63 Size::new(radius_x, radius_y),
64 0.0,
65 false,
66 SweepDirection::Clockwise,
67 true,
68 );
69 }
70
71 context.line_to(keypoints.bottom_left, true);
73
74 radius_x = keypoints.bottom_left.x - keypoints.left_bottom.x;
76 radius_y = keypoints.bottom_left.y - keypoints.left_bottom.y;
77 radius_x = if radius_x > 0.0 { radius_x } else { -radius_x };
78 radius_y = if radius_y > 0.0 { radius_y } else { -radius_y };
79
80 if radius_x != 0.0 || radius_y != 0.0 {
81 context.arc_to(
82 keypoints.left_bottom,
83 Size::new(radius_x, radius_y),
84 0.0,
85 false,
86 SweepDirection::Clockwise,
87 true,
88 );
89 }
90
91 context.line_to(keypoints.left_top, true);
93
94 radius_x = keypoints.top_left.x - keypoints.left_top.x;
96 radius_y = keypoints.top_left.y - keypoints.left_top.y;
97 radius_x = if radius_x > 0.0 { radius_x } else { -radius_x };
98 radius_y = if radius_y > 0.0 { radius_y } else { -radius_y };
99
100 if radius_x != 0.0 || radius_y != 0.0 {
101 context.arc_to(
102 keypoints.top_left,
103 Size::new(radius_x, radius_y),
104 0.0,
105 false,
106 SweepDirection::Clockwise,
107 true,
108 );
109 }
110
111 context.end_figure(true);
112 }
113
114 pub fn draw_rounded_corners_rectangle(context: &mut dyn IGeometryContext, rect: Rect, radius_x: f64, radius_y: f64) {
122 let arc_size = Size::new(radius_x, radius_y);
123
124 context.begin_figure(Point::new(rect.left() + radius_x, rect.top()), true);
142
143 context.line_to(Point::new(rect.right() - radius_x, rect.top()), true);
145 context.arc_to(
146 Point::new(rect.right(), rect.top() + radius_y),
147 arc_size,
148 0.0,
149 false,
150 SweepDirection::Clockwise,
151 true,
152 );
153
154 context.line_to(Point::new(rect.right(), rect.bottom() - radius_y), true);
156 context.arc_to(
157 Point::new(rect.right() - radius_x, rect.bottom()),
158 arc_size,
159 0.0,
160 false,
161 SweepDirection::Clockwise,
162 true,
163 );
164
165 context.line_to(Point::new(rect.left() + radius_x, rect.bottom()), true);
167 context.arc_to(
168 Point::new(rect.left(), rect.bottom() - radius_y),
169 arc_size,
170 0.0,
171 false,
172 SweepDirection::Clockwise,
173 true,
174 );
175
176 context.line_to(Point::new(rect.left(), rect.top() + radius_y), true);
178 context.arc_to(
179 Point::new(rect.left() + radius_x, rect.top()),
180 arc_size,
181 0.0,
182 false,
183 SweepDirection::Clockwise,
184 true,
185 );
186
187 context.end_figure(true);
188 }
189
190 pub fn calculate_rounded_corners_rectangle_win_ui(
203 outer_bounds: Rect,
204 border_thickness: Thickness,
205 corner_radius: CornerRadius,
206 sizing: BackgroundSizing,
207 ) -> RoundedRectKeypoints {
208 let f_outer;
220 let mut bound_rect = outer_bounds;
221
222 if sizing == BackgroundSizing::InnerBorderEdge {
223 bound_rect = outer_bounds.deflate_thickness(border_thickness);
224 f_outer = false;
225 } else if sizing == BackgroundSizing::OuterBorderEdge {
226 f_outer = true;
227 } else {
228 bound_rect = outer_bounds.deflate_thickness(border_thickness * 0.5);
232 f_outer = false;
233 }
234
235 let f_left_top;
239 let f_left_bottom;
240 let f_top_left;
241 let f_top_right;
242 let f_right_top;
243 let f_right_bottom;
244 let f_bottom_left;
245 let f_bottom_right;
246
247 let left;
248 let right;
249 let top;
250 let bottom;
251
252 if border_thickness != Thickness::default() {
255 left = 0.5 * border_thickness.left;
256 right = 0.5 * border_thickness.right;
257 top = 0.5 * border_thickness.top;
258 bottom = 0.5 * border_thickness.bottom;
259 } else {
260 left = 0.0;
261 right = 0.0;
262 top = 0.0;
263 bottom = 0.0;
264 }
265
266 if f_outer {
271 if MathUtilities::are_close_eps(corner_radius.top_left, 0.0, EPSILON) {
272 f_left_top = 0.0;
273 f_top_left = 0.0;
274 } else {
275 f_left_top = corner_radius.top_left + left;
276 f_top_left = corner_radius.top_left + top;
277 }
278
279 if MathUtilities::are_close_eps(corner_radius.top_right, 0.0, EPSILON) {
280 f_top_right = 0.0;
281 f_right_top = 0.0;
282 } else {
283 f_top_right = corner_radius.top_right + top;
284 f_right_top = corner_radius.top_right + right;
285 }
286
287 if MathUtilities::are_close_eps(corner_radius.bottom_right, 0.0, EPSILON) {
288 f_right_bottom = 0.0;
289 f_bottom_right = 0.0;
290 } else {
291 f_right_bottom = corner_radius.bottom_right + right;
292 f_bottom_right = corner_radius.bottom_right + bottom;
293 }
294
295 if MathUtilities::are_close_eps(corner_radius.bottom_left, 0.0, EPSILON) {
296 f_bottom_left = 0.0;
297 f_left_bottom = 0.0;
298 } else {
299 f_bottom_left = corner_radius.bottom_left + bottom;
300 f_left_bottom = corner_radius.bottom_left + left;
301 }
302 } else {
303 f_left_top = math_utilities::max(0.0, corner_radius.top_left - left);
304 f_top_left = math_utilities::max(0.0, corner_radius.top_left - top);
305 f_top_right = math_utilities::max(0.0, corner_radius.top_right - top);
306 f_right_top = math_utilities::max(0.0, corner_radius.top_right - right);
307 f_right_bottom = math_utilities::max(0.0, corner_radius.bottom_right - right);
308 f_bottom_right = math_utilities::max(0.0, corner_radius.bottom_right - bottom);
309 f_bottom_left = math_utilities::max(0.0, corner_radius.bottom_left - bottom);
310 f_left_bottom = math_utilities::max(0.0, corner_radius.bottom_left - left);
311 }
312
313 let mut top_left_x = f_left_top;
314 let top_left_y = 0.0;
315
316 let mut top_right_x = bound_rect.width - f_right_top;
317 let top_right_y = 0.0;
318
319 let right_top_x = bound_rect.width;
320 let mut right_top_y = f_top_right;
321
322 let right_bottom_x = bound_rect.width;
323 let mut right_bottom_y = bound_rect.height - f_bottom_right;
324
325 let mut bottom_right_x = bound_rect.width - f_right_bottom;
326 let bottom_right_y = bound_rect.height;
327
328 let mut bottom_left_x = f_left_bottom;
329 let bottom_left_y = bound_rect.height;
330
331 let left_bottom_x = 0.0;
332 let mut left_bottom_y = bound_rect.height - f_bottom_left;
333
334 let left_top_x = 0.0;
335 let mut left_top_y = f_top_left;
336
337 if top_left_x > top_right_x {
342 let v = (f_left_top) / (f_left_top + f_right_top) * bound_rect.width;
343 top_left_x = v;
344 top_right_x = v;
345 }
346 if right_top_y > right_bottom_y {
348 let v = (f_top_right) / (f_top_right + f_bottom_right) * bound_rect.height;
349 right_top_y = v;
350 right_bottom_y = v;
351 }
352 if bottom_right_x < bottom_left_x {
354 let v = (f_left_bottom) / (f_left_bottom + f_right_bottom) * bound_rect.width;
355 bottom_right_x = v;
356 bottom_left_x = v;
357 }
358 if left_bottom_y < left_top_y {
360 let v = (f_top_left) / (f_top_left + f_bottom_left) * bound_rect.height;
361 left_bottom_y = v;
362 left_top_y = v;
363 }
364
365 let mut keypoints = RoundedRectKeypoints::new();
369 keypoints.top_left = Point::new(bound_rect.x + top_left_x, bound_rect.y + top_left_y);
370 keypoints.top_right = Point::new(bound_rect.x + top_right_x, bound_rect.y + top_right_y);
371
372 keypoints.right_top = Point::new(bound_rect.x + right_top_x, bound_rect.y + right_top_y);
373 keypoints.right_bottom = Point::new(bound_rect.x + right_bottom_x, bound_rect.y + right_bottom_y);
374
375 keypoints.bottom_right = Point::new(bound_rect.x + bottom_right_x, bound_rect.y + bottom_right_y);
376 keypoints.bottom_left = Point::new(bound_rect.x + bottom_left_x, bound_rect.y + bottom_left_y);
377
378 keypoints.left_bottom = Point::new(bound_rect.x + left_bottom_x, bound_rect.y + left_bottom_y);
379 keypoints.left_top = Point::new(bound_rect.x + left_top_x, bound_rect.y + left_top_y);
380
381 keypoints
382 }
383}
384
385#[derive(Clone, Copy, Debug, PartialEq, Default)]
415pub struct RoundedRectKeypoints {
416 pub left_top: Point,
418 pub top_left: Point,
420 pub top_right: Point,
422 pub right_top: Point,
424 pub right_bottom: Point,
426 pub bottom_right: Point,
428 pub bottom_left: Point,
430 pub left_bottom: Point,
432}
433
434impl RoundedRectKeypoints {
435 pub fn new() -> Self {
437 Self::default()
438 }
439
440 pub fn from_rounded_rect(rounded_rect: RoundedRect) -> Self {
444 let rect = rounded_rect.rect;
445 Self {
446 left_top: Point::new(rect.top_left().x, rect.top_left().y + rounded_rect.radii_top_left.y),
447 top_left: Point::new(rect.top_left().x + rounded_rect.radii_top_left.x, rect.top_left().y),
448 top_right: Point::new(rect.top_right().x - rounded_rect.radii_top_right.x, rect.top_right().y),
449 right_top: Point::new(rect.top_right().x, rect.top_right().y + rounded_rect.radii_top_right.y),
450 right_bottom: Point::new(
451 rect.bottom_right().x,
452 rect.bottom_right().y - rounded_rect.radii_bottom_right.y,
453 ),
454 bottom_right: Point::new(
455 rect.bottom_right().x - rounded_rect.radii_bottom_right.x,
456 rect.bottom_right().y,
457 ),
458 bottom_left: Point::new(rect.bottom_left().x + rounded_rect.radii_bottom_left.x, rect.bottom_left().y),
459 left_bottom: Point::new(rect.bottom_left().x, rect.bottom_right().y - rounded_rect.radii_bottom_left.y),
460 }
461 }
462
463 pub fn is_rounded(&self) -> bool {
466 self.top_left != self.left_top
467 || self.top_right != self.right_top
468 || self.bottom_left != self.left_bottom
469 || self.bottom_right != self.right_bottom
470 }
471
472 pub fn to_rect(&self) -> Rect {
479 Rect::from_points(
480 Point::new(self.left_top.x, self.top_left.y),
481 Point::new(self.right_bottom.x, self.bottom_right.y),
482 )
483 }
484
485 pub fn to_rounded_rect(&self) -> RoundedRect {
491 RoundedRect::new(
492 self.to_rect(),
493 Vector::new(self.top_left.x - self.left_top.x, self.left_top.y - self.top_left.y),
494 Vector::new(self.right_top.x - self.top_right.x, self.right_top.y - self.top_right.y),
495 Vector::new(self.right_bottom.x - self.bottom_right.x, self.bottom_right.y - self.right_bottom.y),
496 Vector::new(self.bottom_left.x - self.left_bottom.x, self.bottom_left.y - self.left_bottom.y),
497 )
498 }
499}
500
501#[cfg(test)]
502mod tests {
503 use super::*;
504 use crate::media::FillRule;
505 use std::cell::RefCell;
506 use std::rc::Rc;
507
508 fn inner_border_edge_borders_larger_than_corners(uniform_borders: f64, uniform_corners: f64) {
509 let bounds = Rect::from_size(Size::new(100.0, 100.0));
510 let border_thickness = Thickness::uniform(uniform_borders);
511 let corner_radius = CornerRadius::uniform(uniform_corners);
512
513 let points = GeometryBuilder::calculate_rounded_corners_rectangle_win_ui(
514 bounds,
515 border_thickness,
516 corner_radius,
517 BackgroundSizing::InnerBorderEdge,
518 );
519
520 assert_eq!(Point::new(uniform_borders, uniform_borders), points.left_top);
521 assert_eq!(Point::new(uniform_borders, uniform_borders), points.top_left);
522 assert_eq!(Point::new(100.0 - uniform_borders, uniform_borders), points.top_right);
523 assert_eq!(Point::new(100.0 - uniform_borders, uniform_borders), points.right_top);
524 assert_eq!(Point::new(100.0 - uniform_borders, 100.0 - uniform_borders), points.right_bottom);
525 assert_eq!(Point::new(100.0 - uniform_borders, 100.0 - uniform_borders), points.bottom_right);
526 assert_eq!(Point::new(uniform_borders, 100.0 - uniform_borders), points.bottom_left);
527 assert_eq!(Point::new(uniform_borders, 100.0 - uniform_borders), points.left_bottom);
528
529 assert!(!points.is_rounded());
530 }
531
532 #[test]
533 fn calculate_rounded_corners_rectangle_win_ui_inner_border_edge_borders_larger_than_corners_test() {
534 for (uniform_borders, uniform_corners) in [(20.0, 10.0), (10.0, 5.0), (2.0, 1.0), (1.0, 0.0)] {
535 inner_border_edge_borders_larger_than_corners(uniform_borders, uniform_corners);
536 }
537 }
538
539 fn outer_border_edge_borders_larger_than_corners(uniform_borders: f64, uniform_corners: f64) {
540 let bounds = Rect::from_size(Size::new(100.0, 100.0));
541 let border_thickness = Thickness::uniform(uniform_borders);
542 let corner_radius = CornerRadius::uniform(uniform_corners);
543
544 let points = GeometryBuilder::calculate_rounded_corners_rectangle_win_ui(
545 bounds,
546 border_thickness,
547 corner_radius,
548 BackgroundSizing::OuterBorderEdge,
549 );
550
551 assert_eq!(Point::new(0.0, uniform_borders), points.left_top);
552 assert_eq!(Point::new(uniform_borders, 0.0), points.top_left);
553 assert_eq!(Point::new(100.0 - uniform_borders, 0.0), points.top_right);
554 assert_eq!(Point::new(100.0, uniform_borders), points.right_top);
555 assert_eq!(Point::new(100.0, 100.0 - uniform_borders), points.right_bottom);
556 assert_eq!(Point::new(100.0 - uniform_borders, 100.0), points.bottom_right);
557 assert_eq!(Point::new(uniform_borders, 100.0), points.bottom_left);
558 assert_eq!(Point::new(0.0, 100.0 - uniform_borders), points.left_bottom);
559
560 assert!(points.is_rounded());
561 }
562
563 #[test]
564 fn calculate_rounded_corners_rectangle_win_ui_outer_border_edge_borders_larger_than_corners_test() {
565 for (uniform_borders, uniform_corners) in [(20.0, 10.0), (10.0, 5.0), (2.0, 1.0)] {
566 outer_border_edge_borders_larger_than_corners(uniform_borders, uniform_corners);
567 }
568 }
569
570 #[derive(Default)]
574 struct Recorder {
575 log: Rc<RefCell<Vec<String>>>,
576 }
577
578 impl IGeometryContext for Recorder {
579 fn arc_to(&mut self, point: Point, size: Size, angle: f64, large: bool, sweep: SweepDirection, stroked: bool) {
580 self.log.borrow_mut().push(format!("arc {point} {size} {angle} {large} {sweep:?} {stroked}"));
581 }
582 fn begin_figure(&mut self, start_point: Point, is_filled: bool) {
583 self.log.borrow_mut().push(format!("begin {start_point} {is_filled}"));
584 }
585 fn cubic_bezier_to(&mut self, p1: Point, p2: Point, p3: Point, stroked: bool) {
586 self.log.borrow_mut().push(format!("cubic {p1} {p2} {p3} {stroked}"));
587 }
588 fn quadratic_bezier_to(&mut self, p1: Point, p2: Point, stroked: bool) {
589 self.log.borrow_mut().push(format!("quad {p1} {p2} {stroked}"));
590 }
591 fn line_to(&mut self, point: Point, stroked: bool) {
592 self.log.borrow_mut().push(format!("line {point} {stroked}"));
593 }
594 fn end_figure(&mut self, is_closed: bool) {
595 self.log.borrow_mut().push(format!("end {is_closed}"));
596 }
597 fn set_fill_rule(&mut self, _: FillRule) {}
598 fn dispose(&mut self) {}
599 }
600
601 fn record(draw: impl FnOnce(&mut StreamGeometryContext)) -> Vec<String> {
602 let log = Rc::new(RefCell::new(Vec::new()));
603 let mut context = StreamGeometryContext::from_context(Box::new(Recorder { log: log.clone() }));
604 draw(&mut context);
605 drop(context);
606 let result = log.borrow().clone();
607 result
608 }
609
610 #[test]
611 fn keypoints_from_rounded_rect_round_trip() {
612 let rounded_rect = RoundedRect::new(
613 Rect::new(10.0, 20.0, 100.0, 50.0),
614 Vector::new(1.0, 2.0),
615 Vector::new(3.0, 4.0),
616 Vector::new(5.0, 6.0),
617 Vector::new(7.0, 8.0),
618 );
619
620 let keypoints = RoundedRectKeypoints::from_rounded_rect(rounded_rect);
621
622 assert_eq!(Point::new(10.0, 22.0), keypoints.left_top);
623 assert_eq!(Point::new(11.0, 20.0), keypoints.top_left);
624 assert_eq!(Point::new(107.0, 20.0), keypoints.top_right);
625 assert_eq!(Point::new(110.0, 24.0), keypoints.right_top);
626 assert_eq!(Point::new(110.0, 64.0), keypoints.right_bottom);
627 assert_eq!(Point::new(105.0, 70.0), keypoints.bottom_right);
628 assert_eq!(Point::new(17.0, 70.0), keypoints.bottom_left);
629 assert_eq!(Point::new(10.0, 62.0), keypoints.left_bottom);
630 assert!(keypoints.is_rounded());
631 assert_eq!(Rect::new(10.0, 20.0, 100.0, 50.0), keypoints.to_rect());
632 assert_eq!(rounded_rect, keypoints.to_rounded_rect());
633 }
634
635 #[test]
636 fn default_keypoints_are_not_rounded() {
637 let keypoints = RoundedRectKeypoints::new();
638 assert!(!keypoints.is_rounded());
639 assert_eq!(Rect::default(), keypoints.to_rect());
640 }
641
642 #[test]
643 fn center_border_deflates_by_half_the_border() {
644 let points = GeometryBuilder::calculate_rounded_corners_rectangle_win_ui(
645 Rect::new(10.0, 10.0, 100.0, 60.0),
646 Thickness::uniform(4.0),
647 CornerRadius::uniform(10.0),
648 BackgroundSizing::CenterBorder,
649 );
650
651 assert_eq!(Point::new(12.0, 20.0), points.left_top);
653 assert_eq!(Point::new(20.0, 12.0), points.top_left);
654 assert_eq!(Point::new(100.0, 12.0), points.top_right);
655 assert_eq!(Point::new(108.0, 20.0), points.right_top);
656 assert_eq!(Point::new(108.0, 60.0), points.right_bottom);
657 assert_eq!(Point::new(100.0, 68.0), points.bottom_right);
658 assert_eq!(Point::new(20.0, 68.0), points.bottom_left);
659 assert_eq!(Point::new(12.0, 60.0), points.left_bottom);
660 }
661
662 #[test]
663 fn overlapping_radii_are_partitioned_proportionally() {
664 let points = GeometryBuilder::calculate_rounded_corners_rectangle_win_ui(
665 Rect::new(0.0, 0.0, 100.0, 100.0),
666 Thickness::default(),
667 CornerRadius::new(90.0, 30.0, 30.0, 90.0),
668 BackgroundSizing::OuterBorderEdge,
669 );
670
671 assert_eq!(Point::new(75.0, 0.0), points.top_left);
673 assert_eq!(Point::new(75.0, 0.0), points.top_right);
674 assert_eq!(Point::new(75.0, 100.0), points.bottom_left);
676 assert_eq!(Point::new(75.0, 100.0), points.bottom_right);
677 assert_eq!(Point::new(100.0, 30.0), points.right_top);
679 assert_eq!(Point::new(100.0, 70.0), points.right_bottom);
680 assert_eq!(Point::new(0.0, 50.0), points.left_top);
682 assert_eq!(Point::new(0.0, 50.0), points.left_bottom);
683 }
684
685 #[test]
686 fn keypoints_are_drawn_with_arcs_only_on_rounded_corners() {
687 let keypoints = RoundedRectKeypoints::from_rounded_rect(RoundedRect::new(
688 Rect::new(0.0, 0.0, 100.0, 50.0),
689 Vector::new(10.0, 5.0),
690 Vector::new(0.0, 0.0),
691 Vector::new(20.0, 10.0),
692 Vector::new(0.0, 0.0),
693 ));
694
695 let log = record(|context| GeometryBuilder::draw_rounded_corners_rectangle_keypoints(context, &keypoints));
696
697 assert_eq!(
698 vec![
699 "begin 10, 0 true",
700 "line 100, 0 true",
701 "arc 100, 0 -0, -0 0 false Clockwise true",
704 "line 100, 40 true",
705 "arc 80, 50 20, 10 0 false Clockwise true",
706 "line 0, 50 true",
707 "line 0, 5 true",
708 "arc 10, 0 10, 5 0 false Clockwise true",
709 "end true",
710 ],
711 log
712 );
713 }
714
715 #[test]
716 fn uniform_radii_rectangle_is_drawn_with_four_arcs() {
717 let log = record(|context| {
718 GeometryBuilder::draw_rounded_corners_rectangle(context, Rect::new(0.0, 0.0, 100.0, 50.0), 10.0, 5.0)
719 });
720
721 assert_eq!(
722 vec![
723 "begin 10, 0 true",
724 "line 90, 0 true",
725 "arc 100, 5 10, 5 0 false Clockwise true",
726 "line 100, 45 true",
727 "arc 90, 50 10, 5 0 false Clockwise true",
728 "line 10, 50 true",
729 "arc 0, 45 10, 5 0 false Clockwise true",
730 "line 0, 5 true",
731 "arc 10, 0 10, 5 0 false Clockwise true",
732 "end true",
733 ],
734 log
735 );
736 }
737}