1use std::any::Any;
18
19use kurbo::{Point, Size};
20use reactive_graph::owner::Owner;
21
22use crate::event::{EventCtx, EventResult, InputEvent, PointerPhase};
23#[cfg(feature = "hotpatch")]
24use crate::hotpatch::SeamWitness;
25use crate::layout::BoxConstraints;
26use crate::sliver::{SliverConstraints, SliverGeometry, SliverWidget};
27use crate::view::{AnyView, BuildCtx, ChangeFlags, View};
28use crate::widget::{ChildPod, LayoutCtx, PaintCtx, PaintScene, Widget};
29
30pub trait Component: 'static {
37 type State: 'static;
39
40 fn init(&self) -> Self::State;
47
48 fn build(&self, state: &mut Self::State) -> impl View<Self::State>;
54}
55
56pub struct ComponentView<C: Component> {
61 component: C,
62}
63
64pub fn component<C: Component>(c: C) -> ComponentView<C> {
66 ComponentView { component: c }
67}
68
69#[cfg_attr(feature = "hotpatch", repr(C))]
81pub struct ComponentWidget<C: Component> {
82 #[cfg(feature = "hotpatch")]
84 witness: SeamWitness,
85 #[cfg(feature = "hotpatch")]
87 state: Option<Box<C::State>>,
88 #[cfg(not(feature = "hotpatch"))]
90 state: C::State,
91 prev: AnyView<C::State>,
92 child: ChildPod,
93 owner: Owner,
94 next_id: u64,
96 disposed: bool,
97}
98
99impl<C: Component> ComponentWidget<C> {
100 fn dispose(&mut self) {
102 if !self.disposed {
103 self.owner.cleanup();
104 self.disposed = true;
105 }
106 }
107
108 #[cfg(feature = "hotpatch")]
109 fn state_mut(&mut self) -> &mut C::State {
110 self.state
111 .as_deref_mut()
112 .expect("component state is present while the widget is live")
113 }
114
115 #[cfg(not(feature = "hotpatch"))]
116 fn state_mut(&mut self) -> &mut C::State {
117 &mut self.state
118 }
119
120 #[cfg(all(test, feature = "hotpatch"))]
121 fn state(&self) -> &C::State {
122 self.state
123 .as_deref()
124 .expect("component state is present while the widget is live")
125 }
126
127 #[cfg(all(test, not(feature = "hotpatch")))]
128 fn state(&self) -> &C::State {
129 &self.state
130 }
131
132 #[cfg(all(test, feature = "hotpatch"))]
135 fn set_witness_for_test(&mut self, witness: SeamWitness) {
136 self.witness = witness;
137 }
138}
139
140impl<C: Component> Drop for ComponentWidget<C> {
141 fn drop(&mut self) {
142 #[cfg(feature = "hotpatch")]
145 if crate::hotpatch::verify_witness::<C>(self.witness).is_err() {
146 std::mem::forget(self.state.take());
147 }
148 self.dispose();
150 }
151}
152
153impl<Outer: 'static, C: Component> View<Outer> for ComponentView<C> {
154 type Element = ComponentWidget<C>;
155
156 fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element {
157 let hints = ctx.shortcut_hints();
158 let owner = Owner::new();
159 #[cfg(feature = "hotpatch")]
160 let witness = SeamWitness::of::<C>();
161 let (state, prev, child, next_id) = owner.with(|| {
162 #[cfg(feature = "hotpatch")]
163 let mut state = Box::new(self.component.init());
164 #[cfg(not(feature = "hotpatch"))]
165 let mut state = self.component.init();
166 #[cfg(feature = "hotpatch")]
169 let view = crate::hotpatch::built_or_inert(crate::hotpatch::build_erased(
170 &self.component,
171 &mut *state,
172 witness,
173 ));
174 #[cfg(not(feature = "hotpatch"))]
175 let view = AnyView::new(self.component.build(&mut state));
176 let mut next_id = 0u64;
177 let mut inner = BuildCtx::new(&mut next_id).with_shortcut_hints(hints);
178 inner.set_has_focus(false);
181 let element: Box<dyn Widget> = view.build(&mut inner);
182 (state, view, ChildPod::new(Box::new(element)), next_id)
183 });
184 ComponentWidget {
185 #[cfg(feature = "hotpatch")]
186 witness,
187 #[cfg(feature = "hotpatch")]
188 state: Some(state),
189 #[cfg(not(feature = "hotpatch"))]
190 state,
191 prev,
192 child,
193 owner,
194 next_id,
195 disposed: false,
196 }
197 }
198
199 fn rebuild(
200 &self,
201 _prev: &Self,
202 element: &mut Self::Element,
203 ctx: &mut BuildCtx<'_>,
204 ) -> ChangeFlags {
205 let hints = ctx.shortcut_hints();
206 #[cfg(feature = "hotpatch")]
209 if crate::hotpatch::verify_witness::<C>(element.witness).is_err() {
210 return ChangeFlags::NONE;
211 }
212 let outer_has_focus = ctx.has_focus();
216 let owner = element.owner.clone();
217 owner.with(|| {
218 #[cfg(feature = "hotpatch")]
219 let new_view = {
220 let witness = element.witness;
221 match crate::hotpatch::build_erased(&self.component, element.state_mut(), witness) {
222 Ok(view) => view,
223 Err(_) => return ChangeFlags::NONE,
224 }
225 };
226 #[cfg(not(feature = "hotpatch"))]
227 let new_view = AnyView::new(self.component.build(element.state_mut()));
228 let child_focused = element.child.is_focused();
229 let (flags, swapped) = {
230 let boxed = element
231 .child
232 .widget_mut_for_rebuild()
233 .downcast_mut::<Box<dyn Widget>>()
234 .expect("component child element is a boxed AnyView widget");
235 let before = {
236 let any: &dyn Any = &**boxed;
237 any.type_id()
238 };
239 let mut inner = BuildCtx::new(&mut element.next_id).with_shortcut_hints(hints);
240 inner.set_has_focus(outer_has_focus && child_focused);
243 let (flags, replaced) = inner
247 .track_element_replaced(|inner| new_view.rebuild(&element.prev, boxed, inner));
248 let after = {
249 let any: &dyn Any = &**boxed;
250 any.type_id()
251 };
252 (flags, before != after || replaced)
253 };
254 if swapped {
255 element.child.set_active(false);
256 element.child.set_focused(false);
257 if outer_has_focus && child_focused {
262 crate::event::mark_focus_orphaned();
263 }
264 }
265 if swapped || flags.contains(ChangeFlags::LAYOUT) {
269 element.child.mark_layout_dirty();
270 } else if flags.contains(ChangeFlags::PAINT) {
271 element.child.mark_paint_dirty();
272 }
273 element.prev = new_view;
274 flags
275 })
276 }
277
278 fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
279 #[cfg(feature = "hotpatch")]
280 if crate::hotpatch::verify_witness::<C>(element.witness).is_err() {
281 return;
282 }
283 let hints = ctx.shortcut_hints();
284 let inner_has_focus = ctx.has_focus() && element.child.is_focused();
287 element.owner.clone().with(|| {
288 let mut next_id = element.next_id;
289 let mut inner = BuildCtx::new(&mut next_id).with_shortcut_hints(hints);
290 inner.set_has_focus(inner_has_focus);
291 if let Some(boxed) = element.child.widget_mut().downcast_mut::<Box<dyn Widget>>() {
292 element.prev.teardown(boxed, &mut inner);
293 }
294 });
295 element.dispose();
296 }
297}
298
299impl<C: Component> Widget for ComponentWidget<C> {
300 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
301 self.child.set_origin(Point::ZERO);
302 self.child.layout_child(ctx, bc)
303 }
304
305 fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
306 self.child.paint_child(ctx, scene);
307 }
308
309 fn semantics(&self, ctx: &mut crate::semantics::SemanticsCtx) {
310 self.child.semantics_child(ctx);
311 }
312
313 fn visit_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
314 visitor(&self.child);
315 }
316
317 fn as_sliver(&mut self) -> Option<&mut dyn SliverWidget> {
321 if self.child.is_sliver() {
322 Some(self)
323 } else {
324 None
325 }
326 }
327
328 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
329 let size = ctx.size();
334 let has_focus = ctx.has_focus();
335 let hovered = ctx.is_hovered();
338 let hover_epoch = ctx.hover_epoch();
339 let hover_eligible = ctx.is_hover_eligible();
340 let (result, needs_redraw, layout, captured, hover_claimed, focus_req, focus_rel, ime) = {
343 #[cfg(feature = "hotpatch")]
344 let state_any: &mut dyn Any = self
345 .state
346 .as_deref_mut()
347 .expect("component state is present while the widget is live");
348 #[cfg(not(feature = "hotpatch"))]
349 let state_any: &mut dyn Any = &mut self.state;
350 let mut inner = EventCtx::new(state_any, Point::ZERO, size);
351 inner.set_has_focus(has_focus);
352 inner.set_hovered(hovered);
353 inner.set_hover_epoch(hover_epoch);
354 inner.set_hover_eligible(hover_eligible);
355 let result = route_child(&mut self.child, &mut inner, event);
356 (
357 result,
358 inner.needs_redraw(),
359 (inner.needs_layout(), inner.is_layout_path_dirty()),
360 inner.is_pointer_captured(),
361 inner.is_hover_claimed(),
362 inner.is_focus_requested(),
363 inner.is_focus_released(),
364 inner.take_ime_state(),
365 )
366 };
367 if needs_redraw {
368 ctx.request_redraw();
369 }
370 ctx.absorb_layout(layout.0, layout.1);
371 if captured {
372 ctx.capture_pointer();
373 }
374 if hover_claimed {
375 ctx.claim_hover();
376 }
377 if focus_req {
378 ctx.request_focus();
379 }
380 if focus_rel {
381 ctx.release_focus();
382 }
383 if let Some(ime) = ime {
384 ctx.publish_ime_state(ime);
385 }
386 result
387 }
388}
389
390impl<C: Component> SliverWidget for ComponentWidget<C> {
391 fn layout_sliver(&mut self, ctx: &mut LayoutCtx, sc: &SliverConstraints) -> SliverGeometry {
393 self.child.set_origin(Point::ZERO);
394 self.child.layout_sliver_child(ctx, sc).unwrap_or_else(|| {
398 debug_assert!(
399 false,
400 "ComponentWidget::layout_sliver called on a wrapper whose as_sliver answered None"
401 );
402 SliverGeometry::ZERO
403 })
404 }
405}
406
407fn route_child(pod: &mut ChildPod, ctx: &mut EventCtx<'_>, event: &InputEvent) -> EventResult {
417 if event.is_broadcast() {
418 pod.event_child(ctx, event);
419 return EventResult::Ignored;
420 }
421 if event.is_focus_routed() {
422 if pod.is_focused() {
423 return pod.event_child(ctx, event);
424 }
425 return EventResult::Ignored;
426 }
427 if pod.is_active() {
428 let result = pod.event_child(ctx, event);
429 if releases_capture(event) {
430 pod.set_active(false);
431 }
432 return result;
433 }
434 let inside = pod.contains(event.position());
435 let result = if inside {
436 pod.event_child(ctx, event)
437 } else {
438 EventResult::Ignored
439 };
440 if is_pointer_down(event) && !inside && pod.is_focused() {
441 pod.set_focused(false);
442 }
443 result
444}
445
446fn releases_capture(event: &InputEvent) -> bool {
449 matches!(
450 event,
451 InputEvent::Pointer(p) if matches!(p.phase, PointerPhase::Up | PointerPhase::Cancel)
452 )
453}
454
455fn is_pointer_down(event: &InputEvent) -> bool {
457 matches!(
458 event,
459 InputEvent::Pointer(p) if matches!(p.phase, PointerPhase::Down)
460 )
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466 use crate::event::{
467 CursorIcon, EditingState, ImeState, Key, KeyEvent, Modifiers, NamedKey, PointerButton,
468 PointerEvent, clear_cursor_request, take_cursor_request,
469 };
470 use crate::view::any;
471 use kurbo::Rect;
472 use reactive_graph::owner::{on_cleanup, provide_context, use_context};
473 use std::cell::Cell;
474 use std::rc::Rc;
475 use std::sync::atomic::{AtomicU32, Ordering};
476 use std::sync::{Arc, Mutex};
477
478 fn pointer(phase: PointerPhase, x: f64, y: f64) -> InputEvent {
481 InputEvent::Pointer(PointerEvent {
482 phase,
483 position: Point::new(x, y),
484 button: PointerButton::Primary,
485 })
486 }
487
488 fn key_enter() -> InputEvent {
489 InputEvent::Key(KeyEvent {
490 key: Key::Named(NamedKey::Enter),
491 modifiers: Modifiers::default(),
492 repeat: false,
493 })
494 }
495
496 struct Empty;
499 struct EmptyWidget;
500 impl Widget for EmptyWidget {
501 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
502 bc.constrain(Size::new(10.0, 10.0))
503 }
504 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
505 }
506 impl View<()> for Empty {
507 type Element = EmptyWidget;
508 fn build(&self, _ctx: &mut BuildCtx<'_>) -> EmptyWidget {
509 EmptyWidget
510 }
511 fn rebuild(
512 &self,
513 _prev: &Self,
514 _element: &mut EmptyWidget,
515 _ctx: &mut BuildCtx<'_>,
516 ) -> ChangeFlags {
517 ChangeFlags::NONE
518 }
519 }
520
521 struct OtherLeaf;
523 struct OtherWidget;
524 impl Widget for OtherWidget {
525 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
526 bc.constrain(Size::new(10.0, 10.0))
527 }
528 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
529 }
530 impl View<()> for OtherLeaf {
531 type Element = OtherWidget;
532 fn build(&self, _ctx: &mut BuildCtx<'_>) -> OtherWidget {
533 OtherWidget
534 }
535 fn rebuild(
536 &self,
537 _prev: &Self,
538 _element: &mut OtherWidget,
539 _ctx: &mut BuildCtx<'_>,
540 ) -> ChangeFlags {
541 ChangeFlags::NONE
542 }
543 }
544
545 struct CounterState {
548 count: u32,
549 }
550
551 struct ClickCounter {
555 label: &'static str,
556 }
557
558 impl Component for ClickCounter {
559 type State = CounterState;
560 fn init(&self) -> CounterState {
561 CounterState { count: 0 }
562 }
563 fn build(&self, state: &mut CounterState) -> impl View<CounterState> {
564 any(CounterButtonView {
565 count: state.count,
566 label: self.label,
567 })
568 }
569 }
570
571 struct CounterButtonView {
572 count: u32,
573 label: &'static str,
574 }
575 struct CounterButtonWidget {
576 count: u32,
577 label: &'static str,
578 }
579 impl View<CounterState> for CounterButtonView {
580 type Element = CounterButtonWidget;
581 fn build(&self, _ctx: &mut BuildCtx<'_>) -> CounterButtonWidget {
582 CounterButtonWidget {
583 count: self.count,
584 label: self.label,
585 }
586 }
587 fn rebuild(
588 &self,
589 prev: &Self,
590 element: &mut CounterButtonWidget,
591 _ctx: &mut BuildCtx<'_>,
592 ) -> ChangeFlags {
593 let mut flags = ChangeFlags::NONE;
594 if prev.count != self.count || prev.label != self.label {
595 element.count = self.count;
596 element.label = self.label;
597 flags = ChangeFlags::PAINT;
598 }
599 flags
600 }
601 }
602 impl Widget for CounterButtonWidget {
603 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
604 bc.constrain(Size::new(40.0, 20.0))
605 }
606 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
607 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
608 if let InputEvent::Pointer(p) = event
609 && p.phase == PointerPhase::Down
610 {
611 ctx.state_mut::<CounterState>().count += 1;
612 ctx.request_redraw();
613 return EventResult::Handled;
614 }
615 EventResult::Ignored
616 }
617 }
618
619 fn ambient() -> Owner {
622 let owner = Owner::new();
623 owner.set();
624 owner
625 }
626
627 fn build_widget<Outer: 'static, C: Component>(view: &ComponentView<C>) -> ComponentWidget<C> {
628 let mut next_id = 0u64;
629 let mut ctx = BuildCtx::new(&mut next_id);
630 View::<Outer>::build(view, &mut ctx)
631 }
632
633 #[test]
634 fn local_state_survives_parent_driven_rebuild() {
635 let _owner = ambient();
636 let v1 = component(ClickCounter { label: "a" });
637 let mut widget = build_widget::<(), _>(&v1);
638 let mut lctx = LayoutCtx::new();
639 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 20.0)));
640
641 let mut outer = ();
644 for _ in 0..3 {
645 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 20.0));
646 widget.event(&mut ectx, &pointer(PointerPhase::Down, 5.0, 5.0));
647 }
648 assert_eq!(widget.state().count, 3);
649
650 let v2 = component(ClickCounter { label: "changed" });
652 let mut next_id = 0u64;
653 let mut ctx = BuildCtx::new(&mut next_id);
654 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
655 assert_eq!(
656 widget.state().count,
657 3,
658 "local state retained across rebuild"
659 );
660 }
661
662 #[derive(Clone)]
665 struct Theme(i32);
666
667 struct Provider {
670 sink: Arc<Mutex<Option<i32>>>,
671 }
672 impl Component for Provider {
673 type State = ();
674 fn init(&self) {}
675 fn build(&self, _state: &mut ()) -> impl View<()> {
676 provide_context(Theme(7));
677 any(component(Reader {
678 sink: self.sink.clone(),
679 }))
680 }
681 }
682
683 struct Reader {
685 sink: Arc<Mutex<Option<i32>>>,
686 }
687 impl Component for Reader {
688 type State = ();
689 fn init(&self) {}
690 fn build(&self, _state: &mut ()) -> impl View<()> {
691 *self.sink.lock().unwrap() = use_context::<Theme>().map(|t| t.0);
692 any(Empty)
693 }
694 }
695
696 struct SecretProvider;
699 impl Component for SecretProvider {
700 type State = ();
701 fn init(&self) {}
702 fn build(&self, _state: &mut ()) -> impl View<()> {
703 provide_context(Theme(99));
704 any(Empty)
705 }
706 }
707
708 #[test]
709 fn use_context_resolves_ancestor_provided_value() {
710 let _owner = ambient();
711 let sink = Arc::new(Mutex::new(None));
712 let v = component(Provider { sink: sink.clone() });
713 let _widget = build_widget::<(), _>(&v);
714 assert_eq!(*sink.lock().unwrap(), Some(7));
715 }
716
717 #[test]
718 fn sibling_does_not_see_a_cousins_context() {
719 let parent = ambient();
720 let reader_sink = Arc::new(Mutex::new(Some(-1)));
721 parent.with(|| {
724 let secret = component(SecretProvider);
725 let _s = build_widget::<(), _>(&secret);
726 let reader = component(Reader {
727 sink: reader_sink.clone(),
728 });
729 let _r = build_widget::<(), _>(&reader);
730 });
731 assert_eq!(*reader_sink.lock().unwrap(), None);
732 }
733
734 struct Disposable {
737 probe: Arc<AtomicU32>,
738 }
739 impl Component for Disposable {
740 type State = ();
741 fn init(&self) {
742 let probe = self.probe.clone();
743 on_cleanup(move || {
745 probe.fetch_add(1, Ordering::SeqCst);
746 });
747 }
748 fn build(&self, _state: &mut ()) -> impl View<()> {
749 any(Empty)
750 }
751 }
752
753 #[test]
754 fn anyview_type_swap_disposes_component_owner_once() {
755 let _owner = ambient();
756 let probe = Arc::new(AtomicU32::new(0));
757 let mut next_id = 0u64;
758 let mut ctx = BuildCtx::new(&mut next_id);
759
760 let prev = any::<(), _>(component(Disposable {
761 probe: probe.clone(),
762 }));
763 let mut element: Box<dyn Widget> = prev.build(&mut ctx);
764 assert_eq!(probe.load(Ordering::SeqCst), 0);
765
766 let next = any::<(), _>(OtherLeaf);
769 next.rebuild(&prev, &mut element, &mut ctx);
770 assert_eq!(probe.load(Ordering::SeqCst), 1);
771
772 drop(element);
774 assert_eq!(probe.load(Ordering::SeqCst), 1);
775 }
776
777 struct ConcreteComp;
782 impl Component for ConcreteComp {
783 type State = ();
784 fn init(&self) {}
785 fn build(&self, _state: &mut ()) -> impl View<()> {
786 Empty
787 }
788 }
789
790 struct ErasedComp;
793 impl Component for ErasedComp {
794 type State = ();
795 fn init(&self) {}
796 fn build(&self, _state: &mut ()) -> impl View<()> {
797 any(Empty)
798 }
799 }
800
801 fn child_is_empty_widget<C: Component>(widget: &mut ComponentWidget<C>) -> bool {
804 let boxed = widget
805 .child
806 .widget_mut()
807 .downcast_mut::<Box<dyn Widget>>()
808 .expect("double-boxed AnyView element");
809 let any: &dyn Any = &**boxed;
810 any.is::<EmptyWidget>()
811 }
812
813 #[test]
814 fn build_result_is_erased_exactly_once_at_the_boundary() {
815 let _owner = ambient();
816
817 let concrete = component(ConcreteComp);
818 let mut widget = build_widget::<(), _>(&concrete);
819 assert!(child_is_empty_widget(&mut widget), "concrete body, build");
820 let mut next_id = 0u64;
821 let mut ctx = BuildCtx::new(&mut next_id);
822 View::<()>::rebuild(&concrete, &concrete, &mut widget, &mut ctx);
823 assert!(child_is_empty_widget(&mut widget), "concrete body, rebuild");
824
825 let erased = component(ErasedComp);
826 let mut widget = build_widget::<(), _>(&erased);
827 assert!(child_is_empty_widget(&mut widget), "any(..) body, build");
828 let mut next_id = 0u64;
829 let mut ctx = BuildCtx::new(&mut next_id);
830 View::<()>::rebuild(&erased, &erased, &mut widget, &mut ctx);
831 assert!(child_is_empty_widget(&mut widget), "any(..) body, rebuild");
832 }
833
834 struct SwapComp {
841 swapped: Rc<Cell<bool>>,
842 }
843 impl Component for SwapComp {
844 type State = ();
845 fn init(&self) {}
846 fn build(&self, _state: &mut ()) -> impl View<()> {
847 if self.swapped.get() {
848 any(OtherLeaf)
849 } else {
850 any(Empty)
851 }
852 }
853 }
854
855 #[test]
856 fn component_swap_arm_marks_the_orphan_only_on_a_live_chain() {
857 fn swap_child(outer_live: bool) -> (bool, bool) {
860 let _ = crate::event::take_focus_orphaned();
861 let _owner = ambient();
862 let swapped = Rc::new(Cell::new(false));
863 let v1 = component(SwapComp {
864 swapped: swapped.clone(),
865 });
866 let mut widget = build_widget::<(), _>(&v1);
867 widget.child.set_focused(true);
869
870 swapped.set(true);
871 let v2 = component(SwapComp {
872 swapped: swapped.clone(),
873 });
874 let mut next_id = 0u64;
875 let mut ctx = BuildCtx::new(&mut next_id);
876 ctx.set_has_focus(outer_live);
878 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
879 (
880 widget.child.is_focused(),
881 crate::event::take_focus_orphaned(),
882 )
883 }
884
885 let (still_focused, marked) = swap_child(true);
886 assert!(
887 !still_focused,
888 "a swapped-in widget must not inherit the old focus link"
889 );
890 assert!(
891 marked,
892 "a live focus path dying inside the component's swap releases the session"
893 );
894
895 let (still_focused, marked) = swap_child(false);
896 assert!(!still_focused, "the flag is dropped either way");
897 assert!(
898 !marked,
899 "the same swap under an already-blurred ancestor owns no session to release"
900 );
901 }
902
903 enum Arms {
908 Left(Empty),
909 Right(OtherLeaf),
910 }
911 enum ArmsWidget {
912 Left(EmptyWidget),
913 Right(OtherWidget),
914 }
915 impl Widget for ArmsWidget {
916 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
917 match self {
918 Self::Left(w) => w.layout(ctx, bc),
919 Self::Right(w) => w.layout(ctx, bc),
920 }
921 }
922 fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
923 match self {
924 Self::Left(w) => w.paint(ctx, scene),
925 Self::Right(w) => w.paint(ctx, scene),
926 }
927 }
928 }
929 impl View<()> for Arms {
930 type Element = ArmsWidget;
931 fn build(&self, ctx: &mut BuildCtx<'_>) -> ArmsWidget {
932 match self {
933 Self::Left(v) => ArmsWidget::Left(v.build(ctx)),
934 Self::Right(v) => ArmsWidget::Right(v.build(ctx)),
935 }
936 }
937 fn rebuild(
938 &self,
939 prev: &Self,
940 element: &mut ArmsWidget,
941 ctx: &mut BuildCtx<'_>,
942 ) -> ChangeFlags {
943 match (self, prev, &mut *element) {
944 (Self::Left(v), Self::Left(p), ArmsWidget::Left(w)) => v.rebuild(p, w, ctx),
945 (Self::Right(v), Self::Right(p), ArmsWidget::Right(w)) => v.rebuild(p, w, ctx),
946 _ => {
947 ctx.mark_element_replaced();
948 *element = self.build(ctx);
949 ChangeFlags::LAYOUT | ChangeFlags::PAINT
950 }
951 }
952 }
953 }
954
955 struct ArmsComp {
958 right: Rc<Cell<bool>>,
959 }
960 impl Component for ArmsComp {
961 type State = ();
962 fn init(&self) {}
963 fn build(&self, _state: &mut ()) -> impl View<()> {
964 if self.right.get() {
965 Arms::Right(OtherLeaf)
966 } else {
967 Arms::Left(Empty)
968 }
969 }
970 }
971
972 #[test]
973 fn component_enum_body_arm_swap_clears_the_child_pod_flags() {
974 fn rebuild_arms(right_now: bool) -> (bool, bool, bool) {
978 let _ = crate::event::take_focus_orphaned();
979 let _owner = ambient();
980 let right = Rc::new(Cell::new(false));
981 let v1 = component(ArmsComp {
982 right: right.clone(),
983 });
984 let mut widget = build_widget::<(), _>(&v1);
985 widget.child.set_active(true);
986 widget.child.set_focused(true);
987
988 right.set(right_now);
989 let v2 = component(ArmsComp {
990 right: right.clone(),
991 });
992 let mut next_id = 0u64;
993 let mut ctx = BuildCtx::new(&mut next_id);
994 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
995 (
996 widget.child.is_active(),
997 widget.child.is_focused(),
998 crate::event::take_focus_orphaned(),
999 )
1000 }
1001
1002 let (active, focused, marked) = rebuild_arms(true);
1003 assert!(
1004 !active,
1005 "the fresh arm never saw the Down: the capture is dropped"
1006 );
1007 assert!(
1008 !focused,
1009 "the fresh arm never claimed focus: the link is dropped"
1010 );
1011 assert!(
1012 marked,
1013 "a live focus path dying inside the arm swap releases the session"
1014 );
1015
1016 let (active, focused, marked) = rebuild_arms(false);
1017 assert!(active, "a same-arm rebuild keeps the in-flight capture");
1018 assert!(focused, "a same-arm rebuild keeps the focus path");
1019 assert!(!marked, "a same-arm rebuild orphans nothing");
1020 }
1021
1022 #[test]
1023 fn component_content_only_rebuild_keeps_focus_and_marks_nothing() {
1024 let _ = crate::event::take_focus_orphaned();
1028 let _owner = ambient();
1029 let v1 = component(Labeled { text: "a" });
1030 let mut widget = build_widget::<(), _>(&v1);
1031 widget.child.set_focused(true);
1032
1033 let v2 = component(Labeled { text: "b" });
1034 let mut next_id = 0u64;
1035 let mut ctx = BuildCtx::new(&mut next_id);
1036 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
1037
1038 assert!(
1039 widget.child.is_focused(),
1040 "a content-only rebuild keeps the recorded focus path"
1041 );
1042 assert!(
1043 !crate::event::take_focus_orphaned(),
1044 "an in-place child rebuild must not orphan the focus session"
1045 );
1046 }
1047
1048 #[test]
1049 fn explicit_teardown_disposes_owner_once_and_drop_is_idempotent() {
1050 let _owner = ambient();
1051 let probe = Arc::new(AtomicU32::new(0));
1052 let view = component(Disposable {
1053 probe: probe.clone(),
1054 });
1055 let mut widget = build_widget::<(), _>(&view);
1056
1057 let mut next_id = 0u64;
1058 let mut ctx = BuildCtx::new(&mut next_id);
1059 View::<()>::teardown(&view, &mut widget, &mut ctx);
1060 assert_eq!(probe.load(Ordering::SeqCst), 1);
1061
1062 drop(widget);
1064 assert_eq!(probe.load(Ordering::SeqCst), 1);
1065 }
1066
1067 struct CaptureWidget {
1072 pressed: bool,
1073 }
1074 impl Widget for CaptureWidget {
1075 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1076 bc.constrain(Size::new(40.0, 40.0))
1077 }
1078 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1079 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
1080 if let InputEvent::Pointer(p) = event {
1081 match p.phase {
1082 PointerPhase::Down => {
1083 self.pressed = true;
1084 ctx.capture_pointer();
1085 ctx.request_redraw();
1086 return EventResult::Handled;
1087 }
1088 PointerPhase::Move => return EventResult::Handled,
1089 PointerPhase::Up | PointerPhase::Cancel => {
1091 self.pressed = false;
1092 return EventResult::Handled;
1093 }
1094 }
1095 }
1096 EventResult::Ignored
1097 }
1098 }
1099 struct CaptureView;
1100 impl View<()> for CaptureView {
1101 type Element = CaptureWidget;
1102 fn build(&self, _ctx: &mut BuildCtx<'_>) -> CaptureWidget {
1103 CaptureWidget { pressed: false }
1104 }
1105 fn rebuild(
1106 &self,
1107 _prev: &Self,
1108 _element: &mut CaptureWidget,
1109 _ctx: &mut BuildCtx<'_>,
1110 ) -> ChangeFlags {
1111 ChangeFlags::NONE
1112 }
1113 }
1114 struct CaptureComp;
1115 impl Component for CaptureComp {
1116 type State = ();
1117 fn init(&self) {}
1118 fn build(&self, _state: &mut ()) -> impl View<()> {
1119 any(CaptureView)
1120 }
1121 }
1122
1123 fn inner_widget<C: Component, W: Widget>(w: &mut ComponentWidget<C>) -> &mut W {
1126 w.child
1127 .widget_mut()
1128 .downcast_mut::<Box<dyn Widget>>()
1129 .expect("double-boxed AnyView element")
1130 .downcast_mut::<W>()
1131 .expect("inner concrete widget")
1132 }
1133
1134 #[test]
1135 fn capture_survives_drag_outside_bounds_and_mirrors_outward() {
1136 let _owner = ambient();
1137 let view = component(CaptureComp);
1138 let mut widget = build_widget::<(), _>(&view);
1139 let mut lctx = LayoutCtx::new();
1140 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 40.0)));
1141
1142 let mut outer = ();
1143 let captured = {
1145 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1146 widget.event(&mut ectx, &pointer(PointerPhase::Down, 5.0, 5.0));
1147 ectx.is_pointer_captured()
1148 };
1149 assert!(captured, "inner capture bubbles to the outer context");
1150 assert!(
1151 widget.child.is_active(),
1152 "component pod records the capture"
1153 );
1154
1155 {
1157 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1158 let r = widget.event(&mut ectx, &pointer(PointerPhase::Move, 500.0, 500.0));
1159 assert_eq!(r, EventResult::Handled, "drag outside bounds still routed");
1160 }
1161 assert!(widget.child.is_active(), "capture survives the drag");
1162
1163 {
1165 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1166 widget.event(&mut ectx, &pointer(PointerPhase::Up, 500.0, 500.0));
1167 }
1168 assert!(!widget.child.is_active(), "Up auto-releases the capture");
1169 assert!(!inner_widget::<_, CaptureWidget>(&mut widget).pressed);
1170 }
1171
1172 #[test]
1173 fn synthesized_cancel_over_unit_state_is_safe_and_clears_pressed() {
1174 let _owner = ambient();
1175 let view = component(CaptureComp);
1176 let mut widget = build_widget::<(), _>(&view);
1177 let mut lctx = LayoutCtx::new();
1178 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 40.0)));
1179
1180 let mut outer = ();
1182 {
1183 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1184 widget.event(&mut ectx, &pointer(PointerPhase::Down, 5.0, 5.0));
1185 }
1186 assert!(inner_widget::<_, CaptureWidget>(&mut widget).pressed);
1187
1188 let mut dummy = ();
1191 {
1192 let mut ectx = EventCtx::new(&mut dummy, Point::ZERO, Size::new(40.0, 40.0));
1193 widget.event(&mut ectx, &pointer(PointerPhase::Cancel, 0.0, 0.0));
1194 }
1195 assert!(
1196 !inner_widget::<_, CaptureWidget>(&mut widget).pressed,
1197 "Cancel cleared pressed across the boundary"
1198 );
1199 assert!(!widget.child.is_active(), "Cancel released the capture");
1200 }
1201
1202 struct CursorLeafWidget;
1204 impl Widget for CursorLeafWidget {
1205 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1206 bc.constrain(Size::new(40.0, 40.0))
1207 }
1208 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1209 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
1210 if let InputEvent::Pointer(p) = event
1211 && p.phase == PointerPhase::Move
1212 {
1213 ctx.set_cursor(CursorIcon::Text);
1214 }
1215 EventResult::Ignored
1216 }
1217 }
1218 struct CursorLeafView;
1219 impl View<()> for CursorLeafView {
1220 type Element = CursorLeafWidget;
1221 fn build(&self, _ctx: &mut BuildCtx<'_>) -> CursorLeafWidget {
1222 CursorLeafWidget
1223 }
1224 fn rebuild(
1225 &self,
1226 _prev: &Self,
1227 _element: &mut CursorLeafWidget,
1228 _ctx: &mut BuildCtx<'_>,
1229 ) -> ChangeFlags {
1230 ChangeFlags::NONE
1231 }
1232 }
1233 struct CursorComp;
1234 impl Component for CursorComp {
1235 type State = ();
1236 fn init(&self) {}
1237 fn build(&self, _state: &mut ()) -> impl View<()> {
1238 any(CursorLeafView)
1239 }
1240 }
1241
1242 #[test]
1247 fn a_cursor_request_crosses_the_component_boundary() {
1248 let _owner = ambient();
1249 let view = component(CursorComp);
1250 let mut widget = build_widget::<(), _>(&view);
1251 let mut lctx = LayoutCtx::new();
1252 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 40.0)));
1253
1254 clear_cursor_request();
1257 let mut outer = ();
1258 {
1259 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1260 widget.event(&mut ectx, &pointer(PointerPhase::Move, 5.0, 5.0));
1261 }
1262 assert_eq!(
1263 take_cursor_request(),
1264 Some(CursorIcon::Text),
1265 "a request from below the component boundary reaches the root"
1266 );
1267 }
1268
1269 struct ImeLeafWidget;
1272 impl Widget for ImeLeafWidget {
1273 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1274 bc.constrain(Size::new(100.0, 100.0))
1275 }
1276 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1277 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
1278 match event {
1279 InputEvent::Pointer(p) if p.phase == PointerPhase::Down => {
1280 if p.position.x < 50.0 {
1281 ctx.request_focus();
1282 ctx.publish_ime_state(ImeState {
1283 active: true,
1284 editing: EditingState {
1285 text: "abc".to_string(),
1286 selection_base: 3,
1287 selection_extent: 3,
1288 composing_base: -1,
1289 composing_extent: -1,
1290 },
1291 caret: Some(Rect::new(0.0, 0.0, 1.0, 12.0)),
1292 content_type: Default::default(),
1293 suppress_soft_keyboard: false,
1294 });
1295 }
1296 EventResult::Handled
1297 }
1298 InputEvent::Key(_) | InputEvent::Ime(_) => EventResult::Handled,
1299 _ => EventResult::Ignored,
1300 }
1301 }
1302 }
1303 struct ImeLeafView;
1304 impl View<()> for ImeLeafView {
1305 type Element = ImeLeafWidget;
1306 fn build(&self, _ctx: &mut BuildCtx<'_>) -> ImeLeafWidget {
1307 ImeLeafWidget
1308 }
1309 fn rebuild(
1310 &self,
1311 _prev: &Self,
1312 _element: &mut ImeLeafWidget,
1313 _ctx: &mut BuildCtx<'_>,
1314 ) -> ChangeFlags {
1315 ChangeFlags::NONE
1316 }
1317 }
1318 struct ImeComp;
1319 impl Component for ImeComp {
1320 type State = ();
1321 fn init(&self) {}
1322 fn build(&self, _state: &mut ()) -> impl View<()> {
1323 any(ImeLeafView)
1324 }
1325 }
1326
1327 #[test]
1328 fn focus_ime_and_blur_surface_through_render_root() {
1329 use crate::app::RenderRoot;
1330 let _owner = ambient();
1331 let mut root: RenderRoot<(), ComponentView<ImeComp>> = RenderRoot::new();
1332 let mut state = ();
1333 root.rebuild(&mut |_s: &mut ()| component(ImeComp), &mut state);
1334 root.layout(Size::new(100.0, 100.0));
1335
1336 assert!(!root.is_focus_active());
1337 assert!(root.ime_state().is_none());
1338
1339 root.event(&mut state, &pointer(PointerPhase::Down, 10.0, 10.0));
1342 assert!(root.is_focus_active(), "focus request crossed the boundary");
1343 let ime = root.ime_state().expect("IME surface surfaced at the root");
1344 assert!(ime.active);
1345 assert_eq!(ime.editing.text, "abc");
1346
1347 let outcome = root.event(&mut state, &key_enter());
1349 assert!(outcome.handled, "Key routed to the focused inner leaf");
1350
1351 root.event(&mut state, &pointer(PointerPhase::Down, 80.0, 10.0));
1354 assert!(!root.is_focus_active(), "blur crossed the boundary");
1355 assert!(root.ime_state().is_none());
1356 }
1357
1358 struct TextLeafView {
1361 text: &'static str,
1362 }
1363 struct TextLeafWidget {
1364 text: &'static str,
1365 }
1366 impl View<()> for TextLeafView {
1367 type Element = TextLeafWidget;
1368 fn build(&self, _ctx: &mut BuildCtx<'_>) -> TextLeafWidget {
1369 TextLeafWidget { text: self.text }
1370 }
1371 fn rebuild(
1372 &self,
1373 prev: &Self,
1374 element: &mut TextLeafWidget,
1375 _ctx: &mut BuildCtx<'_>,
1376 ) -> ChangeFlags {
1377 if prev.text != self.text {
1378 element.text = self.text;
1379 ChangeFlags::PAINT
1380 } else {
1381 ChangeFlags::NONE
1382 }
1383 }
1384 }
1385 impl Widget for TextLeafWidget {
1386 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1387 bc.constrain(Size::new(10.0, 10.0))
1388 }
1389 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1390 }
1391 struct Labeled {
1392 text: &'static str,
1393 }
1394 impl Component for Labeled {
1395 type State = ();
1396 fn init(&self) {}
1397 fn build(&self, _state: &mut ()) -> impl View<()> {
1398 any(TextLeafView { text: self.text })
1399 }
1400 }
1401
1402 #[test]
1403 fn child_paint_flags_bubble_out_of_component_rebuild() {
1404 let _owner = ambient();
1405 let v1 = component(Labeled { text: "a" });
1406 let mut widget = build_widget::<(), _>(&v1);
1407
1408 let v_same = component(Labeled { text: "a" });
1410 let mut next_id = 0u64;
1411 let mut ctx = BuildCtx::new(&mut next_id);
1412 let flags = View::<()>::rebuild(&v_same, &v1, &mut widget, &mut ctx);
1413 assert_eq!(flags, ChangeFlags::NONE);
1414
1415 let v2 = component(Labeled { text: "b" });
1418 let flags = View::<()>::rebuild(&v2, &v_same, &mut widget, &mut ctx);
1419 assert_eq!(flags, ChangeFlags::PAINT);
1420 }
1421
1422 #[cfg(feature = "hotpatch")]
1425 mod seam {
1426 use super::*;
1427 use crate::hotpatch::{Inert, SeamWitness};
1428 use std::any::type_name;
1429 use std::mem::offset_of;
1430
1431 fn foreign<C: Component>() -> SeamWitness {
1434 let own = SeamWitness::of::<C>();
1435 SeamWitness {
1436 size_state: own.size_state + 8,
1437 ..own
1438 }
1439 }
1440
1441 fn take_reported<S>() -> bool {
1443 let mine: Vec<_> = frust_hotpatch::pending_layout_mismatches()
1444 .into_iter()
1445 .filter(|m| m.type_name == type_name::<S>())
1446 .collect();
1447 frust_hotpatch::mark_layout_mismatches_reported(&mine);
1448 !mine.is_empty()
1449 }
1450
1451 struct Counted<const N: usize> {
1454 value: u32,
1455 drops: Arc<AtomicU32>,
1456 }
1457 impl<const N: usize> Drop for Counted<N> {
1458 fn drop(&mut self) {
1459 self.drops.fetch_add(1, Ordering::SeqCst);
1460 }
1461 }
1462
1463 struct Probe<const N: usize> {
1466 builds: Arc<AtomicU32>,
1467 drops: Arc<AtomicU32>,
1468 disposed: Arc<AtomicU32>,
1469 child_teardowns: Arc<AtomicU32>,
1470 }
1471 impl<const N: usize> Probe<N> {
1472 fn new() -> Self {
1473 Self {
1474 builds: Arc::new(AtomicU32::new(0)),
1475 drops: Arc::new(AtomicU32::new(0)),
1476 disposed: Arc::new(AtomicU32::new(0)),
1477 child_teardowns: Arc::new(AtomicU32::new(0)),
1478 }
1479 }
1480 fn handle(&self) -> Self {
1481 Self {
1482 builds: self.builds.clone(),
1483 drops: self.drops.clone(),
1484 disposed: self.disposed.clone(),
1485 child_teardowns: self.child_teardowns.clone(),
1486 }
1487 }
1488 }
1489 impl<const N: usize> Component for Probe<N> {
1490 type State = Counted<N>;
1491 fn init(&self) -> Counted<N> {
1492 let disposed = self.disposed.clone();
1493 on_cleanup(move || {
1494 disposed.fetch_add(1, Ordering::SeqCst);
1495 });
1496 Counted {
1497 value: 0,
1498 drops: self.drops.clone(),
1499 }
1500 }
1501 fn build(&self, state: &mut Counted<N>) -> impl View<Counted<N>> {
1502 self.builds.fetch_add(1, Ordering::SeqCst);
1503 state.value += 1;
1504 TeardownProbe {
1505 teardowns: self.child_teardowns.clone(),
1506 }
1507 }
1508 }
1509
1510 struct TeardownProbe {
1512 teardowns: Arc<AtomicU32>,
1513 }
1514 impl<S: 'static> View<S> for TeardownProbe {
1515 type Element = EmptyWidget;
1516 fn build(&self, _ctx: &mut BuildCtx<'_>) -> EmptyWidget {
1517 EmptyWidget
1518 }
1519 fn rebuild(
1520 &self,
1521 _prev: &Self,
1522 _element: &mut EmptyWidget,
1523 _ctx: &mut BuildCtx<'_>,
1524 ) -> ChangeFlags {
1525 ChangeFlags::NONE
1526 }
1527 fn teardown(&self, _element: &mut EmptyWidget, _ctx: &mut BuildCtx<'_>) {
1528 self.teardowns.fetch_add(1, Ordering::SeqCst);
1529 }
1530 }
1531
1532 struct Parent {
1535 nested: Probe<1>,
1536 }
1537 impl Component for Parent {
1538 type State = ();
1539 fn init(&self) {}
1540 fn build(&self, _state: &mut ()) -> impl View<()> {
1541 component(self.nested.handle())
1542 }
1543 }
1544
1545 fn nested_of(parent: &mut ComponentWidget<Parent>) -> &mut ComponentWidget<Probe<1>> {
1546 let boxed = parent
1547 .child
1548 .widget_mut()
1549 .downcast_mut::<Box<dyn Widget>>()
1550 .expect("component child element is a boxed AnyView widget");
1551 (**boxed)
1552 .downcast_mut::<ComponentWidget<Probe<1>>>()
1553 .expect("the parent's subtree is the nested component")
1554 }
1555
1556 #[test]
1557 fn a_nested_widget_with_a_foreign_witness_is_never_rebuilt() {
1558 let _owner = ambient();
1559 let nested = Probe::<1>::new();
1560 let v1 = component(Parent {
1561 nested: nested.handle(),
1562 });
1563 let mut widget = build_widget::<(), _>(&v1);
1564 assert_eq!(nested.builds.load(Ordering::SeqCst), 1);
1565
1566 nested_of(&mut widget).set_witness_for_test(foreign::<Probe<1>>());
1567 let v2 = component(Parent {
1568 nested: nested.handle(),
1569 });
1570 let mut next_id = 0u64;
1571 let mut ctx = BuildCtx::new(&mut next_id);
1572 let flags = View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
1573
1574 assert_eq!(flags, ChangeFlags::NONE);
1575 assert_eq!(nested.builds.load(Ordering::SeqCst), 1, "never rebuilt");
1576 assert_eq!(nested_of(&mut widget).state().value, 1, "state untouched");
1577 assert!(take_reported::<Counted<1>>(), "the mismatch is reported");
1578
1579 drop(widget);
1581 assert_eq!(nested.drops.load(Ordering::SeqCst), 0);
1582 assert_eq!(nested.disposed.load(Ordering::SeqCst), 1);
1583 assert!(take_reported::<Counted<1>>());
1584 }
1585
1586 enum TwoArm<L, R> {
1589 Left(L),
1590 Right(R),
1591 }
1592 enum TwoArmWidget<L, R> {
1593 Left(L),
1594 Right(R),
1595 }
1596 impl<L: Widget, R: Widget> Widget for TwoArmWidget<L, R> {
1597 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1598 match self {
1599 Self::Left(w) => w.layout(ctx, bc),
1600 Self::Right(w) => w.layout(ctx, bc),
1601 }
1602 }
1603 fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
1604 match self {
1605 Self::Left(w) => w.paint(ctx, scene),
1606 Self::Right(w) => w.paint(ctx, scene),
1607 }
1608 }
1609 }
1610 impl<S: 'static, L: View<S>, R: View<S>> View<S> for TwoArm<L, R> {
1611 type Element = TwoArmWidget<L::Element, R::Element>;
1612 fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element {
1613 match self {
1614 Self::Left(v) => TwoArmWidget::Left(v.build(ctx)),
1615 Self::Right(v) => TwoArmWidget::Right(v.build(ctx)),
1616 }
1617 }
1618 fn rebuild(
1619 &self,
1620 prev: &Self,
1621 element: &mut Self::Element,
1622 ctx: &mut BuildCtx<'_>,
1623 ) -> ChangeFlags {
1624 match (self, prev, &mut *element) {
1625 (Self::Left(v), Self::Left(p), TwoArmWidget::Left(w)) => v.rebuild(p, w, ctx),
1626 (Self::Right(v), Self::Right(p), TwoArmWidget::Right(w)) => {
1627 v.rebuild(p, w, ctx)
1628 }
1629 _ => {
1630 prev.teardown(element, ctx);
1633 *element = self.build(ctx);
1634 ChangeFlags::LAYOUT | ChangeFlags::PAINT
1635 }
1636 }
1637 }
1638 fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
1639 match (self, element) {
1640 (Self::Left(v), TwoArmWidget::Left(w)) => v.teardown(w, ctx),
1641 (Self::Right(v), TwoArmWidget::Right(w)) => v.teardown(w, ctx),
1642 _ => {}
1643 }
1644 }
1645 }
1646
1647 #[test]
1648 fn an_inline_arm_swap_leaks_a_foreign_state_and_disposes_its_owner() {
1649 let _owner = ambient();
1650 let probe = Probe::<2>::new();
1651 let left: TwoArm<ComponentView<Probe<2>>, Empty> =
1652 TwoArm::Left(component(probe.handle()));
1653 let mut next_id = 0u64;
1654 let mut ctx = BuildCtx::new(&mut next_id);
1655 let mut element = View::<()>::build(&left, &mut ctx);
1656 match &mut element {
1657 TwoArmWidget::Left(w) => w.set_witness_for_test(foreign::<Probe<2>>()),
1658 TwoArmWidget::Right(_) => unreachable!("built the left arm"),
1659 }
1660
1661 let right: TwoArm<ComponentView<Probe<2>>, Empty> = TwoArm::Right(Empty);
1662 let flags = View::<()>::rebuild(&right, &left, &mut element, &mut ctx);
1663
1664 assert_eq!(flags, ChangeFlags::LAYOUT | ChangeFlags::PAINT);
1665 assert!(matches!(element, TwoArmWidget::Right(_)), "swapped out");
1666 assert_eq!(
1667 probe.drops.load(Ordering::SeqCst),
1668 0,
1669 "the state is neither dropped nor freed"
1670 );
1671 assert_eq!(
1672 probe.child_teardowns.load(Ordering::SeqCst),
1673 0,
1674 "teardown did not proceed"
1675 );
1676 assert_eq!(probe.disposed.load(Ordering::SeqCst), 1, "owner disposed");
1677 assert!(take_reported::<Counted<2>>(), "the mismatch is reported");
1678 }
1679
1680 #[test]
1681 fn a_matching_widget_tears_down_and_drops_its_state() {
1682 let _owner = ambient();
1683 let probe = Probe::<3>::new();
1684 let view = component(probe.handle());
1685 let mut widget = build_widget::<(), _>(&view);
1686 let mut next_id = 0u64;
1687 let mut ctx = BuildCtx::new(&mut next_id);
1688 View::<()>::rebuild(&view, &view, &mut widget, &mut ctx);
1689 assert_eq!(probe.builds.load(Ordering::SeqCst), 2);
1690 View::<()>::teardown(&view, &mut widget, &mut ctx);
1691 assert_eq!(probe.child_teardowns.load(Ordering::SeqCst), 1);
1692 assert_eq!(probe.disposed.load(Ordering::SeqCst), 1);
1693 drop(widget);
1694 assert_eq!(probe.drops.load(Ordering::SeqCst), 1);
1695 assert!(!take_reported::<Counted<3>>());
1696 }
1697
1698 struct Tiny;
1699 impl Component for Tiny {
1700 type State = u8;
1701 fn init(&self) -> u8 {
1702 0
1703 }
1704 fn build(&self, _state: &mut u8) -> impl View<u8> {
1705 Inert
1706 }
1707 }
1708
1709 struct Huge;
1710 impl Component for Huge {
1711 type State = [u64; 32];
1712 fn init(&self) -> [u64; 32] {
1713 [0; 32]
1714 }
1715 fn build(&self, _state: &mut [u64; 32]) -> impl View<[u64; 32]> {
1716 Inert
1717 }
1718 }
1719
1720 #[test]
1721 fn widget_layout_does_not_depend_on_the_state_layout() {
1722 assert_ne!(size_of::<u8>(), size_of::<[u64; 32]>());
1723 assert_eq!(
1724 size_of::<ComponentWidget<Tiny>>(),
1725 size_of::<ComponentWidget<Huge>>()
1726 );
1727 assert_eq!(
1728 align_of::<ComponentWidget<Tiny>>(),
1729 align_of::<ComponentWidget<Huge>>()
1730 );
1731 macro_rules! same_offset {
1732 ($($field:ident),*) => {$(
1733 assert_eq!(
1734 offset_of!(ComponentWidget<Tiny>, $field),
1735 offset_of!(ComponentWidget<Huge>, $field),
1736 stringify!($field)
1737 );
1738 )*};
1739 }
1740 same_offset!(witness, state, prev, child, owner, next_id, disposed);
1741 assert_eq!(offset_of!(ComponentWidget<Tiny>, witness), 0);
1742 }
1743 }
1744}