Skip to main content

frus_widgets/
switcher.rs

1//! [`AnimatedSwitcher`]: moves between **two different children**, rather than between
2//! two values of one.
3//!
4//! Every other implicit animation here moves a number — an opacity, a size, a pin — and
5//! the widget carrying it stays the same widget throughout. A switcher is asked for
6//! something else: the count was 3 and is now 4, and for a moment **both** are on the
7//! screen, the 3 fading out while the 4 fades in. So something has to keep the 3.
8//!
9//! ## What is kept is the value, not the widget
10//!
11//! In the reference the switcher holds on to the old child *widget*, which works because
12//! that widget outlives the frame it was made in. Here nothing does: the view is a pure
13//! function of the application's state, the tree is rebuilt from it, and a boxed widget
14//! can be neither kept nor cloned. What the application gave to *make* the child, though,
15//! is plain data. So a switcher is handed a **value and a way to build a child from one**:
16//!
17//! ```ignore
18//! AnimatedSwitcher::new(0.25, app.count, |n| text(n.to_string()))
19//! ```
20//!
21//! and it is the values that the runtime keeps, under the switcher's identity. The child
22//! on its way out is **built again from its value every frame** it is on the screen — it
23//! is a real subtree, laid out, painted and animating in its own right, not a picture of
24//! what it was. A value changing is what a switch *is*: equal values are the same child,
25//! whatever else changed around them, which is the reference's key.
26//!
27//! ## Where it happens, and what it costs
28//!
29//! A new value can only be noticed while the tree is being **built** — that is the only
30//! moment both it and the one before it are at hand — so the switcher does its work in
31//! [`Widget::build_in`], which is handed its identity and the runtime. What it builds
32//! carries the progress of each child as a plain number, through a **transition** of the
33//! caller's choosing — a fade by default, any of the explicit transitions otherwise — so
34//! while a switch is in flight the tree has to be built again each frame rather than only
35//! painted. That is what a route transition already costs here, for the same reason; it
36//! stops the frame the switch settles.
37//!
38//! The children on their way out take **no input** and are **not announced**: a tap on a
39//! number that is fading away would be a tap on a state the application has already left,
40//! and a screen reader reading it would be reading that state out.
41
42use 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
54/// Builds a child from a value the switcher has kept, or `None` if the value is not the
55/// kind this switcher builds from (a different switcher came to stand in the same place).
56type Build<Msg> = Box<dyn Fn(&dyn Any) -> Option<Box<dyn Widget<Msg>>>>;
57/// Wraps a child in its progress: `1` in place, `0` gone.
58type Transition<Msg> = Box<dyn Fn(Box<dyn Widget<Msg>>, f32) -> Box<dyn Widget<Msg>>>;
59/// Arranges the children, the oldest first and the one arriving last.
60type Arrange<Msg> = Box<dyn Fn(Vec<Box<dyn Widget<Msg>>>) -> Box<dyn Widget<Msg>>>;
61
62/// **Moves between two different children** when the value it shows changes: the old one
63/// leaves as the new one arrives — a fade by default.
64///
65/// ```
66/// use frus_widgets::{AnimatedSwitcher, Text};
67///
68/// let count = 3;
69/// let _shown: AnimatedSwitcher<()> =
70///     AnimatedSwitcher::new(0.25, count, |n: &i32| Text::new(n.to_string()));
71/// ```
72///
73/// It is handed the **value** a child is made from rather than the child, because a value
74/// is what can be kept from one frame to the next — see the module documentation. Two
75/// values that compare equal are the same child: nothing moves. A value that differs is a
76/// switch, and the child that was showing leaves from wherever it had got to.
77///
78/// - [`AnimatedSwitcher::transition`] says **how** a child arrives and leaves. It is
79///   handed the child and its progress, and any explicit transition fits:
80///   `.transition(|child, t| ScaleTransition::new(t, child))`.
81/// - [`AnimatedSwitcher::layout`] says **where** the children go while two are showing.
82///   By default they overlap, centred, in a box as big as the largest of them.
83/// - The first value is shown as it is, with no transition: an implicit animation adopts
84///   what it is given on the frame it appears, here as everywhere.
85///
86/// An absent child is a value like any other: switch over an `Option` and build an empty
87/// box for `None`, and a child fades out to nothing.
88pub 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    /// The arrangement, as the one-element slice [`Widget::children`] hands back. Composed
96    /// once per tree, by the first walk that reaches this node.
97    built: OnceCell<Vec<Box<dyn Widget<Msg>>>>,
98}
99
100/// Whether two kept values are the same child. Values of two different kinds never are.
101fn 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    /// Shows the child `build` makes from `value`, and moves over `duration` seconds to
110    /// the child of each new value.
111    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    /// How long a child takes to **leave**, when it is not as long as one takes to arrive.
138    pub fn reverse_duration(mut self, seconds: f32) -> Self {
139        self.timing.reverse = seconds;
140        self
141    }
142
143    /// The curve a child **arrives** on. Linear by default, as in the reference.
144    pub fn switch_in_curve(mut self, curve: Curve) -> Self {
145        self.timing.switch_in = curve;
146        self
147    }
148
149    /// The curve a child **leaves** on, read as its progress falls from where it was to
150    /// nothing. Linear by default.
151    pub fn switch_out_curve(mut self, curve: Curve) -> Self {
152        self.timing.switch_out = curve;
153        self
154    }
155
156    /// **How** a child arrives and leaves: handed the child and its progress — `1` in
157    /// place, `0` gone — it returns the child as it should be drawn at that point. A fade
158    /// by default.
159    ///
160    /// Both directions go through it, the leaving child with its progress falling, so a
161    /// scale makes the old child shrink away as the new one grows.
162    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    /// **Where** the children go while more than one is showing: handed them oldest first,
171    /// the arriving one last, it returns what holds them. By default they overlap, centred
172    /// in a box as big as the largest.
173    ///
174    /// Each child arrives already keyed and wrapped in its transition, so the arrangement
175    /// only has to place them — in the order given, if the arriving one is to be drawn on
176    /// top.
177    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    /// The children as they stand: from the runtime when a walk hands one over, and at rest
186    /// otherwise — a bare traversal that reaches this node before any walk has is shown the
187    /// value it was given, which is also what the first frame shows.
188    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                // The same wrappers whether it is arriving or leaving, so that the subtree
206                // keeps its identity — and whatever it retains — across the switch. A child
207                // leaving is no longer the answer, to a finger or to a screen reader.
208                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        // Nothing of its own: the arrangement's box is this node's box.
233        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
251/// The default arrangement: every child in one cell, centred, the box as big as the
252/// largest — the reference's centred stack. A [`crate::Stack`] cannot be it here: its
253/// layers are laid out apart from it, so a stack with no size of its own is nothing.
254struct 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/// How long and on what curves a switcher's children move.
283#[derive(Clone, Debug)]
284pub(crate) struct Timing {
285    duration: f32,
286    reverse: f32,
287    switch_in: Curve,
288    switch_out: Curve,
289}
290
291/// One child as the tree is to be built with it.
292pub(crate) struct Shown {
293    serial: u64,
294    value: Rc<dyn Any>,
295    /// Through the curve: what the transition is handed.
296    progress: f32,
297    leaving: bool,
298}
299
300/// One child the runtime is keeping for a switcher.
301struct Entry {
302    /// Its identity among the switcher's children, for as long as it is on the screen.
303    serial: u64,
304    value: Rc<dyn Any>,
305    /// Its own clock, linear: `0` gone, `1` in place. It runs up while the child arrives
306    /// and down while it leaves, **from wherever it had got to** — a child cut off halfway
307    /// in leaves from halfway, rather than jumping to full and fading from there.
308    t: f32,
309    leaving: bool,
310}
311
312/// What the runtime keeps for one switcher: the children on the screen, oldest first.
313/// Only the last can be arriving; every one before it is leaving.
314pub(crate) struct Switch {
315    entries: Vec<Entry>,
316    next: u64,
317    timing: Timing,
318    /// Moved since a tree was last built from it. What moved is baked into the children as
319    /// numbers, so the frame has to be **built** again, not only painted.
320    pub(crate) moved: bool,
321}
322
323impl Switch {
324    /// A switcher seen for the first time: its value in place, with no transition.
325    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    /// Takes in the value a tree was built with, and says what to show.
340    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    /// A new child: everything showing starts to leave, and it starts to arrive.
372    fn arrive(&mut self, value: Rc<dyn Any>, still: bool) {
373        for entry in &mut self.entries {
374            entry.leaving = true;
375        }
376        // With the motion turned down, or no time to do it in, a child is simply gone —
377        // and the new one simply there.
378        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    /// Steps every child's clock by `dt`, and lets go of the ones that have left. Returns
396    /// whether anything moved.
397    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            // `dt` of nought moves nothing, however short the duration: it is the first
415            // frame, not a step.
416            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    /// Hands a switcher what to show, noticing a new value — see [`Switch::meet`].
432    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    /// Steps every [`AnimatedSwitcher`]'s children in and out, and forgets the switchers no
448    /// longer in the tree. Returns whether a child is still moving.
449    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            // Being at the identity is not enough: an identity is a position, and any
457            // widget that comes to stand where the switcher stood has the same one.
458            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        // A switcher that has gone is forgotten, so that one appearing in its place later
476        // is a mount — shown as it is — rather than a switch from what used to be there.
477        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    /// Whether a switcher has moved since the tree was last built, so that the next frame
488    /// has to **build** the tree again rather than only paint it: what moved is carried by
489    /// the children as numbers.
490    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    /// Value 1 is a wide red tile, 2 a narrow blue one, 3 a green one — each clickable
515    /// with its own number and labelled with its own word.
516    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    /// In a row, so the switcher's box is what its children make it.
535    fn view(value: u8) -> Flex<i32> {
536        Flex::row().child(switcher(value))
537    }
538
539    /// One turn of the shell's loop: the tree built with `value`, then the clocks stepped.
540    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    /// What the next frame draws.
547    fn look(rt: &Runtime, value: u8) -> Ui<i32> {
548        build_ui(&view(value), SIZE, rt, &Theme::dark())
549    }
550
551    /// Every filled rectangle, with the opacity it is drawn at through the layers above it.
552    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    /// How visible the tile of `color` is: `0` when it is not drawn at all.
572    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    /// **The first value is shown as it is**: no fade in from nothing on the frame the
589    /// switcher appears, the rule every implicit animation here keeps.
590    #[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    /// **A new value crosses over**: at the change both are there, the old whole and the
600    /// new not yet; halfway each is half; at the end only the new one is left in the tree.
601    #[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    /// **The curves are the caller's**, one each way: the arriving child on the in-curve,
627    /// the leaving one on the out-curve, read as its progress falls.
628    #[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        // Halfway: decelerating in is three quarters there, linear out is half gone.
646        assert!(close(shown(&ui, blue()), 0.75), "{}", shown(&ui, blue()));
647        assert!(close(shown(&ui, red()), 0.5), "{}", shown(&ui, red()));
648    }
649
650    /// **A child cut off on its way in leaves from where it had got to**, not from whole:
651    /// the new value arrives while the last is still coming in, and the one that was
652    /// arriving turns round at half rather than jumping to full and fading from there.
653    #[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); // blue half in
658        step(&mut rt, 3, 0.0); // and now it leaves
659        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    /// **Equal values are the same child**: nothing moves, and nothing asks for the tree to
669    /// be rebuilt.
670    #[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    /// **While a switch moves, the frame has to be built, not only painted** — the progress
680    /// is a number built into the children — and once it settles, it no longer does.
681    #[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    /// **With motion turned down, it simply swaps**: the new child whole, the old one gone,
697    /// on the frame the value changes.
698    #[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    /// **The leaving child takes no input and is not announced.** The point is inside the
709    /// wide red tile and outside the narrow blue one, so nothing but the red could answer
710    /// it — and the red is a state the application has left.
711    #[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    /// **While both show, the box is the larger of the two**, each centred in it; once the
738    /// old one has gone, the box is the new one's.
739    #[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    /// **Behind a key, it still switches.** A key is a transparent wrapper: the walk hands
768    /// it the identity it shares with the switcher, and it has to pass that on. Built
769    /// through the older hook instead, the switcher never learns who it is and just shows
770    /// the newest value whole.
771    #[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    /// **A switcher that goes is forgotten**, so one that appears in its place later is a
786    /// mount — its value shown as it is — and not a switch from what used to be there.
787    #[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}