1use std::any::Any;
40
41use kurbo::{Point, Size};
42use reactive_graph::owner::Owner;
43
44use crate::event::{EventCtx, EventResult, InputEvent, PointerPhase};
45use crate::layout::BoxConstraints;
46use crate::view::{AnyView, BuildCtx, ChangeFlags, View};
47use crate::widget::{ChildPod, LayoutCtx, PaintCtx, PaintScene, Widget};
48
49pub trait Component: 'static {
57 type State: 'static;
59
60 fn init(&self) -> Self::State;
72
73 fn build(&self, state: &mut Self::State) -> AnyView<Self::State>;
79}
80
81pub struct ComponentView<C: Component> {
88 component: C,
89}
90
91pub fn component<C: Component>(c: C) -> ComponentView<C> {
94 ComponentView { component: c }
95}
96
97pub struct ComponentWidget<C: Component> {
110 state: C::State,
113 prev: AnyView<C::State>,
116 child: ChildPod,
119 owner: Owner,
122 next_id: u64,
126 disposed: bool,
129}
130
131impl<C: Component> ComponentWidget<C> {
132 fn dispose(&mut self) {
136 if !self.disposed {
137 self.owner.cleanup();
141 self.disposed = true;
142 }
143 }
144}
145
146impl<C: Component> Drop for ComponentWidget<C> {
147 fn drop(&mut self) {
148 self.dispose();
151 }
152}
153
154impl<Outer: 'static, C: Component> View<Outer> for ComponentView<C> {
155 type Element = ComponentWidget<C>;
156
157 fn build(&self, _ctx: &mut BuildCtx<'_>) -> Self::Element {
158 let owner = Owner::new();
161 let (state, prev, child, next_id) = owner.with(|| {
162 let mut state = self.component.init();
163 let view = self.component.build(&mut state);
164 let mut next_id = 0u64;
166 let mut inner = BuildCtx::new(&mut next_id);
167 inner.set_has_focus(false);
172 let element: Box<dyn Widget> = view.build(&mut inner);
173 (state, view, ChildPod::new(Box::new(element)), next_id)
175 });
176 ComponentWidget {
177 state,
178 prev,
179 child,
180 owner,
181 next_id,
182 disposed: false,
183 }
184 }
185
186 fn rebuild(
187 &self,
188 _prev: &Self,
189 element: &mut Self::Element,
190 ctx: &mut BuildCtx<'_>,
191 ) -> ChangeFlags {
192 let outer_has_focus = ctx.has_focus();
203 let owner = element.owner.clone();
204 owner.with(|| {
205 let new_view = self.component.build(&mut element.state);
206 let child_focused = element.child.is_focused();
208 let (flags, swapped) = {
209 let boxed = element
210 .child
211 .widget_mut()
212 .downcast_mut::<Box<dyn Widget>>()
213 .expect("component child element is a boxed AnyView widget");
214 let before = {
215 let any: &dyn Any = &**boxed;
216 any.type_id()
217 };
218 let mut inner = BuildCtx::new(&mut element.next_id);
219 inner.set_has_focus(outer_has_focus && child_focused);
224 let flags = new_view.rebuild(&element.prev, boxed, &mut inner);
225 let after = {
226 let any: &dyn Any = &**boxed;
227 any.type_id()
228 };
229 (flags, before != after)
230 };
231 if swapped {
232 element.child.set_active(false);
236 element.child.set_focused(false);
237 if outer_has_focus && child_focused {
246 crate::event::mark_focus_orphaned();
247 }
248 }
249 element.prev = new_view;
250 flags
251 })
252 }
253
254 fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
255 let inner_has_focus = ctx.has_focus() && element.child.is_focused();
263 element.owner.clone().with(|| {
264 let mut next_id = element.next_id;
265 let mut inner = BuildCtx::new(&mut next_id);
266 inner.set_has_focus(inner_has_focus);
267 if let Some(boxed) = element.child.widget_mut().downcast_mut::<Box<dyn Widget>>() {
268 element.prev.teardown(boxed, &mut inner);
269 }
270 });
271 element.dispose();
272 }
273}
274
275impl<C: Component> Widget for ComponentWidget<C> {
276 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
277 self.child.set_origin(Point::ZERO);
281 self.child.layout_child(ctx, bc)
282 }
283
284 fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
285 self.child.paint_child(ctx, scene);
290 }
291
292 fn semantics(&self, ctx: &mut crate::semantics::SemanticsCtx) {
293 self.child.semantics_child(ctx);
297 }
298
299 fn visit_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
300 visitor(&self.child);
304 }
305
306 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
307 let size = ctx.size();
318 let has_focus = ctx.has_focus();
319 let hovered = ctx.is_hovered();
324 let hover_epoch = ctx.hover_epoch();
325 let hover_eligible = ctx.is_hover_eligible();
326 let (result, needs_redraw, captured, hover_claimed, focus_req, focus_rel, ime) = {
332 let state_any: &mut dyn Any = &mut self.state;
333 let mut inner = EventCtx::new(state_any, Point::ZERO, size);
334 inner.set_has_focus(has_focus);
335 inner.set_hovered(hovered);
336 inner.set_hover_epoch(hover_epoch);
337 inner.set_hover_eligible(hover_eligible);
338 let result = route_child(&mut self.child, &mut inner, event);
339 (
340 result,
341 inner.needs_redraw(),
342 inner.is_pointer_captured(),
343 inner.is_hover_claimed(),
344 inner.is_focus_requested(),
345 inner.is_focus_released(),
346 inner.take_ime_state(),
347 )
348 };
349 if needs_redraw {
350 ctx.request_redraw();
351 }
352 if captured {
353 ctx.capture_pointer();
354 }
355 if hover_claimed {
356 ctx.claim_hover();
357 }
358 if focus_req {
359 ctx.request_focus();
360 }
361 if focus_rel {
362 ctx.release_focus();
363 }
364 if let Some(ime) = ime {
365 ctx.publish_ime_state(ime);
366 }
367 result
368 }
369}
370
371fn route_child(pod: &mut ChildPod, ctx: &mut EventCtx<'_>, event: &InputEvent) -> EventResult {
385 if event.is_broadcast() {
386 pod.event_child(ctx, event);
389 return EventResult::Ignored;
390 }
391 if event.is_focus_routed() {
392 if pod.is_focused() {
393 return pod.event_child(ctx, event);
394 }
395 return EventResult::Ignored;
396 }
397 if pod.is_active() {
398 let result = pod.event_child(ctx, event);
399 if releases_capture(event) {
400 pod.set_active(false);
401 }
402 return result;
403 }
404 let inside = pod.contains(event.position());
405 let result = if inside {
406 pod.event_child(ctx, event)
407 } else {
408 EventResult::Ignored
409 };
410 if is_pointer_down(event) && !inside && pod.is_focused() {
411 pod.set_focused(false);
412 }
413 result
414}
415
416fn releases_capture(event: &InputEvent) -> bool {
419 matches!(
420 event,
421 InputEvent::Pointer(p) if matches!(p.phase, PointerPhase::Up | PointerPhase::Cancel)
422 )
423}
424
425fn is_pointer_down(event: &InputEvent) -> bool {
427 matches!(
428 event,
429 InputEvent::Pointer(p) if matches!(p.phase, PointerPhase::Down)
430 )
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436 use crate::event::{
437 CursorIcon, EditingState, ImeState, Key, KeyEvent, Modifiers, NamedKey, PointerButton,
438 PointerEvent, clear_cursor_request, take_cursor_request,
439 };
440 use crate::view::any;
441 use kurbo::Rect;
442 use reactive_graph::owner::{on_cleanup, provide_context, use_context};
443 use std::cell::Cell;
444 use std::rc::Rc;
445 use std::sync::atomic::{AtomicU32, Ordering};
446 use std::sync::{Arc, Mutex};
447
448 fn pointer(phase: PointerPhase, x: f64, y: f64) -> InputEvent {
451 InputEvent::Pointer(PointerEvent {
452 phase,
453 position: Point::new(x, y),
454 button: PointerButton::Primary,
455 })
456 }
457
458 fn key_enter() -> InputEvent {
459 InputEvent::Key(KeyEvent {
460 key: Key::Named(NamedKey::Enter),
461 modifiers: Modifiers::default(),
462 repeat: false,
463 })
464 }
465
466 struct Empty;
469 struct EmptyWidget;
470 impl Widget for EmptyWidget {
471 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
472 bc.constrain(Size::new(10.0, 10.0))
473 }
474 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
475 }
476 impl View<()> for Empty {
477 type Element = EmptyWidget;
478 fn build(&self, _ctx: &mut BuildCtx<'_>) -> EmptyWidget {
479 EmptyWidget
480 }
481 fn rebuild(
482 &self,
483 _prev: &Self,
484 _element: &mut EmptyWidget,
485 _ctx: &mut BuildCtx<'_>,
486 ) -> ChangeFlags {
487 ChangeFlags::NONE
488 }
489 }
490
491 struct OtherLeaf;
493 struct OtherWidget;
494 impl Widget for OtherWidget {
495 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
496 bc.constrain(Size::new(10.0, 10.0))
497 }
498 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
499 }
500 impl View<()> for OtherLeaf {
501 type Element = OtherWidget;
502 fn build(&self, _ctx: &mut BuildCtx<'_>) -> OtherWidget {
503 OtherWidget
504 }
505 fn rebuild(
506 &self,
507 _prev: &Self,
508 _element: &mut OtherWidget,
509 _ctx: &mut BuildCtx<'_>,
510 ) -> ChangeFlags {
511 ChangeFlags::NONE
512 }
513 }
514
515 struct CounterState {
518 count: u32,
519 }
520
521 struct ClickCounter {
525 label: &'static str,
526 }
527
528 impl Component for ClickCounter {
529 type State = CounterState;
530 fn init(&self) -> CounterState {
531 CounterState { count: 0 }
532 }
533 fn build(&self, state: &mut CounterState) -> AnyView<CounterState> {
534 any(CounterButtonView {
535 count: state.count,
536 label: self.label,
537 })
538 }
539 }
540
541 struct CounterButtonView {
542 count: u32,
543 label: &'static str,
544 }
545 struct CounterButtonWidget {
546 count: u32,
547 label: &'static str,
548 }
549 impl View<CounterState> for CounterButtonView {
550 type Element = CounterButtonWidget;
551 fn build(&self, _ctx: &mut BuildCtx<'_>) -> CounterButtonWidget {
552 CounterButtonWidget {
553 count: self.count,
554 label: self.label,
555 }
556 }
557 fn rebuild(
558 &self,
559 prev: &Self,
560 element: &mut CounterButtonWidget,
561 _ctx: &mut BuildCtx<'_>,
562 ) -> ChangeFlags {
563 let mut flags = ChangeFlags::NONE;
564 if prev.count != self.count || prev.label != self.label {
565 element.count = self.count;
566 element.label = self.label;
567 flags = ChangeFlags::PAINT;
568 }
569 flags
570 }
571 }
572 impl Widget for CounterButtonWidget {
573 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
574 bc.constrain(Size::new(40.0, 20.0))
575 }
576 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
577 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
578 if let InputEvent::Pointer(p) = event
579 && p.phase == PointerPhase::Down
580 {
581 ctx.state_mut::<CounterState>().count += 1;
582 ctx.request_redraw();
583 return EventResult::Handled;
584 }
585 EventResult::Ignored
586 }
587 }
588
589 fn ambient() -> Owner {
592 let owner = Owner::new();
593 owner.set();
594 owner
595 }
596
597 fn build_widget<Outer: 'static, C: Component>(view: &ComponentView<C>) -> ComponentWidget<C> {
598 let mut next_id = 0u64;
599 let mut ctx = BuildCtx::new(&mut next_id);
600 View::<Outer>::build(view, &mut ctx)
601 }
602
603 #[test]
604 fn local_state_survives_parent_driven_rebuild() {
605 let _owner = ambient();
606 let v1 = component(ClickCounter { label: "a" });
607 let mut widget = build_widget::<(), _>(&v1);
608 let mut lctx = LayoutCtx::new();
609 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 20.0)));
610
611 let mut outer = ();
614 for _ in 0..3 {
615 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 20.0));
616 widget.event(&mut ectx, &pointer(PointerPhase::Down, 5.0, 5.0));
617 }
618 assert_eq!(widget.state.count, 3);
619
620 let v2 = component(ClickCounter { label: "changed" });
622 let mut next_id = 0u64;
623 let mut ctx = BuildCtx::new(&mut next_id);
624 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
625 assert_eq!(widget.state.count, 3, "local state retained across rebuild");
626 }
627
628 #[derive(Clone)]
631 struct Theme(i32);
632
633 struct Provider {
636 sink: Arc<Mutex<Option<i32>>>,
637 }
638 impl Component for Provider {
639 type State = ();
640 fn init(&self) {}
641 fn build(&self, _state: &mut ()) -> AnyView<()> {
642 provide_context(Theme(7));
643 any(component(Reader {
644 sink: self.sink.clone(),
645 }))
646 }
647 }
648
649 struct Reader {
651 sink: Arc<Mutex<Option<i32>>>,
652 }
653 impl Component for Reader {
654 type State = ();
655 fn init(&self) {}
656 fn build(&self, _state: &mut ()) -> AnyView<()> {
657 *self.sink.lock().unwrap() = use_context::<Theme>().map(|t| t.0);
658 any(Empty)
659 }
660 }
661
662 struct SecretProvider;
665 impl Component for SecretProvider {
666 type State = ();
667 fn init(&self) {}
668 fn build(&self, _state: &mut ()) -> AnyView<()> {
669 provide_context(Theme(99));
670 any(Empty)
671 }
672 }
673
674 #[test]
675 fn use_context_resolves_ancestor_provided_value() {
676 let _owner = ambient();
677 let sink = Arc::new(Mutex::new(None));
678 let v = component(Provider { sink: sink.clone() });
679 let _widget = build_widget::<(), _>(&v);
680 assert_eq!(*sink.lock().unwrap(), Some(7));
681 }
682
683 #[test]
684 fn sibling_does_not_see_a_cousins_context() {
685 let parent = ambient();
686 let reader_sink = Arc::new(Mutex::new(Some(-1)));
687 parent.with(|| {
690 let secret = component(SecretProvider);
691 let _s = build_widget::<(), _>(&secret);
692 let reader = component(Reader {
693 sink: reader_sink.clone(),
694 });
695 let _r = build_widget::<(), _>(&reader);
696 });
697 assert_eq!(*reader_sink.lock().unwrap(), None);
698 }
699
700 struct Disposable {
703 probe: Arc<AtomicU32>,
704 }
705 impl Component for Disposable {
706 type State = ();
707 fn init(&self) {
708 let probe = self.probe.clone();
709 on_cleanup(move || {
711 probe.fetch_add(1, Ordering::SeqCst);
712 });
713 }
714 fn build(&self, _state: &mut ()) -> AnyView<()> {
715 any(Empty)
716 }
717 }
718
719 #[test]
720 fn anyview_type_swap_disposes_component_owner_once() {
721 let _owner = ambient();
722 let probe = Arc::new(AtomicU32::new(0));
723 let mut next_id = 0u64;
724 let mut ctx = BuildCtx::new(&mut next_id);
725
726 let prev = any::<(), _>(component(Disposable {
727 probe: probe.clone(),
728 }));
729 let mut element: Box<dyn Widget> = prev.build(&mut ctx);
730 assert_eq!(probe.load(Ordering::SeqCst), 0);
731
732 let next = any::<(), _>(OtherLeaf);
735 next.rebuild(&prev, &mut element, &mut ctx);
736 assert_eq!(probe.load(Ordering::SeqCst), 1);
737
738 drop(element);
740 assert_eq!(probe.load(Ordering::SeqCst), 1);
741 }
742
743 struct SwapComp {
750 swapped: Rc<Cell<bool>>,
751 }
752 impl Component for SwapComp {
753 type State = ();
754 fn init(&self) {}
755 fn build(&self, _state: &mut ()) -> AnyView<()> {
756 if self.swapped.get() {
757 any(OtherLeaf)
758 } else {
759 any(Empty)
760 }
761 }
762 }
763
764 #[test]
765 fn component_swap_arm_marks_the_orphan_only_on_a_live_chain() {
766 fn swap_child(outer_live: bool) -> (bool, bool) {
769 let _ = crate::event::take_focus_orphaned();
770 let _owner = ambient();
771 let swapped = Rc::new(Cell::new(false));
772 let v1 = component(SwapComp {
773 swapped: swapped.clone(),
774 });
775 let mut widget = build_widget::<(), _>(&v1);
776 widget.child.set_focused(true);
778
779 swapped.set(true);
780 let v2 = component(SwapComp {
781 swapped: swapped.clone(),
782 });
783 let mut next_id = 0u64;
784 let mut ctx = BuildCtx::new(&mut next_id);
785 ctx.set_has_focus(outer_live);
787 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
788 (
789 widget.child.is_focused(),
790 crate::event::take_focus_orphaned(),
791 )
792 }
793
794 let (still_focused, marked) = swap_child(true);
795 assert!(
796 !still_focused,
797 "a swapped-in widget must not inherit the old focus link"
798 );
799 assert!(
800 marked,
801 "a live focus path dying inside the component's swap releases the session"
802 );
803
804 let (still_focused, marked) = swap_child(false);
805 assert!(!still_focused, "the flag is dropped either way");
806 assert!(
807 !marked,
808 "the same swap under an already-blurred ancestor owns no session to release"
809 );
810 }
811
812 #[test]
813 fn component_content_only_rebuild_keeps_focus_and_marks_nothing() {
814 let _ = crate::event::take_focus_orphaned();
818 let _owner = ambient();
819 let v1 = component(Labeled { text: "a" });
820 let mut widget = build_widget::<(), _>(&v1);
821 widget.child.set_focused(true);
822
823 let v2 = component(Labeled { text: "b" });
824 let mut next_id = 0u64;
825 let mut ctx = BuildCtx::new(&mut next_id);
826 View::<()>::rebuild(&v2, &v1, &mut widget, &mut ctx);
827
828 assert!(
829 widget.child.is_focused(),
830 "a content-only rebuild keeps the recorded focus path"
831 );
832 assert!(
833 !crate::event::take_focus_orphaned(),
834 "an in-place child rebuild must not orphan the focus session"
835 );
836 }
837
838 #[test]
839 fn explicit_teardown_disposes_owner_once_and_drop_is_idempotent() {
840 let _owner = ambient();
841 let probe = Arc::new(AtomicU32::new(0));
842 let view = component(Disposable {
843 probe: probe.clone(),
844 });
845 let mut widget = build_widget::<(), _>(&view);
846
847 let mut next_id = 0u64;
848 let mut ctx = BuildCtx::new(&mut next_id);
849 View::<()>::teardown(&view, &mut widget, &mut ctx);
850 assert_eq!(probe.load(Ordering::SeqCst), 1);
851
852 drop(widget);
854 assert_eq!(probe.load(Ordering::SeqCst), 1);
855 }
856
857 struct CaptureWidget {
862 pressed: bool,
863 }
864 impl Widget for CaptureWidget {
865 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
866 bc.constrain(Size::new(40.0, 40.0))
867 }
868 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
869 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
870 if let InputEvent::Pointer(p) = event {
871 match p.phase {
872 PointerPhase::Down => {
873 self.pressed = true;
874 ctx.capture_pointer();
875 ctx.request_redraw();
876 return EventResult::Handled;
877 }
878 PointerPhase::Move => return EventResult::Handled,
879 PointerPhase::Up | PointerPhase::Cancel => {
881 self.pressed = false;
882 return EventResult::Handled;
883 }
884 }
885 }
886 EventResult::Ignored
887 }
888 }
889 struct CaptureView;
890 impl View<()> for CaptureView {
891 type Element = CaptureWidget;
892 fn build(&self, _ctx: &mut BuildCtx<'_>) -> CaptureWidget {
893 CaptureWidget { pressed: false }
894 }
895 fn rebuild(
896 &self,
897 _prev: &Self,
898 _element: &mut CaptureWidget,
899 _ctx: &mut BuildCtx<'_>,
900 ) -> ChangeFlags {
901 ChangeFlags::NONE
902 }
903 }
904 struct CaptureComp;
905 impl Component for CaptureComp {
906 type State = ();
907 fn init(&self) {}
908 fn build(&self, _state: &mut ()) -> AnyView<()> {
909 any(CaptureView)
910 }
911 }
912
913 fn inner_widget<C: Component, W: Widget>(w: &mut ComponentWidget<C>) -> &mut W {
916 w.child
917 .widget_mut()
918 .downcast_mut::<Box<dyn Widget>>()
919 .expect("double-boxed AnyView element")
920 .downcast_mut::<W>()
921 .expect("inner concrete widget")
922 }
923
924 #[test]
925 fn capture_survives_drag_outside_bounds_and_mirrors_outward() {
926 let _owner = ambient();
927 let view = component(CaptureComp);
928 let mut widget = build_widget::<(), _>(&view);
929 let mut lctx = LayoutCtx::new();
930 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 40.0)));
931
932 let mut outer = ();
933 let captured = {
935 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
936 widget.event(&mut ectx, &pointer(PointerPhase::Down, 5.0, 5.0));
937 ectx.is_pointer_captured()
938 };
939 assert!(captured, "inner capture bubbles to the outer context");
940 assert!(
941 widget.child.is_active(),
942 "component pod records the capture"
943 );
944
945 {
947 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
948 let r = widget.event(&mut ectx, &pointer(PointerPhase::Move, 500.0, 500.0));
949 assert_eq!(r, EventResult::Handled, "drag outside bounds still routed");
950 }
951 assert!(widget.child.is_active(), "capture survives the drag");
952
953 {
955 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
956 widget.event(&mut ectx, &pointer(PointerPhase::Up, 500.0, 500.0));
957 }
958 assert!(!widget.child.is_active(), "Up auto-releases the capture");
959 assert!(!inner_widget::<_, CaptureWidget>(&mut widget).pressed);
960 }
961
962 #[test]
963 fn synthesized_cancel_over_unit_state_is_safe_and_clears_pressed() {
964 let _owner = ambient();
965 let view = component(CaptureComp);
966 let mut widget = build_widget::<(), _>(&view);
967 let mut lctx = LayoutCtx::new();
968 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 40.0)));
969
970 let mut outer = ();
972 {
973 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
974 widget.event(&mut ectx, &pointer(PointerPhase::Down, 5.0, 5.0));
975 }
976 assert!(inner_widget::<_, CaptureWidget>(&mut widget).pressed);
977
978 let mut dummy = ();
981 {
982 let mut ectx = EventCtx::new(&mut dummy, Point::ZERO, Size::new(40.0, 40.0));
983 widget.event(&mut ectx, &pointer(PointerPhase::Cancel, 0.0, 0.0));
984 }
985 assert!(
986 !inner_widget::<_, CaptureWidget>(&mut widget).pressed,
987 "Cancel cleared pressed across the boundary"
988 );
989 assert!(!widget.child.is_active(), "Cancel released the capture");
990 }
991
992 struct CursorLeafWidget;
994 impl Widget for CursorLeafWidget {
995 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
996 bc.constrain(Size::new(40.0, 40.0))
997 }
998 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
999 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
1000 if let InputEvent::Pointer(p) = event
1001 && p.phase == PointerPhase::Move
1002 {
1003 ctx.set_cursor(CursorIcon::Text);
1004 }
1005 EventResult::Ignored
1006 }
1007 }
1008 struct CursorLeafView;
1009 impl View<()> for CursorLeafView {
1010 type Element = CursorLeafWidget;
1011 fn build(&self, _ctx: &mut BuildCtx<'_>) -> CursorLeafWidget {
1012 CursorLeafWidget
1013 }
1014 fn rebuild(
1015 &self,
1016 _prev: &Self,
1017 _element: &mut CursorLeafWidget,
1018 _ctx: &mut BuildCtx<'_>,
1019 ) -> ChangeFlags {
1020 ChangeFlags::NONE
1021 }
1022 }
1023 struct CursorComp;
1024 impl Component for CursorComp {
1025 type State = ();
1026 fn init(&self) {}
1027 fn build(&self, _state: &mut ()) -> AnyView<()> {
1028 any(CursorLeafView)
1029 }
1030 }
1031
1032 #[test]
1037 fn a_cursor_request_crosses_the_component_boundary() {
1038 let _owner = ambient();
1039 let view = component(CursorComp);
1040 let mut widget = build_widget::<(), _>(&view);
1041 let mut lctx = LayoutCtx::new();
1042 widget.layout(&mut lctx, &BoxConstraints::tight(Size::new(40.0, 40.0)));
1043
1044 clear_cursor_request();
1047 let mut outer = ();
1048 {
1049 let mut ectx = EventCtx::new(&mut outer, Point::ZERO, Size::new(40.0, 40.0));
1050 widget.event(&mut ectx, &pointer(PointerPhase::Move, 5.0, 5.0));
1051 }
1052 assert_eq!(
1053 take_cursor_request(),
1054 Some(CursorIcon::Text),
1055 "a request from below the component boundary reaches the root"
1056 );
1057 }
1058
1059 struct ImeLeafWidget;
1062 impl Widget for ImeLeafWidget {
1063 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1064 bc.constrain(Size::new(100.0, 100.0))
1065 }
1066 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1067 fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
1068 match event {
1069 InputEvent::Pointer(p) if p.phase == PointerPhase::Down => {
1070 if p.position.x < 50.0 {
1071 ctx.request_focus();
1072 ctx.publish_ime_state(ImeState {
1073 active: true,
1074 editing: EditingState {
1075 text: "abc".to_string(),
1076 selection_base: 3,
1077 selection_extent: 3,
1078 composing_base: -1,
1079 composing_extent: -1,
1080 },
1081 caret: Some(Rect::new(0.0, 0.0, 1.0, 12.0)),
1082 content_type: Default::default(),
1083 suppress_soft_keyboard: false,
1084 });
1085 }
1086 EventResult::Handled
1087 }
1088 InputEvent::Key(_) | InputEvent::Ime(_) => EventResult::Handled,
1089 _ => EventResult::Ignored,
1090 }
1091 }
1092 }
1093 struct ImeLeafView;
1094 impl View<()> for ImeLeafView {
1095 type Element = ImeLeafWidget;
1096 fn build(&self, _ctx: &mut BuildCtx<'_>) -> ImeLeafWidget {
1097 ImeLeafWidget
1098 }
1099 fn rebuild(
1100 &self,
1101 _prev: &Self,
1102 _element: &mut ImeLeafWidget,
1103 _ctx: &mut BuildCtx<'_>,
1104 ) -> ChangeFlags {
1105 ChangeFlags::NONE
1106 }
1107 }
1108 struct ImeComp;
1109 impl Component for ImeComp {
1110 type State = ();
1111 fn init(&self) {}
1112 fn build(&self, _state: &mut ()) -> AnyView<()> {
1113 any(ImeLeafView)
1114 }
1115 }
1116
1117 #[test]
1118 fn focus_ime_and_blur_surface_through_render_root() {
1119 use crate::app::RenderRoot;
1120 let _owner = ambient();
1121 let mut root: RenderRoot<(), ComponentView<ImeComp>> = RenderRoot::new();
1122 let mut state = ();
1123 root.rebuild(&mut |_s: &mut ()| component(ImeComp), &mut state);
1124 root.layout(Size::new(100.0, 100.0));
1125
1126 assert!(!root.is_focus_active());
1127 assert!(root.ime_state().is_none());
1128
1129 root.event(&mut state, &pointer(PointerPhase::Down, 10.0, 10.0));
1132 assert!(root.is_focus_active(), "focus request crossed the boundary");
1133 let ime = root.ime_state().expect("IME surface surfaced at the root");
1134 assert!(ime.active);
1135 assert_eq!(ime.editing.text, "abc");
1136
1137 let outcome = root.event(&mut state, &key_enter());
1139 assert!(outcome.handled, "Key routed to the focused inner leaf");
1140
1141 root.event(&mut state, &pointer(PointerPhase::Down, 80.0, 10.0));
1144 assert!(!root.is_focus_active(), "blur crossed the boundary");
1145 assert!(root.ime_state().is_none());
1146 }
1147
1148 struct TextLeafView {
1151 text: &'static str,
1152 }
1153 struct TextLeafWidget {
1154 text: &'static str,
1155 }
1156 impl View<()> for TextLeafView {
1157 type Element = TextLeafWidget;
1158 fn build(&self, _ctx: &mut BuildCtx<'_>) -> TextLeafWidget {
1159 TextLeafWidget { text: self.text }
1160 }
1161 fn rebuild(
1162 &self,
1163 prev: &Self,
1164 element: &mut TextLeafWidget,
1165 _ctx: &mut BuildCtx<'_>,
1166 ) -> ChangeFlags {
1167 if prev.text != self.text {
1168 element.text = self.text;
1169 ChangeFlags::PAINT
1170 } else {
1171 ChangeFlags::NONE
1172 }
1173 }
1174 }
1175 impl Widget for TextLeafWidget {
1176 fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
1177 bc.constrain(Size::new(10.0, 10.0))
1178 }
1179 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
1180 }
1181 struct Labeled {
1182 text: &'static str,
1183 }
1184 impl Component for Labeled {
1185 type State = ();
1186 fn init(&self) {}
1187 fn build(&self, _state: &mut ()) -> AnyView<()> {
1188 any(TextLeafView { text: self.text })
1189 }
1190 }
1191
1192 #[test]
1193 fn child_paint_flags_bubble_out_of_component_rebuild() {
1194 let _owner = ambient();
1195 let v1 = component(Labeled { text: "a" });
1196 let mut widget = build_widget::<(), _>(&v1);
1197
1198 let v_same = component(Labeled { text: "a" });
1200 let mut next_id = 0u64;
1201 let mut ctx = BuildCtx::new(&mut next_id);
1202 let flags = View::<()>::rebuild(&v_same, &v1, &mut widget, &mut ctx);
1203 assert_eq!(flags, ChangeFlags::NONE);
1204
1205 let v2 = component(Labeled { text: "b" });
1208 let flags = View::<()>::rebuild(&v2, &v_same, &mut widget, &mut ctx);
1209 assert_eq!(flags, ChangeFlags::PAINT);
1210 }
1211}