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 = new_view.rebuild(&element.prev, boxed, &mut inner);
342 let after = {
343 let any: &dyn Any = &**boxed;
344 any.type_id()
345 };
346 (flags, before != after)
347 };
348 if swapped {
349 element.child.set_active(false);
353 element.child.set_focused(false);
354 if outer_has_focus && child_focused {
363 crate::event::mark_focus_orphaned();
364 }
365 }
366 element.prev = new_view;
367 flags
368 })
369 }
370
371 fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
372 #[cfg(feature = "hotpatch")]
376 if crate::hotpatch::verify_witness::<C>(element.witness).is_err() {
377 return;
378 }
379 let inner_has_focus = ctx.has_focus() && element.child.is_focused();
387 element.owner.clone().with(|| {
388 let mut next_id = element.next_id;
389 let mut inner = BuildCtx::new(&mut next_id);
390 inner.set_has_focus(inner_has_focus);
391 if let Some(boxed) = element.child.widget_mut().downcast_mut::<Box<dyn Widget>>() {
392 element.prev.teardown(boxed, &mut inner);
393 }
394 });
395 element.dispose();
396 }
397}
398
399impl<C: Component> Widget for ComponentWidget<C> {
400 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
401 self.child.set_origin(Point::ZERO);
405 self.child.layout_child(ctx, bc)
406 }
407
408 fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
409 self.child.paint_child(ctx, scene);
414 }
415
416 fn semantics(&self, ctx: &mut crate::semantics::SemanticsCtx) {
417 self.child.semantics_child(ctx);
421 }
422
423 fn visit_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
424 visitor(&self.child);
428 }
429
430 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
431 let size = ctx.size();
442 let has_focus = ctx.has_focus();
443 let hovered = ctx.is_hovered();
448 let hover_epoch = ctx.hover_epoch();
449 let hover_eligible = ctx.is_hover_eligible();
450 let (result, needs_redraw, captured, hover_claimed, focus_req, focus_rel, ime) = {
456 #[cfg(feature = "hotpatch")]
459 let state_any: &mut dyn Any = self
460 .state
461 .as_deref_mut()
462 .expect("component state is present while the widget is live");
463 #[cfg(not(feature = "hotpatch"))]
464 let state_any: &mut dyn Any = &mut self.state;
465 let mut inner = EventCtx::new(state_any, Point::ZERO, size);
466 inner.set_has_focus(has_focus);
467 inner.set_hovered(hovered);
468 inner.set_hover_epoch(hover_epoch);
469 inner.set_hover_eligible(hover_eligible);
470 let result = route_child(&mut self.child, &mut inner, event);
471 (
472 result,
473 inner.needs_redraw(),
474 inner.is_pointer_captured(),
475 inner.is_hover_claimed(),
476 inner.is_focus_requested(),
477 inner.is_focus_released(),
478 inner.take_ime_state(),
479 )
480 };
481 if needs_redraw {
482 ctx.request_redraw();
483 }
484 if captured {
485 ctx.capture_pointer();
486 }
487 if hover_claimed {
488 ctx.claim_hover();
489 }
490 if focus_req {
491 ctx.request_focus();
492 }
493 if focus_rel {
494 ctx.release_focus();
495 }
496 if let Some(ime) = ime {
497 ctx.publish_ime_state(ime);
498 }
499 result
500 }
501}
502
503fn route_child(pod: &mut ChildPod, ctx: &mut EventCtx<'_>, event: &InputEvent) -> EventResult {
517 if event.is_broadcast() {
518 pod.event_child(ctx, event);
521 return EventResult::Ignored;
522 }
523 if event.is_focus_routed() {
524 if pod.is_focused() {
525 return pod.event_child(ctx, event);
526 }
527 return EventResult::Ignored;
528 }
529 if pod.is_active() {
530 let result = pod.event_child(ctx, event);
531 if releases_capture(event) {
532 pod.set_active(false);
533 }
534 return result;
535 }
536 let inside = pod.contains(event.position());
537 let result = if inside {
538 pod.event_child(ctx, event)
539 } else {
540 EventResult::Ignored
541 };
542 if is_pointer_down(event) && !inside && pod.is_focused() {
543 pod.set_focused(false);
544 }
545 result
546}
547
548fn releases_capture(event: &InputEvent) -> bool {
551 matches!(
552 event,
553 InputEvent::Pointer(p) if matches!(p.phase, PointerPhase::Up | PointerPhase::Cancel)
554 )
555}
556
557fn is_pointer_down(event: &InputEvent) -> bool {
559 matches!(
560 event,
561 InputEvent::Pointer(p) if matches!(p.phase, PointerPhase::Down)
562 )
563}
564
565#[cfg(test)]
566mod tests {
567 use super::*;
568 use crate::event::{
569 CursorIcon, EditingState, ImeState, Key, KeyEvent, Modifiers, NamedKey, PointerButton,
570 PointerEvent, clear_cursor_request, take_cursor_request,
571 };
572 use crate::view::any;
573 use kurbo::Rect;
574 use reactive_graph::owner::{on_cleanup, provide_context, use_context};
575 use std::cell::Cell;
576 use std::rc::Rc;
577 use std::sync::atomic::{AtomicU32, Ordering};
578 use std::sync::{Arc, Mutex};
579
580 fn pointer(phase: PointerPhase, x: f64, y: f64) -> InputEvent {
583 InputEvent::Pointer(PointerEvent {
584 phase,
585 position: Point::new(x, y),
586 button: PointerButton::Primary,
587 })
588 }
589
590 fn key_enter() -> InputEvent {
591 InputEvent::Key(KeyEvent {
592 key: Key::Named(NamedKey::Enter),
593 modifiers: Modifiers::default(),
594 repeat: false,
595 })
596 }
597
598 struct Empty;
601 struct EmptyWidget;
602 impl Widget for EmptyWidget {
603 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
604 bc.constrain(Size::new(10.0, 10.0))
605 }
606 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
607 }
608 impl View<()> for Empty {
609 type Element = EmptyWidget;
610 fn build(&self, _ctx: &mut BuildCtx<'_>) -> EmptyWidget {
611 EmptyWidget
612 }
613 fn rebuild(
614 &self,
615 _prev: &Self,
616 _element: &mut EmptyWidget,
617 _ctx: &mut BuildCtx<'_>,
618 ) -> ChangeFlags {
619 ChangeFlags::NONE
620 }
621 }
622
623 struct OtherLeaf;
625 struct OtherWidget;
626 impl Widget for OtherWidget {
627 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
628 bc.constrain(Size::new(10.0, 10.0))
629 }
630 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
631 }
632 impl View<()> for OtherLeaf {
633 type Element = OtherWidget;
634 fn build(&self, _ctx: &mut BuildCtx<'_>) -> OtherWidget {
635 OtherWidget
636 }
637 fn rebuild(
638 &self,
639 _prev: &Self,
640 _element: &mut OtherWidget,
641 _ctx: &mut BuildCtx<'_>,
642 ) -> ChangeFlags {
643 ChangeFlags::NONE
644 }
645 }
646
647 struct CounterState {
650 count: u32,
651 }
652
653 struct ClickCounter {
657 label: &'static str,
658 }
659
660 impl Component for ClickCounter {
661 type State = CounterState;
662 fn init(&self) -> CounterState {
663 CounterState { count: 0 }
664 }
665 fn build(&self, state: &mut CounterState) -> impl View<CounterState> {
666 any(CounterButtonView {
667 count: state.count,
668 label: self.label,
669 })
670 }
671 }
672
673 struct CounterButtonView {
674 count: u32,
675 label: &'static str,
676 }
677 struct CounterButtonWidget {
678 count: u32,
679 label: &'static str,
680 }
681 impl View<CounterState> for CounterButtonView {
682 type Element = CounterButtonWidget;
683 fn build(&self, _ctx: &mut BuildCtx<'_>) -> CounterButtonWidget {
684 CounterButtonWidget {
685 count: self.count,
686 label: self.label,
687 }
688 }
689 fn rebuild(
690 &self,
691 prev: &Self,
692 element: &mut CounterButtonWidget,
693 _ctx: &mut BuildCtx<'_>,
694 ) -> ChangeFlags {
695 let mut flags = ChangeFlags::NONE;
696 if prev.count != self.count || prev.label != self.label {
697 element.count = self.count;
698 element.label = self.label;
699 flags = ChangeFlags::PAINT;
700 }
701 flags
702 }
703 }
704 impl Widget for CounterButtonWidget {
705 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
706 bc.constrain(Size::new(40.0, 20.0))
707 }
708 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
709 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
710 if let InputEvent::Pointer(p) = event
711 && p.phase == PointerPhase::Down
712 {
713 ctx.state_mut::<CounterState>().count += 1;
714 ctx.request_redraw();
715 return EventResult::Handled;
716 }
717 EventResult::Ignored
718 }
719 }
720
721 fn ambient() -> Owner {
724 let owner = Owner::new();
725 owner.set();
726 owner
727 }
728
729 fn build_widget<Outer: 'static, C: Component>(view: &ComponentView<C>) -> ComponentWidget<C> {
730 let mut next_id = 0u64;
731 let mut ctx = BuildCtx::new(&mut next_id);
732 View::<Outer>::build(view, &mut ctx)
733 }
734
735 #[test]
736 fn local_state_survives_parent_driven_rebuild() {
737 let _owner = ambient();
738 let v1 = component(ClickCounter { label: "a" });
739 let mut widget = build_widget::<(), _>(&v1);
740 let mut lctx = LayoutCtx::new();
741 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 20.0)));
742
743 let mut outer = ();
746 for _ in 0..3 {
747 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 20.0));
748 widget.event(&mut ectx, &pointer(PointerPhase::Down, 5.0, 5.0));
749 }
750 assert_eq!(widget.state().count, 3);
751
752 let v2 = component(ClickCounter { label: "changed" });
754 let mut next_id = 0u64;
755 let mut ctx = BuildCtx::new(&mut next_id);
756 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
757 assert_eq!(
758 widget.state().count,
759 3,
760 "local state retained across rebuild"
761 );
762 }
763
764 #[derive(Clone)]
767 struct Theme(i32);
768
769 struct Provider {
772 sink: Arc<Mutex<Option<i32>>>,
773 }
774 impl Component for Provider {
775 type State = ();
776 fn init(&self) {}
777 fn build(&self, _state: &mut ()) -> impl View<()> {
778 provide_context(Theme(7));
779 any(component(Reader {
780 sink: self.sink.clone(),
781 }))
782 }
783 }
784
785 struct Reader {
787 sink: Arc<Mutex<Option<i32>>>,
788 }
789 impl Component for Reader {
790 type State = ();
791 fn init(&self) {}
792 fn build(&self, _state: &mut ()) -> impl View<()> {
793 *self.sink.lock().unwrap() = use_context::<Theme>().map(|t| t.0);
794 any(Empty)
795 }
796 }
797
798 struct SecretProvider;
801 impl Component for SecretProvider {
802 type State = ();
803 fn init(&self) {}
804 fn build(&self, _state: &mut ()) -> impl View<()> {
805 provide_context(Theme(99));
806 any(Empty)
807 }
808 }
809
810 #[test]
811 fn use_context_resolves_ancestor_provided_value() {
812 let _owner = ambient();
813 let sink = Arc::new(Mutex::new(None));
814 let v = component(Provider { sink: sink.clone() });
815 let _widget = build_widget::<(), _>(&v);
816 assert_eq!(*sink.lock().unwrap(), Some(7));
817 }
818
819 #[test]
820 fn sibling_does_not_see_a_cousins_context() {
821 let parent = ambient();
822 let reader_sink = Arc::new(Mutex::new(Some(-1)));
823 parent.with(|| {
826 let secret = component(SecretProvider);
827 let _s = build_widget::<(), _>(&secret);
828 let reader = component(Reader {
829 sink: reader_sink.clone(),
830 });
831 let _r = build_widget::<(), _>(&reader);
832 });
833 assert_eq!(*reader_sink.lock().unwrap(), None);
834 }
835
836 struct Disposable {
839 probe: Arc<AtomicU32>,
840 }
841 impl Component for Disposable {
842 type State = ();
843 fn init(&self) {
844 let probe = self.probe.clone();
845 on_cleanup(move || {
847 probe.fetch_add(1, Ordering::SeqCst);
848 });
849 }
850 fn build(&self, _state: &mut ()) -> impl View<()> {
851 any(Empty)
852 }
853 }
854
855 #[test]
856 fn anyview_type_swap_disposes_component_owner_once() {
857 let _owner = ambient();
858 let probe = Arc::new(AtomicU32::new(0));
859 let mut next_id = 0u64;
860 let mut ctx = BuildCtx::new(&mut next_id);
861
862 let prev = any::<(), _>(component(Disposable {
863 probe: probe.clone(),
864 }));
865 let mut element: Box<dyn Widget> = prev.build(&mut ctx);
866 assert_eq!(probe.load(Ordering::SeqCst), 0);
867
868 let next = any::<(), _>(OtherLeaf);
871 next.rebuild(&prev, &mut element, &mut ctx);
872 assert_eq!(probe.load(Ordering::SeqCst), 1);
873
874 drop(element);
876 assert_eq!(probe.load(Ordering::SeqCst), 1);
877 }
878
879 struct ConcreteComp;
884 impl Component for ConcreteComp {
885 type State = ();
886 fn init(&self) {}
887 fn build(&self, _state: &mut ()) -> impl View<()> {
888 Empty
889 }
890 }
891
892 struct ErasedComp;
895 impl Component for ErasedComp {
896 type State = ();
897 fn init(&self) {}
898 fn build(&self, _state: &mut ()) -> impl View<()> {
899 any(Empty)
900 }
901 }
902
903 fn child_is_empty_widget<C: Component>(widget: &mut ComponentWidget<C>) -> bool {
906 let boxed = widget
907 .child
908 .widget_mut()
909 .downcast_mut::<Box<dyn Widget>>()
910 .expect("double-boxed AnyView element");
911 let any: &dyn Any = &**boxed;
912 any.is::<EmptyWidget>()
913 }
914
915 #[test]
916 fn build_result_is_erased_exactly_once_at_the_boundary() {
917 let _owner = ambient();
918
919 let concrete = component(ConcreteComp);
920 let mut widget = build_widget::<(), _>(&concrete);
921 assert!(child_is_empty_widget(&mut widget), "concrete body, build");
922 let mut next_id = 0u64;
923 let mut ctx = BuildCtx::new(&mut next_id);
924 View::<()>::rebuild(&concrete, &concrete, &mut widget, &mut ctx);
925 assert!(child_is_empty_widget(&mut widget), "concrete body, rebuild");
926
927 let erased = component(ErasedComp);
928 let mut widget = build_widget::<(), _>(&erased);
929 assert!(child_is_empty_widget(&mut widget), "any(..) body, build");
930 let mut next_id = 0u64;
931 let mut ctx = BuildCtx::new(&mut next_id);
932 View::<()>::rebuild(&erased, &erased, &mut widget, &mut ctx);
933 assert!(child_is_empty_widget(&mut widget), "any(..) body, rebuild");
934 }
935
936 struct SwapComp {
943 swapped: Rc<Cell<bool>>,
944 }
945 impl Component for SwapComp {
946 type State = ();
947 fn init(&self) {}
948 fn build(&self, _state: &mut ()) -> impl View<()> {
949 if self.swapped.get() {
950 any(OtherLeaf)
951 } else {
952 any(Empty)
953 }
954 }
955 }
956
957 #[test]
958 fn component_swap_arm_marks_the_orphan_only_on_a_live_chain() {
959 fn swap_child(outer_live: bool) -> (bool, bool) {
962 let _ = crate::event::take_focus_orphaned();
963 let _owner = ambient();
964 let swapped = Rc::new(Cell::new(false));
965 let v1 = component(SwapComp {
966 swapped: swapped.clone(),
967 });
968 let mut widget = build_widget::<(), _>(&v1);
969 widget.child.set_focused(true);
971
972 swapped.set(true);
973 let v2 = component(SwapComp {
974 swapped: swapped.clone(),
975 });
976 let mut next_id = 0u64;
977 let mut ctx = BuildCtx::new(&mut next_id);
978 ctx.set_has_focus(outer_live);
980 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
981 (
982 widget.child.is_focused(),
983 crate::event::take_focus_orphaned(),
984 )
985 }
986
987 let (still_focused, marked) = swap_child(true);
988 assert!(
989 !still_focused,
990 "a swapped-in widget must not inherit the old focus link"
991 );
992 assert!(
993 marked,
994 "a live focus path dying inside the component's swap releases the session"
995 );
996
997 let (still_focused, marked) = swap_child(false);
998 assert!(!still_focused, "the flag is dropped either way");
999 assert!(
1000 !marked,
1001 "the same swap under an already-blurred ancestor owns no session to release"
1002 );
1003 }
1004
1005 #[test]
1006 fn component_content_only_rebuild_keeps_focus_and_marks_nothing() {
1007 let _ = crate::event::take_focus_orphaned();
1011 let _owner = ambient();
1012 let v1 = component(Labeled { text: "a" });
1013 let mut widget = build_widget::<(), _>(&v1);
1014 widget.child.set_focused(true);
1015
1016 let v2 = component(Labeled { text: "b" });
1017 let mut next_id = 0u64;
1018 let mut ctx = BuildCtx::new(&mut next_id);
1019 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
1020
1021 assert!(
1022 widget.child.is_focused(),
1023 "a content-only rebuild keeps the recorded focus path"
1024 );
1025 assert!(
1026 !crate::event::take_focus_orphaned(),
1027 "an in-place child rebuild must not orphan the focus session"
1028 );
1029 }
1030
1031 #[test]
1032 fn explicit_teardown_disposes_owner_once_and_drop_is_idempotent() {
1033 let _owner = ambient();
1034 let probe = Arc::new(AtomicU32::new(0));
1035 let view = component(Disposable {
1036 probe: probe.clone(),
1037 });
1038 let mut widget = build_widget::<(), _>(&view);
1039
1040 let mut next_id = 0u64;
1041 let mut ctx = BuildCtx::new(&mut next_id);
1042 View::<()>::teardown(&view, &mut widget, &mut ctx);
1043 assert_eq!(probe.load(Ordering::SeqCst), 1);
1044
1045 drop(widget);
1047 assert_eq!(probe.load(Ordering::SeqCst), 1);
1048 }
1049
1050 struct CaptureWidget {
1055 pressed: bool,
1056 }
1057 impl Widget for CaptureWidget {
1058 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1059 bc.constrain(Size::new(40.0, 40.0))
1060 }
1061 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1062 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
1063 if let InputEvent::Pointer(p) = event {
1064 match p.phase {
1065 PointerPhase::Down => {
1066 self.pressed = true;
1067 ctx.capture_pointer();
1068 ctx.request_redraw();
1069 return EventResult::Handled;
1070 }
1071 PointerPhase::Move => return EventResult::Handled,
1072 PointerPhase::Up | PointerPhase::Cancel => {
1074 self.pressed = false;
1075 return EventResult::Handled;
1076 }
1077 }
1078 }
1079 EventResult::Ignored
1080 }
1081 }
1082 struct CaptureView;
1083 impl View<()> for CaptureView {
1084 type Element = CaptureWidget;
1085 fn build(&self, _ctx: &mut BuildCtx<'_>) -> CaptureWidget {
1086 CaptureWidget { pressed: false }
1087 }
1088 fn rebuild(
1089 &self,
1090 _prev: &Self,
1091 _element: &mut CaptureWidget,
1092 _ctx: &mut BuildCtx<'_>,
1093 ) -> ChangeFlags {
1094 ChangeFlags::NONE
1095 }
1096 }
1097 struct CaptureComp;
1098 impl Component for CaptureComp {
1099 type State = ();
1100 fn init(&self) {}
1101 fn build(&self, _state: &mut ()) -> impl View<()> {
1102 any(CaptureView)
1103 }
1104 }
1105
1106 fn inner_widget<C: Component, W: Widget>(w: &mut ComponentWidget<C>) -> &mut W {
1109 w.child
1110 .widget_mut()
1111 .downcast_mut::<Box<dyn Widget>>()
1112 .expect("double-boxed AnyView element")
1113 .downcast_mut::<W>()
1114 .expect("inner concrete widget")
1115 }
1116
1117 #[test]
1118 fn capture_survives_drag_outside_bounds_and_mirrors_outward() {
1119 let _owner = ambient();
1120 let view = component(CaptureComp);
1121 let mut widget = build_widget::<(), _>(&view);
1122 let mut lctx = LayoutCtx::new();
1123 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 40.0)));
1124
1125 let mut outer = ();
1126 let captured = {
1128 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1129 widget.event(&mut ectx, &pointer(PointerPhase::Down, 5.0, 5.0));
1130 ectx.is_pointer_captured()
1131 };
1132 assert!(captured, "inner capture bubbles to the outer context");
1133 assert!(
1134 widget.child.is_active(),
1135 "component pod records the capture"
1136 );
1137
1138 {
1140 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1141 let r = widget.event(&mut ectx, &pointer(PointerPhase::Move, 500.0, 500.0));
1142 assert_eq!(r, EventResult::Handled, "drag outside bounds still routed");
1143 }
1144 assert!(widget.child.is_active(), "capture survives the drag");
1145
1146 {
1148 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1149 widget.event(&mut ectx, &pointer(PointerPhase::Up, 500.0, 500.0));
1150 }
1151 assert!(!widget.child.is_active(), "Up auto-releases the capture");
1152 assert!(!inner_widget::<_, CaptureWidget>(&mut widget).pressed);
1153 }
1154
1155 #[test]
1156 fn synthesized_cancel_over_unit_state_is_safe_and_clears_pressed() {
1157 let _owner = ambient();
1158 let view = component(CaptureComp);
1159 let mut widget = build_widget::<(), _>(&view);
1160 let mut lctx = LayoutCtx::new();
1161 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 40.0)));
1162
1163 let mut outer = ();
1165 {
1166 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1167 widget.event(&mut ectx, &pointer(PointerPhase::Down, 5.0, 5.0));
1168 }
1169 assert!(inner_widget::<_, CaptureWidget>(&mut widget).pressed);
1170
1171 let mut dummy = ();
1174 {
1175 let mut ectx = EventCtx::new(&mut dummy, Point::ZERO, Size::new(40.0, 40.0));
1176 widget.event(&mut ectx, &pointer(PointerPhase::Cancel, 0.0, 0.0));
1177 }
1178 assert!(
1179 !inner_widget::<_, CaptureWidget>(&mut widget).pressed,
1180 "Cancel cleared pressed across the boundary"
1181 );
1182 assert!(!widget.child.is_active(), "Cancel released the capture");
1183 }
1184
1185 struct CursorLeafWidget;
1187 impl Widget for CursorLeafWidget {
1188 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1189 bc.constrain(Size::new(40.0, 40.0))
1190 }
1191 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1192 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
1193 if let InputEvent::Pointer(p) = event
1194 && p.phase == PointerPhase::Move
1195 {
1196 ctx.set_cursor(CursorIcon::Text);
1197 }
1198 EventResult::Ignored
1199 }
1200 }
1201 struct CursorLeafView;
1202 impl View<()> for CursorLeafView {
1203 type Element = CursorLeafWidget;
1204 fn build(&self, _ctx: &mut BuildCtx<'_>) -> CursorLeafWidget {
1205 CursorLeafWidget
1206 }
1207 fn rebuild(
1208 &self,
1209 _prev: &Self,
1210 _element: &mut CursorLeafWidget,
1211 _ctx: &mut BuildCtx<'_>,
1212 ) -> ChangeFlags {
1213 ChangeFlags::NONE
1214 }
1215 }
1216 struct CursorComp;
1217 impl Component for CursorComp {
1218 type State = ();
1219 fn init(&self) {}
1220 fn build(&self, _state: &mut ()) -> impl View<()> {
1221 any(CursorLeafView)
1222 }
1223 }
1224
1225 #[test]
1230 fn a_cursor_request_crosses_the_component_boundary() {
1231 let _owner = ambient();
1232 let view = component(CursorComp);
1233 let mut widget = build_widget::<(), _>(&view);
1234 let mut lctx = LayoutCtx::new();
1235 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 40.0)));
1236
1237 clear_cursor_request();
1240 let mut outer = ();
1241 {
1242 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1243 widget.event(&mut ectx, &pointer(PointerPhase::Move, 5.0, 5.0));
1244 }
1245 assert_eq!(
1246 take_cursor_request(),
1247 Some(CursorIcon::Text),
1248 "a request from below the component boundary reaches the root"
1249 );
1250 }
1251
1252 struct ImeLeafWidget;
1255 impl Widget for ImeLeafWidget {
1256 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1257 bc.constrain(Size::new(100.0, 100.0))
1258 }
1259 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1260 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
1261 match event {
1262 InputEvent::Pointer(p) if p.phase == PointerPhase::Down => {
1263 if p.position.x < 50.0 {
1264 ctx.request_focus();
1265 ctx.publish_ime_state(ImeState {
1266 active: true,
1267 editing: EditingState {
1268 text: "abc".to_string(),
1269 selection_base: 3,
1270 selection_extent: 3,
1271 composing_base: -1,
1272 composing_extent: -1,
1273 },
1274 caret: Some(Rect::new(0.0, 0.0, 1.0, 12.0)),
1275 content_type: Default::default(),
1276 suppress_soft_keyboard: false,
1277 });
1278 }
1279 EventResult::Handled
1280 }
1281 InputEvent::Key(_) | InputEvent::Ime(_) => EventResult::Handled,
1282 _ => EventResult::Ignored,
1283 }
1284 }
1285 }
1286 struct ImeLeafView;
1287 impl View<()> for ImeLeafView {
1288 type Element = ImeLeafWidget;
1289 fn build(&self, _ctx: &mut BuildCtx<'_>) -> ImeLeafWidget {
1290 ImeLeafWidget
1291 }
1292 fn rebuild(
1293 &self,
1294 _prev: &Self,
1295 _element: &mut ImeLeafWidget,
1296 _ctx: &mut BuildCtx<'_>,
1297 ) -> ChangeFlags {
1298 ChangeFlags::NONE
1299 }
1300 }
1301 struct ImeComp;
1302 impl Component for ImeComp {
1303 type State = ();
1304 fn init(&self) {}
1305 fn build(&self, _state: &mut ()) -> impl View<()> {
1306 any(ImeLeafView)
1307 }
1308 }
1309
1310 #[test]
1311 fn focus_ime_and_blur_surface_through_render_root() {
1312 use crate::app::RenderRoot;
1313 let _owner = ambient();
1314 let mut root: RenderRoot<(), ComponentView<ImeComp>> = RenderRoot::new();
1315 let mut state = ();
1316 root.rebuild(&mut |_s: &mut ()| component(ImeComp), &mut state);
1317 root.layout(Size::new(100.0, 100.0));
1318
1319 assert!(!root.is_focus_active());
1320 assert!(root.ime_state().is_none());
1321
1322 root.event(&mut state, &pointer(PointerPhase::Down, 10.0, 10.0));
1325 assert!(root.is_focus_active(), "focus request crossed the boundary");
1326 let ime = root.ime_state().expect("IME surface surfaced at the root");
1327 assert!(ime.active);
1328 assert_eq!(ime.editing.text, "abc");
1329
1330 let outcome = root.event(&mut state, &key_enter());
1332 assert!(outcome.handled, "Key routed to the focused inner leaf");
1333
1334 root.event(&mut state, &pointer(PointerPhase::Down, 80.0, 10.0));
1337 assert!(!root.is_focus_active(), "blur crossed the boundary");
1338 assert!(root.ime_state().is_none());
1339 }
1340
1341 struct TextLeafView {
1344 text: &'static str,
1345 }
1346 struct TextLeafWidget {
1347 text: &'static str,
1348 }
1349 impl View<()> for TextLeafView {
1350 type Element = TextLeafWidget;
1351 fn build(&self, _ctx: &mut BuildCtx<'_>) -> TextLeafWidget {
1352 TextLeafWidget { text: self.text }
1353 }
1354 fn rebuild(
1355 &self,
1356 prev: &Self,
1357 element: &mut TextLeafWidget,
1358 _ctx: &mut BuildCtx<'_>,
1359 ) -> ChangeFlags {
1360 if prev.text != self.text {
1361 element.text = self.text;
1362 ChangeFlags::PAINT
1363 } else {
1364 ChangeFlags::NONE
1365 }
1366 }
1367 }
1368 impl Widget for TextLeafWidget {
1369 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1370 bc.constrain(Size::new(10.0, 10.0))
1371 }
1372 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1373 }
1374 struct Labeled {
1375 text: &'static str,
1376 }
1377 impl Component for Labeled {
1378 type State = ();
1379 fn init(&self) {}
1380 fn build(&self, _state: &mut ()) -> impl View<()> {
1381 any(TextLeafView { text: self.text })
1382 }
1383 }
1384
1385 #[test]
1386 fn child_paint_flags_bubble_out_of_component_rebuild() {
1387 let _owner = ambient();
1388 let v1 = component(Labeled { text: "a" });
1389 let mut widget = build_widget::<(), _>(&v1);
1390
1391 let v_same = component(Labeled { text: "a" });
1393 let mut next_id = 0u64;
1394 let mut ctx = BuildCtx::new(&mut next_id);
1395 let flags = View::<()>::rebuild(&v_same, &v1, &mut widget, &mut ctx);
1396 assert_eq!(flags, ChangeFlags::NONE);
1397
1398 let v2 = component(Labeled { text: "b" });
1401 let flags = View::<()>::rebuild(&v2, &v_same, &mut widget, &mut ctx);
1402 assert_eq!(flags, ChangeFlags::PAINT);
1403 }
1404
1405 #[cfg(feature = "hotpatch")]
1408 mod seam {
1409 use super::*;
1410 use crate::hotpatch::{Inert, SeamWitness};
1411 use std::any::type_name;
1412 use std::mem::offset_of;
1413
1414 fn foreign<C: Component>() -> SeamWitness {
1417 let own = SeamWitness::of::<C>();
1418 SeamWitness {
1419 size_state: own.size_state + 8,
1420 ..own
1421 }
1422 }
1423
1424 fn take_reported<S>() -> bool {
1426 let mine: Vec<_> = frust_hotpatch::pending_layout_mismatches()
1427 .into_iter()
1428 .filter(|m| m.type_name == type_name::<S>())
1429 .collect();
1430 frust_hotpatch::mark_layout_mismatches_reported(&mine);
1431 !mine.is_empty()
1432 }
1433
1434 struct Counted<const N: usize> {
1437 value: u32,
1438 drops: Arc<AtomicU32>,
1439 }
1440 impl<const N: usize> Drop for Counted<N> {
1441 fn drop(&mut self) {
1442 self.drops.fetch_add(1, Ordering::SeqCst);
1443 }
1444 }
1445
1446 struct Probe<const N: usize> {
1449 builds: Arc<AtomicU32>,
1450 drops: Arc<AtomicU32>,
1451 disposed: Arc<AtomicU32>,
1452 child_teardowns: Arc<AtomicU32>,
1453 }
1454 impl<const N: usize> Probe<N> {
1455 fn new() -> Self {
1456 Self {
1457 builds: Arc::new(AtomicU32::new(0)),
1458 drops: Arc::new(AtomicU32::new(0)),
1459 disposed: Arc::new(AtomicU32::new(0)),
1460 child_teardowns: Arc::new(AtomicU32::new(0)),
1461 }
1462 }
1463 fn handle(&self) -> Self {
1464 Self {
1465 builds: self.builds.clone(),
1466 drops: self.drops.clone(),
1467 disposed: self.disposed.clone(),
1468 child_teardowns: self.child_teardowns.clone(),
1469 }
1470 }
1471 }
1472 impl<const N: usize> Component for Probe<N> {
1473 type State = Counted<N>;
1474 fn init(&self) -> Counted<N> {
1475 let disposed = self.disposed.clone();
1476 on_cleanup(move || {
1477 disposed.fetch_add(1, Ordering::SeqCst);
1478 });
1479 Counted {
1480 value: 0,
1481 drops: self.drops.clone(),
1482 }
1483 }
1484 fn build(&self, state: &mut Counted<N>) -> impl View<Counted<N>> {
1485 self.builds.fetch_add(1, Ordering::SeqCst);
1486 state.value += 1;
1487 TeardownProbe {
1488 teardowns: self.child_teardowns.clone(),
1489 }
1490 }
1491 }
1492
1493 struct TeardownProbe {
1495 teardowns: Arc<AtomicU32>,
1496 }
1497 impl<S: 'static> View<S> for TeardownProbe {
1498 type Element = EmptyWidget;
1499 fn build(&self, _ctx: &mut BuildCtx<'_>) -> EmptyWidget {
1500 EmptyWidget
1501 }
1502 fn rebuild(
1503 &self,
1504 _prev: &Self,
1505 _element: &mut EmptyWidget,
1506 _ctx: &mut BuildCtx<'_>,
1507 ) -> ChangeFlags {
1508 ChangeFlags::NONE
1509 }
1510 fn teardown(&self, _element: &mut EmptyWidget, _ctx: &mut BuildCtx<'_>) {
1511 self.teardowns.fetch_add(1, Ordering::SeqCst);
1512 }
1513 }
1514
1515 struct Parent {
1518 nested: Probe<1>,
1519 }
1520 impl Component for Parent {
1521 type State = ();
1522 fn init(&self) {}
1523 fn build(&self, _state: &mut ()) -> impl View<()> {
1524 component(self.nested.handle())
1525 }
1526 }
1527
1528 fn nested_of(parent: &mut ComponentWidget<Parent>) -> &mut ComponentWidget<Probe<1>> {
1529 let boxed = parent
1530 .child
1531 .widget_mut()
1532 .downcast_mut::<Box<dyn Widget>>()
1533 .expect("component child element is a boxed AnyView widget");
1534 (**boxed)
1535 .downcast_mut::<ComponentWidget<Probe<1>>>()
1536 .expect("the parent's subtree is the nested component")
1537 }
1538
1539 #[test]
1540 fn a_nested_widget_with_a_foreign_witness_is_never_rebuilt() {
1541 let _owner = ambient();
1542 let nested = Probe::<1>::new();
1543 let v1 = component(Parent {
1544 nested: nested.handle(),
1545 });
1546 let mut widget = build_widget::<(), _>(&v1);
1547 assert_eq!(nested.builds.load(Ordering::SeqCst), 1);
1548
1549 nested_of(&mut widget).set_witness_for_test(foreign::<Probe<1>>());
1550 let v2 = component(Parent {
1551 nested: nested.handle(),
1552 });
1553 let mut next_id = 0u64;
1554 let mut ctx = BuildCtx::new(&mut next_id);
1555 let flags = View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
1556
1557 assert_eq!(flags, ChangeFlags::NONE);
1558 assert_eq!(nested.builds.load(Ordering::SeqCst), 1, "never rebuilt");
1559 assert_eq!(nested_of(&mut widget).state().value, 1, "state untouched");
1560 assert!(take_reported::<Counted<1>>(), "the mismatch is reported");
1561
1562 drop(widget);
1564 assert_eq!(nested.drops.load(Ordering::SeqCst), 0);
1565 assert_eq!(nested.disposed.load(Ordering::SeqCst), 1);
1566 assert!(take_reported::<Counted<1>>());
1567 }
1568
1569 enum TwoArm<L, R> {
1572 Left(L),
1573 Right(R),
1574 }
1575 enum TwoArmWidget<L, R> {
1576 Left(L),
1577 Right(R),
1578 }
1579 impl<L: Widget, R: Widget> Widget for TwoArmWidget<L, R> {
1580 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1581 match self {
1582 Self::Left(w) => w.layout(ctx, bc),
1583 Self::Right(w) => w.layout(ctx, bc),
1584 }
1585 }
1586 fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
1587 match self {
1588 Self::Left(w) => w.paint(ctx, scene),
1589 Self::Right(w) => w.paint(ctx, scene),
1590 }
1591 }
1592 }
1593 impl<S: 'static, L: View<S>, R: View<S>> View<S> for TwoArm<L, R> {
1594 type Element = TwoArmWidget<L::Element, R::Element>;
1595 fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element {
1596 match self {
1597 Self::Left(v) => TwoArmWidget::Left(v.build(ctx)),
1598 Self::Right(v) => TwoArmWidget::Right(v.build(ctx)),
1599 }
1600 }
1601 fn rebuild(
1602 &self,
1603 prev: &Self,
1604 element: &mut Self::Element,
1605 ctx: &mut BuildCtx<'_>,
1606 ) -> ChangeFlags {
1607 match (self, prev, &mut *element) {
1608 (Self::Left(v), Self::Left(p), TwoArmWidget::Left(w)) => v.rebuild(p, w, ctx),
1609 (Self::Right(v), Self::Right(p), TwoArmWidget::Right(w)) => {
1610 v.rebuild(p, w, ctx)
1611 }
1612 _ => {
1613 prev.teardown(element, ctx);
1616 *element = self.build(ctx);
1617 ChangeFlags::LAYOUT | ChangeFlags::PAINT
1618 }
1619 }
1620 }
1621 fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
1622 match (self, element) {
1623 (Self::Left(v), TwoArmWidget::Left(w)) => v.teardown(w, ctx),
1624 (Self::Right(v), TwoArmWidget::Right(w)) => v.teardown(w, ctx),
1625 _ => {}
1626 }
1627 }
1628 }
1629
1630 #[test]
1631 fn an_inline_arm_swap_leaks_a_foreign_state_and_disposes_its_owner() {
1632 let _owner = ambient();
1633 let probe = Probe::<2>::new();
1634 let left: TwoArm<ComponentView<Probe<2>>, Empty> =
1635 TwoArm::Left(component(probe.handle()));
1636 let mut next_id = 0u64;
1637 let mut ctx = BuildCtx::new(&mut next_id);
1638 let mut element = View::<()>::build(&left, &mut ctx);
1639 match &mut element {
1640 TwoArmWidget::Left(w) => w.set_witness_for_test(foreign::<Probe<2>>()),
1641 TwoArmWidget::Right(_) => unreachable!("built the left arm"),
1642 }
1643
1644 let right: TwoArm<ComponentView<Probe<2>>, Empty> = TwoArm::Right(Empty);
1645 let flags = View::<()>::rebuild(&right, &left, &mut element, &mut ctx);
1646
1647 assert_eq!(flags, ChangeFlags::LAYOUT | ChangeFlags::PAINT);
1648 assert!(matches!(element, TwoArmWidget::Right(_)), "swapped out");
1649 assert_eq!(
1650 probe.drops.load(Ordering::SeqCst),
1651 0,
1652 "the state is neither dropped nor freed"
1653 );
1654 assert_eq!(
1655 probe.child_teardowns.load(Ordering::SeqCst),
1656 0,
1657 "teardown did not proceed"
1658 );
1659 assert_eq!(probe.disposed.load(Ordering::SeqCst), 1, "owner disposed");
1660 assert!(take_reported::<Counted<2>>(), "the mismatch is reported");
1661 }
1662
1663 #[test]
1664 fn a_matching_widget_tears_down_and_drops_its_state() {
1665 let _owner = ambient();
1666 let probe = Probe::<3>::new();
1667 let view = component(probe.handle());
1668 let mut widget = build_widget::<(), _>(&view);
1669 let mut next_id = 0u64;
1670 let mut ctx = BuildCtx::new(&mut next_id);
1671 View::<()>::rebuild(&view, &view, &mut widget, &mut ctx);
1672 assert_eq!(probe.builds.load(Ordering::SeqCst), 2);
1673 View::<()>::teardown(&view, &mut widget, &mut ctx);
1674 assert_eq!(probe.child_teardowns.load(Ordering::SeqCst), 1);
1675 assert_eq!(probe.disposed.load(Ordering::SeqCst), 1);
1676 drop(widget);
1677 assert_eq!(probe.drops.load(Ordering::SeqCst), 1);
1678 assert!(!take_reported::<Counted<3>>());
1679 }
1680
1681 struct Tiny;
1682 impl Component for Tiny {
1683 type State = u8;
1684 fn init(&self) -> u8 {
1685 0
1686 }
1687 fn build(&self, _state: &mut u8) -> impl View<u8> {
1688 Inert
1689 }
1690 }
1691
1692 struct Huge;
1693 impl Component for Huge {
1694 type State = [u64; 32];
1695 fn init(&self) -> [u64; 32] {
1696 [0; 32]
1697 }
1698 fn build(&self, _state: &mut [u64; 32]) -> impl View<[u64; 32]> {
1699 Inert
1700 }
1701 }
1702
1703 #[test]
1704 fn widget_layout_does_not_depend_on_the_state_layout() {
1705 assert_ne!(size_of::<u8>(), size_of::<[u64; 32]>());
1706 assert_eq!(
1707 size_of::<ComponentWidget<Tiny>>(),
1708 size_of::<ComponentWidget<Huge>>()
1709 );
1710 assert_eq!(
1711 align_of::<ComponentWidget<Tiny>>(),
1712 align_of::<ComponentWidget<Huge>>()
1713 );
1714 macro_rules! same_offset {
1715 ($($field:ident),*) => {$(
1716 assert_eq!(
1717 offset_of!(ComponentWidget<Tiny>, $field),
1718 offset_of!(ComponentWidget<Huge>, $field),
1719 stringify!($field)
1720 );
1721 )*};
1722 }
1723 same_offset!(witness, state, prev, child, owner, next_id, disposed);
1724 assert_eq!(offset_of!(ComponentWidget<Tiny>, witness), 0);
1725 }
1726 }
1727}