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. Keyboard broadcasts ([`InputEvent::Shortcut`], [`InputEvent::Key`])
87/// reach the live arm only; the other arm does not exist.
88pub enum EitherWidget<L, R> {
89    /// The left arm's widget.
90    Left(L),
91    /// The right arm's widget.
92    Right(R),
93}
94
95impl<L: Widget, R: Widget> Widget for EitherWidget<L, R> {
96    fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
97        match self {
98            Self::Left(w) => w.layout(ctx, bc),
99            Self::Right(w) => w.layout(ctx, bc),
100        }
101    }
102
103    fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
104        match self {
105            Self::Left(w) => w.paint(ctx, scene),
106            Self::Right(w) => w.paint(ctx, scene),
107        }
108    }
109
110    fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
111        match self {
112            Self::Left(w) => w.event(ctx, event),
113            Self::Right(w) => w.event(ctx, event),
114        }
115    }
116
117    fn semantics(&self, ctx: &mut SemanticsCtx) {
118        match self {
119            Self::Left(w) => w.semantics(ctx),
120            Self::Right(w) => w.semantics(ctx),
121        }
122    }
123
124    fn visit_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
125        match self {
126            Self::Left(w) => w.visit_children(visitor),
127            Self::Right(w) => w.visit_children(visitor),
128        }
129    }
130
131    /// Report the live arm's name: the enum is a storage detail, never a tree
132    /// element in its own right.
133    fn type_name(&self) -> &'static str {
134        match self {
135            Self::Left(w) => w.type_name(),
136            Self::Right(w) => w.type_name(),
137        }
138    }
139
140    /// The live arm's sliver face: a sliver arm stays a sliver through the enum,
141    /// and a box arm keeps answering `None`.
142    fn as_sliver(&mut self) -> Option<&mut dyn SliverWidget> {
143        match self {
144            Self::Left(w) => w.as_sliver(),
145            Self::Right(w) => w.as_sliver(),
146        }
147    }
148
149    /// The live arm's answer: the enum's pod is the arm's Tab stop.
150    fn is_focusable(&self) -> bool {
151        match self {
152            Self::Left(w) => w.is_focusable(),
153            Self::Right(w) => w.is_focusable(),
154        }
155    }
156
157    /// The live arm's traversal children, so a narrowing arm (a navigator, an
158    /// `IndexedStack`) stays narrowed through the enum.
159    fn visit_focus_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
160        match self {
161            Self::Left(w) => w.visit_focus_children(visitor),
162            Self::Right(w) => w.visit_focus_children(visitor),
163        }
164    }
165}
166
167impl<State: 'static, L: View<State>, R: View<State>> View<State> for Either<L, R> {
168    type Element = EitherWidget<L::Element, R::Element>;
169
170    fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element {
171        match self {
172            Self::Left(v) => EitherWidget::Left(v.build(ctx)),
173            Self::Right(v) => EitherWidget::Right(v.build(ctx)),
174        }
175    }
176
177    fn rebuild(
178        &self,
179        prev: &Self,
180        element: &mut Self::Element,
181        ctx: &mut BuildCtx<'_>,
182    ) -> ChangeFlags {
183        match (self, prev, &mut *element) {
184            (Self::Left(v), Self::Left(p), EitherWidget::Left(w)) => v.rebuild(p, w, ctx),
185            (Self::Right(v), Self::Right(p), EitherWidget::Right(w)) => v.rebuild(p, w, ctx),
186            _ => {
187                // Arm swap: tear the old arm down through its own view, then
188                // build the new arm fresh.
189                prev.teardown(element, ctx);
190                // Both arms share one element type, so the parent's type-id
191                // comparison cannot see this swap: report it, so the enclosing
192                // reconciler drops the pod's capture and focus link as for an
193                // `AnyView` swap. Raise the focus orphan here too when the live
194                // chain ran through this view — a root-level or pod-less parent
195                // has no reconciler to do it (the flag is idempotent).
196                ctx.mark_element_replaced();
197                if ctx.has_focus() {
198                    mark_focus_orphaned();
199                }
200                *element = self.build(ctx);
201                ChangeFlags::LAYOUT | ChangeFlags::PAINT
202            }
203        }
204    }
205
206    fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
207        match (self, element) {
208            (Self::Left(v), EitherWidget::Left(w)) => v.teardown(w, ctx),
209            (Self::Right(v), EitherWidget::Right(w)) => v.teardown(w, ctx),
210            _ => {}
211        }
212    }
213
214    fn message(&self, element: &mut Self::Element, state: &mut State) {
215        match (self, element) {
216            (Self::Left(v), EitherWidget::Left(w)) => v.message(w, state),
217            (Self::Right(v), EitherWidget::Right(w)) => v.message(w, state),
218            _ => {}
219        }
220    }
221}
222
223/// Generates `EitherOfN`, `EitherOfNWidget` and their `Widget` / `View` impls
224/// with exactly the semantics of [`Either`].
225macro_rules! either_of {
226    ($(#[$meta:meta])* $arity:literal, $E:ident, $W:ident; $($V:ident),+ $(,)?) => {
227        $(#[$meta])*
228        #[doc = ""]
229        #[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).")]
230        pub enum $E<$($V),+> {
231            $(
232                #[doc = concat!("Arm `", stringify!($V), "`.")]
233                $V($V),
234            )+
235        }
236
237        #[doc = concat!("Retained widget for [`", stringify!($E), "`]: the live arm's widget, delegated to for every pass.")]
238        pub enum $W<$($V),+> {
239            $(
240                #[doc = concat!("Arm `", stringify!($V), "`'s widget.")]
241                $V($V),
242            )+
243        }
244
245        impl<$($V: Widget),+> Widget for $W<$($V),+> {
246            fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
247                match self { $(Self::$V(w) => w.layout(ctx, bc),)+ }
248            }
249
250            fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
251                match self { $(Self::$V(w) => w.paint(ctx, scene),)+ }
252            }
253
254            fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
255                match self { $(Self::$V(w) => w.event(ctx, event),)+ }
256            }
257
258            fn semantics(&self, ctx: &mut SemanticsCtx) {
259                match self { $(Self::$V(w) => w.semantics(ctx),)+ }
260            }
261
262            fn visit_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
263                match self { $(Self::$V(w) => w.visit_children(visitor),)+ }
264            }
265
266            /// Report the live arm's name: the enum is a storage detail, never
267            /// a tree element in its own right.
268            fn type_name(&self) -> &'static str {
269                match self { $(Self::$V(w) => w.type_name(),)+ }
270            }
271
272            /// The live arm's sliver face, forwarded as for [`EitherWidget`].
273            fn as_sliver(&mut self) -> Option<&mut dyn SliverWidget> {
274                match self { $(Self::$V(w) => w.as_sliver(),)+ }
275            }
276
277            /// The live arm's answer, forwarded as for [`EitherWidget`].
278            fn is_focusable(&self) -> bool {
279                match self { $(Self::$V(w) => w.is_focusable(),)+ }
280            }
281
282            /// The live arm's traversal children, forwarded as for [`EitherWidget`].
283            fn visit_focus_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
284                match self { $(Self::$V(w) => w.visit_focus_children(visitor),)+ }
285            }
286        }
287
288        impl<State: 'static, $($V: View<State>),+> View<State> for $E<$($V),+> {
289            type Element = $W<$($V::Element),+>;
290
291            fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element {
292                match self { $(Self::$V(v) => $W::$V(v.build(ctx)),)+ }
293            }
294
295            fn rebuild(
296                &self,
297                prev: &Self,
298                element: &mut Self::Element,
299                ctx: &mut BuildCtx<'_>,
300            ) -> ChangeFlags {
301                match (self, prev, &mut *element) {
302                    $((Self::$V(v), Self::$V(p), $W::$V(w)) => v.rebuild(p, w, ctx),)+
303                    _ => {
304                        // Arm swap: see `Either::rebuild`.
305                        prev.teardown(element, ctx);
306                        ctx.mark_element_replaced();
307                        if ctx.has_focus() {
308                            mark_focus_orphaned();
309                        }
310                        *element = self.build(ctx);
311                        ChangeFlags::LAYOUT | ChangeFlags::PAINT
312                    }
313                }
314            }
315
316            fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
317                match (self, element) {
318                    $((Self::$V(v), $W::$V(w)) => v.teardown(w, ctx),)+
319                    _ => {}
320                }
321            }
322
323            fn message(&self, element: &mut Self::Element, state: &mut State) {
324                match (self, element) {
325                    $((Self::$V(v), $W::$V(w)) => v.message(w, state),)+
326                    _ => {}
327                }
328            }
329        }
330    };
331}
332
333either_of! {
334    /// Pick one of three view types from a `match` while keeping every arm
335    /// statically typed (no [`AnyView`](frust_core::AnyView) erasure).
336    ///
337    /// ```
338    /// use frust_core::View;
339    /// use frust_widgets::{EitherOf3, button, checkbox, text};
340    ///
341    /// enum Phase {
342    ///     Idle,
343    ///     Busy,
344    ///     Done,
345    /// }
346    ///
347    /// struct Screen;
348    ///
349    /// impl Screen {
350    ///     fn body(phase: Phase) -> impl View<()> {
351    ///         match phase {
352    ///             Phase::Idle => EitherOf3::A(text("Idle")),
353    ///             Phase::Busy => EitherOf3::B(button::<(), _>("Cancel", |_| {})),
354    ///             Phase::Done => EitherOf3::C(checkbox::<(), _>(true, "Ok", |_, _| {})),
355    ///         }
356    ///     }
357    /// }
358    ///
359    /// let _ = Screen::body(Phase::Idle);
360    /// let _ = Screen::body(Phase::Busy);
361    /// let _ = Screen::body(Phase::Done);
362    /// ```
363    "three", EitherOf3, EitherOf3Widget; A, B, C
364}
365either_of! { "four", EitherOf4, EitherOf4Widget; A, B, C, D }
366either_of! { "five", EitherOf5, EitherOf5Widget; A, B, C, D, E }
367either_of! { "six", EitherOf6, EitherOf6Widget; A, B, C, D, E, F }
368either_of! { "seven", EitherOf7, EitherOf7Widget; A, B, C, D, E, F, G }
369either_of! { "eight", EitherOf8, EitherOf8Widget; A, B, C, D, E, F, G, H }
370
371#[cfg(test)]
372mod tests {
373    use std::cell::RefCell;
374    use std::rc::Rc;
375
376    use frust_core::accesskit::Role;
377    use frust_core::{DiscardScene, FrameTime, RenderRoot, SemanticsUpdate, take_focus_orphaned};
378    use frust_text::TextContext;
379
380    use super::*;
381    use crate::{ButtonView, CheckboxView, button, checkbox, text};
382
383    type Log = Rc<RefCell<Vec<String>>>;
384
385    /// A fixed-size arm that logs build / teardown / drop under its tag.
386    struct Arm {
387        tag: &'static str,
388        size: Size,
389        log: Log,
390    }
391
392    struct ArmWidget {
393        tag: &'static str,
394        size: Size,
395        log: Log,
396        painted: u32,
397    }
398
399    impl Drop for ArmWidget {
400        fn drop(&mut self) {
401            self.log.borrow_mut().push(format!("drop {}", self.tag));
402        }
403    }
404
405    impl View<()> for Arm {
406        type Element = ArmWidget;
407        fn build(&self, _ctx: &mut BuildCtx<'_>) -> ArmWidget {
408            self.log.borrow_mut().push(format!("build {}", self.tag));
409            ArmWidget {
410                tag: self.tag,
411                size: self.size,
412                log: Rc::clone(&self.log),
413                painted: 0,
414            }
415        }
416        fn rebuild(
417            &self,
418            _prev: &Self,
419            el: &mut ArmWidget,
420            _ctx: &mut BuildCtx<'_>,
421        ) -> ChangeFlags {
422            el.size = self.size;
423            ChangeFlags::NONE
424        }
425        fn teardown(&self, _el: &mut ArmWidget, _ctx: &mut BuildCtx<'_>) {
426            self.log.borrow_mut().push(format!("teardown {}", self.tag));
427        }
428    }
429
430    impl Widget for ArmWidget {
431        fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
432            bc.constrain(self.size)
433        }
434        fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {
435            self.painted += 1;
436            self.log
437                .borrow_mut()
438                .push(format!("paint {} #{}", self.tag, self.painted));
439        }
440    }
441
442    fn arm(tag: &'static str, w: f64, h: f64, log: &Log) -> Arm {
443        Arm {
444            tag,
445            size: Size::new(w, h),
446            log: Rc::clone(log),
447        }
448    }
449
450    fn new_log() -> Log {
451        Rc::new(RefCell::new(Vec::new()))
452    }
453
454    fn drain(log: &Log) -> Vec<String> {
455        std::mem::take(&mut *log.borrow_mut())
456    }
457
458    #[test]
459    fn helper_is_lazy_and_picks_the_arm() {
460        let built = Rc::new(RefCell::new(Vec::new()));
461        let (b1, b2) = (Rc::clone(&built), Rc::clone(&built));
462        let v = either(
463            true,
464            move || {
465                b1.borrow_mut().push("l");
466                1u8
467            },
468            move || {
469                b2.borrow_mut().push("r");
470                2u8
471            },
472        );
473        assert!(matches!(v, Either::Left(1)));
474        assert_eq!(*built.borrow(), vec!["l"], "only the chosen arm runs");
475        assert!(matches!(either(false, || 1u8, || 2u8), Either::Right(2)));
476    }
477
478    /// The behaviour matrix, instantiated per enum so `Either` and `EitherOfN`
479    /// cannot drift. `$a` / `$b` are the first two variants.
480    macro_rules! swap_matrix {
481        ($m:ident, $E:ident, $W:ident, $a:ident, $b:ident, $arms:ty, $ui:ty) => {
482            mod $m {
483                use super::*;
484
485                type Arms = $arms;
486                type Ui = $ui;
487
488                fn pick(first: bool, log: &Log, sa: (f64, f64), sb: (f64, f64)) -> Arms {
489                    if first {
490                        $E::$a(arm("a", sa.0, sa.1, log))
491                    } else {
492                        $E::$b(arm("b", sb.0, sb.1, log))
493                    }
494                }
495
496                #[test]
497                fn layout_and_paint_delegate_to_the_live_variant() {
498                    let log = new_log();
499                    let mut root: RenderRoot<(), Arms> = RenderRoot::new();
500                    let mut state = ();
501                    let mut logic = |_: &mut ()| pick(false, &log, (10.0, 10.0), (30.0, 20.0));
502                    root.rebuild(&mut logic, &mut state);
503                    root.layout(Size::new(200.0, 200.0));
504                    root.paint(&mut DiscardScene, FrameTime::from_nanos(0));
505                    let nodes = root.inspect();
506                    assert!(
507                        nodes[0].type_name.contains(stringify!($W)),
508                        "root is the enum widget, got {}",
509                        nodes[0].type_name
510                    );
511                    assert_eq!(nodes[0].bounds.size(), Size::new(30.0, 20.0));
512                    let seen = drain(&log);
513                    assert!(seen.contains(&"build b".to_string()));
514                    assert!(seen.contains(&"paint b #1".to_string()));
515                    assert!(
516                        !seen.iter().any(|e| e.contains(" a")),
517                        "inactive variant untouched: {seen:?}"
518                    );
519                }
520
521                #[test]
522                fn same_variant_rebuild_keeps_state_swap_resets_it() {
523                    let log = new_log();
524                    let mut root: RenderRoot<(), Arms> = RenderRoot::new();
525                    let mut state = ();
526                    let first = std::cell::Cell::new(true);
527                    let mut logic =
528                        |_: &mut ()| pick(first.get(), &log, (10.0, 10.0), (10.0, 10.0));
529                    root.rebuild(&mut logic, &mut state);
530                    root.layout(Size::new(100.0, 100.0));
531                    root.paint(&mut DiscardScene, FrameTime::from_nanos(0));
532                    root.paint(&mut DiscardScene, FrameTime::from_nanos(1));
533                    assert_eq!(drain(&log), ["build a", "paint a #1", "paint a #2"]);
534
535                    root.rebuild(&mut logic, &mut state);
536                    root.layout(Size::new(100.0, 100.0));
537                    root.paint(&mut DiscardScene, FrameTime::from_nanos(2));
538                    assert_eq!(drain(&log), ["paint a #3"]);
539
540                    first.set(false);
541                    root.rebuild(&mut logic, &mut state);
542                    root.layout(Size::new(100.0, 100.0));
543                    root.paint(&mut DiscardScene, FrameTime::from_nanos(3));
544                    assert_eq!(
545                        drain(&log),
546                        ["teardown a", "build b", "drop a", "paint b #1"]
547                    );
548
549                    first.set(true);
550                    root.rebuild(&mut logic, &mut state);
551                    root.paint(&mut DiscardScene, FrameTime::from_nanos(4));
552                    assert_eq!(
553                        drain(&log),
554                        ["teardown b", "build a", "drop b", "paint a #1"]
555                    );
556                }
557
558                #[test]
559                fn swap_reports_full_rebuild_flags_and_same_variant_does_not() {
560                    let log = new_log();
561                    let mut counter = 0u64;
562                    let mut ctx = BuildCtx::new(&mut counter);
563                    let prev = pick(true, &log, (1.0, 1.0), (1.0, 1.0));
564                    let mut el = prev.build(&mut ctx);
565                    let same = pick(true, &log, (1.0, 1.0), (1.0, 1.0));
566                    assert_eq!(same.rebuild(&prev, &mut el, &mut ctx), ChangeFlags::NONE);
567                    let next = pick(false, &log, (1.0, 1.0), (1.0, 1.0));
568                    let flags = next.rebuild(&same, &mut el, &mut ctx);
569                    assert_eq!(flags, ChangeFlags::LAYOUT | ChangeFlags::PAINT);
570                    assert!(matches!(el, $W::$b(_)));
571                }
572
573                #[test]
574                fn an_arm_swap_reports_element_replaced_and_same_variant_does_not() {
575                    let log = new_log();
576                    let mut counter = 0u64;
577                    let mut ctx = BuildCtx::new(&mut counter);
578                    let prev = pick(true, &log, (1.0, 1.0), (1.0, 1.0));
579                    let mut el = prev.build(&mut ctx);
580                    let same = pick(true, &log, (2.0, 2.0), (1.0, 1.0));
581                    let (_, replaced) =
582                        ctx.track_element_replaced(|ctx| same.rebuild(&prev, &mut el, ctx));
583                    assert!(!replaced, "a same-variant rebuild keeps the widget");
584                    let next = pick(false, &log, (1.0, 1.0), (1.0, 1.0));
585                    let (_, replaced) =
586                        ctx.track_element_replaced(|ctx| next.rebuild(&same, &mut el, ctx));
587                    assert!(replaced, "an arm swap reports the replaced widget");
588                }
589
590                #[test]
591                fn swap_under_a_live_focus_chain_orphans_focus_only_then() {
592                    let log = new_log();
593                    let mut counter = 0u64;
594                    let mut ctx = BuildCtx::new(&mut counter);
595                    let a = pick(true, &log, (1.0, 1.0), (1.0, 1.0));
596                    let b = pick(false, &log, (1.0, 1.0), (1.0, 1.0));
597                    let _ = take_focus_orphaned();
598
599                    ctx.set_has_focus(false);
600                    let mut el = a.build(&mut ctx);
601                    b.rebuild(&a, &mut el, &mut ctx);
602                    assert!(!take_focus_orphaned(), "unfocused swap marks nothing");
603
604                    ctx.set_has_focus(true);
605                    let b2 = pick(false, &log, (1.0, 1.0), (1.0, 1.0));
606                    b2.rebuild(&b, &mut el, &mut ctx);
607                    assert!(!take_focus_orphaned(), "same-variant rebuild marks nothing");
608
609                    a.rebuild(&b2, &mut el, &mut ctx);
610                    assert!(take_focus_orphaned(), "focused swap orphans focus");
611                }
612
613                fn ui(show_button: bool) -> Ui {
614                    if show_button {
615                        $E::$a(button::<(), _>("Go", |_| {}))
616                    } else {
617                        $E::$b(checkbox::<(), _>(true, "Agree", |_, _| {}))
618                    }
619                }
620
621                fn roles(u: &SemanticsUpdate) -> Vec<Role> {
622                    u.nodes.iter().map(|(_, n)| n.role()).collect()
623                }
624
625                #[test]
626                fn semantics_reflect_the_live_variant_and_follow_a_swap() {
627                    let mut root: RenderRoot<(), Ui> = RenderRoot::new();
628                    let mut state = ();
629                    let show_button = std::cell::Cell::new(true);
630                    let mut logic = |_: &mut ()| ui(show_button.get());
631                    let mut tcx = TextContext::new();
632                    root.rebuild(&mut logic, &mut state);
633                    root.layout_with_text(
634                        Size::new(300.0, 300.0),
635                        &mut tcx as &mut dyn std::any::Any,
636                    );
637                    let before = root.semantics();
638                    let top = &before
639                        .nodes
640                        .iter()
641                        .find(|(id, _)| *id == before.root)
642                        .unwrap()
643                        .1;
644                    assert_eq!(
645                        top.children().len(),
646                        1,
647                        "exactly the live variant contributes"
648                    );
649                    show_button.set(false);
650                    root.rebuild(&mut logic, &mut state);
651                    root.layout_with_text(
652                        Size::new(300.0, 300.0),
653                        &mut tcx as &mut dyn std::any::Any,
654                    );
655                    let after = root.semantics();
656                    assert!(roles(&before).contains(&Role::Button));
657                    assert!(!roles(&before).contains(&Role::CheckBox));
658                    assert!(roles(&after).contains(&Role::CheckBox));
659                    assert!(!roles(&after).contains(&Role::Button));
660                }
661            }
662        };
663    }
664
665    swap_matrix!(
666        either_matrix,
667        Either,
668        EitherWidget,
669        Left,
670        Right,
671        Either<Arm, Arm>,
672        Either<ButtonView<()>, CheckboxView<()>>
673    );
674    swap_matrix!(
675        either_of_3_matrix,
676        EitherOf3,
677        EitherOf3Widget,
678        A,
679        B,
680        EitherOf3<Arm, Arm, Arm>,
681        EitherOf3<ButtonView<()>, CheckboxView<()>, ButtonView<()>>
682    );
683    swap_matrix!(
684        either_of_8_matrix,
685        EitherOf8,
686        EitherOf8Widget,
687        A,
688        B,
689        EitherOf8<Arm, Arm, Arm, Arm, Arm, Arm, Arm, Arm>,
690        EitherOf8<
691            ButtonView<()>,
692            CheckboxView<()>,
693            ButtonView<()>,
694            ButtonView<()>,
695            ButtonView<()>,
696            ButtonView<()>,
697            ButtonView<()>,
698            ButtonView<()>,
699        >
700    );
701
702    /// A leaf that is a Tab stop when `focusable`, owning two pods of which
703    /// traversal may enter only the first.
704    struct FocusProbe {
705        focusable: bool,
706        pods: [ChildPod; 2],
707    }
708
709    impl Widget for FocusProbe {
710        fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
711            bc.min()
712        }
713        fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
714        fn visit_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
715            for pod in &self.pods {
716                visitor(pod);
717            }
718        }
719        fn is_focusable(&self) -> bool {
720            self.focusable
721        }
722        fn visit_focus_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
723            visitor(&self.pods[0]);
724        }
725    }
726
727    fn probe(focusable: bool) -> FocusProbe {
728        let log = new_log();
729        let leaf = |tag| {
730            let widget = arm(tag, 1.0, 1.0, &log).build(&mut BuildCtx::new(&mut 0u64));
731            ChildPod::new(Box::new(widget))
732        };
733        FocusProbe {
734            focusable,
735            pods: [leaf("x"), leaf("y")],
736        }
737    }
738
739    /// `(is_focusable, children visited, focus children visited)` for `widget`.
740    fn focus_face(widget: &dyn Widget) -> (bool, usize, usize) {
741        let (mut all, mut focus) = (0, 0);
742        widget.visit_children(&mut |_| all += 1);
743        widget.visit_focus_children(&mut |_| focus += 1);
744        (widget.is_focusable(), all, focus)
745    }
746
747    #[test]
748    fn focus_queries_forward_to_the_live_arm() {
749        type Two = EitherWidget<FocusProbe, FocusProbe>;
750        assert_eq!(focus_face(&Two::Left(probe(true))), (true, 2, 1));
751        assert_eq!(focus_face(&Two::Right(probe(false))), (false, 2, 1));
752        type Three = EitherOf3Widget<FocusProbe, FocusProbe, FocusProbe>;
753        assert_eq!(focus_face(&Three::C(probe(true))), (true, 2, 1));
754        assert_eq!(focus_face(&Three::A(probe(false))), (false, 2, 1));
755        type Eight = EitherOf8Widget<
756            ArmWidget,
757            ArmWidget,
758            ArmWidget,
759            ArmWidget,
760            ArmWidget,
761            ArmWidget,
762            ArmWidget,
763            FocusProbe,
764        >;
765        assert_eq!(focus_face(&Eight::H(probe(true))), (true, 2, 1));
766    }
767
768    #[test]
769    fn tab_reaches_a_control_behind_either() {
770        let mut root: RenderRoot<u32, crate::FlexView<u32>> = RenderRoot::new();
771        let mut logic = |_: &mut u32| {
772            let arm = either(
773                true,
774                || button::<u32, _>("Go", |n: &mut u32| *n += 1),
775                || text("idle"),
776            );
777            crate::column().child(arm)
778        };
779        let mut presses = 0u32;
780        let mut tcx = TextContext::new();
781        root.rebuild(&mut logic, &mut presses);
782        root.layout_with_text(Size::new(200.0, 200.0), &mut tcx as &mut dyn std::any::Any);
783        root.paint(&mut DiscardScene, FrameTime::from_nanos(0));
784        let key = |key| {
785            InputEvent::Key(frust_core::KeyEvent {
786                key,
787                modifiers: frust_core::Modifiers::default(),
788                repeat: false,
789            })
790        };
791        root.event(
792            &mut presses,
793            &key(frust_core::Key::Named(frust_core::NamedKey::Tab)),
794        );
795        assert!(
796            root.is_focus_active(),
797            "the enum's pod is the button's stop"
798        );
799        root.event(&mut presses, &key(frust_core::Key::Character(" ".into())));
800        assert_eq!(presses, 1);
801    }
802
803    #[test]
804    fn eight_variant_instance_swaps_a_to_h() {
805        type Eight = EitherOf8<Arm, Arm, Arm, Arm, Arm, Arm, Arm, Arm>;
806        let log = new_log();
807        let mut counter = 0u64;
808        let mut ctx = BuildCtx::new(&mut counter);
809        let a: Eight = EitherOf8::A(arm("a", 1.0, 1.0, &log));
810        let h: Eight = EitherOf8::H(arm("h", 5.0, 5.0, &log));
811        let mut el = a.build(&mut ctx);
812        assert!(matches!(el, EitherOf8Widget::A(_)));
813        let flags = h.rebuild(&a, &mut el, &mut ctx);
814        assert_eq!(flags, ChangeFlags::LAYOUT | ChangeFlags::PAINT);
815        assert!(matches!(el, EitherOf8Widget::H(_)));
816        assert_eq!(drain(&log), ["build a", "teardown a", "build h", "drop a"]);
817        h.teardown(&mut el, &mut ctx);
818        assert_eq!(drain(&log), ["teardown h"]);
819    }
820}