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::view::{AnyView, BuildCtx, ChangeFlags, View};
49use crate::widget::{ChildPod, LayoutCtx, PaintCtx, PaintScene, Widget};
50
51pub trait Component: 'static {
63 type State: 'static;
65
66 fn init(&self) -> Self::State;
78
79 fn build(&self, state: &mut Self::State) -> impl View<Self::State>;
93}
94
95pub struct ComponentView<C: Component> {
102 component: C,
103}
104
105pub fn component<C: Component>(c: C) -> ComponentView<C> {
108 ComponentView { component: c }
109}
110
111#[cfg_attr(feature = "hotpatch", repr(C))]
133pub struct ComponentWidget<C: Component> {
134 #[cfg(feature = "hotpatch")]
136 witness: SeamWitness,
137 #[cfg(feature = "hotpatch")]
142 state: Option<Box<C::State>>,
143 #[cfg(not(feature = "hotpatch"))]
146 state: C::State,
147 prev: AnyView<C::State>,
150 child: ChildPod,
153 owner: Owner,
156 next_id: u64,
160 disposed: bool,
163}
164
165impl<C: Component> ComponentWidget<C> {
166 fn dispose(&mut self) {
170 if !self.disposed {
171 self.owner.cleanup();
175 self.disposed = true;
176 }
177 }
178
179 #[cfg(feature = "hotpatch")]
181 fn state_mut(&mut self) -> &mut C::State {
182 self.state
183 .as_deref_mut()
184 .expect("component state is present while the widget is live")
185 }
186
187 #[cfg(not(feature = "hotpatch"))]
189 fn state_mut(&mut self) -> &mut C::State {
190 &mut self.state
191 }
192
193 #[cfg(all(test, feature = "hotpatch"))]
195 fn state(&self) -> &C::State {
196 self.state
197 .as_deref()
198 .expect("component state is present while the widget is live")
199 }
200
201 #[cfg(all(test, not(feature = "hotpatch")))]
203 fn state(&self) -> &C::State {
204 &self.state
205 }
206
207 #[cfg(all(test, feature = "hotpatch"))]
210 fn set_witness_for_test(&mut self, witness: SeamWitness) {
211 self.witness = witness;
212 }
213}
214
215impl<C: Component> Drop for ComponentWidget<C> {
216 fn drop(&mut self) {
217 #[cfg(feature = "hotpatch")]
222 if crate::hotpatch::verify_witness::<C>(self.witness).is_err() {
223 std::mem::forget(self.state.take());
224 }
225 self.dispose();
228 }
229}
230
231impl<Outer: 'static, C: Component> View<Outer> for ComponentView<C> {
232 type Element = ComponentWidget<C>;
233
234 fn build(&self, _ctx: &mut BuildCtx<'_>) -> Self::Element {
235 let owner = Owner::new();
238 #[cfg(feature = "hotpatch")]
240 let witness = SeamWitness::of::<C>();
241 let (state, prev, child, next_id) = owner.with(|| {
242 #[cfg(feature = "hotpatch")]
243 let mut state = Box::new(self.component.init());
244 #[cfg(not(feature = "hotpatch"))]
245 let mut state = self.component.init();
246 #[cfg(feature = "hotpatch")]
250 let view = crate::hotpatch::built_or_inert(crate::hotpatch::build_erased(
251 &self.component,
252 &mut *state,
253 witness,
254 ));
255 #[cfg(not(feature = "hotpatch"))]
256 let view = AnyView::new(self.component.build(&mut state));
257 let mut next_id = 0u64;
259 let mut inner = BuildCtx::new(&mut next_id);
260 inner.set_has_focus(false);
265 let element: Box<dyn Widget> = view.build(&mut inner);
266 (state, view, ChildPod::new(Box::new(element)), next_id)
268 });
269 ComponentWidget {
270 #[cfg(feature = "hotpatch")]
271 witness,
272 #[cfg(feature = "hotpatch")]
273 state: Some(state),
274 #[cfg(not(feature = "hotpatch"))]
275 state,
276 prev,
277 child,
278 owner,
279 next_id,
280 disposed: false,
281 }
282 }
283
284 fn rebuild(
285 &self,
286 _prev: &Self,
287 element: &mut Self::Element,
288 ctx: &mut BuildCtx<'_>,
289 ) -> ChangeFlags {
290 #[cfg(feature = "hotpatch")]
294 if crate::hotpatch::verify_witness::<C>(element.witness).is_err() {
295 return ChangeFlags::NONE;
296 }
297 let outer_has_focus = ctx.has_focus();
308 let owner = element.owner.clone();
309 owner.with(|| {
310 #[cfg(feature = "hotpatch")]
313 let new_view = {
314 let witness = element.witness;
315 match crate::hotpatch::build_erased(&self.component, element.state_mut(), witness) {
316 Ok(view) => view,
317 Err(_) => return ChangeFlags::NONE,
319 }
320 };
321 #[cfg(not(feature = "hotpatch"))]
322 let new_view = AnyView::new(self.component.build(element.state_mut()));
323 let child_focused = element.child.is_focused();
325 let (flags, swapped) = {
326 let boxed = element
327 .child
328 .widget_mut()
329 .downcast_mut::<Box<dyn Widget>>()
330 .expect("component child element is a boxed AnyView widget");
331 let before = {
332 let any: &dyn Any = &**boxed;
333 any.type_id()
334 };
335 let mut inner = BuildCtx::new(&mut element.next_id);
336 inner.set_has_focus(outer_has_focus && child_focused);
341 let (flags, replaced) = inner
349 .track_element_replaced(|inner| new_view.rebuild(&element.prev, boxed, inner));
350 let after = {
351 let any: &dyn Any = &**boxed;
352 any.type_id()
353 };
354 (flags, before != after || replaced)
355 };
356 if swapped {
357 element.child.set_active(false);
362 element.child.set_focused(false);
363 if outer_has_focus && child_focused {
372 crate::event::mark_focus_orphaned();
373 }
374 }
375 element.prev = new_view;
376 flags
377 })
378 }
379
380 fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
381 #[cfg(feature = "hotpatch")]
385 if crate::hotpatch::verify_witness::<C>(element.witness).is_err() {
386 return;
387 }
388 let inner_has_focus = ctx.has_focus() && element.child.is_focused();
396 element.owner.clone().with(|| {
397 let mut next_id = element.next_id;
398 let mut inner = BuildCtx::new(&mut next_id);
399 inner.set_has_focus(inner_has_focus);
400 if let Some(boxed) = element.child.widget_mut().downcast_mut::<Box<dyn Widget>>() {
401 element.prev.teardown(boxed, &mut inner);
402 }
403 });
404 element.dispose();
405 }
406}
407
408impl<C: Component> Widget for ComponentWidget<C> {
409 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
410 self.child.set_origin(Point::ZERO);
414 self.child.layout_child(ctx, bc)
415 }
416
417 fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
418 self.child.paint_child(ctx, scene);
423 }
424
425 fn semantics(&self, ctx: &mut crate::semantics::SemanticsCtx) {
426 self.child.semantics_child(ctx);
430 }
431
432 fn visit_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
433 visitor(&self.child);
437 }
438
439 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
440 let size = ctx.size();
451 let has_focus = ctx.has_focus();
452 let hovered = ctx.is_hovered();
457 let hover_epoch = ctx.hover_epoch();
458 let hover_eligible = ctx.is_hover_eligible();
459 let (result, needs_redraw, captured, hover_claimed, focus_req, focus_rel, ime) = {
465 #[cfg(feature = "hotpatch")]
468 let state_any: &mut dyn Any = self
469 .state
470 .as_deref_mut()
471 .expect("component state is present while the widget is live");
472 #[cfg(not(feature = "hotpatch"))]
473 let state_any: &mut dyn Any = &mut self.state;
474 let mut inner = EventCtx::new(state_any, Point::ZERO, size);
475 inner.set_has_focus(has_focus);
476 inner.set_hovered(hovered);
477 inner.set_hover_epoch(hover_epoch);
478 inner.set_hover_eligible(hover_eligible);
479 let result = route_child(&mut self.child, &mut inner, event);
480 (
481 result,
482 inner.needs_redraw(),
483 inner.is_pointer_captured(),
484 inner.is_hover_claimed(),
485 inner.is_focus_requested(),
486 inner.is_focus_released(),
487 inner.take_ime_state(),
488 )
489 };
490 if needs_redraw {
491 ctx.request_redraw();
492 }
493 if captured {
494 ctx.capture_pointer();
495 }
496 if hover_claimed {
497 ctx.claim_hover();
498 }
499 if focus_req {
500 ctx.request_focus();
501 }
502 if focus_rel {
503 ctx.release_focus();
504 }
505 if let Some(ime) = ime {
506 ctx.publish_ime_state(ime);
507 }
508 result
509 }
510}
511
512fn route_child(pod: &mut ChildPod, ctx: &mut EventCtx<'_>, event: &InputEvent) -> EventResult {
526 if event.is_broadcast() {
527 pod.event_child(ctx, event);
530 return EventResult::Ignored;
531 }
532 if event.is_focus_routed() {
533 if pod.is_focused() {
534 return pod.event_child(ctx, event);
535 }
536 return EventResult::Ignored;
537 }
538 if pod.is_active() {
539 let result = pod.event_child(ctx, event);
540 if releases_capture(event) {
541 pod.set_active(false);
542 }
543 return result;
544 }
545 let inside = pod.contains(event.position());
546 let result = if inside {
547 pod.event_child(ctx, event)
548 } else {
549 EventResult::Ignored
550 };
551 if is_pointer_down(event) && !inside && pod.is_focused() {
552 pod.set_focused(false);
553 }
554 result
555}
556
557fn releases_capture(event: &InputEvent) -> bool {
560 matches!(
561 event,
562 InputEvent::Pointer(p) if matches!(p.phase, PointerPhase::Up | PointerPhase::Cancel)
563 )
564}
565
566fn is_pointer_down(event: &InputEvent) -> bool {
568 matches!(
569 event,
570 InputEvent::Pointer(p) if matches!(p.phase, PointerPhase::Down)
571 )
572}
573
574#[cfg(test)]
575mod tests {
576 use super::*;
577 use crate::event::{
578 CursorIcon, EditingState, ImeState, Key, KeyEvent, Modifiers, NamedKey, PointerButton,
579 PointerEvent, clear_cursor_request, take_cursor_request,
580 };
581 use crate::view::any;
582 use kurbo::Rect;
583 use reactive_graph::owner::{on_cleanup, provide_context, use_context};
584 use std::cell::Cell;
585 use std::rc::Rc;
586 use std::sync::atomic::{AtomicU32, Ordering};
587 use std::sync::{Arc, Mutex};
588
589 fn pointer(phase: PointerPhase, x: f64, y: f64) -> InputEvent {
592 InputEvent::Pointer(PointerEvent {
593 phase,
594 position: Point::new(x, y),
595 button: PointerButton::Primary,
596 })
597 }
598
599 fn key_enter() -> InputEvent {
600 InputEvent::Key(KeyEvent {
601 key: Key::Named(NamedKey::Enter),
602 modifiers: Modifiers::default(),
603 repeat: false,
604 })
605 }
606
607 struct Empty;
610 struct EmptyWidget;
611 impl Widget for EmptyWidget {
612 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
613 bc.constrain(Size::new(10.0, 10.0))
614 }
615 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
616 }
617 impl View<()> for Empty {
618 type Element = EmptyWidget;
619 fn build(&self, _ctx: &mut BuildCtx<'_>) -> EmptyWidget {
620 EmptyWidget
621 }
622 fn rebuild(
623 &self,
624 _prev: &Self,
625 _element: &mut EmptyWidget,
626 _ctx: &mut BuildCtx<'_>,
627 ) -> ChangeFlags {
628 ChangeFlags::NONE
629 }
630 }
631
632 struct OtherLeaf;
634 struct OtherWidget;
635 impl Widget for OtherWidget {
636 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
637 bc.constrain(Size::new(10.0, 10.0))
638 }
639 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
640 }
641 impl View<()> for OtherLeaf {
642 type Element = OtherWidget;
643 fn build(&self, _ctx: &mut BuildCtx<'_>) -> OtherWidget {
644 OtherWidget
645 }
646 fn rebuild(
647 &self,
648 _prev: &Self,
649 _element: &mut OtherWidget,
650 _ctx: &mut BuildCtx<'_>,
651 ) -> ChangeFlags {
652 ChangeFlags::NONE
653 }
654 }
655
656 struct CounterState {
659 count: u32,
660 }
661
662 struct ClickCounter {
666 label: &'static str,
667 }
668
669 impl Component for ClickCounter {
670 type State = CounterState;
671 fn init(&self) -> CounterState {
672 CounterState { count: 0 }
673 }
674 fn build(&self, state: &mut CounterState) -> impl View<CounterState> {
675 any(CounterButtonView {
676 count: state.count,
677 label: self.label,
678 })
679 }
680 }
681
682 struct CounterButtonView {
683 count: u32,
684 label: &'static str,
685 }
686 struct CounterButtonWidget {
687 count: u32,
688 label: &'static str,
689 }
690 impl View<CounterState> for CounterButtonView {
691 type Element = CounterButtonWidget;
692 fn build(&self, _ctx: &mut BuildCtx<'_>) -> CounterButtonWidget {
693 CounterButtonWidget {
694 count: self.count,
695 label: self.label,
696 }
697 }
698 fn rebuild(
699 &self,
700 prev: &Self,
701 element: &mut CounterButtonWidget,
702 _ctx: &mut BuildCtx<'_>,
703 ) -> ChangeFlags {
704 let mut flags = ChangeFlags::NONE;
705 if prev.count != self.count || prev.label != self.label {
706 element.count = self.count;
707 element.label = self.label;
708 flags = ChangeFlags::PAINT;
709 }
710 flags
711 }
712 }
713 impl Widget for CounterButtonWidget {
714 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
715 bc.constrain(Size::new(40.0, 20.0))
716 }
717 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
718 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
719 if let InputEvent::Pointer(p) = event
720 && p.phase == PointerPhase::Down
721 {
722 ctx.state_mut::<CounterState>().count += 1;
723 ctx.request_redraw();
724 return EventResult::Handled;
725 }
726 EventResult::Ignored
727 }
728 }
729
730 fn ambient() -> Owner {
733 let owner = Owner::new();
734 owner.set();
735 owner
736 }
737
738 fn build_widget<Outer: 'static, C: Component>(view: &ComponentView<C>) -> ComponentWidget<C> {
739 let mut next_id = 0u64;
740 let mut ctx = BuildCtx::new(&mut next_id);
741 View::<Outer>::build(view, &mut ctx)
742 }
743
744 #[test]
745 fn local_state_survives_parent_driven_rebuild() {
746 let _owner = ambient();
747 let v1 = component(ClickCounter { label: "a" });
748 let mut widget = build_widget::<(), _>(&v1);
749 let mut lctx = LayoutCtx::new();
750 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 20.0)));
751
752 let mut outer = ();
755 for _ in 0..3 {
756 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 20.0));
757 widget.event(&mut ectx, &pointer(PointerPhase::Down, 5.0, 5.0));
758 }
759 assert_eq!(widget.state().count, 3);
760
761 let v2 = component(ClickCounter { label: "changed" });
763 let mut next_id = 0u64;
764 let mut ctx = BuildCtx::new(&mut next_id);
765 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
766 assert_eq!(
767 widget.state().count,
768 3,
769 "local state retained across rebuild"
770 );
771 }
772
773 #[derive(Clone)]
776 struct Theme(i32);
777
778 struct Provider {
781 sink: Arc<Mutex<Option<i32>>>,
782 }
783 impl Component for Provider {
784 type State = ();
785 fn init(&self) {}
786 fn build(&self, _state: &mut ()) -> impl View<()> {
787 provide_context(Theme(7));
788 any(component(Reader {
789 sink: self.sink.clone(),
790 }))
791 }
792 }
793
794 struct Reader {
796 sink: Arc<Mutex<Option<i32>>>,
797 }
798 impl Component for Reader {
799 type State = ();
800 fn init(&self) {}
801 fn build(&self, _state: &mut ()) -> impl View<()> {
802 *self.sink.lock().unwrap() = use_context::<Theme>().map(|t| t.0);
803 any(Empty)
804 }
805 }
806
807 struct SecretProvider;
810 impl Component for SecretProvider {
811 type State = ();
812 fn init(&self) {}
813 fn build(&self, _state: &mut ()) -> impl View<()> {
814 provide_context(Theme(99));
815 any(Empty)
816 }
817 }
818
819 #[test]
820 fn use_context_resolves_ancestor_provided_value() {
821 let _owner = ambient();
822 let sink = Arc::new(Mutex::new(None));
823 let v = component(Provider { sink: sink.clone() });
824 let _widget = build_widget::<(), _>(&v);
825 assert_eq!(*sink.lock().unwrap(), Some(7));
826 }
827
828 #[test]
829 fn sibling_does_not_see_a_cousins_context() {
830 let parent = ambient();
831 let reader_sink = Arc::new(Mutex::new(Some(-1)));
832 parent.with(|| {
835 let secret = component(SecretProvider);
836 let _s = build_widget::<(), _>(&secret);
837 let reader = component(Reader {
838 sink: reader_sink.clone(),
839 });
840 let _r = build_widget::<(), _>(&reader);
841 });
842 assert_eq!(*reader_sink.lock().unwrap(), None);
843 }
844
845 struct Disposable {
848 probe: Arc<AtomicU32>,
849 }
850 impl Component for Disposable {
851 type State = ();
852 fn init(&self) {
853 let probe = self.probe.clone();
854 on_cleanup(move || {
856 probe.fetch_add(1, Ordering::SeqCst);
857 });
858 }
859 fn build(&self, _state: &mut ()) -> impl View<()> {
860 any(Empty)
861 }
862 }
863
864 #[test]
865 fn anyview_type_swap_disposes_component_owner_once() {
866 let _owner = ambient();
867 let probe = Arc::new(AtomicU32::new(0));
868 let mut next_id = 0u64;
869 let mut ctx = BuildCtx::new(&mut next_id);
870
871 let prev = any::<(), _>(component(Disposable {
872 probe: probe.clone(),
873 }));
874 let mut element: Box<dyn Widget> = prev.build(&mut ctx);
875 assert_eq!(probe.load(Ordering::SeqCst), 0);
876
877 let next = any::<(), _>(OtherLeaf);
880 next.rebuild(&prev, &mut element, &mut ctx);
881 assert_eq!(probe.load(Ordering::SeqCst), 1);
882
883 drop(element);
885 assert_eq!(probe.load(Ordering::SeqCst), 1);
886 }
887
888 struct ConcreteComp;
893 impl Component for ConcreteComp {
894 type State = ();
895 fn init(&self) {}
896 fn build(&self, _state: &mut ()) -> impl View<()> {
897 Empty
898 }
899 }
900
901 struct ErasedComp;
904 impl Component for ErasedComp {
905 type State = ();
906 fn init(&self) {}
907 fn build(&self, _state: &mut ()) -> impl View<()> {
908 any(Empty)
909 }
910 }
911
912 fn child_is_empty_widget<C: Component>(widget: &mut ComponentWidget<C>) -> bool {
915 let boxed = widget
916 .child
917 .widget_mut()
918 .downcast_mut::<Box<dyn Widget>>()
919 .expect("double-boxed AnyView element");
920 let any: &dyn Any = &**boxed;
921 any.is::<EmptyWidget>()
922 }
923
924 #[test]
925 fn build_result_is_erased_exactly_once_at_the_boundary() {
926 let _owner = ambient();
927
928 let concrete = component(ConcreteComp);
929 let mut widget = build_widget::<(), _>(&concrete);
930 assert!(child_is_empty_widget(&mut widget), "concrete body, build");
931 let mut next_id = 0u64;
932 let mut ctx = BuildCtx::new(&mut next_id);
933 View::<()>::rebuild(&concrete, &concrete, &mut widget, &mut ctx);
934 assert!(child_is_empty_widget(&mut widget), "concrete body, rebuild");
935
936 let erased = component(ErasedComp);
937 let mut widget = build_widget::<(), _>(&erased);
938 assert!(child_is_empty_widget(&mut widget), "any(..) body, build");
939 let mut next_id = 0u64;
940 let mut ctx = BuildCtx::new(&mut next_id);
941 View::<()>::rebuild(&erased, &erased, &mut widget, &mut ctx);
942 assert!(child_is_empty_widget(&mut widget), "any(..) body, rebuild");
943 }
944
945 struct SwapComp {
952 swapped: Rc<Cell<bool>>,
953 }
954 impl Component for SwapComp {
955 type State = ();
956 fn init(&self) {}
957 fn build(&self, _state: &mut ()) -> impl View<()> {
958 if self.swapped.get() {
959 any(OtherLeaf)
960 } else {
961 any(Empty)
962 }
963 }
964 }
965
966 #[test]
967 fn component_swap_arm_marks_the_orphan_only_on_a_live_chain() {
968 fn swap_child(outer_live: bool) -> (bool, bool) {
971 let _ = crate::event::take_focus_orphaned();
972 let _owner = ambient();
973 let swapped = Rc::new(Cell::new(false));
974 let v1 = component(SwapComp {
975 swapped: swapped.clone(),
976 });
977 let mut widget = build_widget::<(), _>(&v1);
978 widget.child.set_focused(true);
980
981 swapped.set(true);
982 let v2 = component(SwapComp {
983 swapped: swapped.clone(),
984 });
985 let mut next_id = 0u64;
986 let mut ctx = BuildCtx::new(&mut next_id);
987 ctx.set_has_focus(outer_live);
989 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
990 (
991 widget.child.is_focused(),
992 crate::event::take_focus_orphaned(),
993 )
994 }
995
996 let (still_focused, marked) = swap_child(true);
997 assert!(
998 !still_focused,
999 "a swapped-in widget must not inherit the old focus link"
1000 );
1001 assert!(
1002 marked,
1003 "a live focus path dying inside the component's swap releases the session"
1004 );
1005
1006 let (still_focused, marked) = swap_child(false);
1007 assert!(!still_focused, "the flag is dropped either way");
1008 assert!(
1009 !marked,
1010 "the same swap under an already-blurred ancestor owns no session to release"
1011 );
1012 }
1013
1014 enum Arms {
1019 Left(Empty),
1020 Right(OtherLeaf),
1021 }
1022 enum ArmsWidget {
1023 Left(EmptyWidget),
1024 Right(OtherWidget),
1025 }
1026 impl Widget for ArmsWidget {
1027 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1028 match self {
1029 Self::Left(w) => w.layout(ctx, bc),
1030 Self::Right(w) => w.layout(ctx, bc),
1031 }
1032 }
1033 fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
1034 match self {
1035 Self::Left(w) => w.paint(ctx, scene),
1036 Self::Right(w) => w.paint(ctx, scene),
1037 }
1038 }
1039 }
1040 impl View<()> for Arms {
1041 type Element = ArmsWidget;
1042 fn build(&self, ctx: &mut BuildCtx<'_>) -> ArmsWidget {
1043 match self {
1044 Self::Left(v) => ArmsWidget::Left(v.build(ctx)),
1045 Self::Right(v) => ArmsWidget::Right(v.build(ctx)),
1046 }
1047 }
1048 fn rebuild(
1049 &self,
1050 prev: &Self,
1051 element: &mut ArmsWidget,
1052 ctx: &mut BuildCtx<'_>,
1053 ) -> ChangeFlags {
1054 match (self, prev, &mut *element) {
1055 (Self::Left(v), Self::Left(p), ArmsWidget::Left(w)) => v.rebuild(p, w, ctx),
1056 (Self::Right(v), Self::Right(p), ArmsWidget::Right(w)) => v.rebuild(p, w, ctx),
1057 _ => {
1058 ctx.mark_element_replaced();
1059 *element = self.build(ctx);
1060 ChangeFlags::LAYOUT | ChangeFlags::PAINT
1061 }
1062 }
1063 }
1064 }
1065
1066 struct ArmsComp {
1069 right: Rc<Cell<bool>>,
1070 }
1071 impl Component for ArmsComp {
1072 type State = ();
1073 fn init(&self) {}
1074 fn build(&self, _state: &mut ()) -> impl View<()> {
1075 if self.right.get() {
1076 Arms::Right(OtherLeaf)
1077 } else {
1078 Arms::Left(Empty)
1079 }
1080 }
1081 }
1082
1083 #[test]
1084 fn component_enum_body_arm_swap_clears_the_child_pod_flags() {
1085 fn rebuild_arms(right_now: bool) -> (bool, bool, bool) {
1089 let _ = crate::event::take_focus_orphaned();
1090 let _owner = ambient();
1091 let right = Rc::new(Cell::new(false));
1092 let v1 = component(ArmsComp {
1093 right: right.clone(),
1094 });
1095 let mut widget = build_widget::<(), _>(&v1);
1096 widget.child.set_active(true);
1097 widget.child.set_focused(true);
1098
1099 right.set(right_now);
1100 let v2 = component(ArmsComp {
1101 right: right.clone(),
1102 });
1103 let mut next_id = 0u64;
1104 let mut ctx = BuildCtx::new(&mut next_id);
1105 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
1106 (
1107 widget.child.is_active(),
1108 widget.child.is_focused(),
1109 crate::event::take_focus_orphaned(),
1110 )
1111 }
1112
1113 let (active, focused, marked) = rebuild_arms(true);
1114 assert!(
1115 !active,
1116 "the fresh arm never saw the Down: the capture is dropped"
1117 );
1118 assert!(
1119 !focused,
1120 "the fresh arm never claimed focus: the link is dropped"
1121 );
1122 assert!(
1123 marked,
1124 "a live focus path dying inside the arm swap releases the session"
1125 );
1126
1127 let (active, focused, marked) = rebuild_arms(false);
1128 assert!(active, "a same-arm rebuild keeps the in-flight capture");
1129 assert!(focused, "a same-arm rebuild keeps the focus path");
1130 assert!(!marked, "a same-arm rebuild orphans nothing");
1131 }
1132
1133 #[test]
1134 fn component_content_only_rebuild_keeps_focus_and_marks_nothing() {
1135 let _ = crate::event::take_focus_orphaned();
1139 let _owner = ambient();
1140 let v1 = component(Labeled { text: "a" });
1141 let mut widget = build_widget::<(), _>(&v1);
1142 widget.child.set_focused(true);
1143
1144 let v2 = component(Labeled { text: "b" });
1145 let mut next_id = 0u64;
1146 let mut ctx = BuildCtx::new(&mut next_id);
1147 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
1148
1149 assert!(
1150 widget.child.is_focused(),
1151 "a content-only rebuild keeps the recorded focus path"
1152 );
1153 assert!(
1154 !crate::event::take_focus_orphaned(),
1155 "an in-place child rebuild must not orphan the focus session"
1156 );
1157 }
1158
1159 #[test]
1160 fn explicit_teardown_disposes_owner_once_and_drop_is_idempotent() {
1161 let _owner = ambient();
1162 let probe = Arc::new(AtomicU32::new(0));
1163 let view = component(Disposable {
1164 probe: probe.clone(),
1165 });
1166 let mut widget = build_widget::<(), _>(&view);
1167
1168 let mut next_id = 0u64;
1169 let mut ctx = BuildCtx::new(&mut next_id);
1170 View::<()>::teardown(&view, &mut widget, &mut ctx);
1171 assert_eq!(probe.load(Ordering::SeqCst), 1);
1172
1173 drop(widget);
1175 assert_eq!(probe.load(Ordering::SeqCst), 1);
1176 }
1177
1178 struct CaptureWidget {
1183 pressed: bool,
1184 }
1185 impl Widget for CaptureWidget {
1186 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1187 bc.constrain(Size::new(40.0, 40.0))
1188 }
1189 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1190 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
1191 if let InputEvent::Pointer(p) = event {
1192 match p.phase {
1193 PointerPhase::Down => {
1194 self.pressed = true;
1195 ctx.capture_pointer();
1196 ctx.request_redraw();
1197 return EventResult::Handled;
1198 }
1199 PointerPhase::Move => return EventResult::Handled,
1200 PointerPhase::Up | PointerPhase::Cancel => {
1202 self.pressed = false;
1203 return EventResult::Handled;
1204 }
1205 }
1206 }
1207 EventResult::Ignored
1208 }
1209 }
1210 struct CaptureView;
1211 impl View<()> for CaptureView {
1212 type Element = CaptureWidget;
1213 fn build(&self, _ctx: &mut BuildCtx<'_>) -> CaptureWidget {
1214 CaptureWidget { pressed: false }
1215 }
1216 fn rebuild(
1217 &self,
1218 _prev: &Self,
1219 _element: &mut CaptureWidget,
1220 _ctx: &mut BuildCtx<'_>,
1221 ) -> ChangeFlags {
1222 ChangeFlags::NONE
1223 }
1224 }
1225 struct CaptureComp;
1226 impl Component for CaptureComp {
1227 type State = ();
1228 fn init(&self) {}
1229 fn build(&self, _state: &mut ()) -> impl View<()> {
1230 any(CaptureView)
1231 }
1232 }
1233
1234 fn inner_widget<C: Component, W: Widget>(w: &mut ComponentWidget<C>) -> &mut W {
1237 w.child
1238 .widget_mut()
1239 .downcast_mut::<Box<dyn Widget>>()
1240 .expect("double-boxed AnyView element")
1241 .downcast_mut::<W>()
1242 .expect("inner concrete widget")
1243 }
1244
1245 #[test]
1246 fn capture_survives_drag_outside_bounds_and_mirrors_outward() {
1247 let _owner = ambient();
1248 let view = component(CaptureComp);
1249 let mut widget = build_widget::<(), _>(&view);
1250 let mut lctx = LayoutCtx::new();
1251 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 40.0)));
1252
1253 let mut outer = ();
1254 let captured = {
1256 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1257 widget.event(&mut ectx, &pointer(PointerPhase::Down, 5.0, 5.0));
1258 ectx.is_pointer_captured()
1259 };
1260 assert!(captured, "inner capture bubbles to the outer context");
1261 assert!(
1262 widget.child.is_active(),
1263 "component pod records the capture"
1264 );
1265
1266 {
1268 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1269 let r = widget.event(&mut ectx, &pointer(PointerPhase::Move, 500.0, 500.0));
1270 assert_eq!(r, EventResult::Handled, "drag outside bounds still routed");
1271 }
1272 assert!(widget.child.is_active(), "capture survives the drag");
1273
1274 {
1276 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1277 widget.event(&mut ectx, &pointer(PointerPhase::Up, 500.0, 500.0));
1278 }
1279 assert!(!widget.child.is_active(), "Up auto-releases the capture");
1280 assert!(!inner_widget::<_, CaptureWidget>(&mut widget).pressed);
1281 }
1282
1283 #[test]
1284 fn synthesized_cancel_over_unit_state_is_safe_and_clears_pressed() {
1285 let _owner = ambient();
1286 let view = component(CaptureComp);
1287 let mut widget = build_widget::<(), _>(&view);
1288 let mut lctx = LayoutCtx::new();
1289 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 40.0)));
1290
1291 let mut outer = ();
1293 {
1294 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1295 widget.event(&mut ectx, &pointer(PointerPhase::Down, 5.0, 5.0));
1296 }
1297 assert!(inner_widget::<_, CaptureWidget>(&mut widget).pressed);
1298
1299 let mut dummy = ();
1302 {
1303 let mut ectx = EventCtx::new(&mut dummy, Point::ZERO, Size::new(40.0, 40.0));
1304 widget.event(&mut ectx, &pointer(PointerPhase::Cancel, 0.0, 0.0));
1305 }
1306 assert!(
1307 !inner_widget::<_, CaptureWidget>(&mut widget).pressed,
1308 "Cancel cleared pressed across the boundary"
1309 );
1310 assert!(!widget.child.is_active(), "Cancel released the capture");
1311 }
1312
1313 struct CursorLeafWidget;
1315 impl Widget for CursorLeafWidget {
1316 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1317 bc.constrain(Size::new(40.0, 40.0))
1318 }
1319 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1320 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
1321 if let InputEvent::Pointer(p) = event
1322 && p.phase == PointerPhase::Move
1323 {
1324 ctx.set_cursor(CursorIcon::Text);
1325 }
1326 EventResult::Ignored
1327 }
1328 }
1329 struct CursorLeafView;
1330 impl View<()> for CursorLeafView {
1331 type Element = CursorLeafWidget;
1332 fn build(&self, _ctx: &mut BuildCtx<'_>) -> CursorLeafWidget {
1333 CursorLeafWidget
1334 }
1335 fn rebuild(
1336 &self,
1337 _prev: &Self,
1338 _element: &mut CursorLeafWidget,
1339 _ctx: &mut BuildCtx<'_>,
1340 ) -> ChangeFlags {
1341 ChangeFlags::NONE
1342 }
1343 }
1344 struct CursorComp;
1345 impl Component for CursorComp {
1346 type State = ();
1347 fn init(&self) {}
1348 fn build(&self, _state: &mut ()) -> impl View<()> {
1349 any(CursorLeafView)
1350 }
1351 }
1352
1353 #[test]
1358 fn a_cursor_request_crosses_the_component_boundary() {
1359 let _owner = ambient();
1360 let view = component(CursorComp);
1361 let mut widget = build_widget::<(), _>(&view);
1362 let mut lctx = LayoutCtx::new();
1363 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 40.0)));
1364
1365 clear_cursor_request();
1368 let mut outer = ();
1369 {
1370 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1371 widget.event(&mut ectx, &pointer(PointerPhase::Move, 5.0, 5.0));
1372 }
1373 assert_eq!(
1374 take_cursor_request(),
1375 Some(CursorIcon::Text),
1376 "a request from below the component boundary reaches the root"
1377 );
1378 }
1379
1380 struct ImeLeafWidget;
1383 impl Widget for ImeLeafWidget {
1384 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1385 bc.constrain(Size::new(100.0, 100.0))
1386 }
1387 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1388 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
1389 match event {
1390 InputEvent::Pointer(p) if p.phase == PointerPhase::Down => {
1391 if p.position.x < 50.0 {
1392 ctx.request_focus();
1393 ctx.publish_ime_state(ImeState {
1394 active: true,
1395 editing: EditingState {
1396 text: "abc".to_string(),
1397 selection_base: 3,
1398 selection_extent: 3,
1399 composing_base: -1,
1400 composing_extent: -1,
1401 },
1402 caret: Some(Rect::new(0.0, 0.0, 1.0, 12.0)),
1403 content_type: Default::default(),
1404 suppress_soft_keyboard: false,
1405 });
1406 }
1407 EventResult::Handled
1408 }
1409 InputEvent::Key(_) | InputEvent::Ime(_) => EventResult::Handled,
1410 _ => EventResult::Ignored,
1411 }
1412 }
1413 }
1414 struct ImeLeafView;
1415 impl View<()> for ImeLeafView {
1416 type Element = ImeLeafWidget;
1417 fn build(&self, _ctx: &mut BuildCtx<'_>) -> ImeLeafWidget {
1418 ImeLeafWidget
1419 }
1420 fn rebuild(
1421 &self,
1422 _prev: &Self,
1423 _element: &mut ImeLeafWidget,
1424 _ctx: &mut BuildCtx<'_>,
1425 ) -> ChangeFlags {
1426 ChangeFlags::NONE
1427 }
1428 }
1429 struct ImeComp;
1430 impl Component for ImeComp {
1431 type State = ();
1432 fn init(&self) {}
1433 fn build(&self, _state: &mut ()) -> impl View<()> {
1434 any(ImeLeafView)
1435 }
1436 }
1437
1438 #[test]
1439 fn focus_ime_and_blur_surface_through_render_root() {
1440 use crate::app::RenderRoot;
1441 let _owner = ambient();
1442 let mut root: RenderRoot<(), ComponentView<ImeComp>> = RenderRoot::new();
1443 let mut state = ();
1444 root.rebuild(&mut |_s: &mut ()| component(ImeComp), &mut state);
1445 root.layout(Size::new(100.0, 100.0));
1446
1447 assert!(!root.is_focus_active());
1448 assert!(root.ime_state().is_none());
1449
1450 root.event(&mut state, &pointer(PointerPhase::Down, 10.0, 10.0));
1453 assert!(root.is_focus_active(), "focus request crossed the boundary");
1454 let ime = root.ime_state().expect("IME surface surfaced at the root");
1455 assert!(ime.active);
1456 assert_eq!(ime.editing.text, "abc");
1457
1458 let outcome = root.event(&mut state, &key_enter());
1460 assert!(outcome.handled, "Key routed to the focused inner leaf");
1461
1462 root.event(&mut state, &pointer(PointerPhase::Down, 80.0, 10.0));
1465 assert!(!root.is_focus_active(), "blur crossed the boundary");
1466 assert!(root.ime_state().is_none());
1467 }
1468
1469 struct TextLeafView {
1472 text: &'static str,
1473 }
1474 struct TextLeafWidget {
1475 text: &'static str,
1476 }
1477 impl View<()> for TextLeafView {
1478 type Element = TextLeafWidget;
1479 fn build(&self, _ctx: &mut BuildCtx<'_>) -> TextLeafWidget {
1480 TextLeafWidget { text: self.text }
1481 }
1482 fn rebuild(
1483 &self,
1484 prev: &Self,
1485 element: &mut TextLeafWidget,
1486 _ctx: &mut BuildCtx<'_>,
1487 ) -> ChangeFlags {
1488 if prev.text != self.text {
1489 element.text = self.text;
1490 ChangeFlags::PAINT
1491 } else {
1492 ChangeFlags::NONE
1493 }
1494 }
1495 }
1496 impl Widget for TextLeafWidget {
1497 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1498 bc.constrain(Size::new(10.0, 10.0))
1499 }
1500 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1501 }
1502 struct Labeled {
1503 text: &'static str,
1504 }
1505 impl Component for Labeled {
1506 type State = ();
1507 fn init(&self) {}
1508 fn build(&self, _state: &mut ()) -> impl View<()> {
1509 any(TextLeafView { text: self.text })
1510 }
1511 }
1512
1513 #[test]
1514 fn child_paint_flags_bubble_out_of_component_rebuild() {
1515 let _owner = ambient();
1516 let v1 = component(Labeled { text: "a" });
1517 let mut widget = build_widget::<(), _>(&v1);
1518
1519 let v_same = component(Labeled { text: "a" });
1521 let mut next_id = 0u64;
1522 let mut ctx = BuildCtx::new(&mut next_id);
1523 let flags = View::<()>::rebuild(&v_same, &v1, &mut widget, &mut ctx);
1524 assert_eq!(flags, ChangeFlags::NONE);
1525
1526 let v2 = component(Labeled { text: "b" });
1529 let flags = View::<()>::rebuild(&v2, &v_same, &mut widget, &mut ctx);
1530 assert_eq!(flags, ChangeFlags::PAINT);
1531 }
1532
1533 #[cfg(feature = "hotpatch")]
1536 mod seam {
1537 use super::*;
1538 use crate::hotpatch::{Inert, SeamWitness};
1539 use std::any::type_name;
1540 use std::mem::offset_of;
1541
1542 fn foreign<C: Component>() -> SeamWitness {
1545 let own = SeamWitness::of::<C>();
1546 SeamWitness {
1547 size_state: own.size_state + 8,
1548 ..own
1549 }
1550 }
1551
1552 fn take_reported<S>() -> bool {
1554 let mine: Vec<_> = frust_hotpatch::pending_layout_mismatches()
1555 .into_iter()
1556 .filter(|m| m.type_name == type_name::<S>())
1557 .collect();
1558 frust_hotpatch::mark_layout_mismatches_reported(&mine);
1559 !mine.is_empty()
1560 }
1561
1562 struct Counted<const N: usize> {
1565 value: u32,
1566 drops: Arc<AtomicU32>,
1567 }
1568 impl<const N: usize> Drop for Counted<N> {
1569 fn drop(&mut self) {
1570 self.drops.fetch_add(1, Ordering::SeqCst);
1571 }
1572 }
1573
1574 struct Probe<const N: usize> {
1577 builds: Arc<AtomicU32>,
1578 drops: Arc<AtomicU32>,
1579 disposed: Arc<AtomicU32>,
1580 child_teardowns: Arc<AtomicU32>,
1581 }
1582 impl<const N: usize> Probe<N> {
1583 fn new() -> Self {
1584 Self {
1585 builds: Arc::new(AtomicU32::new(0)),
1586 drops: Arc::new(AtomicU32::new(0)),
1587 disposed: Arc::new(AtomicU32::new(0)),
1588 child_teardowns: Arc::new(AtomicU32::new(0)),
1589 }
1590 }
1591 fn handle(&self) -> Self {
1592 Self {
1593 builds: self.builds.clone(),
1594 drops: self.drops.clone(),
1595 disposed: self.disposed.clone(),
1596 child_teardowns: self.child_teardowns.clone(),
1597 }
1598 }
1599 }
1600 impl<const N: usize> Component for Probe<N> {
1601 type State = Counted<N>;
1602 fn init(&self) -> Counted<N> {
1603 let disposed = self.disposed.clone();
1604 on_cleanup(move || {
1605 disposed.fetch_add(1, Ordering::SeqCst);
1606 });
1607 Counted {
1608 value: 0,
1609 drops: self.drops.clone(),
1610 }
1611 }
1612 fn build(&self, state: &mut Counted<N>) -> impl View<Counted<N>> {
1613 self.builds.fetch_add(1, Ordering::SeqCst);
1614 state.value += 1;
1615 TeardownProbe {
1616 teardowns: self.child_teardowns.clone(),
1617 }
1618 }
1619 }
1620
1621 struct TeardownProbe {
1623 teardowns: Arc<AtomicU32>,
1624 }
1625 impl<S: 'static> View<S> for TeardownProbe {
1626 type Element = EmptyWidget;
1627 fn build(&self, _ctx: &mut BuildCtx<'_>) -> EmptyWidget {
1628 EmptyWidget
1629 }
1630 fn rebuild(
1631 &self,
1632 _prev: &Self,
1633 _element: &mut EmptyWidget,
1634 _ctx: &mut BuildCtx<'_>,
1635 ) -> ChangeFlags {
1636 ChangeFlags::NONE
1637 }
1638 fn teardown(&self, _element: &mut EmptyWidget, _ctx: &mut BuildCtx<'_>) {
1639 self.teardowns.fetch_add(1, Ordering::SeqCst);
1640 }
1641 }
1642
1643 struct Parent {
1646 nested: Probe<1>,
1647 }
1648 impl Component for Parent {
1649 type State = ();
1650 fn init(&self) {}
1651 fn build(&self, _state: &mut ()) -> impl View<()> {
1652 component(self.nested.handle())
1653 }
1654 }
1655
1656 fn nested_of(parent: &mut ComponentWidget<Parent>) -> &mut ComponentWidget<Probe<1>> {
1657 let boxed = parent
1658 .child
1659 .widget_mut()
1660 .downcast_mut::<Box<dyn Widget>>()
1661 .expect("component child element is a boxed AnyView widget");
1662 (**boxed)
1663 .downcast_mut::<ComponentWidget<Probe<1>>>()
1664 .expect("the parent's subtree is the nested component")
1665 }
1666
1667 #[test]
1668 fn a_nested_widget_with_a_foreign_witness_is_never_rebuilt() {
1669 let _owner = ambient();
1670 let nested = Probe::<1>::new();
1671 let v1 = component(Parent {
1672 nested: nested.handle(),
1673 });
1674 let mut widget = build_widget::<(), _>(&v1);
1675 assert_eq!(nested.builds.load(Ordering::SeqCst), 1);
1676
1677 nested_of(&mut widget).set_witness_for_test(foreign::<Probe<1>>());
1678 let v2 = component(Parent {
1679 nested: nested.handle(),
1680 });
1681 let mut next_id = 0u64;
1682 let mut ctx = BuildCtx::new(&mut next_id);
1683 let flags = View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
1684
1685 assert_eq!(flags, ChangeFlags::NONE);
1686 assert_eq!(nested.builds.load(Ordering::SeqCst), 1, "never rebuilt");
1687 assert_eq!(nested_of(&mut widget).state().value, 1, "state untouched");
1688 assert!(take_reported::<Counted<1>>(), "the mismatch is reported");
1689
1690 drop(widget);
1692 assert_eq!(nested.drops.load(Ordering::SeqCst), 0);
1693 assert_eq!(nested.disposed.load(Ordering::SeqCst), 1);
1694 assert!(take_reported::<Counted<1>>());
1695 }
1696
1697 enum TwoArm<L, R> {
1700 Left(L),
1701 Right(R),
1702 }
1703 enum TwoArmWidget<L, R> {
1704 Left(L),
1705 Right(R),
1706 }
1707 impl<L: Widget, R: Widget> Widget for TwoArmWidget<L, R> {
1708 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1709 match self {
1710 Self::Left(w) => w.layout(ctx, bc),
1711 Self::Right(w) => w.layout(ctx, bc),
1712 }
1713 }
1714 fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
1715 match self {
1716 Self::Left(w) => w.paint(ctx, scene),
1717 Self::Right(w) => w.paint(ctx, scene),
1718 }
1719 }
1720 }
1721 impl<S: 'static, L: View<S>, R: View<S>> View<S> for TwoArm<L, R> {
1722 type Element = TwoArmWidget<L::Element, R::Element>;
1723 fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element {
1724 match self {
1725 Self::Left(v) => TwoArmWidget::Left(v.build(ctx)),
1726 Self::Right(v) => TwoArmWidget::Right(v.build(ctx)),
1727 }
1728 }
1729 fn rebuild(
1730 &self,
1731 prev: &Self,
1732 element: &mut Self::Element,
1733 ctx: &mut BuildCtx<'_>,
1734 ) -> ChangeFlags {
1735 match (self, prev, &mut *element) {
1736 (Self::Left(v), Self::Left(p), TwoArmWidget::Left(w)) => v.rebuild(p, w, ctx),
1737 (Self::Right(v), Self::Right(p), TwoArmWidget::Right(w)) => {
1738 v.rebuild(p, w, ctx)
1739 }
1740 _ => {
1741 prev.teardown(element, ctx);
1744 *element = self.build(ctx);
1745 ChangeFlags::LAYOUT | ChangeFlags::PAINT
1746 }
1747 }
1748 }
1749 fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
1750 match (self, element) {
1751 (Self::Left(v), TwoArmWidget::Left(w)) => v.teardown(w, ctx),
1752 (Self::Right(v), TwoArmWidget::Right(w)) => v.teardown(w, ctx),
1753 _ => {}
1754 }
1755 }
1756 }
1757
1758 #[test]
1759 fn an_inline_arm_swap_leaks_a_foreign_state_and_disposes_its_owner() {
1760 let _owner = ambient();
1761 let probe = Probe::<2>::new();
1762 let left: TwoArm<ComponentView<Probe<2>>, Empty> =
1763 TwoArm::Left(component(probe.handle()));
1764 let mut next_id = 0u64;
1765 let mut ctx = BuildCtx::new(&mut next_id);
1766 let mut element = View::<()>::build(&left, &mut ctx);
1767 match &mut element {
1768 TwoArmWidget::Left(w) => w.set_witness_for_test(foreign::<Probe<2>>()),
1769 TwoArmWidget::Right(_) => unreachable!("built the left arm"),
1770 }
1771
1772 let right: TwoArm<ComponentView<Probe<2>>, Empty> = TwoArm::Right(Empty);
1773 let flags = View::<()>::rebuild(&right, &left, &mut element, &mut ctx);
1774
1775 assert_eq!(flags, ChangeFlags::LAYOUT | ChangeFlags::PAINT);
1776 assert!(matches!(element, TwoArmWidget::Right(_)), "swapped out");
1777 assert_eq!(
1778 probe.drops.load(Ordering::SeqCst),
1779 0,
1780 "the state is neither dropped nor freed"
1781 );
1782 assert_eq!(
1783 probe.child_teardowns.load(Ordering::SeqCst),
1784 0,
1785 "teardown did not proceed"
1786 );
1787 assert_eq!(probe.disposed.load(Ordering::SeqCst), 1, "owner disposed");
1788 assert!(take_reported::<Counted<2>>(), "the mismatch is reported");
1789 }
1790
1791 #[test]
1792 fn a_matching_widget_tears_down_and_drops_its_state() {
1793 let _owner = ambient();
1794 let probe = Probe::<3>::new();
1795 let view = component(probe.handle());
1796 let mut widget = build_widget::<(), _>(&view);
1797 let mut next_id = 0u64;
1798 let mut ctx = BuildCtx::new(&mut next_id);
1799 View::<()>::rebuild(&view, &view, &mut widget, &mut ctx);
1800 assert_eq!(probe.builds.load(Ordering::SeqCst), 2);
1801 View::<()>::teardown(&view, &mut widget, &mut ctx);
1802 assert_eq!(probe.child_teardowns.load(Ordering::SeqCst), 1);
1803 assert_eq!(probe.disposed.load(Ordering::SeqCst), 1);
1804 drop(widget);
1805 assert_eq!(probe.drops.load(Ordering::SeqCst), 1);
1806 assert!(!take_reported::<Counted<3>>());
1807 }
1808
1809 struct Tiny;
1810 impl Component for Tiny {
1811 type State = u8;
1812 fn init(&self) -> u8 {
1813 0
1814 }
1815 fn build(&self, _state: &mut u8) -> impl View<u8> {
1816 Inert
1817 }
1818 }
1819
1820 struct Huge;
1821 impl Component for Huge {
1822 type State = [u64; 32];
1823 fn init(&self) -> [u64; 32] {
1824 [0; 32]
1825 }
1826 fn build(&self, _state: &mut [u64; 32]) -> impl View<[u64; 32]> {
1827 Inert
1828 }
1829 }
1830
1831 #[test]
1832 fn widget_layout_does_not_depend_on_the_state_layout() {
1833 assert_ne!(size_of::<u8>(), size_of::<[u64; 32]>());
1834 assert_eq!(
1835 size_of::<ComponentWidget<Tiny>>(),
1836 size_of::<ComponentWidget<Huge>>()
1837 );
1838 assert_eq!(
1839 align_of::<ComponentWidget<Tiny>>(),
1840 align_of::<ComponentWidget<Huge>>()
1841 );
1842 macro_rules! same_offset {
1843 ($($field:ident),*) => {$(
1844 assert_eq!(
1845 offset_of!(ComponentWidget<Tiny>, $field),
1846 offset_of!(ComponentWidget<Huge>, $field),
1847 stringify!($field)
1848 );
1849 )*};
1850 }
1851 same_offset!(witness, state, prev, child, owner, next_id, disposed);
1852 assert_eq!(offset_of!(ComponentWidget<Tiny>, witness), 0);
1853 }
1854 }
1855}