1use iced_widget::canvas::{self, Canvas, LineCap, LineJoin, Path, Stroke};
4use iced_widget::core::time::{Duration, Instant};
5use iced_widget::core::{Color, Length, Point, Rectangle, mouse};
6use std::f32::consts::{FRAC_PI_2, FRAC_PI_4, TAU};
7
8use crate::{Theme, tokens};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11enum LinearMode {
12 Determinate,
13 Indeterminate,
14}
15
16#[derive(Debug, Clone)]
18pub struct IndeterminateState {
19 started_at: Instant,
20 elapsed: Duration,
21}
22
23impl IndeterminateState {
24 pub fn new(started_at: Instant) -> Self {
26 Self {
27 started_at,
28 elapsed: Duration::ZERO,
29 }
30 }
31
32 pub fn advance(&mut self, now: Instant) {
34 self.elapsed = now.saturating_duration_since(self.started_at);
35 }
36
37 pub fn linear_phase(&self) -> f32 {
39 elapsed_phase(
40 self.elapsed,
41 tokens::component::linear_progress::INDETERMINATE_DURATION_MS,
42 )
43 }
44
45 pub fn color_phase(&self) -> f32 {
47 elapsed_phase(
48 self.elapsed,
49 tokens::component::linear_progress::INDETERMINATE_DURATION_MS * 2,
50 )
51 }
52
53 pub fn loading_phase(&self) -> f32 {
55 elapsed_phase(
56 self.elapsed,
57 tokens::component::loading_indicator::GLOBAL_ROTATION_DURATION_MS,
58 )
59 }
60
61 pub const fn is_animating(&self) -> bool {
63 true
64 }
65}
66
67impl Default for IndeterminateState {
68 fn default() -> Self {
69 Self::new(Instant::now())
70 }
71}
72
73#[derive(Debug, Clone, Copy)]
74pub struct LinearProgress {
75 mode: LinearMode,
76 progress: f32,
77 phase: f32,
78 color_phase: f32,
79 four_color: bool,
80}
81
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
110pub 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
241pub 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;