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