Skip to main content

frust_widgets/
either.rs

1//! Statically-typed branching views: the two-arm [`Either`] with its lazy
2//! [`either`] helper, and the three-to-eight-arm [`EitherOf3`] ..
3//! [`EitherOf8`].
4//!
5//! A helper whose body is an `if`/`else` (or a `match`) over several different
6//! view types can return `impl View<State>` through these enums instead of
7//! erasing the arms into an [`AnyView`](frust_core::AnyView). Every arm stays
8//! statically typed; only the arm that is live is built.
9//!
10//! # When to use it
11//!
12//! * **Two arms** of different concrete types: [`either`] / [`Either`].
13//! * **Three to eight arms** of different concrete types: [`EitherOf3`] ..
14//!   [`EitherOf8`] (variants `A`, `B`, `C`, ..) built from a `match`.
15//! * **More than eight arms, or a stored page table**: stay with
16//!   [`any`](frust_core::any) / [`AnyView`](frust_core::AnyView).
17//! * **The same concrete type in every arm**: build that one type
18//!   conditionally instead. `Either` / `EitherOfN` remount on an arm swap, so
19//!   they would discard state a single conditionally-configured view keeps.
20//! * **An optional child** inside a container: use the container's `.when(..)` /
21//!   `.when_some(..)` sugar rather than an `Either` with an empty arm.
22//!
23//! # Swap semantics
24//!
25//! Switching arms tears the old arm's element down through the old arm's view,
26//! builds the new arm's element from scratch and reports
27//! `LAYOUT | PAINT`: the previous arm's retained state is dropped, exactly as
28//! an `AnyView` concrete-type swap does. Rebuilding the *same* arm reconciles in
29//! place and preserves its state.
30//!
31//! # Focus and capture bookkeeping
32//!
33//! [`EitherWidget`] (and every `EitherOfNWidget`) is an enum widget delegating
34//! every pass to the live arm; it holds no `AnyView` or `Box<dyn Widget>`
35//! field, so it is not the swap-blind wrapper shape
36//! (`focus-wrapper-erasure-swap-blind`). Every arm shares the single element
37//! type (`EitherWidget<L, R>`, `EitherOf3Widget<A, B, C>`, ..), so a parent
38//! reconciler comparing element type ids across the rebuild cannot see an arm
39//! swap by itself. Each enum therefore **reports** the swap
40//! ([`BuildCtx::mark_element_replaced`]), and the enclosing reconciler (the
41//! `ChildPod` that holds it, through `rebuild_child` / `rebuild_children`, or a
42//! component's child pod) drops the pod's capture and focus link exactly as for
43//! an `AnyView` concrete-type swap — and, in a positional list, cancels the
44//! recorded paths of every later sibling. An enum nested inline inside another
45//! enum propagates the report to the outer one's pod; a pod inside the swapped
46//! arm consumes its own.
47//!
48//! The enum also raises the focus orphan itself
49//! ([`mark_focus_orphaned`](frust_core::mark_focus_orphaned)) when the swap
50//! happens while the focus chain runs through it ([`BuildCtx::has_focus`]): a
51//! root-level enum, or one under a container that does not reconcile through a
52//! pod, has no enclosing reconciler to do it. The orphan flag is idempotent, so
53//! the two marks never double-release.
54
55use frust_core::{
56    BoxConstraints, BuildCtx, ChangeFlags, ChildPod, EventCtx, EventResult, InputEvent, LayoutCtx,
57    PaintCtx, PaintScene, SemanticsCtx, SliverWidget, View, Widget, mark_focus_orphaned,
58};
59use kurbo::Size;
60
61/// A view that is one of two statically-typed arms. See the [module docs](self).
62pub enum Either<L, R> {
63    /// The left arm.
64    Left(L),
65    /// The right arm.
66    Right(R),
67}
68
69/// Pick an arm by `cond`: `l` when true, `r` when false. Only the chosen
70/// closure runs, so the unused arm is never constructed.
71///
72/// Use it for exactly two arms of different types; for three to eight arms use
73/// [`EitherOf3`] .. [`EitherOf8`], and for more than eight (or a stored page
74/// table) erase with [`any`](frust_core::any). When every arm is the same
75/// concrete type, build that one type conditionally instead (a swap remounts).
76/// For an optional child use `.when(..)` / `.when_some(..)` on the container.
77pub fn either<L, R>(cond: bool, l: impl FnOnce() -> L, r: impl FnOnce() -> R) -> Either<L, R> {
78    if cond {
79        Either::Left(l())
80    } else {
81        Either::Right(r())
82    }
83}
84
85/// Retained widget for [`Either`]: the live arm's widget, delegated to for every
86/// pass.
87pub enum EitherWidget<L, R> {
88    /// The left arm's widget.
89    Left(L),
90    /// The right arm's widget.
91    Right(R),
92}
93
94impl<L: Widget, R: Widget> Widget for EitherWidget<L, R> {
95    fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
96        match self {
97            Self::Left(w) => w.layout(ctx, bc),
98            Self::Right(w) => w.layout(ctx, bc),
99        }
100    }
101
102    fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
103        match self {
104            Self::Left(w) => w.paint(ctx, scene),
105            Self::Right(w) => w.paint(ctx, scene),
106        }
107    }
108
109    fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
110        match self {
111            Self::Left(w) => w.event(ctx, event),
112            Self::Right(w) => w.event(ctx, event),
113        }
114    }
115
116    fn semantics(&self, ctx: &mut SemanticsCtx) {
117        match self {
118            Self::Left(w) => w.semantics(ctx),
119            Self::Right(w) => w.semantics(ctx),
120        }
121    }
122
123    fn visit_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
124        match self {
125            Self::Left(w) => w.visit_children(visitor),
126            Self::Right(w) => w.visit_children(visitor),
127        }
128    }
129
130    /// Report the live arm's name: the enum is a storage detail, never a tree
131    /// element in its own right.
132    fn type_name(&self) -> &'static str {
133        match self {
134            Self::Left(w) => w.type_name(),
135            Self::Right(w) => w.type_name(),
136        }
137    }
138
139    /// The live arm's sliver face: a sliver arm stays a sliver through the enum,
140    /// and a box arm keeps answering `None`.
141    fn as_sliver(&mut self) -> Option<&mut dyn SliverWidget> {
142        match self {
143            Self::Left(w) => w.as_sliver(),
144            Self::Right(w) => w.as_sliver(),
145        }
146    }
147}
148
149impl<State: 'static, L: View<State>, R: View<State>> View<State> for Either<L, R> {
150    type Element = EitherWidget<L::Element, R::Element>;
151
152    fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element {
153        match self {
154            Self::Left(v) => EitherWidget::Left(v.build(ctx)),
155            Self::Right(v) => EitherWidget::Right(v.build(ctx)),
156        }
157    }
158
159    fn rebuild(
160        &self,
161        prev: &Self,
162        element: &mut Self::Element,
163        ctx: &mut BuildCtx<'_>,
164    ) -> ChangeFlags {
165        match (self, prev, &mut *element) {
166            (Self::Left(v), Self::Left(p), EitherWidget::Left(w)) => v.rebuild(p, w, ctx),
167            (Self::Right(v), Self::Right(p), EitherWidget::Right(w)) => v.rebuild(p, w, ctx),
168            _ => {
169                // Arm swap: tear the old arm down through its own view, then
170                // build the new arm fresh.
171                prev.teardown(element, ctx);
172                // Both arms share one element type, so the parent's type-id
173                // comparison cannot see this swap: report it, so the enclosing
174                // reconciler drops the pod's capture and focus link as for an
175                // `AnyView` swap. Raise the focus orphan here too when the live
176                // chain ran through this view — a root-level or pod-less parent
177                // has no reconciler to do it (the flag is idempotent).
178                ctx.mark_element_replaced();
179                if ctx.has_focus() {
180                    mark_focus_orphaned();
181                }
182                *element = self.build(ctx);
183                ChangeFlags::LAYOUT | ChangeFlags::PAINT
184            }
185        }
186    }
187
188    fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
189        match (self, element) {
190            (Self::Left(v), EitherWidget::Left(w)) => v.teardown(w, ctx),
191            (Self::Right(v), EitherWidget::Right(w)) => v.teardown(w, ctx),
192            _ => {}
193        }
194    }
195
196    fn message(&self, element: &mut Self::Element, state: &mut State) {
197        match (self, element) {
198            (Self::Left(v), EitherWidget::Left(w)) => v.message(w, state),
199            (Self::Right(v), EitherWidget::Right(w)) => v.message(w, state),
200            _ => {}
201        }
202    }
203}
204
205/// Generates `EitherOfN`, `EitherOfNWidget` and their `Widget` / `View` impls
206/// with exactly the semantics of [`Either`].
207macro_rules! either_of {
208    ($(#[$meta:meta])* $arity:literal, $E:ident, $W:ident; $($V:ident),+ $(,)?) => {
209        $(#[$meta])*
210        #[doc = ""]
211        #[doc = concat!("A view that is one of ", $arity, " statically-typed arms. Arm swaps behave as for [`Either`]: the old arm is torn down, the new one built fresh, and `LAYOUT | PAINT` is reported. See the [module docs](self).")]
212        pub enum $E<$($V),+> {
213            $(
214                #[doc = concat!("Arm `", stringify!($V), "`.")]
215                $V($V),
216            )+
217        }
218
219        #[doc = concat!("Retained widget for [`", stringify!($E), "`]: the live arm's widget, delegated to for every pass.")]
220        pub enum $W<$($V),+> {
221            $(
222                #[doc = concat!("Arm `", stringify!($V), "`'s widget.")]
223                $V($V),
224            )+
225        }
226
227        impl<$($V: Widget),+> Widget for $W<$($V),+> {
228            fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
229                match self { $(Self::$V(w) => w.layout(ctx, bc),)+ }
230            }
231
232            fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
233                match self { $(Self::$V(w) => w.paint(ctx, scene),)+ }
234            }
235
236            fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
237                match self { $(Self::$V(w) => w.event(ctx, event),)+ }
238            }
239
240            fn semantics(&self, ctx: &mut SemanticsCtx) {
241                match self { $(Self::$V(w) => w.semantics(ctx),)+ }
242            }
243
244            fn visit_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
245                match self { $(Self::$V(w) => w.visit_children(visitor),)+ }
246            }
247
248            /// Report the live arm's name: the enum is a storage detail, never
249            /// a tree element in its own right.
250            fn type_name(&self) -> &'static str {
251                match self { $(Self::$V(w) => w.type_name(),)+ }
252            }
253
254            /// The live arm's sliver face, forwarded as for [`EitherWidget`].
255            fn as_sliver(&mut self) -> Option<&mut dyn SliverWidget> {
256                match self { $(Self::$V(w) => w.as_sliver(),)+ }
257            }
258        }
259
260        impl<State: 'static, $($V: View<State>),+> View<State> for $E<$($V),+> {
261            type Element = $W<$($V::Element),+>;
262
263            fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element {
264                match self { $(Self::$V(v) => $W::$V(v.build(ctx)),)+ }
265            }
266
267            fn rebuild(
268                &self,
269                prev: &Self,
270                element: &mut Self::Element,
271                ctx: &mut BuildCtx<'_>,
272            ) -> ChangeFlags {
273                match (self, prev, &mut *element) {
274                    $((Self::$V(v), Self::$V(p), $W::$V(w)) => v.rebuild(p, w, ctx),)+
275                    _ => {
276                        // Arm swap: see `Either::rebuild`.
277                        prev.teardown(element, ctx);
278                        ctx.mark_element_replaced();
279                        if ctx.has_focus() {
280                            mark_focus_orphaned();
281                        }
282                        *element = self.build(ctx);
283                        ChangeFlags::LAYOUT | ChangeFlags::PAINT
284                    }
285                }
286            }
287
288            fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
289                match (self, element) {
290                    $((Self::$V(v), $W::$V(w)) => v.teardown(w, ctx),)+
291                    _ => {}
292                }
293            }
294
295            fn message(&self, element: &mut Self::Element, state: &mut State) {
296                match (self, element) {
297                    $((Self::$V(v), $W::$V(w)) => v.message(w, state),)+
298                    _ => {}
299                }
300            }
301        }
302    };
303}
304
305either_of! {
306    /// Pick one of three view types from a `match` while keeping every arm
307    /// statically typed (no [`AnyView`](frust_core::AnyView) erasure).
308    ///
309    /// ```
310    /// use frust_core::View;
311    /// use frust_widgets::{EitherOf3, button, checkbox, text};
312    ///
313    /// enum Phase {
314    ///     Idle,
315    ///     Busy,
316    ///     Done,
317    /// }
318    ///
319    /// struct Screen;
320    ///
321    /// impl Screen {
322    ///     fn body(phase: Phase) -> impl View<()> {
323    ///         match phase {
324    ///             Phase::Idle => EitherOf3::A(text("Idle")),
325    ///             Phase::Busy => EitherOf3::B(button::<(), _>("Cancel", |_| {})),
326    ///             Phase::Done => EitherOf3::C(checkbox::<(), _>(true, "Ok", |_, _| {})),
327    ///         }
328    ///     }
329    /// }
330    ///
331    /// let _ = Screen::body(Phase::Idle);
332    /// let _ = Screen::body(Phase::Busy);
333    /// let _ = Screen::body(Phase::Done);
334    /// ```
335    "three", EitherOf3, EitherOf3Widget; A, B, C
336}
337either_of! { "four", EitherOf4, EitherOf4Widget; A, B, C, D }
338either_of! { "five", EitherOf5, EitherOf5Widget; A, B, C, D, E }
339either_of! { "six", EitherOf6, EitherOf6Widget; A, B, C, D, E, F }
340either_of! { "seven", EitherOf7, EitherOf7Widget; A, B, C, D, E, F, G }
341either_of! { "eight", EitherOf8, EitherOf8Widget; A, B, C, D, E, F, G, H }
342
343#[cfg(test)]
344mod tests {
345    use std::cell::RefCell;
346    use std::rc::Rc;
347
348    use frust_core::accesskit::Role;
349    use frust_core::{DiscardScene, FrameTime, RenderRoot, SemanticsUpdate, take_focus_orphaned};
350    use frust_text::TextContext;
351
352    use super::*;
353    use crate::{ButtonView, CheckboxView, button, checkbox};
354
355    type Log = Rc<RefCell<Vec<String>>>;
356
357    /// A fixed-size arm that logs build / teardown / drop under its tag.
358    struct Arm {
359        tag: &'static str,
360        size: Size,
361        log: Log,
362    }
363
364    struct ArmWidget {
365        tag: &'static str,
366        size: Size,
367        log: Log,
368        painted: u32,
369    }
370
371    impl Drop for ArmWidget {
372        fn drop(&mut self) {
373            self.log.borrow_mut().push(format!("drop {}", self.tag));
374        }
375    }
376
377    impl View<()> for Arm {
378        type Element = ArmWidget;
379        fn build(&self, _ctx: &mut BuildCtx<'_>) -> ArmWidget {
380            self.log.borrow_mut().push(format!("build {}", self.tag));
381            ArmWidget {
382                tag: self.tag,
383                size: self.size,
384                log: Rc::clone(&self.log),
385                painted: 0,
386            }
387        }
388        fn rebuild(
389            &self,
390            _prev: &Self,
391            el: &mut ArmWidget,
392            _ctx: &mut BuildCtx<'_>,
393        ) -> ChangeFlags {
394            el.size = self.size;
395            ChangeFlags::NONE
396        }
397        fn teardown(&self, _el: &mut ArmWidget, _ctx: &mut BuildCtx<'_>) {
398            self.log.borrow_mut().push(format!("teardown {}", self.tag));
399        }
400    }
401
402    impl Widget for ArmWidget {
403        fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
404            bc.constrain(self.size)
405        }
406        fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {
407            self.painted += 1;
408            self.log
409                .borrow_mut()
410                .push(format!("paint {} #{}", self.tag, self.painted));
411        }
412    }
413
414    fn arm(tag: &'static str, w: f64, h: f64, log: &Log) -> Arm {
415        Arm {
416            tag,
417            size: Size::new(w, h),
418            log: Rc::clone(log),
419        }
420    }
421
422    fn new_log() -> Log {
423        Rc::new(RefCell::new(Vec::new()))
424    }
425
426    fn drain(log: &Log) -> Vec<String> {
427        std::mem::take(&mut *log.borrow_mut())
428    }
429
430    #[test]
431    fn helper_is_lazy_and_picks_the_arm() {
432        let built = Rc::new(RefCell::new(Vec::new()));
433        let (b1, b2) = (Rc::clone(&built), Rc::clone(&built));
434        let v = either(
435            true,
436            move || {
437                b1.borrow_mut().push("l");
438                1u8
439            },
440            move || {
441                b2.borrow_mut().push("r");
442                2u8
443            },
444        );
445        assert!(matches!(v, Either::Left(1)));
446        assert_eq!(*built.borrow(), vec!["l"], "only the chosen arm runs");
447        assert!(matches!(either(false, || 1u8, || 2u8), Either::Right(2)));
448    }
449
450    /// The behaviour matrix, instantiated per enum so `Either` and `EitherOfN`
451    /// cannot drift. `$a` / `$b` are the first two variants.
452    macro_rules! swap_matrix {
453        ($m:ident, $E:ident, $W:ident, $a:ident, $b:ident, $arms:ty, $ui:ty) => {
454            mod $m {
455                use super::*;
456
457                type Arms = $arms;
458                type Ui = $ui;
459
460                fn pick(first: bool, log: &Log, sa: (f64, f64), sb: (f64, f64)) -> Arms {
461                    if first {
462                        $E::$a(arm("a", sa.0, sa.1, log))
463                    } else {
464                        $E::$b(arm("b", sb.0, sb.1, log))
465                    }
466                }
467
468                #[test]
469                fn layout_and_paint_delegate_to_the_live_variant() {
470                    let log = new_log();
471                    let mut root: RenderRoot<(), Arms> = RenderRoot::new();
472                    let mut state = ();
473                    let mut logic = |_: &mut ()| pick(false, &log, (10.0, 10.0), (30.0, 20.0));
474                    root.rebuild(&mut logic, &mut state);
475                    root.layout(Size::new(200.0, 200.0));
476                    root.paint(&mut DiscardScene, FrameTime::from_nanos(0));
477                    let nodes = root.inspect();
478                    assert!(
479                        nodes[0].type_name.contains(stringify!($W)),
480                        "root is the enum widget, got {}",
481                        nodes[0].type_name
482                    );
483                    assert_eq!(nodes[0].bounds.size(), Size::new(30.0, 20.0));
484                    let seen = drain(&log);
485                    assert!(seen.contains(&"build b".to_string()));
486                    assert!(seen.contains(&"paint b #1".to_string()));
487                    assert!(
488                        !seen.iter().any(|e| e.contains(" a")),
489                        "inactive variant untouched: {seen:?}"
490                    );
491                }
492
493                #[test]
494                fn same_variant_rebuild_keeps_state_swap_resets_it() {
495                    let log = new_log();
496                    let mut root: RenderRoot<(), Arms> = RenderRoot::new();
497                    let mut state = ();
498                    let first = std::cell::Cell::new(true);
499                    let mut logic =
500                        |_: &mut ()| pick(first.get(), &log, (10.0, 10.0), (10.0, 10.0));
501                    root.rebuild(&mut logic, &mut state);
502                    root.layout(Size::new(100.0, 100.0));
503                    root.paint(&mut DiscardScene, FrameTime::from_nanos(0));
504                    root.paint(&mut DiscardScene, FrameTime::from_nanos(1));
505                    assert_eq!(drain(&log), ["build a", "paint a #1", "paint a #2"]);
506
507                    root.rebuild(&mut logic, &mut state);
508                    root.layout(Size::new(100.0, 100.0));
509                    root.paint(&mut DiscardScene, FrameTime::from_nanos(2));
510                    assert_eq!(drain(&log), ["paint a #3"]);
511
512                    first.set(false);
513                    root.rebuild(&mut logic, &mut state);
514                    root.layout(Size::new(100.0, 100.0));
515                    root.paint(&mut DiscardScene, FrameTime::from_nanos(3));
516                    assert_eq!(
517                        drain(&log),
518                        ["teardown a", "build b", "drop a", "paint b #1"]
519                    );
520
521                    first.set(true);
522                    root.rebuild(&mut logic, &mut state);
523                    root.paint(&mut DiscardScene, FrameTime::from_nanos(4));
524                    assert_eq!(
525                        drain(&log),
526                        ["teardown b", "build a", "drop b", "paint a #1"]
527                    );
528                }
529
530                #[test]
531                fn swap_reports_full_rebuild_flags_and_same_variant_does_not() {
532                    let log = new_log();
533                    let mut counter = 0u64;
534                    let mut ctx = BuildCtx::new(&mut counter);
535                    let prev = pick(true, &log, (1.0, 1.0), (1.0, 1.0));
536                    let mut el = prev.build(&mut ctx);
537                    let same = pick(true, &log, (1.0, 1.0), (1.0, 1.0));
538                    assert_eq!(same.rebuild(&prev, &mut el, &mut ctx), ChangeFlags::NONE);
539                    let next = pick(false, &log, (1.0, 1.0), (1.0, 1.0));
540                    let flags = next.rebuild(&same, &mut el, &mut ctx);
541                    assert_eq!(flags, ChangeFlags::LAYOUT | ChangeFlags::PAINT);
542                    assert!(matches!(el, $W::$b(_)));
543                }
544
545                #[test]
546                fn an_arm_swap_reports_element_replaced_and_same_variant_does_not() {
547                    let log = new_log();
548                    let mut counter = 0u64;
549                    let mut ctx = BuildCtx::new(&mut counter);
550                    let prev = pick(true, &log, (1.0, 1.0), (1.0, 1.0));
551                    let mut el = prev.build(&mut ctx);
552                    let same = pick(true, &log, (2.0, 2.0), (1.0, 1.0));
553                    let (_, replaced) =
554                        ctx.track_element_replaced(|ctx| same.rebuild(&prev, &mut el, ctx));
555                    assert!(!replaced, "a same-variant rebuild keeps the widget");
556                    let next = pick(false, &log, (1.0, 1.0), (1.0, 1.0));
557                    let (_, replaced) =
558                        ctx.track_element_replaced(|ctx| next.rebuild(&same, &mut el, ctx));
559                    assert!(replaced, "an arm swap reports the replaced widget");
560                }
561
562                #[test]
563                fn swap_under_a_live_focus_chain_orphans_focus_only_then() {
564                    let log = new_log();
565                    let mut counter = 0u64;
566                    let mut ctx = BuildCtx::new(&mut counter);
567                    let a = pick(true, &log, (1.0, 1.0), (1.0, 1.0));
568                    let b = pick(false, &log, (1.0, 1.0), (1.0, 1.0));
569                    let _ = take_focus_orphaned();
570
571                    ctx.set_has_focus(false);
572                    let mut el = a.build(&mut ctx);
573                    b.rebuild(&a, &mut el, &mut ctx);
574                    assert!(!take_focus_orphaned(), "unfocused swap marks nothing");
575
576                    ctx.set_has_focus(true);
577                    let b2 = pick(false, &log, (1.0, 1.0), (1.0, 1.0));
578                    b2.rebuild(&b, &mut el, &mut ctx);
579                    assert!(!take_focus_orphaned(), "same-variant rebuild marks nothing");
580
581                    a.rebuild(&b2, &mut el, &mut ctx);
582                    assert!(take_focus_orphaned(), "focused swap orphans focus");
583                }
584
585                fn ui(show_button: bool) -> Ui {
586                    if show_button {
587                        $E::$a(button::<(), _>("Go", |_| {}))
588                    } else {
589                        $E::$b(checkbox::<(), _>(true, "Agree", |_, _| {}))
590                    }
591                }
592
593                fn roles(u: &SemanticsUpdate) -> Vec<Role> {
594                    u.nodes.iter().map(|(_, n)| n.role()).collect()
595                }
596
597                #[test]
598                fn semantics_reflect_the_live_variant_and_follow_a_swap() {
599                    let mut root: RenderRoot<(), Ui> = RenderRoot::new();
600                    let mut state = ();
601                    let show_button = std::cell::Cell::new(true);
602                    let mut logic = |_: &mut ()| ui(show_button.get());
603                    let mut tcx = TextContext::new();
604                    root.rebuild(&mut logic, &mut state);
605                    root.layout_with_text(
606                        Size::new(300.0, 300.0),
607                        &mut tcx as &mut dyn std::any::Any,
608                    );
609                    let before = root.semantics();
610                    let top = &before
611                        .nodes
612                        .iter()
613                        .find(|(id, _)| *id == before.root)
614                        .unwrap()
615                        .1;
616                    assert_eq!(
617                        top.children().len(),
618                        1,
619                        "exactly the live variant contributes"
620                    );
621                    show_button.set(false);
622                    root.rebuild(&mut logic, &mut state);
623                    root.layout_with_text(
624                        Size::new(300.0, 300.0),
625                        &mut tcx as &mut dyn std::any::Any,
626                    );
627                    let after = root.semantics();
628                    assert!(roles(&before).contains(&Role::Button));
629                    assert!(!roles(&before).contains(&Role::CheckBox));
630                    assert!(roles(&after).contains(&Role::CheckBox));
631                    assert!(!roles(&after).contains(&Role::Button));
632                }
633            }
634        };
635    }
636
637    swap_matrix!(
638        either_matrix,
639        Either,
640        EitherWidget,
641        Left,
642        Right,
643        Either<Arm, Arm>,
644        Either<ButtonView<()>, CheckboxView<()>>
645    );
646    swap_matrix!(
647        either_of_3_matrix,
648        EitherOf3,
649        EitherOf3Widget,
650        A,
651        B,
652        EitherOf3<Arm, Arm, Arm>,
653        EitherOf3<ButtonView<()>, CheckboxView<()>, ButtonView<()>>
654    );
655    swap_matrix!(
656        either_of_8_matrix,
657        EitherOf8,
658        EitherOf8Widget,
659        A,
660        B,
661        EitherOf8<Arm, Arm, Arm, Arm, Arm, Arm, Arm, Arm>,
662        EitherOf8<
663            ButtonView<()>,
664            CheckboxView<()>,
665            ButtonView<()>,
666            ButtonView<()>,
667            ButtonView<()>,
668            ButtonView<()>,
669            ButtonView<()>,
670            ButtonView<()>,
671        >
672    );
673
674    #[test]
675    fn eight_variant_instance_swaps_a_to_h() {
676        type Eight = EitherOf8<Arm, Arm, Arm, Arm, Arm, Arm, Arm, Arm>;
677        let log = new_log();
678        let mut counter = 0u64;
679        let mut ctx = BuildCtx::new(&mut counter);
680        let a: Eight = EitherOf8::A(arm("a", 1.0, 1.0, &log));
681        let h: Eight = EitherOf8::H(arm("h", 5.0, 5.0, &log));
682        let mut el = a.build(&mut ctx);
683        assert!(matches!(el, EitherOf8Widget::A(_)));
684        let flags = h.rebuild(&a, &mut el, &mut ctx);
685        assert_eq!(flags, ChangeFlags::LAYOUT | ChangeFlags::PAINT);
686        assert!(matches!(el, EitherOf8Widget::H(_)));
687        assert_eq!(drain(&log), ["build a", "teardown a", "build h", "drop a"]);
688        h.teardown(&mut el, &mut ctx);
689        assert_eq!(drain(&log), ["teardown h"]);
690    }
691}