1use std::any::Any;
43use std::cell::OnceCell;
44use std::rc::Rc;
45
46use frus_core::{Curve, Rect, Scene};
47use frus_layout::Style;
48
49use crate::interaction::{Status, WidgetId};
50use crate::runtime::Runtime;
51use crate::theme::Theme;
52use crate::widget::Widget;
53
54type Build<Msg> = Box<dyn Fn(&dyn Any) -> Option<Box<dyn Widget<Msg>>>>;
57type Transition<Msg> = Box<dyn Fn(Box<dyn Widget<Msg>>, f32) -> Box<dyn Widget<Msg>>>;
59type Arrange<Msg> = Box<dyn Fn(Vec<Box<dyn Widget<Msg>>>) -> Box<dyn Widget<Msg>>>;
61
62pub struct AnimatedSwitcher<Msg = crate::callback::Callback> {
89 value: Rc<dyn Any>,
90 same: fn(&dyn Any, &dyn Any) -> bool,
91 build: Build<Msg>,
92 timing: Timing,
93 transition: Transition<Msg>,
94 arrange: Arrange<Msg>,
95 built: OnceCell<Vec<Box<dyn Widget<Msg>>>>,
98}
99
100fn same<T: PartialEq + 'static>(a: &dyn Any, b: &dyn Any) -> bool {
102 match (a.downcast_ref::<T>(), b.downcast_ref::<T>()) {
103 (Some(a), Some(b)) => a == b,
104 _ => false,
105 }
106}
107
108impl<Msg: Clone + 'static> AnimatedSwitcher<Msg> {
109 pub fn new<T, W>(duration: f32, value: T, build: impl Fn(&T) -> W + 'static) -> Self
112 where
113 T: PartialEq + 'static,
114 W: Widget<Msg> + 'static,
115 {
116 Self {
117 value: Rc::new(value),
118 same: same::<T>,
119 build: Box::new(move |kept: &dyn Any| {
120 kept.downcast_ref::<T>()
121 .map(|value| Box::new(build(value)) as Box<dyn Widget<Msg>>)
122 }),
123 timing: Timing {
124 duration,
125 reverse: duration,
126 switch_in: Curve::Linear,
127 switch_out: Curve::Linear,
128 },
129 transition: Box::new(|child, progress| {
130 Box::new(crate::transitions::FadeTransition::new(progress, child))
131 }),
132 arrange: Box::new(|layers| Box::new(Overlap { layers })),
133 built: OnceCell::new(),
134 }
135 }
136
137 pub fn reverse_duration(mut self, seconds: f32) -> Self {
139 self.timing.reverse = seconds;
140 self
141 }
142
143 pub fn switch_in_curve(mut self, curve: Curve) -> Self {
145 self.timing.switch_in = curve;
146 self
147 }
148
149 pub fn switch_out_curve(mut self, curve: Curve) -> Self {
152 self.timing.switch_out = curve;
153 self
154 }
155
156 pub fn transition<W: Widget<Msg> + 'static>(
163 mut self,
164 transition: impl Fn(Box<dyn Widget<Msg>>, f32) -> W + 'static,
165 ) -> Self {
166 self.transition = Box::new(move |child, progress| Box::new(transition(child, progress)));
167 self
168 }
169
170 pub fn layout<W: Widget<Msg> + 'static>(
178 mut self,
179 layout: impl Fn(Vec<Box<dyn Widget<Msg>>>) -> W + 'static,
180 ) -> Self {
181 self.arrange = Box::new(move |layers| Box::new(layout(layers)));
182 self
183 }
184
185 fn compose(&self, at: Option<(WidgetId, &Runtime)>) -> Vec<Box<dyn Widget<Msg>>> {
189 let shown = match at {
190 Some((id, runtime)) => {
191 runtime.switch_to(id, self.value.clone(), self.same, self.timing.clone())
192 }
193 None => vec![Shown {
194 serial: 0,
195 value: self.value.clone(),
196 progress: 1.0,
197 leaving: false,
198 }],
199 };
200 let layers = shown
201 .into_iter()
202 .filter_map(|shown| {
203 let child = (self.build)(shown.value.as_ref())?;
204 let drawn = (self.transition)(child, shown.progress);
205 let layer = crate::Keyed::new(
209 shown.serial,
210 crate::barrier::ExcludeSemantics::new(
211 crate::barrier::IgnorePointer::new(drawn).ignoring(shown.leaving),
212 )
213 .excluding(shown.leaving),
214 );
215 Some(Box::new(layer) as Box<dyn Widget<Msg>>)
216 })
217 .collect();
218 vec![(self.arrange)(layers)]
219 }
220}
221
222impl<Msg: Clone + 'static> Widget<Msg> for AnimatedSwitcher<Msg> {
223 fn build_in(&self, id: WidgetId, runtime: &Runtime, _theme: &Theme) {
224 self.built.get_or_init(|| self.compose(Some((id, runtime))));
225 }
226
227 fn switches(&self) -> bool {
228 true
229 }
230
231 fn style(&self) -> Style {
232 Style::default()
234 }
235
236 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
237 self.built.get_or_init(|| self.compose(None))
238 }
239
240 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
241
242 fn on_click(&self) -> Option<Msg> {
243 None
244 }
245
246 fn debug_name(&self) -> &'static str {
247 "AnimatedSwitcher"
248 }
249}
250
251struct Overlap<Msg> {
255 layers: Vec<Box<dyn Widget<Msg>>>,
256}
257
258impl<Msg: Clone> Widget<Msg> for Overlap<Msg> {
259 fn style(&self) -> Style {
260 Style {
261 overlap: true,
262 align: frus_layout::Align::Center,
263 ..Style::default()
264 }
265 }
266
267 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
268 &self.layers
269 }
270
271 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
272
273 fn on_click(&self) -> Option<Msg> {
274 None
275 }
276
277 fn debug_name(&self) -> &'static str {
278 "AnimatedSwitcher"
279 }
280}
281
282#[derive(Clone, Debug)]
284pub(crate) struct Timing {
285 duration: f32,
286 reverse: f32,
287 switch_in: Curve,
288 switch_out: Curve,
289}
290
291pub(crate) struct Shown {
293 serial: u64,
294 value: Rc<dyn Any>,
295 progress: f32,
297 leaving: bool,
298}
299
300struct Entry {
302 serial: u64,
304 value: Rc<dyn Any>,
305 t: f32,
309 leaving: bool,
310}
311
312pub(crate) struct Switch {
315 entries: Vec<Entry>,
316 next: u64,
317 timing: Timing,
318 pub(crate) moved: bool,
321}
322
323impl Switch {
324 fn mounted(value: Rc<dyn Any>, timing: Timing) -> Self {
326 Self {
327 entries: vec![Entry {
328 serial: 0,
329 value,
330 t: 1.0,
331 leaving: false,
332 }],
333 next: 1,
334 timing,
335 moved: false,
336 }
337 }
338
339 pub(crate) fn meet(
341 &mut self,
342 value: Rc<dyn Any>,
343 same: fn(&dyn Any, &dyn Any) -> bool,
344 timing: Timing,
345 still: bool,
346 ) -> Vec<Shown> {
347 self.timing = timing;
348 let showing = self
349 .entries
350 .last()
351 .is_some_and(|e| !e.leaving && same(e.value.as_ref(), value.as_ref()));
352 if !showing {
353 self.arrive(value, still);
354 }
355 self.moved = false;
356 self.entries
357 .iter()
358 .map(|e| Shown {
359 serial: e.serial,
360 value: e.value.clone(),
361 progress: if e.leaving {
362 self.timing.switch_out.transform(e.t)
363 } else {
364 self.timing.switch_in.transform(e.t)
365 },
366 leaving: e.leaving,
367 })
368 .collect()
369 }
370
371 fn arrive(&mut self, value: Rc<dyn Any>, still: bool) {
373 for entry in &mut self.entries {
374 entry.leaving = true;
375 }
376 if still || self.timing.reverse <= 0.0 {
379 self.entries.clear();
380 }
381 let t = if still || self.timing.duration <= 0.0 {
382 1.0
383 } else {
384 0.0
385 };
386 self.entries.push(Entry {
387 serial: self.next,
388 value,
389 t,
390 leaving: false,
391 });
392 self.next += 1;
393 }
394
395 pub(crate) fn advance(&mut self, dt: f32, still: bool) -> bool {
398 let step = |duration: f32| {
399 if still || duration <= 0.0 {
400 f32::INFINITY
401 } else {
402 dt / duration
403 }
404 };
405 let (arrive, leave) = (step(self.timing.duration), step(self.timing.reverse));
406 let mut moved = false;
407 for entry in &mut self.entries {
408 let before = entry.t;
409 entry.t = if entry.leaving {
410 (entry.t - leave).max(0.0)
411 } else {
412 (entry.t + arrive).min(1.0)
413 };
414 if dt > 0.0 && entry.t != before {
417 moved = true;
418 } else {
419 entry.t = before;
420 }
421 }
422 let count = self.entries.len();
423 self.entries.retain(|e| !e.leaving || e.t > 0.0);
424 moved |= self.entries.len() != count;
425 self.moved |= moved;
426 moved
427 }
428}
429
430impl Runtime {
431 pub(crate) fn switch_to(
433 &self,
434 id: WidgetId,
435 value: Rc<dyn Any>,
436 same: fn(&dyn Any, &dyn Any) -> bool,
437 timing: Timing,
438 ) -> Vec<Shown> {
439 let still = self.still;
440 let mut switchers = self.switchers.borrow_mut();
441 switchers
442 .entry(id)
443 .or_insert_with(|| Switch::mounted(value.clone(), timing.clone()))
444 .meet(value, same, timing, still)
445 }
446
447 pub fn advance_switchers<Msg>(&mut self, root: &dyn Widget<Msg>, dt: f32) -> bool {
450 fn find<Msg>(
451 widget: &dyn Widget<Msg>,
452 id: WidgetId,
453 kept: &std::collections::HashMap<WidgetId, Switch>,
454 present: &mut std::collections::HashSet<WidgetId>,
455 ) {
456 if widget.switches() && kept.contains_key(&id) {
459 present.insert(id);
460 }
461 for (index, child) in widget.children().iter().enumerate() {
462 find(
463 child.as_ref(),
464 crate::ui::child_id(id, index, child.as_ref()),
465 kept,
466 present,
467 );
468 }
469 }
470 let still = self.still;
471 let switchers = self.switchers.get_mut();
472 if switchers.is_empty() {
473 return false;
474 }
475 let mut present = std::collections::HashSet::new();
478 find(root, WidgetId::ROOT, switchers, &mut present);
479 switchers.retain(|id, _| present.contains(id));
480 let mut moving = false;
481 for switch in switchers.values_mut() {
482 moving |= switch.advance(dt, still);
483 }
484 moving
485 }
486
487 pub fn switching(&self) -> bool {
491 self.switchers.borrow().values().any(|s| s.moved)
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498 use crate::ui::{build_ui, Ui};
499 use crate::{Container, Flex, Keyed, Text};
500 use frus_core::{Color, Point, Primitive, Size};
501
502 const SIZE: Size = Size::new(200.0, 100.0);
503
504 fn red() -> Color {
505 Color::rgb(1.0, 0.0, 0.0)
506 }
507 fn blue() -> Color {
508 Color::rgb(0.0, 0.0, 1.0)
509 }
510 fn green() -> Color {
511 Color::rgb(0.0, 1.0, 0.0)
512 }
513
514 fn tile(value: u8) -> Container<i32> {
517 let (color, width, word) = match value {
518 1 => (red(), 80.0, "one"),
519 2 => (blue(), 40.0, "two"),
520 _ => (green(), 40.0, "three"),
521 };
522 Container::new()
523 .width(width)
524 .height(20.0)
525 .color(color)
526 .on_click(value as i32)
527 .child(Text::new(word))
528 }
529
530 fn switcher(value: u8) -> AnimatedSwitcher<i32> {
531 AnimatedSwitcher::new(0.2, value, |v: &u8| tile(*v))
532 }
533
534 fn view(value: u8) -> Flex<i32> {
536 Flex::row().child(switcher(value))
537 }
538
539 fn step(rt: &mut Runtime, value: u8, dt: f32) {
541 let tree = view(value);
542 let _ = build_ui(&tree, SIZE, rt, &Theme::dark());
543 rt.advance_switchers(&tree, dt);
544 }
545
546 fn look(rt: &Runtime, value: u8) -> Ui<i32> {
548 build_ui(&view(value), SIZE, rt, &Theme::dark())
549 }
550
551 fn rects(ui: &Ui<i32>) -> Vec<(Rect, Color, f32)> {
553 fn walk(primitives: &[Primitive], opacity: f32, out: &mut Vec<(Rect, Color, f32)>) {
554 for primitive in primitives {
555 match primitive {
556 Primitive::Rect { rect, color, .. } => out.push((*rect, *color, opacity)),
557 Primitive::Layer {
558 primitives,
559 opacity: layer,
560 ..
561 } => walk(primitives, opacity * layer, out),
562 _ => {}
563 }
564 }
565 }
566 let mut out = Vec::new();
567 walk(ui.scene().primitives(), 1.0, &mut out);
568 out
569 }
570
571 fn shown(ui: &Ui<i32>, color: Color) -> f32 {
573 rects(ui)
574 .iter()
575 .filter(|(_, c, _)| *c == color)
576 .map(|(_, _, opacity)| *opacity * color_alpha(color))
577 .fold(0.0, f32::max)
578 }
579
580 fn color_alpha(color: Color) -> f32 {
581 color.a
582 }
583
584 fn close(a: f32, b: f32) -> bool {
585 (a - b).abs() < 1e-3
586 }
587
588 #[test]
591 fn the_first_value_is_shown_as_it_is() {
592 let mut rt = Runtime::default();
593 step(&mut rt, 1, 0.0);
594 let ui = look(&rt, 1);
595 assert_eq!(shown(&ui, red()), 1.0);
596 assert_eq!(shown(&ui, blue()), 0.0);
597 }
598
599 #[test]
602 fn a_new_value_crosses_over_and_the_old_one_is_let_go() {
603 let mut rt = Runtime::default();
604 step(&mut rt, 1, 0.0);
605
606 let ui = look(&rt, 2);
607 assert_eq!(shown(&ui, red()), 1.0, "the old child, still whole");
608 assert_eq!(shown(&ui, blue()), 0.0, "the new one, not yet");
609
610 step(&mut rt, 2, 0.05);
611 let ui = look(&rt, 2);
612 assert!(close(shown(&ui, red()), 0.75), "{}", shown(&ui, red()));
613 assert!(close(shown(&ui, blue()), 0.25), "{}", shown(&ui, blue()));
614
615 step(&mut rt, 2, 0.05);
616 let ui = look(&rt, 2);
617 assert!(close(shown(&ui, red()), 0.5));
618 assert!(close(shown(&ui, blue()), 0.5));
619
620 step(&mut rt, 2, 0.1);
621 let ui = look(&rt, 2);
622 assert_eq!(shown(&ui, red()), 0.0, "gone from the tree");
623 assert_eq!(shown(&ui, blue()), 1.0);
624 }
625
626 #[test]
629 fn each_direction_moves_on_its_own_curve() {
630 let curved = |value: u8| {
631 Flex::<i32>::row().child(
632 AnimatedSwitcher::new(0.2, value, |v: &u8| tile(*v))
633 .switch_in_curve(Curve::Decelerate)
634 .switch_out_curve(Curve::Linear),
635 )
636 };
637 let mut rt = Runtime::default();
638 let tree = curved(1);
639 let _ = build_ui(&tree, SIZE, &rt, &Theme::dark());
640 rt.advance_switchers(&tree, 0.0);
641 let tree = curved(2);
642 let _ = build_ui(&tree, SIZE, &rt, &Theme::dark());
643 rt.advance_switchers(&tree, 0.1);
644 let ui = build_ui(&curved(2), SIZE, &rt, &Theme::dark());
645 assert!(close(shown(&ui, blue()), 0.75), "{}", shown(&ui, blue()));
647 assert!(close(shown(&ui, red()), 0.5), "{}", shown(&ui, red()));
648 }
649
650 #[test]
654 fn a_child_cut_off_halfway_leaves_from_halfway() {
655 let mut rt = Runtime::default();
656 step(&mut rt, 1, 0.0);
657 step(&mut rt, 2, 0.1); step(&mut rt, 3, 0.0); let ui = look(&rt, 3);
660 assert!(close(shown(&ui, blue()), 0.5), "{}", shown(&ui, blue()));
661 assert_eq!(shown(&ui, green()), 0.0, "the newest, not yet in");
662 assert!(
663 close(shown(&ui, red()), 0.5),
664 "the first, still on its way out"
665 );
666 }
667
668 #[test]
671 fn an_equal_value_is_not_a_switch() {
672 let mut rt = Runtime::default();
673 step(&mut rt, 1, 0.0);
674 step(&mut rt, 1, 0.1);
675 assert!(!rt.switching());
676 assert_eq!(shown(&look(&rt, 1), red()), 1.0);
677 }
678
679 #[test]
682 fn a_switch_in_flight_asks_for_the_tree_to_be_built_and_then_stops() {
683 let mut rt = Runtime::default();
684 step(&mut rt, 1, 0.0);
685 assert!(!rt.switching(), "at rest");
686 step(&mut rt, 2, 0.05);
687 assert!(rt.switching(), "moved since it was built");
688 let _ = look(&rt, 2);
689 assert!(!rt.switching(), "and built since");
690 step(&mut rt, 2, 1.0);
691 assert!(rt.switching(), "the step that ends it is a move too");
692 step(&mut rt, 2, 0.05);
693 assert!(!rt.switching(), "settled: painted only from here on");
694 }
695
696 #[test]
699 fn with_motion_turned_down_it_swaps_at_once() {
700 let mut rt = Runtime::default();
701 rt.still = true;
702 step(&mut rt, 1, 0.0);
703 let ui = look(&rt, 2);
704 assert_eq!(shown(&ui, blue()), 1.0);
705 assert_eq!(shown(&ui, red()), 0.0);
706 }
707
708 #[test]
712 fn the_leaving_child_takes_no_input_and_is_not_announced() {
713 let mut rt = Runtime::default();
714 step(&mut rt, 1, 0.0);
715 let before = look(&rt, 1);
716 let only_red = Point::new(5.0, 10.0);
717 let hit = before.hit(only_red).and_then(|id| before.msg_for(id));
718 assert_eq!(
719 hit,
720 Some(1),
721 "the fixture: the red tile answers there at rest"
722 );
723
724 step(&mut rt, 2, 0.1);
725 let ui = look(&rt, 2);
726 assert!(shown(&ui, red()) > 0.0, "the red is still on the screen");
727 assert_eq!(ui.hit(only_red).and_then(|id| ui.msg_for(id)), None);
728 let words: Vec<_> = ui
729 .semantics()
730 .iter()
731 .filter_map(|(_, _, s)| s.label.clone())
732 .collect();
733 assert!(words.iter().any(|w| w.contains("two")), "{words:?}");
734 assert!(!words.iter().any(|w| w.contains("one")), "{words:?}");
735 }
736
737 #[test]
740 fn the_box_is_the_largest_child_while_both_show() {
741 let mut rt = Runtime::default();
742 step(&mut rt, 1, 0.0);
743 step(&mut rt, 2, 0.1);
744 let x_of = |ui: &Ui<i32>, color: Color| {
745 rects(ui)
746 .iter()
747 .find(|(_, c, _)| *c == color)
748 .map(|(r, _, _)| r.x)
749 .unwrap()
750 };
751 let ui = look(&rt, 2);
752 assert_eq!(x_of(&ui, red()), 0.0);
753 assert_eq!(
754 x_of(&ui, blue()),
755 20.0,
756 "the narrow one centred in the wide one's box"
757 );
758
759 step(&mut rt, 2, 1.0);
760 assert_eq!(
761 x_of(&look(&rt, 2), blue()),
762 0.0,
763 "and the box is its own again"
764 );
765 }
766
767 #[test]
772 fn a_switcher_behind_a_key_still_switches() {
773 let keyed = |value: u8| Flex::<i32>::row().child(Keyed::new(7u8, switcher(value)));
774 let mut rt = Runtime::default();
775 for (value, dt) in [(1, 0.0), (2, 0.1)] {
776 let tree = keyed(value);
777 let _ = build_ui(&tree, SIZE, &rt, &Theme::dark());
778 rt.advance_switchers(&tree, dt);
779 }
780 let ui = build_ui(&keyed(2), SIZE, &rt, &Theme::dark());
781 assert!(close(shown(&ui, red()), 0.5), "{}", shown(&ui, red()));
782 assert!(close(shown(&ui, blue()), 0.5), "{}", shown(&ui, blue()));
783 }
784
785 #[test]
788 fn a_switcher_that_goes_is_forgotten() {
789 let mut rt = Runtime::default();
790 step(&mut rt, 1, 0.0);
791 let without = Flex::<i32>::row().child(Container::new().width(10.0).height(10.0));
792 let _ = build_ui(&without, SIZE, &rt, &Theme::dark());
793 rt.advance_switchers(&without, 0.016);
794 assert!(rt.switchers.borrow().is_empty(), "forgotten");
795
796 step(&mut rt, 2, 0.0);
797 let ui = look(&rt, 2);
798 assert_eq!(shown(&ui, blue()), 1.0);
799 assert_eq!(shown(&ui, red()), 0.0);
800 }
801}