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> {
88 Left(L),
90 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 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 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 prev.teardown(element, ctx);
172 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
205macro_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 fn type_name(&self) -> &'static str {
251 match self { $(Self::$V(w) => w.type_name(),)+ }
252 }
253
254 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 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 "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 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 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}