Skip to main content

material_ui_rs/widget/component/
progress_bar.rs

1//! Canvas-based Material 3 progress and loading indicators.
2
3use 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/// A clock-backed state for indeterminate canvas indicators.
17#[derive(Debug, Clone)]
18pub struct IndeterminateState {
19    started_at: Instant,
20    elapsed: Duration,
21}
22
23impl IndeterminateState {
24    /// Creates a new indeterminate animation state.
25    pub fn new(started_at: Instant) -> Self {
26        Self {
27            started_at,
28            elapsed: Duration::ZERO,
29        }
30    }
31
32    /// Advances the state to `now`.
33    pub fn advance(&mut self, now: Instant) {
34        self.elapsed = now.saturating_duration_since(self.started_at);
35    }
36
37    /// Returns the current phase for Material linear progress keyframes.
38    pub fn linear_phase(&self) -> f32 {
39        elapsed_phase(
40            self.elapsed,
41            tokens::component::linear_progress::INDETERMINATE_DURATION_MS,
42        )
43    }
44
45    /// Returns the slower phase used by the four-color linear progress cycle.
46    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    /// Returns the current phase for expressive loading indicator rotation.
54    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    /// Indeterminate indicators animate for as long as they are displayed.
62    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
82/// Creates an expressive determinate linear progress indicator.
83pub 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
93/// Creates an expressive determinate linear progress indicator.
94pub 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
114/// Creates a Material indeterminate linear progress indicator.
115pub 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
135/// Creates a Material indeterminate linear progress indicator with explicit color phase.
136pub 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
225/// Creates an expressive indeterminate loading indicator.
226pub 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
245/// Creates an expressive contained indeterminate loading indicator.
246pub 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
265/// Creates an expressive determinate loading indicator.
266pub 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
285/// Creates an expressive contained determinate loading indicator.
286pub 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;