1use iced_widget::canvas::{self, Canvas, LineCap, LineJoin, Path, Stroke};
4use iced_widget::core::time::{Duration, Instant};
5use iced_widget::core::{Color, Length, Point, Rectangle, mouse};
6use std::f32::consts::{FRAC_PI_2, FRAC_PI_4, TAU};
7
8use crate::{Theme, tokens};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11enum LinearMode {
12 Determinate,
13 Indeterminate,
14}
15
16#[derive(Debug, Clone)]
18pub struct IndeterminateState {
19 started_at: Instant,
20 elapsed: Duration,
21}
22
23impl IndeterminateState {
24 pub fn new(started_at: Instant) -> Self {
26 Self {
27 started_at,
28 elapsed: Duration::ZERO,
29 }
30 }
31
32 pub fn advance(&mut self, now: Instant) {
34 self.elapsed = now.saturating_duration_since(self.started_at);
35 }
36
37 pub fn linear_phase(&self) -> f32 {
39 elapsed_phase(
40 self.elapsed,
41 tokens::component::linear_progress::INDETERMINATE_DURATION_MS,
42 )
43 }
44
45 pub fn color_phase(&self) -> f32 {
47 elapsed_phase(
48 self.elapsed,
49 tokens::component::linear_progress::INDETERMINATE_DURATION_MS * 2,
50 )
51 }
52
53 pub fn loading_phase(&self) -> f32 {
55 elapsed_phase(
56 self.elapsed,
57 tokens::component::loading_indicator::GLOBAL_ROTATION_DURATION_MS,
58 )
59 }
60
61 pub const fn is_animating(&self) -> bool {
63 true
64 }
65}
66
67impl Default for IndeterminateState {
68 fn default() -> Self {
69 Self::new(Instant::now())
70 }
71}
72
73#[derive(Debug, Clone, Copy)]
74pub struct LinearProgress {
75 mode: LinearMode,
76 progress: f32,
77 phase: f32,
78 color_phase: f32,
79 four_color: bool,
80}
81
82pub fn linear<'a, Message, Renderer>(
84 progress: f32,
85 phase: f32,
86) -> Canvas<LinearProgress, Message, Theme, Renderer>
87where
88 Renderer: iced_widget::graphics::geometry::Renderer + 'a,
89{
90 linear_wavy(progress, phase)
91}
92
93pub fn linear_wavy<'a, Message, Renderer>(
95 progress: f32,
96 phase: f32,
97) -> Canvas<LinearProgress, Message, Theme, Renderer>
98where
99 Renderer: iced_widget::graphics::geometry::Renderer + 'a,
100{
101 Canvas::new(LinearProgress {
102 mode: LinearMode::Determinate,
103 progress: progress.clamp(0.0, 1.0),
104 phase,
105 color_phase: phase,
106 four_color: false,
107 })
108 .width(Length::Fill)
109 .height(Length::Fixed(
110 tokens::component::linear_progress::WAVE_HEIGHT,
111 ))
112}
113
114pub fn linear_indeterminate<'a, Message, Renderer>(
116 phase: f32,
117 four_color: bool,
118) -> Canvas<LinearProgress, Message, Theme, Renderer>
119where
120 Renderer: iced_widget::graphics::geometry::Renderer + 'a,
121{
122 Canvas::new(LinearProgress {
123 mode: LinearMode::Indeterminate,
124 progress: 0.0,
125 phase,
126 color_phase: phase * 0.5,
127 four_color,
128 })
129 .width(Length::Fill)
130 .height(Length::Fixed(
131 tokens::component::linear_progress::WAVE_HEIGHT,
132 ))
133}
134
135pub fn linear_indeterminate_with_color_phase<'a, Message, Renderer>(
137 phase: f32,
138 color_phase: f32,
139) -> Canvas<LinearProgress, Message, Theme, Renderer>
140where
141 Renderer: iced_widget::graphics::geometry::Renderer + 'a,
142{
143 Canvas::new(LinearProgress {
144 mode: LinearMode::Indeterminate,
145 progress: 0.0,
146 phase,
147 color_phase,
148 four_color: true,
149 })
150 .width(Length::Fill)
151 .height(Length::Fixed(
152 tokens::component::linear_progress::WAVE_HEIGHT,
153 ))
154}
155
156impl<Message, Renderer> canvas::Program<Message, Theme, Renderer> for LinearProgress
157where
158 Renderer: iced_widget::graphics::geometry::Renderer,
159{
160 type State = ();
161
162 fn draw(
163 &self,
164 _state: &Self::State,
165 renderer: &Renderer,
166 theme: &Theme,
167 bounds: Rectangle,
168 _cursor: mouse::Cursor,
169 ) -> Vec<canvas::Geometry<Renderer>> {
170 let mut frame = canvas::Frame::new(renderer, bounds.size());
171 let colors = theme.colors();
172
173 let active = if self.four_color {
174 four_color_indicator(
175 colors.primary.color,
176 colors.primary.container,
177 colors.tertiary.color,
178 colors.tertiary.container,
179 self.color_phase,
180 )
181 } else {
182 colors.primary.color
183 };
184
185 let track = colors.surface.container.highest;
186
187 match self.mode {
188 LinearMode::Determinate => {
189 draw_linear_determinate_track(&mut frame, track, active, self.progress);
190 draw_linear_determinate(&mut frame, active, self.progress, self.phase);
191 }
192 LinearMode::Indeterminate => {
193 let bars = indeterminate_bars(self.phase);
194
195 draw_linear_indeterminate_track(&mut frame, track, &bars);
196
197 for (index, bar) in bars.into_iter().enumerate() {
198 draw_indeterminate_bar(
199 &mut frame,
200 active,
201 bar,
202 self.phase + index as f32 * 0.25,
203 );
204 }
205 }
206 }
207
208 vec![frame.into_geometry()]
209 }
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213enum LoadingMode {
214 Uncontained,
215 Contained,
216}
217
218#[derive(Debug, Clone, Copy)]
219pub struct LoadingIndicator {
220 mode: LoadingMode,
221 progress: Option<f32>,
222 phase: f32,
223}
224
225pub fn loading_indicator<'a, Message, Renderer>(
227 phase: f32,
228) -> Canvas<LoadingIndicator, Message, Theme, Renderer>
229where
230 Renderer: iced_widget::graphics::geometry::Renderer + 'a,
231{
232 Canvas::new(LoadingIndicator {
233 mode: LoadingMode::Uncontained,
234 progress: None,
235 phase,
236 })
237 .width(Length::Fixed(
238 tokens::component::loading_indicator::CONTAINER_WIDTH,
239 ))
240 .height(Length::Fixed(
241 tokens::component::loading_indicator::CONTAINER_HEIGHT,
242 ))
243}
244
245pub fn contained_loading_indicator<'a, Message, Renderer>(
247 phase: f32,
248) -> Canvas<LoadingIndicator, Message, Theme, Renderer>
249where
250 Renderer: iced_widget::graphics::geometry::Renderer + 'a,
251{
252 Canvas::new(LoadingIndicator {
253 mode: LoadingMode::Contained,
254 progress: None,
255 phase,
256 })
257 .width(Length::Fixed(
258 tokens::component::loading_indicator::CONTAINER_WIDTH,
259 ))
260 .height(Length::Fixed(
261 tokens::component::loading_indicator::CONTAINER_HEIGHT,
262 ))
263}
264
265pub fn determinate_loading_indicator<'a, Message, Renderer>(
267 progress: f32,
268) -> Canvas<LoadingIndicator, Message, Theme, Renderer>
269where
270 Renderer: iced_widget::graphics::geometry::Renderer + 'a,
271{
272 Canvas::new(LoadingIndicator {
273 mode: LoadingMode::Uncontained,
274 progress: Some(progress.clamp(0.0, 1.0)),
275 phase: 0.0,
276 })
277 .width(Length::Fixed(
278 tokens::component::loading_indicator::CONTAINER_WIDTH,
279 ))
280 .height(Length::Fixed(
281 tokens::component::loading_indicator::CONTAINER_HEIGHT,
282 ))
283}
284
285pub fn determinate_contained_loading_indicator<'a, Message, Renderer>(
287 progress: f32,
288) -> Canvas<LoadingIndicator, Message, Theme, Renderer>
289where
290 Renderer: iced_widget::graphics::geometry::Renderer + 'a,
291{
292 Canvas::new(LoadingIndicator {
293 mode: LoadingMode::Contained,
294 progress: Some(progress.clamp(0.0, 1.0)),
295 phase: 0.0,
296 })
297 .width(Length::Fixed(
298 tokens::component::loading_indicator::CONTAINER_WIDTH,
299 ))
300 .height(Length::Fixed(
301 tokens::component::loading_indicator::CONTAINER_HEIGHT,
302 ))
303}
304
305impl<Message, Renderer> canvas::Program<Message, Theme, Renderer> for LoadingIndicator
306where
307 Renderer: iced_widget::graphics::geometry::Renderer,
308{
309 type State = ();
310
311 fn draw(
312 &self,
313 _state: &Self::State,
314 renderer: &Renderer,
315 theme: &Theme,
316 bounds: Rectangle,
317 _cursor: mouse::Cursor,
318 ) -> Vec<canvas::Geometry<Renderer>> {
319 let mut frame = canvas::Frame::new(renderer, bounds.size());
320 let colors = theme.colors();
321
322 let (container, active) = match self.mode {
323 LoadingMode::Uncontained => (None, colors.primary.color),
324 LoadingMode::Contained => (
325 Some(colors.primary.container),
326 colors.primary.container_text,
327 ),
328 };
329
330 if let Some(color) = container {
331 let container = Path::circle(frame.center(), frame.width().min(frame.height()) / 2.0);
332 frame.fill(&container, color);
333 }
334
335 let side = frame.width().min(frame.height());
336 let path = if let Some(progress) = self.progress {
337 determinate_loading_shape_path(frame.center(), side, progress)
338 } else {
339 loading_shape_path(frame.center(), side, self.phase)
340 };
341 frame.fill(&path, active);
342
343 vec![frame.into_geometry()]
344 }
345}
346
347fn elapsed_phase(elapsed: Duration, duration_ms: u16) -> f32 {
348 let duration = f32::from(duration_ms) / 1000.0;
349
350 if duration <= 0.0 {
351 return 0.0;
352 }
353
354 (elapsed.as_secs_f32() / duration).rem_euclid(1.0)
355}
356
357fn draw_linear_determinate_track<Renderer>(
358 frame: &mut canvas::Frame<Renderer>,
359 track: Color,
360 stop: Color,
361 progress: f32,
362) where
363 Renderer: iced_widget::graphics::geometry::Renderer,
364{
365 let width = frame.width();
366 let height = frame.height();
367 let y = height / 2.0;
368 let stroke_width = tokens::component::linear_progress::TRACK_THICKNESS;
369 let left = stroke_width / 2.0;
370 let stop_size = tokens::component::linear_progress::STOP_SIZE;
371 let stop_center_x =
372 width - tokens::component::linear_progress::STOP_TRAILING_SPACE - stop_size / 2.0;
373 let right = (stop_center_x - stop_size / 2.0).max(left);
374 let active_end = left + (right - left) * progress.clamp(0.0, 1.0);
375 let track_start =
376 (active_end + tokens::component::linear_progress::TRACK_ACTIVE_SPACE + stroke_width)
377 .clamp(left, right);
378
379 if track_start < right {
380 frame.stroke(
381 &Path::line(Point::new(track_start, y), Point::new(right, y)),
382 round_stroke(track, stroke_width),
383 );
384 }
385
386 let stop_radius = linear_stop_radius(progress, width);
387 if stop_radius > 0.0 {
388 frame.fill(
389 &Path::circle(Point::new(stop_center_x, y), stop_radius),
390 stop,
391 );
392 }
393}
394
395fn draw_linear_indeterminate_track<Renderer>(
396 frame: &mut canvas::Frame<Renderer>,
397 track: Color,
398 bars: &[IndeterminateBar; 2],
399) where
400 Renderer: iced_widget::graphics::geometry::Renderer,
401{
402 let stroke_width = tokens::component::linear_progress::TRACK_THICKNESS;
403 let left = stroke_width / 2.0;
404 let right = frame.width() - stroke_width / 2.0;
405 let y = frame.height() / 2.0;
406 let gap = tokens::component::linear_progress::TRACK_ACTIVE_SPACE + stroke_width;
407 let mut cursor = left;
408
409 let mut ranges = [
410 linear_bar_range(bars[0], left, right),
411 linear_bar_range(bars[1], left, right),
412 ];
413 ranges.sort_by(|a, b| a.0.total_cmp(&b.0));
414
415 for (start, end) in ranges {
416 if end <= start {
417 continue;
418 }
419
420 let track_end = (start - gap).clamp(left, right);
421 if track_end > cursor {
422 frame.stroke(
423 &Path::line(Point::new(cursor, y), Point::new(track_end, y)),
424 round_stroke(track, stroke_width),
425 );
426 }
427
428 cursor = cursor.max((end + gap).clamp(left, right));
429 }
430
431 if cursor < right {
432 frame.stroke(
433 &Path::line(Point::new(cursor, y), Point::new(right, y)),
434 round_stroke(track, stroke_width),
435 );
436 }
437}
438
439fn linear_stop_radius(progress: f32, width: f32) -> f32 {
440 let stop_size = tokens::component::linear_progress::STOP_SIZE;
441 let stroke_width = tokens::component::linear_progress::TRACK_THICKNESS;
442 let stop_x = width - tokens::component::linear_progress::STOP_TRAILING_SPACE - stop_size;
443 let progress_x = width * progress.clamp(0.0, 1.0) + stroke_width / 2.0;
444 let size = if stop_x <= progress_x {
445 (stop_size - (progress_x - stop_x)).max(0.0)
446 } else {
447 stop_size
448 };
449
450 size / 2.0
451}
452
453fn draw_linear_determinate<Renderer>(
454 frame: &mut canvas::Frame<Renderer>,
455 active: Color,
456 progress: f32,
457 phase: f32,
458) where
459 Renderer: iced_widget::graphics::geometry::Renderer,
460{
461 let stroke_width = tokens::component::linear_progress::ACTIVE_INDICATOR_HEIGHT;
462 let left = stroke_width / 2.0;
463 let right = frame.width()
464 - tokens::component::linear_progress::STOP_TRAILING_SPACE
465 - tokens::component::linear_progress::STOP_SIZE;
466 let end = left + (right - left).max(0.0) * progress.clamp(0.0, 1.0);
467 let amplitude = tokens::component::linear_progress::ACTIVE_WAVE_AMPLITUDE
468 * determinate_wave_amplitude(progress);
469
470 if end <= left {
471 return;
472 }
473
474 let path = wave_path(
475 left,
476 end,
477 frame.height() / 2.0,
478 amplitude,
479 tokens::component::linear_progress::ACTIVE_WAVE_WAVELENGTH,
480 phase,
481 );
482 frame.stroke(&path, round_stroke(active, stroke_width));
483}
484
485fn draw_indeterminate_bar<Renderer>(
486 frame: &mut canvas::Frame<Renderer>,
487 active: Color,
488 bar: IndeterminateBar,
489 wave_phase: f32,
490) where
491 Renderer: iced_widget::graphics::geometry::Renderer,
492{
493 let stroke_width = tokens::component::linear_progress::ACTIVE_INDICATOR_HEIGHT;
494 let left = stroke_width / 2.0;
495 let right = frame.width() - stroke_width / 2.0;
496 let (start, end) = linear_bar_range(bar, left, right);
497
498 if end <= start {
499 return;
500 }
501
502 let path = wave_path(
503 start,
504 end,
505 frame.height() / 2.0,
506 tokens::component::linear_progress::ACTIVE_WAVE_AMPLITUDE,
507 tokens::component::linear_progress::INDETERMINATE_ACTIVE_WAVE_WAVELENGTH,
508 wave_phase,
509 );
510
511 frame.stroke(&path, round_stroke(active, stroke_width));
512}
513
514fn determinate_wave_amplitude(progress: f32) -> f32 {
515 let progress = progress.clamp(0.0, 1.0);
516
517 if progress <= 0.1 || progress >= 0.95 {
518 0.0
519 } else {
520 1.0
521 }
522}
523
524fn linear_bar_range(bar: IndeterminateBar, left: f32, right: f32) -> (f32, f32) {
525 let width = (right - left).max(0.0);
526 let start = left + width * bar.tail.clamp(0.0, 1.0);
527 let end = left + width * bar.head.clamp(0.0, 1.0);
528
529 if end >= start {
530 (start, end)
531 } else {
532 (end, start)
533 }
534}
535
536fn round_stroke(color: Color, width: f32) -> Stroke<'static> {
537 Stroke::default()
538 .with_color(color)
539 .with_width(width)
540 .with_line_cap(LineCap::Round)
541 .with_line_join(LineJoin::Round)
542}
543
544fn wave_path(start: f32, end: f32, y: f32, amplitude: f32, wavelength: f32, phase: f32) -> Path {
545 let length = (end - start).max(0.0);
546 let step = 3.0_f32.max(wavelength / 12.0);
547
548 Path::new(|path| {
549 path.move_to(Point::new(
550 start,
551 y + wave_offset(0.0, amplitude, wavelength, phase),
552 ));
553
554 let mut distance = step;
555 while distance < length {
556 let x = start + distance;
557 path.line_to(Point::new(
558 x,
559 y + wave_offset(distance, amplitude, wavelength, phase),
560 ));
561 distance += step;
562 }
563
564 path.line_to(Point::new(
565 end,
566 y + wave_offset(length, amplitude, wavelength, phase),
567 ));
568 })
569}
570
571fn wave_offset(distance: f32, amplitude: f32, wavelength: f32, phase: f32) -> f32 {
572 if wavelength <= 0.0 {
573 return 0.0;
574 }
575
576 ((distance / wavelength) * TAU + phase.rem_euclid(1.0) * TAU).sin() * amplitude
577}
578
579#[derive(Debug, Clone, Copy, PartialEq)]
580struct IndeterminateBar {
581 tail: f32,
582 head: f32,
583}
584
585fn indeterminate_bars(phase: f32) -> [IndeterminateBar; 2] {
586 [
587 IndeterminateBar {
588 tail: indeterminate_keyframe_progress(
589 phase,
590 tokens::component::linear_progress::FIRST_LINE_TAIL_DELAY_MS,
591 tokens::component::linear_progress::FIRST_LINE_TAIL_DURATION_MS,
592 ),
593 head: indeterminate_keyframe_progress(
594 phase,
595 tokens::component::linear_progress::FIRST_LINE_HEAD_DELAY_MS,
596 tokens::component::linear_progress::FIRST_LINE_HEAD_DURATION_MS,
597 ),
598 },
599 IndeterminateBar {
600 tail: indeterminate_keyframe_progress(
601 phase,
602 tokens::component::linear_progress::SECOND_LINE_TAIL_DELAY_MS,
603 tokens::component::linear_progress::SECOND_LINE_TAIL_DURATION_MS,
604 ),
605 head: indeterminate_keyframe_progress(
606 phase,
607 tokens::component::linear_progress::SECOND_LINE_HEAD_DELAY_MS,
608 tokens::component::linear_progress::SECOND_LINE_HEAD_DURATION_MS,
609 ),
610 },
611 ]
612}
613
614fn indeterminate_keyframe_progress(phase: f32, delay_ms: u16, duration_ms: u16) -> f32 {
615 let elapsed_ms = phase.rem_euclid(1.0)
616 * f32::from(tokens::component::linear_progress::INDETERMINATE_DURATION_MS);
617 let delay_ms = f32::from(delay_ms);
618 let duration_ms = f32::from(duration_ms);
619
620 if elapsed_ms <= delay_ms {
621 return 0.0;
622 }
623
624 if elapsed_ms >= delay_ms + duration_ms {
625 return 1.0;
626 }
627
628 tokens::motion::EASING_EMPHASIZED_ACCELERATE.transform((elapsed_ms - delay_ms) / duration_ms)
629}
630
631fn four_color_indicator(
632 primary: Color,
633 primary_container: Color,
634 tertiary: Color,
635 tertiary_container: Color,
636 phase: f32,
637) -> Color {
638 let phase = phase.rem_euclid(1.0);
639
640 if !(0.15..0.25).contains(&phase)
641 && !(0.40..0.50).contains(&phase)
642 && !(0.65..0.75).contains(&phase)
643 && !(0.90..1.0).contains(&phase)
644 {
645 if phase < 0.25 || phase >= 0.90 {
646 return primary;
647 }
648 if phase < 0.50 {
649 return primary_container;
650 }
651 if phase < 0.75 {
652 return tertiary;
653 }
654
655 return tertiary_container;
656 }
657
658 if phase < 0.25 {
659 color_lerp(primary, primary_container, (phase - 0.15) / 0.10)
660 } else if phase < 0.50 {
661 color_lerp(primary_container, tertiary, (phase - 0.40) / 0.10)
662 } else if phase < 0.75 {
663 color_lerp(tertiary, tertiary_container, (phase - 0.65) / 0.10)
664 } else {
665 color_lerp(tertiary_container, primary, (phase - 0.90) / 0.10)
666 }
667}
668
669fn color_lerp(from: Color, to: Color, progress: f32) -> Color {
670 let progress = progress.clamp(0.0, 1.0);
671
672 Color {
673 r: from.r + (to.r - from.r) * progress,
674 g: from.g + (to.g - from.g) * progress,
675 b: from.b + (to.b - from.b) * progress,
676 a: from.a + (to.a - from.a) * progress,
677 }
678}
679
680fn loading_shape_path(center: Point, side: f32, phase: f32) -> Path {
681 let phase = phase.rem_euclid(1.0);
682 let polygons = indeterminate_loading_polygons();
683 let morphs = morph_sequence(&polygons, true);
684 let scale_factor = loading_shape_scale(&polygons);
685 let morph_position = (phase
686 * f32::from(tokens::component::loading_indicator::GLOBAL_ROTATION_DURATION_MS)
687 / f32::from(tokens::component::loading_indicator::MORPH_INTERVAL_MS))
688 .rem_euclid(morphs.len() as f32);
689 let from_index = morph_position.floor() as usize;
690 let local_progress = morph_position.fract();
691 let morph_progress = loading_spring_progress(local_progress);
692 let rotation = phase * TAU + (from_index as f32 + 1.0 + morph_progress) * FRAC_PI_2;
693
694 morphed_loading_shape_path(
695 &morphs[from_index],
696 center,
697 side,
698 scale_factor,
699 morph_progress,
700 rotation,
701 )
702}
703
704fn determinate_loading_shape_path(center: Point, side: f32, progress: f32) -> Path {
705 let progress = progress.clamp(0.0, 1.0);
706 let polygons = determinate_loading_polygons();
707 let morphs = morph_sequence(&polygons, false);
708 let scale_factor = loading_shape_scale(&polygons);
709 let rotation = -progress * std::f32::consts::PI;
710
711 morphed_loading_shape_path(&morphs[0], center, side, scale_factor, progress, rotation)
712}
713
714fn morphed_loading_shape_path(
715 morph: &Morph,
716 center: Point,
717 side: f32,
718 scale_factor: f32,
719 morph_progress: f32,
720 rotation: f32,
721) -> Path {
722 let cubics = morph.as_cubics(morph_progress);
723
724 processed_cubic_path(&cubics, center, side, scale_factor, rotation)
725}
726
727fn loading_spring_progress(progress: f32) -> f32 {
728 let seconds = progress.clamp(0.0, 1.0)
729 * f32::from(tokens::component::loading_indicator::MORPH_INTERVAL_MS)
730 / 1000.0;
731 let damping_ratio = tokens::component::loading_indicator::MORPH_SPRING_DAMPING_RATIO;
732 let stiffness = tokens::component::loading_indicator::MORPH_SPRING_STIFFNESS;
733 let natural_frequency = stiffness.sqrt();
734
735 if damping_ratio >= 1.0 {
736 return (1.0 - (-natural_frequency * seconds).exp()).clamp(0.0, 1.0);
737 }
738
739 let damped_frequency = natural_frequency * (1.0 - damping_ratio * damping_ratio).sqrt();
740 let envelope = (-damping_ratio * natural_frequency * seconds).exp();
741 let phase = damped_frequency * seconds;
742 let response = 1.0
743 - envelope
744 * (phase.cos()
745 + damping_ratio / (1.0 - damping_ratio * damping_ratio).sqrt() * phase.sin());
746
747 response.clamp(0.0, 1.0)
748}
749
750fn processed_cubic_path(
751 cubics: &[Cubic],
752 center: Point,
753 side: f32,
754 scale_factor: f32,
755 rotation: f32,
756) -> Path {
757 if cubics.is_empty() {
758 return Path::new(|_| {});
759 }
760
761 let transformed = processed_cubics(cubics, center, side, scale_factor, rotation);
762
763 Path::new(|path| {
764 path.move_to(Point::new(
765 transformed[0].anchor0_x(),
766 transformed[0].anchor0_y(),
767 ));
768
769 for cubic in &transformed {
770 path.bezier_curve_to(
771 Point::new(cubic.control0_x(), cubic.control0_y()),
772 Point::new(cubic.control1_x(), cubic.control1_y()),
773 Point::new(cubic.anchor1_x(), cubic.anchor1_y()),
774 );
775 }
776
777 path.close();
778 })
779}
780
781fn processed_cubics(
782 cubics: &[Cubic],
783 center: Point,
784 side: f32,
785 scale_factor: f32,
786 rotation: f32,
787) -> Vec<Cubic> {
788 if cubics.is_empty() {
789 return Vec::new();
790 }
791
792 let scale = side * scale_factor;
793 let transformed: Vec<Cubic> = cubics
794 .iter()
795 .map(|cubic| cubic.transformed(|point| Point::new(point.x * scale, point.y * scale)))
796 .collect();
797 let bounds = cubics_bounds(&transformed, false);
798 let bounds_center = bounds_center(bounds);
799 let translation = point_sub(center, bounds_center);
800
801 transformed
802 .into_iter()
803 .map(|cubic| {
804 cubic.transformed(|point| {
805 rotate_point_around(point_add(point, translation), center, rotation)
806 })
807 })
808 .collect()
809}
810
811fn loading_shape_scale(polygons: &[RoundedPolygon]) -> f32 {
812 let mut scale_factor = 1.0_f32;
813
814 for polygon in polygons {
815 let bounds = polygon.calculate_bounds(true);
816 let max_bounds = polygon.calculate_max_bounds();
817 let scale_x = bounds_width(bounds) / bounds_width(max_bounds);
818 let scale_y = bounds_height(bounds) / bounds_height(max_bounds);
819
820 scale_factor = scale_factor.min(scale_x.max(scale_y));
821 }
822
823 scale_factor * tokens::component::loading_indicator::ACTIVE_INDICATOR_SCALE
824}
825
826fn indeterminate_loading_polygons() -> Vec<RoundedPolygon> {
827 vec![
828 material_soft_burst(),
829 material_cookie9(),
830 material_pentagon(),
831 material_pill(),
832 material_sunny(),
833 material_cookie4(),
834 material_oval(),
835 ]
836}
837
838fn determinate_loading_polygons() -> Vec<RoundedPolygon> {
839 vec![
840 material_circle().transformed(|point| rotate_point(point, TAU / 20.0)),
841 material_soft_burst(),
842 ]
843}
844
845fn morph_sequence(polygons: &[RoundedPolygon], circular_sequence: bool) -> Vec<Morph> {
846 let mut morphs = Vec::new();
847
848 for index in 0..polygons.len() {
849 if index + 1 < polygons.len() {
850 morphs.push(Morph::new(
851 polygons[index].normalized(),
852 polygons[index + 1].normalized(),
853 ));
854 } else if circular_sequence {
855 morphs.push(Morph::new(
856 polygons[index].normalized(),
857 polygons[0].normalized(),
858 ));
859 }
860 }
861
862 morphs
863}
864
865fn material_circle() -> RoundedPolygon {
866 rounded_polygon_circle(10, 1.0, Point::ORIGIN).normalized()
867}
868
869fn material_oval() -> RoundedPolygon {
870 rounded_polygon_circle(8, 1.0, Point::ORIGIN)
871 .transformed(|point| Point::new(point.x, point.y * 0.64))
872 .transformed(|point| rotate_point(point, -FRAC_PI_4))
873 .normalized()
874}
875
876fn material_pill() -> RoundedPolygon {
877 custom_material_polygon(
878 &[
879 ShapeVertex::new(0.961, 0.039, CornerRounding::new(0.426)),
880 ShapeVertex::new(1.001, 0.428, CornerRounding::UNROUNDED),
881 ShapeVertex::new(1.000, 0.609, CornerRounding::new(1.0)),
882 ],
883 2,
884 true,
885 )
886 .normalized()
887}
888
889fn material_pentagon() -> RoundedPolygon {
890 custom_material_polygon(
891 &[
892 ShapeVertex::new(0.500, -0.009, CornerRounding::new(0.172)),
893 ShapeVertex::new(1.030, 0.365, CornerRounding::new(0.164)),
894 ShapeVertex::new(0.828, 0.970, CornerRounding::new(0.169)),
895 ],
896 1,
897 true,
898 )
899 .normalized()
900}
901
902fn material_sunny() -> RoundedPolygon {
903 rounded_polygon_star(8, 1.0, 0.8, CornerRounding::new(0.15), Point::ORIGIN).normalized()
904}
905
906fn material_cookie4() -> RoundedPolygon {
907 custom_material_polygon(
908 &[
909 ShapeVertex::new(1.237, 1.236, CornerRounding::new(0.258)),
910 ShapeVertex::new(0.500, 0.918, CornerRounding::new(0.233)),
911 ],
912 4,
913 false,
914 )
915 .normalized()
916}
917
918fn material_cookie9() -> RoundedPolygon {
919 rounded_polygon_star(9, 1.0, 0.8, CornerRounding::new(0.5), Point::ORIGIN)
920 .transformed(|point| rotate_point(point, -FRAC_PI_2))
921 .normalized()
922}
923
924fn material_soft_burst() -> RoundedPolygon {
925 custom_material_polygon(
926 &[
927 ShapeVertex::new(0.193, 0.277, CornerRounding::new(0.053)),
928 ShapeVertex::new(0.176, 0.055, CornerRounding::new(0.053)),
929 ],
930 10,
931 false,
932 )
933 .normalized()
934}
935
936#[derive(Debug, Clone, Copy, PartialEq)]
937struct ShapeVertex {
938 point: Point,
939 rounding: CornerRounding,
940}
941
942impl ShapeVertex {
943 fn new(x: f32, y: f32, rounding: CornerRounding) -> Self {
944 Self {
945 point: Point::new(x, y),
946 rounding,
947 }
948 }
949}
950
951fn custom_material_polygon(points: &[ShapeVertex], reps: usize, mirroring: bool) -> RoundedPolygon {
952 let center = Point::new(0.5, 0.5);
953 let repeated = repeat_material_vertices(points, reps, center, mirroring);
954 let vertices: Vec<Point> = repeated.iter().map(|vertex| vertex.point).collect();
955 let roundings: Vec<CornerRounding> = repeated.iter().map(|vertex| vertex.rounding).collect();
956
957 RoundedPolygon::from_vertices(&vertices, &roundings, Some(center))
958}
959
960fn repeat_material_vertices(
961 points: &[ShapeVertex],
962 reps: usize,
963 center: Point,
964 mirroring: bool,
965) -> Vec<ShapeVertex> {
966 if mirroring {
967 let angles: Vec<f32> = points
968 .iter()
969 .map(|vertex| (vertex.point.y - center.y).atan2(vertex.point.x - center.x))
970 .collect();
971 let distances: Vec<f32> = points
972 .iter()
973 .map(|vertex| point_distance(point_sub(vertex.point, center)))
974 .collect();
975 let actual_reps = reps * 2;
976 let section_angle = TAU / actual_reps as f32;
977 let mut vertices = Vec::with_capacity(points.len() * actual_reps);
978
979 for rep in 0..actual_reps {
980 for index in 0..points.len() {
981 let source = if rep % 2 == 0 {
982 index
983 } else {
984 points.len() - 1 - index
985 };
986
987 if source > 0 || rep % 2 == 0 {
988 let angle = section_angle * rep as f32
989 + if rep % 2 == 0 {
990 angles[source]
991 } else {
992 section_angle - angles[source] + 2.0 * angles[0]
993 };
994
995 vertices.push(ShapeVertex::new(
996 center.x + angle.cos() * distances[source],
997 center.y + angle.sin() * distances[source],
998 points[source].rounding,
999 ));
1000 }
1001 }
1002 }
1003
1004 vertices
1005 } else {
1006 let mut vertices = Vec::with_capacity(points.len() * reps);
1007
1008 for index in 0..points.len() * reps {
1009 let source = index % points.len();
1010 let rep = index / points.len();
1011 let point =
1012 rotate_point_around(points[source].point, center, rep as f32 * TAU / reps as f32);
1013
1014 vertices.push(ShapeVertex {
1015 point,
1016 rounding: points[source].rounding,
1017 });
1018 }
1019
1020 vertices
1021 }
1022}
1023
1024#[derive(Debug, Clone, Copy, PartialEq)]
1025struct CornerRounding {
1026 radius: f32,
1027 smoothing: f32,
1028}
1029
1030impl CornerRounding {
1031 const UNROUNDED: Self = Self {
1032 radius: 0.0,
1033 smoothing: 0.0,
1034 };
1035
1036 const fn new(radius: f32) -> Self {
1037 Self {
1038 radius,
1039 smoothing: 0.0,
1040 }
1041 }
1042}
1043
1044#[derive(Debug, Clone, Copy, PartialEq)]
1045struct Cubic {
1046 points: [f32; 8],
1047}
1048
1049impl Cubic {
1050 fn new(
1051 anchor0_x: f32,
1052 anchor0_y: f32,
1053 control0_x: f32,
1054 control0_y: f32,
1055 control1_x: f32,
1056 control1_y: f32,
1057 anchor1_x: f32,
1058 anchor1_y: f32,
1059 ) -> Self {
1060 Self {
1061 points: [
1062 anchor0_x, anchor0_y, control0_x, control0_y, control1_x, control1_y, anchor1_x,
1063 anchor1_y,
1064 ],
1065 }
1066 }
1067
1068 fn from_points(anchor0: Point, control0: Point, control1: Point, anchor1: Point) -> Self {
1069 Self::new(
1070 anchor0.x, anchor0.y, control0.x, control0.y, control1.x, control1.y, anchor1.x,
1071 anchor1.y,
1072 )
1073 }
1074
1075 fn straight_line(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
1076 Self::new(
1077 x0,
1078 y0,
1079 lerp(x0, x1, 1.0 / 3.0),
1080 lerp(y0, y1, 1.0 / 3.0),
1081 lerp(x0, x1, 2.0 / 3.0),
1082 lerp(y0, y1, 2.0 / 3.0),
1083 x1,
1084 y1,
1085 )
1086 }
1087
1088 fn circular_arc(center_x: f32, center_y: f32, x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
1089 let p0d = direction_vector(x0 - center_x, y0 - center_y);
1090 let p1d = direction_vector(x1 - center_x, y1 - center_y);
1091 let rotated_p0 = rotate90(p0d);
1092 let rotated_p1 = rotate90(p1d);
1093 let clockwise = point_dot(rotated_p0, Point::new(x1 - center_x, y1 - center_y)) >= 0.0;
1094 let cosa = point_dot(p0d, p1d);
1095
1096 if cosa > 0.999 {
1097 return Self::straight_line(x0, y0, x1, y1);
1098 }
1099
1100 let k = distance_components(x0 - center_x, y0 - center_y) * 4.0 / 3.0
1101 * ((2.0 * (1.0 - cosa)).sqrt() - (1.0 - cosa * cosa).sqrt())
1102 / (1.0 - cosa)
1103 * if clockwise { 1.0 } else { -1.0 };
1104
1105 Self::new(
1106 x0,
1107 y0,
1108 x0 + rotated_p0.x * k,
1109 y0 + rotated_p0.y * k,
1110 x1 - rotated_p1.x * k,
1111 y1 - rotated_p1.y * k,
1112 x1,
1113 y1,
1114 )
1115 }
1116
1117 fn anchor0_x(&self) -> f32 {
1118 self.points[0]
1119 }
1120
1121 fn anchor0_y(&self) -> f32 {
1122 self.points[1]
1123 }
1124
1125 fn control0_x(&self) -> f32 {
1126 self.points[2]
1127 }
1128
1129 fn control0_y(&self) -> f32 {
1130 self.points[3]
1131 }
1132
1133 fn control1_x(&self) -> f32 {
1134 self.points[4]
1135 }
1136
1137 fn control1_y(&self) -> f32 {
1138 self.points[5]
1139 }
1140
1141 fn anchor1_x(&self) -> f32 {
1142 self.points[6]
1143 }
1144
1145 fn anchor1_y(&self) -> f32 {
1146 self.points[7]
1147 }
1148
1149 fn point_on_curve(&self, t: f32) -> Point {
1150 let u = 1.0 - t;
1151
1152 Point::new(
1153 self.anchor0_x() * (u * u * u)
1154 + self.control0_x() * (3.0 * t * u * u)
1155 + self.control1_x() * (3.0 * t * t * u)
1156 + self.anchor1_x() * (t * t * t),
1157 self.anchor0_y() * (u * u * u)
1158 + self.control0_y() * (3.0 * t * u * u)
1159 + self.control1_y() * (3.0 * t * t * u)
1160 + self.anchor1_y() * (t * t * t),
1161 )
1162 }
1163
1164 fn split(&self, t: f32) -> (Self, Self) {
1165 let u = 1.0 - t;
1166 let point_on_curve = self.point_on_curve(t);
1167
1168 (
1169 Self::new(
1170 self.anchor0_x(),
1171 self.anchor0_y(),
1172 self.anchor0_x() * u + self.control0_x() * t,
1173 self.anchor0_y() * u + self.control0_y() * t,
1174 self.anchor0_x() * (u * u)
1175 + self.control0_x() * (2.0 * u * t)
1176 + self.control1_x() * (t * t),
1177 self.anchor0_y() * (u * u)
1178 + self.control0_y() * (2.0 * u * t)
1179 + self.control1_y() * (t * t),
1180 point_on_curve.x,
1181 point_on_curve.y,
1182 ),
1183 Self::new(
1184 point_on_curve.x,
1185 point_on_curve.y,
1186 self.control0_x() * (u * u)
1187 + self.control1_x() * (2.0 * u * t)
1188 + self.anchor1_x() * (t * t),
1189 self.control0_y() * (u * u)
1190 + self.control1_y() * (2.0 * u * t)
1191 + self.anchor1_y() * (t * t),
1192 self.control1_x() * u + self.anchor1_x() * t,
1193 self.control1_y() * u + self.anchor1_y() * t,
1194 self.anchor1_x(),
1195 self.anchor1_y(),
1196 ),
1197 )
1198 }
1199
1200 fn reverse(&self) -> Self {
1201 Self::new(
1202 self.anchor1_x(),
1203 self.anchor1_y(),
1204 self.control1_x(),
1205 self.control1_y(),
1206 self.control0_x(),
1207 self.control0_y(),
1208 self.anchor0_x(),
1209 self.anchor0_y(),
1210 )
1211 }
1212
1213 fn transformed(&self, mut f: impl FnMut(Point) -> Point) -> Self {
1214 Self::from_points(
1215 f(Point::new(self.anchor0_x(), self.anchor0_y())),
1216 f(Point::new(self.control0_x(), self.control0_y())),
1217 f(Point::new(self.control1_x(), self.control1_y())),
1218 f(Point::new(self.anchor1_x(), self.anchor1_y())),
1219 )
1220 }
1221
1222 fn zero_length(&self) -> bool {
1223 (self.anchor0_x() - self.anchor1_x()).abs() < DISTANCE_EPSILON
1224 && (self.anchor0_y() - self.anchor1_y()).abs() < DISTANCE_EPSILON
1225 }
1226
1227 fn calculate_bounds(&self, approximate: bool) -> [f32; 4] {
1228 if self.zero_length() {
1229 return [
1230 self.anchor0_x(),
1231 self.anchor0_y(),
1232 self.anchor0_x(),
1233 self.anchor0_y(),
1234 ];
1235 }
1236
1237 let mut min_x = self.anchor0_x().min(self.anchor1_x());
1238 let mut min_y = self.anchor0_y().min(self.anchor1_y());
1239 let mut max_x = self.anchor0_x().max(self.anchor1_x());
1240 let mut max_y = self.anchor0_y().max(self.anchor1_y());
1241
1242 if approximate {
1243 return [
1244 min_x.min(self.control0_x().min(self.control1_x())),
1245 min_y.min(self.control0_y().min(self.control1_y())),
1246 max_x.max(self.control0_x().max(self.control1_x())),
1247 max_y.max(self.control0_y().max(self.control1_y())),
1248 ];
1249 }
1250
1251 update_cubic_bounds_axis(
1252 self.anchor0_x(),
1253 self.control0_x(),
1254 self.control1_x(),
1255 self.anchor1_x(),
1256 |t| self.point_on_curve(t).x,
1257 &mut min_x,
1258 &mut max_x,
1259 );
1260 update_cubic_bounds_axis(
1261 self.anchor0_y(),
1262 self.control0_y(),
1263 self.control1_y(),
1264 self.anchor1_y(),
1265 |t| self.point_on_curve(t).y,
1266 &mut min_y,
1267 &mut max_y,
1268 );
1269
1270 [min_x, min_y, max_x, max_y]
1271 }
1272}
1273
1274#[derive(Debug, Clone, PartialEq)]
1275enum Feature {
1276 Edge(Vec<Cubic>),
1277 Corner { cubics: Vec<Cubic>, convex: bool },
1278}
1279
1280impl Feature {
1281 fn cubics(&self) -> &[Cubic] {
1282 match self {
1283 Self::Edge(cubics) | Self::Corner { cubics, .. } => cubics,
1284 }
1285 }
1286
1287 fn transformed(&self, f: impl Fn(Point) -> Point + Copy) -> Self {
1288 match self {
1289 Self::Edge(cubics) => {
1290 Self::Edge(cubics.iter().map(|cubic| cubic.transformed(f)).collect())
1291 }
1292 Self::Corner { cubics, convex } => Self::Corner {
1293 cubics: cubics.iter().map(|cubic| cubic.transformed(f)).collect(),
1294 convex: *convex,
1295 },
1296 }
1297 }
1298
1299 fn is_corner(&self) -> bool {
1300 matches!(self, Self::Corner { .. })
1301 }
1302
1303 fn is_convex_corner(&self) -> bool {
1304 matches!(self, Self::Corner { convex: true, .. })
1305 }
1306
1307 fn is_concave_corner(&self) -> bool {
1308 matches!(self, Self::Corner { convex: false, .. })
1309 }
1310}
1311
1312#[derive(Debug, Clone, PartialEq)]
1313struct RoundedPolygon {
1314 features: Vec<Feature>,
1315 center: Point,
1316 cubics: Vec<Cubic>,
1317}
1318
1319impl RoundedPolygon {
1320 fn from_features(features: Vec<Feature>, center: Point) -> Self {
1321 let cubics = polygon_cubics(&features, center);
1322
1323 Self {
1324 features,
1325 center,
1326 cubics,
1327 }
1328 }
1329
1330 fn from_vertices(
1331 vertices: &[Point],
1332 per_vertex_rounding: &[CornerRounding],
1333 center: Option<Point>,
1334 ) -> Self {
1335 assert!(vertices.len() >= 3);
1336 assert_eq!(vertices.len(), per_vertex_rounding.len());
1337
1338 let rounded_corners: Vec<PolygonCorner> = (0..vertices.len())
1339 .map(|index| {
1340 PolygonCorner::new(
1341 vertices[(index + vertices.len() - 1) % vertices.len()],
1342 vertices[index],
1343 vertices[(index + 1) % vertices.len()],
1344 per_vertex_rounding[index],
1345 )
1346 })
1347 .collect();
1348 let cut_adjusts: Vec<(f32, f32)> = (0..vertices.len())
1349 .map(|index| {
1350 let expected_round_cut = rounded_corners[index].expected_round_cut
1351 + rounded_corners[(index + 1) % vertices.len()].expected_round_cut;
1352 let expected_cut = rounded_corners[index].expected_cut()
1353 + rounded_corners[(index + 1) % vertices.len()].expected_cut();
1354 let side_size = point_distance(point_sub(
1355 vertices[index],
1356 vertices[(index + 1) % vertices.len()],
1357 ));
1358
1359 if expected_round_cut > side_size {
1360 (side_size / expected_round_cut, 0.0)
1361 } else if expected_cut > side_size {
1362 (
1363 1.0,
1364 (side_size - expected_round_cut) / (expected_cut - expected_round_cut),
1365 )
1366 } else {
1367 (1.0, 1.0)
1368 }
1369 })
1370 .collect();
1371 let corners: Vec<Vec<Cubic>> = (0..vertices.len())
1372 .map(|index| {
1373 let (round_cut_ratio0, cut_ratio0) =
1374 cut_adjusts[(index + vertices.len() - 1) % vertices.len()];
1375 let (round_cut_ratio1, cut_ratio1) = cut_adjusts[index];
1376 let allowed_cut0 = rounded_corners[index].expected_round_cut * round_cut_ratio0
1377 + (rounded_corners[index].expected_cut()
1378 - rounded_corners[index].expected_round_cut)
1379 * cut_ratio0;
1380 let allowed_cut1 = rounded_corners[index].expected_round_cut * round_cut_ratio1
1381 + (rounded_corners[index].expected_cut()
1382 - rounded_corners[index].expected_round_cut)
1383 * cut_ratio1;
1384
1385 rounded_corners[index].get_cubics(allowed_cut0, allowed_cut1)
1386 })
1387 .collect();
1388 let mut features = Vec::with_capacity(vertices.len() * 2);
1389
1390 for index in 0..vertices.len() {
1391 let previous = vertices[(index + vertices.len() - 1) % vertices.len()];
1392 let current = vertices[index];
1393 let next = vertices[(index + 1) % vertices.len()];
1394 let convex = convex(previous, current, next);
1395
1396 features.push(Feature::Corner {
1397 cubics: corners[index].clone(),
1398 convex,
1399 });
1400 features.push(Feature::Edge(vec![Cubic::straight_line(
1401 corners[index].last().unwrap().anchor1_x(),
1402 corners[index].last().unwrap().anchor1_y(),
1403 corners[(index + 1) % vertices.len()]
1404 .first()
1405 .unwrap()
1406 .anchor0_x(),
1407 corners[(index + 1) % vertices.len()]
1408 .first()
1409 .unwrap()
1410 .anchor0_y(),
1411 )]));
1412 }
1413
1414 Self::from_features(
1415 features,
1416 center.unwrap_or_else(|| calculate_center(vertices)),
1417 )
1418 }
1419
1420 fn transformed(&self, f: impl Fn(Point) -> Point + Copy) -> Self {
1421 Self::from_features(
1422 self.features
1423 .iter()
1424 .map(|feature| feature.transformed(f))
1425 .collect(),
1426 f(self.center),
1427 )
1428 }
1429
1430 fn normalized(&self) -> Self {
1431 let bounds = self.calculate_bounds(true);
1432 let width = bounds_width(bounds);
1433 let height = bounds_height(bounds);
1434 let side = width.max(height);
1435 let offset_x = (side - width) / 2.0 - bounds[0];
1436 let offset_y = (side - height) / 2.0 - bounds[1];
1437
1438 self.transformed(|point| {
1439 Point::new((point.x + offset_x) / side, (point.y + offset_y) / side)
1440 })
1441 }
1442
1443 fn calculate_bounds(&self, approximate: bool) -> [f32; 4] {
1444 cubics_bounds(&self.cubics, approximate)
1445 }
1446
1447 fn calculate_max_bounds(&self) -> [f32; 4] {
1448 let mut max_dist_squared = 0.0_f32;
1449
1450 for cubic in &self.cubics {
1451 let anchor_distance = distance_squared(
1452 cubic.anchor0_x() - self.center.x,
1453 cubic.anchor0_y() - self.center.y,
1454 );
1455 let middle = cubic.point_on_curve(0.5);
1456 let middle_distance =
1457 distance_squared(middle.x - self.center.x, middle.y - self.center.y);
1458
1459 max_dist_squared = max_dist_squared.max(anchor_distance.max(middle_distance));
1460 }
1461
1462 let distance = max_dist_squared.sqrt();
1463
1464 [
1465 self.center.x - distance,
1466 self.center.y - distance,
1467 self.center.x + distance,
1468 self.center.y + distance,
1469 ]
1470 }
1471}
1472
1473fn rounded_polygon_circle(num_vertices: usize, radius: f32, center: Point) -> RoundedPolygon {
1474 let theta = std::f32::consts::PI / num_vertices as f32;
1475 let polygon_radius = radius / theta.cos();
1476 let vertices = vertices_from_num_verts(num_vertices, polygon_radius, center);
1477 let roundings = vec![CornerRounding::new(radius); num_vertices];
1478
1479 RoundedPolygon::from_vertices(&vertices, &roundings, Some(center))
1480}
1481
1482fn rounded_polygon_star(
1483 num_vertices_per_radius: usize,
1484 radius: f32,
1485 inner_radius: f32,
1486 rounding: CornerRounding,
1487 center: Point,
1488) -> RoundedPolygon {
1489 assert!(radius > 0.0 && inner_radius > 0.0 && inner_radius < radius);
1490
1491 let vertices =
1492 star_vertices_from_num_verts(num_vertices_per_radius, radius, inner_radius, center);
1493 let roundings = vec![rounding; vertices.len()];
1494
1495 RoundedPolygon::from_vertices(&vertices, &roundings, Some(center))
1496}
1497
1498fn vertices_from_num_verts(num_vertices: usize, radius: f32, center: Point) -> Vec<Point> {
1499 (0..num_vertices)
1500 .map(|index| radial_to_cartesian(radius, TAU / num_vertices as f32 * index as f32, center))
1501 .collect()
1502}
1503
1504fn star_vertices_from_num_verts(
1505 num_vertices_per_radius: usize,
1506 radius: f32,
1507 inner_radius: f32,
1508 center: Point,
1509) -> Vec<Point> {
1510 let mut vertices = Vec::with_capacity(num_vertices_per_radius * 2);
1511
1512 for index in 0..num_vertices_per_radius {
1513 vertices.push(radial_to_cartesian(
1514 radius,
1515 TAU / num_vertices_per_radius as f32 * index as f32,
1516 center,
1517 ));
1518 vertices.push(radial_to_cartesian(
1519 inner_radius,
1520 std::f32::consts::PI / num_vertices_per_radius as f32 * (2 * index + 1) as f32,
1521 center,
1522 ));
1523 }
1524
1525 vertices
1526}
1527
1528fn polygon_cubics(features: &[Feature], center: Point) -> Vec<Cubic> {
1529 let mut cubics = Vec::new();
1530 let mut first_cubic = None;
1531 let mut last_cubic: Option<Cubic> = None;
1532 let mut first_feature_split_start = None;
1533 let mut first_feature_split_end = None;
1534
1535 if !features.is_empty() && features[0].cubics().len() == 3 {
1536 let (start, end) = features[0].cubics()[1].split(0.5);
1537 first_feature_split_start = Some(vec![features[0].cubics()[0], start]);
1538 first_feature_split_end = Some(vec![end, features[0].cubics()[2]]);
1539 }
1540
1541 for index in 0..=features.len() {
1542 let feature_cubics: Option<&[Cubic]> = if index == 0 {
1543 first_feature_split_end
1544 .as_deref()
1545 .or(Some(features[0].cubics()))
1546 } else if index == features.len() {
1547 first_feature_split_start.as_deref()
1548 } else {
1549 Some(features[index].cubics())
1550 };
1551
1552 let Some(feature_cubics) = feature_cubics else {
1553 break;
1554 };
1555
1556 for cubic in feature_cubics {
1557 if !cubic.zero_length() {
1558 if let Some(last) = last_cubic.take() {
1559 cubics.push(last);
1560 }
1561
1562 last_cubic = Some(*cubic);
1563 let _ = first_cubic.get_or_insert(*cubic);
1564 } else if let Some(last) = last_cubic.as_mut() {
1565 last.points[6] = cubic.anchor1_x();
1566 last.points[7] = cubic.anchor1_y();
1567 }
1568 }
1569 }
1570
1571 if let (Some(last), Some(first)) = (last_cubic, first_cubic) {
1572 cubics.push(Cubic::new(
1573 last.anchor0_x(),
1574 last.anchor0_y(),
1575 last.control0_x(),
1576 last.control0_y(),
1577 last.control1_x(),
1578 last.control1_y(),
1579 first.anchor0_x(),
1580 first.anchor0_y(),
1581 ));
1582 } else {
1583 cubics.push(Cubic::new(
1584 center.x, center.y, center.x, center.y, center.x, center.y, center.x, center.y,
1585 ));
1586 }
1587
1588 cubics
1589}
1590
1591#[derive(Debug, Clone, Copy)]
1592struct PolygonCorner {
1593 p0: Point,
1594 p1: Point,
1595 p2: Point,
1596 d1: Point,
1597 d2: Point,
1598 corner_radius: f32,
1599 smoothing: f32,
1600 expected_round_cut: f32,
1601}
1602
1603impl PolygonCorner {
1604 fn new(p0: Point, p1: Point, p2: Point, rounding: CornerRounding) -> Self {
1605 let v01 = point_sub(p0, p1);
1606 let v21 = point_sub(p2, p1);
1607 let d01 = point_distance(v01);
1608 let d21 = point_distance(v21);
1609
1610 if d01 > 0.0 && d21 > 0.0 {
1611 let d1 = point_scale(v01, 1.0 / d01);
1612 let d2 = point_scale(v21, 1.0 / d21);
1613 let cos_angle = point_dot(d1, d2);
1614 let sin_angle = (1.0 - square(cos_angle)).sqrt();
1615 let expected_round_cut = if sin_angle > 1e-3 {
1616 rounding.radius * (cos_angle + 1.0) / sin_angle
1617 } else {
1618 0.0
1619 };
1620
1621 Self {
1622 p0,
1623 p1,
1624 p2,
1625 d1,
1626 d2,
1627 corner_radius: rounding.radius,
1628 smoothing: rounding.smoothing,
1629 expected_round_cut,
1630 }
1631 } else {
1632 Self {
1633 p0,
1634 p1,
1635 p2,
1636 d1: Point::ORIGIN,
1637 d2: Point::ORIGIN,
1638 corner_radius: 0.0,
1639 smoothing: 0.0,
1640 expected_round_cut: 0.0,
1641 }
1642 }
1643 }
1644
1645 fn expected_cut(&self) -> f32 {
1646 (1.0 + self.smoothing) * self.expected_round_cut
1647 }
1648
1649 fn get_cubics(&self, allowed_cut0: f32, allowed_cut1: f32) -> Vec<Cubic> {
1650 let allowed_cut = allowed_cut0.min(allowed_cut1);
1651
1652 if self.expected_round_cut < DISTANCE_EPSILON
1653 || allowed_cut < DISTANCE_EPSILON
1654 || self.corner_radius < DISTANCE_EPSILON
1655 {
1656 return vec![Cubic::straight_line(
1657 self.p1.x, self.p1.y, self.p1.x, self.p1.y,
1658 )];
1659 }
1660
1661 let actual_round_cut = allowed_cut.min(self.expected_round_cut);
1662 let actual_smoothing0 = self.calculate_actual_smoothing_value(allowed_cut0);
1663 let actual_smoothing1 = self.calculate_actual_smoothing_value(allowed_cut1);
1664 let actual_radius = self.corner_radius * actual_round_cut / self.expected_round_cut;
1665 let center_distance = (square(actual_radius) + square(actual_round_cut)).sqrt();
1666 let circle_center = point_add(
1667 self.p1,
1668 point_scale(
1669 point_direction(point_scale(point_add(self.d1, self.d2), 0.5)),
1670 center_distance,
1671 ),
1672 );
1673 let circle_intersection0 = point_add(self.p1, point_scale(self.d1, actual_round_cut));
1674 let circle_intersection2 = point_add(self.p1, point_scale(self.d2, actual_round_cut));
1675 let flanking0 = self.compute_flanking_curve(
1676 actual_round_cut,
1677 actual_smoothing0,
1678 self.p1,
1679 self.p0,
1680 circle_intersection0,
1681 circle_intersection2,
1682 circle_center,
1683 actual_radius,
1684 );
1685 let flanking2 = self
1686 .compute_flanking_curve(
1687 actual_round_cut,
1688 actual_smoothing1,
1689 self.p1,
1690 self.p2,
1691 circle_intersection2,
1692 circle_intersection0,
1693 circle_center,
1694 actual_radius,
1695 )
1696 .reverse();
1697
1698 vec![
1699 flanking0,
1700 Cubic::circular_arc(
1701 circle_center.x,
1702 circle_center.y,
1703 flanking0.anchor1_x(),
1704 flanking0.anchor1_y(),
1705 flanking2.anchor0_x(),
1706 flanking2.anchor0_y(),
1707 ),
1708 flanking2,
1709 ]
1710 }
1711
1712 fn calculate_actual_smoothing_value(&self, allowed_cut: f32) -> f32 {
1713 if allowed_cut > self.expected_cut() {
1714 self.smoothing
1715 } else if allowed_cut > self.expected_round_cut {
1716 self.smoothing * (allowed_cut - self.expected_round_cut)
1717 / (self.expected_cut() - self.expected_round_cut)
1718 } else {
1719 0.0
1720 }
1721 }
1722
1723 #[allow(clippy::too_many_arguments)]
1724 fn compute_flanking_curve(
1725 &self,
1726 actual_round_cut: f32,
1727 actual_smoothing_value: f32,
1728 corner: Point,
1729 side_start: Point,
1730 circle_segment_intersection: Point,
1731 other_circle_segment_intersection: Point,
1732 circle_center: Point,
1733 actual_radius: f32,
1734 ) -> Cubic {
1735 let side_direction = point_direction(point_sub(side_start, corner));
1736 let curve_start = point_add(
1737 corner,
1738 point_scale(
1739 side_direction,
1740 actual_round_cut * (1.0 + actual_smoothing_value),
1741 ),
1742 );
1743 let p = point_lerp(
1744 circle_segment_intersection,
1745 point_scale(
1746 point_add(
1747 circle_segment_intersection,
1748 other_circle_segment_intersection,
1749 ),
1750 0.5,
1751 ),
1752 actual_smoothing_value,
1753 );
1754 let curve_end = point_add(
1755 circle_center,
1756 point_scale(
1757 direction_vector(p.x - circle_center.x, p.y - circle_center.y),
1758 actual_radius,
1759 ),
1760 );
1761 let circle_tangent = rotate90(point_sub(curve_end, circle_center));
1762 let anchor_end = line_intersection(side_start, side_direction, curve_end, circle_tangent)
1763 .unwrap_or(circle_segment_intersection);
1764 let anchor_start = point_scale(
1765 point_add(curve_start, point_scale(anchor_end, 2.0)),
1766 1.0 / 3.0,
1767 );
1768
1769 Cubic::from_points(curve_start, anchor_start, anchor_end, curve_end)
1770 }
1771}
1772
1773fn line_intersection(p0: Point, d0: Point, p1: Point, d1: Point) -> Option<Point> {
1774 let rotated_d1 = rotate90(d1);
1775 let denominator = point_dot(d0, rotated_d1);
1776
1777 if denominator.abs() < DISTANCE_EPSILON {
1778 return None;
1779 }
1780
1781 let numerator = point_dot(point_sub(p1, p0), rotated_d1);
1782
1783 if denominator.abs() < DISTANCE_EPSILON * numerator.abs() {
1784 return None;
1785 }
1786
1787 Some(point_add(p0, point_scale(d0, numerator / denominator)))
1788}
1789
1790#[derive(Debug, Clone)]
1791struct Morph {
1792 pairs: Vec<(Cubic, Cubic)>,
1793}
1794
1795impl Morph {
1796 fn new(start: RoundedPolygon, end: RoundedPolygon) -> Self {
1797 Self {
1798 pairs: match_polygons(&start, &end),
1799 }
1800 }
1801
1802 fn as_cubics(&self, progress: f32) -> Vec<Cubic> {
1803 let mut cubics = Vec::with_capacity(self.pairs.len());
1804 let mut first_cubic = None;
1805 let mut last_cubic = None;
1806
1807 for (start, end) in &self.pairs {
1808 let cubic = Cubic {
1809 points: std::array::from_fn(|index| {
1810 lerp(start.points[index], end.points[index], progress)
1811 }),
1812 };
1813
1814 let _ = first_cubic.get_or_insert(cubic);
1815 if let Some(last) = last_cubic.take() {
1816 cubics.push(last);
1817 }
1818 last_cubic = Some(cubic);
1819 }
1820
1821 if let (Some(last), Some(first)) = (last_cubic, first_cubic) {
1822 cubics.push(Cubic::new(
1823 last.anchor0_x(),
1824 last.anchor0_y(),
1825 last.control0_x(),
1826 last.control0_y(),
1827 last.control1_x(),
1828 last.control1_y(),
1829 first.anchor0_x(),
1830 first.anchor0_y(),
1831 ));
1832 }
1833
1834 cubics
1835 }
1836}
1837
1838#[derive(Debug, Clone)]
1839struct ProgressableFeature {
1840 progress: f32,
1841 feature: Feature,
1842}
1843
1844#[derive(Debug, Clone)]
1845struct MeasuredCubic {
1846 cubic: Cubic,
1847 start_outline_progress: f32,
1848 end_outline_progress: f32,
1849}
1850
1851impl MeasuredCubic {
1852 fn cut_at_progress(&self, cut_outline_progress: f32) -> (Self, Self) {
1853 let bounded_cut_outline_progress =
1854 cut_outline_progress.clamp(self.start_outline_progress, self.end_outline_progress);
1855 let outline_progress_size = self.end_outline_progress - self.start_outline_progress;
1856 let progress_from_start = bounded_cut_outline_progress - self.start_outline_progress;
1857 let relative_progress = progress_from_start / outline_progress_size;
1858 let measured_size = measure_cubic(self.cubic);
1859 let t = find_cubic_cut_point(self.cubic, relative_progress * measured_size);
1860 let (first, second) = self.cubic.split(t);
1861
1862 (
1863 Self {
1864 cubic: first,
1865 start_outline_progress: self.start_outline_progress,
1866 end_outline_progress: bounded_cut_outline_progress,
1867 },
1868 Self {
1869 cubic: second,
1870 start_outline_progress: bounded_cut_outline_progress,
1871 end_outline_progress: self.end_outline_progress,
1872 },
1873 )
1874 }
1875}
1876
1877#[derive(Debug, Clone)]
1878struct MeasuredPolygon {
1879 features: Vec<ProgressableFeature>,
1880 cubics: Vec<MeasuredCubic>,
1881}
1882
1883impl MeasuredPolygon {
1884 fn new(
1885 features: Vec<ProgressableFeature>,
1886 cubics: Vec<Cubic>,
1887 outline_progress: Vec<f32>,
1888 ) -> Self {
1889 assert_eq!(outline_progress.len(), cubics.len() + 1);
1890 assert!((outline_progress[0] - 0.0).abs() < DISTANCE_EPSILON);
1891 assert!((outline_progress[outline_progress.len() - 1] - 1.0).abs() < DISTANCE_EPSILON);
1892
1893 let mut measured_cubics = Vec::new();
1894 let mut start_outline_progress = 0.0;
1895
1896 for index in 0..cubics.len() {
1897 if outline_progress[index + 1] - outline_progress[index] > DISTANCE_EPSILON {
1898 measured_cubics.push(MeasuredCubic {
1899 cubic: cubics[index],
1900 start_outline_progress,
1901 end_outline_progress: outline_progress[index + 1],
1902 });
1903 start_outline_progress = outline_progress[index + 1];
1904 }
1905 }
1906
1907 if let Some(last) = measured_cubics.last_mut() {
1908 last.end_outline_progress = 1.0;
1909 }
1910
1911 Self {
1912 features,
1913 cubics: measured_cubics,
1914 }
1915 }
1916
1917 fn measure_polygon(polygon: &RoundedPolygon) -> Self {
1918 let mut cubics = Vec::new();
1919 let mut feature_to_cubic = Vec::new();
1920
1921 for feature in &polygon.features {
1922 for (cubic_index, cubic) in feature.cubics().iter().enumerate() {
1923 if feature.is_corner() && cubic_index == feature.cubics().len() / 2 {
1924 feature_to_cubic.push((feature.clone(), cubics.len()));
1925 }
1926 cubics.push(*cubic);
1927 }
1928 }
1929
1930 let mut measures = Vec::with_capacity(cubics.len() + 1);
1931 let mut total = 0.0;
1932 measures.push(total);
1933
1934 for cubic in &cubics {
1935 total += measure_cubic(*cubic);
1936 measures.push(total);
1937 }
1938
1939 let outline_progress: Vec<f32> = measures.iter().map(|measure| measure / total).collect();
1940 let features = feature_to_cubic
1941 .into_iter()
1942 .map(|(feature, index)| ProgressableFeature {
1943 progress: positive_modulo(
1944 (outline_progress[index] + outline_progress[index + 1]) / 2.0,
1945 1.0,
1946 ),
1947 feature,
1948 })
1949 .collect();
1950
1951 Self::new(features, cubics, outline_progress)
1952 }
1953
1954 fn cut_and_shift(&self, cutting_point: f32) -> Self {
1955 assert!((0.0..=1.0).contains(&cutting_point));
1956
1957 if cutting_point < DISTANCE_EPSILON {
1958 return self.clone();
1959 }
1960
1961 let target_index = self
1962 .cubics
1963 .iter()
1964 .position(|cubic| {
1965 cutting_point >= cubic.start_outline_progress
1966 && cutting_point <= cubic.end_outline_progress
1967 })
1968 .unwrap_or(self.cubics.len() - 1);
1969 let target = &self.cubics[target_index];
1970 let (first, second) = target.cut_at_progress(cutting_point);
1971 let mut cubics = Vec::with_capacity(self.cubics.len() + 1);
1972
1973 cubics.push(second.cubic);
1974 for index in 1..self.cubics.len() {
1975 cubics.push(self.cubics[(index + target_index) % self.cubics.len()].cubic);
1976 }
1977 cubics.push(first.cubic);
1978
1979 let mut outline_progress = Vec::with_capacity(self.cubics.len() + 2);
1980
1981 for index in 0..self.cubics.len() + 2 {
1982 outline_progress.push(match index {
1983 0 => 0.0,
1984 n if n == self.cubics.len() + 1 => 1.0,
1985 _ => {
1986 let cubic_index = (target_index + index - 1) % self.cubics.len();
1987 positive_modulo(
1988 self.cubics[cubic_index].end_outline_progress - cutting_point,
1989 1.0,
1990 )
1991 }
1992 });
1993 }
1994
1995 let features = self
1996 .features
1997 .iter()
1998 .map(|feature| ProgressableFeature {
1999 progress: positive_modulo(feature.progress - cutting_point, 1.0),
2000 feature: feature.feature.clone(),
2001 })
2002 .collect();
2003
2004 Self::new(features, cubics, outline_progress)
2005 }
2006}
2007
2008fn match_polygons(start: &RoundedPolygon, end: &RoundedPolygon) -> Vec<(Cubic, Cubic)> {
2009 let measured_start = MeasuredPolygon::measure_polygon(start);
2010 let measured_end = MeasuredPolygon::measure_polygon(end);
2011 let mapper = feature_mapper(&measured_start.features, &measured_end.features);
2012 let end_cut_point = mapper.map(0.0);
2013 let shifted_start = measured_start;
2014 let shifted_end = measured_end.cut_and_shift(end_cut_point);
2015 let mut pairs = Vec::new();
2016 let mut start_index = 0;
2017 let mut end_index = 0;
2018 let mut start_cubic = shifted_start.cubics.get(start_index).cloned();
2019 start_index += 1;
2020 let mut end_cubic = shifted_end.cubics.get(end_index).cloned();
2021 end_index += 1;
2022
2023 while let (Some(start), Some(end)) = (start_cubic.clone(), end_cubic.clone()) {
2024 let start_end_progress = if start_index == shifted_start.cubics.len() {
2025 1.0
2026 } else {
2027 start.end_outline_progress
2028 };
2029 let end_end_progress = if end_index == shifted_end.cubics.len() {
2030 1.0
2031 } else {
2032 mapper.map_back(positive_modulo(
2033 end.end_outline_progress + end_cut_point,
2034 1.0,
2035 ))
2036 };
2037 let min_progress = start_end_progress.min(end_end_progress);
2038 let (start_segment, new_start) = if start_end_progress > min_progress + ANGLE_EPSILON {
2039 let (segment, remainder) = start.cut_at_progress(min_progress);
2040
2041 (segment, Some(remainder))
2042 } else {
2043 let next = shifted_start.cubics.get(start_index).cloned();
2044 start_index += 1;
2045
2046 (start, next)
2047 };
2048 let (end_segment, new_end) = if end_end_progress > min_progress + ANGLE_EPSILON {
2049 let (segment, remainder) = end.cut_at_progress(positive_modulo(
2050 mapper.map(min_progress) - end_cut_point,
2051 1.0,
2052 ));
2053
2054 (segment, Some(remainder))
2055 } else {
2056 let next = shifted_end.cubics.get(end_index).cloned();
2057 end_index += 1;
2058
2059 (end, next)
2060 };
2061
2062 pairs.push((start_segment.cubic, end_segment.cubic));
2063 start_cubic = new_start;
2064 end_cubic = new_end;
2065 }
2066
2067 assert!(start_cubic.is_none() && end_cubic.is_none());
2068
2069 pairs
2070}
2071
2072#[derive(Debug, Clone)]
2073struct DoubleMapper {
2074 source_values: Vec<f32>,
2075 target_values: Vec<f32>,
2076}
2077
2078impl DoubleMapper {
2079 fn new(mappings: &[(f32, f32)]) -> Self {
2080 let source_values = mappings.iter().map(|mapping| mapping.0).collect();
2081 let target_values = mappings.iter().map(|mapping| mapping.1).collect();
2082
2083 Self {
2084 source_values,
2085 target_values,
2086 }
2087 }
2088
2089 fn map(&self, progress: f32) -> f32 {
2090 linear_map(&self.source_values, &self.target_values, progress)
2091 }
2092
2093 fn map_back(&self, progress: f32) -> f32 {
2094 linear_map(&self.target_values, &self.source_values, progress)
2095 }
2096}
2097
2098fn feature_mapper(
2099 features1: &[ProgressableFeature],
2100 features2: &[ProgressableFeature],
2101) -> DoubleMapper {
2102 let filtered1: Vec<ProgressableFeature> = features1
2103 .iter()
2104 .filter(|feature| feature.feature.is_corner())
2105 .cloned()
2106 .collect();
2107 let filtered2: Vec<ProgressableFeature> = features2
2108 .iter()
2109 .filter(|feature| feature.feature.is_corner())
2110 .cloned()
2111 .collect();
2112 let mappings = feature_mapping(&filtered1, &filtered2);
2113
2114 DoubleMapper::new(&mappings)
2115}
2116
2117fn feature_mapping(
2118 features1: &[ProgressableFeature],
2119 features2: &[ProgressableFeature],
2120) -> Vec<(f32, f32)> {
2121 let mut distances = Vec::new();
2122
2123 for (index1, feature1) in features1.iter().enumerate() {
2124 for (index2, feature2) in features2.iter().enumerate() {
2125 let distance = feature_distance_squared(&feature1.feature, &feature2.feature);
2126
2127 if distance != f32::MAX {
2128 distances.push((distance, index1, index2));
2129 }
2130 }
2131 }
2132
2133 distances.sort_by(|a, b| a.0.total_cmp(&b.0));
2134
2135 if distances.is_empty() {
2136 return vec![(0.0, 0.0), (0.5, 0.5)];
2137 }
2138
2139 if distances.len() == 1 {
2140 let (_, index1, index2) = distances[0];
2141 let f1 = features1[index1].progress;
2142 let f2 = features2[index2].progress;
2143
2144 return vec![(f1, f2), ((f1 + 0.5) % 1.0, (f2 + 0.5) % 1.0)];
2145 }
2146
2147 let mut helper = MappingHelper::new();
2148
2149 for (_, index1, index2) in distances {
2150 helper.add_mapping(features1, features2, index1, index2);
2151 }
2152
2153 helper.mapping
2154}
2155
2156struct MappingHelper {
2157 mapping: Vec<(f32, f32)>,
2158 used1: Vec<usize>,
2159 used2: Vec<usize>,
2160}
2161
2162impl MappingHelper {
2163 fn new() -> Self {
2164 Self {
2165 mapping: Vec::new(),
2166 used1: Vec::new(),
2167 used2: Vec::new(),
2168 }
2169 }
2170
2171 fn add_mapping(
2172 &mut self,
2173 features1: &[ProgressableFeature],
2174 features2: &[ProgressableFeature],
2175 index1: usize,
2176 index2: usize,
2177 ) {
2178 if self.used1.contains(&index1) || self.used2.contains(&index2) {
2179 return;
2180 }
2181
2182 let f1 = features1[index1].progress;
2183 let f2 = features2[index2].progress;
2184 let insertion_index = self
2185 .mapping
2186 .iter()
2187 .position(|mapping| mapping.0 > f1)
2188 .unwrap_or(self.mapping.len());
2189 let len = self.mapping.len();
2190
2191 if len >= 1 {
2192 let before = self.mapping[(insertion_index + len - 1) % len];
2193 let after = self.mapping[insertion_index % len];
2194
2195 if progress_distance(f1, before.0) < DISTANCE_EPSILON
2196 || progress_distance(f1, after.0) < DISTANCE_EPSILON
2197 || progress_distance(f2, before.1) < DISTANCE_EPSILON
2198 || progress_distance(f2, after.1) < DISTANCE_EPSILON
2199 {
2200 return;
2201 }
2202
2203 if len > 1 && !progress_in_range(f2, before.1, after.1) {
2204 return;
2205 }
2206 }
2207
2208 self.mapping.insert(insertion_index, (f1, f2));
2209 self.used1.push(index1);
2210 self.used2.push(index2);
2211 }
2212}
2213
2214fn feature_distance_squared(first: &Feature, second: &Feature) -> f32 {
2215 if (first.is_convex_corner() && second.is_concave_corner())
2216 || (first.is_concave_corner() && second.is_convex_corner())
2217 {
2218 return f32::MAX;
2219 }
2220
2221 distance_squared_point(point_sub(
2222 feature_representative_point(first),
2223 feature_representative_point(second),
2224 ))
2225}
2226
2227fn feature_representative_point(feature: &Feature) -> Point {
2228 Point::new(
2229 (feature.cubics().first().unwrap().anchor0_x()
2230 + feature.cubics().last().unwrap().anchor1_x())
2231 / 2.0,
2232 (feature.cubics().first().unwrap().anchor0_y()
2233 + feature.cubics().last().unwrap().anchor1_y())
2234 / 2.0,
2235 )
2236}
2237
2238fn linear_map(x_values: &[f32], y_values: &[f32], progress: f32) -> f32 {
2239 let progress = if progress >= 1.0 {
2240 0.0
2241 } else {
2242 positive_modulo(progress, 1.0)
2243 };
2244 let segment_start_index = (0..x_values.len())
2245 .find(|index| {
2246 progress_in_range(
2247 progress,
2248 x_values[*index],
2249 x_values[(*index + 1) % x_values.len()],
2250 )
2251 })
2252 .unwrap_or(0);
2253 let segment_end_index = (segment_start_index + 1) % x_values.len();
2254 let segment_size_x = positive_modulo(
2255 x_values[segment_end_index] - x_values[segment_start_index],
2256 1.0,
2257 );
2258 let segment_size_y = positive_modulo(
2259 y_values[segment_end_index] - y_values[segment_start_index],
2260 1.0,
2261 );
2262 let position = if segment_size_x < 0.001 {
2263 0.5
2264 } else {
2265 positive_modulo(progress - x_values[segment_start_index], 1.0) / segment_size_x
2266 };
2267
2268 positive_modulo(
2269 y_values[segment_start_index] + segment_size_y * position,
2270 1.0,
2271 )
2272}
2273
2274fn progress_in_range(progress: f32, from: f32, to: f32) -> bool {
2275 if to >= from {
2276 (from..=to).contains(&progress)
2277 } else {
2278 progress >= from || progress <= to
2279 }
2280}
2281
2282fn progress_distance(first: f32, second: f32) -> f32 {
2283 let distance = (first - second).abs();
2284
2285 distance.min(1.0 - distance)
2286}
2287
2288fn measure_cubic(cubic: Cubic) -> f32 {
2289 closest_progress_to(cubic, f32::INFINITY).1
2290}
2291
2292fn find_cubic_cut_point(cubic: Cubic, measure: f32) -> f32 {
2293 closest_progress_to(cubic, measure).0
2294}
2295
2296fn closest_progress_to(cubic: Cubic, threshold: f32) -> (f32, f32) {
2297 const SEGMENTS: usize = 3;
2298 let mut total = 0.0;
2299 let mut remainder = threshold;
2300 let mut previous = Point::new(cubic.anchor0_x(), cubic.anchor0_y());
2301
2302 for index in 1..=SEGMENTS {
2303 let progress = index as f32 / SEGMENTS as f32;
2304 let point = cubic.point_on_curve(progress);
2305 let segment = point_distance(point_sub(point, previous));
2306
2307 if segment >= remainder {
2308 return (
2309 progress - (1.0 - remainder / segment) / SEGMENTS as f32,
2310 threshold,
2311 );
2312 }
2313
2314 remainder -= segment;
2315 total += segment;
2316 previous = point;
2317 }
2318
2319 (1.0, total)
2320}
2321
2322fn update_cubic_bounds_axis(
2323 anchor0: f32,
2324 control0: f32,
2325 control1: f32,
2326 anchor1: f32,
2327 point: impl Fn(f32) -> f32,
2328 min_value: &mut f32,
2329 max_value: &mut f32,
2330) {
2331 let a = -anchor0 + 3.0 * control0 - 3.0 * control1 + anchor1;
2332 let b = 2.0 * anchor0 - 4.0 * control0 + 2.0 * control1;
2333 let c = -anchor0 + control0;
2334
2335 if a.abs() < DISTANCE_EPSILON {
2336 if b != 0.0 {
2337 let t = 2.0 * c / (-2.0 * b);
2338 update_bounds_with_curve_point(t, &point, min_value, max_value);
2339 }
2340 } else {
2341 let discriminant = b * b - 4.0 * a * c;
2342
2343 if discriminant >= 0.0 {
2344 update_bounds_with_curve_point(
2345 (-b + discriminant.sqrt()) / (2.0 * a),
2346 &point,
2347 min_value,
2348 max_value,
2349 );
2350 update_bounds_with_curve_point(
2351 (-b - discriminant.sqrt()) / (2.0 * a),
2352 &point,
2353 min_value,
2354 max_value,
2355 );
2356 }
2357 }
2358}
2359
2360fn update_bounds_with_curve_point(
2361 t: f32,
2362 point: &impl Fn(f32) -> f32,
2363 min_value: &mut f32,
2364 max_value: &mut f32,
2365) {
2366 if (0.0..=1.0).contains(&t) {
2367 let value = point(t);
2368 *min_value = min_value.min(value);
2369 *max_value = max_value.max(value);
2370 }
2371}
2372
2373fn cubics_bounds(cubics: &[Cubic], approximate: bool) -> [f32; 4] {
2374 let mut min_x = f32::INFINITY;
2375 let mut min_y = f32::INFINITY;
2376 let mut max_x = f32::NEG_INFINITY;
2377 let mut max_y = f32::NEG_INFINITY;
2378
2379 for cubic in cubics {
2380 let bounds = cubic.calculate_bounds(approximate);
2381
2382 min_x = min_x.min(bounds[0]);
2383 min_y = min_y.min(bounds[1]);
2384 max_x = max_x.max(bounds[2]);
2385 max_y = max_y.max(bounds[3]);
2386 }
2387
2388 [min_x, min_y, max_x, max_y]
2389}
2390
2391fn bounds_width(bounds: [f32; 4]) -> f32 {
2392 bounds[2] - bounds[0]
2393}
2394
2395fn bounds_height(bounds: [f32; 4]) -> f32 {
2396 bounds[3] - bounds[1]
2397}
2398
2399fn bounds_center(bounds: [f32; 4]) -> Point {
2400 Point::new((bounds[0] + bounds[2]) / 2.0, (bounds[1] + bounds[3]) / 2.0)
2401}
2402
2403fn calculate_center(vertices: &[Point]) -> Point {
2404 let sum = vertices
2405 .iter()
2406 .fold(Point::ORIGIN, |sum, point| point_add(sum, *point));
2407
2408 point_scale(sum, 1.0 / vertices.len() as f32)
2409}
2410
2411fn radial_to_cartesian(radius: f32, angle: f32, center: Point) -> Point {
2412 point_add(
2413 point_scale(direction_vector_from_angle(angle), radius),
2414 center,
2415 )
2416}
2417
2418fn convex(previous: Point, current: Point, next: Point) -> bool {
2419 point_clockwise(point_sub(current, previous), point_sub(next, current))
2420}
2421
2422fn rotate_point(point: Point, rotation: f32) -> Point {
2423 let cos = rotation.cos();
2424 let sin = rotation.sin();
2425
2426 Point::new(point.x * cos - point.y * sin, point.x * sin + point.y * cos)
2427}
2428
2429fn rotate_point_around(point: Point, center: Point, rotation: f32) -> Point {
2430 point_add(rotate_point(point_sub(point, center), rotation), center)
2431}
2432
2433fn point_add(first: Point, second: Point) -> Point {
2434 Point::new(first.x + second.x, first.y + second.y)
2435}
2436
2437fn point_sub(first: Point, second: Point) -> Point {
2438 Point::new(first.x - second.x, first.y - second.y)
2439}
2440
2441fn point_scale(point: Point, scale: f32) -> Point {
2442 Point::new(point.x * scale, point.y * scale)
2443}
2444
2445fn point_lerp(first: Point, second: Point, progress: f32) -> Point {
2446 Point::new(
2447 lerp(first.x, second.x, progress),
2448 lerp(first.y, second.y, progress),
2449 )
2450}
2451
2452fn point_distance(point: Point) -> f32 {
2453 distance_components(point.x, point.y)
2454}
2455
2456fn point_direction(point: Point) -> Point {
2457 let distance = point_distance(point);
2458
2459 assert!(distance > 0.0);
2460 point_scale(point, 1.0 / distance)
2461}
2462
2463fn point_dot(first: Point, second: Point) -> f32 {
2464 first.x * second.x + first.y * second.y
2465}
2466
2467fn point_clockwise(first: Point, second: Point) -> bool {
2468 first.x * second.y - first.y * second.x > 0.0
2469}
2470
2471fn rotate90(point: Point) -> Point {
2472 Point::new(-point.y, point.x)
2473}
2474
2475fn direction_vector(x: f32, y: f32) -> Point {
2476 let distance = distance_components(x, y);
2477
2478 assert!(distance > 0.0);
2479 Point::new(x / distance, y / distance)
2480}
2481
2482fn direction_vector_from_angle(angle: f32) -> Point {
2483 Point::new(angle.cos(), angle.sin())
2484}
2485
2486fn distance_components(x: f32, y: f32) -> f32 {
2487 (x * x + y * y).sqrt()
2488}
2489
2490fn distance_squared(x: f32, y: f32) -> f32 {
2491 x * x + y * y
2492}
2493
2494fn distance_squared_point(point: Point) -> f32 {
2495 distance_squared(point.x, point.y)
2496}
2497
2498fn square(value: f32) -> f32 {
2499 value * value
2500}
2501
2502fn lerp(start: f32, end: f32, progress: f32) -> f32 {
2503 (1.0 - progress) * start + progress * end
2504}
2505
2506fn positive_modulo(value: f32, modulus: f32) -> f32 {
2507 (value % modulus + modulus) % modulus
2508}
2509
2510const DISTANCE_EPSILON: f32 = 1e-4;
2511const ANGLE_EPSILON: f32 = 1e-6;
2512
2513#[cfg(test)]
2514#[path = "../../../tests/widget/component/progress_bar.rs"]
2515mod tests;