1use crate::{
19 AbsoluteLength, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent,
20 DispatchPhase, Element, ElementId, Entity, EntityId, FileDropEvent, FocusHandle, Global,
21 GlobalElementId, Hitbox, HitboxBehavior, HitboxId, InspectorElementId, IntoElement, IsZero,
22 KeyContext, KeyDownEvent, KeyUpEvent, KeyboardButton, KeyboardClickEvent, LayoutId,
23 ModifiersChangedEvent, MouseButton, MouseClickEvent, MouseDownEvent, MouseExitEvent,
24 MouseMoveEvent, MouseUpEvent, OngoingScroll, Overflow, ParentElement, PinchEvent, Pixels,
25 Point, Render, ScrollEvent, SharedString, Size, Style, StyleRefinement, Styled, Task,
26 TooltipId, Visibility, Window, WindowControlArea, point, px, size,
27};
28use collections::HashMap;
29use refineable::Refineable;
30use smallvec::SmallVec;
31use stacksafe::{StackSafe, stacksafe};
32use std::{
33 any::{Any, TypeId},
34 cell::RefCell,
35 cmp::Ordering,
36 fmt::Debug,
37 marker::PhantomData,
38 mem,
39 rc::Rc,
40 sync::Arc,
41 time::Duration,
42};
43use util::ResultExt;
44
45use super::ImageCacheProvider;
46
47const DRAG_THRESHOLD: f64 = 2.;
48const DEFAULT_TOOLTIP_SHOW_DELAY: Duration = Duration::from_millis(500);
49const HOVERABLE_TOOLTIP_HIDE_DELAY: Duration = Duration::from_millis(500);
50
51pub struct GroupStyle {
53 pub group: SharedString,
55
56 pub style: Box<StyleRefinement>,
59}
60
61pub struct DragMoveEvent<T> {
63 pub event: MouseMoveEvent,
65
66 pub bounds: Bounds<Pixels>,
68 drag: PhantomData<T>,
69 dragged_item: Arc<dyn Any>,
70}
71
72impl<T: 'static> DragMoveEvent<T> {
73 pub fn drag<'b>(&self, cx: &'b App) -> &'b T {
75 cx.active_drag
76 .as_ref()
77 .and_then(|drag| drag.value.downcast_ref::<T>())
78 .expect("DragMoveEvent is only valid when the stored active drag is of the same type.")
79 }
80
81 pub fn dragged_item(&self) -> &dyn Any {
83 self.dragged_item.as_ref()
84 }
85}
86
87#[derive(Clone, Debug)]
89pub struct ResizeEvent {
90 pub size: Size<Pixels>,
92 pub bounds: Bounds<Pixels>,
94}
95
96impl Interactivity {
97 #[cfg(any(feature = "inspector", debug_assertions))]
99 #[track_caller]
100 pub fn new() -> Interactivity {
101 Interactivity {
102 source_location: Some(core::panic::Location::caller()),
103 ..Default::default()
104 }
105 }
106
107 #[cfg(not(any(feature = "inspector", debug_assertions)))]
109 pub fn new() -> Interactivity {
110 Interactivity::default()
111 }
112
113 pub fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
115 #[cfg(any(feature = "inspector", debug_assertions))]
116 {
117 self.source_location
118 }
119
120 #[cfg(not(any(feature = "inspector", debug_assertions)))]
121 {
122 None
123 }
124 }
125
126 pub fn on_mouse_down(
131 &mut self,
132 button: MouseButton,
133 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
134 ) {
135 self.mouse_down_listeners
136 .push(Box::new(move |event, phase, hitbox, window, cx| {
137 if phase == DispatchPhase::Bubble
138 && event.button == button
139 && hitbox.is_hovered(window)
140 {
141 (listener)(event, window, cx)
142 }
143 }));
144 }
145
146 pub fn capture_any_mouse_down(
151 &mut self,
152 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
153 ) {
154 self.mouse_down_listeners
155 .push(Box::new(move |event, phase, hitbox, window, cx| {
156 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
157 (listener)(event, window, cx)
158 }
159 }));
160 }
161
162 pub fn on_any_mouse_down(
167 &mut self,
168 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
169 ) {
170 self.mouse_down_listeners
171 .push(Box::new(move |event, phase, hitbox, window, cx| {
172 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
173 (listener)(event, window, cx)
174 }
175 }));
176 }
177
178 pub fn on_mouse_up(
183 &mut self,
184 button: MouseButton,
185 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
186 ) {
187 self.mouse_up_listeners
188 .push(Box::new(move |event, phase, hitbox, window, cx| {
189 if phase == DispatchPhase::Bubble
190 && event.button == button
191 && hitbox.is_hovered(window)
192 {
193 (listener)(event, window, cx)
194 }
195 }));
196 }
197
198 pub fn capture_any_mouse_up(
203 &mut self,
204 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
205 ) {
206 self.mouse_up_listeners
207 .push(Box::new(move |event, phase, hitbox, window, cx| {
208 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
209 (listener)(event, window, cx)
210 }
211 }));
212 }
213
214 pub fn on_any_mouse_up(
219 &mut self,
220 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
221 ) {
222 self.mouse_up_listeners
223 .push(Box::new(move |event, phase, hitbox, window, cx| {
224 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
225 (listener)(event, window, cx)
226 }
227 }));
228 }
229
230 pub fn on_mouse_down_out(
236 &mut self,
237 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
238 ) {
239 self.mouse_down_listeners
240 .push(Box::new(move |event, phase, hitbox, window, cx| {
241 if phase == DispatchPhase::Capture
242 && !window.has_active_prompt()
243 && !hitbox.contains(&window.mouse_position())
244 {
245 (listener)(event, window, cx)
246 }
247 }));
248 }
249
250 pub fn on_mouse_up_out(
256 &mut self,
257 button: MouseButton,
258 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
259 ) {
260 self.mouse_up_listeners
261 .push(Box::new(move |event, phase, hitbox, window, cx| {
262 if phase == DispatchPhase::Capture
263 && event.button == button
264 && !hitbox.is_hovered(window)
265 {
266 (listener)(event, window, cx);
267 }
268 }));
269 }
270
271 pub fn on_mouse_move(
276 &mut self,
277 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
278 ) {
279 self.mouse_move_listeners
280 .push(Box::new(move |event, phase, hitbox, window, cx| {
281 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
282 (listener)(event, window, cx);
283 }
284 }));
285 }
286
287 pub fn on_mouse_exit(
292 &mut self,
293 listener: impl Fn(&MouseExitEvent, &mut Window, &mut App) + 'static,
294 ) {
295 self.mouse_exit_listeners
296 .push(Box::new(move |event, phase, hitbox, window, cx| {
297 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
298 (listener)(event, window, cx);
299 }
300 }));
301 }
302
303 pub fn on_file_drop_exit(
311 &mut self,
312 listener: impl Fn(&FileDropEvent, &mut Window, &mut App) + 'static,
313 ) {
314 self.file_drop_exit_listeners
315 .push(Box::new(move |event, phase, hitbox, window, cx| {
316 if phase == DispatchPhase::Bubble
317 && matches!(event, FileDropEvent::Exited)
318 && hitbox.id.is_hovered_ignoring_last_input(window)
319 {
320 (listener)(event, window, cx);
321 }
322 }));
323 }
324
325 pub fn on_drag_move<T>(
333 &mut self,
334 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
335 ) where
336 T: 'static,
337 {
338 self.mouse_move_listeners
339 .push(Box::new(move |event, phase, hitbox, window, cx| {
340 if phase == DispatchPhase::Capture
341 && let Some(drag) = &cx.active_drag
342 && drag.value.as_ref().type_id() == TypeId::of::<T>()
343 {
344 (listener)(
345 &DragMoveEvent {
346 event: event.clone(),
347 bounds: hitbox.bounds,
348 drag: PhantomData,
349 dragged_item: Arc::clone(&drag.value),
350 },
351 window,
352 cx,
353 );
354 }
355 }));
356 }
357
358 pub fn on_scroll_wheel(
363 &mut self,
364 listener: impl Fn(&ScrollEvent, &mut Window, &mut App) + 'static,
365 ) {
366 self.scroll_wheel_listeners
367 .push(Box::new(move |event, phase, hitbox, window, cx| {
368 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
369 (listener)(event, window, cx);
370 }
371 }));
372 }
373
374 pub fn on_pinch(&mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) {
379 self.pinch_listeners
380 .push(Box::new(move |event, phase, hitbox, window, cx| {
381 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
382 (listener)(event, window, cx);
383 }
384 }));
385 }
386
387 pub fn capture_pinch(
392 &mut self,
393 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
394 ) {
395 self.pinch_listeners
396 .push(Box::new(move |event, phase, _hitbox, window, cx| {
397 if phase == DispatchPhase::Capture {
398 (listener)(event, window, cx);
399 } else {
400 cx.propagate();
401 }
402 }));
403 }
404
405 pub fn capture_action<A: Action>(
410 &mut self,
411 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
412 ) {
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::Capture {
418 (listener)(action, window, cx)
419 } else {
420 cx.propagate();
421 }
422 }),
423 ));
424 }
425
426 pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Window, &mut App) + 'static) {
431 self.action_listeners.push((
432 TypeId::of::<A>(),
433 Box::new(move |action, phase, window, cx| {
434 let action = action.downcast_ref().unwrap();
435 if phase == DispatchPhase::Bubble {
436 (listener)(action, window, cx)
437 }
438 }),
439 ));
440 }
441
442 pub fn on_boxed_action(
449 &mut self,
450 action: &dyn Action,
451 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
452 ) {
453 let action = action.boxed_clone();
454 self.action_listeners.push((
455 (*action).type_id(),
456 Box::new(move |_, phase, window, cx| {
457 if phase == DispatchPhase::Bubble {
458 (listener)(&*action, window, cx)
459 }
460 }),
461 ));
462 }
463
464 pub fn on_key_down(
469 &mut self,
470 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
471 ) {
472 self.key_down_listeners
473 .push(Box::new(move |event, phase, window, cx| {
474 if phase == DispatchPhase::Bubble {
475 (listener)(event, window, cx)
476 }
477 }));
478 }
479
480 pub fn capture_key_down(
485 &mut self,
486 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
487 ) {
488 self.key_down_listeners
489 .push(Box::new(move |event, phase, window, cx| {
490 if phase == DispatchPhase::Capture {
491 listener(event, window, cx)
492 }
493 }));
494 }
495
496 pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static) {
501 self.key_up_listeners
502 .push(Box::new(move |event, phase, window, cx| {
503 if phase == DispatchPhase::Bubble {
504 listener(event, window, cx)
505 }
506 }));
507 }
508
509 pub fn capture_key_up(
514 &mut self,
515 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
516 ) {
517 self.key_up_listeners
518 .push(Box::new(move |event, phase, window, cx| {
519 if phase == DispatchPhase::Capture {
520 listener(event, window, cx)
521 }
522 }));
523 }
524
525 pub fn on_modifiers_changed(
530 &mut self,
531 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
532 ) {
533 self.modifiers_changed_listeners
534 .push(Box::new(move |event, window, cx| {
535 listener(event, window, cx)
536 }));
537 }
538
539 pub fn on_drop<T: 'static>(&mut self, listener: impl Fn(&T, &mut Window, &mut App) + 'static) {
544 self.drop_listeners.push((
545 TypeId::of::<T>(),
546 Box::new(move |dragged_value, window, cx| {
547 listener(dragged_value.downcast_ref().unwrap(), window, cx);
548 }),
549 ));
550 }
551
552 pub fn can_drop(
555 &mut self,
556 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
557 ) {
558 self.can_drop_predicate = Some(Box::new(predicate));
559 }
560
561 pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
566 where
567 Self: Sized,
568 {
569 self.click_listeners.push(Rc::new(move |event, window, cx| {
570 listener(event, window, cx)
571 }));
572 }
573
574 pub fn on_drag<T, W>(
581 &mut self,
582 value: T,
583 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
584 ) where
585 Self: Sized,
586 T: 'static,
587 W: 'static + Render,
588 {
589 debug_assert!(
590 self.drag_listener.is_none(),
591 "calling on_drag more than once on the same element is not supported"
592 );
593 self.drag_listener = Some((
594 Arc::new(value),
595 Box::new(move |value, offset, window, cx| {
596 constructor(value.downcast_ref().unwrap(), offset, window, cx).into()
597 }),
598 ));
599 }
600
601 pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static)
612 where
613 Self: Sized,
614 {
615 debug_assert!(
616 self.hover_listener.is_none(),
617 "calling on_hover more than once on the same element is not supported"
618 );
619 self.hover_listener = Some(Box::new(listener));
620 }
621
622 pub fn hover_listener_mode(&mut self, mode: HoverListenerMode)
626 where
627 Self: Sized,
628 {
629 self.hover_listener_mode = mode;
630 }
631
632 pub fn on_resize(&mut self, listener: impl Fn(&ResizeEvent, &mut Window, &mut App) + 'static)
637 where
638 Self: Sized,
639 {
640 self.resize_listeners.push(Box::new(listener));
641 }
642
643 pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static)
646 where
647 Self: Sized,
648 {
649 debug_assert!(
650 self.tooltip_builder.is_none(),
651 "calling tooltip more than once on the same element is not supported"
652 );
653 self.tooltip_builder = Some(TooltipBuilder {
654 build: Rc::new(build_tooltip),
655 hoverable: false,
656 });
657 }
658
659 pub fn hoverable_tooltip(
663 &mut self,
664 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
665 ) where
666 Self: Sized,
667 {
668 debug_assert!(
669 self.tooltip_builder.is_none(),
670 "calling tooltip more than once on the same element is not supported"
671 );
672 self.tooltip_builder = Some(TooltipBuilder {
673 build: Rc::new(build_tooltip),
674 hoverable: true,
675 });
676 }
677
678 pub fn tooltip_show_delay(&mut self, delay: Duration) {
681 self.tooltip_show_delay = Some(delay);
682 }
683
684 pub fn occlude_mouse(&mut self) {
689 self.hitbox_behavior = HitboxBehavior::BlockMouse;
690 }
691
692 pub fn window_control_area(&mut self, area: WindowControlArea) {
695 self.window_control = Some(area);
696 }
697
698 pub fn block_mouse_except_scroll(&mut self) {
703 self.hitbox_behavior = HitboxBehavior::BlockMouseExceptScroll;
704 }
705}
706
707pub trait InteractiveElement: Sized {
710 fn interactivity(&mut self) -> &mut Interactivity;
712
713 fn group(mut self, group: impl Into<SharedString>) -> Self {
715 self.interactivity().group = Some(group.into());
716 self
717 }
718
719 fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
721 self.interactivity().element_id = Some(id.into());
722
723 Stateful { element: self }
724 }
725
726 fn track_focus(mut self, focus_handle: &FocusHandle) -> Self {
730 self.interactivity().focusable = true;
731 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
732 self
733 }
734
735 fn tab_stop(mut self, tab_stop: bool) -> Self {
742 self.interactivity().tab_stop = tab_stop;
743 self
744 }
745
746 fn tab_index(mut self, index: isize) -> Self {
751 self.interactivity().focusable = true;
752 self.interactivity().tab_index = Some(index);
753 self.interactivity().tab_stop = true;
754 self
755 }
756
757 fn tab_group(mut self) -> Self {
762 self.interactivity().tab_group = true;
763 if self.interactivity().tab_index.is_none() {
764 self.interactivity().tab_index = Some(0);
765 }
766 self
767 }
768
769 fn key_context<C, E>(mut self, key_context: C) -> Self
772 where
773 C: TryInto<KeyContext, Error = E>,
774 E: Debug,
775 {
776 if let Some(key_context) = key_context.try_into().log_err() {
777 self.interactivity().key_context = Some(key_context);
778 }
779 self
780 }
781
782 fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
784 debug_assert!(
785 self.interactivity().hover_style.is_none(),
786 "hover style already set"
787 );
788 self.interactivity().hover_style = Some(Box::new(f(StyleRefinement::default())));
789 self
790 }
791
792 fn group_hover(
794 mut self,
795 group_name: impl Into<SharedString>,
796 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
797 ) -> Self {
798 self.interactivity().group_hover_style = Some(GroupStyle {
799 group: group_name.into(),
800 style: Box::new(f(StyleRefinement::default())),
801 });
802 self
803 }
804
805 fn on_mouse_down(
810 mut self,
811 button: MouseButton,
812 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
813 ) -> Self {
814 self.interactivity().on_mouse_down(button, listener);
815 self
816 }
817
818 #[cfg(any(test, all(debug_assertions, feature = "test-support")))]
825 fn debug_selector(mut self, f: impl FnOnce() -> String) -> Self {
826 self.interactivity().debug_selector = Some(f());
827 self
828 }
829
830 #[cfg(not(any(test, all(debug_assertions, feature = "test-support"))))]
837 #[inline]
838 fn debug_selector(self, _: impl FnOnce() -> String) -> Self {
839 self
840 }
841
842 fn capture_any_mouse_down(
847 mut self,
848 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
849 ) -> Self {
850 self.interactivity().capture_any_mouse_down(listener);
851 self
852 }
853
854 fn on_any_mouse_down(
859 mut self,
860 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
861 ) -> Self {
862 self.interactivity().on_any_mouse_down(listener);
863 self
864 }
865
866 fn on_mouse_up(
871 mut self,
872 button: MouseButton,
873 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
874 ) -> Self {
875 self.interactivity().on_mouse_up(button, listener);
876 self
877 }
878
879 fn capture_any_mouse_up(
884 mut self,
885 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
886 ) -> Self {
887 self.interactivity().capture_any_mouse_up(listener);
888 self
889 }
890
891 fn on_mouse_down_out(
897 mut self,
898 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
899 ) -> Self {
900 self.interactivity().on_mouse_down_out(listener);
901 self
902 }
903
904 fn on_mouse_up_out(
910 mut self,
911 button: MouseButton,
912 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
913 ) -> Self {
914 self.interactivity().on_mouse_up_out(button, listener);
915 self
916 }
917
918 fn on_mouse_move(
923 mut self,
924 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
925 ) -> Self {
926 self.interactivity().on_mouse_move(listener);
927 self
928 }
929
930 fn on_mouse_exit(
935 mut self,
936 listener: impl Fn(&MouseExitEvent, &mut Window, &mut App) + 'static,
937 ) -> Self {
938 self.interactivity().on_mouse_exit(listener);
939 self
940 }
941
942 fn on_file_drop_exit(
950 mut self,
951 listener: impl Fn(&FileDropEvent, &mut Window, &mut App) + 'static,
952 ) -> Self {
953 self.interactivity().on_file_drop_exit(listener);
954 self
955 }
956
957 fn on_drag_move<T: 'static>(
965 mut self,
966 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
967 ) -> Self {
968 self.interactivity().on_drag_move(listener);
969 self
970 }
971
972 fn on_scroll_wheel(
977 mut self,
978 listener: impl Fn(&ScrollEvent, &mut Window, &mut App) + 'static,
979 ) -> Self {
980 self.interactivity().on_scroll_wheel(listener);
981 self
982 }
983
984 fn on_pinch(mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) -> Self {
989 self.interactivity().on_pinch(listener);
990 self
991 }
992
993 fn capture_pinch(
998 mut self,
999 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
1000 ) -> Self {
1001 self.interactivity().capture_pinch(listener);
1002 self
1003 }
1004
1005 fn capture_action<A: Action>(
1010 mut self,
1011 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
1012 ) -> Self {
1013 self.interactivity().capture_action(listener);
1014 self
1015 }
1016
1017 fn on_action<A: Action>(
1022 mut self,
1023 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
1024 ) -> Self {
1025 self.interactivity().on_action(listener);
1026 self
1027 }
1028
1029 fn on_boxed_action(
1036 mut self,
1037 action: &dyn Action,
1038 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
1039 ) -> Self {
1040 self.interactivity().on_boxed_action(action, listener);
1041 self
1042 }
1043
1044 fn on_key_down(
1049 mut self,
1050 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1051 ) -> Self {
1052 self.interactivity().on_key_down(listener);
1053 self
1054 }
1055
1056 fn capture_key_down(
1061 mut self,
1062 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1063 ) -> Self {
1064 self.interactivity().capture_key_down(listener);
1065 self
1066 }
1067
1068 fn on_key_up(
1073 mut self,
1074 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1075 ) -> Self {
1076 self.interactivity().on_key_up(listener);
1077 self
1078 }
1079
1080 fn capture_key_up(
1085 mut self,
1086 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1087 ) -> Self {
1088 self.interactivity().capture_key_up(listener);
1089 self
1090 }
1091
1092 fn on_modifiers_changed(
1097 mut self,
1098 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
1099 ) -> Self {
1100 self.interactivity().on_modifiers_changed(listener);
1101 self
1102 }
1103
1104 fn drag_over<S: 'static>(
1106 mut self,
1107 f: impl 'static + Fn(StyleRefinement, &S, &mut Window, &mut App) -> StyleRefinement,
1108 ) -> Self {
1109 self.interactivity().drag_over_styles.push((
1110 TypeId::of::<S>(),
1111 Box::new(move |currently_dragged: &dyn Any, window, cx| {
1112 f(
1113 StyleRefinement::default(),
1114 currently_dragged.downcast_ref::<S>().unwrap(),
1115 window,
1116 cx,
1117 )
1118 }),
1119 ));
1120 self
1121 }
1122
1123 fn group_drag_over<S: 'static>(
1125 mut self,
1126 group_name: impl Into<SharedString>,
1127 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1128 ) -> Self {
1129 self.interactivity().group_drag_over_styles.push((
1130 TypeId::of::<S>(),
1131 GroupStyle {
1132 group: group_name.into(),
1133 style: Box::new(f(StyleRefinement::default())),
1134 },
1135 ));
1136 self
1137 }
1138
1139 fn on_drop<T: 'static>(
1144 mut self,
1145 listener: impl Fn(&T, &mut Window, &mut App) + 'static,
1146 ) -> Self {
1147 self.interactivity().on_drop(listener);
1148 self
1149 }
1150
1151 fn can_drop(
1154 mut self,
1155 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
1156 ) -> Self {
1157 self.interactivity().can_drop(predicate);
1158 self
1159 }
1160
1161 fn occlude(mut self) -> Self {
1165 self.interactivity().occlude_mouse();
1166 self
1167 }
1168
1169 fn window_control_area(mut self, area: WindowControlArea) -> Self {
1172 self.interactivity().window_control_area(area);
1173 self
1174 }
1175
1176 fn block_mouse_except_scroll(mut self) -> Self {
1181 self.interactivity().block_mouse_except_scroll();
1182 self
1183 }
1184
1185 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1188 where
1189 Self: Sized,
1190 {
1191 self.interactivity().focus_style = Some(Box::new(f(StyleRefinement::default())));
1192 self
1193 }
1194
1195 fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1198 where
1199 Self: Sized,
1200 {
1201 self.interactivity().in_focus_style = Some(Box::new(f(StyleRefinement::default())));
1202 self
1203 }
1204}
1205
1206pub trait StatefulInteractiveElement: InteractiveElement {
1209 fn role(mut self, role: accesskit::Role) -> Self {
1211 debug_assert!(
1212 role != accesskit::Role::GenericContainer,
1213 "GenericContainer is filtered out of the a11y tree and has no effect"
1214 );
1215 self.interactivity().override_role = Some(role);
1216 self
1217 }
1218
1219 fn accessibility_id(mut self, id: impl Into<SharedString>) -> Self {
1224 self.interactivity().accessibility_id = Some(id.into());
1225 self
1226 }
1227
1228 fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
1230 self.interactivity().aria_label = Some(label.into());
1231 self
1232 }
1233
1234 fn aria_description(mut self, description: impl Into<SharedString>) -> Self {
1236 self.interactivity().aria_description = Some(description.into());
1237 self
1238 }
1239
1240 fn aria_value(mut self, value: impl Into<SharedString>) -> Self {
1242 self.interactivity().aria_value = Some(value.into());
1243 self
1244 }
1245
1246 fn aria_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
1248 self.interactivity().aria_placeholder = Some(placeholder.into());
1249 self
1250 }
1251
1252 fn aria_disabled(mut self, disabled: bool) -> Self {
1254 self.interactivity().aria_disabled = disabled;
1255 self
1256 }
1257
1258 fn aria_required(mut self, required: bool) -> Self {
1260 self.interactivity().aria_required = required;
1261 self
1262 }
1263
1264 fn aria_invalid(mut self, invalid: accesskit::Invalid) -> Self {
1266 self.interactivity().aria_invalid = Some(invalid);
1267 self
1268 }
1269
1270 fn aria_modal(mut self, modal: bool) -> Self {
1272 self.interactivity().aria_modal = modal;
1273 self
1274 }
1275
1276 fn aria_keyshortcuts(mut self, keyshortcuts: impl Into<SharedString>) -> Self {
1280 self.interactivity().aria_keyshortcuts = Some(keyshortcuts.into());
1281 self
1282 }
1283
1284 fn aria_selected(mut self, selected: bool) -> Self {
1286 self.interactivity().aria_selected = Some(selected);
1287 self
1288 }
1289
1290 fn aria_expanded(mut self, expanded: bool) -> Self {
1292 self.interactivity().aria_expanded = Some(expanded);
1293 self
1294 }
1295
1296 fn aria_toggled(mut self, toggled: accesskit::Toggled) -> Self {
1298 self.interactivity().aria_toggled = Some(toggled);
1299 self
1300 }
1301
1302 fn aria_numeric_value(mut self, value: f64) -> Self {
1304 self.interactivity().aria_numeric_value = Some(value);
1305 self
1306 }
1307
1308 fn aria_min_numeric_value(mut self, value: f64) -> Self {
1310 self.interactivity().aria_min_numeric_value = Some(value);
1311 self
1312 }
1313
1314 fn aria_max_numeric_value(mut self, value: f64) -> Self {
1316 self.interactivity().aria_max_numeric_value = Some(value);
1317 self
1318 }
1319
1320 fn aria_orientation(mut self, orientation: accesskit::Orientation) -> Self {
1322 self.interactivity().aria_orientation = Some(orientation);
1323 self
1324 }
1325
1326 fn aria_level(mut self, level: usize) -> Self {
1328 self.interactivity().aria_level = Some(level);
1329 self
1330 }
1331
1332 fn aria_position_in_set(mut self, position: usize) -> Self {
1334 self.interactivity().aria_position_in_set = Some(position);
1335 self
1336 }
1337
1338 fn aria_size_of_set(mut self, size: usize) -> Self {
1340 self.interactivity().aria_size_of_set = Some(size);
1341 self
1342 }
1343
1344 fn aria_row_index(mut self, index: usize) -> Self {
1346 self.interactivity().aria_row_index = Some(index);
1347 self
1348 }
1349
1350 fn aria_column_index(mut self, index: usize) -> Self {
1352 self.interactivity().aria_column_index = Some(index);
1353 self
1354 }
1355
1356 fn aria_row_count(mut self, count: usize) -> Self {
1358 self.interactivity().aria_row_count = Some(count);
1359 self
1360 }
1361
1362 fn aria_column_count(mut self, count: usize) -> Self {
1364 self.interactivity().aria_column_count = Some(count);
1365 self
1366 }
1367
1368 fn on_a11y_action(
1370 mut self,
1371 action: accesskit::Action,
1372 listener: impl FnMut(Option<&accesskit::ActionData>, &mut Window, &mut App) + 'static,
1373 ) -> Self {
1374 self.interactivity()
1375 .a11y_action_listeners
1376 .push((action, Box::new(listener)));
1377 self
1378 }
1379
1380 fn focusable(mut self) -> Self {
1382 self.interactivity().focusable = true;
1383 self
1384 }
1385
1386 fn overflow_scroll(mut self) -> Self {
1388 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1389 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1390 self
1391 }
1392
1393 fn overflow_x_scroll(mut self) -> Self {
1395 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1396 self
1397 }
1398
1399 fn overflow_y_scroll(mut self) -> Self {
1401 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1402 self
1403 }
1404
1405 fn restrict_scroll_to_axis(mut self) -> Self {
1409 self.interactivity().base_style.restrict_scroll_to_axis = Some(true);
1410 self
1411 }
1412
1413 fn scrollbar_width(mut self, width: impl Into<AbsoluteLength>) -> Self {
1418 self.interactivity().base_style.scrollbar_width = Some(width.into());
1419 self
1420 }
1421
1422 fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
1424 self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone());
1425 self
1426 }
1427
1428 fn anchor_scroll(mut self, scroll_anchor: Option<ScrollAnchor>) -> Self {
1430 self.interactivity().scroll_anchor = scroll_anchor;
1431 self
1432 }
1433
1434 fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1436 where
1437 Self: Sized,
1438 {
1439 self.interactivity().active_style = Some(Box::new(f(StyleRefinement::default())));
1440 self
1441 }
1442
1443 fn group_active(
1445 mut self,
1446 group_name: impl Into<SharedString>,
1447 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1448 ) -> Self
1449 where
1450 Self: Sized,
1451 {
1452 self.interactivity().group_active_style = Some(GroupStyle {
1453 group: group_name.into(),
1454 style: Box::new(f(StyleRefinement::default())),
1455 });
1456 self
1457 }
1458
1459 fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self
1464 where
1465 Self: Sized,
1466 {
1467 self.interactivity().on_click(listener);
1468 self
1469 }
1470
1471 fn on_drag<T, W>(
1479 mut self,
1480 value: T,
1481 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
1482 ) -> Self
1483 where
1484 Self: Sized,
1485 T: 'static,
1486 W: 'static + Render,
1487 {
1488 self.interactivity().on_drag(value, constructor);
1489 self
1490 }
1491
1492 fn on_hover(mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self
1503 where
1504 Self: Sized,
1505 {
1506 self.interactivity().on_hover(listener);
1507 self
1508 }
1509
1510 fn hover_listener_mode(mut self, mode: HoverListenerMode) -> Self
1514 where
1515 Self: Sized,
1516 {
1517 self.interactivity().hover_listener_mode(mode);
1518 self
1519 }
1520
1521 fn tooltip(mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self
1524 where
1525 Self: Sized,
1526 {
1527 self.interactivity().tooltip(build_tooltip);
1528 self
1529 }
1530
1531 fn hoverable_tooltip(
1535 mut self,
1536 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
1537 ) -> Self
1538 where
1539 Self: Sized,
1540 {
1541 self.interactivity().hoverable_tooltip(build_tooltip);
1542 self
1543 }
1544
1545 fn tooltip_show_delay(mut self, delay: Duration) -> Self
1548 where
1549 Self: Sized,
1550 {
1551 self.interactivity().tooltip_show_delay(delay);
1552 self
1553 }
1554
1555 fn on_resize(mut self, listener: impl Fn(&ResizeEvent, &mut Window, &mut App) + 'static) -> Self
1560 where
1561 Self: Sized,
1562 {
1563 self.interactivity().on_resize(listener);
1564 self
1565 }
1566}
1567
1568pub(crate) type MouseDownListener =
1569 Box<dyn Fn(&MouseDownEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1570pub(crate) type MouseUpListener =
1571 Box<dyn Fn(&MouseUpEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1572
1573pub(crate) type MouseMoveListener =
1574 Box<dyn Fn(&MouseMoveEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1575pub(crate) type MouseExitListener =
1576 Box<dyn Fn(&MouseExitEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1577
1578pub(crate) type FileDropExitListener =
1579 Box<dyn Fn(&FileDropEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1580
1581pub(crate) type ScrollWheelListener =
1582 Box<dyn Fn(&ScrollEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1583
1584pub(crate) type PinchListener =
1585 Box<dyn Fn(&PinchEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1586
1587pub(crate) type ClickListener = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>;
1588
1589#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1592pub enum HoverListenerMode {
1593 #[default]
1596 InputModalityAware,
1597 InputModalityIndependent,
1601}
1602
1603impl HoverListenerMode {
1604 fn is_hovered(self, hitbox: &Hitbox, window: &Window) -> bool {
1605 match self {
1606 Self::InputModalityAware => hitbox.is_hovered(window),
1607 Self::InputModalityIndependent => hitbox.id.is_hovered_ignoring_last_input(window),
1608 }
1609 }
1610}
1611
1612pub(crate) type DragListener =
1613 Box<dyn Fn(&dyn Any, Point<Pixels>, &mut Window, &mut App) -> AnyView + 'static>;
1614
1615type DropListener = Box<dyn Fn(&dyn Any, &mut Window, &mut App) + 'static>;
1616
1617type CanDropPredicate = Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static>;
1618
1619pub(crate) struct TooltipBuilder {
1620 build: Rc<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>,
1621 hoverable: bool,
1622}
1623
1624pub(crate) type KeyDownListener =
1625 Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1626
1627pub(crate) type KeyUpListener =
1628 Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1629
1630pub(crate) type ModifiersChangedListener =
1631 Box<dyn Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static>;
1632
1633pub(crate) type ActionListener =
1634 Box<dyn Fn(&dyn Any, DispatchPhase, &mut Window, &mut App) + 'static>;
1635
1636pub(crate) type ResizeListener = Box<dyn Fn(&ResizeEvent, &mut Window, &mut App) + 'static>;
1637
1638#[track_caller]
1640pub fn div() -> Div {
1641 Div {
1642 interactivity: Interactivity::new(),
1643 children: SmallVec::default(),
1644 prepaint_listener: None,
1645 image_cache: None,
1646 }
1647}
1648
1649pub struct Div {
1651 interactivity: Interactivity,
1652 children: SmallVec<[StackSafe<AnyElement>; 2]>,
1653 prepaint_listener: Option<Box<dyn Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static>>,
1654 image_cache: Option<Box<dyn ImageCacheProvider>>,
1655}
1656
1657impl Div {
1658 pub fn on_children_prepainted(
1661 mut self,
1662 listener: impl Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static,
1663 ) -> Self {
1664 self.prepaint_listener = Some(Box::new(listener));
1665 self
1666 }
1667
1668 pub fn image_cache(mut self, cache: impl ImageCacheProvider) -> Self {
1670 self.image_cache = Some(Box::new(cache));
1671 self
1672 }
1673}
1674
1675pub struct DivFrameState {
1682 child_layout_ids: SmallVec<[LayoutId; 2]>,
1683}
1684
1685#[derive(Clone)]
1687pub struct DivInspectorState {
1688 #[cfg(any(feature = "inspector", debug_assertions))]
1692 pub base_style: Box<StyleRefinement>,
1693 pub bounds: Bounds<Pixels>,
1695 pub content_size: Size<Pixels>,
1697}
1698
1699impl Styled for Div {
1700 fn style(&mut self) -> &mut StyleRefinement {
1701 &mut self.interactivity.base_style
1702 }
1703}
1704
1705impl InteractiveElement for Div {
1706 fn interactivity(&mut self) -> &mut Interactivity {
1707 &mut self.interactivity
1708 }
1709}
1710
1711impl ParentElement for Div {
1712 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1713 self.children
1714 .extend(elements.into_iter().map(StackSafe::new))
1715 }
1716}
1717
1718impl Element for Div {
1719 type RequestLayoutState = DivFrameState;
1720 type PrepaintState = Option<Hitbox>;
1721
1722 fn id(&self) -> Option<ElementId> {
1723 self.interactivity.element_id.clone()
1724 }
1725
1726 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
1727 self.interactivity.source_location()
1728 }
1729
1730 fn a11y_role(&self) -> Option<accesskit::Role> {
1731 self.interactivity
1732 .override_role
1733 .filter(|role| *role != accesskit::Role::GenericContainer)
1734 }
1735
1736 fn write_a11y_info(&self, node: &mut accesskit::Node) {
1737 self.interactivity.write_a11y_info(node);
1738 }
1739
1740 #[stacksafe]
1741 fn request_layout(
1742 &mut self,
1743 global_id: Option<&GlobalElementId>,
1744 inspector_id: Option<&InspectorElementId>,
1745 window: &mut Window,
1746 cx: &mut App,
1747 ) -> (LayoutId, Self::RequestLayoutState) {
1748 let mut child_layout_ids = SmallVec::new();
1749 let image_cache = self
1750 .image_cache
1751 .as_mut()
1752 .map(|provider| provider.provide(window, cx));
1753
1754 let layout_id = window.with_image_cache(image_cache, |window| {
1755 self.interactivity.request_layout(
1756 global_id,
1757 inspector_id,
1758 window,
1759 cx,
1760 |style, window, cx| {
1761 window.with_text_style(style.text_style().cloned(), |window| {
1762 child_layout_ids = self
1763 .children
1764 .iter_mut()
1765 .map(|child| child.request_layout(window, cx))
1766 .collect::<SmallVec<_>>();
1767 window.request_layout(style, child_layout_ids.iter().copied(), cx)
1768 })
1769 },
1770 )
1771 });
1772
1773 (layout_id, DivFrameState { child_layout_ids })
1774 }
1775
1776 #[stacksafe]
1777 fn prepaint(
1778 &mut self,
1779 global_id: Option<&GlobalElementId>,
1780 inspector_id: Option<&InspectorElementId>,
1781 bounds: Bounds<Pixels>,
1782 request_layout: &mut Self::RequestLayoutState,
1783 window: &mut Window,
1784 cx: &mut App,
1785 ) -> Option<Hitbox> {
1786 let has_prepaint_listener = self.prepaint_listener.is_some();
1787 let mut children_bounds = Vec::with_capacity(if has_prepaint_listener {
1788 request_layout.child_layout_ids.len()
1789 } else {
1790 0
1791 });
1792
1793 let mut child_min = point(Pixels::MAX, Pixels::MAX);
1794 let mut child_max = Point::default();
1795 if let Some(handle) = self.interactivity.scroll_anchor.as_ref() {
1796 *handle.last_origin.borrow_mut() = bounds.origin - window.element_offset();
1797 }
1798 let content_size = if request_layout.child_layout_ids.is_empty() {
1799 bounds.size
1800 } else if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
1801 let mut state = scroll_handle.0.borrow_mut();
1802 state.child_bounds = Vec::with_capacity(request_layout.child_layout_ids.len());
1803 for child_layout_id in &request_layout.child_layout_ids {
1804 let child_bounds = window.layout_bounds(*child_layout_id);
1805 child_min = child_min.min(&child_bounds.origin);
1806 child_max = child_max.max(&child_bounds.bottom_right());
1807 state.child_bounds.push(child_bounds);
1808 }
1809 (child_max - child_min).into()
1810 } else {
1811 for child_layout_id in &request_layout.child_layout_ids {
1812 let child_bounds = window.layout_bounds(*child_layout_id);
1813 child_min = child_min.min(&child_bounds.origin);
1814 child_max = child_max.max(&child_bounds.bottom_right());
1815
1816 if has_prepaint_listener {
1817 children_bounds.push(child_bounds);
1818 }
1819 }
1820 (child_max - child_min).into()
1821 };
1822
1823 if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
1824 scroll_handle.scroll_to_active_item();
1825 }
1826
1827 self.interactivity.prepaint(
1828 global_id,
1829 inspector_id,
1830 bounds,
1831 content_size,
1832 window,
1833 cx,
1834 |_style, scroll_offset, hitbox, window, cx| {
1835 window.with_element_offset(scroll_offset, |window| {
1836 for child in &mut self.children {
1837 child.prepaint(window, cx);
1838 }
1839 });
1840
1841 if let Some(listener) = self.prepaint_listener.as_ref() {
1842 listener(children_bounds, window, cx);
1843 }
1844
1845 hitbox
1846 },
1847 )
1848 }
1849
1850 #[stacksafe]
1851 fn paint(
1852 &mut self,
1853 global_id: Option<&GlobalElementId>,
1854 inspector_id: Option<&InspectorElementId>,
1855 bounds: Bounds<Pixels>,
1856 _request_layout: &mut Self::RequestLayoutState,
1857 hitbox: &mut Option<Hitbox>,
1858 window: &mut Window,
1859 cx: &mut App,
1860 ) {
1861 let image_cache = self
1862 .image_cache
1863 .as_mut()
1864 .map(|provider| provider.provide(window, cx));
1865
1866 window.with_image_cache(image_cache, |window| {
1867 self.interactivity.paint(
1868 global_id,
1869 inspector_id,
1870 bounds,
1871 hitbox.as_ref(),
1872 window,
1873 cx,
1874 |_style, window, cx| {
1875 for child in &mut self.children {
1876 child.paint(window, cx);
1877 }
1878 },
1879 )
1880 });
1881 }
1882}
1883
1884impl IntoElement for Div {
1885 type Element = Self;
1886
1887 fn into_element(self) -> Self::Element {
1888 self
1889 }
1890}
1891
1892#[derive(Default)]
1895pub struct Interactivity {
1896 pub element_id: Option<ElementId>,
1898 pub active: Option<bool>,
1900 pub hovered: Option<bool>,
1903 pub(crate) tooltip_id: Option<TooltipId>,
1904 pub(crate) content_size: Size<Pixels>,
1905 pub(crate) key_context: Option<KeyContext>,
1906 pub(crate) focusable: bool,
1907 pub(crate) tracked_focus_handle: Option<FocusHandle>,
1908 pub(crate) tracked_scroll_handle: Option<ScrollHandle>,
1909 pub(crate) scroll_anchor: Option<ScrollAnchor>,
1910 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
1911 pub(crate) ongoing_scroll: Option<Rc<RefCell<OngoingScroll>>>,
1912 pub(crate) group: Option<SharedString>,
1913 pub base_style: Box<StyleRefinement>,
1916 pub(crate) focus_style: Option<Box<StyleRefinement>>,
1917 pub(crate) in_focus_style: Option<Box<StyleRefinement>>,
1918 pub(crate) hover_style: Option<Box<StyleRefinement>>,
1919 pub(crate) group_hover_style: Option<GroupStyle>,
1920 pub(crate) active_style: Option<Box<StyleRefinement>>,
1921 pub(crate) group_active_style: Option<GroupStyle>,
1922 pub(crate) drag_over_styles: Vec<(
1923 TypeId,
1924 Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> StyleRefinement>,
1925 )>,
1926 pub(crate) group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
1927 pub(crate) mouse_down_listeners: Vec<MouseDownListener>,
1928 pub(crate) mouse_up_listeners: Vec<MouseUpListener>,
1929 pub(crate) mouse_move_listeners: Vec<MouseMoveListener>,
1930 pub(crate) mouse_exit_listeners: Vec<MouseExitListener>,
1931 pub(crate) file_drop_exit_listeners: Vec<FileDropExitListener>,
1932 pub(crate) scroll_wheel_listeners: Vec<ScrollWheelListener>,
1933 pub(crate) pinch_listeners: Vec<PinchListener>,
1934 pub(crate) key_down_listeners: Vec<KeyDownListener>,
1935 pub(crate) key_up_listeners: Vec<KeyUpListener>,
1936 pub(crate) modifiers_changed_listeners: Vec<ModifiersChangedListener>,
1937 pub(crate) action_listeners: Vec<(TypeId, ActionListener)>,
1938 pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
1939 pub(crate) can_drop_predicate: Option<CanDropPredicate>,
1940 pub(crate) click_listeners: Vec<ClickListener>,
1941 pub(crate) drag_listener: Option<(Arc<dyn Any>, DragListener)>,
1942 pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut Window, &mut App)>>,
1943 pub(crate) hover_listener_mode: HoverListenerMode,
1944 pub(crate) resize_listeners: Vec<ResizeListener>,
1945 pub(crate) tooltip_builder: Option<TooltipBuilder>,
1946 pub(crate) tooltip_show_delay: Option<Duration>,
1947 pub(crate) window_control: Option<WindowControlArea>,
1948 pub(crate) hitbox_behavior: HitboxBehavior,
1949 pub(crate) tab_index: Option<isize>,
1950 pub(crate) tab_group: bool,
1951 pub(crate) tab_stop: bool,
1952
1953 pub(crate) a11y_action_listeners:
1954 Vec<(accesskit::Action, crate::window::a11y::A11yActionListener)>,
1955 pub(crate) override_role: Option<accesskit::Role>,
1956 pub(crate) accessibility_id: Option<SharedString>,
1957 pub(crate) aria_label: Option<SharedString>,
1958 pub(crate) aria_description: Option<SharedString>,
1959 pub(crate) aria_value: Option<SharedString>,
1960 pub(crate) aria_placeholder: Option<SharedString>,
1961 pub(crate) aria_disabled: bool,
1962 pub(crate) aria_required: bool,
1963 pub(crate) aria_invalid: Option<accesskit::Invalid>,
1964 pub(crate) aria_modal: bool,
1965 pub(crate) aria_keyshortcuts: Option<SharedString>,
1966 pub(crate) aria_selected: Option<bool>,
1967 pub(crate) aria_expanded: Option<bool>,
1968 pub(crate) aria_toggled: Option<accesskit::Toggled>,
1969 pub(crate) aria_numeric_value: Option<f64>,
1970 pub(crate) aria_min_numeric_value: Option<f64>,
1971 pub(crate) aria_max_numeric_value: Option<f64>,
1972 pub(crate) aria_orientation: Option<accesskit::Orientation>,
1973 pub(crate) aria_level: Option<usize>,
1974 pub(crate) aria_position_in_set: Option<usize>,
1975 pub(crate) aria_size_of_set: Option<usize>,
1976 pub(crate) aria_row_index: Option<usize>,
1977 pub(crate) aria_column_index: Option<usize>,
1978 pub(crate) aria_row_count: Option<usize>,
1979 pub(crate) aria_column_count: Option<usize>,
1980
1981 #[cfg(any(feature = "inspector", debug_assertions))]
1982 pub(crate) source_location: Option<&'static core::panic::Location<'static>>,
1983
1984 #[cfg(any(test, all(debug_assertions, feature = "test-support")))]
1985 pub(crate) debug_selector: Option<String>,
1986}
1987
1988impl Interactivity {
1989 pub fn request_layout(
1991 &mut self,
1992 global_id: Option<&GlobalElementId>,
1993 _inspector_id: Option<&InspectorElementId>,
1994 window: &mut Window,
1995 cx: &mut App,
1996 f: impl FnOnce(Style, &mut Window, &mut App) -> LayoutId,
1997 ) -> LayoutId {
1998 #[cfg(any(feature = "inspector", debug_assertions))]
1999 window.with_inspector_state(
2000 _inspector_id,
2001 cx,
2002 |inspector_state: &mut Option<DivInspectorState>, _window| {
2003 if let Some(inspector_state) = inspector_state {
2004 self.base_style = inspector_state.base_style.clone();
2005 } else {
2006 *inspector_state = Some(DivInspectorState {
2007 base_style: self.base_style.clone(),
2008 bounds: Default::default(),
2009 content_size: Default::default(),
2010 })
2011 }
2012 },
2013 );
2014
2015 window.with_optional_element_state::<InteractiveElementState, _>(
2016 global_id,
2017 |element_state, window| {
2018 let mut element_state =
2019 element_state.map(|element_state| element_state.unwrap_or_default());
2020
2021 if let Some(element_state) = element_state.as_ref()
2022 && cx.has_active_drag()
2023 {
2024 if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() {
2025 *pending_mouse_down.borrow_mut() = None;
2026 }
2027 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
2028 *clicked_state.borrow_mut() = ElementClickedState::default();
2029 }
2030 }
2031
2032 if self.focusable
2037 && self.tracked_focus_handle.is_none()
2038 && let Some(element_state) = element_state.as_mut()
2039 {
2040 let mut handle = element_state
2041 .focus_handle
2042 .get_or_insert_with(|| cx.focus_handle())
2043 .clone()
2044 .tab_stop(self.tab_stop);
2045
2046 if let Some(index) = self.tab_index {
2047 handle = handle.tab_index(index);
2048 }
2049
2050 self.tracked_focus_handle = Some(handle);
2051 }
2052
2053 if let Some(scroll_handle) = self.tracked_scroll_handle.as_ref() {
2054 let scroll_handle_state = scroll_handle.0.borrow();
2055 self.scroll_offset = Some(scroll_handle_state.offset.clone());
2056 self.ongoing_scroll = Some(scroll_handle_state.ongoing_scroll.clone());
2057 } else if (self.base_style.overflow.x == Some(Overflow::Scroll)
2058 || self.base_style.overflow.y == Some(Overflow::Scroll))
2059 && let Some(element_state) = element_state.as_mut()
2060 {
2061 self.scroll_offset = Some(
2062 element_state
2063 .scroll_offset
2064 .get_or_insert_with(Rc::default)
2065 .clone(),
2066 );
2067 self.ongoing_scroll = Some(
2068 element_state
2069 .ongoing_scroll
2070 .get_or_insert_with(|| Rc::new(RefCell::new(OngoingScroll::default())))
2071 .clone(),
2072 );
2073 }
2074
2075 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
2076 let layout_id = f(style, window, cx);
2077 (layout_id, element_state)
2078 },
2079 )
2080 }
2081
2082 pub fn prepaint<R>(
2084 &mut self,
2085 global_id: Option<&GlobalElementId>,
2086 _inspector_id: Option<&InspectorElementId>,
2087 bounds: Bounds<Pixels>,
2088 content_size: Size<Pixels>,
2089 window: &mut Window,
2090 cx: &mut App,
2091 f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut Window, &mut App) -> R,
2092 ) -> R {
2093 self.content_size = content_size;
2094
2095 #[cfg(any(feature = "inspector", debug_assertions))]
2096 window.with_inspector_state(
2097 _inspector_id,
2098 cx,
2099 |inspector_state: &mut Option<DivInspectorState>, _window| {
2100 if let Some(inspector_state) = inspector_state {
2101 inspector_state.bounds = bounds;
2102 inspector_state.content_size = content_size;
2103 }
2104 },
2105 );
2106
2107 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
2108 window.set_focus_handle(focus_handle, cx);
2109
2110 if window.a11y.is_active()
2111 && let Some(global_id) = global_id
2112 {
2113 let node_id = global_id.accesskit_node_id();
2114 window.a11y.focus_ids.insert(node_id, focus_handle.id);
2115 if focus_handle.is_focused(window) && window.a11y.nodes.has_node(node_id) {
2116 window.a11y.nodes.set_focus(node_id);
2117 }
2118 }
2119 }
2120 window.with_optional_element_state::<InteractiveElementState, _>(
2121 global_id,
2122 |element_state, window| {
2123 let mut element_state =
2124 element_state.map(|element_state| element_state.unwrap_or_default());
2125 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
2126
2127 if let Some(element_state) = element_state.as_mut() {
2128 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
2129 let clicked_state = clicked_state.borrow();
2130 self.active = Some(clicked_state.element);
2131 }
2132 if let Some(active_tooltip) = element_state.active_tooltip.as_ref() {
2133 if self.tooltip_builder.is_some() {
2134 self.tooltip_id = set_tooltip_on_window(active_tooltip, window);
2135 } else {
2136 element_state.active_tooltip.take();
2138 }
2139 }
2140 }
2141
2142 window.with_text_style(style.text_style().cloned(), |window| {
2143 window.with_content_mask(
2144 style.overflow_mask(bounds, window.rem_size()),
2145 |window| {
2146 let hitbox = if self.should_insert_hitbox(&style, window, cx) {
2147 Some(window.insert_hitbox(bounds, self.hitbox_behavior))
2148 } else {
2149 None
2150 };
2151
2152 let scroll_offset =
2153 self.clamp_scroll_position(bounds, &style, window, cx);
2154 let result = f(&style, scroll_offset, hitbox, window, cx);
2155 (result, element_state)
2156 },
2157 )
2158 })
2159 },
2160 )
2161 }
2162
2163 fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
2164 self.hitbox_behavior != HitboxBehavior::Normal
2165 || self.window_control.is_some()
2166 || style.mouse_cursor.is_some()
2167 || self.group.is_some()
2168 || self.scroll_offset.is_some()
2169 || self.tracked_focus_handle.is_some()
2170 || self.hover_style.is_some()
2171 || self.group_hover_style.is_some()
2172 || self.hover_listener.is_some()
2173 || !self.mouse_up_listeners.is_empty()
2174 || !self.mouse_down_listeners.is_empty()
2175 || !self.mouse_move_listeners.is_empty()
2176 || !self.mouse_exit_listeners.is_empty()
2177 || !self.file_drop_exit_listeners.is_empty()
2178 || !self.click_listeners.is_empty()
2179 || !self.scroll_wheel_listeners.is_empty()
2180 || !self.pinch_listeners.is_empty()
2181 || self.drag_listener.is_some()
2182 || !self.drop_listeners.is_empty()
2183 || self.tooltip_builder.is_some()
2184 || window.is_inspector_picking(cx)
2185 }
2186
2187 fn clamp_scroll_position(
2188 &self,
2189 bounds: Bounds<Pixels>,
2190 style: &Style,
2191 window: &mut Window,
2192 _cx: &mut App,
2193 ) -> Point<Pixels> {
2194 fn round_to_two_decimals(pixels: Pixels) -> Pixels {
2195 const ROUNDING_FACTOR: f32 = 100.0;
2196 (pixels * ROUNDING_FACTOR).round() / ROUNDING_FACTOR
2197 }
2198
2199 if let Some(scroll_offset) = self.scroll_offset.as_ref() {
2200 let mut scroll_to_bottom = false;
2201 let mut tracked_scroll_handle = self
2202 .tracked_scroll_handle
2203 .as_ref()
2204 .map(|handle| handle.0.borrow_mut());
2205 if let Some(mut scroll_handle_state) = tracked_scroll_handle.as_deref_mut() {
2206 scroll_handle_state.overflow = style.overflow;
2207 scroll_to_bottom = mem::take(&mut scroll_handle_state.scroll_to_bottom);
2208 }
2209
2210 let rem_size = window.rem_size();
2211 let padding = style
2216 .padding
2217 .to_pixels(bounds.size.into(), rem_size)
2218 .map(|edge| window.pixel_snap(*edge));
2219 let padding_size = size(padding.left + padding.right, padding.top + padding.bottom);
2220 let padded_content_size = self.content_size + padding_size;
2227 let scroll_max = (padded_content_size - bounds.size)
2228 .map(round_to_two_decimals)
2229 .max(&Default::default());
2230 let mut scroll_offset = scroll_offset.borrow_mut();
2233
2234 scroll_offset.x = scroll_offset.x.clamp(-scroll_max.width, px(0.));
2235 if scroll_to_bottom {
2236 scroll_offset.y = -scroll_max.height;
2237 } else {
2238 scroll_offset.y = scroll_offset.y.clamp(-scroll_max.height, px(0.));
2239 }
2240
2241 if let Some(mut scroll_handle_state) = tracked_scroll_handle {
2242 scroll_handle_state.max_offset = scroll_max;
2243 scroll_handle_state.bounds = bounds;
2244 }
2245
2246 *scroll_offset
2247 } else {
2248 Point::default()
2249 }
2250 }
2251
2252 pub fn paint(
2261 &mut self,
2262 global_id: Option<&GlobalElementId>,
2263 _inspector_id: Option<&InspectorElementId>,
2264 bounds: Bounds<Pixels>,
2265 hitbox: Option<&Hitbox>,
2266 window: &mut Window,
2267 cx: &mut App,
2268 f: impl FnOnce(&Style, &mut Window, &mut App),
2269 ) {
2270 self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(window));
2271 window.with_optional_element_state::<InteractiveElementState, _>(
2272 global_id,
2273 |element_state, window| {
2274 let mut element_state =
2275 element_state.map(|element_state| element_state.unwrap_or_default());
2276
2277 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
2278
2279 #[cfg(any(test, all(debug_assertions, feature = "test-support")))]
2280 if let Some(debug_selector) = &self.debug_selector {
2281 window
2282 .next_frame
2283 .record_debug_bounds(debug_selector.clone(), bounds);
2284 }
2285
2286 self.paint_hover_group_handler(window, cx);
2287
2288 if !self.resize_listeners.is_empty() {
2289 if let Some(element_state) = element_state.as_mut() {
2290 let size_changed = element_state
2291 .prev_bounds
2292 .map(|prev| prev.size != bounds.size)
2293 .unwrap_or(true);
2294
2295 if size_changed {
2296 element_state.prev_bounds = Some(bounds);
2297 let resize_event = ResizeEvent {
2298 size: bounds.size,
2299 bounds,
2300 };
2301 for listener in &self.resize_listeners {
2302 listener(&resize_event, window, cx);
2303 }
2304 }
2305 }
2306 }
2307
2308 if style.visibility == Visibility::Hidden {
2309 return ((), element_state);
2310 }
2311
2312 let mut tab_group = None;
2313 if self.tab_group {
2314 tab_group = self.tab_index;
2315 }
2316 if let Some(focus_handle) = &self.tracked_focus_handle {
2317 window.next_frame.tab_stops.insert(focus_handle);
2318 }
2319
2320 window.with_element_opacity(style.opacity, |window| {
2321 style.paint(bounds, window, cx, |window: &mut Window, cx: &mut App| {
2322 window.with_text_style(style.text_style().cloned(), |window| {
2323 window.with_content_mask(
2324 style.overflow_mask(bounds, window.rem_size()),
2325 |window| {
2326 window.with_tab_group(tab_group, |window| {
2327 if let Some(hitbox) = hitbox {
2328 #[cfg(debug_assertions)]
2329 self.paint_debug_info(
2330 global_id, hitbox, &style, window, cx,
2331 );
2332
2333 if let Some(drag) = cx.active_drag.as_ref() {
2334 if let Some(mouse_cursor) = drag.cursor_style {
2335 window.set_window_cursor_style(mouse_cursor);
2336 }
2337 } else {
2338 if let Some(mouse_cursor) = style.mouse_cursor {
2339 window.set_cursor_style(mouse_cursor, hitbox);
2340 }
2341 }
2342
2343 if let Some(group) = self.group.clone() {
2344 GroupHitboxes::push(group, hitbox.id, cx);
2345 }
2346
2347 if let Some(area) = self.window_control {
2348 window.insert_window_control_hitbox(
2349 area,
2350 hitbox.clone(),
2351 );
2352 }
2353
2354 self.paint_mouse_listeners(
2355 hitbox,
2356 element_state.as_mut(),
2357 window,
2358 cx,
2359 );
2360 self.paint_scroll_listener(hitbox, &style, window, cx);
2361 }
2362
2363 self.paint_keyboard_listeners(window, cx);
2364
2365 if window.a11y.is_active()
2366 && let Some(global_id) = global_id
2367 && !self.a11y_action_listeners.is_empty()
2368 {
2369 let node_id = global_id.accesskit_node_id();
2370 for (action, listener) in
2371 self.a11y_action_listeners.drain(..)
2372 {
2373 window.on_a11y_action(node_id, action, listener);
2374 }
2375 }
2376
2377 f(&style, window, cx);
2378
2379 if let Some(_hitbox) = hitbox {
2380 #[cfg(any(feature = "inspector", debug_assertions))]
2381 window.insert_inspector_hitbox(
2382 _hitbox.id,
2383 _inspector_id,
2384 cx,
2385 );
2386
2387 if let Some(group) = self.group.as_ref() {
2388 GroupHitboxes::pop(group, cx);
2389 }
2390 }
2391 })
2392 },
2393 );
2394 });
2395 });
2396 });
2397
2398 ((), element_state)
2399 },
2400 );
2401 }
2402
2403 #[cfg(debug_assertions)]
2404 fn paint_debug_info(
2405 &self,
2406 global_id: Option<&GlobalElementId>,
2407 hitbox: &Hitbox,
2408 style: &Style,
2409 window: &mut Window,
2410 cx: &mut App,
2411 ) {
2412 use crate::{BorderStyle, TextAlign};
2413
2414 if let Some(global_id) = global_id.as_ref().filter(|_| {
2415 (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
2416 && hitbox.is_hovered(window)
2417 }) {
2418 const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
2419 let element_id = format!("{:?}", global_id);
2420 let str_len = element_id.len();
2421
2422 let render_debug_text = |window: &mut Window| {
2423 if let Some(text) = window
2424 .text_system()
2425 .shape_text(
2426 element_id.into(),
2427 FONT_SIZE,
2428 &[window.text_style().to_run(str_len)],
2429 None,
2430 None,
2431 )
2432 .ok()
2433 .and_then(|mut text| text.pop())
2434 {
2435 text.paint(hitbox.origin, FONT_SIZE, TextAlign::Left, None, window, cx)
2436 .ok();
2437
2438 let text_bounds = crate::Bounds {
2439 origin: hitbox.origin,
2440 size: text.size(FONT_SIZE),
2441 };
2442 if let Some(source_location) = self.source_location.filter(|_| {
2443 text_bounds.contains(&window.mouse_position())
2444 && window.modifiers().secondary(cx)
2445 }) {
2446 let secondary_held = window.modifiers().secondary(cx);
2447 window.on_key_event({
2448 move |e: &crate::ModifiersChangedEvent, _phase, window, cx| {
2449 if e.modifiers.secondary(cx) != secondary_held
2450 && text_bounds.contains(&window.mouse_position())
2451 {
2452 window.refresh();
2453 }
2454 }
2455 });
2456
2457 let was_hovered = hitbox.is_hovered(window);
2458 let current_view = window.current_view();
2459 window.on_mouse_event({
2460 let hitbox = hitbox.clone();
2461 move |_: &MouseMoveEvent, phase, window, cx| {
2462 if phase == DispatchPhase::Capture {
2463 let hovered = hitbox.is_hovered(window);
2464 if hovered != was_hovered {
2465 cx.notify(current_view)
2466 }
2467 }
2468 }
2469 });
2470
2471 window.on_mouse_event({
2472 let hitbox = hitbox.clone();
2473 let location = source_location;
2474 move |e: &crate::MouseDownEvent, phase, window, cx| {
2475 if text_bounds.contains(&e.position)
2476 && phase.capture()
2477 && hitbox.is_hovered(window)
2478 {
2479 cx.stop_propagation();
2480 let Ok(dir) = std::env::current_dir() else {
2481 return;
2482 };
2483
2484 eprintln!(
2485 "This element was created at:\n{}:{}:{}",
2486 dir.join(location.file()).to_string_lossy(),
2487 location.line(),
2488 location.column()
2489 );
2490 }
2491 }
2492 });
2493 window.paint_quad(crate::outline(
2494 crate::Bounds {
2495 origin: hitbox.origin
2496 + crate::point(crate::px(0.), FONT_SIZE - px(2.)),
2497 size: crate::Size {
2498 width: text_bounds.size.width,
2499 height: crate::px(1.),
2500 },
2501 },
2502 crate::red(),
2503 BorderStyle::default(),
2504 ))
2505 }
2506 }
2507 };
2508
2509 window.with_text_style(
2510 Some(crate::TextStyleRefinement {
2511 color: Some(crate::red()),
2512 line_height: Some(FONT_SIZE.into()),
2513 background_color: Some(crate::white()),
2514 ..Default::default()
2515 }),
2516 render_debug_text,
2517 )
2518 }
2519 }
2520
2521 fn paint_mouse_listeners(
2522 &mut self,
2523 hitbox: &Hitbox,
2524 element_state: Option<&mut InteractiveElementState>,
2525 window: &mut Window,
2526 cx: &mut App,
2527 ) {
2528 let is_focused = self
2529 .tracked_focus_handle
2530 .as_ref()
2531 .map(|handle| handle.is_focused(window))
2532 .unwrap_or(false);
2533
2534 if let Some(focus_handle) = self.tracked_focus_handle.clone() {
2538 let hitbox = hitbox.clone();
2539 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _| {
2540 if phase == DispatchPhase::Bubble
2541 && hitbox.is_hovered(window)
2542 && !window.default_prevented()
2543 {
2544 window.focus(&focus_handle);
2545 window.prevent_default();
2548 }
2549 });
2550 }
2551
2552 for listener in self.mouse_down_listeners.drain(..) {
2553 let hitbox = hitbox.clone();
2554 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
2555 listener(event, phase, &hitbox, window, cx);
2556 })
2557 }
2558
2559 for listener in self.mouse_up_listeners.drain(..) {
2560 let hitbox = hitbox.clone();
2561 window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
2562 listener(event, phase, &hitbox, window, cx);
2563 })
2564 }
2565
2566 for listener in self.mouse_move_listeners.drain(..) {
2567 let hitbox = hitbox.clone();
2568 window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
2569 listener(event, phase, &hitbox, window, cx);
2570 })
2571 }
2572
2573 for listener in self.mouse_exit_listeners.drain(..) {
2574 let hitbox = hitbox.clone();
2575 window.on_mouse_event(move |event: &MouseExitEvent, phase, window, cx| {
2576 listener(event, phase, &hitbox, window, cx);
2577 })
2578 }
2579
2580 for listener in self.file_drop_exit_listeners.drain(..) {
2581 let hitbox = hitbox.clone();
2582 window.on_mouse_event(move |event: &FileDropEvent, phase, window, cx| {
2583 listener(event, phase, &hitbox, window, cx);
2584 })
2585 }
2586
2587 for listener in self.scroll_wheel_listeners.drain(..) {
2588 let hitbox = hitbox.clone();
2589 window.on_mouse_event(move |event: &ScrollEvent, phase, window, cx| {
2590 listener(event, phase, &hitbox, window, cx);
2591 })
2592 }
2593
2594 for listener in self.pinch_listeners.drain(..) {
2595 let hitbox = hitbox.clone();
2596 window.on_mouse_event(move |event: &PinchEvent, phase, window, cx| {
2597 listener(event, phase, &hitbox, window, cx);
2598 })
2599 }
2600
2601 if self.hover_style.is_some()
2602 || self.base_style.mouse_cursor.is_some()
2603 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
2604 {
2605 let hitbox = hitbox.clone();
2606 let was_hovered = hitbox.is_hovered(window);
2607 let current_view = window.current_view();
2608 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2609 let hovered = hitbox.is_hovered(window);
2610 if phase == DispatchPhase::Capture && hovered != was_hovered {
2611 cx.notify(current_view);
2612 }
2613 });
2614 }
2615 let drag_cursor_style = self.base_style.as_ref().mouse_cursor;
2616
2617 let mut drag_listener = mem::take(&mut self.drag_listener);
2618 let drop_listeners = mem::take(&mut self.drop_listeners);
2619 let click_listeners = mem::take(&mut self.click_listeners);
2620 let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
2621
2622 if !drop_listeners.is_empty() {
2623 let hitbox = hitbox.clone();
2624 window.on_mouse_event({
2625 move |_: &MouseUpEvent, phase, window, cx| {
2626 if let Some(drag) = &cx.active_drag
2627 && phase == DispatchPhase::Bubble
2628 && hitbox.is_hovered(window)
2629 {
2630 let drag_state_type = drag.value.as_ref().type_id();
2631 for (drop_state_type, listener) in &drop_listeners {
2632 if *drop_state_type == drag_state_type {
2633 let drag = cx
2634 .active_drag
2635 .take()
2636 .expect("checked for type drag state type above");
2637
2638 let mut can_drop = true;
2639 if let Some(predicate) = &can_drop_predicate {
2640 can_drop = predicate(drag.value.as_ref(), window, cx);
2641 }
2642
2643 if can_drop {
2644 listener(drag.value.as_ref(), window, cx);
2645 window.refresh();
2646 cx.stop_propagation();
2647 }
2648 }
2649 }
2650 }
2651 }
2652 });
2653 }
2654
2655 if let Some(element_state) = element_state {
2656 if !click_listeners.is_empty() || drag_listener.is_some() {
2657 let pending_mouse_down = element_state
2658 .pending_mouse_down
2659 .get_or_insert_with(Default::default)
2660 .clone();
2661
2662 let clicked_state = element_state
2663 .clicked_state
2664 .get_or_insert_with(Default::default)
2665 .clone();
2666
2667 window.on_mouse_event({
2668 let pending_mouse_down = pending_mouse_down.clone();
2669 let hitbox = hitbox.clone();
2670 move |event: &MouseDownEvent, phase, window, _cx| {
2671 if phase == DispatchPhase::Bubble
2672 && event.button == MouseButton::Left
2673 && hitbox.is_hovered(window)
2674 {
2675 *pending_mouse_down.borrow_mut() = Some(event.clone());
2676 window.refresh();
2677 }
2678 }
2679 });
2680
2681 window.on_mouse_event({
2682 let pending_mouse_down = pending_mouse_down.clone();
2683 let hitbox = hitbox.clone();
2684 move |event: &MouseMoveEvent, phase, window, cx| {
2685 if phase == DispatchPhase::Capture {
2686 return;
2687 }
2688
2689 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2690 if let Some(mouse_down) = pending_mouse_down.clone()
2691 && !cx.has_active_drag()
2692 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
2693 && let Some((drag_value, drag_listener)) = drag_listener.take()
2694 {
2695 *clicked_state.borrow_mut() = ElementClickedState::default();
2696 let cursor_offset = event.position - hitbox.origin;
2697 let drag =
2698 (drag_listener)(drag_value.as_ref(), cursor_offset, window, cx);
2699 cx.active_drag = Some(AnyDrag {
2700 view: drag,
2701 value: drag_value,
2702 cursor_offset,
2703 cursor_style: drag_cursor_style,
2704 });
2705 pending_mouse_down.take();
2706 window.refresh();
2707 cx.stop_propagation();
2708 }
2709 }
2710 });
2711
2712 if is_focused {
2713 window.on_key_event({
2715 let click_listeners = click_listeners.clone();
2716 let hitbox = hitbox.clone();
2717 move |event: &KeyUpEvent, phase, window, cx| {
2718 if phase.bubble() && !window.default_prevented() {
2719 let stroke = &event.keystroke;
2720 let keyboard_button = if stroke.key.eq("enter") {
2721 Some(KeyboardButton::Enter)
2722 } else if stroke.key.eq("space") {
2723 Some(KeyboardButton::Space)
2724 } else {
2725 None
2726 };
2727
2728 if let Some(button) = keyboard_button
2729 && !stroke.modifiers.modified()
2730 {
2731 let click_event = ClickEvent::Keyboard(KeyboardClickEvent {
2732 button,
2733 bounds: hitbox.bounds,
2734 });
2735
2736 for listener in &click_listeners {
2737 listener(&click_event, window, cx);
2738 }
2739 }
2740 }
2741 }
2742 });
2743 }
2744
2745 window.on_mouse_event({
2746 let mut captured_mouse_down = None;
2747 let hitbox = hitbox.clone();
2748 move |event: &MouseUpEvent, phase, window, cx| match phase {
2749 DispatchPhase::Capture => {
2753 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2754 if pending_mouse_down.is_some() && hitbox.is_hovered(window) {
2755 captured_mouse_down = pending_mouse_down.take();
2756 window.refresh();
2757 } else if pending_mouse_down.is_some() {
2758 pending_mouse_down.take();
2764 window.refresh();
2765 }
2766 }
2767 DispatchPhase::Bubble => {
2769 if let Some(mouse_down) = captured_mouse_down.take() {
2770 let mouse_click = ClickEvent::Mouse(MouseClickEvent {
2771 down: mouse_down,
2772 up: event.clone(),
2773 });
2774 for listener in &click_listeners {
2775 listener(&mouse_click, window, cx);
2776 }
2777 }
2778 }
2779 }
2780 });
2781 }
2782
2783 if let Some(hover_listener) = self.hover_listener.take() {
2784 let was_hovered = element_state
2785 .hover_state
2786 .get_or_insert_with(Default::default)
2787 .clone();
2788 let has_mouse_down = element_state
2789 .pending_mouse_down
2790 .get_or_insert_with(Default::default)
2791 .clone();
2792 let hover_listener = Rc::new(hover_listener);
2793 let hover_listener_state = was_hovered.clone();
2794 let update_hover = move |is_hovered: bool, window: &mut Window, cx: &mut App| {
2795 let mut was_hovered = hover_listener_state.borrow_mut();
2796 if is_hovered != *was_hovered {
2797 *was_hovered = is_hovered;
2798 drop(was_hovered);
2799 hover_listener(&is_hovered, window, cx);
2800 }
2801 };
2802 let hover_listener_mode = self.hover_listener_mode;
2803
2804 if has_mouse_down.borrow().is_none() {
2805 let is_hovered =
2806 !cx.has_active_drag() && hover_listener_mode.is_hovered(hitbox, window);
2807 if is_hovered != *was_hovered.borrow() {
2808 let update_hover = update_hover.clone();
2809 window.defer(cx, move |window, cx| {
2810 update_hover(is_hovered, window, cx);
2811 });
2812 }
2813 }
2814
2815 window.on_mouse_event({
2816 let update_hover = update_hover.clone();
2817 let hitbox = hitbox.clone();
2818 move |_: &MouseMoveEvent, phase, window, cx| {
2819 if phase == DispatchPhase::Bubble {
2820 let is_hovered = has_mouse_down.borrow().is_none()
2821 && !cx.has_active_drag()
2822 && hover_listener_mode.is_hovered(&hitbox, window);
2823 update_hover(is_hovered, window, cx);
2824 }
2825 }
2826 });
2827
2828 window.on_mouse_event(move |_: &MouseExitEvent, phase, window, cx| {
2831 if phase == DispatchPhase::Bubble {
2832 update_hover(false, window, cx);
2833 }
2834 });
2835 }
2836
2837 if let Some(tooltip_builder) = self.tooltip_builder.take() {
2838 let active_tooltip = element_state
2839 .active_tooltip
2840 .get_or_insert_with(Default::default)
2841 .clone();
2842 let pending_mouse_down = element_state
2843 .pending_mouse_down
2844 .get_or_insert_with(Default::default)
2845 .clone();
2846
2847 let tooltip_is_hoverable = tooltip_builder.hoverable;
2848 let build_tooltip = Rc::new(move |window: &mut Window, cx: &mut App| {
2849 Some(((tooltip_builder.build)(window, cx), tooltip_is_hoverable))
2850 });
2851 let check_is_hovered_during_prepaint = Rc::new({
2853 let pending_mouse_down = pending_mouse_down.clone();
2854 let source_bounds = hitbox.bounds;
2855 move |window: &Window| {
2856 pending_mouse_down.borrow().is_none()
2857 && source_bounds.contains(&window.mouse_position())
2858 }
2859 });
2860 let check_is_hovered = Rc::new({
2861 let hitbox = hitbox.clone();
2862 move |window: &Window| {
2863 pending_mouse_down.borrow().is_none() && hitbox.is_hovered(window)
2864 }
2865 });
2866 register_tooltip_mouse_handlers(
2867 &active_tooltip,
2868 self.tooltip_id,
2869 build_tooltip,
2870 check_is_hovered,
2871 check_is_hovered_during_prepaint,
2872 self.tooltip_show_delay,
2873 window,
2874 );
2875 }
2876
2877 let active_state = element_state
2878 .clicked_state
2879 .get_or_insert_with(Default::default)
2880 .clone();
2881 if active_state.borrow().is_clicked() {
2882 window.on_mouse_event(move |_: &MouseUpEvent, phase, window, _cx| {
2883 if phase == DispatchPhase::Capture {
2884 *active_state.borrow_mut() = ElementClickedState::default();
2885 window.refresh();
2886 }
2887 });
2888 } else {
2889 let active_group_hitbox = self
2890 .group_active_style
2891 .as_ref()
2892 .and_then(|group_active| GroupHitboxes::get(&group_active.group, cx));
2893 let hitbox = hitbox.clone();
2894 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _cx| {
2895 if phase == DispatchPhase::Bubble && !window.default_prevented() {
2896 let group_hovered = active_group_hitbox
2897 .is_some_and(|group_hitbox_id| group_hitbox_id.is_hovered(window));
2898 let element_hovered = hitbox.is_hovered(window);
2899 if group_hovered || element_hovered {
2900 *active_state.borrow_mut() = ElementClickedState {
2901 group: group_hovered,
2902 element: element_hovered,
2903 };
2904 window.refresh();
2905 }
2906 }
2907 });
2908 }
2909 }
2910 }
2911
2912 fn paint_keyboard_listeners(&mut self, window: &mut Window, _cx: &mut App) {
2913 let key_down_listeners = mem::take(&mut self.key_down_listeners);
2914 let key_up_listeners = mem::take(&mut self.key_up_listeners);
2915 let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners);
2916 let action_listeners = mem::take(&mut self.action_listeners);
2917 if let Some(context) = self.key_context.clone() {
2918 window.set_key_context(context);
2919 }
2920
2921 for listener in key_down_listeners {
2922 window.on_key_event(move |event: &KeyDownEvent, phase, window, cx| {
2923 listener(event, phase, window, cx);
2924 })
2925 }
2926
2927 for listener in key_up_listeners {
2928 window.on_key_event(move |event: &KeyUpEvent, phase, window, cx| {
2929 listener(event, phase, window, cx);
2930 })
2931 }
2932
2933 for listener in modifiers_changed_listeners {
2934 window.on_modifiers_changed(move |event: &ModifiersChangedEvent, window, cx| {
2935 listener(event, window, cx);
2936 })
2937 }
2938
2939 for (action_type, listener) in action_listeners {
2940 window.on_action(action_type, listener)
2941 }
2942 }
2943
2944 fn paint_hover_group_handler(&self, window: &mut Window, cx: &mut App) {
2945 let group_hitbox = self
2946 .group_hover_style
2947 .as_ref()
2948 .and_then(|group_hover| GroupHitboxes::get(&group_hover.group, cx));
2949
2950 if let Some(group_hitbox) = group_hitbox {
2951 let was_hovered = group_hitbox.is_hovered(window);
2952 let current_view = window.current_view();
2953 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2954 let hovered = group_hitbox.is_hovered(window);
2955 if phase == DispatchPhase::Capture && hovered != was_hovered {
2956 cx.notify(current_view);
2957 }
2958 });
2959 }
2960 }
2961
2962 fn paint_scroll_listener(
2963 &self,
2964 hitbox: &Hitbox,
2965 style: &Style,
2966 window: &mut Window,
2967 _cx: &mut App,
2968 ) {
2969 if let Some(scroll_offset) = self.scroll_offset.clone() {
2970 let ongoing_scroll = self.ongoing_scroll.clone();
2971 let overflow = style.overflow;
2972 let allow_concurrent_scroll = style.allow_concurrent_scroll;
2973 let restrict_scroll_to_axis = style.restrict_scroll_to_axis;
2974 let line_height = window.line_height();
2975 let hitbox = hitbox.clone();
2976 let current_view = window.current_view();
2977 window.on_mouse_event(move |event: &ScrollEvent, phase, window, cx| {
2978 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
2979 let mut scroll_offset = scroll_offset.borrow_mut();
2980 let old_scroll_offset = *scroll_offset;
2981 let mut delta = event.delta.pixel_delta(line_height);
2982
2983 if restrict_scroll_to_axis
2984 && event.delta.precise()
2985 && let Some(ongoing_scroll) = &ongoing_scroll
2986 {
2987 ongoing_scroll
2988 .borrow_mut()
2989 .filter(&mut delta, event.touch_phase);
2990 }
2991
2992 let mut delta_x = match overflow.x {
2993 Overflow::Scroll if !delta.x.is_zero() => delta.x,
2994 Overflow::Scroll
2995 if !restrict_scroll_to_axis && overflow.y != Overflow::Scroll =>
2996 {
2997 delta.y
2998 }
2999 _ => Pixels::ZERO,
3000 };
3001 let mut delta_y = match overflow.y {
3002 Overflow::Scroll if !delta.y.is_zero() => delta.y,
3003 Overflow::Scroll
3004 if !restrict_scroll_to_axis && overflow.x != Overflow::Scroll =>
3005 {
3006 delta.x
3007 }
3008 _ => Pixels::ZERO,
3009 };
3010 if !allow_concurrent_scroll && !delta_x.is_zero() && !delta_y.is_zero() {
3011 if delta_x.abs() > delta_y.abs() {
3012 delta_y = Pixels::ZERO;
3013 } else {
3014 delta_x = Pixels::ZERO;
3015 }
3016 }
3017 scroll_offset.y += delta_y;
3018 scroll_offset.x += delta_x;
3019 if *scroll_offset != old_scroll_offset {
3020 cx.notify(current_view);
3021 }
3022 }
3023 });
3024 }
3025 }
3026
3027 pub fn compute_style(
3029 &self,
3030 global_id: Option<&GlobalElementId>,
3031 hitbox: Option<&Hitbox>,
3032 window: &mut Window,
3033 cx: &mut App,
3034 ) -> Style {
3035 window.with_optional_element_state(global_id, |element_state, window| {
3036 let mut element_state =
3037 element_state.map(|element_state| element_state.unwrap_or_default());
3038 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
3039 (style, element_state)
3040 })
3041 }
3042
3043 fn compute_style_internal(
3045 &self,
3046 hitbox: Option<&Hitbox>,
3047 element_state: Option<&mut InteractiveElementState>,
3048 window: &mut Window,
3049 cx: &mut App,
3050 ) -> Style {
3051 let mut style = Style::default();
3052 style.refine(&self.base_style);
3053
3054 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
3055 if let Some(in_focus_style) = self.in_focus_style.as_ref()
3056 && focus_handle.within_focused(window, cx)
3057 {
3058 style.refine(in_focus_style);
3059 }
3060
3061 if let Some(focus_style) = self.focus_style.as_ref()
3062 && focus_handle.is_focused(window)
3063 {
3064 style.refine(focus_style);
3065 }
3066 }
3067
3068 if let Some(hitbox) = hitbox {
3069 if !cx.has_active_drag() {
3070 if let Some(group_hover) = self.group_hover_style.as_ref()
3071 && let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx)
3072 && group_hitbox_id.is_hovered(window)
3073 {
3074 style.refine(&group_hover.style);
3075 }
3076
3077 if let Some(hover_style) = self.hover_style.as_ref()
3078 && hitbox.is_hovered(window)
3079 {
3080 style.refine(hover_style);
3081 }
3082 }
3083
3084 if let Some(drag) = cx.active_drag.take() {
3085 let mut can_drop = true;
3086 if let Some(can_drop_predicate) = &self.can_drop_predicate {
3087 can_drop = can_drop_predicate(drag.value.as_ref(), window, cx);
3088 }
3089
3090 if can_drop {
3091 for (state_type, group_drag_style) in &self.group_drag_over_styles {
3092 if let Some(group_hitbox_id) =
3093 GroupHitboxes::get(&group_drag_style.group, cx)
3094 && *state_type == drag.value.as_ref().type_id()
3095 && group_hitbox_id.is_hovered(window)
3096 {
3097 style.refine(&group_drag_style.style);
3098 }
3099 }
3100
3101 for (state_type, build_drag_over_style) in &self.drag_over_styles {
3102 if *state_type == drag.value.as_ref().type_id() && hitbox.is_hovered(window)
3103 {
3104 style.refine(&build_drag_over_style(drag.value.as_ref(), window, cx));
3105 }
3106 }
3107 }
3108
3109 style.mouse_cursor = drag.cursor_style;
3110 cx.active_drag = Some(drag);
3111 }
3112 }
3113
3114 if let Some(element_state) = element_state {
3115 let clicked_state = element_state
3116 .clicked_state
3117 .get_or_insert_with(Default::default)
3118 .borrow();
3119 if clicked_state.group
3120 && let Some(group) = self.group_active_style.as_ref()
3121 {
3122 style.refine(&group.style)
3123 }
3124
3125 if let Some(active_style) = self.active_style.as_ref()
3126 && clicked_state.element
3127 {
3128 style.refine(active_style)
3129 }
3130 }
3131
3132 style
3133 }
3134
3135 pub(crate) fn write_a11y_info(&self, node: &mut accesskit::Node) {
3136 if let Some(id) = &self.accessibility_id {
3137 node.set_author_id(id.to_string());
3138 }
3139 if let Some(label) = &self.aria_label {
3140 node.set_label(label.to_string());
3141 }
3142 if let Some(description) = &self.aria_description {
3143 node.set_description(description.to_string());
3144 }
3145 if let Some(value) = &self.aria_value {
3146 node.set_value(value.to_string());
3147 }
3148 if let Some(placeholder) = &self.aria_placeholder {
3149 node.set_placeholder(placeholder.to_string());
3150 }
3151 if self.aria_disabled {
3152 node.set_disabled();
3153 }
3154 if self.aria_required {
3155 node.set_required();
3156 }
3157 if let Some(invalid) = self.aria_invalid {
3158 node.set_invalid(invalid);
3159 }
3160 if self.aria_modal {
3161 node.set_modal();
3162 }
3163 if let Some(keyshortcuts) = &self.aria_keyshortcuts {
3164 node.set_keyboard_shortcut(keyshortcuts.to_string());
3165 }
3166 if let Some(selected) = self.aria_selected {
3167 node.set_selected(selected);
3168 }
3169 if let Some(expanded) = self.aria_expanded {
3170 node.set_expanded(expanded);
3171 }
3172 if let Some(toggled) = self.aria_toggled {
3173 node.set_toggled(toggled);
3174 }
3175 if let Some(value) = self.aria_numeric_value {
3176 node.set_numeric_value(value);
3177 }
3178 if let Some(value) = self.aria_min_numeric_value {
3179 node.set_min_numeric_value(value);
3180 }
3181 if let Some(value) = self.aria_max_numeric_value {
3182 node.set_max_numeric_value(value);
3183 }
3184 if let Some(orientation) = self.aria_orientation {
3185 node.set_orientation(orientation);
3186 }
3187 if let Some(level) = self.aria_level {
3188 node.set_level(level);
3189 }
3190 if let Some(position) = self.aria_position_in_set {
3191 node.set_position_in_set(position);
3192 }
3193 if let Some(size) = self.aria_size_of_set {
3194 node.set_size_of_set(size);
3195 }
3196 if let Some(index) = self.aria_row_index {
3197 node.set_row_index(index);
3198 }
3199 if let Some(index) = self.aria_column_index {
3200 node.set_column_index(index);
3201 }
3202 if let Some(count) = self.aria_row_count {
3203 node.set_row_count(count);
3204 }
3205 if let Some(count) = self.aria_column_count {
3206 node.set_column_count(count);
3207 }
3208 if !self.click_listeners.is_empty() {
3209 node.add_action(accesskit::Action::Click);
3210 }
3211 if self.tracked_focus_handle.is_some() || self.focusable {
3212 node.add_action(accesskit::Action::Focus);
3213 }
3214 for (action, _) in &self.a11y_action_listeners {
3215 node.add_action(*action);
3216 }
3217 }
3218}
3219
3220#[derive(Default)]
3223pub struct InteractiveElementState {
3224 pub(crate) focus_handle: Option<FocusHandle>,
3225 pub(crate) clicked_state: Option<Rc<RefCell<ElementClickedState>>>,
3226 pub(crate) hover_state: Option<Rc<RefCell<bool>>>,
3227 pub(crate) pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
3228 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
3229 ongoing_scroll: Option<Rc<RefCell<OngoingScroll>>>,
3230 pub(crate) active_tooltip: Option<Rc<RefCell<Option<ActiveTooltip>>>>,
3231 pub(crate) prev_bounds: Option<Bounds<Pixels>>,
3232}
3233
3234#[derive(Copy, Clone, Default, Eq, PartialEq)]
3236pub struct ElementClickedState {
3237 pub group: bool,
3239
3240 pub element: bool,
3242}
3243
3244impl ElementClickedState {
3245 fn is_clicked(&self) -> bool {
3246 self.group || self.element
3247 }
3248}
3249
3250pub(crate) enum ActiveTooltip {
3251 WaitingForShow { _task: Task<()> },
3253 Visible {
3255 tooltip: AnyTooltip,
3256 is_hoverable: bool,
3257 },
3258 WaitingForHide {
3261 tooltip: AnyTooltip,
3262 _task: Task<()>,
3263 },
3264}
3265
3266pub(crate) fn clear_active_tooltip(
3267 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3268 window: &mut Window,
3269) {
3270 match active_tooltip.borrow_mut().take() {
3271 None => {}
3272 Some(ActiveTooltip::WaitingForShow { .. }) => {}
3273 Some(ActiveTooltip::Visible { .. }) => window.refresh(),
3274 Some(ActiveTooltip::WaitingForHide { .. }) => window.refresh(),
3275 }
3276}
3277
3278pub(crate) fn clear_active_tooltip_if_not_hoverable(
3279 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3280 window: &mut Window,
3281) {
3282 let should_clear = match active_tooltip.borrow().as_ref() {
3283 None => false,
3284 Some(ActiveTooltip::WaitingForShow { .. }) => false,
3285 Some(ActiveTooltip::Visible { is_hoverable, .. }) => !is_hoverable,
3286 Some(ActiveTooltip::WaitingForHide { .. }) => false,
3287 };
3288 if should_clear {
3289 active_tooltip.borrow_mut().take();
3290 window.refresh();
3291 }
3292}
3293
3294pub(crate) fn set_tooltip_on_window(
3295 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3296 window: &mut Window,
3297) -> Option<TooltipId> {
3298 let tooltip = match active_tooltip.borrow().as_ref() {
3299 None => return None,
3300 Some(ActiveTooltip::WaitingForShow { .. }) => return None,
3301 Some(ActiveTooltip::Visible { tooltip, .. }) => tooltip.clone(),
3302 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => tooltip.clone(),
3303 };
3304 Some(window.set_tooltip(tooltip))
3305}
3306
3307pub(crate) fn register_tooltip_mouse_handlers(
3308 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3309 tooltip_id: Option<TooltipId>,
3310 build_tooltip: Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3311 check_is_hovered: Rc<dyn Fn(&Window) -> bool>,
3312 check_is_hovered_during_prepaint: Rc<dyn Fn(&Window) -> bool>,
3313 show_delay: Option<Duration>,
3314 window: &mut Window,
3315) {
3316 let current_view = window.current_view();
3317 let show_delay = show_delay.unwrap_or(DEFAULT_TOOLTIP_SHOW_DELAY);
3318
3319 window.on_mouse_event({
3320 let active_tooltip = active_tooltip.clone();
3321 let build_tooltip = build_tooltip.clone();
3322 let check_is_hovered = check_is_hovered.clone();
3323 move |_: &MouseMoveEvent, phase, window, cx| {
3324 handle_tooltip_mouse_move(
3325 &active_tooltip,
3326 &build_tooltip,
3327 &check_is_hovered,
3328 &check_is_hovered_during_prepaint,
3329 tooltip_id,
3330 current_view,
3331 phase,
3332 show_delay,
3333 window,
3334 cx,
3335 )
3336 }
3337 });
3338
3339 window.on_mouse_event({
3340 let active_tooltip = active_tooltip.clone();
3341 move |_: &MouseDownEvent, _phase, window: &mut Window, _cx| {
3342 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3343 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3344 }
3345 }
3346 });
3347
3348 window.on_mouse_event({
3349 let active_tooltip = active_tooltip.clone();
3350 move |_: &ScrollEvent, _phase, window: &mut Window, _cx| {
3351 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3352 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3353 }
3354 }
3355 });
3356}
3357
3358fn handle_tooltip_mouse_move(
3370 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3371 build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3372 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3373 check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
3374 tooltip_id: Option<TooltipId>,
3375 current_view: EntityId,
3376 phase: DispatchPhase,
3377 show_delay: Duration,
3378 window: &mut Window,
3379 cx: &mut App,
3380) {
3381 enum Action {
3384 None,
3385 CancelShow,
3386 ScheduleShow,
3387 CheckVisible,
3388 }
3389
3390 let action = match active_tooltip.borrow().as_ref() {
3391 None => {
3392 let is_hovered = check_is_hovered(window);
3393 if is_hovered && phase.bubble() {
3394 Action::ScheduleShow
3395 } else {
3396 Action::None
3397 }
3398 }
3399 Some(ActiveTooltip::WaitingForShow { .. }) => {
3400 let is_hovered = check_is_hovered(window);
3401 if is_hovered {
3402 Action::None
3403 } else {
3404 Action::CancelShow
3405 }
3406 }
3407 Some(ActiveTooltip::Visible { is_hoverable, .. }) => {
3408 if phase.capture()
3409 && !check_is_hovered(window)
3410 && (!*is_hoverable
3411 || !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3412 {
3413 Action::CheckVisible
3414 } else {
3415 Action::None
3416 }
3417 }
3418 Some(ActiveTooltip::WaitingForHide { .. }) => {
3419 if phase.capture()
3420 && (check_is_hovered(window)
3421 || tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3422 {
3423 Action::CheckVisible
3424 } else {
3425 Action::None
3426 }
3427 }
3428 };
3429
3430 match action {
3431 Action::None => {}
3432 Action::CancelShow => {
3433 active_tooltip.borrow_mut().take();
3435 }
3436 Action::ScheduleShow => {
3437 let delayed_show_task = window.spawn(cx, {
3438 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3439 let build_tooltip = build_tooltip.clone();
3440 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3441 async move |cx| {
3442 cx.background_executor().timer(show_delay).await;
3443 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3444 return;
3445 };
3446 cx.update(|window, cx| {
3447 let new_tooltip =
3448 build_tooltip(window, cx).map(|(view, tooltip_is_hoverable)| {
3449 let weak_active_tooltip = Rc::downgrade(&active_tooltip);
3450 ActiveTooltip::Visible {
3451 tooltip: AnyTooltip {
3452 view,
3453 mouse_position: window.mouse_position(),
3454 check_visible_and_update: Rc::new(
3455 move |tooltip_bounds, window, cx| {
3456 let Some(active_tooltip) =
3457 weak_active_tooltip.upgrade()
3458 else {
3459 return false;
3460 };
3461 handle_tooltip_check_visible_and_update(
3462 &active_tooltip,
3463 tooltip_is_hoverable,
3464 &check_is_hovered_during_prepaint,
3465 tooltip_bounds,
3466 window,
3467 cx,
3468 )
3469 },
3470 ),
3471 },
3472 is_hoverable: tooltip_is_hoverable,
3473 }
3474 });
3475 *active_tooltip.borrow_mut() = new_tooltip;
3476 window.refresh();
3477 })
3478 .ok();
3479 }
3480 });
3481 active_tooltip
3482 .borrow_mut()
3483 .replace(ActiveTooltip::WaitingForShow {
3484 _task: delayed_show_task,
3485 });
3486 }
3487 Action::CheckVisible => cx.notify(current_view),
3488 }
3489}
3490
3491fn handle_tooltip_check_visible_and_update(
3495 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3496 tooltip_is_hoverable: bool,
3497 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3498 tooltip_bounds: Bounds<Pixels>,
3499 window: &mut Window,
3500 cx: &mut App,
3501) -> bool {
3502 enum Action {
3505 None,
3506 Hide,
3507 ScheduleHide(AnyTooltip),
3508 CancelHide(AnyTooltip),
3509 }
3510
3511 let is_hovered = check_is_hovered(window)
3512 || (tooltip_is_hoverable && tooltip_bounds.contains(&window.mouse_position()));
3513 let action = match active_tooltip.borrow().as_ref() {
3514 Some(ActiveTooltip::Visible { tooltip, .. }) => {
3515 if is_hovered {
3516 Action::None
3517 } else {
3518 if tooltip_is_hoverable {
3519 Action::ScheduleHide(tooltip.clone())
3520 } else {
3521 Action::Hide
3522 }
3523 }
3524 }
3525 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => {
3526 if is_hovered {
3527 Action::CancelHide(tooltip.clone())
3528 } else {
3529 Action::None
3530 }
3531 }
3532 None | Some(ActiveTooltip::WaitingForShow { .. }) => Action::None,
3533 };
3534
3535 match action {
3536 Action::None => {}
3537 Action::Hide => clear_active_tooltip(active_tooltip, window),
3538 Action::ScheduleHide(tooltip) => {
3539 let delayed_hide_task = window.spawn(cx, {
3540 let active_tooltip = active_tooltip.clone();
3541 async move |cx| {
3542 cx.background_executor()
3543 .timer(HOVERABLE_TOOLTIP_HIDE_DELAY)
3544 .await;
3545 if active_tooltip.borrow_mut().take().is_some() {
3546 cx.update(|window, _cx| window.refresh()).ok();
3547 }
3548 }
3549 });
3550 active_tooltip
3551 .borrow_mut()
3552 .replace(ActiveTooltip::WaitingForHide {
3553 tooltip,
3554 _task: delayed_hide_task,
3555 });
3556 }
3557 Action::CancelHide(tooltip) => {
3558 active_tooltip.borrow_mut().replace(ActiveTooltip::Visible {
3560 tooltip,
3561 is_hoverable: true,
3562 });
3563 }
3564 }
3565
3566 active_tooltip.borrow().is_some()
3567}
3568
3569#[derive(Default)]
3570pub(crate) struct GroupHitboxes(HashMap<SharedString, SmallVec<[HitboxId; 1]>>);
3571
3572impl Global for GroupHitboxes {}
3573
3574impl GroupHitboxes {
3575 pub fn get(name: &SharedString, cx: &mut App) -> Option<HitboxId> {
3576 cx.default_global::<Self>()
3577 .0
3578 .get(name)
3579 .and_then(|bounds_stack| bounds_stack.last())
3580 .cloned()
3581 }
3582
3583 pub fn push(name: SharedString, hitbox_id: HitboxId, cx: &mut App) {
3584 cx.default_global::<Self>()
3585 .0
3586 .entry(name)
3587 .or_default()
3588 .push(hitbox_id);
3589 }
3590
3591 pub fn pop(name: &SharedString, cx: &mut App) {
3592 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
3593 }
3594}
3595
3596pub struct Stateful<E> {
3598 pub(crate) element: E,
3599}
3600
3601impl<E> Styled for Stateful<E>
3602where
3603 E: Styled,
3604{
3605 fn style(&mut self) -> &mut StyleRefinement {
3606 self.element.style()
3607 }
3608}
3609
3610impl<E> StatefulInteractiveElement for Stateful<E>
3611where
3612 E: Element,
3613 Self: InteractiveElement,
3614{
3615}
3616
3617impl<E> InteractiveElement for Stateful<E>
3618where
3619 E: InteractiveElement,
3620{
3621 fn interactivity(&mut self) -> &mut Interactivity {
3622 self.element.interactivity()
3623 }
3624}
3625
3626impl<E> Element for Stateful<E>
3627where
3628 E: Element,
3629{
3630 type RequestLayoutState = E::RequestLayoutState;
3631 type PrepaintState = E::PrepaintState;
3632
3633 fn id(&self) -> Option<ElementId> {
3634 self.element.id()
3635 }
3636
3637 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
3638 self.element.source_location()
3639 }
3640
3641 fn a11y_role(&self) -> Option<accesskit::Role> {
3642 self.element.a11y_role()
3643 }
3644
3645 fn write_a11y_info(&self, node: &mut accesskit::Node) {
3646 self.element.write_a11y_info(node);
3647 }
3648
3649 fn request_layout(
3650 &mut self,
3651 id: Option<&GlobalElementId>,
3652 inspector_id: Option<&InspectorElementId>,
3653 window: &mut Window,
3654 cx: &mut App,
3655 ) -> (LayoutId, Self::RequestLayoutState) {
3656 self.element.request_layout(id, inspector_id, window, cx)
3657 }
3658
3659 fn prepaint(
3660 &mut self,
3661 id: Option<&GlobalElementId>,
3662 inspector_id: Option<&InspectorElementId>,
3663 bounds: Bounds<Pixels>,
3664 state: &mut Self::RequestLayoutState,
3665 window: &mut Window,
3666 cx: &mut App,
3667 ) -> E::PrepaintState {
3668 self.element
3669 .prepaint(id, inspector_id, bounds, state, window, cx)
3670 }
3671
3672 fn paint(
3673 &mut self,
3674 id: Option<&GlobalElementId>,
3675 inspector_id: Option<&InspectorElementId>,
3676 bounds: Bounds<Pixels>,
3677 request_layout: &mut Self::RequestLayoutState,
3678 prepaint: &mut Self::PrepaintState,
3679 window: &mut Window,
3680 cx: &mut App,
3681 ) {
3682 self.element.paint(
3683 id,
3684 inspector_id,
3685 bounds,
3686 request_layout,
3687 prepaint,
3688 window,
3689 cx,
3690 );
3691 }
3692}
3693
3694impl<E> IntoElement for Stateful<E>
3695where
3696 E: Element,
3697{
3698 type Element = Self;
3699
3700 fn into_element(self) -> Self::Element {
3701 self
3702 }
3703}
3704
3705impl<E> ParentElement for Stateful<E>
3706where
3707 E: ParentElement,
3708{
3709 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
3710 self.element.extend(elements)
3711 }
3712}
3713
3714#[derive(Clone)]
3717pub struct ScrollAnchor {
3718 handle: ScrollHandle,
3719 last_origin: Rc<RefCell<Point<Pixels>>>,
3720}
3721
3722impl ScrollAnchor {
3723 pub fn for_handle(handle: ScrollHandle) -> Self {
3725 Self {
3726 handle,
3727 last_origin: Default::default(),
3728 }
3729 }
3730 pub fn scroll_to(&self, window: &mut Window, _cx: &mut App) {
3732 let this = self.clone();
3733
3734 window.on_next_frame(move |_, _| {
3735 let viewport_bounds = this.handle.bounds();
3736 let self_bounds = *this.last_origin.borrow();
3737 this.handle.set_offset(viewport_bounds.origin - self_bounds);
3738 });
3739 }
3740}
3741
3742#[derive(Default, Debug)]
3743struct ScrollHandleState {
3744 offset: Rc<RefCell<Point<Pixels>>>,
3745 ongoing_scroll: Rc<RefCell<OngoingScroll>>,
3746 bounds: Bounds<Pixels>,
3747 max_offset: Size<Pixels>,
3748 child_bounds: Vec<Bounds<Pixels>>,
3749 scroll_to_bottom: bool,
3750 overflow: Point<Overflow>,
3751 active_item: Option<ScrollActiveItem>,
3752}
3753
3754#[derive(Default, Debug, Clone, Copy)]
3755struct ScrollActiveItem {
3756 index: usize,
3757 strategy: ScrollStrategy,
3758}
3759
3760#[derive(Default, Debug, Clone, Copy)]
3761enum ScrollStrategy {
3762 #[default]
3763 FirstVisible,
3764 Top,
3765}
3766
3767#[derive(Clone, Debug)]
3771pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
3772
3773impl Default for ScrollHandle {
3774 fn default() -> Self {
3775 Self::new()
3776 }
3777}
3778
3779impl ScrollHandle {
3780 pub fn new() -> Self {
3782 Self(Rc::default())
3783 }
3784
3785 pub fn offset(&self) -> Point<Pixels> {
3787 *self.0.borrow().offset.borrow()
3788 }
3789
3790 pub fn max_offset(&self) -> Size<Pixels> {
3792 self.0.borrow().max_offset
3793 }
3794
3795 pub fn top_item(&self) -> usize {
3797 let state = self.0.borrow();
3798 let top = state.bounds.top() - state.offset.borrow().y;
3799
3800 match state.child_bounds.binary_search_by(|bounds| {
3801 if top < bounds.top() {
3802 Ordering::Greater
3803 } else if top > bounds.bottom() {
3804 Ordering::Less
3805 } else {
3806 Ordering::Equal
3807 }
3808 }) {
3809 Ok(ix) => ix,
3810 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
3811 }
3812 }
3813
3814 pub fn bottom_item(&self) -> usize {
3816 let state = self.0.borrow();
3817 let bottom = state.bounds.bottom() - state.offset.borrow().y;
3818
3819 match state.child_bounds.binary_search_by(|bounds| {
3820 if bottom < bounds.top() {
3821 Ordering::Greater
3822 } else if bottom > bounds.bottom() {
3823 Ordering::Less
3824 } else {
3825 Ordering::Equal
3826 }
3827 }) {
3828 Ok(ix) => ix,
3829 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
3830 }
3831 }
3832
3833 pub fn bounds(&self) -> Bounds<Pixels> {
3835 self.0.borrow().bounds
3836 }
3837
3838 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
3840 self.0.borrow().child_bounds.get(ix).cloned()
3841 }
3842
3843 pub fn scroll_to_item(&self, ix: usize) {
3845 let mut state = self.0.borrow_mut();
3846 state.active_item = Some(ScrollActiveItem {
3847 index: ix,
3848 strategy: ScrollStrategy::default(),
3849 });
3850 }
3851
3852 pub fn scroll_to_top_of_item(&self, ix: usize) {
3855 let mut state = self.0.borrow_mut();
3856 state.active_item = Some(ScrollActiveItem {
3857 index: ix,
3858 strategy: ScrollStrategy::Top,
3859 });
3860 }
3861
3862 fn scroll_to_active_item(&self) {
3866 let mut state = self.0.borrow_mut();
3867
3868 let Some(active_item) = state.active_item else {
3869 return;
3870 };
3871
3872 let active_item = match state.child_bounds.get(active_item.index) {
3873 Some(bounds) => {
3874 let mut scroll_offset = state.offset.borrow_mut();
3875
3876 match active_item.strategy {
3877 ScrollStrategy::FirstVisible => {
3878 if state.overflow.y == Overflow::Scroll {
3879 if bounds.top() + scroll_offset.y < state.bounds.top() {
3880 scroll_offset.y = state.bounds.top() - bounds.top();
3881 } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
3882 scroll_offset.y = state.bounds.bottom() - bounds.bottom();
3883 }
3884 }
3885 }
3886 ScrollStrategy::Top => {
3887 scroll_offset.y = state.bounds.top() - bounds.top();
3888 }
3889 }
3890
3891 if state.overflow.x == Overflow::Scroll {
3892 if bounds.left() + scroll_offset.x < state.bounds.left() {
3893 scroll_offset.x = state.bounds.left() - bounds.left();
3894 } else if bounds.right() + scroll_offset.x > state.bounds.right() {
3895 scroll_offset.x = state.bounds.right() - bounds.right();
3896 }
3897 }
3898 None
3899 }
3900 None => Some(active_item),
3901 };
3902 state.active_item = active_item;
3903 }
3904
3905 pub fn scroll_to_bottom(&self) {
3907 let mut state = self.0.borrow_mut();
3908 state.scroll_to_bottom = true;
3909 }
3910
3911 pub fn set_offset(&self, mut position: Point<Pixels>) {
3915 let state = self.0.borrow();
3916 *state.offset.borrow_mut() = position;
3917 }
3918
3919 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
3921 let ix = self.top_item();
3922 let state = self.0.borrow();
3923
3924 if let Some(child_bounds) = state.child_bounds.get(ix) {
3925 (
3926 ix,
3927 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
3928 )
3929 } else {
3930 (ix, px(0.))
3931 }
3932 }
3933
3934 pub fn logical_scroll_bottom(&self) -> (usize, Pixels) {
3936 let ix = self.bottom_item();
3937 let state = self.0.borrow();
3938
3939 if let Some(child_bounds) = state.child_bounds.get(ix) {
3940 (
3941 ix,
3942 child_bounds.bottom() + state.offset.borrow().y - state.bounds.bottom(),
3943 )
3944 } else {
3945 (ix, px(0.))
3946 }
3947 }
3948
3949 pub fn children_count(&self) -> usize {
3951 self.0.borrow().child_bounds.len()
3952 }
3953}
3954
3955#[cfg(test)]
3956mod tests {
3957 use super::*;
3958 use crate::{
3959 AnyWindowHandle, AppContext as _, Context, InputEvent as _, MouseButton, Render,
3960 ScrollDelta, ScrollEvent, TestAppContext, fallback_prompt_renderer, hsla, point, size,
3961 };
3962 use std::{
3963 cell::{Cell, RefCell},
3964 rc::Rc,
3965 };
3966
3967 struct MouseDownOutOwner {
3968 mouse_down_out_count: Rc<Cell<usize>>,
3969 }
3970
3971 impl Render for MouseDownOutOwner {
3972 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
3973 let mouse_down_out_count = self.mouse_down_out_count.clone();
3974 div()
3975 .size_full()
3976 .child(
3977 div()
3978 .id("target")
3979 .size(px(50.))
3980 .on_mouse_down_out(move |_, _, _| {
3981 mouse_down_out_count.set(mouse_down_out_count.get() + 1);
3982 }),
3983 )
3984 }
3985 }
3986
3987 #[test]
3988 fn mouse_down_out_is_suppressed_while_window_prompt_is_active() {
3989 let mut test_app = TestAppContext::single();
3990 let mouse_down_out_count = Rc::new(Cell::new(0));
3991 let (_, cx) = test_app.add_window_view({
3992 let mouse_down_out_count = mouse_down_out_count.clone();
3993 move |_, _| MouseDownOutOwner {
3994 mouse_down_out_count,
3995 }
3996 });
3997
3998 cx.simulate_mouse_down(
3999 point(px(75.), px(75.)),
4000 MouseButton::Left,
4001 Default::default(),
4002 );
4003 assert_eq!(mouse_down_out_count.get(), 1);
4004
4005 cx.update(|window, cx| {
4006 cx.set_prompt_builder(fallback_prompt_renderer);
4007 let _receiver =
4008 window.prompt(crate::PromptLevel::Warning, "message", None, &["Ok"], cx);
4009 });
4010 cx.run_until_parked();
4011
4012 cx.simulate_mouse_down(
4013 point(px(75.), px(75.)),
4014 MouseButton::Left,
4015 Default::default(),
4016 );
4017 assert_eq!(
4018 mouse_down_out_count.get(),
4019 1,
4020 "mouse down over an active prompt must not reach underlying listeners"
4021 );
4022 }
4023
4024 struct HoverListenerLayoutTestView {
4025 target_left: Pixels,
4026 hover_transitions: Rc<RefCell<Vec<bool>>>,
4027 }
4028
4029 impl Render for HoverListenerLayoutTestView {
4030 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4031 let hover_transitions = self.hover_transitions.clone();
4032 div().relative().size_full().child(
4033 div()
4034 .id("hover-target")
4035 .absolute()
4036 .left(self.target_left)
4037 .top_0()
4038 .size(px(20.))
4039 .on_click(|_, _, _| {})
4040 .on_hover(move |is_hovered, _, _| {
4041 hover_transitions.borrow_mut().push(*is_hovered);
4042 }),
4043 )
4044 }
4045 }
4046
4047 #[gpui::test]
4048 fn default_hover_listener_updates_when_layout_changes_under_stationary_mouse(
4049 cx: &mut TestAppContext,
4050 ) {
4051 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4052 let window = cx.add_window({
4053 let hover_transitions = hover_transitions.clone();
4054 move |_, _| HoverListenerLayoutTestView {
4055 target_left: px(40.),
4056 hover_transitions,
4057 }
4058 });
4059 let any_window = AnyWindowHandle::from(window);
4060
4061 cx.update_window(any_window, |_, window, cx| {
4062 window.draw(cx).clear();
4063 window.dispatch_event(
4064 MouseMoveEvent {
4065 position: point(px(10.), px(10.)),
4066 modifiers: Default::default(),
4067 pressed_button: None,
4068 }
4069 .to_platform_input(),
4070 cx,
4071 );
4072 })
4073 .unwrap();
4074 assert!(hover_transitions.borrow().is_empty());
4075
4076 window
4077 .update(cx, |view, _, cx| {
4078 view.target_left = px(0.);
4079 cx.notify();
4080 })
4081 .unwrap();
4082 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
4083 .unwrap();
4084 assert_eq!(*hover_transitions.borrow(), [true]);
4085
4086 window
4087 .update(cx, |view, _, cx| {
4088 view.target_left = px(40.);
4089 cx.notify();
4090 })
4091 .unwrap();
4092 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
4093 .unwrap();
4094 assert_eq!(*hover_transitions.borrow(), [true, false]);
4095 }
4096
4097 #[gpui::test]
4098 fn default_hover_listener_ends_after_key_press(cx: &mut TestAppContext) {
4099 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4100 let window = cx.add_window({
4101 let hover_transitions = hover_transitions.clone();
4102 move |_, _| HoverListenerLayoutTestView {
4103 target_left: px(0.),
4104 hover_transitions,
4105 }
4106 });
4107 let any_window = AnyWindowHandle::from(window);
4108
4109 cx.update_window(any_window, |_, window, cx| {
4110 window.draw(cx).clear();
4111 window.dispatch_event(
4112 MouseMoveEvent {
4113 position: point(px(10.), px(10.)),
4114 modifiers: Default::default(),
4115 pressed_button: None,
4116 }
4117 .to_platform_input(),
4118 cx,
4119 );
4120 })
4121 .unwrap();
4122 assert_eq!(*hover_transitions.borrow(), [true]);
4123
4124 key_down(cx, any_window, "a");
4125 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
4126 .unwrap();
4127 assert_eq!(*hover_transitions.borrow(), [true, false]);
4128 }
4129
4130 struct HoverListenerModeLayoutTestView {
4131 target_left: Pixels,
4132 hover_transitions: Rc<RefCell<Vec<bool>>>,
4133 }
4134
4135 impl Render for HoverListenerModeLayoutTestView {
4136 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4137 let hover_transitions = self.hover_transitions.clone();
4138 div().relative().size_full().child(
4139 div()
4140 .id("hover-target")
4141 .absolute()
4142 .left(self.target_left)
4143 .top_0()
4144 .size(px(20.))
4145 .hover_listener_mode(HoverListenerMode::InputModalityIndependent)
4146 .on_hover(move |is_hovered, _, _| {
4147 hover_transitions.borrow_mut().push(*is_hovered);
4148 }),
4149 )
4150 }
4151 }
4152
4153 #[gpui::test]
4154 fn input_modality_independent_hover_listener_updates_after_key_press(cx: &mut TestAppContext) {
4155 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4156 let window = cx.add_window({
4157 let hover_transitions = hover_transitions.clone();
4158 move |_, _| HoverListenerModeLayoutTestView {
4159 target_left: px(40.),
4160 hover_transitions,
4161 }
4162 });
4163 let any_window = AnyWindowHandle::from(window);
4164 let pointer_position = point(px(10.), px(10.));
4165
4166 cx.update_window(any_window, |_, window, cx| {
4167 window.draw(cx).clear();
4168 window.dispatch_event(
4169 MouseMoveEvent {
4170 position: pointer_position,
4171 modifiers: Default::default(),
4172 pressed_button: None,
4173 }
4174 .to_platform_input(),
4175 cx,
4176 );
4177 })
4178 .unwrap();
4179 assert!(hover_transitions.borrow().is_empty());
4180
4181 key_down(cx, any_window, "a");
4182 window
4183 .update(cx, |view, _, cx| {
4184 view.target_left = px(0.);
4185 cx.notify();
4186 })
4187 .unwrap();
4188 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
4189 .unwrap();
4190 assert_eq!(*hover_transitions.borrow(), [true]);
4191
4192 key_down(cx, any_window, "b");
4193 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
4194 .unwrap();
4195 assert_eq!(*hover_transitions.borrow(), [true]);
4196
4197 cx.update_window(any_window, |_, window, cx| {
4198 window.dispatch_event(
4199 MouseMoveEvent {
4200 position: point(px(30.), px(10.)),
4201 modifiers: Default::default(),
4202 pressed_button: None,
4203 }
4204 .to_platform_input(),
4205 cx,
4206 );
4207 })
4208 .unwrap();
4209 assert_eq!(*hover_transitions.borrow(), [true, false]);
4210
4211 cx.update_window(any_window, |_, window, cx| {
4212 window.dispatch_event(
4213 MouseMoveEvent {
4214 position: pointer_position,
4215 modifiers: Default::default(),
4216 pressed_button: None,
4217 }
4218 .to_platform_input(),
4219 cx,
4220 );
4221 window.dispatch_event(
4222 MouseExitEvent {
4223 position: pointer_position,
4224 ..Default::default()
4225 }
4226 .to_platform_input(),
4227 cx,
4228 );
4229 })
4230 .unwrap();
4231 assert_eq!(*hover_transitions.borrow(), [true, false, true, false]);
4232 }
4233
4234 #[gpui::test]
4235 fn default_hover_listener_remains_hovered_during_stationary_mouse_press(
4236 cx: &mut TestAppContext,
4237 ) {
4238 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4239 let window = cx.add_window({
4240 let hover_transitions = hover_transitions.clone();
4241 move |_, _| HoverListenerLayoutTestView {
4242 target_left: px(0.),
4243 hover_transitions,
4244 }
4245 });
4246 let any_window = AnyWindowHandle::from(window);
4247 let mouse_position = point(px(10.), px(10.));
4248
4249 cx.update_window(any_window, |_, window, cx| {
4250 window.draw(cx).clear();
4251 window.dispatch_event(
4252 MouseMoveEvent {
4253 position: mouse_position,
4254 modifiers: Default::default(),
4255 pressed_button: None,
4256 }
4257 .to_platform_input(),
4258 cx,
4259 );
4260 })
4261 .unwrap();
4262 assert_eq!(*hover_transitions.borrow(), [true]);
4263
4264 cx.update_window(any_window, |_, window, cx| {
4265 window.dispatch_event(
4266 MouseDownEvent {
4267 position: mouse_position,
4268 button: MouseButton::Left,
4269 modifiers: Default::default(),
4270 click_count: 1,
4271 first_mouse: false,
4272 }
4273 .to_platform_input(),
4274 cx,
4275 );
4276 window.draw(cx).clear();
4277 })
4278 .unwrap();
4279 assert_eq!(*hover_transitions.borrow(), [true]);
4280
4281 cx.update_window(any_window, |_, window, cx| {
4282 window.dispatch_event(
4283 MouseUpEvent {
4284 position: mouse_position,
4285 button: MouseButton::Left,
4286 modifiers: Default::default(),
4287 click_count: 1,
4288 }
4289 .to_platform_input(),
4290 cx,
4291 );
4292 window.draw(cx).clear();
4293 })
4294 .unwrap();
4295 assert_eq!(*hover_transitions.borrow(), [true]);
4296 }
4297
4298 fn key_down(cx: &mut TestAppContext, window: AnyWindowHandle, key: &str) {
4299 let keystroke = crate::Keystroke::parse(key).unwrap();
4300 cx.update_window(window, |_, window, cx| {
4301 window.dispatch_event(
4302 KeyDownEvent {
4303 keystroke,
4304 is_held: false,
4305 prefer_character_input: false,
4306 }
4307 .to_platform_input(),
4308 cx,
4309 );
4310 })
4311 .unwrap();
4312 }
4313
4314 #[test]
4315 fn test_tooltip_show_delay_sets_interactivity_state() {
4316 let delay = Duration::from_millis(125);
4317 let element = div().id("tooltip-target").tooltip_show_delay(delay);
4318
4319 assert_eq!(
4320 element.element.interactivity.tooltip_show_delay,
4321 Some(delay)
4322 );
4323 }
4324
4325 #[test]
4326 fn aria_description_and_keyshortcuts_are_written_to_accesskit() {
4327 let element = div()
4328 .id("save-button")
4329 .role(accesskit::Role::Button)
4330 .aria_description("Save the active document")
4331 .aria_keyshortcuts("Ctrl+S");
4332 let mut node = accesskit::Node::new(accesskit::Role::Button);
4333
4334 element.element.interactivity.write_a11y_info(&mut node);
4335
4336 assert_eq!(node.description(), Some("Save the active document"));
4337 assert_eq!(node.keyboard_shortcut(), Some("Ctrl+S"));
4338 }
4339
4340 #[test]
4341 fn accessibility_id_is_written_to_accesskit() {
4342 let element = div()
4343 .id("buffer-font-size")
4344 .accessibility_id("settings.buffer-font-size");
4345 let mut node = accesskit::Node::new(accesskit::Role::SpinButton);
4346
4347 element.element.interactivity.write_a11y_info(&mut node);
4348
4349 assert_eq!(node.author_id(), Some("settings.buffer-font-size"));
4350 }
4351
4352 #[test]
4353 fn form_control_properties_are_written_to_accesskit() {
4354 let element = div()
4355 .id("email")
4356 .role(accesskit::Role::TextInput)
4357 .aria_value("alice@example.com")
4358 .aria_placeholder("name@example.com")
4359 .aria_disabled(true)
4360 .aria_required(true)
4361 .aria_invalid(accesskit::Invalid::True)
4362 .aria_modal(true);
4363 let mut node = accesskit::Node::new(accesskit::Role::TextInput);
4364
4365 element.element.interactivity.write_a11y_info(&mut node);
4366
4367 assert_eq!(node.value(), Some("alice@example.com"));
4368 assert_eq!(node.placeholder(), Some("name@example.com"));
4369 assert!(node.is_disabled());
4370 assert!(node.is_required());
4371 assert_eq!(node.invalid(), Some(accesskit::Invalid::True));
4372 assert!(node.is_modal());
4373 }
4374
4375 #[test]
4376 fn test_shadow_sm_preset_preserves_shadow_fields() {
4377 let mut element = div().shadow_sm();
4378 let shadows = element
4379 .style()
4380 .box_shadow
4381 .as_ref()
4382 .expect("shadow_sm should set box shadows");
4383
4384 assert_eq!(shadows.len(), 2);
4385 assert_eq!(shadows[0].color, hsla(0., 0., 0., 0.1));
4386 assert_eq!(shadows[0].offset, point(px(0.), px(1.)));
4387 assert_eq!(shadows[0].blur_radius, px(3.));
4388 assert_eq!(shadows[0].spread_radius, px(0.));
4389 assert!(!shadows[0].inset);
4390
4391 assert_eq!(shadows[1].color, hsla(0., 0., 0., 0.1));
4392 assert_eq!(shadows[1].offset, point(px(0.), px(1.)));
4393 assert_eq!(shadows[1].blur_radius, px(2.));
4394 assert_eq!(shadows[1].spread_radius, px(-1.));
4395 assert!(!shadows[1].inset);
4396 }
4397
4398 #[gpui::test]
4399 fn test_fractional_padding_does_not_make_a_fitting_container_scrollable(
4400 cx: &mut TestAppContext,
4401 ) {
4402 struct PaddedContainer {
4403 scroll_handle: ScrollHandle,
4404 }
4405
4406 impl Render for PaddedContainer {
4407 fn render(
4408 &mut self,
4409 _window: &mut Window,
4410 _cx: &mut Context<Self>,
4411 ) -> impl IntoElement {
4412 div().size_full().child(
4415 div()
4416 .id("container")
4417 .h(px(50.))
4418 .w(px(100.))
4419 .py(px(4.25))
4420 .overflow_y_scroll()
4421 .track_scroll(&self.scroll_handle)
4422 .child(div().w_full().h(px(42.))),
4423 )
4424 }
4425 }
4426
4427 let scroll_handle = ScrollHandle::new();
4428 let window: AnyWindowHandle = cx
4429 .add_window({
4430 let scroll_handle = scroll_handle.clone();
4431 move |_, _| PaddedContainer { scroll_handle }
4432 })
4433 .into();
4434 cx.update_window(window, |_, window, cx| window.draw(cx).clear())
4435 .unwrap();
4436
4437 assert_eq!(scroll_handle.max_offset().height, px(0.));
4438 }
4439
4440 struct ContentSizedGrid;
4441
4442 impl Render for ContentSizedGrid {
4443 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4444 let widths = [px(100.), px(200.), px(50.)];
4445 div().size_full().child(
4446 div()
4447 .w_full()
4448 .grid()
4449 .grid_cols_max_content(widths.len() as u16)
4450 .children(widths.into_iter().enumerate().map(|(index, width)| {
4451 div()
4452 .debug_selector(move || format!("cell-{index}"))
4453 .w(width)
4454 .h(px(10.))
4455 })),
4456 )
4457 }
4458 }
4459
4460 #[gpui::test]
4461 fn grid_cols_max_content_sizes_columns_to_their_content(cx: &mut TestAppContext) {
4462 let window = cx.add_window(|_, _| ContentSizedGrid);
4463 cx.update_window(window.into(), |_, window, cx| window.draw(cx).clear())
4464 .unwrap();
4465
4466 let mut bounds = |selector: &'static str| {
4467 cx.update_window(window.into(), |_, window, _| {
4468 window.rendered_frame.debug_bounds.get(selector).copied()
4469 })
4470 .unwrap()
4471 .unwrap_or_else(|| panic!("{selector} was not rendered"))
4472 };
4473
4474 assert_eq!(bounds("cell-0").origin.x, px(0.));
4475 assert_eq!(bounds("cell-0").size.width, px(100.));
4476 assert_eq!(bounds("cell-1").origin.x, px(100.));
4477 assert_eq!(bounds("cell-1").size.width, px(200.));
4478 assert_eq!(bounds("cell-2").origin.x, px(300.));
4479 assert_eq!(bounds("cell-2").size.width, px(50.));
4480 }
4481
4482 #[test]
4483 fn restrict_scroll_to_axis_sets_style_flag() {
4484 let mut element = div().id("axis-lock").restrict_scroll_to_axis();
4485 assert_eq!(
4486 element
4487 .element
4488 .interactivity
4489 .base_style
4490 .restrict_scroll_to_axis,
4491 Some(true)
4492 );
4493 }
4494
4495 struct AxisLockedScrollView {
4496 handle: ScrollHandle,
4497 }
4498
4499 impl Render for AxisLockedScrollView {
4500 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4501 div()
4502 .id("axis-lock-scroller")
4503 .size_full()
4504 .overflow_scroll()
4505 .restrict_scroll_to_axis()
4506 .track_scroll(&self.handle)
4507 .child(div().size(px(2000.)))
4508 }
4509 }
4510
4511 #[gpui::test]
4512 fn restrict_scroll_to_axis_locks_precise_gestures_to_start_axis(cx: &mut TestAppContext) {
4513 let handle = ScrollHandle::new();
4514 let (_, cx) = cx.add_window_view({
4515 let handle = handle.clone();
4516 move |_, _| AxisLockedScrollView { handle }
4517 });
4518 cx.simulate_resize(size(px(200.), px(200.)));
4519
4520 cx.update(|window, cx| {
4521 window.dispatch_event(
4522 ScrollEvent {
4523 position: point(px(50.), px(50.)),
4524 delta: ScrollDelta::Pixels(point(px(-20.), px(-4.))),
4525 touch_phase: crate::TouchPhase::Started,
4526 ..Default::default()
4527 }
4528 .to_platform_input(),
4529 cx,
4530 );
4531 });
4532 cx.run_until_parked();
4533 let after_start = handle.offset();
4534 assert!(after_start.x < px(0.), "horizontal start should scroll x");
4535 assert_eq!(
4536 after_start.y,
4537 px(0.),
4538 "sticky start axis should drop the weaker vertical component"
4539 );
4540
4541 cx.update(|window, cx| {
4542 window.dispatch_event(
4543 ScrollEvent {
4544 position: point(px(50.), px(50.)),
4545 delta: ScrollDelta::Pixels(point(px(-3.), px(-2.))),
4546 touch_phase: crate::TouchPhase::Moved,
4547 ..Default::default()
4548 }
4549 .to_platform_input(),
4550 cx,
4551 );
4552 });
4553 cx.run_until_parked();
4554 let after_continue = handle.offset();
4555 assert!(after_continue.x < after_start.x);
4556 assert_eq!(after_continue.y, px(0.));
4557
4558 cx.update(|window, cx| {
4559 window.dispatch_event(
4560 ScrollEvent {
4561 position: point(px(50.), px(50.)),
4562 delta: ScrollDelta::Pixels(point(px(-2.), px(-20.))),
4563 touch_phase: crate::TouchPhase::Moved,
4564 ..Default::default()
4565 }
4566 .to_platform_input(),
4567 cx,
4568 );
4569 });
4570 cx.run_until_parked();
4571 let after_unlock = handle.offset();
4572 assert!(
4573 after_unlock.y < px(0.),
4574 "a strong perpendicular delta should break the axis lock"
4575 );
4576 }
4577
4578 #[gpui::test]
4579 fn restrict_scroll_to_axis_style_flag_still_blocks_axis_remap(cx: &mut TestAppContext) {
4580 let handle = ScrollHandle::new();
4581 struct VerticalOnly {
4582 handle: ScrollHandle,
4583 }
4584 impl Render for VerticalOnly {
4585 fn render(
4586 &mut self,
4587 _window: &mut Window,
4588 _cx: &mut Context<Self>,
4589 ) -> impl IntoElement {
4590 let mut scroller = div()
4591 .id("vertical-only")
4592 .size_full()
4593 .overflow_y_scroll()
4594 .track_scroll(&self.handle)
4595 .child(div().h(px(2000.)).w_full());
4596 scroller.style().restrict_scroll_to_axis = Some(true);
4597 scroller
4598 }
4599 }
4600
4601 let (_, cx) = cx.add_window_view({
4602 let handle = handle.clone();
4603 move |_, _| VerticalOnly { handle }
4604 });
4605 cx.simulate_resize(size(px(200.), px(200.)));
4606
4607 cx.update(|window, cx| {
4608 window.dispatch_event(
4609 ScrollEvent {
4610 position: point(px(50.), px(50.)),
4611 delta: ScrollDelta::Pixels(point(px(-20.), px(0.))),
4612 ..Default::default()
4613 }
4614 .to_platform_input(),
4615 cx,
4616 );
4617 });
4618 cx.run_until_parked();
4619 assert_eq!(
4620 handle.offset(),
4621 point(px(0.), px(0.)),
4622 "existing restrict_scroll_to_axis callers must not have horizontal input remapped onto a vertical-only scroller"
4623 );
4624 }
4625}