1use 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
61pub enum Either<L, R> {
63 Left(L),
65 Right(R),
67}
68
69pub 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
85pub enum EitherWidget<L, R> {
89 Left(L),
91 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 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 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 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 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 prev.teardown(element, ctx);
190 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
223macro_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 fn type_name(&self) -> &'static str {
269 match self { $(Self::$V(w) => w.type_name(),)+ }
270 }
271
272 fn as_sliver(&mut self) -> Option<&mut dyn SliverWidget> {
274 match self { $(Self::$V(w) => w.as_sliver(),)+ }
275 }
276
277 fn is_focusable(&self) -> bool {
279 match self { $(Self::$V(w) => w.is_focusable(),)+ }
280 }
281
282 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 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 "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 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 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 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 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}