1use frust_core::{
39 BoxConstraints, BuildCtx, ChangeFlags, ChildPod, EventCtx, EventResult, InputEvent, LayoutCtx,
40 PaintCtx, PaintScene, SemanticsCtx, View, Widget, mark_focus_orphaned,
41};
42use kurbo::Size;
43
44pub enum Either<L, R> {
46 Left(L),
48 Right(R),
50}
51
52pub fn either<L, R>(cond: bool, l: impl FnOnce() -> L, r: impl FnOnce() -> R) -> Either<L, R> {
59 if cond {
60 Either::Left(l())
61 } else {
62 Either::Right(r())
63 }
64}
65
66pub enum EitherWidget<L, R> {
69 Left(L),
71 Right(R),
73}
74
75impl<L: Widget, R: Widget> Widget for EitherWidget<L, R> {
76 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
77 match self {
78 Self::Left(w) => w.layout(ctx, bc),
79 Self::Right(w) => w.layout(ctx, bc),
80 }
81 }
82
83 fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
84 match self {
85 Self::Left(w) => w.paint(ctx, scene),
86 Self::Right(w) => w.paint(ctx, scene),
87 }
88 }
89
90 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
91 match self {
92 Self::Left(w) => w.event(ctx, event),
93 Self::Right(w) => w.event(ctx, event),
94 }
95 }
96
97 fn semantics(&self, ctx: &mut SemanticsCtx) {
98 match self {
99 Self::Left(w) => w.semantics(ctx),
100 Self::Right(w) => w.semantics(ctx),
101 }
102 }
103
104 fn visit_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
105 match self {
106 Self::Left(w) => w.visit_children(visitor),
107 Self::Right(w) => w.visit_children(visitor),
108 }
109 }
110
111 fn type_name(&self) -> &'static str {
114 match self {
115 Self::Left(w) => w.type_name(),
116 Self::Right(w) => w.type_name(),
117 }
118 }
119}
120
121impl<State: 'static, L: View<State>, R: View<State>> View<State> for Either<L, R> {
122 type Element = EitherWidget<L::Element, R::Element>;
123
124 fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element {
125 match self {
126 Self::Left(v) => EitherWidget::Left(v.build(ctx)),
127 Self::Right(v) => EitherWidget::Right(v.build(ctx)),
128 }
129 }
130
131 fn rebuild(
132 &self,
133 prev: &Self,
134 element: &mut Self::Element,
135 ctx: &mut BuildCtx<'_>,
136 ) -> ChangeFlags {
137 match (self, prev, &mut *element) {
138 (Self::Left(v), Self::Left(p), EitherWidget::Left(w)) => v.rebuild(p, w, ctx),
139 (Self::Right(v), Self::Right(p), EitherWidget::Right(w)) => v.rebuild(p, w, ctx),
140 _ => {
141 prev.teardown(element, ctx);
144 if ctx.has_focus() {
148 mark_focus_orphaned();
149 }
150 *element = self.build(ctx);
151 ChangeFlags::LAYOUT | ChangeFlags::PAINT
152 }
153 }
154 }
155
156 fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
157 match (self, element) {
158 (Self::Left(v), EitherWidget::Left(w)) => v.teardown(w, ctx),
159 (Self::Right(v), EitherWidget::Right(w)) => v.teardown(w, ctx),
160 _ => {}
161 }
162 }
163
164 fn message(&self, element: &mut Self::Element, state: &mut State) {
165 match (self, element) {
166 (Self::Left(v), EitherWidget::Left(w)) => v.message(w, state),
167 (Self::Right(v), EitherWidget::Right(w)) => v.message(w, state),
168 _ => {}
169 }
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use std::cell::RefCell;
176 use std::rc::Rc;
177
178 use frust_core::accesskit::Role;
179 use frust_core::{DiscardScene, FrameTime, RenderRoot, SemanticsUpdate, take_focus_orphaned};
180 use frust_text::TextContext;
181
182 use super::*;
183 use crate::{ButtonView, CheckboxView, button, checkbox};
184
185 type Log = Rc<RefCell<Vec<String>>>;
186
187 struct Arm {
189 tag: &'static str,
190 size: Size,
191 log: Log,
192 }
193
194 struct ArmWidget {
195 tag: &'static str,
196 size: Size,
197 log: Log,
198 painted: u32,
199 }
200
201 impl Drop for ArmWidget {
202 fn drop(&mut self) {
203 self.log.borrow_mut().push(format!("drop {}", self.tag));
204 }
205 }
206
207 impl View<()> for Arm {
208 type Element = ArmWidget;
209 fn build(&self, _ctx: &mut BuildCtx<'_>) -> ArmWidget {
210 self.log.borrow_mut().push(format!("build {}", self.tag));
211 ArmWidget {
212 tag: self.tag,
213 size: self.size,
214 log: Rc::clone(&self.log),
215 painted: 0,
216 }
217 }
218 fn rebuild(
219 &self,
220 _prev: &Self,
221 el: &mut ArmWidget,
222 _ctx: &mut BuildCtx<'_>,
223 ) -> ChangeFlags {
224 el.size = self.size;
225 ChangeFlags::NONE
226 }
227 fn teardown(&self, _el: &mut ArmWidget, _ctx: &mut BuildCtx<'_>) {
228 self.log.borrow_mut().push(format!("teardown {}", self.tag));
229 }
230 }
231
232 impl Widget for ArmWidget {
233 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
234 bc.constrain(self.size)
235 }
236 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {
237 self.painted += 1;
238 self.log
239 .borrow_mut()
240 .push(format!("paint {} #{}", self.tag, self.painted));
241 }
242 }
243
244 fn arm(tag: &'static str, w: f64, h: f64, log: &Log) -> Arm {
245 Arm {
246 tag,
247 size: Size::new(w, h),
248 log: Rc::clone(log),
249 }
250 }
251
252 fn new_log() -> Log {
253 Rc::new(RefCell::new(Vec::new()))
254 }
255
256 fn drain(log: &Log) -> Vec<String> {
257 std::mem::take(&mut *log.borrow_mut())
258 }
259
260 type TwoArms = Either<Arm, Arm>;
261
262 #[test]
263 fn helper_is_lazy_and_picks_the_arm() {
264 let built = Rc::new(RefCell::new(Vec::new()));
265 let (b1, b2) = (Rc::clone(&built), Rc::clone(&built));
266 let v = either(
267 true,
268 move || {
269 b1.borrow_mut().push("l");
270 1u8
271 },
272 move || {
273 b2.borrow_mut().push("r");
274 2u8
275 },
276 );
277 assert!(matches!(v, Either::Left(1)));
278 assert_eq!(*built.borrow(), vec!["l"], "only the chosen arm runs");
279 assert!(matches!(either(false, || 1u8, || 2u8), Either::Right(2)));
280 }
281
282 #[test]
283 fn layout_and_paint_delegate_to_the_active_arm() {
284 let log = new_log();
285 let mut root: RenderRoot<(), TwoArms> = RenderRoot::new();
286 let mut state = ();
287 let mut logic = |_: &mut ()| {
288 either(
289 false,
290 || arm("a", 10.0, 10.0, &log),
291 || arm("b", 30.0, 20.0, &log),
292 )
293 };
294 root.rebuild(&mut logic, &mut state);
295 root.layout(Size::new(200.0, 200.0));
296 root.paint(&mut DiscardScene, FrameTime::from_nanos(0));
297 let nodes = root.inspect();
298 assert!(
299 nodes[0].type_name.contains("EitherWidget"),
300 "root is the either widget, got {}",
301 nodes[0].type_name
302 );
303 assert_eq!(nodes[0].bounds.size(), Size::new(30.0, 20.0));
304 let seen = drain(&log);
305 assert!(seen.contains(&"build b".to_string()));
306 assert!(seen.contains(&"paint b #1".to_string()));
307 assert!(
308 !seen.iter().any(|e| e.contains(" a")),
309 "inactive arm untouched: {seen:?}"
310 );
311 }
312
313 #[test]
314 fn same_arm_rebuild_keeps_state_swap_resets_it() {
315 let log = new_log();
316 let mut root: RenderRoot<(), TwoArms> = RenderRoot::new();
317 let mut state = ();
318 let left = std::cell::Cell::new(true);
319 let mut logic = |_: &mut ()| {
320 either(
321 left.get(),
322 || arm("a", 10.0, 10.0, &log),
323 || arm("b", 10.0, 10.0, &log),
324 )
325 };
326 root.rebuild(&mut logic, &mut state);
327 root.layout(Size::new(100.0, 100.0));
328 root.paint(&mut DiscardScene, FrameTime::from_nanos(0));
329 root.paint(&mut DiscardScene, FrameTime::from_nanos(1));
330 assert_eq!(drain(&log), ["build a", "paint a #1", "paint a #2"]);
331
332 root.rebuild(&mut logic, &mut state);
334 root.layout(Size::new(100.0, 100.0));
335 root.paint(&mut DiscardScene, FrameTime::from_nanos(2));
336 assert_eq!(drain(&log), ["paint a #3"]);
337
338 left.set(false);
340 root.rebuild(&mut logic, &mut state);
341 root.layout(Size::new(100.0, 100.0));
342 root.paint(&mut DiscardScene, FrameTime::from_nanos(3));
343 assert_eq!(
344 drain(&log),
345 ["teardown a", "build b", "drop a", "paint b #1"]
346 );
347
348 left.set(true);
350 root.rebuild(&mut logic, &mut state);
351 root.paint(&mut DiscardScene, FrameTime::from_nanos(4));
352 assert_eq!(
353 drain(&log),
354 ["teardown b", "build a", "drop b", "paint a #1"]
355 );
356 }
357
358 #[test]
359 fn swap_reports_full_rebuild_flags_and_same_arm_does_not() {
360 let log = new_log();
361 let mut counter = 0u64;
362 let mut ctx = BuildCtx::new(&mut counter);
363 let prev: TwoArms = Either::Left(arm("a", 1.0, 1.0, &log));
364 let mut el = prev.build(&mut ctx);
365 let same: TwoArms = Either::Left(arm("a", 1.0, 1.0, &log));
366 assert_eq!(same.rebuild(&prev, &mut el, &mut ctx), ChangeFlags::NONE);
367 let next: TwoArms = Either::Right(arm("b", 1.0, 1.0, &log));
368 let flags = next.rebuild(&same, &mut el, &mut ctx);
369 assert_eq!(flags, ChangeFlags::LAYOUT | ChangeFlags::PAINT);
370 assert!(matches!(el, EitherWidget::Right(_)));
371 }
372
373 #[test]
374 fn swap_under_a_live_focus_chain_orphans_focus_only_then() {
375 let log = new_log();
376 let mut counter = 0u64;
377 let mut ctx = BuildCtx::new(&mut counter);
378 let a: TwoArms = Either::Left(arm("a", 1.0, 1.0, &log));
379 let b: TwoArms = Either::Right(arm("b", 1.0, 1.0, &log));
380 let _ = take_focus_orphaned();
381
382 ctx.set_has_focus(false);
384 let mut el = a.build(&mut ctx);
385 b.rebuild(&a, &mut el, &mut ctx);
386 assert!(!take_focus_orphaned(), "unfocused swap marks nothing");
387
388 ctx.set_has_focus(true);
390 let b2: TwoArms = Either::Right(arm("b", 1.0, 1.0, &log));
391 b2.rebuild(&b, &mut el, &mut ctx);
392 assert!(!take_focus_orphaned(), "same-arm rebuild marks nothing");
393
394 a.rebuild(&b2, &mut el, &mut ctx);
396 assert!(take_focus_orphaned(), "focused swap orphans focus");
397 }
398
399 type Ui = Either<ButtonView<()>, CheckboxView<()>>;
400
401 fn semantics_for(show_button: bool) -> SemanticsUpdate {
402 let mut root: RenderRoot<(), Ui> = RenderRoot::new();
403 let mut state = ();
404 let mut logic = |_: &mut ()| -> Ui {
405 either(
406 show_button,
407 || button::<(), _>("Go", |_| {}),
408 || checkbox::<(), _>(true, "Agree", |_, _| {}),
409 )
410 };
411 root.rebuild(&mut logic, &mut state);
412 let mut tcx = TextContext::new();
413 root.layout_with_text(Size::new(300.0, 300.0), &mut tcx as &mut dyn std::any::Any);
414 root.semantics()
415 }
416
417 #[test]
418 fn semantics_tree_reflects_the_active_arm() {
419 let role_of_child = |u: &SemanticsUpdate| {
420 let root = &u.nodes.iter().find(|(id, _)| *id == u.root).unwrap().1;
421 let kids = root.children();
422 assert_eq!(kids.len(), 1, "exactly the live arm contributes");
423 u.nodes
424 .iter()
425 .find(|(id, _)| *id == kids[0])
426 .unwrap()
427 .1
428 .role()
429 };
430 assert_eq!(role_of_child(&semantics_for(true)), Role::Button);
431 assert_eq!(role_of_child(&semantics_for(false)), Role::CheckBox);
432 }
433
434 #[test]
435 fn semantics_follow_a_live_swap() {
436 let mut root: RenderRoot<(), Ui> = RenderRoot::new();
437 let mut state = ();
438 let show_button = std::cell::Cell::new(true);
439 let mut logic = |_: &mut ()| -> Ui {
440 either(
441 show_button.get(),
442 || button::<(), _>("Go", |_| {}),
443 || checkbox::<(), _>(true, "Agree", |_, _| {}),
444 )
445 };
446 let mut tcx = TextContext::new();
447 root.rebuild(&mut logic, &mut state);
448 root.layout_with_text(Size::new(300.0, 300.0), &mut tcx as &mut dyn std::any::Any);
449 let before = root.semantics();
450 show_button.set(false);
451 root.rebuild(&mut logic, &mut state);
452 root.layout_with_text(Size::new(300.0, 300.0), &mut tcx as &mut dyn std::any::Any);
453 let after = root.semantics();
454 let roles = |u: &SemanticsUpdate| u.nodes.iter().map(|(_, n)| n.role()).collect::<Vec<_>>();
455 assert!(roles(&before).contains(&Role::Button));
456 assert!(!roles(&before).contains(&Role::CheckBox));
457 assert!(roles(&after).contains(&Role::CheckBox));
458 assert!(!roles(&after).contains(&Role::Button));
459 }
460}