1use std::any::Any;
40
41use kurbo::{Point, Size};
42use reactive_graph::owner::Owner;
43
44use crate::event::{EventCtx, EventResult, InputEvent, PointerPhase};
45#[cfg(feature = "hotpatch")]
46use crate::hotpatch::SeamWitness;
47use crate::layout::BoxConstraints;
48use crate::sliver::{SliverConstraints, SliverGeometry, SliverWidget};
49use crate::view::{AnyView, BuildCtx, ChangeFlags, View};
50use crate::widget::{ChildPod, LayoutCtx, PaintCtx, PaintScene, Widget};
51
52pub trait Component: 'static {
64 type State: 'static;
66
67 fn init(&self) -> Self::State;
79
80 fn build(&self, state: &mut Self::State) -> impl View<Self::State>;
94}
95
96pub struct ComponentView<C: Component> {
103 component: C,
104}
105
106pub fn component<C: Component>(c: C) -> ComponentView<C> {
109 ComponentView { component: c }
110}
111
112#[cfg_attr(feature = "hotpatch", repr(C))]
134pub struct ComponentWidget<C: Component> {
135 #[cfg(feature = "hotpatch")]
137 witness: SeamWitness,
138 #[cfg(feature = "hotpatch")]
143 state: Option<Box<C::State>>,
144 #[cfg(not(feature = "hotpatch"))]
147 state: C::State,
148 prev: AnyView<C::State>,
151 child: ChildPod,
154 owner: Owner,
157 next_id: u64,
161 disposed: bool,
164}
165
166impl<C: Component> ComponentWidget<C> {
167 fn dispose(&mut self) {
171 if !self.disposed {
172 self.owner.cleanup();
176 self.disposed = true;
177 }
178 }
179
180 #[cfg(feature = "hotpatch")]
182 fn state_mut(&mut self) -> &mut C::State {
183 self.state
184 .as_deref_mut()
185 .expect("component state is present while the widget is live")
186 }
187
188 #[cfg(not(feature = "hotpatch"))]
190 fn state_mut(&mut self) -> &mut C::State {
191 &mut self.state
192 }
193
194 #[cfg(all(test, feature = "hotpatch"))]
196 fn state(&self) -> &C::State {
197 self.state
198 .as_deref()
199 .expect("component state is present while the widget is live")
200 }
201
202 #[cfg(all(test, not(feature = "hotpatch")))]
204 fn state(&self) -> &C::State {
205 &self.state
206 }
207
208 #[cfg(all(test, feature = "hotpatch"))]
211 fn set_witness_for_test(&mut self, witness: SeamWitness) {
212 self.witness = witness;
213 }
214}
215
216impl<C: Component> Drop for ComponentWidget<C> {
217 fn drop(&mut self) {
218 #[cfg(feature = "hotpatch")]
223 if crate::hotpatch::verify_witness::<C>(self.witness).is_err() {
224 std::mem::forget(self.state.take());
225 }
226 self.dispose();
229 }
230}
231
232impl<Outer: 'static, C: Component> View<Outer> for ComponentView<C> {
233 type Element = ComponentWidget<C>;
234
235 fn build(&self, _ctx: &mut BuildCtx<'_>) -> Self::Element {
236 let owner = Owner::new();
239 #[cfg(feature = "hotpatch")]
241 let witness = SeamWitness::of::<C>();
242 let (state, prev, child, next_id) = owner.with(|| {
243 #[cfg(feature = "hotpatch")]
244 let mut state = Box::new(self.component.init());
245 #[cfg(not(feature = "hotpatch"))]
246 let mut state = self.component.init();
247 #[cfg(feature = "hotpatch")]
251 let view = crate::hotpatch::built_or_inert(crate::hotpatch::build_erased(
252 &self.component,
253 &mut *state,
254 witness,
255 ));
256 #[cfg(not(feature = "hotpatch"))]
257 let view = AnyView::new(self.component.build(&mut state));
258 let mut next_id = 0u64;
260 let mut inner = BuildCtx::new(&mut next_id);
261 inner.set_has_focus(false);
266 let element: Box<dyn Widget> = view.build(&mut inner);
267 (state, view, ChildPod::new(Box::new(element)), next_id)
269 });
270 ComponentWidget {
271 #[cfg(feature = "hotpatch")]
272 witness,
273 #[cfg(feature = "hotpatch")]
274 state: Some(state),
275 #[cfg(not(feature = "hotpatch"))]
276 state,
277 prev,
278 child,
279 owner,
280 next_id,
281 disposed: false,
282 }
283 }
284
285 fn rebuild(
286 &self,
287 _prev: &Self,
288 element: &mut Self::Element,
289 ctx: &mut BuildCtx<'_>,
290 ) -> ChangeFlags {
291 #[cfg(feature = "hotpatch")]
295 if crate::hotpatch::verify_witness::<C>(element.witness).is_err() {
296 return ChangeFlags::NONE;
297 }
298 let outer_has_focus = ctx.has_focus();
309 let owner = element.owner.clone();
310 owner.with(|| {
311 #[cfg(feature = "hotpatch")]
314 let new_view = {
315 let witness = element.witness;
316 match crate::hotpatch::build_erased(&self.component, element.state_mut(), witness) {
317 Ok(view) => view,
318 Err(_) => return ChangeFlags::NONE,
320 }
321 };
322 #[cfg(not(feature = "hotpatch"))]
323 let new_view = AnyView::new(self.component.build(element.state_mut()));
324 let child_focused = element.child.is_focused();
326 let (flags, swapped) = {
327 let boxed = element
330 .child
331 .widget_mut_for_rebuild()
332 .downcast_mut::<Box<dyn Widget>>()
333 .expect("component child element is a boxed AnyView widget");
334 let before = {
335 let any: &dyn Any = &**boxed;
336 any.type_id()
337 };
338 let mut inner = BuildCtx::new(&mut element.next_id);
339 inner.set_has_focus(outer_has_focus && child_focused);
344 let (flags, replaced) = inner
352 .track_element_replaced(|inner| new_view.rebuild(&element.prev, boxed, inner));
353 let after = {
354 let any: &dyn Any = &**boxed;
355 any.type_id()
356 };
357 (flags, before != after || replaced)
358 };
359 if swapped {
360 element.child.set_active(false);
365 element.child.set_focused(false);
366 if outer_has_focus && child_focused {
375 crate::event::mark_focus_orphaned();
376 }
377 }
378 if swapped || flags.contains(ChangeFlags::LAYOUT) {
385 element.child.mark_layout_dirty();
386 } else if flags.contains(ChangeFlags::PAINT) {
387 element.child.mark_paint_dirty();
388 }
389 element.prev = new_view;
390 flags
391 })
392 }
393
394 fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
395 #[cfg(feature = "hotpatch")]
399 if crate::hotpatch::verify_witness::<C>(element.witness).is_err() {
400 return;
401 }
402 let inner_has_focus = ctx.has_focus() && element.child.is_focused();
410 element.owner.clone().with(|| {
411 let mut next_id = element.next_id;
412 let mut inner = BuildCtx::new(&mut next_id);
413 inner.set_has_focus(inner_has_focus);
414 if let Some(boxed) = element.child.widget_mut().downcast_mut::<Box<dyn Widget>>() {
415 element.prev.teardown(boxed, &mut inner);
416 }
417 });
418 element.dispose();
419 }
420}
421
422impl<C: Component> Widget for ComponentWidget<C> {
423 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
424 self.child.set_origin(Point::ZERO);
428 self.child.layout_child(ctx, bc)
429 }
430
431 fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
432 self.child.paint_child(ctx, scene);
437 }
438
439 fn semantics(&self, ctx: &mut crate::semantics::SemanticsCtx) {
440 self.child.semantics_child(ctx);
444 }
445
446 fn visit_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
447 visitor(&self.child);
451 }
452
453 fn as_sliver(&mut self) -> Option<&mut dyn SliverWidget> {
459 if self.child.is_sliver() {
460 Some(self)
461 } else {
462 None
463 }
464 }
465
466 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
467 let size = ctx.size();
478 let has_focus = ctx.has_focus();
479 let hovered = ctx.is_hovered();
484 let hover_epoch = ctx.hover_epoch();
485 let hover_eligible = ctx.is_hover_eligible();
486 let (result, needs_redraw, layout, captured, hover_claimed, focus_req, focus_rel, ime) = {
492 #[cfg(feature = "hotpatch")]
495 let state_any: &mut dyn Any = self
496 .state
497 .as_deref_mut()
498 .expect("component state is present while the widget is live");
499 #[cfg(not(feature = "hotpatch"))]
500 let state_any: &mut dyn Any = &mut self.state;
501 let mut inner = EventCtx::new(state_any, Point::ZERO, size);
502 inner.set_has_focus(has_focus);
503 inner.set_hovered(hovered);
504 inner.set_hover_epoch(hover_epoch);
505 inner.set_hover_eligible(hover_eligible);
506 let result = route_child(&mut self.child, &mut inner, event);
507 (
508 result,
509 inner.needs_redraw(),
510 (inner.needs_layout(), inner.is_layout_path_dirty()),
511 inner.is_pointer_captured(),
512 inner.is_hover_claimed(),
513 inner.is_focus_requested(),
514 inner.is_focus_released(),
515 inner.take_ime_state(),
516 )
517 };
518 if needs_redraw {
519 ctx.request_redraw();
520 }
521 ctx.absorb_layout(layout.0, layout.1);
525 if captured {
526 ctx.capture_pointer();
527 }
528 if hover_claimed {
529 ctx.claim_hover();
530 }
531 if focus_req {
532 ctx.request_focus();
533 }
534 if focus_rel {
535 ctx.release_focus();
536 }
537 if let Some(ime) = ime {
538 ctx.publish_ime_state(ime);
539 }
540 result
541 }
542}
543
544impl<C: Component> SliverWidget for ComponentWidget<C> {
545 fn layout_sliver(&mut self, ctx: &mut LayoutCtx, sc: &SliverConstraints) -> SliverGeometry {
548 self.child.set_origin(Point::ZERO);
549 self.child.layout_sliver_child(ctx, sc).unwrap_or_else(|| {
555 debug_assert!(
556 false,
557 "ComponentWidget::layout_sliver called on a wrapper whose as_sliver answered None"
558 );
559 SliverGeometry::ZERO
560 })
561 }
562}
563
564fn route_child(pod: &mut ChildPod, ctx: &mut EventCtx<'_>, event: &InputEvent) -> EventResult {
578 if event.is_broadcast() {
579 pod.event_child(ctx, event);
582 return EventResult::Ignored;
583 }
584 if event.is_focus_routed() {
585 if pod.is_focused() {
586 return pod.event_child(ctx, event);
587 }
588 return EventResult::Ignored;
589 }
590 if pod.is_active() {
591 let result = pod.event_child(ctx, event);
592 if releases_capture(event) {
593 pod.set_active(false);
594 }
595 return result;
596 }
597 let inside = pod.contains(event.position());
598 let result = if inside {
599 pod.event_child(ctx, event)
600 } else {
601 EventResult::Ignored
602 };
603 if is_pointer_down(event) && !inside && pod.is_focused() {
604 pod.set_focused(false);
605 }
606 result
607}
608
609fn releases_capture(event: &InputEvent) -> bool {
612 matches!(
613 event,
614 InputEvent::Pointer(p) if matches!(p.phase, PointerPhase::Up | PointerPhase::Cancel)
615 )
616}
617
618fn is_pointer_down(event: &InputEvent) -> bool {
620 matches!(
621 event,
622 InputEvent::Pointer(p) if matches!(p.phase, PointerPhase::Down)
623 )
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629 use crate::event::{
630 CursorIcon, EditingState, ImeState, Key, KeyEvent, Modifiers, NamedKey, PointerButton,
631 PointerEvent, clear_cursor_request, take_cursor_request,
632 };
633 use crate::view::any;
634 use kurbo::Rect;
635 use reactive_graph::owner::{on_cleanup, provide_context, use_context};
636 use std::cell::Cell;
637 use std::rc::Rc;
638 use std::sync::atomic::{AtomicU32, Ordering};
639 use std::sync::{Arc, Mutex};
640
641 fn pointer(phase: PointerPhase, x: f64, y: f64) -> InputEvent {
644 InputEvent::Pointer(PointerEvent {
645 phase,
646 position: Point::new(x, y),
647 button: PointerButton::Primary,
648 })
649 }
650
651 fn key_enter() -> InputEvent {
652 InputEvent::Key(KeyEvent {
653 key: Key::Named(NamedKey::Enter),
654 modifiers: Modifiers::default(),
655 repeat: false,
656 })
657 }
658
659 struct Empty;
662 struct EmptyWidget;
663 impl Widget for EmptyWidget {
664 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
665 bc.constrain(Size::new(10.0, 10.0))
666 }
667 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
668 }
669 impl View<()> for Empty {
670 type Element = EmptyWidget;
671 fn build(&self, _ctx: &mut BuildCtx<'_>) -> EmptyWidget {
672 EmptyWidget
673 }
674 fn rebuild(
675 &self,
676 _prev: &Self,
677 _element: &mut EmptyWidget,
678 _ctx: &mut BuildCtx<'_>,
679 ) -> ChangeFlags {
680 ChangeFlags::NONE
681 }
682 }
683
684 struct OtherLeaf;
686 struct OtherWidget;
687 impl Widget for OtherWidget {
688 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
689 bc.constrain(Size::new(10.0, 10.0))
690 }
691 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
692 }
693 impl View<()> for OtherLeaf {
694 type Element = OtherWidget;
695 fn build(&self, _ctx: &mut BuildCtx<'_>) -> OtherWidget {
696 OtherWidget
697 }
698 fn rebuild(
699 &self,
700 _prev: &Self,
701 _element: &mut OtherWidget,
702 _ctx: &mut BuildCtx<'_>,
703 ) -> ChangeFlags {
704 ChangeFlags::NONE
705 }
706 }
707
708 struct CounterState {
711 count: u32,
712 }
713
714 struct ClickCounter {
718 label: &'static str,
719 }
720
721 impl Component for ClickCounter {
722 type State = CounterState;
723 fn init(&self) -> CounterState {
724 CounterState { count: 0 }
725 }
726 fn build(&self, state: &mut CounterState) -> impl View<CounterState> {
727 any(CounterButtonView {
728 count: state.count,
729 label: self.label,
730 })
731 }
732 }
733
734 struct CounterButtonView {
735 count: u32,
736 label: &'static str,
737 }
738 struct CounterButtonWidget {
739 count: u32,
740 label: &'static str,
741 }
742 impl View<CounterState> for CounterButtonView {
743 type Element = CounterButtonWidget;
744 fn build(&self, _ctx: &mut BuildCtx<'_>) -> CounterButtonWidget {
745 CounterButtonWidget {
746 count: self.count,
747 label: self.label,
748 }
749 }
750 fn rebuild(
751 &self,
752 prev: &Self,
753 element: &mut CounterButtonWidget,
754 _ctx: &mut BuildCtx<'_>,
755 ) -> ChangeFlags {
756 let mut flags = ChangeFlags::NONE;
757 if prev.count != self.count || prev.label != self.label {
758 element.count = self.count;
759 element.label = self.label;
760 flags = ChangeFlags::PAINT;
761 }
762 flags
763 }
764 }
765 impl Widget for CounterButtonWidget {
766 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
767 bc.constrain(Size::new(40.0, 20.0))
768 }
769 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
770 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
771 if let InputEvent::Pointer(p) = event
772 && p.phase == PointerPhase::Down
773 {
774 ctx.state_mut::<CounterState>().count += 1;
775 ctx.request_redraw();
776 return EventResult::Handled;
777 }
778 EventResult::Ignored
779 }
780 }
781
782 fn ambient() -> Owner {
785 let owner = Owner::new();
786 owner.set();
787 owner
788 }
789
790 fn build_widget<Outer: 'static, C: Component>(view: &ComponentView<C>) -> ComponentWidget<C> {
791 let mut next_id = 0u64;
792 let mut ctx = BuildCtx::new(&mut next_id);
793 View::<Outer>::build(view, &mut ctx)
794 }
795
796 #[test]
797 fn local_state_survives_parent_driven_rebuild() {
798 let _owner = ambient();
799 let v1 = component(ClickCounter { label: "a" });
800 let mut widget = build_widget::<(), _>(&v1);
801 let mut lctx = LayoutCtx::new();
802 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 20.0)));
803
804 let mut outer = ();
807 for _ in 0..3 {
808 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 20.0));
809 widget.event(&mut ectx, &pointer(PointerPhase::Down, 5.0, 5.0));
810 }
811 assert_eq!(widget.state().count, 3);
812
813 let v2 = component(ClickCounter { label: "changed" });
815 let mut next_id = 0u64;
816 let mut ctx = BuildCtx::new(&mut next_id);
817 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
818 assert_eq!(
819 widget.state().count,
820 3,
821 "local state retained across rebuild"
822 );
823 }
824
825 #[derive(Clone)]
828 struct Theme(i32);
829
830 struct Provider {
833 sink: Arc<Mutex<Option<i32>>>,
834 }
835 impl Component for Provider {
836 type State = ();
837 fn init(&self) {}
838 fn build(&self, _state: &mut ()) -> impl View<()> {
839 provide_context(Theme(7));
840 any(component(Reader {
841 sink: self.sink.clone(),
842 }))
843 }
844 }
845
846 struct Reader {
848 sink: Arc<Mutex<Option<i32>>>,
849 }
850 impl Component for Reader {
851 type State = ();
852 fn init(&self) {}
853 fn build(&self, _state: &mut ()) -> impl View<()> {
854 *self.sink.lock().unwrap() = use_context::<Theme>().map(|t| t.0);
855 any(Empty)
856 }
857 }
858
859 struct SecretProvider;
862 impl Component for SecretProvider {
863 type State = ();
864 fn init(&self) {}
865 fn build(&self, _state: &mut ()) -> impl View<()> {
866 provide_context(Theme(99));
867 any(Empty)
868 }
869 }
870
871 #[test]
872 fn use_context_resolves_ancestor_provided_value() {
873 let _owner = ambient();
874 let sink = Arc::new(Mutex::new(None));
875 let v = component(Provider { sink: sink.clone() });
876 let _widget = build_widget::<(), _>(&v);
877 assert_eq!(*sink.lock().unwrap(), Some(7));
878 }
879
880 #[test]
881 fn sibling_does_not_see_a_cousins_context() {
882 let parent = ambient();
883 let reader_sink = Arc::new(Mutex::new(Some(-1)));
884 parent.with(|| {
887 let secret = component(SecretProvider);
888 let _s = build_widget::<(), _>(&secret);
889 let reader = component(Reader {
890 sink: reader_sink.clone(),
891 });
892 let _r = build_widget::<(), _>(&reader);
893 });
894 assert_eq!(*reader_sink.lock().unwrap(), None);
895 }
896
897 struct Disposable {
900 probe: Arc<AtomicU32>,
901 }
902 impl Component for Disposable {
903 type State = ();
904 fn init(&self) {
905 let probe = self.probe.clone();
906 on_cleanup(move || {
908 probe.fetch_add(1, Ordering::SeqCst);
909 });
910 }
911 fn build(&self, _state: &mut ()) -> impl View<()> {
912 any(Empty)
913 }
914 }
915
916 #[test]
917 fn anyview_type_swap_disposes_component_owner_once() {
918 let _owner = ambient();
919 let probe = Arc::new(AtomicU32::new(0));
920 let mut next_id = 0u64;
921 let mut ctx = BuildCtx::new(&mut next_id);
922
923 let prev = any::<(), _>(component(Disposable {
924 probe: probe.clone(),
925 }));
926 let mut element: Box<dyn Widget> = prev.build(&mut ctx);
927 assert_eq!(probe.load(Ordering::SeqCst), 0);
928
929 let next = any::<(), _>(OtherLeaf);
932 next.rebuild(&prev, &mut element, &mut ctx);
933 assert_eq!(probe.load(Ordering::SeqCst), 1);
934
935 drop(element);
937 assert_eq!(probe.load(Ordering::SeqCst), 1);
938 }
939
940 struct ConcreteComp;
945 impl Component for ConcreteComp {
946 type State = ();
947 fn init(&self) {}
948 fn build(&self, _state: &mut ()) -> impl View<()> {
949 Empty
950 }
951 }
952
953 struct ErasedComp;
956 impl Component for ErasedComp {
957 type State = ();
958 fn init(&self) {}
959 fn build(&self, _state: &mut ()) -> impl View<()> {
960 any(Empty)
961 }
962 }
963
964 fn child_is_empty_widget<C: Component>(widget: &mut ComponentWidget<C>) -> bool {
967 let boxed = widget
968 .child
969 .widget_mut()
970 .downcast_mut::<Box<dyn Widget>>()
971 .expect("double-boxed AnyView element");
972 let any: &dyn Any = &**boxed;
973 any.is::<EmptyWidget>()
974 }
975
976 #[test]
977 fn build_result_is_erased_exactly_once_at_the_boundary() {
978 let _owner = ambient();
979
980 let concrete = component(ConcreteComp);
981 let mut widget = build_widget::<(), _>(&concrete);
982 assert!(child_is_empty_widget(&mut widget), "concrete body, build");
983 let mut next_id = 0u64;
984 let mut ctx = BuildCtx::new(&mut next_id);
985 View::<()>::rebuild(&concrete, &concrete, &mut widget, &mut ctx);
986 assert!(child_is_empty_widget(&mut widget), "concrete body, rebuild");
987
988 let erased = component(ErasedComp);
989 let mut widget = build_widget::<(), _>(&erased);
990 assert!(child_is_empty_widget(&mut widget), "any(..) body, build");
991 let mut next_id = 0u64;
992 let mut ctx = BuildCtx::new(&mut next_id);
993 View::<()>::rebuild(&erased, &erased, &mut widget, &mut ctx);
994 assert!(child_is_empty_widget(&mut widget), "any(..) body, rebuild");
995 }
996
997 struct SwapComp {
1004 swapped: Rc<Cell<bool>>,
1005 }
1006 impl Component for SwapComp {
1007 type State = ();
1008 fn init(&self) {}
1009 fn build(&self, _state: &mut ()) -> impl View<()> {
1010 if self.swapped.get() {
1011 any(OtherLeaf)
1012 } else {
1013 any(Empty)
1014 }
1015 }
1016 }
1017
1018 #[test]
1019 fn component_swap_arm_marks_the_orphan_only_on_a_live_chain() {
1020 fn swap_child(outer_live: bool) -> (bool, bool) {
1023 let _ = crate::event::take_focus_orphaned();
1024 let _owner = ambient();
1025 let swapped = Rc::new(Cell::new(false));
1026 let v1 = component(SwapComp {
1027 swapped: swapped.clone(),
1028 });
1029 let mut widget = build_widget::<(), _>(&v1);
1030 widget.child.set_focused(true);
1032
1033 swapped.set(true);
1034 let v2 = component(SwapComp {
1035 swapped: swapped.clone(),
1036 });
1037 let mut next_id = 0u64;
1038 let mut ctx = BuildCtx::new(&mut next_id);
1039 ctx.set_has_focus(outer_live);
1041 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
1042 (
1043 widget.child.is_focused(),
1044 crate::event::take_focus_orphaned(),
1045 )
1046 }
1047
1048 let (still_focused, marked) = swap_child(true);
1049 assert!(
1050 !still_focused,
1051 "a swapped-in widget must not inherit the old focus link"
1052 );
1053 assert!(
1054 marked,
1055 "a live focus path dying inside the component's swap releases the session"
1056 );
1057
1058 let (still_focused, marked) = swap_child(false);
1059 assert!(!still_focused, "the flag is dropped either way");
1060 assert!(
1061 !marked,
1062 "the same swap under an already-blurred ancestor owns no session to release"
1063 );
1064 }
1065
1066 enum Arms {
1071 Left(Empty),
1072 Right(OtherLeaf),
1073 }
1074 enum ArmsWidget {
1075 Left(EmptyWidget),
1076 Right(OtherWidget),
1077 }
1078 impl Widget for ArmsWidget {
1079 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1080 match self {
1081 Self::Left(w) => w.layout(ctx, bc),
1082 Self::Right(w) => w.layout(ctx, bc),
1083 }
1084 }
1085 fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
1086 match self {
1087 Self::Left(w) => w.paint(ctx, scene),
1088 Self::Right(w) => w.paint(ctx, scene),
1089 }
1090 }
1091 }
1092 impl View<()> for Arms {
1093 type Element = ArmsWidget;
1094 fn build(&self, ctx: &mut BuildCtx<'_>) -> ArmsWidget {
1095 match self {
1096 Self::Left(v) => ArmsWidget::Left(v.build(ctx)),
1097 Self::Right(v) => ArmsWidget::Right(v.build(ctx)),
1098 }
1099 }
1100 fn rebuild(
1101 &self,
1102 prev: &Self,
1103 element: &mut ArmsWidget,
1104 ctx: &mut BuildCtx<'_>,
1105 ) -> ChangeFlags {
1106 match (self, prev, &mut *element) {
1107 (Self::Left(v), Self::Left(p), ArmsWidget::Left(w)) => v.rebuild(p, w, ctx),
1108 (Self::Right(v), Self::Right(p), ArmsWidget::Right(w)) => v.rebuild(p, w, ctx),
1109 _ => {
1110 ctx.mark_element_replaced();
1111 *element = self.build(ctx);
1112 ChangeFlags::LAYOUT | ChangeFlags::PAINT
1113 }
1114 }
1115 }
1116 }
1117
1118 struct ArmsComp {
1121 right: Rc<Cell<bool>>,
1122 }
1123 impl Component for ArmsComp {
1124 type State = ();
1125 fn init(&self) {}
1126 fn build(&self, _state: &mut ()) -> impl View<()> {
1127 if self.right.get() {
1128 Arms::Right(OtherLeaf)
1129 } else {
1130 Arms::Left(Empty)
1131 }
1132 }
1133 }
1134
1135 #[test]
1136 fn component_enum_body_arm_swap_clears_the_child_pod_flags() {
1137 fn rebuild_arms(right_now: bool) -> (bool, bool, bool) {
1141 let _ = crate::event::take_focus_orphaned();
1142 let _owner = ambient();
1143 let right = Rc::new(Cell::new(false));
1144 let v1 = component(ArmsComp {
1145 right: right.clone(),
1146 });
1147 let mut widget = build_widget::<(), _>(&v1);
1148 widget.child.set_active(true);
1149 widget.child.set_focused(true);
1150
1151 right.set(right_now);
1152 let v2 = component(ArmsComp {
1153 right: right.clone(),
1154 });
1155 let mut next_id = 0u64;
1156 let mut ctx = BuildCtx::new(&mut next_id);
1157 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
1158 (
1159 widget.child.is_active(),
1160 widget.child.is_focused(),
1161 crate::event::take_focus_orphaned(),
1162 )
1163 }
1164
1165 let (active, focused, marked) = rebuild_arms(true);
1166 assert!(
1167 !active,
1168 "the fresh arm never saw the Down: the capture is dropped"
1169 );
1170 assert!(
1171 !focused,
1172 "the fresh arm never claimed focus: the link is dropped"
1173 );
1174 assert!(
1175 marked,
1176 "a live focus path dying inside the arm swap releases the session"
1177 );
1178
1179 let (active, focused, marked) = rebuild_arms(false);
1180 assert!(active, "a same-arm rebuild keeps the in-flight capture");
1181 assert!(focused, "a same-arm rebuild keeps the focus path");
1182 assert!(!marked, "a same-arm rebuild orphans nothing");
1183 }
1184
1185 #[test]
1186 fn component_content_only_rebuild_keeps_focus_and_marks_nothing() {
1187 let _ = crate::event::take_focus_orphaned();
1191 let _owner = ambient();
1192 let v1 = component(Labeled { text: "a" });
1193 let mut widget = build_widget::<(), _>(&v1);
1194 widget.child.set_focused(true);
1195
1196 let v2 = component(Labeled { text: "b" });
1197 let mut next_id = 0u64;
1198 let mut ctx = BuildCtx::new(&mut next_id);
1199 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
1200
1201 assert!(
1202 widget.child.is_focused(),
1203 "a content-only rebuild keeps the recorded focus path"
1204 );
1205 assert!(
1206 !crate::event::take_focus_orphaned(),
1207 "an in-place child rebuild must not orphan the focus session"
1208 );
1209 }
1210
1211 #[test]
1212 fn explicit_teardown_disposes_owner_once_and_drop_is_idempotent() {
1213 let _owner = ambient();
1214 let probe = Arc::new(AtomicU32::new(0));
1215 let view = component(Disposable {
1216 probe: probe.clone(),
1217 });
1218 let mut widget = build_widget::<(), _>(&view);
1219
1220 let mut next_id = 0u64;
1221 let mut ctx = BuildCtx::new(&mut next_id);
1222 View::<()>::teardown(&view, &mut widget, &mut ctx);
1223 assert_eq!(probe.load(Ordering::SeqCst), 1);
1224
1225 drop(widget);
1227 assert_eq!(probe.load(Ordering::SeqCst), 1);
1228 }
1229
1230 struct CaptureWidget {
1235 pressed: bool,
1236 }
1237 impl Widget for CaptureWidget {
1238 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1239 bc.constrain(Size::new(40.0, 40.0))
1240 }
1241 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1242 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
1243 if let InputEvent::Pointer(p) = event {
1244 match p.phase {
1245 PointerPhase::Down => {
1246 self.pressed = true;
1247 ctx.capture_pointer();
1248 ctx.request_redraw();
1249 return EventResult::Handled;
1250 }
1251 PointerPhase::Move => return EventResult::Handled,
1252 PointerPhase::Up | PointerPhase::Cancel => {
1254 self.pressed = false;
1255 return EventResult::Handled;
1256 }
1257 }
1258 }
1259 EventResult::Ignored
1260 }
1261 }
1262 struct CaptureView;
1263 impl View<()> for CaptureView {
1264 type Element = CaptureWidget;
1265 fn build(&self, _ctx: &mut BuildCtx<'_>) -> CaptureWidget {
1266 CaptureWidget { pressed: false }
1267 }
1268 fn rebuild(
1269 &self,
1270 _prev: &Self,
1271 _element: &mut CaptureWidget,
1272 _ctx: &mut BuildCtx<'_>,
1273 ) -> ChangeFlags {
1274 ChangeFlags::NONE
1275 }
1276 }
1277 struct CaptureComp;
1278 impl Component for CaptureComp {
1279 type State = ();
1280 fn init(&self) {}
1281 fn build(&self, _state: &mut ()) -> impl View<()> {
1282 any(CaptureView)
1283 }
1284 }
1285
1286 fn inner_widget<C: Component, W: Widget>(w: &mut ComponentWidget<C>) -> &mut W {
1289 w.child
1290 .widget_mut()
1291 .downcast_mut::<Box<dyn Widget>>()
1292 .expect("double-boxed AnyView element")
1293 .downcast_mut::<W>()
1294 .expect("inner concrete widget")
1295 }
1296
1297 #[test]
1298 fn capture_survives_drag_outside_bounds_and_mirrors_outward() {
1299 let _owner = ambient();
1300 let view = component(CaptureComp);
1301 let mut widget = build_widget::<(), _>(&view);
1302 let mut lctx = LayoutCtx::new();
1303 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 40.0)));
1304
1305 let mut outer = ();
1306 let captured = {
1308 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1309 widget.event(&mut ectx, &pointer(PointerPhase::Down, 5.0, 5.0));
1310 ectx.is_pointer_captured()
1311 };
1312 assert!(captured, "inner capture bubbles to the outer context");
1313 assert!(
1314 widget.child.is_active(),
1315 "component pod records the capture"
1316 );
1317
1318 {
1320 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1321 let r = widget.event(&mut ectx, &pointer(PointerPhase::Move, 500.0, 500.0));
1322 assert_eq!(r, EventResult::Handled, "drag outside bounds still routed");
1323 }
1324 assert!(widget.child.is_active(), "capture survives the drag");
1325
1326 {
1328 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1329 widget.event(&mut ectx, &pointer(PointerPhase::Up, 500.0, 500.0));
1330 }
1331 assert!(!widget.child.is_active(), "Up auto-releases the capture");
1332 assert!(!inner_widget::<_, CaptureWidget>(&mut widget).pressed);
1333 }
1334
1335 #[test]
1336 fn synthesized_cancel_over_unit_state_is_safe_and_clears_pressed() {
1337 let _owner = ambient();
1338 let view = component(CaptureComp);
1339 let mut widget = build_widget::<(), _>(&view);
1340 let mut lctx = LayoutCtx::new();
1341 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 40.0)));
1342
1343 let mut outer = ();
1345 {
1346 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1347 widget.event(&mut ectx, &pointer(PointerPhase::Down, 5.0, 5.0));
1348 }
1349 assert!(inner_widget::<_, CaptureWidget>(&mut widget).pressed);
1350
1351 let mut dummy = ();
1354 {
1355 let mut ectx = EventCtx::new(&mut dummy, Point::ZERO, Size::new(40.0, 40.0));
1356 widget.event(&mut ectx, &pointer(PointerPhase::Cancel, 0.0, 0.0));
1357 }
1358 assert!(
1359 !inner_widget::<_, CaptureWidget>(&mut widget).pressed,
1360 "Cancel cleared pressed across the boundary"
1361 );
1362 assert!(!widget.child.is_active(), "Cancel released the capture");
1363 }
1364
1365 struct CursorLeafWidget;
1367 impl Widget for CursorLeafWidget {
1368 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1369 bc.constrain(Size::new(40.0, 40.0))
1370 }
1371 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1372 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
1373 if let InputEvent::Pointer(p) = event
1374 && p.phase == PointerPhase::Move
1375 {
1376 ctx.set_cursor(CursorIcon::Text);
1377 }
1378 EventResult::Ignored
1379 }
1380 }
1381 struct CursorLeafView;
1382 impl View<()> for CursorLeafView {
1383 type Element = CursorLeafWidget;
1384 fn build(&self, _ctx: &mut BuildCtx<'_>) -> CursorLeafWidget {
1385 CursorLeafWidget
1386 }
1387 fn rebuild(
1388 &self,
1389 _prev: &Self,
1390 _element: &mut CursorLeafWidget,
1391 _ctx: &mut BuildCtx<'_>,
1392 ) -> ChangeFlags {
1393 ChangeFlags::NONE
1394 }
1395 }
1396 struct CursorComp;
1397 impl Component for CursorComp {
1398 type State = ();
1399 fn init(&self) {}
1400 fn build(&self, _state: &mut ()) -> impl View<()> {
1401 any(CursorLeafView)
1402 }
1403 }
1404
1405 #[test]
1410 fn a_cursor_request_crosses_the_component_boundary() {
1411 let _owner = ambient();
1412 let view = component(CursorComp);
1413 let mut widget = build_widget::<(), _>(&view);
1414 let mut lctx = LayoutCtx::new();
1415 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 40.0)));
1416
1417 clear_cursor_request();
1420 let mut outer = ();
1421 {
1422 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1423 widget.event(&mut ectx, &pointer(PointerPhase::Move, 5.0, 5.0));
1424 }
1425 assert_eq!(
1426 take_cursor_request(),
1427 Some(CursorIcon::Text),
1428 "a request from below the component boundary reaches the root"
1429 );
1430 }
1431
1432 struct ImeLeafWidget;
1435 impl Widget for ImeLeafWidget {
1436 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1437 bc.constrain(Size::new(100.0, 100.0))
1438 }
1439 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1440 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
1441 match event {
1442 InputEvent::Pointer(p) if p.phase == PointerPhase::Down => {
1443 if p.position.x < 50.0 {
1444 ctx.request_focus();
1445 ctx.publish_ime_state(ImeState {
1446 active: true,
1447 editing: EditingState {
1448 text: "abc".to_string(),
1449 selection_base: 3,
1450 selection_extent: 3,
1451 composing_base: -1,
1452 composing_extent: -1,
1453 },
1454 caret: Some(Rect::new(0.0, 0.0, 1.0, 12.0)),
1455 content_type: Default::default(),
1456 suppress_soft_keyboard: false,
1457 });
1458 }
1459 EventResult::Handled
1460 }
1461 InputEvent::Key(_) | InputEvent::Ime(_) => EventResult::Handled,
1462 _ => EventResult::Ignored,
1463 }
1464 }
1465 }
1466 struct ImeLeafView;
1467 impl View<()> for ImeLeafView {
1468 type Element = ImeLeafWidget;
1469 fn build(&self, _ctx: &mut BuildCtx<'_>) -> ImeLeafWidget {
1470 ImeLeafWidget
1471 }
1472 fn rebuild(
1473 &self,
1474 _prev: &Self,
1475 _element: &mut ImeLeafWidget,
1476 _ctx: &mut BuildCtx<'_>,
1477 ) -> ChangeFlags {
1478 ChangeFlags::NONE
1479 }
1480 }
1481 struct ImeComp;
1482 impl Component for ImeComp {
1483 type State = ();
1484 fn init(&self) {}
1485 fn build(&self, _state: &mut ()) -> impl View<()> {
1486 any(ImeLeafView)
1487 }
1488 }
1489
1490 #[test]
1491 fn focus_ime_and_blur_surface_through_render_root() {
1492 use crate::app::RenderRoot;
1493 let _owner = ambient();
1494 let mut root: RenderRoot<(), ComponentView<ImeComp>> = RenderRoot::new();
1495 let mut state = ();
1496 root.rebuild(&mut |_s: &mut ()| component(ImeComp), &mut state);
1497 root.layout(Size::new(100.0, 100.0));
1498
1499 assert!(!root.is_focus_active());
1500 assert!(root.ime_state().is_none());
1501
1502 root.event(&mut state, &pointer(PointerPhase::Down, 10.0, 10.0));
1505 assert!(root.is_focus_active(), "focus request crossed the boundary");
1506 let ime = root.ime_state().expect("IME surface surfaced at the root");
1507 assert!(ime.active);
1508 assert_eq!(ime.editing.text, "abc");
1509
1510 let outcome = root.event(&mut state, &key_enter());
1512 assert!(outcome.handled, "Key routed to the focused inner leaf");
1513
1514 root.event(&mut state, &pointer(PointerPhase::Down, 80.0, 10.0));
1517 assert!(!root.is_focus_active(), "blur crossed the boundary");
1518 assert!(root.ime_state().is_none());
1519 }
1520
1521 struct TextLeafView {
1524 text: &'static str,
1525 }
1526 struct TextLeafWidget {
1527 text: &'static str,
1528 }
1529 impl View<()> for TextLeafView {
1530 type Element = TextLeafWidget;
1531 fn build(&self, _ctx: &mut BuildCtx<'_>) -> TextLeafWidget {
1532 TextLeafWidget { text: self.text }
1533 }
1534 fn rebuild(
1535 &self,
1536 prev: &Self,
1537 element: &mut TextLeafWidget,
1538 _ctx: &mut BuildCtx<'_>,
1539 ) -> ChangeFlags {
1540 if prev.text != self.text {
1541 element.text = self.text;
1542 ChangeFlags::PAINT
1543 } else {
1544 ChangeFlags::NONE
1545 }
1546 }
1547 }
1548 impl Widget for TextLeafWidget {
1549 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1550 bc.constrain(Size::new(10.0, 10.0))
1551 }
1552 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1553 }
1554 struct Labeled {
1555 text: &'static str,
1556 }
1557 impl Component for Labeled {
1558 type State = ();
1559 fn init(&self) {}
1560 fn build(&self, _state: &mut ()) -> impl View<()> {
1561 any(TextLeafView { text: self.text })
1562 }
1563 }
1564
1565 #[test]
1566 fn child_paint_flags_bubble_out_of_component_rebuild() {
1567 let _owner = ambient();
1568 let v1 = component(Labeled { text: "a" });
1569 let mut widget = build_widget::<(), _>(&v1);
1570
1571 let v_same = component(Labeled { text: "a" });
1573 let mut next_id = 0u64;
1574 let mut ctx = BuildCtx::new(&mut next_id);
1575 let flags = View::<()>::rebuild(&v_same, &v1, &mut widget, &mut ctx);
1576 assert_eq!(flags, ChangeFlags::NONE);
1577
1578 let v2 = component(Labeled { text: "b" });
1581 let flags = View::<()>::rebuild(&v2, &v_same, &mut widget, &mut ctx);
1582 assert_eq!(flags, ChangeFlags::PAINT);
1583 }
1584
1585 #[cfg(feature = "hotpatch")]
1588 mod seam {
1589 use super::*;
1590 use crate::hotpatch::{Inert, SeamWitness};
1591 use std::any::type_name;
1592 use std::mem::offset_of;
1593
1594 fn foreign<C: Component>() -> SeamWitness {
1597 let own = SeamWitness::of::<C>();
1598 SeamWitness {
1599 size_state: own.size_state + 8,
1600 ..own
1601 }
1602 }
1603
1604 fn take_reported<S>() -> bool {
1606 let mine: Vec<_> = frust_hotpatch::pending_layout_mismatches()
1607 .into_iter()
1608 .filter(|m| m.type_name == type_name::<S>())
1609 .collect();
1610 frust_hotpatch::mark_layout_mismatches_reported(&mine);
1611 !mine.is_empty()
1612 }
1613
1614 struct Counted<const N: usize> {
1617 value: u32,
1618 drops: Arc<AtomicU32>,
1619 }
1620 impl<const N: usize> Drop for Counted<N> {
1621 fn drop(&mut self) {
1622 self.drops.fetch_add(1, Ordering::SeqCst);
1623 }
1624 }
1625
1626 struct Probe<const N: usize> {
1629 builds: Arc<AtomicU32>,
1630 drops: Arc<AtomicU32>,
1631 disposed: Arc<AtomicU32>,
1632 child_teardowns: Arc<AtomicU32>,
1633 }
1634 impl<const N: usize> Probe<N> {
1635 fn new() -> Self {
1636 Self {
1637 builds: Arc::new(AtomicU32::new(0)),
1638 drops: Arc::new(AtomicU32::new(0)),
1639 disposed: Arc::new(AtomicU32::new(0)),
1640 child_teardowns: Arc::new(AtomicU32::new(0)),
1641 }
1642 }
1643 fn handle(&self) -> Self {
1644 Self {
1645 builds: self.builds.clone(),
1646 drops: self.drops.clone(),
1647 disposed: self.disposed.clone(),
1648 child_teardowns: self.child_teardowns.clone(),
1649 }
1650 }
1651 }
1652 impl<const N: usize> Component for Probe<N> {
1653 type State = Counted<N>;
1654 fn init(&self) -> Counted<N> {
1655 let disposed = self.disposed.clone();
1656 on_cleanup(move || {
1657 disposed.fetch_add(1, Ordering::SeqCst);
1658 });
1659 Counted {
1660 value: 0,
1661 drops: self.drops.clone(),
1662 }
1663 }
1664 fn build(&self, state: &mut Counted<N>) -> impl View<Counted<N>> {
1665 self.builds.fetch_add(1, Ordering::SeqCst);
1666 state.value += 1;
1667 TeardownProbe {
1668 teardowns: self.child_teardowns.clone(),
1669 }
1670 }
1671 }
1672
1673 struct TeardownProbe {
1675 teardowns: Arc<AtomicU32>,
1676 }
1677 impl<S: 'static> View<S> for TeardownProbe {
1678 type Element = EmptyWidget;
1679 fn build(&self, _ctx: &mut BuildCtx<'_>) -> EmptyWidget {
1680 EmptyWidget
1681 }
1682 fn rebuild(
1683 &self,
1684 _prev: &Self,
1685 _element: &mut EmptyWidget,
1686 _ctx: &mut BuildCtx<'_>,
1687 ) -> ChangeFlags {
1688 ChangeFlags::NONE
1689 }
1690 fn teardown(&self, _element: &mut EmptyWidget, _ctx: &mut BuildCtx<'_>) {
1691 self.teardowns.fetch_add(1, Ordering::SeqCst);
1692 }
1693 }
1694
1695 struct Parent {
1698 nested: Probe<1>,
1699 }
1700 impl Component for Parent {
1701 type State = ();
1702 fn init(&self) {}
1703 fn build(&self, _state: &mut ()) -> impl View<()> {
1704 component(self.nested.handle())
1705 }
1706 }
1707
1708 fn nested_of(parent: &mut ComponentWidget<Parent>) -> &mut ComponentWidget<Probe<1>> {
1709 let boxed = parent
1710 .child
1711 .widget_mut()
1712 .downcast_mut::<Box<dyn Widget>>()
1713 .expect("component child element is a boxed AnyView widget");
1714 (**boxed)
1715 .downcast_mut::<ComponentWidget<Probe<1>>>()
1716 .expect("the parent's subtree is the nested component")
1717 }
1718
1719 #[test]
1720 fn a_nested_widget_with_a_foreign_witness_is_never_rebuilt() {
1721 let _owner = ambient();
1722 let nested = Probe::<1>::new();
1723 let v1 = component(Parent {
1724 nested: nested.handle(),
1725 });
1726 let mut widget = build_widget::<(), _>(&v1);
1727 assert_eq!(nested.builds.load(Ordering::SeqCst), 1);
1728
1729 nested_of(&mut widget).set_witness_for_test(foreign::<Probe<1>>());
1730 let v2 = component(Parent {
1731 nested: nested.handle(),
1732 });
1733 let mut next_id = 0u64;
1734 let mut ctx = BuildCtx::new(&mut next_id);
1735 let flags = View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
1736
1737 assert_eq!(flags, ChangeFlags::NONE);
1738 assert_eq!(nested.builds.load(Ordering::SeqCst), 1, "never rebuilt");
1739 assert_eq!(nested_of(&mut widget).state().value, 1, "state untouched");
1740 assert!(take_reported::<Counted<1>>(), "the mismatch is reported");
1741
1742 drop(widget);
1744 assert_eq!(nested.drops.load(Ordering::SeqCst), 0);
1745 assert_eq!(nested.disposed.load(Ordering::SeqCst), 1);
1746 assert!(take_reported::<Counted<1>>());
1747 }
1748
1749 enum TwoArm<L, R> {
1752 Left(L),
1753 Right(R),
1754 }
1755 enum TwoArmWidget<L, R> {
1756 Left(L),
1757 Right(R),
1758 }
1759 impl<L: Widget, R: Widget> Widget for TwoArmWidget<L, R> {
1760 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1761 match self {
1762 Self::Left(w) => w.layout(ctx, bc),
1763 Self::Right(w) => w.layout(ctx, bc),
1764 }
1765 }
1766 fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
1767 match self {
1768 Self::Left(w) => w.paint(ctx, scene),
1769 Self::Right(w) => w.paint(ctx, scene),
1770 }
1771 }
1772 }
1773 impl<S: 'static, L: View<S>, R: View<S>> View<S> for TwoArm<L, R> {
1774 type Element = TwoArmWidget<L::Element, R::Element>;
1775 fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element {
1776 match self {
1777 Self::Left(v) => TwoArmWidget::Left(v.build(ctx)),
1778 Self::Right(v) => TwoArmWidget::Right(v.build(ctx)),
1779 }
1780 }
1781 fn rebuild(
1782 &self,
1783 prev: &Self,
1784 element: &mut Self::Element,
1785 ctx: &mut BuildCtx<'_>,
1786 ) -> ChangeFlags {
1787 match (self, prev, &mut *element) {
1788 (Self::Left(v), Self::Left(p), TwoArmWidget::Left(w)) => v.rebuild(p, w, ctx),
1789 (Self::Right(v), Self::Right(p), TwoArmWidget::Right(w)) => {
1790 v.rebuild(p, w, ctx)
1791 }
1792 _ => {
1793 prev.teardown(element, ctx);
1796 *element = self.build(ctx);
1797 ChangeFlags::LAYOUT | ChangeFlags::PAINT
1798 }
1799 }
1800 }
1801 fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
1802 match (self, element) {
1803 (Self::Left(v), TwoArmWidget::Left(w)) => v.teardown(w, ctx),
1804 (Self::Right(v), TwoArmWidget::Right(w)) => v.teardown(w, ctx),
1805 _ => {}
1806 }
1807 }
1808 }
1809
1810 #[test]
1811 fn an_inline_arm_swap_leaks_a_foreign_state_and_disposes_its_owner() {
1812 let _owner = ambient();
1813 let probe = Probe::<2>::new();
1814 let left: TwoArm<ComponentView<Probe<2>>, Empty> =
1815 TwoArm::Left(component(probe.handle()));
1816 let mut next_id = 0u64;
1817 let mut ctx = BuildCtx::new(&mut next_id);
1818 let mut element = View::<()>::build(&left, &mut ctx);
1819 match &mut element {
1820 TwoArmWidget::Left(w) => w.set_witness_for_test(foreign::<Probe<2>>()),
1821 TwoArmWidget::Right(_) => unreachable!("built the left arm"),
1822 }
1823
1824 let right: TwoArm<ComponentView<Probe<2>>, Empty> = TwoArm::Right(Empty);
1825 let flags = View::<()>::rebuild(&right, &left, &mut element, &mut ctx);
1826
1827 assert_eq!(flags, ChangeFlags::LAYOUT | ChangeFlags::PAINT);
1828 assert!(matches!(element, TwoArmWidget::Right(_)), "swapped out");
1829 assert_eq!(
1830 probe.drops.load(Ordering::SeqCst),
1831 0,
1832 "the state is neither dropped nor freed"
1833 );
1834 assert_eq!(
1835 probe.child_teardowns.load(Ordering::SeqCst),
1836 0,
1837 "teardown did not proceed"
1838 );
1839 assert_eq!(probe.disposed.load(Ordering::SeqCst), 1, "owner disposed");
1840 assert!(take_reported::<Counted<2>>(), "the mismatch is reported");
1841 }
1842
1843 #[test]
1844 fn a_matching_widget_tears_down_and_drops_its_state() {
1845 let _owner = ambient();
1846 let probe = Probe::<3>::new();
1847 let view = component(probe.handle());
1848 let mut widget = build_widget::<(), _>(&view);
1849 let mut next_id = 0u64;
1850 let mut ctx = BuildCtx::new(&mut next_id);
1851 View::<()>::rebuild(&view, &view, &mut widget, &mut ctx);
1852 assert_eq!(probe.builds.load(Ordering::SeqCst), 2);
1853 View::<()>::teardown(&view, &mut widget, &mut ctx);
1854 assert_eq!(probe.child_teardowns.load(Ordering::SeqCst), 1);
1855 assert_eq!(probe.disposed.load(Ordering::SeqCst), 1);
1856 drop(widget);
1857 assert_eq!(probe.drops.load(Ordering::SeqCst), 1);
1858 assert!(!take_reported::<Counted<3>>());
1859 }
1860
1861 struct Tiny;
1862 impl Component for Tiny {
1863 type State = u8;
1864 fn init(&self) -> u8 {
1865 0
1866 }
1867 fn build(&self, _state: &mut u8) -> impl View<u8> {
1868 Inert
1869 }
1870 }
1871
1872 struct Huge;
1873 impl Component for Huge {
1874 type State = [u64; 32];
1875 fn init(&self) -> [u64; 32] {
1876 [0; 32]
1877 }
1878 fn build(&self, _state: &mut [u64; 32]) -> impl View<[u64; 32]> {
1879 Inert
1880 }
1881 }
1882
1883 #[test]
1884 fn widget_layout_does_not_depend_on_the_state_layout() {
1885 assert_ne!(size_of::<u8>(), size_of::<[u64; 32]>());
1886 assert_eq!(
1887 size_of::<ComponentWidget<Tiny>>(),
1888 size_of::<ComponentWidget<Huge>>()
1889 );
1890 assert_eq!(
1891 align_of::<ComponentWidget<Tiny>>(),
1892 align_of::<ComponentWidget<Huge>>()
1893 );
1894 macro_rules! same_offset {
1895 ($($field:ident),*) => {$(
1896 assert_eq!(
1897 offset_of!(ComponentWidget<Tiny>, $field),
1898 offset_of!(ComponentWidget<Huge>, $field),
1899 stringify!($field)
1900 );
1901 )*};
1902 }
1903 same_offset!(witness, state, prev, child, owner, next_id, disposed);
1904 assert_eq!(offset_of!(ComponentWidget<Tiny>, witness), 0);
1905 }
1906 }
1907}