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 = window.with_element_opacity(style.opacity, |window| {
2157 f(&style, scroll_offset, hitbox, window, cx)
2158 });
2159 (result, element_state)
2160 },
2161 )
2162 })
2163 },
2164 )
2165 }
2166
2167 fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
2168 self.hitbox_behavior != HitboxBehavior::Normal
2169 || self.window_control.is_some()
2170 || style.mouse_cursor.is_some()
2171 || self.group.is_some()
2172 || self.scroll_offset.is_some()
2173 || self.tracked_focus_handle.is_some()
2174 || self.hover_style.is_some()
2175 || self.group_hover_style.is_some()
2176 || self.hover_listener.is_some()
2177 || !self.mouse_up_listeners.is_empty()
2178 || !self.mouse_down_listeners.is_empty()
2179 || !self.mouse_move_listeners.is_empty()
2180 || !self.mouse_exit_listeners.is_empty()
2181 || !self.file_drop_exit_listeners.is_empty()
2182 || !self.click_listeners.is_empty()
2183 || !self.scroll_wheel_listeners.is_empty()
2184 || !self.pinch_listeners.is_empty()
2185 || self.drag_listener.is_some()
2186 || !self.drop_listeners.is_empty()
2187 || self.tooltip_builder.is_some()
2188 || window.is_inspector_picking(cx)
2189 }
2190
2191 fn clamp_scroll_position(
2192 &self,
2193 bounds: Bounds<Pixels>,
2194 style: &Style,
2195 window: &mut Window,
2196 _cx: &mut App,
2197 ) -> Point<Pixels> {
2198 fn round_to_two_decimals(pixels: Pixels) -> Pixels {
2199 const ROUNDING_FACTOR: f32 = 100.0;
2200 (pixels * ROUNDING_FACTOR).round() / ROUNDING_FACTOR
2201 }
2202
2203 if let Some(scroll_offset) = self.scroll_offset.as_ref() {
2204 let mut scroll_to_bottom = false;
2205 let mut tracked_scroll_handle = self
2206 .tracked_scroll_handle
2207 .as_ref()
2208 .map(|handle| handle.0.borrow_mut());
2209 if let Some(mut scroll_handle_state) = tracked_scroll_handle.as_deref_mut() {
2210 scroll_handle_state.overflow = style.overflow;
2211 scroll_to_bottom = mem::take(&mut scroll_handle_state.scroll_to_bottom);
2212 }
2213
2214 let rem_size = window.rem_size();
2215 let padding = style
2220 .padding
2221 .to_pixels(bounds.size.into(), rem_size)
2222 .map(|edge| window.pixel_snap(*edge));
2223 let padding_size = size(padding.left + padding.right, padding.top + padding.bottom);
2224 let padded_content_size = self.content_size + padding_size;
2231 let scroll_max = (padded_content_size - bounds.size)
2232 .map(round_to_two_decimals)
2233 .max(&Default::default());
2234 let mut scroll_offset = scroll_offset.borrow_mut();
2237
2238 scroll_offset.x = scroll_offset.x.clamp(-scroll_max.width, px(0.));
2239 if scroll_to_bottom {
2240 scroll_offset.y = -scroll_max.height;
2241 } else {
2242 scroll_offset.y = scroll_offset.y.clamp(-scroll_max.height, px(0.));
2243 }
2244
2245 if let Some(mut scroll_handle_state) = tracked_scroll_handle {
2246 scroll_handle_state.max_offset = scroll_max;
2247 scroll_handle_state.bounds = bounds;
2248 }
2249
2250 *scroll_offset
2251 } else {
2252 Point::default()
2253 }
2254 }
2255
2256 pub fn paint(
2265 &mut self,
2266 global_id: Option<&GlobalElementId>,
2267 _inspector_id: Option<&InspectorElementId>,
2268 bounds: Bounds<Pixels>,
2269 hitbox: Option<&Hitbox>,
2270 window: &mut Window,
2271 cx: &mut App,
2272 f: impl FnOnce(&Style, &mut Window, &mut App),
2273 ) {
2274 self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(window));
2275 window.with_optional_element_state::<InteractiveElementState, _>(
2276 global_id,
2277 |element_state, window| {
2278 let mut element_state =
2279 element_state.map(|element_state| element_state.unwrap_or_default());
2280
2281 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
2282
2283 #[cfg(any(test, all(debug_assertions, feature = "test-support")))]
2284 if let Some(debug_selector) = &self.debug_selector {
2285 window
2286 .next_frame
2287 .record_debug_bounds(debug_selector.clone(), bounds);
2288 }
2289
2290 self.paint_hover_group_handler(window, cx);
2291
2292 if !self.resize_listeners.is_empty() {
2293 if let Some(element_state) = element_state.as_mut() {
2294 let size_changed = element_state
2295 .prev_bounds
2296 .map(|prev| prev.size != bounds.size)
2297 .unwrap_or(true);
2298
2299 if size_changed {
2300 element_state.prev_bounds = Some(bounds);
2301 let resize_event = ResizeEvent {
2302 size: bounds.size,
2303 bounds,
2304 };
2305 for listener in &self.resize_listeners {
2306 listener(&resize_event, window, cx);
2307 }
2308 }
2309 }
2310 }
2311
2312 if style.visibility == Visibility::Hidden {
2313 return ((), element_state);
2314 }
2315
2316 let mut tab_group = None;
2317 if self.tab_group {
2318 tab_group = self.tab_index;
2319 }
2320 if let Some(focus_handle) = &self.tracked_focus_handle {
2321 window.next_frame.tab_stops.insert(focus_handle);
2322 }
2323
2324 window.with_element_opacity(style.opacity, |window| {
2325 style.paint(bounds, window, cx, |window: &mut Window, cx: &mut App| {
2326 window.with_text_style(style.text_style().cloned(), |window| {
2327 window.with_content_mask(
2328 style.overflow_mask(bounds, window.rem_size()),
2329 |window| {
2330 window.with_tab_group(tab_group, |window| {
2331 if let Some(hitbox) = hitbox {
2332 #[cfg(debug_assertions)]
2333 self.paint_debug_info(
2334 global_id, hitbox, &style, window, cx,
2335 );
2336
2337 if let Some(drag) = cx.active_drag.as_ref() {
2338 if let Some(mouse_cursor) = drag.cursor_style {
2339 window.set_window_cursor_style(mouse_cursor);
2340 }
2341 } else {
2342 if let Some(mouse_cursor) = style.mouse_cursor {
2343 window.set_cursor_style(mouse_cursor, hitbox);
2344 }
2345 }
2346
2347 if let Some(group) = self.group.clone() {
2348 GroupHitboxes::push(group, hitbox.id, cx);
2349 }
2350
2351 if let Some(area) = self.window_control {
2352 window.insert_window_control_hitbox(
2353 area,
2354 hitbox.clone(),
2355 );
2356 }
2357
2358 self.paint_mouse_listeners(
2359 hitbox,
2360 element_state.as_mut(),
2361 window,
2362 cx,
2363 );
2364 self.paint_scroll_listener(hitbox, &style, window, cx);
2365 }
2366
2367 self.paint_keyboard_listeners(window, cx);
2368
2369 if window.a11y.is_active()
2370 && let Some(global_id) = global_id
2371 && !self.a11y_action_listeners.is_empty()
2372 {
2373 let node_id = global_id.accesskit_node_id();
2374 for (action, listener) in
2375 self.a11y_action_listeners.drain(..)
2376 {
2377 window.on_a11y_action(node_id, action, listener);
2378 }
2379 }
2380
2381 f(&style, window, cx);
2382
2383 if let Some(_hitbox) = hitbox {
2384 #[cfg(any(feature = "inspector", debug_assertions))]
2385 window.insert_inspector_hitbox(
2386 _hitbox.id,
2387 _inspector_id,
2388 cx,
2389 );
2390
2391 if let Some(group) = self.group.as_ref() {
2392 GroupHitboxes::pop(group, cx);
2393 }
2394 }
2395 })
2396 },
2397 );
2398 });
2399 });
2400 });
2401
2402 ((), element_state)
2403 },
2404 );
2405 }
2406
2407 #[cfg(debug_assertions)]
2408 fn paint_debug_info(
2409 &self,
2410 global_id: Option<&GlobalElementId>,
2411 hitbox: &Hitbox,
2412 style: &Style,
2413 window: &mut Window,
2414 cx: &mut App,
2415 ) {
2416 use crate::{BorderStyle, TextAlign};
2417
2418 if let Some(global_id) = global_id.as_ref().filter(|_| {
2419 (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
2420 && hitbox.is_hovered(window)
2421 }) {
2422 const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
2423 let element_id = format!("{:?}", global_id);
2424 let str_len = element_id.len();
2425
2426 let render_debug_text = |window: &mut Window| {
2427 if let Some(text) = window
2428 .text_system()
2429 .shape_text(
2430 element_id.into(),
2431 FONT_SIZE,
2432 &[window.text_style().to_run(str_len)],
2433 None,
2434 None,
2435 )
2436 .ok()
2437 .and_then(|mut text| text.pop())
2438 {
2439 text.paint(hitbox.origin, FONT_SIZE, TextAlign::Left, None, window, cx)
2440 .ok();
2441
2442 let text_bounds = crate::Bounds {
2443 origin: hitbox.origin,
2444 size: text.size(FONT_SIZE),
2445 };
2446 if let Some(source_location) = self.source_location.filter(|_| {
2447 text_bounds.contains(&window.mouse_position())
2448 && window.modifiers().secondary(cx)
2449 }) {
2450 let secondary_held = window.modifiers().secondary(cx);
2451 window.on_key_event({
2452 move |e: &crate::ModifiersChangedEvent, _phase, window, cx| {
2453 if e.modifiers.secondary(cx) != secondary_held
2454 && text_bounds.contains(&window.mouse_position())
2455 {
2456 window.refresh();
2457 }
2458 }
2459 });
2460
2461 let was_hovered = hitbox.is_hovered(window);
2462 let current_view = window.current_view();
2463 window.on_mouse_event({
2464 let hitbox = hitbox.clone();
2465 move |_: &MouseMoveEvent, phase, window, cx| {
2466 if phase == DispatchPhase::Capture {
2467 let hovered = hitbox.is_hovered(window);
2468 if hovered != was_hovered {
2469 cx.notify(current_view)
2470 }
2471 }
2472 }
2473 });
2474
2475 window.on_mouse_event({
2476 let hitbox = hitbox.clone();
2477 let location = source_location;
2478 move |e: &crate::MouseDownEvent, phase, window, cx| {
2479 if text_bounds.contains(&e.position)
2480 && phase.capture()
2481 && hitbox.is_hovered(window)
2482 {
2483 cx.stop_propagation();
2484 let Ok(dir) = std::env::current_dir() else {
2485 return;
2486 };
2487
2488 eprintln!(
2489 "This element was created at:\n{}:{}:{}",
2490 dir.join(location.file()).to_string_lossy(),
2491 location.line(),
2492 location.column()
2493 );
2494 }
2495 }
2496 });
2497 window.paint_quad(crate::outline(
2498 crate::Bounds {
2499 origin: hitbox.origin
2500 + crate::point(crate::px(0.), FONT_SIZE - px(2.)),
2501 size: crate::Size {
2502 width: text_bounds.size.width,
2503 height: crate::px(1.),
2504 },
2505 },
2506 crate::red(),
2507 BorderStyle::default(),
2508 ))
2509 }
2510 }
2511 };
2512
2513 window.with_text_style(
2514 Some(crate::TextStyleRefinement {
2515 color: Some(crate::red()),
2516 line_height: Some(FONT_SIZE.into()),
2517 background_color: Some(crate::white()),
2518 ..Default::default()
2519 }),
2520 render_debug_text,
2521 )
2522 }
2523 }
2524
2525 fn paint_mouse_listeners(
2526 &mut self,
2527 hitbox: &Hitbox,
2528 element_state: Option<&mut InteractiveElementState>,
2529 window: &mut Window,
2530 cx: &mut App,
2531 ) {
2532 let is_focused = self
2533 .tracked_focus_handle
2534 .as_ref()
2535 .map(|handle| handle.is_focused(window))
2536 .unwrap_or(false);
2537
2538 if let Some(focus_handle) = self.tracked_focus_handle.clone() {
2542 let hitbox = hitbox.clone();
2543 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _| {
2544 if phase == DispatchPhase::Bubble
2545 && hitbox.is_hovered(window)
2546 && !window.default_prevented()
2547 {
2548 window.focus(&focus_handle);
2549 window.prevent_default();
2552 }
2553 });
2554 }
2555
2556 for listener in self.mouse_down_listeners.drain(..) {
2557 let hitbox = hitbox.clone();
2558 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
2559 listener(event, phase, &hitbox, window, cx);
2560 })
2561 }
2562
2563 for listener in self.mouse_up_listeners.drain(..) {
2564 let hitbox = hitbox.clone();
2565 window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
2566 listener(event, phase, &hitbox, window, cx);
2567 })
2568 }
2569
2570 for listener in self.mouse_move_listeners.drain(..) {
2571 let hitbox = hitbox.clone();
2572 window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
2573 listener(event, phase, &hitbox, window, cx);
2574 })
2575 }
2576
2577 for listener in self.mouse_exit_listeners.drain(..) {
2578 let hitbox = hitbox.clone();
2579 window.on_mouse_event(move |event: &MouseExitEvent, phase, window, cx| {
2580 listener(event, phase, &hitbox, window, cx);
2581 })
2582 }
2583
2584 for listener in self.file_drop_exit_listeners.drain(..) {
2585 let hitbox = hitbox.clone();
2586 window.on_mouse_event(move |event: &FileDropEvent, phase, window, cx| {
2587 listener(event, phase, &hitbox, window, cx);
2588 })
2589 }
2590
2591 for listener in self.scroll_wheel_listeners.drain(..) {
2592 let hitbox = hitbox.clone();
2593 window.on_mouse_event(move |event: &ScrollEvent, phase, window, cx| {
2594 listener(event, phase, &hitbox, window, cx);
2595 })
2596 }
2597
2598 for listener in self.pinch_listeners.drain(..) {
2599 let hitbox = hitbox.clone();
2600 window.on_mouse_event(move |event: &PinchEvent, phase, window, cx| {
2601 listener(event, phase, &hitbox, window, cx);
2602 })
2603 }
2604
2605 if self.hover_style.is_some()
2606 || self.base_style.mouse_cursor.is_some()
2607 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
2608 {
2609 let hitbox = hitbox.clone();
2610 let was_hovered = hitbox.is_hovered(window);
2611 let current_view = window.current_view();
2612 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2613 let hovered = hitbox.is_hovered(window);
2614 if phase == DispatchPhase::Capture && hovered != was_hovered {
2615 cx.notify(current_view);
2616 }
2617 });
2618 }
2619 let drag_cursor_style = self.base_style.as_ref().mouse_cursor;
2620
2621 let mut drag_listener = mem::take(&mut self.drag_listener);
2622 let drop_listeners = mem::take(&mut self.drop_listeners);
2623 let click_listeners = mem::take(&mut self.click_listeners);
2624 let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
2625
2626 if !drop_listeners.is_empty() {
2627 let hitbox = hitbox.clone();
2628 window.on_mouse_event({
2629 move |_: &MouseUpEvent, phase, window, cx| {
2630 if let Some(drag) = &cx.active_drag
2631 && phase == DispatchPhase::Bubble
2632 && hitbox.is_hovered(window)
2633 {
2634 let drag_state_type = drag.value.as_ref().type_id();
2635 for (drop_state_type, listener) in &drop_listeners {
2636 if *drop_state_type == drag_state_type {
2637 let drag = cx
2638 .active_drag
2639 .take()
2640 .expect("checked for type drag state type above");
2641
2642 let mut can_drop = true;
2643 if let Some(predicate) = &can_drop_predicate {
2644 can_drop = predicate(drag.value.as_ref(), window, cx);
2645 }
2646
2647 if can_drop {
2648 listener(drag.value.as_ref(), window, cx);
2649 window.refresh();
2650 cx.stop_propagation();
2651 }
2652 }
2653 }
2654 }
2655 }
2656 });
2657 }
2658
2659 if let Some(element_state) = element_state {
2660 if !click_listeners.is_empty() || drag_listener.is_some() {
2661 let pending_mouse_down = element_state
2662 .pending_mouse_down
2663 .get_or_insert_with(Default::default)
2664 .clone();
2665
2666 let clicked_state = element_state
2667 .clicked_state
2668 .get_or_insert_with(Default::default)
2669 .clone();
2670
2671 window.on_mouse_event({
2672 let pending_mouse_down = pending_mouse_down.clone();
2673 let hitbox = hitbox.clone();
2674 move |event: &MouseDownEvent, phase, window, _cx| {
2675 if phase == DispatchPhase::Bubble
2676 && event.button == MouseButton::Left
2677 && hitbox.is_hovered(window)
2678 {
2679 *pending_mouse_down.borrow_mut() = Some(event.clone());
2680 window.refresh();
2681 }
2682 }
2683 });
2684
2685 window.on_mouse_event({
2686 let pending_mouse_down = pending_mouse_down.clone();
2687 let hitbox = hitbox.clone();
2688 move |event: &MouseMoveEvent, phase, window, cx| {
2689 if phase == DispatchPhase::Capture {
2690 return;
2691 }
2692
2693 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2694 if let Some(mouse_down) = pending_mouse_down.clone()
2695 && !cx.has_active_drag()
2696 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
2697 && let Some((drag_value, drag_listener)) = drag_listener.take()
2698 {
2699 *clicked_state.borrow_mut() = ElementClickedState::default();
2700 let cursor_offset = event.position - hitbox.origin;
2701 let drag =
2702 (drag_listener)(drag_value.as_ref(), cursor_offset, window, cx);
2703 cx.active_drag = Some(AnyDrag {
2704 view: drag,
2705 value: drag_value,
2706 cursor_offset,
2707 cursor_style: drag_cursor_style,
2708 });
2709 pending_mouse_down.take();
2710 window.refresh();
2711 cx.stop_propagation();
2712 }
2713 }
2714 });
2715
2716 if is_focused {
2717 window.on_key_event({
2719 let click_listeners = click_listeners.clone();
2720 let hitbox = hitbox.clone();
2721 move |event: &KeyUpEvent, phase, window, cx| {
2722 if phase.bubble() && !window.default_prevented() {
2723 let stroke = &event.keystroke;
2724 let keyboard_button = if stroke.key.eq("enter") {
2725 Some(KeyboardButton::Enter)
2726 } else if stroke.key.eq("space") {
2727 Some(KeyboardButton::Space)
2728 } else {
2729 None
2730 };
2731
2732 if let Some(button) = keyboard_button
2733 && !stroke.modifiers.modified()
2734 {
2735 let click_event = ClickEvent::Keyboard(KeyboardClickEvent {
2736 button,
2737 bounds: hitbox.bounds,
2738 });
2739
2740 for listener in &click_listeners {
2741 listener(&click_event, window, cx);
2742 }
2743 }
2744 }
2745 }
2746 });
2747 }
2748
2749 window.on_mouse_event({
2750 let mut captured_mouse_down = None;
2751 let hitbox = hitbox.clone();
2752 move |event: &MouseUpEvent, phase, window, cx| match phase {
2753 DispatchPhase::Capture => {
2757 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2758 if pending_mouse_down.is_some() && hitbox.is_hovered(window) {
2759 captured_mouse_down = pending_mouse_down.take();
2760 window.refresh();
2761 } else if pending_mouse_down.is_some() {
2762 pending_mouse_down.take();
2768 window.refresh();
2769 }
2770 }
2771 DispatchPhase::Bubble => {
2773 if let Some(mouse_down) = captured_mouse_down.take() {
2774 let mouse_click = ClickEvent::Mouse(MouseClickEvent {
2775 down: mouse_down,
2776 up: event.clone(),
2777 });
2778 for listener in &click_listeners {
2779 listener(&mouse_click, window, cx);
2780 }
2781 }
2782 }
2783 }
2784 });
2785 }
2786
2787 if let Some(hover_listener) = self.hover_listener.take() {
2788 let was_hovered = element_state
2789 .hover_state
2790 .get_or_insert_with(Default::default)
2791 .clone();
2792 let has_mouse_down = element_state
2793 .pending_mouse_down
2794 .get_or_insert_with(Default::default)
2795 .clone();
2796 let hover_listener = Rc::new(hover_listener);
2797 let hover_listener_state = was_hovered.clone();
2798 let update_hover = move |is_hovered: bool, window: &mut Window, cx: &mut App| {
2799 let mut was_hovered = hover_listener_state.borrow_mut();
2800 if is_hovered != *was_hovered {
2801 *was_hovered = is_hovered;
2802 drop(was_hovered);
2803 hover_listener(&is_hovered, window, cx);
2804 }
2805 };
2806 let hover_listener_mode = self.hover_listener_mode;
2807
2808 if has_mouse_down.borrow().is_none() {
2809 let is_hovered =
2810 !cx.has_active_drag() && hover_listener_mode.is_hovered(hitbox, window);
2811 if is_hovered != *was_hovered.borrow() {
2812 let update_hover = update_hover.clone();
2813 window.defer(cx, move |window, cx| {
2814 update_hover(is_hovered, window, cx);
2815 });
2816 }
2817 }
2818
2819 window.on_mouse_event({
2820 let update_hover = update_hover.clone();
2821 let hitbox = hitbox.clone();
2822 move |_: &MouseMoveEvent, phase, window, cx| {
2823 if phase == DispatchPhase::Bubble {
2824 let is_hovered = has_mouse_down.borrow().is_none()
2825 && !cx.has_active_drag()
2826 && hover_listener_mode.is_hovered(&hitbox, window);
2827 update_hover(is_hovered, window, cx);
2828 }
2829 }
2830 });
2831
2832 window.on_mouse_event(move |_: &MouseExitEvent, phase, window, cx| {
2835 if phase == DispatchPhase::Bubble {
2836 update_hover(false, window, cx);
2837 }
2838 });
2839 }
2840
2841 if let Some(tooltip_builder) = self.tooltip_builder.take() {
2842 let active_tooltip = element_state
2843 .active_tooltip
2844 .get_or_insert_with(Default::default)
2845 .clone();
2846 let pending_mouse_down = element_state
2847 .pending_mouse_down
2848 .get_or_insert_with(Default::default)
2849 .clone();
2850
2851 let tooltip_is_hoverable = tooltip_builder.hoverable;
2852 let build_tooltip = Rc::new(move |window: &mut Window, cx: &mut App| {
2853 Some(((tooltip_builder.build)(window, cx), tooltip_is_hoverable))
2854 });
2855 let check_is_hovered_during_prepaint = Rc::new({
2857 let pending_mouse_down = pending_mouse_down.clone();
2858 let source_bounds = hitbox.bounds;
2859 move |window: &Window| {
2860 pending_mouse_down.borrow().is_none()
2861 && source_bounds.contains(&window.mouse_position())
2862 }
2863 });
2864 let check_is_hovered = Rc::new({
2865 let hitbox = hitbox.clone();
2866 move |window: &Window| {
2867 pending_mouse_down.borrow().is_none() && hitbox.is_hovered(window)
2868 }
2869 });
2870 register_tooltip_mouse_handlers(
2871 &active_tooltip,
2872 self.tooltip_id,
2873 build_tooltip,
2874 check_is_hovered,
2875 check_is_hovered_during_prepaint,
2876 self.tooltip_show_delay,
2877 window,
2878 );
2879 }
2880
2881 let active_state = element_state
2882 .clicked_state
2883 .get_or_insert_with(Default::default)
2884 .clone();
2885 if active_state.borrow().is_clicked() {
2886 window.on_mouse_event(move |_: &MouseUpEvent, phase, window, _cx| {
2887 if phase == DispatchPhase::Capture {
2888 *active_state.borrow_mut() = ElementClickedState::default();
2889 window.refresh();
2890 }
2891 });
2892 } else {
2893 let active_group_hitbox = self
2894 .group_active_style
2895 .as_ref()
2896 .and_then(|group_active| GroupHitboxes::get(&group_active.group, cx));
2897 let hitbox = hitbox.clone();
2898 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _cx| {
2899 if phase == DispatchPhase::Bubble && !window.default_prevented() {
2900 let group_hovered = active_group_hitbox
2901 .is_some_and(|group_hitbox_id| group_hitbox_id.is_hovered(window));
2902 let element_hovered = hitbox.is_hovered(window);
2903 if group_hovered || element_hovered {
2904 *active_state.borrow_mut() = ElementClickedState {
2905 group: group_hovered,
2906 element: element_hovered,
2907 };
2908 window.refresh();
2909 }
2910 }
2911 });
2912 }
2913 }
2914 }
2915
2916 fn paint_keyboard_listeners(&mut self, window: &mut Window, _cx: &mut App) {
2917 let key_down_listeners = mem::take(&mut self.key_down_listeners);
2918 let key_up_listeners = mem::take(&mut self.key_up_listeners);
2919 let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners);
2920 let action_listeners = mem::take(&mut self.action_listeners);
2921 if let Some(context) = self.key_context.clone() {
2922 window.set_key_context(context);
2923 }
2924
2925 for listener in key_down_listeners {
2926 window.on_key_event(move |event: &KeyDownEvent, phase, window, cx| {
2927 listener(event, phase, window, cx);
2928 })
2929 }
2930
2931 for listener in key_up_listeners {
2932 window.on_key_event(move |event: &KeyUpEvent, phase, window, cx| {
2933 listener(event, phase, window, cx);
2934 })
2935 }
2936
2937 for listener in modifiers_changed_listeners {
2938 window.on_modifiers_changed(move |event: &ModifiersChangedEvent, window, cx| {
2939 listener(event, window, cx);
2940 })
2941 }
2942
2943 for (action_type, listener) in action_listeners {
2944 window.on_action(action_type, listener)
2945 }
2946 }
2947
2948 fn paint_hover_group_handler(&self, window: &mut Window, cx: &mut App) {
2949 let group_hitbox = self
2950 .group_hover_style
2951 .as_ref()
2952 .and_then(|group_hover| GroupHitboxes::get(&group_hover.group, cx));
2953
2954 if let Some(group_hitbox) = group_hitbox {
2955 let was_hovered = group_hitbox.is_hovered(window);
2956 let current_view = window.current_view();
2957 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2958 let hovered = group_hitbox.is_hovered(window);
2959 if phase == DispatchPhase::Capture && hovered != was_hovered {
2960 cx.notify(current_view);
2961 }
2962 });
2963 }
2964 }
2965
2966 fn paint_scroll_listener(
2967 &self,
2968 hitbox: &Hitbox,
2969 style: &Style,
2970 window: &mut Window,
2971 _cx: &mut App,
2972 ) {
2973 if let Some(scroll_offset) = self.scroll_offset.clone() {
2974 let ongoing_scroll = self.ongoing_scroll.clone();
2975 let overflow = style.overflow;
2976 let allow_concurrent_scroll = style.allow_concurrent_scroll;
2977 let restrict_scroll_to_axis = style.restrict_scroll_to_axis;
2978 let line_height = window.line_height();
2979 let hitbox = hitbox.clone();
2980 let current_view = window.current_view();
2981 window.on_mouse_event(move |event: &ScrollEvent, phase, window, cx| {
2982 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
2983 let mut scroll_offset = scroll_offset.borrow_mut();
2984 let old_scroll_offset = *scroll_offset;
2985 let mut delta = event.delta.pixel_delta(line_height);
2986
2987 if restrict_scroll_to_axis
2988 && event.delta.precise()
2989 && let Some(ongoing_scroll) = &ongoing_scroll
2990 {
2991 ongoing_scroll
2992 .borrow_mut()
2993 .filter(&mut delta, event.touch_phase);
2994 }
2995
2996 let mut delta_x = match overflow.x {
2997 Overflow::Scroll if !delta.x.is_zero() => delta.x,
2998 Overflow::Scroll
2999 if !restrict_scroll_to_axis && overflow.y != Overflow::Scroll =>
3000 {
3001 delta.y
3002 }
3003 _ => Pixels::ZERO,
3004 };
3005 let mut delta_y = match overflow.y {
3006 Overflow::Scroll if !delta.y.is_zero() => delta.y,
3007 Overflow::Scroll
3008 if !restrict_scroll_to_axis && overflow.x != Overflow::Scroll =>
3009 {
3010 delta.x
3011 }
3012 _ => Pixels::ZERO,
3013 };
3014 if !allow_concurrent_scroll && !delta_x.is_zero() && !delta_y.is_zero() {
3015 if delta_x.abs() > delta_y.abs() {
3016 delta_y = Pixels::ZERO;
3017 } else {
3018 delta_x = Pixels::ZERO;
3019 }
3020 }
3021 scroll_offset.y += delta_y;
3022 scroll_offset.x += delta_x;
3023 if *scroll_offset != old_scroll_offset {
3024 cx.notify(current_view);
3025 }
3026 }
3027 });
3028 }
3029 }
3030
3031 pub fn compute_style(
3033 &self,
3034 global_id: Option<&GlobalElementId>,
3035 hitbox: Option<&Hitbox>,
3036 window: &mut Window,
3037 cx: &mut App,
3038 ) -> Style {
3039 window.with_optional_element_state(global_id, |element_state, window| {
3040 let mut element_state =
3041 element_state.map(|element_state| element_state.unwrap_or_default());
3042 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
3043 (style, element_state)
3044 })
3045 }
3046
3047 fn compute_style_internal(
3049 &self,
3050 hitbox: Option<&Hitbox>,
3051 element_state: Option<&mut InteractiveElementState>,
3052 window: &mut Window,
3053 cx: &mut App,
3054 ) -> Style {
3055 let mut style = Style::default();
3056 style.refine(&self.base_style);
3057
3058 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
3059 if let Some(in_focus_style) = self.in_focus_style.as_ref()
3060 && focus_handle.within_focused(window, cx)
3061 {
3062 style.refine(in_focus_style);
3063 }
3064
3065 if let Some(focus_style) = self.focus_style.as_ref()
3066 && focus_handle.is_focused(window)
3067 {
3068 style.refine(focus_style);
3069 }
3070 }
3071
3072 if let Some(hitbox) = hitbox {
3073 if !cx.has_active_drag() {
3074 if let Some(group_hover) = self.group_hover_style.as_ref()
3075 && let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx)
3076 && group_hitbox_id.is_hovered(window)
3077 {
3078 style.refine(&group_hover.style);
3079 }
3080
3081 if let Some(hover_style) = self.hover_style.as_ref()
3082 && hitbox.is_hovered(window)
3083 {
3084 style.refine(hover_style);
3085 }
3086 }
3087
3088 if let Some(drag) = cx.active_drag.take() {
3089 let mut can_drop = true;
3090 if let Some(can_drop_predicate) = &self.can_drop_predicate {
3091 can_drop = can_drop_predicate(drag.value.as_ref(), window, cx);
3092 }
3093
3094 if can_drop {
3095 for (state_type, group_drag_style) in &self.group_drag_over_styles {
3096 if let Some(group_hitbox_id) =
3097 GroupHitboxes::get(&group_drag_style.group, cx)
3098 && *state_type == drag.value.as_ref().type_id()
3099 && group_hitbox_id.is_hovered(window)
3100 {
3101 style.refine(&group_drag_style.style);
3102 }
3103 }
3104
3105 for (state_type, build_drag_over_style) in &self.drag_over_styles {
3106 if *state_type == drag.value.as_ref().type_id() && hitbox.is_hovered(window)
3107 {
3108 style.refine(&build_drag_over_style(drag.value.as_ref(), window, cx));
3109 }
3110 }
3111 }
3112
3113 style.mouse_cursor = drag.cursor_style;
3114 cx.active_drag = Some(drag);
3115 }
3116 }
3117
3118 if let Some(element_state) = element_state {
3119 let clicked_state = element_state
3120 .clicked_state
3121 .get_or_insert_with(Default::default)
3122 .borrow();
3123 if clicked_state.group
3124 && let Some(group) = self.group_active_style.as_ref()
3125 {
3126 style.refine(&group.style)
3127 }
3128
3129 if let Some(active_style) = self.active_style.as_ref()
3130 && clicked_state.element
3131 {
3132 style.refine(active_style)
3133 }
3134 }
3135
3136 style
3137 }
3138
3139 pub(crate) fn write_a11y_info(&self, node: &mut accesskit::Node) {
3140 if let Some(id) = &self.accessibility_id {
3141 node.set_author_id(id.to_string());
3142 }
3143 if let Some(label) = &self.aria_label {
3144 node.set_label(label.to_string());
3145 }
3146 if let Some(description) = &self.aria_description {
3147 node.set_description(description.to_string());
3148 }
3149 if let Some(value) = &self.aria_value {
3150 node.set_value(value.to_string());
3151 }
3152 if let Some(placeholder) = &self.aria_placeholder {
3153 node.set_placeholder(placeholder.to_string());
3154 }
3155 if self.aria_disabled {
3156 node.set_disabled();
3157 }
3158 if self.aria_required {
3159 node.set_required();
3160 }
3161 if let Some(invalid) = self.aria_invalid {
3162 node.set_invalid(invalid);
3163 }
3164 if self.aria_modal {
3165 node.set_modal();
3166 }
3167 if let Some(keyshortcuts) = &self.aria_keyshortcuts {
3168 node.set_keyboard_shortcut(keyshortcuts.to_string());
3169 }
3170 if let Some(selected) = self.aria_selected {
3171 node.set_selected(selected);
3172 }
3173 if let Some(expanded) = self.aria_expanded {
3174 node.set_expanded(expanded);
3175 }
3176 if let Some(toggled) = self.aria_toggled {
3177 node.set_toggled(toggled);
3178 }
3179 if let Some(value) = self.aria_numeric_value {
3180 node.set_numeric_value(value);
3181 }
3182 if let Some(value) = self.aria_min_numeric_value {
3183 node.set_min_numeric_value(value);
3184 }
3185 if let Some(value) = self.aria_max_numeric_value {
3186 node.set_max_numeric_value(value);
3187 }
3188 if let Some(orientation) = self.aria_orientation {
3189 node.set_orientation(orientation);
3190 }
3191 if let Some(level) = self.aria_level {
3192 node.set_level(level);
3193 }
3194 if let Some(position) = self.aria_position_in_set {
3195 node.set_position_in_set(position);
3196 }
3197 if let Some(size) = self.aria_size_of_set {
3198 node.set_size_of_set(size);
3199 }
3200 if let Some(index) = self.aria_row_index {
3201 node.set_row_index(index);
3202 }
3203 if let Some(index) = self.aria_column_index {
3204 node.set_column_index(index);
3205 }
3206 if let Some(count) = self.aria_row_count {
3207 node.set_row_count(count);
3208 }
3209 if let Some(count) = self.aria_column_count {
3210 node.set_column_count(count);
3211 }
3212 if !self.click_listeners.is_empty() {
3213 node.add_action(accesskit::Action::Click);
3214 }
3215 if self.tracked_focus_handle.is_some() || self.focusable {
3216 node.add_action(accesskit::Action::Focus);
3217 }
3218 for (action, _) in &self.a11y_action_listeners {
3219 node.add_action(*action);
3220 }
3221 }
3222}
3223
3224#[derive(Default)]
3227pub struct InteractiveElementState {
3228 pub(crate) focus_handle: Option<FocusHandle>,
3229 pub(crate) clicked_state: Option<Rc<RefCell<ElementClickedState>>>,
3230 pub(crate) hover_state: Option<Rc<RefCell<bool>>>,
3231 pub(crate) pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
3232 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
3233 ongoing_scroll: Option<Rc<RefCell<OngoingScroll>>>,
3234 pub(crate) active_tooltip: Option<Rc<RefCell<Option<ActiveTooltip>>>>,
3235 pub(crate) prev_bounds: Option<Bounds<Pixels>>,
3236}
3237
3238#[derive(Copy, Clone, Default, Eq, PartialEq)]
3240pub struct ElementClickedState {
3241 pub group: bool,
3243
3244 pub element: bool,
3246}
3247
3248impl ElementClickedState {
3249 fn is_clicked(&self) -> bool {
3250 self.group || self.element
3251 }
3252}
3253
3254pub(crate) enum ActiveTooltip {
3255 WaitingForShow { _task: Task<()> },
3257 Visible {
3259 tooltip: AnyTooltip,
3260 is_hoverable: bool,
3261 },
3262 WaitingForHide {
3265 tooltip: AnyTooltip,
3266 _task: Task<()>,
3267 },
3268}
3269
3270pub(crate) fn clear_active_tooltip(
3271 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3272 window: &mut Window,
3273) {
3274 match active_tooltip.borrow_mut().take() {
3275 None => {}
3276 Some(ActiveTooltip::WaitingForShow { .. }) => {}
3277 Some(ActiveTooltip::Visible { .. }) => window.refresh(),
3278 Some(ActiveTooltip::WaitingForHide { .. }) => window.refresh(),
3279 }
3280}
3281
3282pub(crate) fn clear_active_tooltip_if_not_hoverable(
3283 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3284 window: &mut Window,
3285) {
3286 let should_clear = match active_tooltip.borrow().as_ref() {
3287 None => false,
3288 Some(ActiveTooltip::WaitingForShow { .. }) => false,
3289 Some(ActiveTooltip::Visible { is_hoverable, .. }) => !is_hoverable,
3290 Some(ActiveTooltip::WaitingForHide { .. }) => false,
3291 };
3292 if should_clear {
3293 active_tooltip.borrow_mut().take();
3294 window.refresh();
3295 }
3296}
3297
3298pub(crate) fn set_tooltip_on_window(
3299 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3300 window: &mut Window,
3301) -> Option<TooltipId> {
3302 let tooltip = match active_tooltip.borrow().as_ref() {
3303 None => return None,
3304 Some(ActiveTooltip::WaitingForShow { .. }) => return None,
3305 Some(ActiveTooltip::Visible { tooltip, .. }) => tooltip.clone(),
3306 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => tooltip.clone(),
3307 };
3308 Some(window.set_tooltip(tooltip))
3309}
3310
3311pub(crate) fn register_tooltip_mouse_handlers(
3312 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3313 tooltip_id: Option<TooltipId>,
3314 build_tooltip: Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3315 check_is_hovered: Rc<dyn Fn(&Window) -> bool>,
3316 check_is_hovered_during_prepaint: Rc<dyn Fn(&Window) -> bool>,
3317 show_delay: Option<Duration>,
3318 window: &mut Window,
3319) {
3320 let current_view = window.current_view();
3321 let show_delay = show_delay.unwrap_or(DEFAULT_TOOLTIP_SHOW_DELAY);
3322
3323 window.on_mouse_event({
3324 let active_tooltip = active_tooltip.clone();
3325 let build_tooltip = build_tooltip.clone();
3326 let check_is_hovered = check_is_hovered.clone();
3327 move |_: &MouseMoveEvent, phase, window, cx| {
3328 handle_tooltip_mouse_move(
3329 &active_tooltip,
3330 &build_tooltip,
3331 &check_is_hovered,
3332 &check_is_hovered_during_prepaint,
3333 tooltip_id,
3334 current_view,
3335 phase,
3336 show_delay,
3337 window,
3338 cx,
3339 )
3340 }
3341 });
3342
3343 window.on_mouse_event({
3344 let active_tooltip = active_tooltip.clone();
3345 move |_: &MouseDownEvent, _phase, window: &mut Window, _cx| {
3346 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3347 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3348 }
3349 }
3350 });
3351
3352 window.on_mouse_event({
3353 let active_tooltip = active_tooltip.clone();
3354 move |_: &ScrollEvent, _phase, window: &mut Window, _cx| {
3355 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3356 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3357 }
3358 }
3359 });
3360}
3361
3362fn handle_tooltip_mouse_move(
3374 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3375 build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3376 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3377 check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
3378 tooltip_id: Option<TooltipId>,
3379 current_view: EntityId,
3380 phase: DispatchPhase,
3381 show_delay: Duration,
3382 window: &mut Window,
3383 cx: &mut App,
3384) {
3385 enum Action {
3388 None,
3389 CancelShow,
3390 ScheduleShow,
3391 CheckVisible,
3392 }
3393
3394 let action = match active_tooltip.borrow().as_ref() {
3395 None => {
3396 let is_hovered = check_is_hovered(window);
3397 if is_hovered && phase.bubble() {
3398 Action::ScheduleShow
3399 } else {
3400 Action::None
3401 }
3402 }
3403 Some(ActiveTooltip::WaitingForShow { .. }) => {
3404 let is_hovered = check_is_hovered(window);
3405 if is_hovered {
3406 Action::None
3407 } else {
3408 Action::CancelShow
3409 }
3410 }
3411 Some(ActiveTooltip::Visible { is_hoverable, .. }) => {
3412 if phase.capture()
3413 && !check_is_hovered(window)
3414 && (!*is_hoverable
3415 || !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3416 {
3417 Action::CheckVisible
3418 } else {
3419 Action::None
3420 }
3421 }
3422 Some(ActiveTooltip::WaitingForHide { .. }) => {
3423 if phase.capture()
3424 && (check_is_hovered(window)
3425 || tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3426 {
3427 Action::CheckVisible
3428 } else {
3429 Action::None
3430 }
3431 }
3432 };
3433
3434 match action {
3435 Action::None => {}
3436 Action::CancelShow => {
3437 active_tooltip.borrow_mut().take();
3439 }
3440 Action::ScheduleShow => {
3441 let delayed_show_task = window.spawn(cx, {
3442 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3443 let build_tooltip = build_tooltip.clone();
3444 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3445 async move |cx| {
3446 cx.background_executor().timer(show_delay).await;
3447 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3448 return;
3449 };
3450 cx.update(|window, cx| {
3451 let new_tooltip =
3452 build_tooltip(window, cx).map(|(view, tooltip_is_hoverable)| {
3453 let weak_active_tooltip = Rc::downgrade(&active_tooltip);
3454 ActiveTooltip::Visible {
3455 tooltip: AnyTooltip {
3456 view,
3457 mouse_position: window.mouse_position(),
3458 check_visible_and_update: Rc::new(
3459 move |tooltip_bounds, window, cx| {
3460 let Some(active_tooltip) =
3461 weak_active_tooltip.upgrade()
3462 else {
3463 return false;
3464 };
3465 handle_tooltip_check_visible_and_update(
3466 &active_tooltip,
3467 tooltip_is_hoverable,
3468 &check_is_hovered_during_prepaint,
3469 tooltip_bounds,
3470 window,
3471 cx,
3472 )
3473 },
3474 ),
3475 },
3476 is_hoverable: tooltip_is_hoverable,
3477 }
3478 });
3479 *active_tooltip.borrow_mut() = new_tooltip;
3480 window.refresh();
3481 })
3482 .ok();
3483 }
3484 });
3485 active_tooltip
3486 .borrow_mut()
3487 .replace(ActiveTooltip::WaitingForShow {
3488 _task: delayed_show_task,
3489 });
3490 }
3491 Action::CheckVisible => cx.notify(current_view),
3492 }
3493}
3494
3495fn handle_tooltip_check_visible_and_update(
3499 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3500 tooltip_is_hoverable: bool,
3501 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3502 tooltip_bounds: Bounds<Pixels>,
3503 window: &mut Window,
3504 cx: &mut App,
3505) -> bool {
3506 enum Action {
3509 None,
3510 Hide,
3511 ScheduleHide(AnyTooltip),
3512 CancelHide(AnyTooltip),
3513 }
3514
3515 let is_hovered = check_is_hovered(window)
3516 || (tooltip_is_hoverable && tooltip_bounds.contains(&window.mouse_position()));
3517 let action = match active_tooltip.borrow().as_ref() {
3518 Some(ActiveTooltip::Visible { tooltip, .. }) => {
3519 if is_hovered {
3520 Action::None
3521 } else {
3522 if tooltip_is_hoverable {
3523 Action::ScheduleHide(tooltip.clone())
3524 } else {
3525 Action::Hide
3526 }
3527 }
3528 }
3529 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => {
3530 if is_hovered {
3531 Action::CancelHide(tooltip.clone())
3532 } else {
3533 Action::None
3534 }
3535 }
3536 None | Some(ActiveTooltip::WaitingForShow { .. }) => Action::None,
3537 };
3538
3539 match action {
3540 Action::None => {}
3541 Action::Hide => clear_active_tooltip(active_tooltip, window),
3542 Action::ScheduleHide(tooltip) => {
3543 let delayed_hide_task = window.spawn(cx, {
3544 let active_tooltip = active_tooltip.clone();
3545 async move |cx| {
3546 cx.background_executor()
3547 .timer(HOVERABLE_TOOLTIP_HIDE_DELAY)
3548 .await;
3549 if active_tooltip.borrow_mut().take().is_some() {
3550 cx.update(|window, _cx| window.refresh()).ok();
3551 }
3552 }
3553 });
3554 active_tooltip
3555 .borrow_mut()
3556 .replace(ActiveTooltip::WaitingForHide {
3557 tooltip,
3558 _task: delayed_hide_task,
3559 });
3560 }
3561 Action::CancelHide(tooltip) => {
3562 active_tooltip.borrow_mut().replace(ActiveTooltip::Visible {
3564 tooltip,
3565 is_hoverable: true,
3566 });
3567 }
3568 }
3569
3570 active_tooltip.borrow().is_some()
3571}
3572
3573#[derive(Default)]
3574pub(crate) struct GroupHitboxes(HashMap<SharedString, SmallVec<[HitboxId; 1]>>);
3575
3576impl Global for GroupHitboxes {}
3577
3578impl GroupHitboxes {
3579 pub fn get(name: &SharedString, cx: &mut App) -> Option<HitboxId> {
3580 cx.default_global::<Self>()
3581 .0
3582 .get(name)
3583 .and_then(|bounds_stack| bounds_stack.last())
3584 .cloned()
3585 }
3586
3587 pub fn push(name: SharedString, hitbox_id: HitboxId, cx: &mut App) {
3588 cx.default_global::<Self>()
3589 .0
3590 .entry(name)
3591 .or_default()
3592 .push(hitbox_id);
3593 }
3594
3595 pub fn pop(name: &SharedString, cx: &mut App) {
3596 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
3597 }
3598}
3599
3600pub struct Stateful<E> {
3602 pub(crate) element: E,
3603}
3604
3605impl<E> Styled for Stateful<E>
3606where
3607 E: Styled,
3608{
3609 fn style(&mut self) -> &mut StyleRefinement {
3610 self.element.style()
3611 }
3612}
3613
3614impl<E> StatefulInteractiveElement for Stateful<E>
3615where
3616 E: Element,
3617 Self: InteractiveElement,
3618{
3619}
3620
3621impl<E> InteractiveElement for Stateful<E>
3622where
3623 E: InteractiveElement,
3624{
3625 fn interactivity(&mut self) -> &mut Interactivity {
3626 self.element.interactivity()
3627 }
3628}
3629
3630impl<E> Element for Stateful<E>
3631where
3632 E: Element,
3633{
3634 type RequestLayoutState = E::RequestLayoutState;
3635 type PrepaintState = E::PrepaintState;
3636
3637 fn id(&self) -> Option<ElementId> {
3638 self.element.id()
3639 }
3640
3641 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
3642 self.element.source_location()
3643 }
3644
3645 fn a11y_role(&self) -> Option<accesskit::Role> {
3646 self.element.a11y_role()
3647 }
3648
3649 fn write_a11y_info(&self, node: &mut accesskit::Node) {
3650 self.element.write_a11y_info(node);
3651 }
3652
3653 fn request_layout(
3654 &mut self,
3655 id: Option<&GlobalElementId>,
3656 inspector_id: Option<&InspectorElementId>,
3657 window: &mut Window,
3658 cx: &mut App,
3659 ) -> (LayoutId, Self::RequestLayoutState) {
3660 self.element.request_layout(id, inspector_id, window, cx)
3661 }
3662
3663 fn prepaint(
3664 &mut self,
3665 id: Option<&GlobalElementId>,
3666 inspector_id: Option<&InspectorElementId>,
3667 bounds: Bounds<Pixels>,
3668 state: &mut Self::RequestLayoutState,
3669 window: &mut Window,
3670 cx: &mut App,
3671 ) -> E::PrepaintState {
3672 self.element
3673 .prepaint(id, inspector_id, bounds, state, window, cx)
3674 }
3675
3676 fn paint(
3677 &mut self,
3678 id: Option<&GlobalElementId>,
3679 inspector_id: Option<&InspectorElementId>,
3680 bounds: Bounds<Pixels>,
3681 request_layout: &mut Self::RequestLayoutState,
3682 prepaint: &mut Self::PrepaintState,
3683 window: &mut Window,
3684 cx: &mut App,
3685 ) {
3686 self.element.paint(
3687 id,
3688 inspector_id,
3689 bounds,
3690 request_layout,
3691 prepaint,
3692 window,
3693 cx,
3694 );
3695 }
3696}
3697
3698impl<E> IntoElement for Stateful<E>
3699where
3700 E: Element,
3701{
3702 type Element = Self;
3703
3704 fn into_element(self) -> Self::Element {
3705 self
3706 }
3707}
3708
3709impl<E> ParentElement for Stateful<E>
3710where
3711 E: ParentElement,
3712{
3713 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
3714 self.element.extend(elements)
3715 }
3716}
3717
3718#[derive(Clone)]
3721pub struct ScrollAnchor {
3722 handle: ScrollHandle,
3723 last_origin: Rc<RefCell<Point<Pixels>>>,
3724}
3725
3726impl ScrollAnchor {
3727 pub fn for_handle(handle: ScrollHandle) -> Self {
3729 Self {
3730 handle,
3731 last_origin: Default::default(),
3732 }
3733 }
3734 pub fn scroll_to(&self, window: &mut Window, _cx: &mut App) {
3736 let this = self.clone();
3737
3738 window.on_next_frame(move |_, _| {
3739 let viewport_bounds = this.handle.bounds();
3740 let self_bounds = *this.last_origin.borrow();
3741 this.handle.set_offset(viewport_bounds.origin - self_bounds);
3742 });
3743 }
3744}
3745
3746#[derive(Default, Debug)]
3747struct ScrollHandleState {
3748 offset: Rc<RefCell<Point<Pixels>>>,
3749 ongoing_scroll: Rc<RefCell<OngoingScroll>>,
3750 bounds: Bounds<Pixels>,
3751 max_offset: Size<Pixels>,
3752 child_bounds: Vec<Bounds<Pixels>>,
3753 scroll_to_bottom: bool,
3754 overflow: Point<Overflow>,
3755 active_item: Option<ScrollActiveItem>,
3756}
3757
3758#[derive(Default, Debug, Clone, Copy)]
3759struct ScrollActiveItem {
3760 index: usize,
3761 strategy: ScrollStrategy,
3762}
3763
3764#[derive(Default, Debug, Clone, Copy)]
3765enum ScrollStrategy {
3766 #[default]
3767 FirstVisible,
3768 Top,
3769}
3770
3771#[derive(Clone, Debug)]
3775pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
3776
3777impl Default for ScrollHandle {
3778 fn default() -> Self {
3779 Self::new()
3780 }
3781}
3782
3783impl ScrollHandle {
3784 pub fn new() -> Self {
3786 Self(Rc::default())
3787 }
3788
3789 pub fn offset(&self) -> Point<Pixels> {
3791 *self.0.borrow().offset.borrow()
3792 }
3793
3794 pub fn max_offset(&self) -> Size<Pixels> {
3796 self.0.borrow().max_offset
3797 }
3798
3799 pub fn top_item(&self) -> usize {
3801 let state = self.0.borrow();
3802 let top = state.bounds.top() - state.offset.borrow().y;
3803
3804 match state.child_bounds.binary_search_by(|bounds| {
3805 if top < bounds.top() {
3806 Ordering::Greater
3807 } else if top > bounds.bottom() {
3808 Ordering::Less
3809 } else {
3810 Ordering::Equal
3811 }
3812 }) {
3813 Ok(ix) => ix,
3814 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
3815 }
3816 }
3817
3818 pub fn bottom_item(&self) -> usize {
3820 let state = self.0.borrow();
3821 let bottom = state.bounds.bottom() - state.offset.borrow().y;
3822
3823 match state.child_bounds.binary_search_by(|bounds| {
3824 if bottom < bounds.top() {
3825 Ordering::Greater
3826 } else if bottom > bounds.bottom() {
3827 Ordering::Less
3828 } else {
3829 Ordering::Equal
3830 }
3831 }) {
3832 Ok(ix) => ix,
3833 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
3834 }
3835 }
3836
3837 pub fn bounds(&self) -> Bounds<Pixels> {
3839 self.0.borrow().bounds
3840 }
3841
3842 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
3844 self.0.borrow().child_bounds.get(ix).cloned()
3845 }
3846
3847 pub fn scroll_to_item(&self, ix: usize) {
3849 let mut state = self.0.borrow_mut();
3850 state.active_item = Some(ScrollActiveItem {
3851 index: ix,
3852 strategy: ScrollStrategy::default(),
3853 });
3854 }
3855
3856 pub fn scroll_to_top_of_item(&self, ix: usize) {
3859 let mut state = self.0.borrow_mut();
3860 state.active_item = Some(ScrollActiveItem {
3861 index: ix,
3862 strategy: ScrollStrategy::Top,
3863 });
3864 }
3865
3866 fn scroll_to_active_item(&self) {
3870 let mut state = self.0.borrow_mut();
3871
3872 let Some(active_item) = state.active_item else {
3873 return;
3874 };
3875
3876 let active_item = match state.child_bounds.get(active_item.index) {
3877 Some(bounds) => {
3878 let mut scroll_offset = state.offset.borrow_mut();
3879
3880 match active_item.strategy {
3881 ScrollStrategy::FirstVisible => {
3882 if state.overflow.y == Overflow::Scroll {
3883 if bounds.top() + scroll_offset.y < state.bounds.top() {
3884 scroll_offset.y = state.bounds.top() - bounds.top();
3885 } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
3886 scroll_offset.y = state.bounds.bottom() - bounds.bottom();
3887 }
3888 }
3889 }
3890 ScrollStrategy::Top => {
3891 scroll_offset.y = state.bounds.top() - bounds.top();
3892 }
3893 }
3894
3895 if state.overflow.x == Overflow::Scroll {
3896 if bounds.left() + scroll_offset.x < state.bounds.left() {
3897 scroll_offset.x = state.bounds.left() - bounds.left();
3898 } else if bounds.right() + scroll_offset.x > state.bounds.right() {
3899 scroll_offset.x = state.bounds.right() - bounds.right();
3900 }
3901 }
3902 None
3903 }
3904 None => Some(active_item),
3905 };
3906 state.active_item = active_item;
3907 }
3908
3909 pub fn scroll_to_bottom(&self) {
3911 let mut state = self.0.borrow_mut();
3912 state.scroll_to_bottom = true;
3913 }
3914
3915 pub fn set_offset(&self, mut position: Point<Pixels>) {
3919 let state = self.0.borrow();
3920 *state.offset.borrow_mut() = position;
3921 }
3922
3923 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
3925 let ix = self.top_item();
3926 let state = self.0.borrow();
3927
3928 if let Some(child_bounds) = state.child_bounds.get(ix) {
3929 (
3930 ix,
3931 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
3932 )
3933 } else {
3934 (ix, px(0.))
3935 }
3936 }
3937
3938 pub fn logical_scroll_bottom(&self) -> (usize, Pixels) {
3940 let ix = self.bottom_item();
3941 let state = self.0.borrow();
3942
3943 if let Some(child_bounds) = state.child_bounds.get(ix) {
3944 (
3945 ix,
3946 child_bounds.bottom() + state.offset.borrow().y - state.bounds.bottom(),
3947 )
3948 } else {
3949 (ix, px(0.))
3950 }
3951 }
3952
3953 pub fn children_count(&self) -> usize {
3955 self.0.borrow().child_bounds.len()
3956 }
3957}
3958
3959#[cfg(test)]
3960mod tests {
3961 use super::*;
3962 use crate::{
3963 AnyWindowHandle, AppContext as _, Context, InputEvent as _, MouseButton, Render,
3964 ScrollDelta, ScrollEvent, TestAppContext, fallback_prompt_renderer, hsla, point, size,
3965 };
3966 use std::{
3967 cell::{Cell, RefCell},
3968 rc::Rc,
3969 };
3970
3971 struct MouseDownOutOwner {
3972 mouse_down_out_count: Rc<Cell<usize>>,
3973 }
3974
3975 impl Render for MouseDownOutOwner {
3976 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
3977 let mouse_down_out_count = self.mouse_down_out_count.clone();
3978 div()
3979 .size_full()
3980 .child(
3981 div()
3982 .id("target")
3983 .size(px(50.))
3984 .on_mouse_down_out(move |_, _, _| {
3985 mouse_down_out_count.set(mouse_down_out_count.get() + 1);
3986 }),
3987 )
3988 }
3989 }
3990
3991 #[test]
3992 fn mouse_down_out_is_suppressed_while_window_prompt_is_active() {
3993 let mut test_app = TestAppContext::single();
3994 let mouse_down_out_count = Rc::new(Cell::new(0));
3995 let (_, cx) = test_app.add_window_view({
3996 let mouse_down_out_count = mouse_down_out_count.clone();
3997 move |_, _| MouseDownOutOwner {
3998 mouse_down_out_count,
3999 }
4000 });
4001
4002 cx.simulate_mouse_down(
4003 point(px(75.), px(75.)),
4004 MouseButton::Left,
4005 Default::default(),
4006 );
4007 assert_eq!(mouse_down_out_count.get(), 1);
4008
4009 cx.update(|window, cx| {
4010 cx.set_prompt_builder(fallback_prompt_renderer);
4011 let _receiver =
4012 window.prompt(crate::PromptLevel::Warning, "message", None, &["Ok"], cx);
4013 });
4014 cx.run_until_parked();
4015
4016 cx.simulate_mouse_down(
4017 point(px(75.), px(75.)),
4018 MouseButton::Left,
4019 Default::default(),
4020 );
4021 assert_eq!(
4022 mouse_down_out_count.get(),
4023 1,
4024 "mouse down over an active prompt must not reach underlying listeners"
4025 );
4026 }
4027
4028 struct HoverListenerLayoutTestView {
4029 target_left: Pixels,
4030 hover_transitions: Rc<RefCell<Vec<bool>>>,
4031 }
4032
4033 impl Render for HoverListenerLayoutTestView {
4034 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4035 let hover_transitions = self.hover_transitions.clone();
4036 div().relative().size_full().child(
4037 div()
4038 .id("hover-target")
4039 .absolute()
4040 .left(self.target_left)
4041 .top_0()
4042 .size(px(20.))
4043 .on_click(|_, _, _| {})
4044 .on_hover(move |is_hovered, _, _| {
4045 hover_transitions.borrow_mut().push(*is_hovered);
4046 }),
4047 )
4048 }
4049 }
4050
4051 #[gpui::test]
4052 fn default_hover_listener_updates_when_layout_changes_under_stationary_mouse(
4053 cx: &mut TestAppContext,
4054 ) {
4055 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4056 let window = cx.add_window({
4057 let hover_transitions = hover_transitions.clone();
4058 move |_, _| HoverListenerLayoutTestView {
4059 target_left: px(40.),
4060 hover_transitions,
4061 }
4062 });
4063 let any_window = AnyWindowHandle::from(window);
4064
4065 cx.update_window(any_window, |_, window, cx| {
4066 window.draw(cx).clear();
4067 window.dispatch_event(
4068 MouseMoveEvent {
4069 position: point(px(10.), px(10.)),
4070 modifiers: Default::default(),
4071 pressed_button: None,
4072 }
4073 .to_platform_input(),
4074 cx,
4075 );
4076 })
4077 .unwrap();
4078 assert!(hover_transitions.borrow().is_empty());
4079
4080 window
4081 .update(cx, |view, _, cx| {
4082 view.target_left = px(0.);
4083 cx.notify();
4084 })
4085 .unwrap();
4086 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
4087 .unwrap();
4088 assert_eq!(*hover_transitions.borrow(), [true]);
4089
4090 window
4091 .update(cx, |view, _, cx| {
4092 view.target_left = px(40.);
4093 cx.notify();
4094 })
4095 .unwrap();
4096 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
4097 .unwrap();
4098 assert_eq!(*hover_transitions.borrow(), [true, false]);
4099 }
4100
4101 #[gpui::test]
4102 fn default_hover_listener_ends_after_key_press(cx: &mut TestAppContext) {
4103 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4104 let window = cx.add_window({
4105 let hover_transitions = hover_transitions.clone();
4106 move |_, _| HoverListenerLayoutTestView {
4107 target_left: px(0.),
4108 hover_transitions,
4109 }
4110 });
4111 let any_window = AnyWindowHandle::from(window);
4112
4113 cx.update_window(any_window, |_, window, cx| {
4114 window.draw(cx).clear();
4115 window.dispatch_event(
4116 MouseMoveEvent {
4117 position: point(px(10.), px(10.)),
4118 modifiers: Default::default(),
4119 pressed_button: None,
4120 }
4121 .to_platform_input(),
4122 cx,
4123 );
4124 })
4125 .unwrap();
4126 assert_eq!(*hover_transitions.borrow(), [true]);
4127
4128 key_down(cx, any_window, "a");
4129 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
4130 .unwrap();
4131 assert_eq!(*hover_transitions.borrow(), [true, false]);
4132 }
4133
4134 struct HoverListenerModeLayoutTestView {
4135 target_left: Pixels,
4136 hover_transitions: Rc<RefCell<Vec<bool>>>,
4137 }
4138
4139 impl Render for HoverListenerModeLayoutTestView {
4140 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4141 let hover_transitions = self.hover_transitions.clone();
4142 div().relative().size_full().child(
4143 div()
4144 .id("hover-target")
4145 .absolute()
4146 .left(self.target_left)
4147 .top_0()
4148 .size(px(20.))
4149 .hover_listener_mode(HoverListenerMode::InputModalityIndependent)
4150 .on_hover(move |is_hovered, _, _| {
4151 hover_transitions.borrow_mut().push(*is_hovered);
4152 }),
4153 )
4154 }
4155 }
4156
4157 #[gpui::test]
4158 fn input_modality_independent_hover_listener_updates_after_key_press(cx: &mut TestAppContext) {
4159 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4160 let window = cx.add_window({
4161 let hover_transitions = hover_transitions.clone();
4162 move |_, _| HoverListenerModeLayoutTestView {
4163 target_left: px(40.),
4164 hover_transitions,
4165 }
4166 });
4167 let any_window = AnyWindowHandle::from(window);
4168 let pointer_position = point(px(10.), px(10.));
4169
4170 cx.update_window(any_window, |_, window, cx| {
4171 window.draw(cx).clear();
4172 window.dispatch_event(
4173 MouseMoveEvent {
4174 position: pointer_position,
4175 modifiers: Default::default(),
4176 pressed_button: None,
4177 }
4178 .to_platform_input(),
4179 cx,
4180 );
4181 })
4182 .unwrap();
4183 assert!(hover_transitions.borrow().is_empty());
4184
4185 key_down(cx, any_window, "a");
4186 window
4187 .update(cx, |view, _, cx| {
4188 view.target_left = px(0.);
4189 cx.notify();
4190 })
4191 .unwrap();
4192 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
4193 .unwrap();
4194 assert_eq!(*hover_transitions.borrow(), [true]);
4195
4196 key_down(cx, any_window, "b");
4197 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
4198 .unwrap();
4199 assert_eq!(*hover_transitions.borrow(), [true]);
4200
4201 cx.update_window(any_window, |_, window, cx| {
4202 window.dispatch_event(
4203 MouseMoveEvent {
4204 position: point(px(30.), px(10.)),
4205 modifiers: Default::default(),
4206 pressed_button: None,
4207 }
4208 .to_platform_input(),
4209 cx,
4210 );
4211 })
4212 .unwrap();
4213 assert_eq!(*hover_transitions.borrow(), [true, false]);
4214
4215 cx.update_window(any_window, |_, window, cx| {
4216 window.dispatch_event(
4217 MouseMoveEvent {
4218 position: pointer_position,
4219 modifiers: Default::default(),
4220 pressed_button: None,
4221 }
4222 .to_platform_input(),
4223 cx,
4224 );
4225 window.dispatch_event(
4226 MouseExitEvent {
4227 position: pointer_position,
4228 ..Default::default()
4229 }
4230 .to_platform_input(),
4231 cx,
4232 );
4233 })
4234 .unwrap();
4235 assert_eq!(*hover_transitions.borrow(), [true, false, true, false]);
4236 }
4237
4238 #[gpui::test]
4239 fn default_hover_listener_remains_hovered_during_stationary_mouse_press(
4240 cx: &mut TestAppContext,
4241 ) {
4242 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4243 let window = cx.add_window({
4244 let hover_transitions = hover_transitions.clone();
4245 move |_, _| HoverListenerLayoutTestView {
4246 target_left: px(0.),
4247 hover_transitions,
4248 }
4249 });
4250 let any_window = AnyWindowHandle::from(window);
4251 let mouse_position = point(px(10.), px(10.));
4252
4253 cx.update_window(any_window, |_, window, cx| {
4254 window.draw(cx).clear();
4255 window.dispatch_event(
4256 MouseMoveEvent {
4257 position: mouse_position,
4258 modifiers: Default::default(),
4259 pressed_button: None,
4260 }
4261 .to_platform_input(),
4262 cx,
4263 );
4264 })
4265 .unwrap();
4266 assert_eq!(*hover_transitions.borrow(), [true]);
4267
4268 cx.update_window(any_window, |_, window, cx| {
4269 window.dispatch_event(
4270 MouseDownEvent {
4271 position: mouse_position,
4272 button: MouseButton::Left,
4273 modifiers: Default::default(),
4274 click_count: 1,
4275 first_mouse: false,
4276 }
4277 .to_platform_input(),
4278 cx,
4279 );
4280 window.draw(cx).clear();
4281 })
4282 .unwrap();
4283 assert_eq!(*hover_transitions.borrow(), [true]);
4284
4285 cx.update_window(any_window, |_, window, cx| {
4286 window.dispatch_event(
4287 MouseUpEvent {
4288 position: mouse_position,
4289 button: MouseButton::Left,
4290 modifiers: Default::default(),
4291 click_count: 1,
4292 }
4293 .to_platform_input(),
4294 cx,
4295 );
4296 window.draw(cx).clear();
4297 })
4298 .unwrap();
4299 assert_eq!(*hover_transitions.borrow(), [true]);
4300 }
4301
4302 fn key_down(cx: &mut TestAppContext, window: AnyWindowHandle, key: &str) {
4303 let keystroke = crate::Keystroke::parse(key).unwrap();
4304 cx.update_window(window, |_, window, cx| {
4305 window.dispatch_event(
4306 KeyDownEvent {
4307 keystroke,
4308 is_held: false,
4309 prefer_character_input: false,
4310 }
4311 .to_platform_input(),
4312 cx,
4313 );
4314 })
4315 .unwrap();
4316 }
4317
4318 #[test]
4319 fn test_tooltip_show_delay_sets_interactivity_state() {
4320 let delay = Duration::from_millis(125);
4321 let element = div().id("tooltip-target").tooltip_show_delay(delay);
4322
4323 assert_eq!(
4324 element.element.interactivity.tooltip_show_delay,
4325 Some(delay)
4326 );
4327 }
4328
4329 #[test]
4330 fn aria_description_and_keyshortcuts_are_written_to_accesskit() {
4331 let element = div()
4332 .id("save-button")
4333 .role(accesskit::Role::Button)
4334 .aria_description("Save the active document")
4335 .aria_keyshortcuts("Ctrl+S");
4336 let mut node = accesskit::Node::new(accesskit::Role::Button);
4337
4338 element.element.interactivity.write_a11y_info(&mut node);
4339
4340 assert_eq!(node.description(), Some("Save the active document"));
4341 assert_eq!(node.keyboard_shortcut(), Some("Ctrl+S"));
4342 }
4343
4344 #[test]
4345 fn accessibility_id_is_written_to_accesskit() {
4346 let element = div()
4347 .id("buffer-font-size")
4348 .accessibility_id("settings.buffer-font-size");
4349 let mut node = accesskit::Node::new(accesskit::Role::SpinButton);
4350
4351 element.element.interactivity.write_a11y_info(&mut node);
4352
4353 assert_eq!(node.author_id(), Some("settings.buffer-font-size"));
4354 }
4355
4356 #[test]
4357 fn form_control_properties_are_written_to_accesskit() {
4358 let element = div()
4359 .id("email")
4360 .role(accesskit::Role::TextInput)
4361 .aria_value("alice@example.com")
4362 .aria_placeholder("name@example.com")
4363 .aria_disabled(true)
4364 .aria_required(true)
4365 .aria_invalid(accesskit::Invalid::True)
4366 .aria_modal(true);
4367 let mut node = accesskit::Node::new(accesskit::Role::TextInput);
4368
4369 element.element.interactivity.write_a11y_info(&mut node);
4370
4371 assert_eq!(node.value(), Some("alice@example.com"));
4372 assert_eq!(node.placeholder(), Some("name@example.com"));
4373 assert!(node.is_disabled());
4374 assert!(node.is_required());
4375 assert_eq!(node.invalid(), Some(accesskit::Invalid::True));
4376 assert!(node.is_modal());
4377 }
4378
4379 #[test]
4380 fn test_shadow_sm_preset_preserves_shadow_fields() {
4381 let mut element = div().shadow_sm();
4382 let shadows = element
4383 .style()
4384 .box_shadow
4385 .as_ref()
4386 .expect("shadow_sm should set box shadows");
4387
4388 assert_eq!(shadows.len(), 2);
4389 assert_eq!(shadows[0].color, hsla(0., 0., 0., 0.1));
4390 assert_eq!(shadows[0].offset, point(px(0.), px(1.)));
4391 assert_eq!(shadows[0].blur_radius, px(3.));
4392 assert_eq!(shadows[0].spread_radius, px(0.));
4393 assert!(!shadows[0].inset);
4394
4395 assert_eq!(shadows[1].color, hsla(0., 0., 0., 0.1));
4396 assert_eq!(shadows[1].offset, point(px(0.), px(1.)));
4397 assert_eq!(shadows[1].blur_radius, px(2.));
4398 assert_eq!(shadows[1].spread_radius, px(-1.));
4399 assert!(!shadows[1].inset);
4400 }
4401
4402 #[gpui::test]
4403 fn test_fractional_padding_does_not_make_a_fitting_container_scrollable(
4404 cx: &mut TestAppContext,
4405 ) {
4406 struct PaddedContainer {
4407 scroll_handle: ScrollHandle,
4408 }
4409
4410 impl Render for PaddedContainer {
4411 fn render(
4412 &mut self,
4413 _window: &mut Window,
4414 _cx: &mut Context<Self>,
4415 ) -> impl IntoElement {
4416 div().size_full().child(
4419 div()
4420 .id("container")
4421 .h(px(50.))
4422 .w(px(100.))
4423 .py(px(4.25))
4424 .overflow_y_scroll()
4425 .track_scroll(&self.scroll_handle)
4426 .child(div().w_full().h(px(42.))),
4427 )
4428 }
4429 }
4430
4431 let scroll_handle = ScrollHandle::new();
4432 let window: AnyWindowHandle = cx
4433 .add_window({
4434 let scroll_handle = scroll_handle.clone();
4435 move |_, _| PaddedContainer { scroll_handle }
4436 })
4437 .into();
4438 cx.update_window(window, |_, window, cx| window.draw(cx).clear())
4439 .unwrap();
4440
4441 assert_eq!(scroll_handle.max_offset().height, px(0.));
4442 }
4443
4444 struct ContentSizedGrid;
4445
4446 impl Render for ContentSizedGrid {
4447 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4448 let widths = [px(100.), px(200.), px(50.)];
4449 div().size_full().child(
4450 div()
4451 .w_full()
4452 .grid()
4453 .grid_cols_max_content(widths.len() as u16)
4454 .children(widths.into_iter().enumerate().map(|(index, width)| {
4455 div()
4456 .debug_selector(move || format!("cell-{index}"))
4457 .w(width)
4458 .h(px(10.))
4459 })),
4460 )
4461 }
4462 }
4463
4464 #[gpui::test]
4465 fn grid_cols_max_content_sizes_columns_to_their_content(cx: &mut TestAppContext) {
4466 let window = cx.add_window(|_, _| ContentSizedGrid);
4467 cx.update_window(window.into(), |_, window, cx| window.draw(cx).clear())
4468 .unwrap();
4469
4470 let mut bounds = |selector: &'static str| {
4471 cx.update_window(window.into(), |_, window, _| {
4472 window.rendered_frame.debug_bounds.get(selector).copied()
4473 })
4474 .unwrap()
4475 .unwrap_or_else(|| panic!("{selector} was not rendered"))
4476 };
4477
4478 assert_eq!(bounds("cell-0").origin.x, px(0.));
4479 assert_eq!(bounds("cell-0").size.width, px(100.));
4480 assert_eq!(bounds("cell-1").origin.x, px(100.));
4481 assert_eq!(bounds("cell-1").size.width, px(200.));
4482 assert_eq!(bounds("cell-2").origin.x, px(300.));
4483 assert_eq!(bounds("cell-2").size.width, px(50.));
4484 }
4485
4486 #[test]
4487 fn restrict_scroll_to_axis_sets_style_flag() {
4488 let mut element = div().id("axis-lock").restrict_scroll_to_axis();
4489 assert_eq!(
4490 element
4491 .element
4492 .interactivity
4493 .base_style
4494 .restrict_scroll_to_axis,
4495 Some(true)
4496 );
4497 }
4498
4499 struct AxisLockedScrollView {
4500 handle: ScrollHandle,
4501 }
4502
4503 impl Render for AxisLockedScrollView {
4504 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4505 div()
4506 .id("axis-lock-scroller")
4507 .size_full()
4508 .overflow_scroll()
4509 .restrict_scroll_to_axis()
4510 .track_scroll(&self.handle)
4511 .child(div().size(px(2000.)))
4512 }
4513 }
4514
4515 #[gpui::test]
4516 fn restrict_scroll_to_axis_locks_precise_gestures_to_start_axis(cx: &mut TestAppContext) {
4517 let handle = ScrollHandle::new();
4518 let (_, cx) = cx.add_window_view({
4519 let handle = handle.clone();
4520 move |_, _| AxisLockedScrollView { handle }
4521 });
4522 cx.simulate_resize(size(px(200.), px(200.)));
4523
4524 cx.update(|window, cx| {
4525 window.dispatch_event(
4526 ScrollEvent {
4527 position: point(px(50.), px(50.)),
4528 delta: ScrollDelta::Pixels(point(px(-20.), px(-4.))),
4529 touch_phase: crate::TouchPhase::Started,
4530 ..Default::default()
4531 }
4532 .to_platform_input(),
4533 cx,
4534 );
4535 });
4536 cx.run_until_parked();
4537 let after_start = handle.offset();
4538 assert!(after_start.x < px(0.), "horizontal start should scroll x");
4539 assert_eq!(
4540 after_start.y,
4541 px(0.),
4542 "sticky start axis should drop the weaker vertical component"
4543 );
4544
4545 cx.update(|window, cx| {
4546 window.dispatch_event(
4547 ScrollEvent {
4548 position: point(px(50.), px(50.)),
4549 delta: ScrollDelta::Pixels(point(px(-3.), px(-2.))),
4550 touch_phase: crate::TouchPhase::Moved,
4551 ..Default::default()
4552 }
4553 .to_platform_input(),
4554 cx,
4555 );
4556 });
4557 cx.run_until_parked();
4558 let after_continue = handle.offset();
4559 assert!(after_continue.x < after_start.x);
4560 assert_eq!(after_continue.y, px(0.));
4561
4562 cx.update(|window, cx| {
4563 window.dispatch_event(
4564 ScrollEvent {
4565 position: point(px(50.), px(50.)),
4566 delta: ScrollDelta::Pixels(point(px(-2.), px(-20.))),
4567 touch_phase: crate::TouchPhase::Moved,
4568 ..Default::default()
4569 }
4570 .to_platform_input(),
4571 cx,
4572 );
4573 });
4574 cx.run_until_parked();
4575 let after_unlock = handle.offset();
4576 assert!(
4577 after_unlock.y < px(0.),
4578 "a strong perpendicular delta should break the axis lock"
4579 );
4580 }
4581
4582 #[gpui::test]
4583 fn restrict_scroll_to_axis_style_flag_still_blocks_axis_remap(cx: &mut TestAppContext) {
4584 let handle = ScrollHandle::new();
4585 struct VerticalOnly {
4586 handle: ScrollHandle,
4587 }
4588 impl Render for VerticalOnly {
4589 fn render(
4590 &mut self,
4591 _window: &mut Window,
4592 _cx: &mut Context<Self>,
4593 ) -> impl IntoElement {
4594 let mut scroller = div()
4595 .id("vertical-only")
4596 .size_full()
4597 .overflow_y_scroll()
4598 .track_scroll(&self.handle)
4599 .child(div().h(px(2000.)).w_full());
4600 scroller.style().restrict_scroll_to_axis = Some(true);
4601 scroller
4602 }
4603 }
4604
4605 let (_, cx) = cx.add_window_view({
4606 let handle = handle.clone();
4607 move |_, _| VerticalOnly { handle }
4608 });
4609 cx.simulate_resize(size(px(200.), px(200.)));
4610
4611 cx.update(|window, cx| {
4612 window.dispatch_event(
4613 ScrollEvent {
4614 position: point(px(50.), px(50.)),
4615 delta: ScrollDelta::Pixels(point(px(-20.), px(0.))),
4616 ..Default::default()
4617 }
4618 .to_platform_input(),
4619 cx,
4620 );
4621 });
4622 cx.run_until_parked();
4623 assert_eq!(
4624 handle.offset(),
4625 point(px(0.), px(0.)),
4626 "existing restrict_scroll_to_axis callers must not have horizontal input remapped onto a vertical-only scroller"
4627 );
4628 }
4629}