1use frust_core::{
56 BoxConstraints, BuildCtx, ChangeFlags, ChildPod, EventCtx, EventResult, InputEvent, LayoutCtx,
57 PaintCtx, PaintScene, SemanticsCtx, 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
140impl<State: 'static, L: View<State>, R: View<State>> View<State> for Either<L, R> {
141 type Element = EitherWidget<L::Element, R::Element>;
142
143 fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element {
144 match self {
145 Self::Left(v) => EitherWidget::Left(v.build(ctx)),
146 Self::Right(v) => EitherWidget::Right(v.build(ctx)),
147 }
148 }
149
150 fn rebuild(
151 &self,
152 prev: &Self,
153 element: &mut Self::Element,
154 ctx: &mut BuildCtx<'_>,
155 ) -> ChangeFlags {
156 match (self, prev, &mut *element) {
157 (Self::Left(v), Self::Left(p), EitherWidget::Left(w)) => v.rebuild(p, w, ctx),
158 (Self::Right(v), Self::Right(p), EitherWidget::Right(w)) => v.rebuild(p, w, ctx),
159 _ => {
160 prev.teardown(element, ctx);
163 ctx.mark_element_replaced();
170 if ctx.has_focus() {
171 mark_focus_orphaned();
172 }
173 *element = self.build(ctx);
174 ChangeFlags::LAYOUT | ChangeFlags::PAINT
175 }
176 }
177 }
178
179 fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
180 match (self, element) {
181 (Self::Left(v), EitherWidget::Left(w)) => v.teardown(w, ctx),
182 (Self::Right(v), EitherWidget::Right(w)) => v.teardown(w, ctx),
183 _ => {}
184 }
185 }
186
187 fn message(&self, element: &mut Self::Element, state: &mut State) {
188 match (self, element) {
189 (Self::Left(v), EitherWidget::Left(w)) => v.message(w, state),
190 (Self::Right(v), EitherWidget::Right(w)) => v.message(w, state),
191 _ => {}
192 }
193 }
194}
195
196macro_rules! either_of {
199 ($(#[$meta:meta])* $arity:literal, $E:ident, $W:ident; $($V:ident),+ $(,)?) => {
200 $(#[$meta])*
201 #[doc = ""]
202 #[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).")]
203 pub enum $E<$($V),+> {
204 $(
205 #[doc = concat!("Arm `", stringify!($V), "`.")]
206 $V($V),
207 )+
208 }
209
210 #[doc = concat!("Retained widget for [`", stringify!($E), "`]: the live arm's widget, delegated to for every pass.")]
211 pub enum $W<$($V),+> {
212 $(
213 #[doc = concat!("Arm `", stringify!($V), "`'s widget.")]
214 $V($V),
215 )+
216 }
217
218 impl<$($V: Widget),+> Widget for $W<$($V),+> {
219 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
220 match self { $(Self::$V(w) => w.layout(ctx, bc),)+ }
221 }
222
223 fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
224 match self { $(Self::$V(w) => w.paint(ctx, scene),)+ }
225 }
226
227 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
228 match self { $(Self::$V(w) => w.event(ctx, event),)+ }
229 }
230
231 fn semantics(&self, ctx: &mut SemanticsCtx) {
232 match self { $(Self::$V(w) => w.semantics(ctx),)+ }
233 }
234
235 fn visit_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
236 match self { $(Self::$V(w) => w.visit_children(visitor),)+ }
237 }
238
239 fn type_name(&self) -> &'static str {
242 match self { $(Self::$V(w) => w.type_name(),)+ }
243 }
244 }
245
246 impl<State: 'static, $($V: View<State>),+> View<State> for $E<$($V),+> {
247 type Element = $W<$($V::Element),+>;
248
249 fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element {
250 match self { $(Self::$V(v) => $W::$V(v.build(ctx)),)+ }
251 }
252
253 fn rebuild(
254 &self,
255 prev: &Self,
256 element: &mut Self::Element,
257 ctx: &mut BuildCtx<'_>,
258 ) -> ChangeFlags {
259 match (self, prev, &mut *element) {
260 $((Self::$V(v), Self::$V(p), $W::$V(w)) => v.rebuild(p, w, ctx),)+
261 _ => {
262 prev.teardown(element, ctx);
264 ctx.mark_element_replaced();
265 if ctx.has_focus() {
266 mark_focus_orphaned();
267 }
268 *element = self.build(ctx);
269 ChangeFlags::LAYOUT | ChangeFlags::PAINT
270 }
271 }
272 }
273
274 fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
275 match (self, element) {
276 $((Self::$V(v), $W::$V(w)) => v.teardown(w, ctx),)+
277 _ => {}
278 }
279 }
280
281 fn message(&self, element: &mut Self::Element, state: &mut State) {
282 match (self, element) {
283 $((Self::$V(v), $W::$V(w)) => v.message(w, state),)+
284 _ => {}
285 }
286 }
287 }
288 };
289}
290
291either_of! {
292 "three", EitherOf3, EitherOf3Widget; A, B, C
322}
323either_of! { "four", EitherOf4, EitherOf4Widget; A, B, C, D }
324either_of! { "five", EitherOf5, EitherOf5Widget; A, B, C, D, E }
325either_of! { "six", EitherOf6, EitherOf6Widget; A, B, C, D, E, F }
326either_of! { "seven", EitherOf7, EitherOf7Widget; A, B, C, D, E, F, G }
327either_of! { "eight", EitherOf8, EitherOf8Widget; A, B, C, D, E, F, G, H }
328
329#[cfg(test)]
330mod tests {
331 use std::cell::RefCell;
332 use std::rc::Rc;
333
334 use frust_core::accesskit::Role;
335 use frust_core::{DiscardScene, FrameTime, RenderRoot, SemanticsUpdate, take_focus_orphaned};
336 use frust_text::TextContext;
337
338 use super::*;
339 use crate::{ButtonView, CheckboxView, button, checkbox};
340
341 type Log = Rc<RefCell<Vec<String>>>;
342
343 struct Arm {
345 tag: &'static str,
346 size: Size,
347 log: Log,
348 }
349
350 struct ArmWidget {
351 tag: &'static str,
352 size: Size,
353 log: Log,
354 painted: u32,
355 }
356
357 impl Drop for ArmWidget {
358 fn drop(&mut self) {
359 self.log.borrow_mut().push(format!("drop {}", self.tag));
360 }
361 }
362
363 impl View<()> for Arm {
364 type Element = ArmWidget;
365 fn build(&self, _ctx: &mut BuildCtx<'_>) -> ArmWidget {
366 self.log.borrow_mut().push(format!("build {}", self.tag));
367 ArmWidget {
368 tag: self.tag,
369 size: self.size,
370 log: Rc::clone(&self.log),
371 painted: 0,
372 }
373 }
374 fn rebuild(
375 &self,
376 _prev: &Self,
377 el: &mut ArmWidget,
378 _ctx: &mut BuildCtx<'_>,
379 ) -> ChangeFlags {
380 el.size = self.size;
381 ChangeFlags::NONE
382 }
383 fn teardown(&self, _el: &mut ArmWidget, _ctx: &mut BuildCtx<'_>) {
384 self.log.borrow_mut().push(format!("teardown {}", self.tag));
385 }
386 }
387
388 impl Widget for ArmWidget {
389 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
390 bc.constrain(self.size)
391 }
392 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {
393 self.painted += 1;
394 self.log
395 .borrow_mut()
396 .push(format!("paint {} #{}", self.tag, self.painted));
397 }
398 }
399
400 fn arm(tag: &'static str, w: f64, h: f64, log: &Log) -> Arm {
401 Arm {
402 tag,
403 size: Size::new(w, h),
404 log: Rc::clone(log),
405 }
406 }
407
408 fn new_log() -> Log {
409 Rc::new(RefCell::new(Vec::new()))
410 }
411
412 fn drain(log: &Log) -> Vec<String> {
413 std::mem::take(&mut *log.borrow_mut())
414 }
415
416 #[test]
417 fn helper_is_lazy_and_picks_the_arm() {
418 let built = Rc::new(RefCell::new(Vec::new()));
419 let (b1, b2) = (Rc::clone(&built), Rc::clone(&built));
420 let v = either(
421 true,
422 move || {
423 b1.borrow_mut().push("l");
424 1u8
425 },
426 move || {
427 b2.borrow_mut().push("r");
428 2u8
429 },
430 );
431 assert!(matches!(v, Either::Left(1)));
432 assert_eq!(*built.borrow(), vec!["l"], "only the chosen arm runs");
433 assert!(matches!(either(false, || 1u8, || 2u8), Either::Right(2)));
434 }
435
436 macro_rules! swap_matrix {
439 ($m:ident, $E:ident, $W:ident, $a:ident, $b:ident, $arms:ty, $ui:ty) => {
440 mod $m {
441 use super::*;
442
443 type Arms = $arms;
444 type Ui = $ui;
445
446 fn pick(first: bool, log: &Log, sa: (f64, f64), sb: (f64, f64)) -> Arms {
447 if first {
448 $E::$a(arm("a", sa.0, sa.1, log))
449 } else {
450 $E::$b(arm("b", sb.0, sb.1, log))
451 }
452 }
453
454 #[test]
455 fn layout_and_paint_delegate_to_the_live_variant() {
456 let log = new_log();
457 let mut root: RenderRoot<(), Arms> = RenderRoot::new();
458 let mut state = ();
459 let mut logic = |_: &mut ()| pick(false, &log, (10.0, 10.0), (30.0, 20.0));
460 root.rebuild(&mut logic, &mut state);
461 root.layout(Size::new(200.0, 200.0));
462 root.paint(&mut DiscardScene, FrameTime::from_nanos(0));
463 let nodes = root.inspect();
464 assert!(
465 nodes[0].type_name.contains(stringify!($W)),
466 "root is the enum widget, got {}",
467 nodes[0].type_name
468 );
469 assert_eq!(nodes[0].bounds.size(), Size::new(30.0, 20.0));
470 let seen = drain(&log);
471 assert!(seen.contains(&"build b".to_string()));
472 assert!(seen.contains(&"paint b #1".to_string()));
473 assert!(
474 !seen.iter().any(|e| e.contains(" a")),
475 "inactive variant untouched: {seen:?}"
476 );
477 }
478
479 #[test]
480 fn same_variant_rebuild_keeps_state_swap_resets_it() {
481 let log = new_log();
482 let mut root: RenderRoot<(), Arms> = RenderRoot::new();
483 let mut state = ();
484 let first = std::cell::Cell::new(true);
485 let mut logic =
486 |_: &mut ()| pick(first.get(), &log, (10.0, 10.0), (10.0, 10.0));
487 root.rebuild(&mut logic, &mut state);
488 root.layout(Size::new(100.0, 100.0));
489 root.paint(&mut DiscardScene, FrameTime::from_nanos(0));
490 root.paint(&mut DiscardScene, FrameTime::from_nanos(1));
491 assert_eq!(drain(&log), ["build a", "paint a #1", "paint a #2"]);
492
493 root.rebuild(&mut logic, &mut state);
494 root.layout(Size::new(100.0, 100.0));
495 root.paint(&mut DiscardScene, FrameTime::from_nanos(2));
496 assert_eq!(drain(&log), ["paint a #3"]);
497
498 first.set(false);
499 root.rebuild(&mut logic, &mut state);
500 root.layout(Size::new(100.0, 100.0));
501 root.paint(&mut DiscardScene, FrameTime::from_nanos(3));
502 assert_eq!(
503 drain(&log),
504 ["teardown a", "build b", "drop a", "paint b #1"]
505 );
506
507 first.set(true);
508 root.rebuild(&mut logic, &mut state);
509 root.paint(&mut DiscardScene, FrameTime::from_nanos(4));
510 assert_eq!(
511 drain(&log),
512 ["teardown b", "build a", "drop b", "paint a #1"]
513 );
514 }
515
516 #[test]
517 fn swap_reports_full_rebuild_flags_and_same_variant_does_not() {
518 let log = new_log();
519 let mut counter = 0u64;
520 let mut ctx = BuildCtx::new(&mut counter);
521 let prev = pick(true, &log, (1.0, 1.0), (1.0, 1.0));
522 let mut el = prev.build(&mut ctx);
523 let same = pick(true, &log, (1.0, 1.0), (1.0, 1.0));
524 assert_eq!(same.rebuild(&prev, &mut el, &mut ctx), ChangeFlags::NONE);
525 let next = pick(false, &log, (1.0, 1.0), (1.0, 1.0));
526 let flags = next.rebuild(&same, &mut el, &mut ctx);
527 assert_eq!(flags, ChangeFlags::LAYOUT | ChangeFlags::PAINT);
528 assert!(matches!(el, $W::$b(_)));
529 }
530
531 #[test]
532 fn an_arm_swap_reports_element_replaced_and_same_variant_does_not() {
533 let log = new_log();
534 let mut counter = 0u64;
535 let mut ctx = BuildCtx::new(&mut counter);
536 let prev = pick(true, &log, (1.0, 1.0), (1.0, 1.0));
537 let mut el = prev.build(&mut ctx);
538 let same = pick(true, &log, (2.0, 2.0), (1.0, 1.0));
539 let (_, replaced) =
540 ctx.track_element_replaced(|ctx| same.rebuild(&prev, &mut el, ctx));
541 assert!(!replaced, "a same-variant rebuild keeps the widget");
542 let next = pick(false, &log, (1.0, 1.0), (1.0, 1.0));
543 let (_, replaced) =
544 ctx.track_element_replaced(|ctx| next.rebuild(&same, &mut el, ctx));
545 assert!(replaced, "an arm swap reports the replaced widget");
546 }
547
548 #[test]
549 fn swap_under_a_live_focus_chain_orphans_focus_only_then() {
550 let log = new_log();
551 let mut counter = 0u64;
552 let mut ctx = BuildCtx::new(&mut counter);
553 let a = pick(true, &log, (1.0, 1.0), (1.0, 1.0));
554 let b = pick(false, &log, (1.0, 1.0), (1.0, 1.0));
555 let _ = take_focus_orphaned();
556
557 ctx.set_has_focus(false);
558 let mut el = a.build(&mut ctx);
559 b.rebuild(&a, &mut el, &mut ctx);
560 assert!(!take_focus_orphaned(), "unfocused swap marks nothing");
561
562 ctx.set_has_focus(true);
563 let b2 = pick(false, &log, (1.0, 1.0), (1.0, 1.0));
564 b2.rebuild(&b, &mut el, &mut ctx);
565 assert!(!take_focus_orphaned(), "same-variant rebuild marks nothing");
566
567 a.rebuild(&b2, &mut el, &mut ctx);
568 assert!(take_focus_orphaned(), "focused swap orphans focus");
569 }
570
571 fn ui(show_button: bool) -> Ui {
572 if show_button {
573 $E::$a(button::<(), _>("Go", |_| {}))
574 } else {
575 $E::$b(checkbox::<(), _>(true, "Agree", |_, _| {}))
576 }
577 }
578
579 fn roles(u: &SemanticsUpdate) -> Vec<Role> {
580 u.nodes.iter().map(|(_, n)| n.role()).collect()
581 }
582
583 #[test]
584 fn semantics_reflect_the_live_variant_and_follow_a_swap() {
585 let mut root: RenderRoot<(), Ui> = RenderRoot::new();
586 let mut state = ();
587 let show_button = std::cell::Cell::new(true);
588 let mut logic = |_: &mut ()| ui(show_button.get());
589 let mut tcx = TextContext::new();
590 root.rebuild(&mut logic, &mut state);
591 root.layout_with_text(
592 Size::new(300.0, 300.0),
593 &mut tcx as &mut dyn std::any::Any,
594 );
595 let before = root.semantics();
596 let top = &before
597 .nodes
598 .iter()
599 .find(|(id, _)| *id == before.root)
600 .unwrap()
601 .1;
602 assert_eq!(
603 top.children().len(),
604 1,
605 "exactly the live variant contributes"
606 );
607 show_button.set(false);
608 root.rebuild(&mut logic, &mut state);
609 root.layout_with_text(
610 Size::new(300.0, 300.0),
611 &mut tcx as &mut dyn std::any::Any,
612 );
613 let after = root.semantics();
614 assert!(roles(&before).contains(&Role::Button));
615 assert!(!roles(&before).contains(&Role::CheckBox));
616 assert!(roles(&after).contains(&Role::CheckBox));
617 assert!(!roles(&after).contains(&Role::Button));
618 }
619 }
620 };
621 }
622
623 swap_matrix!(
624 either_matrix,
625 Either,
626 EitherWidget,
627 Left,
628 Right,
629 Either<Arm, Arm>,
630 Either<ButtonView<()>, CheckboxView<()>>
631 );
632 swap_matrix!(
633 either_of_3_matrix,
634 EitherOf3,
635 EitherOf3Widget,
636 A,
637 B,
638 EitherOf3<Arm, Arm, Arm>,
639 EitherOf3<ButtonView<()>, CheckboxView<()>, ButtonView<()>>
640 );
641 swap_matrix!(
642 either_of_8_matrix,
643 EitherOf8,
644 EitherOf8Widget,
645 A,
646 B,
647 EitherOf8<Arm, Arm, Arm, Arm, Arm, Arm, Arm, Arm>,
648 EitherOf8<
649 ButtonView<()>,
650 CheckboxView<()>,
651 ButtonView<()>,
652 ButtonView<()>,
653 ButtonView<()>,
654 ButtonView<()>,
655 ButtonView<()>,
656 ButtonView<()>,
657 >
658 );
659
660 #[test]
661 fn eight_variant_instance_swaps_a_to_h() {
662 type Eight = EitherOf8<Arm, Arm, Arm, Arm, Arm, Arm, Arm, Arm>;
663 let log = new_log();
664 let mut counter = 0u64;
665 let mut ctx = BuildCtx::new(&mut counter);
666 let a: Eight = EitherOf8::A(arm("a", 1.0, 1.0, &log));
667 let h: Eight = EitherOf8::H(arm("h", 5.0, 5.0, &log));
668 let mut el = a.build(&mut ctx);
669 assert!(matches!(el, EitherOf8Widget::A(_)));
670 let flags = h.rebuild(&a, &mut el, &mut ctx);
671 assert_eq!(flags, ChangeFlags::LAYOUT | ChangeFlags::PAINT);
672 assert!(matches!(el, EitherOf8Widget::H(_)));
673 assert_eq!(drain(&log), ["build a", "teardown a", "build h", "drop a"]);
674 h.teardown(&mut el, &mut ctx);
675 assert_eq!(drain(&log), ["teardown h"]);
676 }
677}