1use crate::PinchEvent;
19use crate::{
20 Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent, DispatchPhase,
21 Display, Element, ElementId, Entity, EntityId, FocusHandle, Global, GlobalElementId, Hitbox,
22 HitboxBehavior, HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent,
23 KeyUpEvent, KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent, MouseButton,
24 MouseClickEvent, MouseDownEvent, MouseMoveEvent, MousePressureEvent, MouseUpEvent, Overflow,
25 ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
26 StyleRefinement, Styled, Task, TooltipId, Visibility, Window, WindowControlArea, point, px,
27 size,
28};
29use open_gpui_collections::HashMap;
30use open_gpui_core_util::ResultExt;
31use open_gpui_refineable::Refineable;
32use smallvec::SmallVec;
33use stacksafe::{StackSafe, stacksafe};
34use std::{
35 any::{Any, TypeId},
36 cell::RefCell,
37 cmp::Ordering,
38 fmt::Debug,
39 marker::PhantomData,
40 mem,
41 rc::Rc,
42 sync::Arc,
43 time::Duration,
44};
45
46use super::ImageCacheProvider;
47
48const DRAG_THRESHOLD: f64 = 2.;
49const DEFAULT_TOOLTIP_SHOW_DELAY: Duration = Duration::from_millis(500);
50const HOVERABLE_TOOLTIP_HIDE_DELAY: Duration = Duration::from_millis(500);
51
52pub struct GroupStyle {
54 pub group: SharedString,
56
57 pub style: Box<StyleRefinement>,
60}
61
62pub struct DragMoveEvent<T> {
64 pub event: MouseMoveEvent,
66
67 pub bounds: Bounds<Pixels>,
69 drag: PhantomData<T>,
70 dragged_item: Arc<dyn Any>,
71}
72
73impl<T: 'static> DragMoveEvent<T> {
74 pub fn drag<'b>(&self, cx: &'b App) -> &'b T {
76 cx.active_drag
77 .as_ref()
78 .and_then(|drag| drag.value.downcast_ref::<T>())
79 .expect("DragMoveEvent is only valid when the stored active drag is of the same type.")
80 }
81
82 pub fn dragged_item(&self) -> &dyn Any {
84 self.dragged_item.as_ref()
85 }
86}
87
88impl Interactivity {
89 #[cfg(any(feature = "inspector", debug_assertions))]
91 #[track_caller]
92 pub fn new() -> Interactivity {
93 Interactivity {
94 source_location: Some(core::panic::Location::caller()),
95 ..Default::default()
96 }
97 }
98
99 #[cfg(not(any(feature = "inspector", debug_assertions)))]
101 pub fn new() -> Interactivity {
102 Interactivity::default()
103 }
104
105 pub fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
107 #[cfg(any(feature = "inspector", debug_assertions))]
108 {
109 self.source_location
110 }
111
112 #[cfg(not(any(feature = "inspector", debug_assertions)))]
113 {
114 None
115 }
116 }
117
118 pub fn on_mouse_down(
123 &mut self,
124 button: MouseButton,
125 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
126 ) {
127 self.mouse_down_listeners
128 .push(Box::new(move |event, phase, hitbox, window, cx| {
129 if phase == DispatchPhase::Bubble
130 && event.button == button
131 && hitbox.is_hovered(window)
132 {
133 (listener)(event, window, cx)
134 }
135 }));
136 }
137
138 pub fn capture_any_mouse_down(
143 &mut self,
144 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
145 ) {
146 self.mouse_down_listeners
147 .push(Box::new(move |event, phase, hitbox, window, cx| {
148 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
149 (listener)(event, window, cx)
150 }
151 }));
152 }
153
154 pub fn on_any_mouse_down(
159 &mut self,
160 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
161 ) {
162 self.mouse_down_listeners
163 .push(Box::new(move |event, phase, hitbox, window, cx| {
164 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
165 (listener)(event, window, cx)
166 }
167 }));
168 }
169
170 pub fn on_mouse_pressure(
175 &mut self,
176 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
177 ) {
178 self.mouse_pressure_listeners
179 .push(Box::new(move |event, phase, hitbox, window, cx| {
180 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
181 (listener)(event, window, cx)
182 }
183 }));
184 }
185
186 pub fn capture_mouse_pressure(
191 &mut self,
192 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
193 ) {
194 self.mouse_pressure_listeners
195 .push(Box::new(move |event, phase, hitbox, window, cx| {
196 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
197 (listener)(event, window, cx)
198 }
199 }));
200 }
201
202 pub fn on_mouse_up(
207 &mut self,
208 button: MouseButton,
209 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
210 ) {
211 self.mouse_up_listeners
212 .push(Box::new(move |event, phase, hitbox, window, cx| {
213 if phase == DispatchPhase::Bubble
214 && event.button == button
215 && hitbox.is_hovered(window)
216 {
217 (listener)(event, window, cx)
218 }
219 }));
220 }
221
222 pub fn capture_any_mouse_up(
227 &mut self,
228 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
229 ) {
230 self.mouse_up_listeners
231 .push(Box::new(move |event, phase, hitbox, window, cx| {
232 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
233 (listener)(event, window, cx)
234 }
235 }));
236 }
237
238 pub fn on_any_mouse_up(
243 &mut self,
244 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
245 ) {
246 self.mouse_up_listeners
247 .push(Box::new(move |event, phase, hitbox, window, cx| {
248 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
249 (listener)(event, window, cx)
250 }
251 }));
252 }
253
254 pub fn on_mouse_down_out(
260 &mut self,
261 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
262 ) {
263 self.mouse_down_listeners
264 .push(Box::new(move |event, phase, hitbox, window, cx| {
265 if phase == DispatchPhase::Capture && !hitbox.contains(&window.mouse_position()) {
266 (listener)(event, window, cx)
267 }
268 }));
269 }
270
271 pub fn on_mouse_up_out(
277 &mut self,
278 button: MouseButton,
279 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
280 ) {
281 self.mouse_up_listeners
282 .push(Box::new(move |event, phase, hitbox, window, cx| {
283 if phase == DispatchPhase::Capture
284 && event.button == button
285 && !hitbox.is_hovered(window)
286 {
287 (listener)(event, window, cx);
288 }
289 }));
290 }
291
292 pub fn on_mouse_move(
297 &mut self,
298 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
299 ) {
300 self.mouse_move_listeners
301 .push(Box::new(move |event, phase, hitbox, window, cx| {
302 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
303 (listener)(event, window, cx);
304 }
305 }));
306 }
307
308 pub fn on_drag_move<T>(
316 &mut self,
317 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
318 ) where
319 T: 'static,
320 {
321 self.mouse_move_listeners
322 .push(Box::new(move |event, phase, hitbox, window, cx| {
323 if phase == DispatchPhase::Capture
324 && let Some(drag) = &cx.active_drag
325 && drag.value.as_ref().type_id() == TypeId::of::<T>()
326 {
327 (listener)(
328 &DragMoveEvent {
329 event: event.clone(),
330 bounds: hitbox.bounds,
331 drag: PhantomData,
332 dragged_item: Arc::clone(&drag.value),
333 },
334 window,
335 cx,
336 );
337 }
338 }));
339 }
340
341 pub fn on_scroll_wheel(
346 &mut self,
347 listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
348 ) {
349 self.scroll_wheel_listeners
350 .push(Box::new(move |event, phase, hitbox, window, cx| {
351 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
352 (listener)(event, window, cx);
353 }
354 }));
355 }
356
357 pub fn on_pinch(&mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) {
361 self.pinch_listeners
362 .push(Box::new(move |event, phase, hitbox, window, cx| {
363 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
364 (listener)(event, window, cx);
365 }
366 }));
367 }
368
369 pub fn capture_pinch(
373 &mut self,
374 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
375 ) {
376 self.pinch_listeners
377 .push(Box::new(move |event, phase, _hitbox, window, cx| {
378 if phase == DispatchPhase::Capture {
379 (listener)(event, window, cx);
380 } else {
381 cx.propagate();
382 }
383 }));
384 }
385
386 pub fn capture_action<A: Action>(
391 &mut self,
392 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
393 ) {
394 self.action_listeners.push((
395 TypeId::of::<A>(),
396 Box::new(move |action, phase, window, cx| {
397 let action = action.downcast_ref().unwrap();
398 if phase == DispatchPhase::Capture {
399 (listener)(action, window, cx)
400 } else {
401 cx.propagate();
402 }
403 }),
404 ));
405 }
406
407 #[track_caller]
412 pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Window, &mut App) + 'static) {
413 self.action_listeners.push((
414 TypeId::of::<A>(),
415 Box::new(move |action, phase, window, cx| {
416 let action = action.downcast_ref().unwrap();
417 if phase == DispatchPhase::Bubble {
418 (listener)(action, window, cx)
419 }
420 }),
421 ));
422 }
423
424 pub fn on_boxed_action(
431 &mut self,
432 action: &dyn Action,
433 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
434 ) {
435 let action = action.boxed_clone();
436 self.action_listeners.push((
437 (*action).type_id(),
438 Box::new(move |_, phase, window, cx| {
439 if phase == DispatchPhase::Bubble {
440 (listener)(&*action, window, cx)
441 }
442 }),
443 ));
444 }
445
446 pub fn on_key_down(
451 &mut self,
452 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
453 ) {
454 self.key_down_listeners
455 .push(Box::new(move |event, phase, window, cx| {
456 if phase == DispatchPhase::Bubble {
457 (listener)(event, window, cx)
458 }
459 }));
460 }
461
462 pub fn capture_key_down(
467 &mut self,
468 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
469 ) {
470 self.key_down_listeners
471 .push(Box::new(move |event, phase, window, cx| {
472 if phase == DispatchPhase::Capture {
473 listener(event, window, cx)
474 }
475 }));
476 }
477
478 pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static) {
483 self.key_up_listeners
484 .push(Box::new(move |event, phase, window, cx| {
485 if phase == DispatchPhase::Bubble {
486 listener(event, window, cx)
487 }
488 }));
489 }
490
491 pub fn capture_key_up(
496 &mut self,
497 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
498 ) {
499 self.key_up_listeners
500 .push(Box::new(move |event, phase, window, cx| {
501 if phase == DispatchPhase::Capture {
502 listener(event, window, cx)
503 }
504 }));
505 }
506
507 pub fn on_modifiers_changed(
512 &mut self,
513 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
514 ) {
515 self.modifiers_changed_listeners
516 .push(Box::new(move |event, window, cx| {
517 listener(event, window, cx)
518 }));
519 }
520
521 pub fn on_drop<T: 'static>(&mut self, listener: impl Fn(&T, &mut Window, &mut App) + 'static) {
526 self.drop_listeners.push((
527 TypeId::of::<T>(),
528 Box::new(move |dragged_value, window, cx| {
529 listener(dragged_value.downcast_ref().unwrap(), window, cx);
530 }),
531 ));
532 }
533
534 pub fn can_drop(
537 &mut self,
538 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
539 ) {
540 self.can_drop_predicate = Some(Box::new(predicate));
541 }
542
543 pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
548 where
549 Self: Sized,
550 {
551 self.click_listeners.push(Rc::new(move |event, window, cx| {
552 listener(event, window, cx)
553 }));
554 }
555
556 pub fn on_aux_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
561 where
562 Self: Sized,
563 {
564 self.aux_click_listeners
565 .push(Rc::new(move |event, window, cx| {
566 listener(event, window, cx)
567 }));
568 }
569
570 pub fn on_drag<T, W>(
577 &mut self,
578 value: T,
579 constructor: impl Fn(&T, Point<Pixels>, Bounds<Pixels>, &mut Window, &mut App) -> Entity<W>
580 + 'static,
581 ) where
582 Self: Sized,
583 T: 'static,
584 W: 'static + Render,
585 {
586 debug_assert!(
587 self.drag_listener.is_none(),
588 "calling on_drag more than once on the same element is not supported"
589 );
590 self.drag_listener = Some((
591 Arc::new(value),
592 Box::new(move |value, offset, bounds, window, cx| {
593 constructor(value.downcast_ref().unwrap(), offset, bounds, window, cx).into()
594 }),
595 ));
596 }
597
598 pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static)
604 where
605 Self: Sized,
606 {
607 debug_assert!(
608 self.hover_listener.is_none(),
609 "calling on_hover more than once on the same element is not supported"
610 );
611 self.hover_listener = Some(Box::new(listener));
612 }
613
614 pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static)
617 where
618 Self: Sized,
619 {
620 debug_assert!(
621 self.tooltip_builder.is_none(),
622 "calling tooltip more than once on the same element is not supported"
623 );
624 self.tooltip_builder = Some(TooltipBuilder {
625 build: Rc::new(build_tooltip),
626 hoverable: false,
627 });
628 }
629
630 pub fn hoverable_tooltip(
634 &mut self,
635 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
636 ) where
637 Self: Sized,
638 {
639 debug_assert!(
640 self.tooltip_builder.is_none(),
641 "calling tooltip more than once on the same element is not supported"
642 );
643 self.tooltip_builder = Some(TooltipBuilder {
644 build: Rc::new(build_tooltip),
645 hoverable: true,
646 });
647 }
648
649 pub fn tooltip_show_delay(&mut self, delay: Duration) {
652 self.tooltip_show_delay = Some(delay);
653 }
654
655 pub fn occlude_mouse(&mut self) {
660 self.hitbox_behavior = HitboxBehavior::BlockMouse;
661 }
662
663 pub fn window_control_area(&mut self, area: WindowControlArea) {
666 self.window_control = Some(area);
667 }
668
669 pub fn block_mouse_except_scroll(&mut self) {
674 self.hitbox_behavior = HitboxBehavior::BlockMouseExceptScroll;
675 }
676
677 fn has_pinch_listeners(&self) -> bool {
678 !self.pinch_listeners.is_empty()
679 }
680}
681
682pub trait InteractiveElement: Sized {
685 fn interactivity(&mut self) -> &mut Interactivity;
687
688 fn group(mut self, group: impl Into<SharedString>) -> Self {
690 self.interactivity().group = Some(group.into());
691 self
692 }
693
694 fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
696 self.interactivity().element_id = Some(id.into());
697
698 Stateful { element: self }
699 }
700
701 fn track_focus(mut self, focus_handle: &FocusHandle) -> Self {
705 self.interactivity().focusable = true;
706 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
707 self
708 }
709
710 fn tab_stop(mut self, tab_stop: bool) -> Self {
717 self.interactivity().tab_stop = tab_stop;
718 self
719 }
720
721 fn tab_index(mut self, index: isize) -> Self {
726 self.interactivity().focusable = true;
727 self.interactivity().tab_index = Some(index);
728 self.interactivity().tab_stop = true;
729 self
730 }
731
732 fn tab_group(mut self) -> Self {
737 self.interactivity().tab_group = true;
738 if self.interactivity().tab_index.is_none() {
739 self.interactivity().tab_index = Some(0);
740 }
741 self
742 }
743
744 fn key_context<C, E>(mut self, key_context: C) -> Self
747 where
748 C: TryInto<KeyContext, Error = E>,
749 E: std::fmt::Display,
750 {
751 if let Some(key_context) = key_context.try_into().log_err() {
752 self.interactivity().key_context = Some(key_context);
753 }
754 self
755 }
756
757 fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
759 debug_assert!(
760 self.interactivity().hover_style.is_none(),
761 "hover style already set"
762 );
763 self.interactivity().hover_style = Some(Box::new(f(StyleRefinement::default())));
764 self
765 }
766
767 fn group_hover(
769 mut self,
770 group_name: impl Into<SharedString>,
771 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
772 ) -> Self {
773 self.interactivity().group_hover_style = Some(GroupStyle {
774 group: group_name.into(),
775 style: Box::new(f(StyleRefinement::default())),
776 });
777 self
778 }
779
780 fn on_mouse_down(
785 mut self,
786 button: MouseButton,
787 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
788 ) -> Self {
789 self.interactivity().on_mouse_down(button, listener);
790 self
791 }
792
793 #[cfg(any(test, feature = "test-support"))]
794 fn debug_selector(mut self, f: impl FnOnce() -> String) -> Self {
798 self.interactivity().debug_selector = Some(f());
799 self
800 }
801
802 #[cfg(not(any(test, feature = "test-support")))]
803 #[inline]
807 fn debug_selector(self, _: impl FnOnce() -> String) -> Self {
808 self
809 }
810
811 fn capture_any_mouse_down(
816 mut self,
817 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
818 ) -> Self {
819 self.interactivity().capture_any_mouse_down(listener);
820 self
821 }
822
823 fn on_any_mouse_down(
828 mut self,
829 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
830 ) -> Self {
831 self.interactivity().on_any_mouse_down(listener);
832 self
833 }
834
835 fn on_mouse_up(
840 mut self,
841 button: MouseButton,
842 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
843 ) -> Self {
844 self.interactivity().on_mouse_up(button, listener);
845 self
846 }
847
848 fn capture_any_mouse_up(
853 mut self,
854 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
855 ) -> Self {
856 self.interactivity().capture_any_mouse_up(listener);
857 self
858 }
859
860 fn on_mouse_pressure(
865 mut self,
866 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
867 ) -> Self {
868 self.interactivity().on_mouse_pressure(listener);
869 self
870 }
871
872 fn capture_mouse_pressure(
877 mut self,
878 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
879 ) -> Self {
880 self.interactivity().capture_mouse_pressure(listener);
881 self
882 }
883
884 fn on_mouse_down_out(
890 mut self,
891 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
892 ) -> Self {
893 self.interactivity().on_mouse_down_out(listener);
894 self
895 }
896
897 fn on_mouse_up_out(
903 mut self,
904 button: MouseButton,
905 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
906 ) -> Self {
907 self.interactivity().on_mouse_up_out(button, listener);
908 self
909 }
910
911 fn on_mouse_move(
916 mut self,
917 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
918 ) -> Self {
919 self.interactivity().on_mouse_move(listener);
920 self
921 }
922
923 fn on_drag_move<T: 'static>(
931 mut self,
932 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
933 ) -> Self {
934 self.interactivity().on_drag_move(listener);
935 self
936 }
937
938 fn on_scroll_wheel(
943 mut self,
944 listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
945 ) -> Self {
946 self.interactivity().on_scroll_wheel(listener);
947 self
948 }
949
950 fn on_pinch(mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) -> Self {
955 self.interactivity().on_pinch(listener);
956 self
957 }
958
959 fn capture_pinch(
964 mut self,
965 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
966 ) -> Self {
967 self.interactivity().capture_pinch(listener);
968 self
969 }
970 fn capture_action<A: Action>(
975 mut self,
976 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
977 ) -> Self {
978 self.interactivity().capture_action(listener);
979 self
980 }
981
982 #[track_caller]
987 fn on_action<A: Action>(
988 mut self,
989 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
990 ) -> Self {
991 self.interactivity().on_action(listener);
992 self
993 }
994
995 fn on_boxed_action(
1002 mut self,
1003 action: &dyn Action,
1004 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
1005 ) -> Self {
1006 self.interactivity().on_boxed_action(action, listener);
1007 self
1008 }
1009
1010 fn on_key_down(
1015 mut self,
1016 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1017 ) -> Self {
1018 self.interactivity().on_key_down(listener);
1019 self
1020 }
1021
1022 fn capture_key_down(
1027 mut self,
1028 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1029 ) -> Self {
1030 self.interactivity().capture_key_down(listener);
1031 self
1032 }
1033
1034 fn on_key_up(
1039 mut self,
1040 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1041 ) -> Self {
1042 self.interactivity().on_key_up(listener);
1043 self
1044 }
1045
1046 fn capture_key_up(
1051 mut self,
1052 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1053 ) -> Self {
1054 self.interactivity().capture_key_up(listener);
1055 self
1056 }
1057
1058 fn on_modifiers_changed(
1063 mut self,
1064 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
1065 ) -> Self {
1066 self.interactivity().on_modifiers_changed(listener);
1067 self
1068 }
1069
1070 fn drag_over<S: 'static>(
1072 mut self,
1073 f: impl 'static + Fn(StyleRefinement, &S, &mut Window, &mut App) -> StyleRefinement,
1074 ) -> Self {
1075 self.interactivity().drag_over_styles.push((
1076 TypeId::of::<S>(),
1077 Box::new(move |currently_dragged: &dyn Any, window, cx| {
1078 f(
1079 StyleRefinement::default(),
1080 currently_dragged.downcast_ref::<S>().unwrap(),
1081 window,
1082 cx,
1083 )
1084 }),
1085 ));
1086 self
1087 }
1088
1089 fn group_drag_over<S: 'static>(
1091 mut self,
1092 group_name: impl Into<SharedString>,
1093 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1094 ) -> Self {
1095 self.interactivity().group_drag_over_styles.push((
1096 TypeId::of::<S>(),
1097 GroupStyle {
1098 group: group_name.into(),
1099 style: Box::new(f(StyleRefinement::default())),
1100 },
1101 ));
1102 self
1103 }
1104
1105 fn on_drop<T: 'static>(
1110 mut self,
1111 listener: impl Fn(&T, &mut Window, &mut App) + 'static,
1112 ) -> Self {
1113 self.interactivity().on_drop(listener);
1114 self
1115 }
1116
1117 fn can_drop(
1120 mut self,
1121 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
1122 ) -> Self {
1123 self.interactivity().can_drop(predicate);
1124 self
1125 }
1126
1127 fn occlude(mut self) -> Self {
1131 self.interactivity().occlude_mouse();
1132 self
1133 }
1134
1135 fn window_control_area(mut self, area: WindowControlArea) -> Self {
1138 self.interactivity().window_control_area(area);
1139 self
1140 }
1141
1142 fn block_mouse_except_scroll(mut self) -> Self {
1147 self.interactivity().block_mouse_except_scroll();
1148 self
1149 }
1150
1151 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1154 where
1155 Self: Sized,
1156 {
1157 self.interactivity().focus_style = Some(Box::new(f(StyleRefinement::default())));
1158 self
1159 }
1160
1161 fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1164 where
1165 Self: Sized,
1166 {
1167 self.interactivity().in_focus_style = Some(Box::new(f(StyleRefinement::default())));
1168 self
1169 }
1170
1171 fn focus_visible(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1176 where
1177 Self: Sized,
1178 {
1179 self.interactivity().focus_visible_style = Some(Box::new(f(StyleRefinement::default())));
1180 self
1181 }
1182}
1183
1184pub trait StatefulInteractiveElement: InteractiveElement {
1187 fn role(mut self, role: accesskit::Role) -> Self {
1191 debug_assert!(
1192 role != accesskit::Role::GenericContainer,
1193 "GenericContainer is filtered out of the a11y tree and has no effect"
1194 );
1195 self.interactivity().override_role = Some(role);
1196 self
1197 }
1198
1199 fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
1201 self.interactivity().aria_label = Some(label.into());
1202 self
1203 }
1204
1205 fn aria_controls(mut self, controls: impl IntoIterator<Item = accesskit::NodeId>) -> Self {
1207 self.interactivity().aria_controls = Some(controls.into_iter().collect());
1208 self
1209 }
1210
1211 fn aria_labelled_by(
1213 mut self,
1214 labelled_by: impl IntoIterator<Item = accesskit::NodeId>,
1215 ) -> Self {
1216 self.interactivity().aria_labelled_by = Some(labelled_by.into_iter().collect());
1217 self
1218 }
1219
1220 fn aria_selected(mut self, selected: bool) -> Self {
1222 self.interactivity().aria_selected = Some(selected);
1223 self
1224 }
1225
1226 fn aria_required(mut self, required: bool) -> Self {
1228 self.interactivity().aria_required = Some(required);
1229 self
1230 }
1231
1232 fn aria_disabled(mut self, disabled: bool) -> Self {
1234 self.interactivity().aria_disabled = Some(disabled);
1235 self
1236 }
1237
1238 fn aria_expanded(mut self, expanded: bool) -> Self {
1240 self.interactivity().aria_expanded = Some(expanded);
1241 self
1242 }
1243
1244 fn aria_toggled(mut self, toggled: accesskit::Toggled) -> Self {
1246 self.interactivity().aria_toggled = Some(toggled);
1247 self
1248 }
1249
1250 fn aria_numeric_value(mut self, value: f64) -> Self {
1252 self.interactivity().aria_numeric_value = Some(value);
1253 self
1254 }
1255
1256 fn aria_min_numeric_value(mut self, value: f64) -> Self {
1258 self.interactivity().aria_min_numeric_value = Some(value);
1259 self
1260 }
1261
1262 fn aria_max_numeric_value(mut self, value: f64) -> Self {
1264 self.interactivity().aria_max_numeric_value = Some(value);
1265 self
1266 }
1267
1268 fn aria_orientation(mut self, orientation: accesskit::Orientation) -> Self {
1270 self.interactivity().aria_orientation = Some(orientation);
1271 self
1272 }
1273
1274 fn aria_level(mut self, level: usize) -> Self {
1276 self.interactivity().aria_level = Some(level);
1277 self
1278 }
1279
1280 fn aria_position_in_set(mut self, position: usize) -> Self {
1282 self.interactivity().aria_position_in_set = Some(position);
1283 self
1284 }
1285
1286 fn aria_size_of_set(mut self, size: usize) -> Self {
1288 self.interactivity().aria_size_of_set = Some(size);
1289 self
1290 }
1291
1292 fn aria_row_index(mut self, index: usize) -> Self {
1294 self.interactivity().aria_row_index = Some(index);
1295 self
1296 }
1297
1298 fn aria_column_index(mut self, index: usize) -> Self {
1300 self.interactivity().aria_column_index = Some(index);
1301 self
1302 }
1303
1304 fn aria_row_count(mut self, count: usize) -> Self {
1306 self.interactivity().aria_row_count = Some(count);
1307 self
1308 }
1309
1310 fn aria_column_count(mut self, count: usize) -> Self {
1312 self.interactivity().aria_column_count = Some(count);
1313 self
1314 }
1315
1316 fn on_a11y_action(
1321 mut self,
1322 action: accesskit::Action,
1323 listener: impl FnMut(Option<&accesskit::ActionData>, &mut crate::Window, &mut crate::App)
1324 + 'static,
1325 ) -> Self {
1326 self.interactivity()
1327 .a11y_action_listeners
1328 .push((action, Box::new(listener)));
1329 self
1330 }
1331
1332 fn focusable(mut self) -> Self {
1334 self.interactivity().focusable = true;
1335 self
1336 }
1337
1338 fn overflow_scroll(mut self) -> Self {
1340 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1341 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1342 self
1343 }
1344
1345 fn overflow_x_scroll(mut self) -> Self {
1347 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1348 self
1349 }
1350
1351 fn overflow_y_scroll(mut self) -> Self {
1353 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1354 self
1355 }
1356
1357 fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
1359 self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone());
1360 self
1361 }
1362
1363 fn anchor_scroll(mut self, scroll_anchor: Option<ScrollAnchor>) -> Self {
1365 self.interactivity().scroll_anchor = scroll_anchor;
1366 self
1367 }
1368
1369 fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1371 where
1372 Self: Sized,
1373 {
1374 self.interactivity().active_style = Some(Box::new(f(StyleRefinement::default())));
1375 self
1376 }
1377
1378 fn group_active(
1380 mut self,
1381 group_name: impl Into<SharedString>,
1382 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1383 ) -> Self
1384 where
1385 Self: Sized,
1386 {
1387 self.interactivity().group_active_style = Some(GroupStyle {
1388 group: group_name.into(),
1389 style: Box::new(f(StyleRefinement::default())),
1390 });
1391 self
1392 }
1393
1394 fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self
1399 where
1400 Self: Sized,
1401 {
1402 self.interactivity().on_click(listener);
1403 self
1404 }
1405
1406 fn on_aux_click(
1411 mut self,
1412 listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
1413 ) -> Self
1414 where
1415 Self: Sized,
1416 {
1417 self.interactivity().on_aux_click(listener);
1418 self
1419 }
1420
1421 fn on_drag<T, W>(
1430 mut self,
1431 value: T,
1432 constructor: impl Fn(&T, Point<Pixels>, Bounds<Pixels>, &mut Window, &mut App) -> Entity<W>
1433 + 'static,
1434 ) -> Self
1435 where
1436 Self: Sized,
1437 T: 'static,
1438 W: 'static + Render,
1439 {
1440 self.interactivity().on_drag(value, constructor);
1441 self
1442 }
1443
1444 fn on_hover(mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self
1450 where
1451 Self: Sized,
1452 {
1453 self.interactivity().on_hover(listener);
1454 self
1455 }
1456
1457 fn tooltip(mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self
1460 where
1461 Self: Sized,
1462 {
1463 self.interactivity().tooltip(build_tooltip);
1464 self
1465 }
1466
1467 fn hoverable_tooltip(
1471 mut self,
1472 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
1473 ) -> Self
1474 where
1475 Self: Sized,
1476 {
1477 self.interactivity().hoverable_tooltip(build_tooltip);
1478 self
1479 }
1480
1481 fn tooltip_show_delay(mut self, delay: Duration) -> Self
1484 where
1485 Self: Sized,
1486 {
1487 self.interactivity().tooltip_show_delay(delay);
1488 self
1489 }
1490}
1491
1492pub(crate) type MouseDownListener =
1493 Box<dyn Fn(&MouseDownEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1494pub(crate) type MouseUpListener =
1495 Box<dyn Fn(&MouseUpEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1496pub(crate) type MousePressureListener =
1497 Box<dyn Fn(&MousePressureEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1498pub(crate) type MouseMoveListener =
1499 Box<dyn Fn(&MouseMoveEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1500
1501pub(crate) type ScrollWheelListener =
1502 Box<dyn Fn(&ScrollWheelEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1503
1504pub(crate) type PinchListener =
1505 Box<dyn Fn(&PinchEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1506
1507pub(crate) type ClickListener = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>;
1508
1509pub(crate) type DragListener = Box<
1510 dyn Fn(&dyn Any, Point<Pixels>, Bounds<Pixels>, &mut Window, &mut App) -> AnyView + 'static,
1511>;
1512
1513type DropListener = Box<dyn Fn(&dyn Any, &mut Window, &mut App) + 'static>;
1514
1515type CanDropPredicate = Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static>;
1516
1517pub(crate) struct TooltipBuilder {
1518 build: Rc<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>,
1519 hoverable: bool,
1520}
1521
1522pub(crate) type KeyDownListener =
1523 Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1524
1525pub(crate) type KeyUpListener =
1526 Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1527
1528pub(crate) type ModifiersChangedListener =
1529 Box<dyn Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static>;
1530
1531pub(crate) type ActionListener =
1532 Box<dyn Fn(&dyn Any, DispatchPhase, &mut Window, &mut App) + 'static>;
1533
1534#[track_caller]
1536pub fn div() -> Div {
1537 Div {
1538 interactivity: Interactivity::new(),
1539 children: SmallVec::default(),
1540 prepaint_listener: None,
1541 image_cache: None,
1542 prepaint_order_fn: None,
1543 }
1544}
1545
1546pub struct Div {
1548 interactivity: Interactivity,
1549 children: SmallVec<[StackSafe<AnyElement>; 2]>,
1550 prepaint_listener: Option<Box<dyn Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static>>,
1551 image_cache: Option<Box<dyn ImageCacheProvider>>,
1552 prepaint_order_fn: Option<Box<dyn Fn(&mut Window, &mut App) -> SmallVec<[usize; 8]>>>,
1553}
1554
1555impl Div {
1556 pub fn on_children_prepainted(
1559 mut self,
1560 listener: impl Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static,
1561 ) -> Self {
1562 self.prepaint_listener = Some(Box::new(listener));
1563 self
1564 }
1565
1566 pub fn image_cache(mut self, cache: impl ImageCacheProvider) -> Self {
1568 self.image_cache = Some(Box::new(cache));
1569 self
1570 }
1571
1572 pub fn with_dynamic_prepaint_order(
1581 mut self,
1582 order_fn: impl Fn(&mut Window, &mut App) -> SmallVec<[usize; 8]> + 'static,
1583 ) -> Self {
1584 self.prepaint_order_fn = Some(Box::new(order_fn));
1585 self
1586 }
1587}
1588
1589pub struct DivFrameState {
1596 child_layout_ids: SmallVec<[LayoutId; 2]>,
1597}
1598
1599#[derive(Clone)]
1601pub struct DivInspectorState {
1602 #[cfg(any(feature = "inspector", debug_assertions))]
1606 pub base_style: Box<StyleRefinement>,
1607 pub bounds: Bounds<Pixels>,
1609 pub content_size: Size<Pixels>,
1611}
1612
1613impl Styled for Div {
1614 fn style(&mut self) -> &mut StyleRefinement {
1615 &mut self.interactivity.base_style
1616 }
1617}
1618
1619impl InteractiveElement for Div {
1620 fn interactivity(&mut self) -> &mut Interactivity {
1621 &mut self.interactivity
1622 }
1623}
1624
1625impl ParentElement for Div {
1626 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1627 self.children
1628 .extend(elements.into_iter().map(StackSafe::new))
1629 }
1630}
1631
1632impl Element for Div {
1633 type RequestLayoutState = DivFrameState;
1634 type PrepaintState = Option<Hitbox>;
1635
1636 fn id(&self) -> Option<ElementId> {
1637 self.interactivity.element_id.clone()
1638 }
1639
1640 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
1641 self.interactivity.source_location()
1642 }
1643
1644 fn a11y_role(&self) -> Option<accesskit::Role> {
1645 self.interactivity
1648 .override_role
1649 .filter(|role| *role != accesskit::Role::GenericContainer)
1650 }
1651
1652 fn write_a11y_info(&self, node: &mut accesskit::Node) {
1653 self.interactivity.write_a11y_info(node);
1654 }
1655
1656 #[stacksafe]
1657 fn request_layout(
1658 &mut self,
1659 global_id: Option<&GlobalElementId>,
1660 inspector_id: Option<&InspectorElementId>,
1661 window: &mut Window,
1662 cx: &mut App,
1663 ) -> (LayoutId, Self::RequestLayoutState) {
1664 let mut child_layout_ids = SmallVec::new();
1665 let image_cache = self
1666 .image_cache
1667 .as_mut()
1668 .map(|provider| provider.provide(window, cx));
1669
1670 let layout_id = window.with_image_cache(image_cache, |window| {
1671 self.interactivity.request_layout(
1672 global_id,
1673 inspector_id,
1674 window,
1675 cx,
1676 |style, window, cx| {
1677 window.with_text_style(style.text_style().cloned(), |window| {
1678 child_layout_ids = self
1679 .children
1680 .iter_mut()
1681 .map(|child| child.request_layout(window, cx))
1682 .collect::<SmallVec<_>>();
1683 window.request_layout(style, child_layout_ids.iter().copied(), cx)
1684 })
1685 },
1686 )
1687 });
1688
1689 (layout_id, DivFrameState { child_layout_ids })
1690 }
1691
1692 #[stacksafe]
1693 fn prepaint(
1694 &mut self,
1695 global_id: Option<&GlobalElementId>,
1696 inspector_id: Option<&InspectorElementId>,
1697 bounds: Bounds<Pixels>,
1698 request_layout: &mut Self::RequestLayoutState,
1699 window: &mut Window,
1700 cx: &mut App,
1701 ) -> Option<Hitbox> {
1702 let image_cache = self
1703 .image_cache
1704 .as_mut()
1705 .map(|provider| provider.provide(window, cx));
1706
1707 let has_prepaint_listener = self.prepaint_listener.is_some();
1708 let mut children_bounds = Vec::with_capacity(if has_prepaint_listener {
1709 request_layout.child_layout_ids.len()
1710 } else {
1711 0
1712 });
1713
1714 let mut child_min = point(Pixels::MAX, Pixels::MAX);
1715 let mut child_max = Point::default();
1716 if let Some(handle) = self.interactivity.scroll_anchor.as_ref() {
1717 *handle.last_origin.borrow_mut() = bounds.origin - window.element_offset();
1718 }
1719 let content_size = if request_layout.child_layout_ids.is_empty() {
1720 bounds.size
1721 } else if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
1722 let mut state = scroll_handle.0.borrow_mut();
1723 state.child_bounds = Vec::with_capacity(request_layout.child_layout_ids.len());
1724 for child_layout_id in &request_layout.child_layout_ids {
1725 let child_bounds = window.layout_bounds(*child_layout_id);
1726 child_min = child_min.min(&child_bounds.origin);
1727 child_max = child_max.max(&child_bounds.bottom_right());
1728 state.child_bounds.push(child_bounds);
1729 }
1730 (child_max - child_min).into()
1731 } else {
1732 for child_layout_id in &request_layout.child_layout_ids {
1733 let child_bounds = window.layout_bounds(*child_layout_id);
1734 child_min = child_min.min(&child_bounds.origin);
1735 child_max = child_max.max(&child_bounds.bottom_right());
1736
1737 if has_prepaint_listener {
1738 children_bounds.push(child_bounds);
1739 }
1740 }
1741 (child_max - child_min).into()
1742 };
1743
1744 if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
1745 scroll_handle.scroll_to_active_item();
1746 }
1747
1748 self.interactivity.prepaint(
1749 global_id,
1750 inspector_id,
1751 bounds,
1752 content_size,
1753 window,
1754 cx,
1755 |style, scroll_offset, hitbox, window, cx| {
1756 if style.display == Display::None {
1758 return hitbox;
1759 }
1760
1761 window.with_image_cache(image_cache, |window| {
1762 window.with_element_offset(scroll_offset, |window| {
1763 if let Some(order_fn) = &self.prepaint_order_fn {
1764 let order = order_fn(window, cx);
1765 for idx in order {
1766 if let Some(child) = self.children.get_mut(idx) {
1767 child.prepaint(window, cx);
1768 }
1769 }
1770 } else {
1771 for child in &mut self.children {
1772 child.prepaint(window, cx);
1773 }
1774 }
1775 });
1776
1777 if let Some(listener) = self.prepaint_listener.as_ref() {
1778 listener(children_bounds, window, cx);
1779 }
1780 });
1781
1782 hitbox
1783 },
1784 )
1785 }
1786
1787 #[stacksafe]
1788 fn paint(
1789 &mut self,
1790 global_id: Option<&GlobalElementId>,
1791 inspector_id: Option<&InspectorElementId>,
1792 bounds: Bounds<Pixels>,
1793 _request_layout: &mut Self::RequestLayoutState,
1794 hitbox: &mut Option<Hitbox>,
1795 window: &mut Window,
1796 cx: &mut App,
1797 ) {
1798 let image_cache = self
1799 .image_cache
1800 .as_mut()
1801 .map(|provider| provider.provide(window, cx));
1802
1803 window.with_image_cache(image_cache, |window| {
1804 self.interactivity.paint(
1805 global_id,
1806 inspector_id,
1807 bounds,
1808 hitbox.as_ref(),
1809 window,
1810 cx,
1811 |style, window, cx| {
1812 if style.display == Display::None {
1814 return;
1815 }
1816
1817 for child in &mut self.children {
1818 child.paint(window, cx);
1819 }
1820 },
1821 )
1822 });
1823 }
1824}
1825
1826impl IntoElement for Div {
1827 type Element = Self;
1828
1829 fn into_element(self) -> Self::Element {
1830 self
1831 }
1832}
1833
1834#[derive(Default)]
1837pub struct Interactivity {
1838 pub element_id: Option<ElementId>,
1840 pub active: Option<bool>,
1842 pub hovered: Option<bool>,
1845 pub(crate) tooltip_id: Option<TooltipId>,
1846 pub(crate) content_size: Size<Pixels>,
1847 pub(crate) key_context: Option<KeyContext>,
1848 pub(crate) focusable: bool,
1849 pub(crate) tracked_focus_handle: Option<FocusHandle>,
1850 pub(crate) tracked_scroll_handle: Option<ScrollHandle>,
1851 pub(crate) scroll_anchor: Option<ScrollAnchor>,
1852 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
1853 pub(crate) group: Option<SharedString>,
1854 pub base_style: Box<StyleRefinement>,
1857 pub(crate) focus_style: Option<Box<StyleRefinement>>,
1858 pub(crate) in_focus_style: Option<Box<StyleRefinement>>,
1859 pub(crate) focus_visible_style: Option<Box<StyleRefinement>>,
1860 pub(crate) hover_style: Option<Box<StyleRefinement>>,
1861 pub(crate) group_hover_style: Option<GroupStyle>,
1862 pub(crate) active_style: Option<Box<StyleRefinement>>,
1863 pub(crate) group_active_style: Option<GroupStyle>,
1864 pub(crate) drag_over_styles: Vec<(
1865 TypeId,
1866 Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> StyleRefinement>,
1867 )>,
1868 pub(crate) group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
1869 pub(crate) mouse_down_listeners: Vec<MouseDownListener>,
1870 pub(crate) mouse_up_listeners: Vec<MouseUpListener>,
1871 pub(crate) mouse_pressure_listeners: Vec<MousePressureListener>,
1872 pub(crate) mouse_move_listeners: Vec<MouseMoveListener>,
1873 pub(crate) scroll_wheel_listeners: Vec<ScrollWheelListener>,
1874 pub(crate) pinch_listeners: Vec<PinchListener>,
1875 pub(crate) key_down_listeners: Vec<KeyDownListener>,
1876 pub(crate) key_up_listeners: Vec<KeyUpListener>,
1877 pub(crate) modifiers_changed_listeners: Vec<ModifiersChangedListener>,
1878 pub(crate) action_listeners: Vec<(TypeId, ActionListener)>,
1879 pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
1880 pub(crate) can_drop_predicate: Option<CanDropPredicate>,
1881 pub(crate) click_listeners: Vec<ClickListener>,
1882 pub(crate) aux_click_listeners: Vec<ClickListener>,
1883 pub(crate) drag_listener: Option<(Arc<dyn Any>, DragListener)>,
1884 pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut Window, &mut App)>>,
1885 pub(crate) tooltip_builder: Option<TooltipBuilder>,
1886 pub(crate) tooltip_show_delay: Option<Duration>,
1887 pub(crate) window_control: Option<WindowControlArea>,
1888 pub(crate) hitbox_behavior: HitboxBehavior,
1889 pub(crate) tab_index: Option<isize>,
1890 pub(crate) tab_group: bool,
1891 pub(crate) tab_stop: bool,
1892
1893 pub(crate) a11y_action_listeners:
1894 Vec<(accesskit::Action, crate::window::a11y::A11yActionListener)>,
1895 pub(crate) override_role: Option<accesskit::Role>,
1896 pub(crate) aria_label: Option<SharedString>,
1897 pub(crate) aria_controls: Option<Vec<accesskit::NodeId>>,
1898 pub(crate) aria_labelled_by: Option<Vec<accesskit::NodeId>>,
1899 pub(crate) aria_selected: Option<bool>,
1900 pub(crate) aria_required: Option<bool>,
1901 pub(crate) aria_disabled: Option<bool>,
1902 pub(crate) aria_expanded: Option<bool>,
1903 pub(crate) aria_toggled: Option<accesskit::Toggled>,
1904 pub(crate) aria_numeric_value: Option<f64>,
1905 pub(crate) aria_min_numeric_value: Option<f64>,
1906 pub(crate) aria_max_numeric_value: Option<f64>,
1907 pub(crate) aria_orientation: Option<accesskit::Orientation>,
1908 pub(crate) aria_level: Option<usize>,
1909 pub(crate) aria_position_in_set: Option<usize>,
1910 pub(crate) aria_size_of_set: Option<usize>,
1911 pub(crate) aria_row_index: Option<usize>,
1912 pub(crate) aria_column_index: Option<usize>,
1913 pub(crate) aria_row_count: Option<usize>,
1914 pub(crate) aria_column_count: Option<usize>,
1915
1916 #[cfg(any(feature = "inspector", debug_assertions))]
1917 pub(crate) source_location: Option<&'static core::panic::Location<'static>>,
1918
1919 #[cfg(any(test, feature = "test-support"))]
1920 pub(crate) debug_selector: Option<String>,
1921}
1922
1923impl Interactivity {
1924 pub fn request_layout(
1926 &mut self,
1927 global_id: Option<&GlobalElementId>,
1928 _inspector_id: Option<&InspectorElementId>,
1929 window: &mut Window,
1930 cx: &mut App,
1931 f: impl FnOnce(Style, &mut Window, &mut App) -> LayoutId,
1932 ) -> LayoutId {
1933 #[cfg(any(feature = "inspector", debug_assertions))]
1934 window.with_inspector_state(
1935 _inspector_id,
1936 cx,
1937 |inspector_state: &mut Option<DivInspectorState>, _window| {
1938 if let Some(inspector_state) = inspector_state {
1939 self.base_style = inspector_state.base_style.clone();
1940 } else {
1941 *inspector_state = Some(DivInspectorState {
1942 base_style: self.base_style.clone(),
1943 bounds: Default::default(),
1944 content_size: Default::default(),
1945 })
1946 }
1947 },
1948 );
1949
1950 window.with_optional_element_state::<InteractiveElementState, _>(
1951 global_id,
1952 |element_state, window| {
1953 let mut element_state =
1954 element_state.map(|element_state| element_state.unwrap_or_default());
1955
1956 if let Some(element_state) = element_state.as_ref()
1957 && cx.has_active_drag()
1958 {
1959 if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() {
1960 *pending_mouse_down.borrow_mut() = None;
1961 }
1962 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
1963 *clicked_state.borrow_mut() = ElementClickedState::default();
1964 }
1965 }
1966
1967 if self.focusable
1972 && self.tracked_focus_handle.is_none()
1973 && let Some(element_state) = element_state.as_mut()
1974 {
1975 let mut handle = element_state
1976 .focus_handle
1977 .get_or_insert_with(|| cx.focus_handle())
1978 .clone()
1979 .tab_stop(self.tab_stop);
1980
1981 if let Some(index) = self.tab_index {
1982 handle = handle.tab_index(index);
1983 }
1984
1985 self.tracked_focus_handle = Some(handle);
1986 }
1987
1988 if let Some(scroll_handle) = self.tracked_scroll_handle.as_ref() {
1989 self.scroll_offset = Some(scroll_handle.0.borrow().offset.clone());
1990 } else if (self.base_style.overflow.x == Some(Overflow::Scroll)
1991 || self.base_style.overflow.y == Some(Overflow::Scroll))
1992 && let Some(element_state) = element_state.as_mut()
1993 {
1994 self.scroll_offset = Some(
1995 element_state
1996 .scroll_offset
1997 .get_or_insert_with(Rc::default)
1998 .clone(),
1999 );
2000 }
2001
2002 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
2003 let layout_id = f(style, window, cx);
2004 (layout_id, element_state)
2005 },
2006 )
2007 }
2008
2009 pub fn prepaint<R>(
2011 &mut self,
2012 global_id: Option<&GlobalElementId>,
2013 _inspector_id: Option<&InspectorElementId>,
2014 bounds: Bounds<Pixels>,
2015 content_size: Size<Pixels>,
2016 window: &mut Window,
2017 cx: &mut App,
2018 f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut Window, &mut App) -> R,
2019 ) -> R {
2020 self.content_size = content_size;
2021
2022 #[cfg(any(feature = "inspector", debug_assertions))]
2023 window.with_inspector_state(
2024 _inspector_id,
2025 cx,
2026 |inspector_state: &mut Option<DivInspectorState>, _window| {
2027 if let Some(inspector_state) = inspector_state {
2028 inspector_state.bounds = bounds;
2029 inspector_state.content_size = content_size;
2030 }
2031 },
2032 );
2033
2034 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
2035 window.set_focus_handle(focus_handle, cx);
2036
2037 if window.a11y.is_active() {
2038 if let Some(global_id) = global_id {
2039 let node_id = global_id.accesskit_node_id();
2040 window.a11y.focus_ids.insert(node_id, focus_handle.id);
2041 if focus_handle.is_focused(window) && window.a11y.nodes.has_node(node_id) {
2042 window.a11y.nodes.set_focus(node_id);
2043 }
2044 }
2045 }
2046 }
2047 window.with_optional_element_state::<InteractiveElementState, _>(
2048 global_id,
2049 |element_state, window| {
2050 let mut element_state =
2051 element_state.map(|element_state| element_state.unwrap_or_default());
2052 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
2053
2054 if let Some(element_state) = element_state.as_mut() {
2055 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
2056 let clicked_state = clicked_state.borrow();
2057 self.active = Some(clicked_state.element);
2058 }
2059 if self.hover_style.is_some() || self.group_hover_style.is_some() {
2060 element_state
2061 .hover_state
2062 .get_or_insert_with(Default::default);
2063 }
2064 if let Some(active_tooltip) = element_state.active_tooltip.as_ref() {
2065 if self.tooltip_builder.is_some() {
2066 self.tooltip_id = set_tooltip_on_window(active_tooltip, window);
2067 } else {
2068 element_state.active_tooltip.take();
2070 }
2071 }
2072 }
2073
2074 window.with_text_style(style.text_style().cloned(), |window| {
2075 window.with_content_mask(
2076 style.overflow_mask(bounds, window.rem_size()),
2077 |window| {
2078 let hitbox = if self.should_insert_hitbox(&style, window, cx) {
2079 Some(window.insert_hitbox(bounds, self.hitbox_behavior))
2080 } else {
2081 None
2082 };
2083
2084 let scroll_offset =
2085 self.clamp_scroll_position(bounds, &style, window, cx);
2086 let result = f(&style, scroll_offset, hitbox, window, cx);
2087 (result, element_state)
2088 },
2089 )
2090 })
2091 },
2092 )
2093 }
2094
2095 fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
2096 self.hitbox_behavior != HitboxBehavior::Normal
2097 || self.window_control.is_some()
2098 || style.mouse_cursor.is_some()
2099 || self.group.is_some()
2100 || self.scroll_offset.is_some()
2101 || self.tracked_focus_handle.is_some()
2102 || self.hover_style.is_some()
2103 || self.group_hover_style.is_some()
2104 || self.hover_listener.is_some()
2105 || !self.mouse_up_listeners.is_empty()
2106 || !self.mouse_pressure_listeners.is_empty()
2107 || !self.mouse_down_listeners.is_empty()
2108 || !self.mouse_move_listeners.is_empty()
2109 || !self.click_listeners.is_empty()
2110 || !self.aux_click_listeners.is_empty()
2111 || !self.scroll_wheel_listeners.is_empty()
2112 || self.has_pinch_listeners()
2113 || self.drag_listener.is_some()
2114 || !self.drop_listeners.is_empty()
2115 || self.tooltip_builder.is_some()
2116 || window.is_inspector_picking(cx)
2117 }
2118
2119 fn clamp_scroll_position(
2120 &self,
2121 bounds: Bounds<Pixels>,
2122 style: &Style,
2123 window: &mut Window,
2124 _cx: &mut App,
2125 ) -> Point<Pixels> {
2126 fn round_to_two_decimals(pixels: Pixels) -> Pixels {
2127 const ROUNDING_FACTOR: f32 = 100.0;
2128 (pixels * ROUNDING_FACTOR).round() / ROUNDING_FACTOR
2129 }
2130
2131 if let Some(scroll_offset) = self.scroll_offset.as_ref() {
2132 let mut scroll_to_bottom = false;
2133 let mut tracked_scroll_handle = self
2134 .tracked_scroll_handle
2135 .as_ref()
2136 .map(|handle| handle.0.borrow_mut());
2137 if let Some(mut scroll_handle_state) = tracked_scroll_handle.as_deref_mut() {
2138 scroll_handle_state.overflow = style.overflow;
2139 scroll_to_bottom = mem::take(&mut scroll_handle_state.scroll_to_bottom);
2140 }
2141
2142 let rem_size = window.rem_size();
2143 let padding = style.padding.to_pixels(bounds.size.into(), rem_size);
2144 let padding_size = size(padding.left + padding.right, padding.top + padding.bottom);
2145 let padded_content_size = self.content_size + padding_size;
2152 let scroll_max = Point::from(padded_content_size - bounds.size)
2153 .map(round_to_two_decimals)
2154 .max(&Default::default());
2155 let mut scroll_offset = scroll_offset.borrow_mut();
2158
2159 scroll_offset.x = scroll_offset.x.clamp(-scroll_max.x, px(0.));
2160 if scroll_to_bottom {
2161 scroll_offset.y = -scroll_max.y;
2162 } else {
2163 scroll_offset.y = scroll_offset.y.clamp(-scroll_max.y, px(0.));
2164 }
2165
2166 if let Some(mut scroll_handle_state) = tracked_scroll_handle {
2167 scroll_handle_state.max_offset = scroll_max;
2168 scroll_handle_state.bounds = bounds;
2169 }
2170
2171 *scroll_offset
2172 } else {
2173 Point::default()
2174 }
2175 }
2176
2177 pub fn paint(
2186 &mut self,
2187 global_id: Option<&GlobalElementId>,
2188 _inspector_id: Option<&InspectorElementId>,
2189 bounds: Bounds<Pixels>,
2190 hitbox: Option<&Hitbox>,
2191 window: &mut Window,
2192 cx: &mut App,
2193 f: impl FnOnce(&Style, &mut Window, &mut App),
2194 ) {
2195 self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(window));
2196 window.with_optional_element_state::<InteractiveElementState, _>(
2197 global_id,
2198 |element_state, window| {
2199 let mut element_state =
2200 element_state.map(|element_state| element_state.unwrap_or_default());
2201
2202 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
2203
2204 #[cfg(any(feature = "test-support", test))]
2205 if let Some(debug_selector) = &self.debug_selector {
2206 window
2207 .next_frame
2208 .debug_bounds
2209 .insert(debug_selector.clone(), bounds);
2210 if let Some(focus_handle) = &self.tracked_focus_handle {
2211 window
2212 .next_frame
2213 .debug_focus_handles
2214 .insert(debug_selector.clone(), focus_handle.id);
2215 }
2216 }
2217
2218 self.paint_hover_group_handler(window, cx);
2219
2220 if style.visibility == Visibility::Hidden {
2221 return ((), element_state);
2222 }
2223
2224 let mut tab_group = None;
2225 if self.tab_group {
2226 tab_group = self.tab_index;
2227 }
2228 if let Some(focus_handle) = &self.tracked_focus_handle {
2229 window.next_frame.tab_stops.insert(focus_handle);
2230 }
2231
2232 window.with_element_opacity(style.opacity, |window| {
2233 style.paint(bounds, window, cx, |window: &mut Window, cx: &mut App| {
2234 window.with_text_style(style.text_style().cloned(), |window| {
2235 window.with_content_mask(
2236 style.overflow_mask(bounds, window.rem_size()),
2237 |window| {
2238 window.with_tab_group(tab_group, |window| {
2239 if let Some(hitbox) = hitbox {
2240 #[cfg(debug_assertions)]
2241 self.paint_debug_info(
2242 global_id, hitbox, &style, window, cx,
2243 );
2244
2245 if let Some(drag) = cx.active_drag.as_ref() {
2246 if let Some(mouse_cursor) = drag.cursor_style {
2247 if window.is_mouse_in_window() {
2248 window
2249 .set_window_cursor_style(mouse_cursor);
2250 }
2251 }
2252 } else {
2253 if let Some(mouse_cursor) = style.mouse_cursor {
2254 window.set_cursor_style(mouse_cursor, hitbox);
2255 }
2256 }
2257
2258 if let Some(group) = self.group.clone() {
2259 GroupHitboxes::push(group, hitbox.id, cx);
2260 }
2261
2262 if let Some(area) = self.window_control {
2263 window.insert_window_control_hitbox(
2264 area,
2265 hitbox.clone(),
2266 );
2267 }
2268
2269 self.paint_mouse_listeners(
2270 hitbox,
2271 element_state.as_mut(),
2272 window,
2273 cx,
2274 );
2275 self.paint_scroll_listener(hitbox, &style, window, cx);
2276 }
2277
2278 self.paint_keyboard_listeners(window, cx);
2279
2280 if window.a11y.is_active() {
2281 if let Some(global_id) = global_id {
2282 if !self.a11y_action_listeners.is_empty() {
2283 let node_id = global_id.accesskit_node_id();
2284 for (action, listener) in
2285 self.a11y_action_listeners.drain(..)
2286 {
2287 window.on_a11y_action(
2288 node_id, action, listener,
2289 );
2290 }
2291 }
2292 }
2293 }
2294
2295 f(&style, window, cx);
2296
2297 if let Some(_hitbox) = hitbox {
2298 #[cfg(any(feature = "inspector", debug_assertions))]
2299 window.insert_inspector_hitbox(
2300 _hitbox.id,
2301 _inspector_id,
2302 cx,
2303 );
2304
2305 if let Some(group) = self.group.as_ref() {
2306 GroupHitboxes::pop(group, cx);
2307 }
2308 }
2309 })
2310 },
2311 );
2312 });
2313 });
2314 });
2315
2316 ((), element_state)
2317 },
2318 );
2319 }
2320
2321 #[cfg(debug_assertions)]
2322 fn paint_debug_info(
2323 &self,
2324 global_id: Option<&GlobalElementId>,
2325 hitbox: &Hitbox,
2326 style: &Style,
2327 window: &mut Window,
2328 cx: &mut App,
2329 ) {
2330 use crate::{BorderStyle, TextAlign};
2331
2332 if let Some(global_id) = global_id
2333 && (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
2334 && hitbox.is_hovered(window)
2335 {
2336 const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
2337 let element_id = format!("{global_id:?}");
2338 let str_len = element_id.len();
2339
2340 let render_debug_text = |window: &mut Window| {
2341 if let Some(text) = window
2342 .text_system()
2343 .shape_text(
2344 element_id.into(),
2345 FONT_SIZE,
2346 &[window.text_style().to_run(str_len)],
2347 None,
2348 None,
2349 )
2350 .ok()
2351 .and_then(|mut text| text.pop())
2352 {
2353 text.paint(hitbox.origin, FONT_SIZE, TextAlign::Left, None, window, cx)
2354 .ok();
2355
2356 let text_bounds = crate::Bounds {
2357 origin: hitbox.origin,
2358 size: text.size(FONT_SIZE),
2359 };
2360 if let Some(source_location) = self.source_location
2361 && text_bounds.contains(&window.mouse_position())
2362 && window.modifiers().secondary()
2363 {
2364 let secondary_held = window.modifiers().secondary();
2365 window.on_key_event({
2366 move |e: &crate::ModifiersChangedEvent, _phase, window, _cx| {
2367 if e.modifiers.secondary() != secondary_held
2368 && text_bounds.contains(&window.mouse_position())
2369 {
2370 window.refresh();
2371 }
2372 }
2373 });
2374
2375 let was_hovered = hitbox.is_hovered(window);
2376 let current_view = window.current_view();
2377 window.on_mouse_event({
2378 let hitbox = hitbox.clone();
2379 move |_: &MouseMoveEvent, phase, window, cx| {
2380 if phase == DispatchPhase::Capture {
2381 let hovered = hitbox.is_hovered(window);
2382 if hovered != was_hovered {
2383 cx.notify(current_view)
2384 }
2385 }
2386 }
2387 });
2388
2389 window.on_mouse_event({
2390 let hitbox = hitbox.clone();
2391 move |e: &crate::MouseDownEvent, phase, window, cx| {
2392 if text_bounds.contains(&e.position)
2393 && phase.capture()
2394 && hitbox.is_hovered(window)
2395 {
2396 cx.stop_propagation();
2397 let Ok(dir) = std::env::current_dir() else {
2398 return;
2399 };
2400
2401 eprintln!(
2402 "This element was created at:\n{}:{}:{}",
2403 dir.join(source_location.file()).to_string_lossy(),
2404 source_location.line(),
2405 source_location.column()
2406 );
2407 }
2408 }
2409 });
2410 window.paint_quad(crate::outline(
2411 crate::Bounds {
2412 origin: hitbox.origin
2413 + crate::point(crate::px(0.), FONT_SIZE - px(2.)),
2414 size: crate::Size {
2415 width: text_bounds.size.width,
2416 height: crate::px(1.),
2417 },
2418 },
2419 crate::red(),
2420 BorderStyle::default(),
2421 ))
2422 }
2423 }
2424 };
2425
2426 window.with_text_style(
2427 Some(crate::TextStyleRefinement {
2428 color: Some(crate::red()),
2429 line_height: Some(FONT_SIZE.into()),
2430 background_color: Some(crate::white()),
2431 ..Default::default()
2432 }),
2433 render_debug_text,
2434 )
2435 }
2436 }
2437
2438 fn paint_mouse_listeners(
2439 &mut self,
2440 hitbox: &Hitbox,
2441 element_state: Option<&mut InteractiveElementState>,
2442 window: &mut Window,
2443 cx: &mut App,
2444 ) {
2445 let is_focused = self
2446 .tracked_focus_handle
2447 .as_ref()
2448 .map(|handle| handle.is_focused(window))
2449 .unwrap_or(false);
2450
2451 if let Some(focus_handle) = self.tracked_focus_handle.clone() {
2455 let hitbox = hitbox.clone();
2456 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, cx| {
2457 if phase == DispatchPhase::Bubble
2458 && hitbox.is_hovered(window)
2459 && !window.default_prevented()
2460 {
2461 window.focus(&focus_handle, cx);
2462 window.prevent_default();
2465 }
2466 });
2467 }
2468
2469 for listener in self.mouse_down_listeners.drain(..) {
2470 let hitbox = hitbox.clone();
2471 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
2472 listener(event, phase, &hitbox, window, cx);
2473 })
2474 }
2475
2476 for listener in self.mouse_up_listeners.drain(..) {
2477 let hitbox = hitbox.clone();
2478 window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
2479 listener(event, phase, &hitbox, window, cx);
2480 })
2481 }
2482
2483 for listener in self.mouse_pressure_listeners.drain(..) {
2484 let hitbox = hitbox.clone();
2485 window.on_mouse_event(move |event: &MousePressureEvent, phase, window, cx| {
2486 listener(event, phase, &hitbox, window, cx);
2487 })
2488 }
2489
2490 for listener in self.mouse_move_listeners.drain(..) {
2491 let hitbox = hitbox.clone();
2492 window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
2493 listener(event, phase, &hitbox, window, cx);
2494 })
2495 }
2496
2497 for listener in self.scroll_wheel_listeners.drain(..) {
2498 let hitbox = hitbox.clone();
2499 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
2500 listener(event, phase, &hitbox, window, cx);
2501 })
2502 }
2503
2504 for listener in self.pinch_listeners.drain(..) {
2505 let hitbox = hitbox.clone();
2506 window.on_mouse_event(move |event: &PinchEvent, phase, window, cx| {
2507 listener(event, phase, &hitbox, window, cx);
2508 })
2509 }
2510
2511 if self.hover_style.is_some()
2512 || self.base_style.mouse_cursor.is_some()
2513 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
2514 {
2515 let hitbox = hitbox.clone();
2516 let hover_state = self.hover_style.as_ref().and_then(|_| {
2517 element_state
2518 .as_ref()
2519 .and_then(|state| state.hover_state.as_ref())
2520 .cloned()
2521 });
2522 let current_view = window.current_view();
2523
2524 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2525 let hovered = hitbox.is_hovered(window);
2526 let was_hovered = hover_state
2527 .as_ref()
2528 .is_some_and(|state| state.borrow().element);
2529 if phase == DispatchPhase::Capture && hovered != was_hovered {
2530 if let Some(hover_state) = &hover_state {
2531 hover_state.borrow_mut().element = hovered;
2532 cx.notify(current_view);
2533 }
2534 }
2535 });
2536 }
2537
2538 if let Some(group_hover) = self.group_hover_style.as_ref() {
2539 if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
2540 let hover_state = element_state
2541 .as_ref()
2542 .and_then(|element| element.hover_state.as_ref())
2543 .cloned();
2544 let current_view = window.current_view();
2545
2546 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2547 let group_hovered = group_hitbox_id.is_hovered(window);
2548 let was_group_hovered = hover_state
2549 .as_ref()
2550 .is_some_and(|state| state.borrow().group);
2551 if phase == DispatchPhase::Capture && group_hovered != was_group_hovered {
2552 if let Some(hover_state) = &hover_state {
2553 hover_state.borrow_mut().group = group_hovered;
2554 }
2555 cx.notify(current_view);
2556 }
2557 });
2558 }
2559 }
2560
2561 let drag_cursor_style = self.base_style.as_ref().mouse_cursor;
2562
2563 let mut drag_listener = mem::take(&mut self.drag_listener);
2564 let drop_listeners = mem::take(&mut self.drop_listeners);
2565 let click_listeners = mem::take(&mut self.click_listeners);
2566 let aux_click_listeners = mem::take(&mut self.aux_click_listeners);
2567 let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
2568
2569 if !drop_listeners.is_empty() {
2570 let hitbox = hitbox.clone();
2571 window.on_mouse_event({
2572 move |_: &MouseUpEvent, phase, window, cx| {
2573 if let Some(drag) = &cx.active_drag
2574 && phase == DispatchPhase::Bubble
2575 && hitbox.is_hovered(window)
2576 {
2577 let drag_state_type = drag.value.as_ref().type_id();
2578 for (drop_state_type, listener) in &drop_listeners {
2579 if *drop_state_type == drag_state_type {
2580 let drag = cx
2581 .active_drag
2582 .take()
2583 .expect("checked for type drag state type above");
2584
2585 let mut can_drop = true;
2586 if let Some(predicate) = &can_drop_predicate {
2587 can_drop = predicate(drag.value.as_ref(), window, cx);
2588 }
2589
2590 if can_drop {
2591 listener(drag.value.as_ref(), window, cx);
2592 window.refresh();
2593 cx.stop_propagation();
2594 }
2595 }
2596 }
2597 }
2598 }
2599 });
2600 }
2601
2602 if let Some(element_state) = element_state {
2603 if !click_listeners.is_empty()
2604 || !aux_click_listeners.is_empty()
2605 || drag_listener.is_some()
2606 {
2607 let pending_mouse_down = element_state
2608 .pending_mouse_down
2609 .get_or_insert_with(Default::default)
2610 .clone();
2611
2612 let clicked_state = element_state
2613 .clicked_state
2614 .get_or_insert_with(Default::default)
2615 .clone();
2616
2617 window.on_mouse_event({
2618 let pending_mouse_down = pending_mouse_down.clone();
2619 let hitbox = hitbox.clone();
2620 let has_aux_click_listeners = !aux_click_listeners.is_empty();
2621 move |event: &MouseDownEvent, phase, window, _cx| {
2622 if phase == DispatchPhase::Bubble
2623 && (event.button == MouseButton::Left || has_aux_click_listeners)
2624 && hitbox.is_hovered(window)
2625 {
2626 *pending_mouse_down.borrow_mut() = Some(event.clone());
2627 window.refresh();
2628 }
2629 }
2630 });
2631
2632 window.on_mouse_event({
2633 let pending_mouse_down = pending_mouse_down.clone();
2634 let hitbox = hitbox.clone();
2635 move |event: &MouseMoveEvent, phase, window, cx| {
2636 if phase == DispatchPhase::Capture {
2637 return;
2638 }
2639
2640 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2641 if let Some(mouse_down) = pending_mouse_down.clone()
2642 && !cx.has_active_drag()
2643 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
2644 && let Some((drag_value, drag_listener)) = drag_listener.take()
2645 && mouse_down.button == MouseButton::Left
2646 {
2647 *clicked_state.borrow_mut() = ElementClickedState::default();
2648 let cursor_offset = event.position - hitbox.origin;
2649 let drag = (drag_listener)(
2650 drag_value.as_ref(),
2651 cursor_offset,
2652 hitbox.bounds,
2653 window,
2654 cx,
2655 );
2656 cx.active_drag = Some(AnyDrag {
2657 view: drag,
2658 value: drag_value,
2659 cursor_offset,
2660 cursor_style: drag_cursor_style,
2661 });
2662 pending_mouse_down.take();
2663 window.refresh();
2664 cx.stop_propagation();
2665 }
2666 }
2667 });
2668
2669 if is_focused {
2670 window.on_key_event({
2672 let click_listeners = click_listeners.clone();
2673 let hitbox = hitbox.clone();
2674 move |event: &KeyUpEvent, phase, window, cx| {
2675 if phase.bubble() && !window.default_prevented() {
2676 let stroke = &event.keystroke;
2677 let keyboard_button = if stroke.key.eq("enter") {
2678 Some(KeyboardButton::Enter)
2679 } else if stroke.key.eq("space") {
2680 Some(KeyboardButton::Space)
2681 } else {
2682 None
2683 };
2684
2685 if let Some(button) = keyboard_button
2686 && !stroke.modifiers.modified()
2687 {
2688 let click_event = ClickEvent::Keyboard(KeyboardClickEvent {
2689 button,
2690 bounds: hitbox.bounds,
2691 });
2692
2693 for listener in &click_listeners {
2694 listener(&click_event, window, cx);
2695 }
2696 }
2697 }
2698 }
2699 });
2700 }
2701
2702 window.on_mouse_event({
2703 let mut captured_mouse_down = None;
2704 let hitbox = hitbox.clone();
2705 move |event: &MouseUpEvent, phase, window, cx| match phase {
2706 DispatchPhase::Capture => {
2710 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2711 if pending_mouse_down.is_some() && hitbox.is_hovered(window) {
2712 captured_mouse_down = pending_mouse_down.take();
2713 window.refresh();
2714 } else if pending_mouse_down.is_some() {
2715 pending_mouse_down.take();
2721 window.refresh();
2722 }
2723 }
2724 DispatchPhase::Bubble => {
2726 if let Some(mouse_down) = captured_mouse_down.take() {
2727 let btn = mouse_down.button;
2728
2729 let mouse_click = ClickEvent::Mouse(MouseClickEvent {
2730 down: mouse_down,
2731 up: event.clone(),
2732 });
2733
2734 match btn {
2735 MouseButton::Left => {
2736 for listener in &click_listeners {
2737 listener(&mouse_click, window, cx);
2738 }
2739 }
2740 _ => {
2741 for listener in &aux_click_listeners {
2742 listener(&mouse_click, window, cx);
2743 }
2744 }
2745 }
2746 }
2747 }
2748 }
2749 });
2750 }
2751
2752 if let Some(hover_listener) = self.hover_listener.take() {
2753 let hitbox = hitbox.clone();
2754 let was_hovered = element_state
2755 .hover_listener_state
2756 .get_or_insert_with(Default::default)
2757 .clone();
2758 let has_mouse_down = element_state
2759 .pending_mouse_down
2760 .get_or_insert_with(Default::default)
2761 .clone();
2762
2763 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2764 if phase != DispatchPhase::Bubble {
2765 return;
2766 }
2767 let is_hovered = has_mouse_down.borrow().is_none()
2768 && !cx.has_active_drag()
2769 && hitbox.is_hovered(window);
2770 let mut was_hovered = was_hovered.borrow_mut();
2771
2772 if is_hovered != *was_hovered {
2773 *was_hovered = is_hovered;
2774 drop(was_hovered);
2775
2776 hover_listener(&is_hovered, window, cx);
2777 }
2778 });
2779 }
2780
2781 if let Some(tooltip_builder) = self.tooltip_builder.take() {
2782 let active_tooltip = element_state
2783 .active_tooltip
2784 .get_or_insert_with(Default::default)
2785 .clone();
2786 let pending_mouse_down = element_state
2787 .pending_mouse_down
2788 .get_or_insert_with(Default::default)
2789 .clone();
2790
2791 let tooltip_is_hoverable = tooltip_builder.hoverable;
2792 let build_tooltip = Rc::new(move |window: &mut Window, cx: &mut App| {
2793 Some(((tooltip_builder.build)(window, cx), tooltip_is_hoverable))
2794 });
2795 let check_is_hovered_during_prepaint = Rc::new({
2797 let pending_mouse_down = pending_mouse_down.clone();
2798 let source_bounds = hitbox.bounds;
2799 move |window: &Window| {
2800 !window.last_input_was_keyboard()
2801 && pending_mouse_down.borrow().is_none()
2802 && source_bounds.contains(&window.mouse_position())
2803 }
2804 });
2805 let check_is_hovered = Rc::new({
2806 let hitbox = hitbox.clone();
2807 move |window: &Window| {
2808 pending_mouse_down.borrow().is_none() && hitbox.is_hovered(window)
2809 }
2810 });
2811 register_tooltip_mouse_handlers(
2812 &active_tooltip,
2813 self.tooltip_id,
2814 build_tooltip,
2815 check_is_hovered,
2816 check_is_hovered_during_prepaint,
2817 self.tooltip_show_delay,
2818 window,
2819 );
2820 }
2821
2822 let active_state = element_state
2825 .clicked_state
2826 .get_or_insert_with(Default::default)
2827 .clone();
2828
2829 {
2830 let active_state = active_state.clone();
2831 window.on_mouse_event(move |_: &MouseUpEvent, phase, window, _cx| {
2832 if phase == DispatchPhase::Capture && active_state.borrow().is_clicked() {
2833 *active_state.borrow_mut() = ElementClickedState::default();
2834 window.refresh();
2835 }
2836 });
2837 }
2838
2839 {
2840 let active_group_hitbox = self
2841 .group_active_style
2842 .as_ref()
2843 .and_then(|group_active| GroupHitboxes::get(&group_active.group, cx));
2844 let hitbox = hitbox.clone();
2845 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _cx| {
2846 if phase == DispatchPhase::Bubble && !window.default_prevented() {
2847 let group_hovered = active_group_hitbox
2848 .is_some_and(|group_hitbox_id| group_hitbox_id.is_hovered(window));
2849 let element_hovered = hitbox.is_hovered(window);
2850 if group_hovered || element_hovered {
2851 *active_state.borrow_mut() = ElementClickedState {
2852 group: group_hovered,
2853 element: element_hovered,
2854 };
2855 window.refresh();
2856 }
2857 }
2858 });
2859 }
2860 }
2861 }
2862
2863 fn paint_keyboard_listeners(&mut self, window: &mut Window, _cx: &mut App) {
2864 let key_down_listeners = mem::take(&mut self.key_down_listeners);
2865 let key_up_listeners = mem::take(&mut self.key_up_listeners);
2866 let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners);
2867 let action_listeners = mem::take(&mut self.action_listeners);
2868 if let Some(context) = self.key_context.clone() {
2869 window.set_key_context(context);
2870 }
2871
2872 for listener in key_down_listeners {
2873 window.on_key_event(move |event: &KeyDownEvent, phase, window, cx| {
2874 listener(event, phase, window, cx);
2875 })
2876 }
2877
2878 for listener in key_up_listeners {
2879 window.on_key_event(move |event: &KeyUpEvent, phase, window, cx| {
2880 listener(event, phase, window, cx);
2881 })
2882 }
2883
2884 for listener in modifiers_changed_listeners {
2885 window.on_modifiers_changed(move |event: &ModifiersChangedEvent, window, cx| {
2886 listener(event, window, cx);
2887 })
2888 }
2889
2890 for (action_type, listener) in action_listeners {
2891 window.on_action(action_type, listener)
2892 }
2893 }
2894
2895 fn paint_hover_group_handler(&self, window: &mut Window, cx: &mut App) {
2896 let group_hitbox = self
2897 .group_hover_style
2898 .as_ref()
2899 .and_then(|group_hover| GroupHitboxes::get(&group_hover.group, cx));
2900
2901 if let Some(group_hitbox) = group_hitbox {
2902 let was_hovered = group_hitbox.is_hovered(window);
2903 let current_view = window.current_view();
2904 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2905 let hovered = group_hitbox.is_hovered(window);
2906 if phase == DispatchPhase::Capture && hovered != was_hovered {
2907 cx.notify(current_view);
2908 }
2909 });
2910 }
2911 }
2912
2913 fn paint_scroll_listener(
2914 &self,
2915 hitbox: &Hitbox,
2916 style: &Style,
2917 window: &mut Window,
2918 _cx: &mut App,
2919 ) {
2920 if let Some(scroll_offset) = self.scroll_offset.clone() {
2921 let overflow = style.overflow;
2922 let allow_concurrent_scroll = style.allow_concurrent_scroll;
2923 let restrict_scroll_to_axis = style.restrict_scroll_to_axis;
2924 let line_height = window.line_height();
2925 let hitbox = hitbox.clone();
2926 let current_view = window.current_view();
2927 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
2928 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
2929 let mut scroll_offset = scroll_offset.borrow_mut();
2930 let old_scroll_offset = *scroll_offset;
2931 let delta = event.delta.pixel_delta(line_height);
2932
2933 let mut delta_x = Pixels::ZERO;
2934 if overflow.x == Overflow::Scroll {
2935 if !delta.x.is_zero() {
2936 delta_x = delta.x;
2937 } else if !restrict_scroll_to_axis && overflow.y != Overflow::Scroll {
2938 delta_x = delta.y;
2939 }
2940 }
2941 let mut delta_y = Pixels::ZERO;
2942 if overflow.y == Overflow::Scroll {
2943 if !delta.y.is_zero() {
2944 delta_y = delta.y;
2945 } else if !restrict_scroll_to_axis && overflow.x != Overflow::Scroll {
2946 delta_y = delta.x;
2947 }
2948 }
2949 if !allow_concurrent_scroll && !delta_x.is_zero() && !delta_y.is_zero() {
2950 if delta_x.abs() > delta_y.abs() {
2951 delta_y = Pixels::ZERO;
2952 } else {
2953 delta_x = Pixels::ZERO;
2954 }
2955 }
2956 scroll_offset.y += delta_y;
2957 scroll_offset.x += delta_x;
2958 if !delta_x.is_zero() || !delta_y.is_zero() {
2959 window.prevent_default();
2963 cx.stop_propagation();
2964 if *scroll_offset != old_scroll_offset {
2965 cx.notify(current_view);
2966 }
2967 }
2968 }
2969 });
2970 }
2971 }
2972
2973 pub fn compute_style(
2975 &self,
2976 global_id: Option<&GlobalElementId>,
2977 hitbox: Option<&Hitbox>,
2978 window: &mut Window,
2979 cx: &mut App,
2980 ) -> Style {
2981 window.with_optional_element_state(global_id, |element_state, window| {
2982 let mut element_state =
2983 element_state.map(|element_state| element_state.unwrap_or_default());
2984 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
2985 (style, element_state)
2986 })
2987 }
2988
2989 fn compute_style_internal(
2991 &self,
2992 hitbox: Option<&Hitbox>,
2993 element_state: Option<&mut InteractiveElementState>,
2994 window: &mut Window,
2995 cx: &mut App,
2996 ) -> Style {
2997 let mut style = Style::default();
2998 style.refine(&self.base_style);
2999
3000 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
3001 if let Some(in_focus_style) = self.in_focus_style.as_ref()
3002 && focus_handle.within_focused(window, cx)
3003 {
3004 style.refine(in_focus_style);
3005 }
3006
3007 if let Some(focus_style) = self.focus_style.as_ref()
3008 && focus_handle.is_focused(window)
3009 {
3010 style.refine(focus_style);
3011 }
3012
3013 if let Some(focus_visible_style) = self.focus_visible_style.as_ref()
3014 && focus_handle.is_focused(window)
3015 && window.last_input_was_keyboard()
3016 {
3017 style.refine(focus_visible_style);
3018 }
3019 }
3020
3021 if !cx.has_active_drag() {
3022 if let Some(group_hover) = self.group_hover_style.as_ref() {
3023 let is_group_hovered =
3024 if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
3025 group_hitbox_id.is_hovered(window)
3026 } else if let Some(element_state) = element_state.as_ref() {
3027 element_state
3028 .hover_state
3029 .as_ref()
3030 .map(|state| state.borrow().group)
3031 .unwrap_or(false)
3032 } else {
3033 false
3034 };
3035
3036 if is_group_hovered {
3037 style.refine(&group_hover.style);
3038 }
3039 }
3040
3041 if let Some(hover_style) = self.hover_style.as_ref() {
3042 let is_hovered = if let Some(hitbox) = hitbox {
3043 hitbox.is_hovered(window)
3044 } else if let Some(element_state) = element_state.as_ref() {
3045 element_state
3046 .hover_state
3047 .as_ref()
3048 .map(|state| state.borrow().element)
3049 .unwrap_or(false)
3050 } else {
3051 false
3052 };
3053
3054 if is_hovered {
3055 style.refine(hover_style);
3056 }
3057 }
3058 }
3059
3060 if let Some(hitbox) = hitbox {
3061 if let Some(drag) = cx.active_drag.take() {
3062 let mut can_drop = true;
3063 if let Some(can_drop_predicate) = &self.can_drop_predicate {
3064 can_drop = can_drop_predicate(drag.value.as_ref(), window, cx);
3065 }
3066
3067 if can_drop {
3068 for (state_type, group_drag_style) in &self.group_drag_over_styles {
3069 if let Some(group_hitbox_id) =
3070 GroupHitboxes::get(&group_drag_style.group, cx)
3071 && *state_type == drag.value.as_ref().type_id()
3072 && group_hitbox_id.is_hovered(window)
3073 {
3074 style.refine(&group_drag_style.style);
3075 }
3076 }
3077
3078 for (state_type, build_drag_over_style) in &self.drag_over_styles {
3079 if *state_type == drag.value.as_ref().type_id() && hitbox.is_hovered(window)
3080 {
3081 style.refine(&build_drag_over_style(drag.value.as_ref(), window, cx));
3082 }
3083 }
3084 }
3085
3086 style.mouse_cursor = if window.is_mouse_in_window() {
3087 drag.cursor_style
3088 } else {
3089 None
3090 };
3091 cx.active_drag = Some(drag);
3092 }
3093 }
3094
3095 if let Some(element_state) = element_state {
3096 let clicked_state = element_state
3097 .clicked_state
3098 .get_or_insert_with(Default::default)
3099 .borrow();
3100 if clicked_state.group
3101 && let Some(group) = self.group_active_style.as_ref()
3102 {
3103 style.refine(&group.style)
3104 }
3105
3106 if let Some(active_style) = self.active_style.as_ref()
3107 && clicked_state.element
3108 {
3109 style.refine(active_style)
3110 }
3111 }
3112
3113 style
3114 }
3115
3116 pub(crate) fn write_a11y_info(&self, node: &mut accesskit::Node) {
3117 if let Some(label) = &self.aria_label {
3118 node.set_label(label.to_string());
3119 }
3120 if let Some(controls) = &self.aria_controls {
3121 node.set_controls(controls.clone());
3122 }
3123 if let Some(labelled_by) = &self.aria_labelled_by {
3124 node.set_labelled_by(labelled_by.clone());
3125 }
3126 if let Some(selected) = self.aria_selected {
3127 node.set_selected(selected);
3128 }
3129 if let Some(required) = self.aria_required {
3130 if required {
3131 node.set_required();
3132 } else {
3133 node.clear_required();
3134 }
3135 }
3136 if let Some(disabled) = self.aria_disabled {
3137 if disabled {
3138 node.set_disabled();
3139 } else {
3140 node.clear_disabled();
3141 }
3142 }
3143 if let Some(expanded) = self.aria_expanded {
3144 node.set_expanded(expanded);
3145 }
3146 if let Some(toggled) = self.aria_toggled {
3147 node.set_toggled(toggled);
3148 }
3149 if let Some(value) = self.aria_numeric_value {
3150 node.set_numeric_value(value);
3151 }
3152 if let Some(value) = self.aria_min_numeric_value {
3153 node.set_min_numeric_value(value);
3154 }
3155 if let Some(value) = self.aria_max_numeric_value {
3156 node.set_max_numeric_value(value);
3157 }
3158 if let Some(orientation) = self.aria_orientation {
3159 node.set_orientation(orientation);
3160 }
3161 if let Some(level) = self.aria_level {
3162 node.set_level(level);
3163 }
3164 if let Some(position) = self.aria_position_in_set {
3165 node.set_position_in_set(position);
3166 }
3167 if let Some(size) = self.aria_size_of_set {
3168 node.set_size_of_set(size);
3169 }
3170 if let Some(index) = self.aria_row_index {
3171 node.set_row_index(index);
3172 }
3173 if let Some(index) = self.aria_column_index {
3174 node.set_column_index(index);
3175 }
3176 if let Some(count) = self.aria_row_count {
3177 node.set_row_count(count);
3178 }
3179 if let Some(count) = self.aria_column_count {
3180 node.set_column_count(count);
3181 }
3182 if !self.click_listeners.is_empty() {
3183 node.add_action(accesskit::Action::Click);
3184 }
3185 if self.tracked_focus_handle.is_some() || self.focusable {
3186 node.add_action(accesskit::Action::Focus);
3187 }
3188 for (action, _) in &self.a11y_action_listeners {
3189 node.add_action(*action);
3190 }
3191 }
3192}
3193
3194#[derive(Default)]
3197pub struct InteractiveElementState {
3198 pub(crate) focus_handle: Option<FocusHandle>,
3199 pub(crate) clicked_state: Option<Rc<RefCell<ElementClickedState>>>,
3200 pub(crate) hover_state: Option<Rc<RefCell<ElementHoverState>>>,
3201 pub(crate) hover_listener_state: Option<Rc<RefCell<bool>>>,
3202 pub(crate) pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
3203 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
3204 pub(crate) active_tooltip: Option<Rc<RefCell<Option<ActiveTooltip>>>>,
3205}
3206
3207#[derive(Copy, Clone, Default, Eq, PartialEq)]
3209pub struct ElementClickedState {
3210 pub group: bool,
3212
3213 pub element: bool,
3215}
3216
3217impl ElementClickedState {
3218 fn is_clicked(&self) -> bool {
3219 self.group || self.element
3220 }
3221}
3222
3223#[derive(Copy, Clone, Default, Eq, PartialEq)]
3225pub struct ElementHoverState {
3226 pub group: bool,
3228
3229 pub element: bool,
3231}
3232
3233pub(crate) enum ActiveTooltip {
3234 WaitingForShow { _task: Task<()> },
3236 Visible {
3238 tooltip: AnyTooltip,
3239 is_hoverable: bool,
3240 },
3241 WaitingForHide {
3244 tooltip: AnyTooltip,
3245 _task: Task<()>,
3246 },
3247}
3248
3249pub(crate) fn clear_active_tooltip(
3250 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3251 window: &mut Window,
3252) {
3253 match active_tooltip.borrow_mut().take() {
3254 None => {}
3255 Some(ActiveTooltip::WaitingForShow { .. }) => {}
3256 Some(ActiveTooltip::Visible { .. }) => window.refresh(),
3257 Some(ActiveTooltip::WaitingForHide { .. }) => window.refresh(),
3258 }
3259}
3260
3261pub(crate) fn clear_active_tooltip_if_not_hoverable(
3262 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3263 window: &mut Window,
3264) {
3265 let should_clear = match active_tooltip.borrow().as_ref() {
3266 None => false,
3267 Some(ActiveTooltip::WaitingForShow { .. }) => false,
3268 Some(ActiveTooltip::Visible { is_hoverable, .. }) => !is_hoverable,
3269 Some(ActiveTooltip::WaitingForHide { .. }) => false,
3270 };
3271 if should_clear {
3272 active_tooltip.borrow_mut().take();
3273 window.refresh();
3274 }
3275}
3276
3277pub(crate) fn set_tooltip_on_window(
3278 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3279 window: &mut Window,
3280) -> Option<TooltipId> {
3281 let tooltip = match active_tooltip.borrow().as_ref() {
3282 None => return None,
3283 Some(ActiveTooltip::WaitingForShow { .. }) => return None,
3284 Some(ActiveTooltip::Visible { tooltip, .. }) => tooltip.clone(),
3285 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => tooltip.clone(),
3286 };
3287 Some(window.set_tooltip(tooltip))
3288}
3289
3290pub(crate) fn register_tooltip_mouse_handlers(
3291 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3292 tooltip_id: Option<TooltipId>,
3293 build_tooltip: Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3294 check_is_hovered: Rc<dyn Fn(&Window) -> bool>,
3295 check_is_hovered_during_prepaint: Rc<dyn Fn(&Window) -> bool>,
3296 show_delay: Option<Duration>,
3297 window: &mut Window,
3298) {
3299 let current_view = window.current_view();
3300 let show_delay = show_delay.unwrap_or(DEFAULT_TOOLTIP_SHOW_DELAY);
3301
3302 window.on_mouse_event({
3303 let active_tooltip = active_tooltip.clone();
3304 let build_tooltip = build_tooltip.clone();
3305 let check_is_hovered = check_is_hovered.clone();
3306 move |_: &MouseMoveEvent, phase, window, cx| {
3307 handle_tooltip_mouse_move(
3308 &active_tooltip,
3309 &build_tooltip,
3310 &check_is_hovered,
3311 &check_is_hovered_during_prepaint,
3312 tooltip_id,
3313 current_view,
3314 phase,
3315 show_delay,
3316 window,
3317 cx,
3318 )
3319 }
3320 });
3321
3322 window.on_mouse_event({
3323 let active_tooltip = active_tooltip.clone();
3324 move |_: &MouseDownEvent, _phase, window: &mut Window, _cx| {
3325 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3326 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3327 }
3328 }
3329 });
3330
3331 window.on_mouse_event({
3332 let active_tooltip = active_tooltip.clone();
3333 move |_: &ScrollWheelEvent, _phase, window: &mut Window, _cx| {
3334 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3335 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3336 }
3337 }
3338 });
3339}
3340
3341fn handle_tooltip_mouse_move(
3353 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3354 build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3355 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3356 check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
3357 tooltip_id: Option<TooltipId>,
3358 current_view: EntityId,
3359 phase: DispatchPhase,
3360 show_delay: Duration,
3361 window: &mut Window,
3362 cx: &mut App,
3363) {
3364 enum Action {
3367 None,
3368 CancelShow,
3369 ScheduleShow,
3370 CheckVisible,
3371 }
3372
3373 let action = match active_tooltip.borrow().as_ref() {
3374 None => {
3375 let is_hovered = check_is_hovered(window);
3376 if is_hovered && phase.bubble() {
3377 Action::ScheduleShow
3378 } else {
3379 Action::None
3380 }
3381 }
3382 Some(ActiveTooltip::WaitingForShow { .. }) => {
3383 let is_hovered = check_is_hovered(window);
3384 if is_hovered {
3385 Action::None
3386 } else {
3387 Action::CancelShow
3388 }
3389 }
3390 Some(ActiveTooltip::Visible { is_hoverable, .. }) => {
3391 if phase.capture()
3392 && !check_is_hovered(window)
3393 && (!*is_hoverable
3394 || !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3395 {
3396 Action::CheckVisible
3397 } else {
3398 Action::None
3399 }
3400 }
3401 Some(ActiveTooltip::WaitingForHide { .. }) => {
3402 if phase.capture()
3403 && (check_is_hovered(window)
3404 || tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3405 {
3406 Action::CheckVisible
3407 } else {
3408 Action::None
3409 }
3410 }
3411 };
3412
3413 match action {
3414 Action::None => {}
3415 Action::CancelShow => {
3416 active_tooltip.borrow_mut().take();
3418 }
3419 Action::ScheduleShow => {
3420 let delayed_show_task = window.spawn(cx, {
3421 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3422 let build_tooltip = build_tooltip.clone();
3423 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3424 async move |cx| {
3425 cx.background_executor().timer(show_delay).await;
3426 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3427 return;
3428 };
3429 cx.update(|window, cx| {
3430 let new_tooltip =
3431 build_tooltip(window, cx).map(|(view, tooltip_is_hoverable)| {
3432 let weak_active_tooltip = Rc::downgrade(&active_tooltip);
3433 ActiveTooltip::Visible {
3434 tooltip: AnyTooltip {
3435 view,
3436 mouse_position: window.mouse_position(),
3437 check_visible_and_update: Rc::new(
3438 move |tooltip_bounds, window, cx| {
3439 let Some(active_tooltip) =
3440 weak_active_tooltip.upgrade()
3441 else {
3442 return false;
3443 };
3444 handle_tooltip_check_visible_and_update(
3445 &active_tooltip,
3446 tooltip_is_hoverable,
3447 &check_is_hovered_during_prepaint,
3448 tooltip_bounds,
3449 window,
3450 cx,
3451 )
3452 },
3453 ),
3454 },
3455 is_hoverable: tooltip_is_hoverable,
3456 }
3457 });
3458 *active_tooltip.borrow_mut() = new_tooltip;
3459 window.refresh();
3460 })
3461 .ok();
3462 }
3463 });
3464 active_tooltip
3465 .borrow_mut()
3466 .replace(ActiveTooltip::WaitingForShow {
3467 _task: delayed_show_task,
3468 });
3469 }
3470 Action::CheckVisible => cx.notify(current_view),
3471 }
3472}
3473
3474fn handle_tooltip_check_visible_and_update(
3478 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3479 tooltip_is_hoverable: bool,
3480 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3481 tooltip_bounds: Bounds<Pixels>,
3482 window: &mut Window,
3483 cx: &mut App,
3484) -> bool {
3485 enum Action {
3488 None,
3489 Hide,
3490 ScheduleHide(AnyTooltip),
3491 CancelHide(AnyTooltip),
3492 }
3493
3494 let is_hovered = check_is_hovered(window)
3495 || (tooltip_is_hoverable && tooltip_bounds.contains(&window.mouse_position()));
3496 let action = match active_tooltip.borrow().as_ref() {
3497 Some(ActiveTooltip::Visible { tooltip, .. }) => {
3498 if is_hovered {
3499 Action::None
3500 } else {
3501 if tooltip_is_hoverable {
3502 Action::ScheduleHide(tooltip.clone())
3503 } else {
3504 Action::Hide
3505 }
3506 }
3507 }
3508 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => {
3509 if is_hovered {
3510 Action::CancelHide(tooltip.clone())
3511 } else {
3512 Action::None
3513 }
3514 }
3515 None | Some(ActiveTooltip::WaitingForShow { .. }) => Action::None,
3516 };
3517
3518 match action {
3519 Action::None => {}
3520 Action::Hide => clear_active_tooltip(active_tooltip, window),
3521 Action::ScheduleHide(tooltip) => {
3522 let delayed_hide_task = window.spawn(cx, {
3523 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3524 async move |cx| {
3525 cx.background_executor()
3526 .timer(HOVERABLE_TOOLTIP_HIDE_DELAY)
3527 .await;
3528 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3529 return;
3530 };
3531 if active_tooltip.borrow_mut().take().is_some() {
3532 cx.update(|window, _cx| window.refresh()).ok();
3533 }
3534 }
3535 });
3536 active_tooltip
3537 .borrow_mut()
3538 .replace(ActiveTooltip::WaitingForHide {
3539 tooltip,
3540 _task: delayed_hide_task,
3541 });
3542 }
3543 Action::CancelHide(tooltip) => {
3544 active_tooltip.borrow_mut().replace(ActiveTooltip::Visible {
3546 tooltip,
3547 is_hoverable: true,
3548 });
3549 }
3550 }
3551
3552 active_tooltip.borrow().is_some()
3553}
3554
3555#[derive(Default)]
3556pub(crate) struct GroupHitboxes(HashMap<SharedString, SmallVec<[HitboxId; 1]>>);
3557
3558impl Global for GroupHitboxes {}
3559
3560impl GroupHitboxes {
3561 pub fn get(name: &SharedString, cx: &mut App) -> Option<HitboxId> {
3562 cx.default_global::<Self>()
3563 .0
3564 .get(name)
3565 .and_then(|bounds_stack| bounds_stack.last())
3566 .cloned()
3567 }
3568
3569 pub fn push(name: SharedString, hitbox_id: HitboxId, cx: &mut App) {
3570 cx.default_global::<Self>()
3571 .0
3572 .entry(name)
3573 .or_default()
3574 .push(hitbox_id);
3575 }
3576
3577 pub fn pop(name: &SharedString, cx: &mut App) {
3578 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
3579 }
3580}
3581
3582pub struct Stateful<E> {
3584 pub(crate) element: E,
3585}
3586
3587impl<E> Styled for Stateful<E>
3588where
3589 E: Styled,
3590{
3591 fn style(&mut self) -> &mut StyleRefinement {
3592 self.element.style()
3593 }
3594}
3595
3596impl<E> StatefulInteractiveElement for Stateful<E>
3597where
3598 E: Element,
3599 Self: InteractiveElement,
3600{
3601}
3602
3603impl<E> InteractiveElement for Stateful<E>
3604where
3605 E: InteractiveElement,
3606{
3607 fn interactivity(&mut self) -> &mut Interactivity {
3608 self.element.interactivity()
3609 }
3610}
3611
3612impl<E> Element for Stateful<E>
3613where
3614 E: Element,
3615{
3616 type RequestLayoutState = E::RequestLayoutState;
3617 type PrepaintState = E::PrepaintState;
3618
3619 fn id(&self) -> Option<ElementId> {
3620 self.element.id()
3621 }
3622
3623 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
3624 self.element.source_location()
3625 }
3626
3627 fn a11y_role(&self) -> Option<accesskit::Role> {
3628 self.element.a11y_role()
3629 }
3630
3631 fn write_a11y_info(&self, node: &mut accesskit::Node) {
3632 self.element.write_a11y_info(node);
3633 }
3634
3635 fn request_layout(
3636 &mut self,
3637 id: Option<&GlobalElementId>,
3638 inspector_id: Option<&InspectorElementId>,
3639 window: &mut Window,
3640 cx: &mut App,
3641 ) -> (LayoutId, Self::RequestLayoutState) {
3642 self.element.request_layout(id, inspector_id, window, cx)
3643 }
3644
3645 fn prepaint(
3646 &mut self,
3647 id: Option<&GlobalElementId>,
3648 inspector_id: Option<&InspectorElementId>,
3649 bounds: Bounds<Pixels>,
3650 state: &mut Self::RequestLayoutState,
3651 window: &mut Window,
3652 cx: &mut App,
3653 ) -> E::PrepaintState {
3654 self.element
3655 .prepaint(id, inspector_id, bounds, state, window, cx)
3656 }
3657
3658 fn paint(
3659 &mut self,
3660 id: Option<&GlobalElementId>,
3661 inspector_id: Option<&InspectorElementId>,
3662 bounds: Bounds<Pixels>,
3663 request_layout: &mut Self::RequestLayoutState,
3664 prepaint: &mut Self::PrepaintState,
3665 window: &mut Window,
3666 cx: &mut App,
3667 ) {
3668 self.element.paint(
3669 id,
3670 inspector_id,
3671 bounds,
3672 request_layout,
3673 prepaint,
3674 window,
3675 cx,
3676 );
3677 }
3678}
3679
3680impl<E> IntoElement for Stateful<E>
3681where
3682 E: Element,
3683{
3684 type Element = Self;
3685
3686 fn into_element(self) -> Self::Element {
3687 self
3688 }
3689}
3690
3691impl<E> ParentElement for Stateful<E>
3692where
3693 E: ParentElement,
3694{
3695 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
3696 self.element.extend(elements)
3697 }
3698}
3699
3700#[derive(Clone, Debug)]
3703pub struct ScrollAnchor {
3704 handle: ScrollHandle,
3705 last_origin: Rc<RefCell<Point<Pixels>>>,
3706}
3707
3708impl ScrollAnchor {
3709 pub fn for_handle(handle: ScrollHandle) -> Self {
3711 Self {
3712 handle,
3713 last_origin: Default::default(),
3714 }
3715 }
3716 pub fn scroll_to(&self, window: &mut Window, _cx: &mut App) {
3718 let this = self.clone();
3719
3720 window.on_next_frame(move |_, _| {
3721 this.scroll_now();
3722 });
3723 }
3724
3725 pub fn scroll_now(&self) {
3727 let viewport_bounds = self.handle.bounds();
3728 let self_bounds = *self.last_origin.borrow();
3729 self.handle.set_offset(viewport_bounds.origin - self_bounds);
3730 }
3731}
3732
3733#[derive(Default, Debug)]
3734struct ScrollHandleState {
3735 offset: Rc<RefCell<Point<Pixels>>>,
3736 bounds: Bounds<Pixels>,
3737 max_offset: Point<Pixels>,
3738 child_bounds: Vec<Bounds<Pixels>>,
3739 scroll_to_bottom: bool,
3740 overflow: Point<Overflow>,
3741 active_item: Option<ScrollActiveItem>,
3742}
3743
3744#[derive(Default, Debug, Clone, Copy)]
3745struct ScrollActiveItem {
3746 index: usize,
3747 strategy: ScrollStrategy,
3748}
3749
3750#[derive(Default, Debug, Clone, Copy)]
3751enum ScrollStrategy {
3752 #[default]
3753 FirstVisible,
3754 Top,
3755}
3756
3757#[derive(Clone, Debug)]
3761pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
3762
3763impl Default for ScrollHandle {
3764 fn default() -> Self {
3765 Self::new()
3766 }
3767}
3768
3769impl ScrollHandle {
3770 pub fn new() -> Self {
3772 Self(Rc::default())
3773 }
3774
3775 pub fn offset(&self) -> Point<Pixels> {
3777 *self.0.borrow().offset.borrow()
3778 }
3779
3780 pub fn max_offset(&self) -> Point<Pixels> {
3782 self.0.borrow().max_offset
3783 }
3784
3785 pub fn top_item(&self) -> usize {
3787 let state = self.0.borrow();
3788 let top = state.bounds.top() - state.offset.borrow().y;
3789
3790 match state.child_bounds.binary_search_by(|bounds| {
3791 if top < bounds.top() {
3792 Ordering::Greater
3793 } else if top > bounds.bottom() {
3794 Ordering::Less
3795 } else {
3796 Ordering::Equal
3797 }
3798 }) {
3799 Ok(ix) => ix,
3800 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
3801 }
3802 }
3803
3804 pub fn bottom_item(&self) -> usize {
3806 let state = self.0.borrow();
3807 let bottom = state.bounds.bottom() - state.offset.borrow().y;
3808
3809 match state.child_bounds.binary_search_by(|bounds| {
3810 if bottom < bounds.top() {
3811 Ordering::Greater
3812 } else if bottom > bounds.bottom() {
3813 Ordering::Less
3814 } else {
3815 Ordering::Equal
3816 }
3817 }) {
3818 Ok(ix) => ix,
3819 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
3820 }
3821 }
3822
3823 pub fn bounds(&self) -> Bounds<Pixels> {
3825 self.0.borrow().bounds
3826 }
3827
3828 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
3830 self.0.borrow().child_bounds.get(ix).cloned()
3831 }
3832
3833 pub fn scroll_to_item(&self, ix: usize) {
3835 let mut state = self.0.borrow_mut();
3836 state.active_item = Some(ScrollActiveItem {
3837 index: ix,
3838 strategy: ScrollStrategy::default(),
3839 });
3840 }
3841
3842 pub fn scroll_to_top_of_item(&self, ix: usize) {
3845 let mut state = self.0.borrow_mut();
3846 state.active_item = Some(ScrollActiveItem {
3847 index: ix,
3848 strategy: ScrollStrategy::Top,
3849 });
3850 }
3851
3852 fn scroll_to_active_item(&self) {
3856 let mut state = self.0.borrow_mut();
3857
3858 let Some(active_item) = state.active_item else {
3859 return;
3860 };
3861
3862 let active_item = match state.child_bounds.get(active_item.index) {
3863 Some(bounds) => {
3864 let mut scroll_offset = state.offset.borrow_mut();
3865
3866 match active_item.strategy {
3867 ScrollStrategy::FirstVisible => {
3868 if state.overflow.y == Overflow::Scroll {
3869 let child_height = bounds.size.height;
3870 let viewport_height = state.bounds.size.height;
3871 if child_height > viewport_height {
3872 scroll_offset.y = state.bounds.top() - bounds.top();
3873 } else if bounds.top() + scroll_offset.y < state.bounds.top() {
3874 scroll_offset.y = state.bounds.top() - bounds.top();
3875 } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
3876 scroll_offset.y = state.bounds.bottom() - bounds.bottom();
3877 }
3878 }
3879 }
3880 ScrollStrategy::Top => {
3881 scroll_offset.y = state.bounds.top() - bounds.top();
3882 }
3883 }
3884
3885 if state.overflow.x == Overflow::Scroll {
3886 let child_width = bounds.size.width;
3887 let viewport_width = state.bounds.size.width;
3888 if child_width > viewport_width {
3889 scroll_offset.x = state.bounds.left() - bounds.left();
3890 } else if bounds.left() + scroll_offset.x < state.bounds.left() {
3891 scroll_offset.x = state.bounds.left() - bounds.left();
3892 } else if bounds.right() + scroll_offset.x > state.bounds.right() {
3893 scroll_offset.x = state.bounds.right() - bounds.right();
3894 }
3895 }
3896 None
3897 }
3898 None => Some(active_item),
3899 };
3900 state.active_item = active_item;
3901 }
3902
3903 pub fn scroll_to_bottom(&self) {
3905 let mut state = self.0.borrow_mut();
3906 state.scroll_to_bottom = true;
3907 }
3908
3909 pub fn set_offset(&self, mut position: Point<Pixels>) {
3913 let state = self.0.borrow();
3914 *state.offset.borrow_mut() = position;
3915 }
3916
3917 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
3919 let ix = self.top_item();
3920 let state = self.0.borrow();
3921
3922 if let Some(child_bounds) = state.child_bounds.get(ix) {
3923 (
3924 ix,
3925 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
3926 )
3927 } else {
3928 (ix, px(0.))
3929 }
3930 }
3931
3932 pub fn logical_scroll_bottom(&self) -> (usize, Pixels) {
3934 let ix = self.bottom_item();
3935 let state = self.0.borrow();
3936
3937 if let Some(child_bounds) = state.child_bounds.get(ix) {
3938 (
3939 ix,
3940 child_bounds.bottom() + state.offset.borrow().y - state.bounds.bottom(),
3941 )
3942 } else {
3943 (ix, px(0.))
3944 }
3945 }
3946
3947 pub fn children_count(&self) -> usize {
3949 self.0.borrow().child_bounds.len()
3950 }
3951}
3952
3953#[cfg(test)]
3954mod tests {
3955 use super::*;
3956 use crate::{
3957 AppContext as _, Context, InputEvent, MouseMoveEvent, TestAppContext,
3958 util::FluentBuilder as _,
3959 };
3960 use std::rc::Weak;
3961
3962 struct TestTooltipView;
3963
3964 impl Render for TestTooltipView {
3965 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
3966 div().w(px(20.)).h(px(20.)).child("tooltip")
3967 }
3968 }
3969
3970 type CapturedActiveTooltip = Rc<RefCell<Option<Weak<RefCell<Option<ActiveTooltip>>>>>>;
3971
3972 struct TooltipCaptureElement {
3973 child: AnyElement,
3974 captured_active_tooltip: CapturedActiveTooltip,
3975 }
3976
3977 impl IntoElement for TooltipCaptureElement {
3978 type Element = Self;
3979
3980 fn into_element(self) -> Self::Element {
3981 self
3982 }
3983 }
3984
3985 impl Element for TooltipCaptureElement {
3986 type RequestLayoutState = ();
3987 type PrepaintState = ();
3988
3989 fn id(&self) -> Option<ElementId> {
3990 None
3991 }
3992
3993 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
3994 None
3995 }
3996
3997 fn request_layout(
3998 &mut self,
3999 _id: Option<&GlobalElementId>,
4000 _inspector_id: Option<&InspectorElementId>,
4001 window: &mut Window,
4002 cx: &mut App,
4003 ) -> (LayoutId, Self::RequestLayoutState) {
4004 (self.child.request_layout(window, cx), ())
4005 }
4006
4007 fn prepaint(
4008 &mut self,
4009 _id: Option<&GlobalElementId>,
4010 _inspector_id: Option<&InspectorElementId>,
4011 _bounds: Bounds<Pixels>,
4012 _request_layout: &mut Self::RequestLayoutState,
4013 window: &mut Window,
4014 cx: &mut App,
4015 ) -> Self::PrepaintState {
4016 self.child.prepaint(window, cx);
4017 }
4018
4019 fn paint(
4020 &mut self,
4021 _id: Option<&GlobalElementId>,
4022 _inspector_id: Option<&InspectorElementId>,
4023 _bounds: Bounds<Pixels>,
4024 _request_layout: &mut Self::RequestLayoutState,
4025 _prepaint: &mut Self::PrepaintState,
4026 window: &mut Window,
4027 cx: &mut App,
4028 ) {
4029 self.child.paint(window, cx);
4030 window.with_global_id("target".into(), |global_id, window| {
4031 window.with_element_state::<InteractiveElementState, _>(
4032 global_id,
4033 |state, _window| {
4034 let state = state.unwrap();
4035 *self.captured_active_tooltip.borrow_mut() =
4036 state.active_tooltip.as_ref().map(Rc::downgrade);
4037 ((), state)
4038 },
4039 )
4040 });
4041 }
4042 }
4043
4044 struct TooltipOwner {
4045 captured_active_tooltip: CapturedActiveTooltip,
4046 show_delay_override: Option<Duration>,
4047 }
4048
4049 impl Render for TooltipOwner {
4050 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4051 TooltipCaptureElement {
4052 child: div()
4053 .size_full()
4054 .child(
4055 div()
4056 .id("target")
4057 .w(px(50.))
4058 .h(px(50.))
4059 .tooltip(|_, cx| cx.new(|_| TestTooltipView).into())
4060 .when_some(self.show_delay_override, |this, delay| {
4061 this.tooltip_show_delay(delay)
4062 }),
4063 )
4064 .into_any_element(),
4065 captured_active_tooltip: self.captured_active_tooltip.clone(),
4066 }
4067 }
4068 }
4069
4070 #[test]
4071 fn scroll_handle_aligns_wide_children_to_left_edge() {
4072 let handle = ScrollHandle::new();
4073 {
4074 let mut state = handle.0.borrow_mut();
4075 state.bounds = Bounds::new(point(px(0.), px(0.)), size(px(80.), px(20.)));
4076 state.child_bounds = vec![Bounds::new(point(px(25.), px(0.)), size(px(200.), px(20.)))];
4077 state.overflow.x = Overflow::Scroll;
4078 state.active_item = Some(ScrollActiveItem {
4079 index: 0,
4080 strategy: ScrollStrategy::default(),
4081 });
4082 }
4083
4084 handle.scroll_to_active_item();
4085
4086 assert_eq!(handle.offset().x, px(-25.));
4087 }
4088
4089 #[test]
4090 fn scroll_handle_aligns_tall_children_to_top_edge() {
4091 let handle = ScrollHandle::new();
4092 {
4093 let mut state = handle.0.borrow_mut();
4094 state.bounds = Bounds::new(point(px(0.), px(0.)), size(px(20.), px(80.)));
4095 state.child_bounds = vec![Bounds::new(point(px(0.), px(25.)), size(px(20.), px(200.)))];
4096 state.overflow.y = Overflow::Scroll;
4097 state.active_item = Some(ScrollActiveItem {
4098 index: 0,
4099 strategy: ScrollStrategy::default(),
4100 });
4101 }
4102
4103 handle.scroll_to_active_item();
4104
4105 assert_eq!(handle.offset().y, px(-25.));
4106 }
4107
4108 fn setup_tooltip_owner_test(
4109 show_delay_override: Option<Duration>,
4110 ) -> (
4111 TestAppContext,
4112 crate::AnyWindowHandle,
4113 CapturedActiveTooltip,
4114 ) {
4115 let mut test_app = TestAppContext::single();
4116 let captured_active_tooltip: CapturedActiveTooltip = Rc::new(RefCell::new(None));
4117 let window = test_app.add_window({
4118 let captured_active_tooltip = captured_active_tooltip.clone();
4119 move |_, _| TooltipOwner {
4120 captured_active_tooltip,
4121 show_delay_override,
4122 }
4123 });
4124 let any_window = window.into();
4125
4126 test_app
4127 .update_window(any_window, |_, window, cx| {
4128 window.draw(cx).clear();
4129 })
4130 .unwrap();
4131
4132 test_app
4133 .update_window(any_window, |_, window, cx| {
4134 window.dispatch_event(
4135 MouseMoveEvent {
4136 position: point(px(10.), px(10.)),
4137 modifiers: Default::default(),
4138 pressed_button: None,
4139 }
4140 .to_platform_input(),
4141 cx,
4142 );
4143 })
4144 .unwrap();
4145
4146 test_app
4147 .update_window(any_window, |_, window, cx| {
4148 window.draw(cx).clear();
4149 })
4150 .unwrap();
4151
4152 (test_app, any_window, captured_active_tooltip)
4153 }
4154
4155 #[test]
4156 fn tooltip_waiting_for_show_is_released_when_its_owner_disappears() {
4157 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
4158
4159 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4160 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4161 assert!(matches!(
4162 active_tooltip.borrow().as_ref(),
4163 Some(ActiveTooltip::WaitingForShow { .. })
4164 ));
4165
4166 test_app
4167 .update_window(any_window, |_, window, _| {
4168 window.remove_window();
4169 })
4170 .unwrap();
4171 test_app.run_until_parked();
4172 drop(active_tooltip);
4173
4174 assert!(weak_active_tooltip.upgrade().is_none());
4175 }
4176
4177 #[test]
4178 fn tooltip_respects_custom_show_delay() {
4179 let extra_delay = Duration::from_secs(1);
4180 let show_delay_override = DEFAULT_TOOLTIP_SHOW_DELAY + extra_delay;
4181 let (mut test_app, _any_window, captured_active_tooltip) =
4182 setup_tooltip_owner_test(Some(show_delay_override));
4183
4184 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4185 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4186
4187 test_app
4188 .dispatcher
4189 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
4190 test_app.run_until_parked();
4191
4192 assert!(matches!(
4193 active_tooltip.borrow().as_ref(),
4194 Some(ActiveTooltip::WaitingForShow { .. })
4195 ));
4196
4197 test_app.dispatcher.advance_clock(extra_delay);
4198 test_app.run_until_parked();
4199
4200 assert!(matches!(
4201 active_tooltip.borrow().as_ref(),
4202 Some(ActiveTooltip::Visible { .. })
4203 ));
4204 }
4205
4206 #[test]
4207 fn tooltip_is_released_when_its_owner_disappears() {
4208 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
4209
4210 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4211 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4212
4213 test_app
4214 .dispatcher
4215 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
4216 test_app.run_until_parked();
4217
4218 assert!(matches!(
4219 active_tooltip.borrow().as_ref(),
4220 Some(ActiveTooltip::Visible { .. })
4221 ));
4222
4223 test_app
4224 .update_window(any_window, |_, window, _| {
4225 window.remove_window();
4226 })
4227 .unwrap();
4228 test_app.run_until_parked();
4229 drop(active_tooltip);
4230
4231 assert!(weak_active_tooltip.upgrade().is_none());
4232 }
4233
4234 #[test]
4235 fn tooltip_hides_after_mouse_leaves_origin() {
4236 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
4237
4238 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4239 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4240
4241 test_app
4242 .dispatcher
4243 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
4244 test_app.run_until_parked();
4245
4246 assert!(matches!(
4247 active_tooltip.borrow().as_ref(),
4248 Some(ActiveTooltip::Visible { .. })
4249 ));
4250
4251 test_app
4252 .update_window(any_window, |_, window, cx| {
4253 window.dispatch_event(
4254 MouseMoveEvent {
4255 position: point(px(75.), px(75.)),
4256 modifiers: Default::default(),
4257 pressed_button: None,
4258 }
4259 .to_platform_input(),
4260 cx,
4261 );
4262 })
4263 .unwrap();
4264
4265 assert!(active_tooltip.borrow().is_none());
4266 }
4267}