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, ScrollWheelEvent, 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(&ScrollWheelEvent, &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, feature = "test-support"))]
819 fn debug_selector(mut self, f: impl FnOnce() -> String) -> Self {
823 self.interactivity().debug_selector = Some(f());
824 self
825 }
826
827 #[cfg(not(any(test, feature = "test-support")))]
828 #[inline]
832 fn debug_selector(self, _: impl FnOnce() -> String) -> Self {
833 self
834 }
835
836 fn capture_any_mouse_down(
841 mut self,
842 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
843 ) -> Self {
844 self.interactivity().capture_any_mouse_down(listener);
845 self
846 }
847
848 fn on_any_mouse_down(
853 mut self,
854 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
855 ) -> Self {
856 self.interactivity().on_any_mouse_down(listener);
857 self
858 }
859
860 fn on_mouse_up(
865 mut self,
866 button: MouseButton,
867 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
868 ) -> Self {
869 self.interactivity().on_mouse_up(button, listener);
870 self
871 }
872
873 fn capture_any_mouse_up(
878 mut self,
879 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
880 ) -> Self {
881 self.interactivity().capture_any_mouse_up(listener);
882 self
883 }
884
885 fn on_mouse_down_out(
891 mut self,
892 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
893 ) -> Self {
894 self.interactivity().on_mouse_down_out(listener);
895 self
896 }
897
898 fn on_mouse_up_out(
904 mut self,
905 button: MouseButton,
906 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
907 ) -> Self {
908 self.interactivity().on_mouse_up_out(button, listener);
909 self
910 }
911
912 fn on_mouse_move(
917 mut self,
918 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
919 ) -> Self {
920 self.interactivity().on_mouse_move(listener);
921 self
922 }
923
924 fn on_mouse_exit(
929 mut self,
930 listener: impl Fn(&MouseExitEvent, &mut Window, &mut App) + 'static,
931 ) -> Self {
932 self.interactivity().on_mouse_exit(listener);
933 self
934 }
935
936 fn on_file_drop_exit(
944 mut self,
945 listener: impl Fn(&FileDropEvent, &mut Window, &mut App) + 'static,
946 ) -> Self {
947 self.interactivity().on_file_drop_exit(listener);
948 self
949 }
950
951 fn on_drag_move<T: 'static>(
959 mut self,
960 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
961 ) -> Self {
962 self.interactivity().on_drag_move(listener);
963 self
964 }
965
966 fn on_scroll_wheel(
971 mut self,
972 listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
973 ) -> Self {
974 self.interactivity().on_scroll_wheel(listener);
975 self
976 }
977
978 fn on_pinch(mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) -> Self {
983 self.interactivity().on_pinch(listener);
984 self
985 }
986
987 fn capture_pinch(
992 mut self,
993 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
994 ) -> Self {
995 self.interactivity().capture_pinch(listener);
996 self
997 }
998
999 fn capture_action<A: Action>(
1004 mut self,
1005 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
1006 ) -> Self {
1007 self.interactivity().capture_action(listener);
1008 self
1009 }
1010
1011 fn on_action<A: Action>(
1016 mut self,
1017 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
1018 ) -> Self {
1019 self.interactivity().on_action(listener);
1020 self
1021 }
1022
1023 fn on_boxed_action(
1030 mut self,
1031 action: &dyn Action,
1032 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
1033 ) -> Self {
1034 self.interactivity().on_boxed_action(action, listener);
1035 self
1036 }
1037
1038 fn on_key_down(
1043 mut self,
1044 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1045 ) -> Self {
1046 self.interactivity().on_key_down(listener);
1047 self
1048 }
1049
1050 fn capture_key_down(
1055 mut self,
1056 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1057 ) -> Self {
1058 self.interactivity().capture_key_down(listener);
1059 self
1060 }
1061
1062 fn on_key_up(
1067 mut self,
1068 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1069 ) -> Self {
1070 self.interactivity().on_key_up(listener);
1071 self
1072 }
1073
1074 fn capture_key_up(
1079 mut self,
1080 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1081 ) -> Self {
1082 self.interactivity().capture_key_up(listener);
1083 self
1084 }
1085
1086 fn on_modifiers_changed(
1091 mut self,
1092 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
1093 ) -> Self {
1094 self.interactivity().on_modifiers_changed(listener);
1095 self
1096 }
1097
1098 fn drag_over<S: 'static>(
1100 mut self,
1101 f: impl 'static + Fn(StyleRefinement, &S, &mut Window, &mut App) -> StyleRefinement,
1102 ) -> Self {
1103 self.interactivity().drag_over_styles.push((
1104 TypeId::of::<S>(),
1105 Box::new(move |currently_dragged: &dyn Any, window, cx| {
1106 f(
1107 StyleRefinement::default(),
1108 currently_dragged.downcast_ref::<S>().unwrap(),
1109 window,
1110 cx,
1111 )
1112 }),
1113 ));
1114 self
1115 }
1116
1117 fn group_drag_over<S: 'static>(
1119 mut self,
1120 group_name: impl Into<SharedString>,
1121 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1122 ) -> Self {
1123 self.interactivity().group_drag_over_styles.push((
1124 TypeId::of::<S>(),
1125 GroupStyle {
1126 group: group_name.into(),
1127 style: Box::new(f(StyleRefinement::default())),
1128 },
1129 ));
1130 self
1131 }
1132
1133 fn on_drop<T: 'static>(
1138 mut self,
1139 listener: impl Fn(&T, &mut Window, &mut App) + 'static,
1140 ) -> Self {
1141 self.interactivity().on_drop(listener);
1142 self
1143 }
1144
1145 fn can_drop(
1148 mut self,
1149 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
1150 ) -> Self {
1151 self.interactivity().can_drop(predicate);
1152 self
1153 }
1154
1155 fn occlude(mut self) -> Self {
1159 self.interactivity().occlude_mouse();
1160 self
1161 }
1162
1163 fn window_control_area(mut self, area: WindowControlArea) -> Self {
1166 self.interactivity().window_control_area(area);
1167 self
1168 }
1169
1170 fn block_mouse_except_scroll(mut self) -> Self {
1175 self.interactivity().block_mouse_except_scroll();
1176 self
1177 }
1178
1179 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1182 where
1183 Self: Sized,
1184 {
1185 self.interactivity().focus_style = Some(Box::new(f(StyleRefinement::default())));
1186 self
1187 }
1188
1189 fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1192 where
1193 Self: Sized,
1194 {
1195 self.interactivity().in_focus_style = Some(Box::new(f(StyleRefinement::default())));
1196 self
1197 }
1198}
1199
1200pub trait StatefulInteractiveElement: InteractiveElement {
1203 fn role(mut self, role: accesskit::Role) -> Self {
1205 debug_assert!(
1206 role != accesskit::Role::GenericContainer,
1207 "GenericContainer is filtered out of the a11y tree and has no effect"
1208 );
1209 self.interactivity().override_role = Some(role);
1210 self
1211 }
1212
1213 fn accessibility_id(mut self, id: impl Into<SharedString>) -> Self {
1218 self.interactivity().accessibility_id = Some(id.into());
1219 self
1220 }
1221
1222 fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
1224 self.interactivity().aria_label = Some(label.into());
1225 self
1226 }
1227
1228 fn aria_description(mut self, description: impl Into<SharedString>) -> Self {
1230 self.interactivity().aria_description = Some(description.into());
1231 self
1232 }
1233
1234 fn aria_value(mut self, value: impl Into<SharedString>) -> Self {
1236 self.interactivity().aria_value = Some(value.into());
1237 self
1238 }
1239
1240 fn aria_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
1242 self.interactivity().aria_placeholder = Some(placeholder.into());
1243 self
1244 }
1245
1246 fn aria_disabled(mut self, disabled: bool) -> Self {
1248 self.interactivity().aria_disabled = disabled;
1249 self
1250 }
1251
1252 fn aria_required(mut self, required: bool) -> Self {
1254 self.interactivity().aria_required = required;
1255 self
1256 }
1257
1258 fn aria_invalid(mut self, invalid: accesskit::Invalid) -> Self {
1260 self.interactivity().aria_invalid = Some(invalid);
1261 self
1262 }
1263
1264 fn aria_modal(mut self, modal: bool) -> Self {
1266 self.interactivity().aria_modal = modal;
1267 self
1268 }
1269
1270 fn aria_keyshortcuts(mut self, keyshortcuts: impl Into<SharedString>) -> Self {
1274 self.interactivity().aria_keyshortcuts = Some(keyshortcuts.into());
1275 self
1276 }
1277
1278 fn aria_selected(mut self, selected: bool) -> Self {
1280 self.interactivity().aria_selected = Some(selected);
1281 self
1282 }
1283
1284 fn aria_expanded(mut self, expanded: bool) -> Self {
1286 self.interactivity().aria_expanded = Some(expanded);
1287 self
1288 }
1289
1290 fn aria_toggled(mut self, toggled: accesskit::Toggled) -> Self {
1292 self.interactivity().aria_toggled = Some(toggled);
1293 self
1294 }
1295
1296 fn aria_numeric_value(mut self, value: f64) -> Self {
1298 self.interactivity().aria_numeric_value = Some(value);
1299 self
1300 }
1301
1302 fn aria_min_numeric_value(mut self, value: f64) -> Self {
1304 self.interactivity().aria_min_numeric_value = Some(value);
1305 self
1306 }
1307
1308 fn aria_max_numeric_value(mut self, value: f64) -> Self {
1310 self.interactivity().aria_max_numeric_value = Some(value);
1311 self
1312 }
1313
1314 fn aria_orientation(mut self, orientation: accesskit::Orientation) -> Self {
1316 self.interactivity().aria_orientation = Some(orientation);
1317 self
1318 }
1319
1320 fn aria_level(mut self, level: usize) -> Self {
1322 self.interactivity().aria_level = Some(level);
1323 self
1324 }
1325
1326 fn aria_position_in_set(mut self, position: usize) -> Self {
1328 self.interactivity().aria_position_in_set = Some(position);
1329 self
1330 }
1331
1332 fn aria_size_of_set(mut self, size: usize) -> Self {
1334 self.interactivity().aria_size_of_set = Some(size);
1335 self
1336 }
1337
1338 fn aria_row_index(mut self, index: usize) -> Self {
1340 self.interactivity().aria_row_index = Some(index);
1341 self
1342 }
1343
1344 fn aria_column_index(mut self, index: usize) -> Self {
1346 self.interactivity().aria_column_index = Some(index);
1347 self
1348 }
1349
1350 fn aria_row_count(mut self, count: usize) -> Self {
1352 self.interactivity().aria_row_count = Some(count);
1353 self
1354 }
1355
1356 fn aria_column_count(mut self, count: usize) -> Self {
1358 self.interactivity().aria_column_count = Some(count);
1359 self
1360 }
1361
1362 fn on_a11y_action(
1364 mut self,
1365 action: accesskit::Action,
1366 listener: impl FnMut(Option<&accesskit::ActionData>, &mut Window, &mut App) + 'static,
1367 ) -> Self {
1368 self.interactivity()
1369 .a11y_action_listeners
1370 .push((action, Box::new(listener)));
1371 self
1372 }
1373
1374 fn focusable(mut self) -> Self {
1376 self.interactivity().focusable = true;
1377 self
1378 }
1379
1380 fn overflow_scroll(mut self) -> Self {
1382 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1383 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1384 self
1385 }
1386
1387 fn overflow_x_scroll(mut self) -> Self {
1389 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1390 self
1391 }
1392
1393 fn overflow_y_scroll(mut self) -> Self {
1395 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1396 self
1397 }
1398
1399 fn restrict_scroll_to_axis(mut self) -> Self {
1403 self.interactivity().base_style.restrict_scroll_to_axis = Some(true);
1404 self
1405 }
1406
1407 fn scrollbar_width(mut self, width: impl Into<AbsoluteLength>) -> Self {
1412 self.interactivity().base_style.scrollbar_width = Some(width.into());
1413 self
1414 }
1415
1416 fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
1418 self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone());
1419 self
1420 }
1421
1422 fn anchor_scroll(mut self, scroll_anchor: Option<ScrollAnchor>) -> Self {
1424 self.interactivity().scroll_anchor = scroll_anchor;
1425 self
1426 }
1427
1428 fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1430 where
1431 Self: Sized,
1432 {
1433 self.interactivity().active_style = Some(Box::new(f(StyleRefinement::default())));
1434 self
1435 }
1436
1437 fn group_active(
1439 mut self,
1440 group_name: impl Into<SharedString>,
1441 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1442 ) -> Self
1443 where
1444 Self: Sized,
1445 {
1446 self.interactivity().group_active_style = Some(GroupStyle {
1447 group: group_name.into(),
1448 style: Box::new(f(StyleRefinement::default())),
1449 });
1450 self
1451 }
1452
1453 fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self
1458 where
1459 Self: Sized,
1460 {
1461 self.interactivity().on_click(listener);
1462 self
1463 }
1464
1465 fn on_drag<T, W>(
1473 mut self,
1474 value: T,
1475 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
1476 ) -> Self
1477 where
1478 Self: Sized,
1479 T: 'static,
1480 W: 'static + Render,
1481 {
1482 self.interactivity().on_drag(value, constructor);
1483 self
1484 }
1485
1486 fn on_hover(mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self
1497 where
1498 Self: Sized,
1499 {
1500 self.interactivity().on_hover(listener);
1501 self
1502 }
1503
1504 fn hover_listener_mode(mut self, mode: HoverListenerMode) -> Self
1508 where
1509 Self: Sized,
1510 {
1511 self.interactivity().hover_listener_mode(mode);
1512 self
1513 }
1514
1515 fn tooltip(mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self
1518 where
1519 Self: Sized,
1520 {
1521 self.interactivity().tooltip(build_tooltip);
1522 self
1523 }
1524
1525 fn hoverable_tooltip(
1529 mut self,
1530 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
1531 ) -> Self
1532 where
1533 Self: Sized,
1534 {
1535 self.interactivity().hoverable_tooltip(build_tooltip);
1536 self
1537 }
1538
1539 fn tooltip_show_delay(mut self, delay: Duration) -> Self
1542 where
1543 Self: Sized,
1544 {
1545 self.interactivity().tooltip_show_delay(delay);
1546 self
1547 }
1548
1549 fn on_resize(mut self, listener: impl Fn(&ResizeEvent, &mut Window, &mut App) + 'static) -> Self
1554 where
1555 Self: Sized,
1556 {
1557 self.interactivity().on_resize(listener);
1558 self
1559 }
1560}
1561
1562pub(crate) type MouseDownListener =
1563 Box<dyn Fn(&MouseDownEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1564pub(crate) type MouseUpListener =
1565 Box<dyn Fn(&MouseUpEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1566
1567pub(crate) type MouseMoveListener =
1568 Box<dyn Fn(&MouseMoveEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1569pub(crate) type MouseExitListener =
1570 Box<dyn Fn(&MouseExitEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1571
1572pub(crate) type FileDropExitListener =
1573 Box<dyn Fn(&FileDropEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1574
1575pub(crate) type ScrollWheelListener =
1576 Box<dyn Fn(&ScrollWheelEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1577
1578pub(crate) type PinchListener =
1579 Box<dyn Fn(&PinchEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1580
1581pub(crate) type ClickListener = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>;
1582
1583#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1586pub enum HoverListenerMode {
1587 #[default]
1590 InputModalityAware,
1591 InputModalityIndependent,
1595}
1596
1597impl HoverListenerMode {
1598 fn is_hovered(self, hitbox: &Hitbox, window: &Window) -> bool {
1599 match self {
1600 Self::InputModalityAware => hitbox.is_hovered(window),
1601 Self::InputModalityIndependent => hitbox.id.is_hovered_ignoring_last_input(window),
1602 }
1603 }
1604}
1605
1606pub(crate) type DragListener =
1607 Box<dyn Fn(&dyn Any, Point<Pixels>, &mut Window, &mut App) -> AnyView + 'static>;
1608
1609type DropListener = Box<dyn Fn(&dyn Any, &mut Window, &mut App) + 'static>;
1610
1611type CanDropPredicate = Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static>;
1612
1613pub(crate) struct TooltipBuilder {
1614 build: Rc<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>,
1615 hoverable: bool,
1616}
1617
1618pub(crate) type KeyDownListener =
1619 Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1620
1621pub(crate) type KeyUpListener =
1622 Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1623
1624pub(crate) type ModifiersChangedListener =
1625 Box<dyn Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static>;
1626
1627pub(crate) type ActionListener =
1628 Box<dyn Fn(&dyn Any, DispatchPhase, &mut Window, &mut App) + 'static>;
1629
1630pub(crate) type ResizeListener = Box<dyn Fn(&ResizeEvent, &mut Window, &mut App) + 'static>;
1631
1632#[track_caller]
1634pub fn div() -> Div {
1635 Div {
1636 interactivity: Interactivity::new(),
1637 children: SmallVec::default(),
1638 prepaint_listener: None,
1639 image_cache: None,
1640 }
1641}
1642
1643pub struct Div {
1645 interactivity: Interactivity,
1646 children: SmallVec<[StackSafe<AnyElement>; 2]>,
1647 prepaint_listener: Option<Box<dyn Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static>>,
1648 image_cache: Option<Box<dyn ImageCacheProvider>>,
1649}
1650
1651impl Div {
1652 pub fn on_children_prepainted(
1655 mut self,
1656 listener: impl Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static,
1657 ) -> Self {
1658 self.prepaint_listener = Some(Box::new(listener));
1659 self
1660 }
1661
1662 pub fn image_cache(mut self, cache: impl ImageCacheProvider) -> Self {
1664 self.image_cache = Some(Box::new(cache));
1665 self
1666 }
1667}
1668
1669pub struct DivFrameState {
1676 child_layout_ids: SmallVec<[LayoutId; 2]>,
1677}
1678
1679#[derive(Clone)]
1681pub struct DivInspectorState {
1682 #[cfg(any(feature = "inspector", debug_assertions))]
1686 pub base_style: Box<StyleRefinement>,
1687 pub bounds: Bounds<Pixels>,
1689 pub content_size: Size<Pixels>,
1691}
1692
1693impl Styled for Div {
1694 fn style(&mut self) -> &mut StyleRefinement {
1695 &mut self.interactivity.base_style
1696 }
1697}
1698
1699impl InteractiveElement for Div {
1700 fn interactivity(&mut self) -> &mut Interactivity {
1701 &mut self.interactivity
1702 }
1703}
1704
1705impl ParentElement for Div {
1706 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1707 self.children
1708 .extend(elements.into_iter().map(StackSafe::new))
1709 }
1710}
1711
1712impl Element for Div {
1713 type RequestLayoutState = DivFrameState;
1714 type PrepaintState = Option<Hitbox>;
1715
1716 fn id(&self) -> Option<ElementId> {
1717 self.interactivity.element_id.clone()
1718 }
1719
1720 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
1721 self.interactivity.source_location()
1722 }
1723
1724 fn a11y_role(&self) -> Option<accesskit::Role> {
1725 self.interactivity
1726 .override_role
1727 .filter(|role| *role != accesskit::Role::GenericContainer)
1728 }
1729
1730 fn write_a11y_info(&self, node: &mut accesskit::Node) {
1731 self.interactivity.write_a11y_info(node);
1732 }
1733
1734 #[stacksafe]
1735 fn request_layout(
1736 &mut self,
1737 global_id: Option<&GlobalElementId>,
1738 inspector_id: Option<&InspectorElementId>,
1739 window: &mut Window,
1740 cx: &mut App,
1741 ) -> (LayoutId, Self::RequestLayoutState) {
1742 let mut child_layout_ids = SmallVec::new();
1743 let image_cache = self
1744 .image_cache
1745 .as_mut()
1746 .map(|provider| provider.provide(window, cx));
1747
1748 let layout_id = window.with_image_cache(image_cache, |window| {
1749 self.interactivity.request_layout(
1750 global_id,
1751 inspector_id,
1752 window,
1753 cx,
1754 |style, window, cx| {
1755 window.with_text_style(style.text_style().cloned(), |window| {
1756 child_layout_ids = self
1757 .children
1758 .iter_mut()
1759 .map(|child| child.request_layout(window, cx))
1760 .collect::<SmallVec<_>>();
1761 window.request_layout(style, child_layout_ids.iter().copied(), cx)
1762 })
1763 },
1764 )
1765 });
1766
1767 (layout_id, DivFrameState { child_layout_ids })
1768 }
1769
1770 #[stacksafe]
1771 fn prepaint(
1772 &mut self,
1773 global_id: Option<&GlobalElementId>,
1774 inspector_id: Option<&InspectorElementId>,
1775 bounds: Bounds<Pixels>,
1776 request_layout: &mut Self::RequestLayoutState,
1777 window: &mut Window,
1778 cx: &mut App,
1779 ) -> Option<Hitbox> {
1780 let has_prepaint_listener = self.prepaint_listener.is_some();
1781 let mut children_bounds = Vec::with_capacity(if has_prepaint_listener {
1782 request_layout.child_layout_ids.len()
1783 } else {
1784 0
1785 });
1786
1787 let mut child_min = point(Pixels::MAX, Pixels::MAX);
1788 let mut child_max = Point::default();
1789 if let Some(handle) = self.interactivity.scroll_anchor.as_ref() {
1790 *handle.last_origin.borrow_mut() = bounds.origin - window.element_offset();
1791 }
1792 let content_size = if request_layout.child_layout_ids.is_empty() {
1793 bounds.size
1794 } else if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
1795 let mut state = scroll_handle.0.borrow_mut();
1796 state.child_bounds = Vec::with_capacity(request_layout.child_layout_ids.len());
1797 for child_layout_id in &request_layout.child_layout_ids {
1798 let child_bounds = window.layout_bounds(*child_layout_id);
1799 child_min = child_min.min(&child_bounds.origin);
1800 child_max = child_max.max(&child_bounds.bottom_right());
1801 state.child_bounds.push(child_bounds);
1802 }
1803 (child_max - child_min).into()
1804 } else {
1805 for child_layout_id in &request_layout.child_layout_ids {
1806 let child_bounds = window.layout_bounds(*child_layout_id);
1807 child_min = child_min.min(&child_bounds.origin);
1808 child_max = child_max.max(&child_bounds.bottom_right());
1809
1810 if has_prepaint_listener {
1811 children_bounds.push(child_bounds);
1812 }
1813 }
1814 (child_max - child_min).into()
1815 };
1816
1817 if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
1818 scroll_handle.scroll_to_active_item();
1819 }
1820
1821 self.interactivity.prepaint(
1822 global_id,
1823 inspector_id,
1824 bounds,
1825 content_size,
1826 window,
1827 cx,
1828 |_style, scroll_offset, hitbox, window, cx| {
1829 window.with_element_offset(scroll_offset, |window| {
1830 for child in &mut self.children {
1831 child.prepaint(window, cx);
1832 }
1833 });
1834
1835 if let Some(listener) = self.prepaint_listener.as_ref() {
1836 listener(children_bounds, window, cx);
1837 }
1838
1839 hitbox
1840 },
1841 )
1842 }
1843
1844 #[stacksafe]
1845 fn paint(
1846 &mut self,
1847 global_id: Option<&GlobalElementId>,
1848 inspector_id: Option<&InspectorElementId>,
1849 bounds: Bounds<Pixels>,
1850 _request_layout: &mut Self::RequestLayoutState,
1851 hitbox: &mut Option<Hitbox>,
1852 window: &mut Window,
1853 cx: &mut App,
1854 ) {
1855 let image_cache = self
1856 .image_cache
1857 .as_mut()
1858 .map(|provider| provider.provide(window, cx));
1859
1860 window.with_image_cache(image_cache, |window| {
1861 self.interactivity.paint(
1862 global_id,
1863 inspector_id,
1864 bounds,
1865 hitbox.as_ref(),
1866 window,
1867 cx,
1868 |_style, window, cx| {
1869 for child in &mut self.children {
1870 child.paint(window, cx);
1871 }
1872 },
1873 )
1874 });
1875 }
1876}
1877
1878impl IntoElement for Div {
1879 type Element = Self;
1880
1881 fn into_element(self) -> Self::Element {
1882 self
1883 }
1884}
1885
1886#[derive(Default)]
1889pub struct Interactivity {
1890 pub element_id: Option<ElementId>,
1892 pub active: Option<bool>,
1894 pub hovered: Option<bool>,
1897 pub(crate) tooltip_id: Option<TooltipId>,
1898 pub(crate) content_size: Size<Pixels>,
1899 pub(crate) key_context: Option<KeyContext>,
1900 pub(crate) focusable: bool,
1901 pub(crate) tracked_focus_handle: Option<FocusHandle>,
1902 pub(crate) tracked_scroll_handle: Option<ScrollHandle>,
1903 pub(crate) scroll_anchor: Option<ScrollAnchor>,
1904 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
1905 pub(crate) ongoing_scroll: Option<Rc<RefCell<OngoingScroll>>>,
1906 pub(crate) group: Option<SharedString>,
1907 pub base_style: Box<StyleRefinement>,
1910 pub(crate) focus_style: Option<Box<StyleRefinement>>,
1911 pub(crate) in_focus_style: Option<Box<StyleRefinement>>,
1912 pub(crate) hover_style: Option<Box<StyleRefinement>>,
1913 pub(crate) group_hover_style: Option<GroupStyle>,
1914 pub(crate) active_style: Option<Box<StyleRefinement>>,
1915 pub(crate) group_active_style: Option<GroupStyle>,
1916 pub(crate) drag_over_styles: Vec<(
1917 TypeId,
1918 Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> StyleRefinement>,
1919 )>,
1920 pub(crate) group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
1921 pub(crate) mouse_down_listeners: Vec<MouseDownListener>,
1922 pub(crate) mouse_up_listeners: Vec<MouseUpListener>,
1923 pub(crate) mouse_move_listeners: Vec<MouseMoveListener>,
1924 pub(crate) mouse_exit_listeners: Vec<MouseExitListener>,
1925 pub(crate) file_drop_exit_listeners: Vec<FileDropExitListener>,
1926 pub(crate) scroll_wheel_listeners: Vec<ScrollWheelListener>,
1927 pub(crate) pinch_listeners: Vec<PinchListener>,
1928 pub(crate) key_down_listeners: Vec<KeyDownListener>,
1929 pub(crate) key_up_listeners: Vec<KeyUpListener>,
1930 pub(crate) modifiers_changed_listeners: Vec<ModifiersChangedListener>,
1931 pub(crate) action_listeners: Vec<(TypeId, ActionListener)>,
1932 pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
1933 pub(crate) can_drop_predicate: Option<CanDropPredicate>,
1934 pub(crate) click_listeners: Vec<ClickListener>,
1935 pub(crate) drag_listener: Option<(Arc<dyn Any>, DragListener)>,
1936 pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut Window, &mut App)>>,
1937 pub(crate) hover_listener_mode: HoverListenerMode,
1938 pub(crate) resize_listeners: Vec<ResizeListener>,
1939 pub(crate) tooltip_builder: Option<TooltipBuilder>,
1940 pub(crate) tooltip_show_delay: Option<Duration>,
1941 pub(crate) window_control: Option<WindowControlArea>,
1942 pub(crate) hitbox_behavior: HitboxBehavior,
1943 pub(crate) tab_index: Option<isize>,
1944 pub(crate) tab_group: bool,
1945 pub(crate) tab_stop: bool,
1946
1947 pub(crate) a11y_action_listeners:
1948 Vec<(accesskit::Action, crate::window::a11y::A11yActionListener)>,
1949 pub(crate) override_role: Option<accesskit::Role>,
1950 pub(crate) accessibility_id: Option<SharedString>,
1951 pub(crate) aria_label: Option<SharedString>,
1952 pub(crate) aria_description: Option<SharedString>,
1953 pub(crate) aria_value: Option<SharedString>,
1954 pub(crate) aria_placeholder: Option<SharedString>,
1955 pub(crate) aria_disabled: bool,
1956 pub(crate) aria_required: bool,
1957 pub(crate) aria_invalid: Option<accesskit::Invalid>,
1958 pub(crate) aria_modal: bool,
1959 pub(crate) aria_keyshortcuts: Option<SharedString>,
1960 pub(crate) aria_selected: Option<bool>,
1961 pub(crate) aria_expanded: Option<bool>,
1962 pub(crate) aria_toggled: Option<accesskit::Toggled>,
1963 pub(crate) aria_numeric_value: Option<f64>,
1964 pub(crate) aria_min_numeric_value: Option<f64>,
1965 pub(crate) aria_max_numeric_value: Option<f64>,
1966 pub(crate) aria_orientation: Option<accesskit::Orientation>,
1967 pub(crate) aria_level: Option<usize>,
1968 pub(crate) aria_position_in_set: Option<usize>,
1969 pub(crate) aria_size_of_set: Option<usize>,
1970 pub(crate) aria_row_index: Option<usize>,
1971 pub(crate) aria_column_index: Option<usize>,
1972 pub(crate) aria_row_count: Option<usize>,
1973 pub(crate) aria_column_count: Option<usize>,
1974
1975 #[cfg(any(feature = "inspector", debug_assertions))]
1976 pub(crate) source_location: Option<&'static core::panic::Location<'static>>,
1977
1978 #[cfg(any(test, feature = "test-support"))]
1979 pub(crate) debug_selector: Option<String>,
1980}
1981
1982impl Interactivity {
1983 pub fn request_layout(
1985 &mut self,
1986 global_id: Option<&GlobalElementId>,
1987 _inspector_id: Option<&InspectorElementId>,
1988 window: &mut Window,
1989 cx: &mut App,
1990 f: impl FnOnce(Style, &mut Window, &mut App) -> LayoutId,
1991 ) -> LayoutId {
1992 #[cfg(any(feature = "inspector", debug_assertions))]
1993 window.with_inspector_state(
1994 _inspector_id,
1995 cx,
1996 |inspector_state: &mut Option<DivInspectorState>, _window| {
1997 if let Some(inspector_state) = inspector_state {
1998 self.base_style = inspector_state.base_style.clone();
1999 } else {
2000 *inspector_state = Some(DivInspectorState {
2001 base_style: self.base_style.clone(),
2002 bounds: Default::default(),
2003 content_size: Default::default(),
2004 })
2005 }
2006 },
2007 );
2008
2009 window.with_optional_element_state::<InteractiveElementState, _>(
2010 global_id,
2011 |element_state, window| {
2012 let mut element_state =
2013 element_state.map(|element_state| element_state.unwrap_or_default());
2014
2015 if let Some(element_state) = element_state.as_ref()
2016 && cx.has_active_drag()
2017 {
2018 if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() {
2019 *pending_mouse_down.borrow_mut() = None;
2020 }
2021 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
2022 *clicked_state.borrow_mut() = ElementClickedState::default();
2023 }
2024 }
2025
2026 if self.focusable
2031 && self.tracked_focus_handle.is_none()
2032 && let Some(element_state) = element_state.as_mut()
2033 {
2034 let mut handle = element_state
2035 .focus_handle
2036 .get_or_insert_with(|| cx.focus_handle())
2037 .clone()
2038 .tab_stop(self.tab_stop);
2039
2040 if let Some(index) = self.tab_index {
2041 handle = handle.tab_index(index);
2042 }
2043
2044 self.tracked_focus_handle = Some(handle);
2045 }
2046
2047 if let Some(scroll_handle) = self.tracked_scroll_handle.as_ref() {
2048 let scroll_handle_state = scroll_handle.0.borrow();
2049 self.scroll_offset = Some(scroll_handle_state.offset.clone());
2050 self.ongoing_scroll = Some(scroll_handle_state.ongoing_scroll.clone());
2051 } else if (self.base_style.overflow.x == Some(Overflow::Scroll)
2052 || self.base_style.overflow.y == Some(Overflow::Scroll))
2053 && let Some(element_state) = element_state.as_mut()
2054 {
2055 self.scroll_offset = Some(
2056 element_state
2057 .scroll_offset
2058 .get_or_insert_with(Rc::default)
2059 .clone(),
2060 );
2061 self.ongoing_scroll = Some(
2062 element_state
2063 .ongoing_scroll
2064 .get_or_insert_with(|| Rc::new(RefCell::new(OngoingScroll::default())))
2065 .clone(),
2066 );
2067 }
2068
2069 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
2070 let layout_id = f(style, window, cx);
2071 (layout_id, element_state)
2072 },
2073 )
2074 }
2075
2076 pub fn prepaint<R>(
2078 &mut self,
2079 global_id: Option<&GlobalElementId>,
2080 _inspector_id: Option<&InspectorElementId>,
2081 bounds: Bounds<Pixels>,
2082 content_size: Size<Pixels>,
2083 window: &mut Window,
2084 cx: &mut App,
2085 f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut Window, &mut App) -> R,
2086 ) -> R {
2087 self.content_size = content_size;
2088
2089 #[cfg(any(feature = "inspector", debug_assertions))]
2090 window.with_inspector_state(
2091 _inspector_id,
2092 cx,
2093 |inspector_state: &mut Option<DivInspectorState>, _window| {
2094 if let Some(inspector_state) = inspector_state {
2095 inspector_state.bounds = bounds;
2096 inspector_state.content_size = content_size;
2097 }
2098 },
2099 );
2100
2101 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
2102 window.set_focus_handle(focus_handle, cx);
2103
2104 if window.a11y.is_active()
2105 && let Some(global_id) = global_id
2106 {
2107 let node_id = global_id.accesskit_node_id();
2108 window.a11y.focus_ids.insert(node_id, focus_handle.id);
2109 if focus_handle.is_focused(window) && window.a11y.nodes.has_node(node_id) {
2110 window.a11y.nodes.set_focus(node_id);
2111 }
2112 }
2113 }
2114 window.with_optional_element_state::<InteractiveElementState, _>(
2115 global_id,
2116 |element_state, window| {
2117 let mut element_state =
2118 element_state.map(|element_state| element_state.unwrap_or_default());
2119 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
2120
2121 if let Some(element_state) = element_state.as_mut() {
2122 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
2123 let clicked_state = clicked_state.borrow();
2124 self.active = Some(clicked_state.element);
2125 }
2126 if let Some(active_tooltip) = element_state.active_tooltip.as_ref() {
2127 if self.tooltip_builder.is_some() {
2128 self.tooltip_id = set_tooltip_on_window(active_tooltip, window);
2129 } else {
2130 element_state.active_tooltip.take();
2132 }
2133 }
2134 }
2135
2136 window.with_text_style(style.text_style().cloned(), |window| {
2137 window.with_content_mask(
2138 style.overflow_mask(bounds, window.rem_size()),
2139 |window| {
2140 let hitbox = if self.should_insert_hitbox(&style, window, cx) {
2141 Some(window.insert_hitbox(bounds, self.hitbox_behavior))
2142 } else {
2143 None
2144 };
2145
2146 let scroll_offset =
2147 self.clamp_scroll_position(bounds, &style, window, cx);
2148 let result = f(&style, scroll_offset, hitbox, window, cx);
2149 (result, element_state)
2150 },
2151 )
2152 })
2153 },
2154 )
2155 }
2156
2157 fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
2158 self.hitbox_behavior != HitboxBehavior::Normal
2159 || self.window_control.is_some()
2160 || style.mouse_cursor.is_some()
2161 || self.group.is_some()
2162 || self.scroll_offset.is_some()
2163 || self.tracked_focus_handle.is_some()
2164 || self.hover_style.is_some()
2165 || self.group_hover_style.is_some()
2166 || self.hover_listener.is_some()
2167 || !self.mouse_up_listeners.is_empty()
2168 || !self.mouse_down_listeners.is_empty()
2169 || !self.mouse_move_listeners.is_empty()
2170 || !self.mouse_exit_listeners.is_empty()
2171 || !self.file_drop_exit_listeners.is_empty()
2172 || !self.click_listeners.is_empty()
2173 || !self.scroll_wheel_listeners.is_empty()
2174 || !self.pinch_listeners.is_empty()
2175 || self.drag_listener.is_some()
2176 || !self.drop_listeners.is_empty()
2177 || self.tooltip_builder.is_some()
2178 || window.is_inspector_picking(cx)
2179 }
2180
2181 fn clamp_scroll_position(
2182 &self,
2183 bounds: Bounds<Pixels>,
2184 style: &Style,
2185 window: &mut Window,
2186 _cx: &mut App,
2187 ) -> Point<Pixels> {
2188 fn round_to_two_decimals(pixels: Pixels) -> Pixels {
2189 const ROUNDING_FACTOR: f32 = 100.0;
2190 (pixels * ROUNDING_FACTOR).round() / ROUNDING_FACTOR
2191 }
2192
2193 if let Some(scroll_offset) = self.scroll_offset.as_ref() {
2194 let mut scroll_to_bottom = false;
2195 let mut tracked_scroll_handle = self
2196 .tracked_scroll_handle
2197 .as_ref()
2198 .map(|handle| handle.0.borrow_mut());
2199 if let Some(mut scroll_handle_state) = tracked_scroll_handle.as_deref_mut() {
2200 scroll_handle_state.overflow = style.overflow;
2201 scroll_to_bottom = mem::take(&mut scroll_handle_state.scroll_to_bottom);
2202 }
2203
2204 let rem_size = window.rem_size();
2205 let padding = style
2210 .padding
2211 .to_pixels(bounds.size.into(), rem_size)
2212 .map(|edge| window.pixel_snap(*edge));
2213 let padding_size = size(padding.left + padding.right, padding.top + padding.bottom);
2214 let padded_content_size = self.content_size + padding_size;
2221 let scroll_max = (padded_content_size - bounds.size)
2222 .map(round_to_two_decimals)
2223 .max(&Default::default());
2224 let mut scroll_offset = scroll_offset.borrow_mut();
2227
2228 scroll_offset.x = scroll_offset.x.clamp(-scroll_max.width, px(0.));
2229 if scroll_to_bottom {
2230 scroll_offset.y = -scroll_max.height;
2231 } else {
2232 scroll_offset.y = scroll_offset.y.clamp(-scroll_max.height, px(0.));
2233 }
2234
2235 if let Some(mut scroll_handle_state) = tracked_scroll_handle {
2236 scroll_handle_state.max_offset = scroll_max;
2237 scroll_handle_state.bounds = bounds;
2238 }
2239
2240 *scroll_offset
2241 } else {
2242 Point::default()
2243 }
2244 }
2245
2246 pub fn paint(
2255 &mut self,
2256 global_id: Option<&GlobalElementId>,
2257 _inspector_id: Option<&InspectorElementId>,
2258 bounds: Bounds<Pixels>,
2259 hitbox: Option<&Hitbox>,
2260 window: &mut Window,
2261 cx: &mut App,
2262 f: impl FnOnce(&Style, &mut Window, &mut App),
2263 ) {
2264 self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(window));
2265 window.with_optional_element_state::<InteractiveElementState, _>(
2266 global_id,
2267 |element_state, window| {
2268 let mut element_state =
2269 element_state.map(|element_state| element_state.unwrap_or_default());
2270
2271 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
2272
2273 #[cfg(any(feature = "test-support", test))]
2274 if let Some(debug_selector) = &self.debug_selector {
2275 window
2276 .next_frame
2277 .record_debug_bounds(debug_selector.clone(), bounds);
2278 }
2279
2280 self.paint_hover_group_handler(window, cx);
2281
2282 if !self.resize_listeners.is_empty() {
2283 if let Some(element_state) = element_state.as_mut() {
2284 let size_changed = element_state
2285 .prev_bounds
2286 .map(|prev| prev.size != bounds.size)
2287 .unwrap_or(true);
2288
2289 if size_changed {
2290 element_state.prev_bounds = Some(bounds);
2291 let resize_event = ResizeEvent {
2292 size: bounds.size,
2293 bounds,
2294 };
2295 for listener in &self.resize_listeners {
2296 listener(&resize_event, window, cx);
2297 }
2298 }
2299 }
2300 }
2301
2302 if style.visibility == Visibility::Hidden {
2303 return ((), element_state);
2304 }
2305
2306 let mut tab_group = None;
2307 if self.tab_group {
2308 tab_group = self.tab_index;
2309 }
2310 if let Some(focus_handle) = &self.tracked_focus_handle {
2311 window.next_frame.tab_stops.insert(focus_handle);
2312 }
2313
2314 window.with_element_opacity(style.opacity, |window| {
2315 style.paint(bounds, window, cx, |window: &mut Window, cx: &mut App| {
2316 window.with_text_style(style.text_style().cloned(), |window| {
2317 window.with_content_mask(
2318 style.overflow_mask(bounds, window.rem_size()),
2319 |window| {
2320 window.with_tab_group(tab_group, |window| {
2321 if let Some(hitbox) = hitbox {
2322 #[cfg(debug_assertions)]
2323 self.paint_debug_info(
2324 global_id, hitbox, &style, window, cx,
2325 );
2326
2327 if let Some(drag) = cx.active_drag.as_ref() {
2328 if let Some(mouse_cursor) = drag.cursor_style {
2329 window.set_window_cursor_style(mouse_cursor);
2330 }
2331 } else {
2332 if let Some(mouse_cursor) = style.mouse_cursor {
2333 window.set_cursor_style(mouse_cursor, hitbox);
2334 }
2335 }
2336
2337 if let Some(group) = self.group.clone() {
2338 GroupHitboxes::push(group, hitbox.id, cx);
2339 }
2340
2341 if let Some(area) = self.window_control {
2342 window.insert_window_control_hitbox(
2343 area,
2344 hitbox.clone(),
2345 );
2346 }
2347
2348 self.paint_mouse_listeners(
2349 hitbox,
2350 element_state.as_mut(),
2351 window,
2352 cx,
2353 );
2354 self.paint_scroll_listener(hitbox, &style, window, cx);
2355 }
2356
2357 self.paint_keyboard_listeners(window, cx);
2358
2359 if window.a11y.is_active()
2360 && let Some(global_id) = global_id
2361 && !self.a11y_action_listeners.is_empty()
2362 {
2363 let node_id = global_id.accesskit_node_id();
2364 for (action, listener) in
2365 self.a11y_action_listeners.drain(..)
2366 {
2367 window.on_a11y_action(node_id, action, listener);
2368 }
2369 }
2370
2371 f(&style, window, cx);
2372
2373 if let Some(_hitbox) = hitbox {
2374 #[cfg(any(feature = "inspector", debug_assertions))]
2375 window.insert_inspector_hitbox(
2376 _hitbox.id,
2377 _inspector_id,
2378 cx,
2379 );
2380
2381 if let Some(group) = self.group.as_ref() {
2382 GroupHitboxes::pop(group, cx);
2383 }
2384 }
2385 })
2386 },
2387 );
2388 });
2389 });
2390 });
2391
2392 ((), element_state)
2393 },
2394 );
2395 }
2396
2397 #[cfg(debug_assertions)]
2398 fn paint_debug_info(
2399 &self,
2400 global_id: Option<&GlobalElementId>,
2401 hitbox: &Hitbox,
2402 style: &Style,
2403 window: &mut Window,
2404 cx: &mut App,
2405 ) {
2406 use crate::{BorderStyle, TextAlign};
2407
2408 if let Some(global_id) = global_id.as_ref().filter(|_| {
2409 (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
2410 && hitbox.is_hovered(window)
2411 }) {
2412 const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
2413 let element_id = format!("{:?}", global_id);
2414 let str_len = element_id.len();
2415
2416 let render_debug_text = |window: &mut Window| {
2417 if let Some(text) = window
2418 .text_system()
2419 .shape_text(
2420 element_id.into(),
2421 FONT_SIZE,
2422 &[window.text_style().to_run(str_len)],
2423 None,
2424 None,
2425 )
2426 .ok()
2427 .and_then(|mut text| text.pop())
2428 {
2429 text.paint(hitbox.origin, FONT_SIZE, TextAlign::Left, None, window, cx)
2430 .ok();
2431
2432 let text_bounds = crate::Bounds {
2433 origin: hitbox.origin,
2434 size: text.size(FONT_SIZE),
2435 };
2436 if let Some(source_location) = self.source_location.filter(|_| {
2437 text_bounds.contains(&window.mouse_position())
2438 && window.modifiers().secondary()
2439 }) {
2440 let secondary_held = window.modifiers().secondary();
2441 window.on_key_event({
2442 move |e: &crate::ModifiersChangedEvent, _phase, window, _cx| {
2443 if e.modifiers.secondary() != secondary_held
2444 && text_bounds.contains(&window.mouse_position())
2445 {
2446 window.refresh();
2447 }
2448 }
2449 });
2450
2451 let was_hovered = hitbox.is_hovered(window);
2452 let current_view = window.current_view();
2453 window.on_mouse_event({
2454 let hitbox = hitbox.clone();
2455 move |_: &MouseMoveEvent, phase, window, cx| {
2456 if phase == DispatchPhase::Capture {
2457 let hovered = hitbox.is_hovered(window);
2458 if hovered != was_hovered {
2459 cx.notify(current_view)
2460 }
2461 }
2462 }
2463 });
2464
2465 window.on_mouse_event({
2466 let hitbox = hitbox.clone();
2467 let location = source_location;
2468 move |e: &crate::MouseDownEvent, phase, window, cx| {
2469 if text_bounds.contains(&e.position)
2470 && phase.capture()
2471 && hitbox.is_hovered(window)
2472 {
2473 cx.stop_propagation();
2474 let Ok(dir) = std::env::current_dir() else {
2475 return;
2476 };
2477
2478 eprintln!(
2479 "This element was created at:\n{}:{}:{}",
2480 dir.join(location.file()).to_string_lossy(),
2481 location.line(),
2482 location.column()
2483 );
2484 }
2485 }
2486 });
2487 window.paint_quad(crate::outline(
2488 crate::Bounds {
2489 origin: hitbox.origin
2490 + crate::point(crate::px(0.), FONT_SIZE - px(2.)),
2491 size: crate::Size {
2492 width: text_bounds.size.width,
2493 height: crate::px(1.),
2494 },
2495 },
2496 crate::red(),
2497 BorderStyle::default(),
2498 ))
2499 }
2500 }
2501 };
2502
2503 window.with_text_style(
2504 Some(crate::TextStyleRefinement {
2505 color: Some(crate::red()),
2506 line_height: Some(FONT_SIZE.into()),
2507 background_color: Some(crate::white()),
2508 ..Default::default()
2509 }),
2510 render_debug_text,
2511 )
2512 }
2513 }
2514
2515 fn paint_mouse_listeners(
2516 &mut self,
2517 hitbox: &Hitbox,
2518 element_state: Option<&mut InteractiveElementState>,
2519 window: &mut Window,
2520 cx: &mut App,
2521 ) {
2522 let is_focused = self
2523 .tracked_focus_handle
2524 .as_ref()
2525 .map(|handle| handle.is_focused(window))
2526 .unwrap_or(false);
2527
2528 if let Some(focus_handle) = self.tracked_focus_handle.clone() {
2532 let hitbox = hitbox.clone();
2533 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _| {
2534 if phase == DispatchPhase::Bubble
2535 && hitbox.is_hovered(window)
2536 && !window.default_prevented()
2537 {
2538 window.focus(&focus_handle);
2539 window.prevent_default();
2542 }
2543 });
2544 }
2545
2546 for listener in self.mouse_down_listeners.drain(..) {
2547 let hitbox = hitbox.clone();
2548 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
2549 listener(event, phase, &hitbox, window, cx);
2550 })
2551 }
2552
2553 for listener in self.mouse_up_listeners.drain(..) {
2554 let hitbox = hitbox.clone();
2555 window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
2556 listener(event, phase, &hitbox, window, cx);
2557 })
2558 }
2559
2560 for listener in self.mouse_move_listeners.drain(..) {
2561 let hitbox = hitbox.clone();
2562 window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
2563 listener(event, phase, &hitbox, window, cx);
2564 })
2565 }
2566
2567 for listener in self.mouse_exit_listeners.drain(..) {
2568 let hitbox = hitbox.clone();
2569 window.on_mouse_event(move |event: &MouseExitEvent, phase, window, cx| {
2570 listener(event, phase, &hitbox, window, cx);
2571 })
2572 }
2573
2574 for listener in self.file_drop_exit_listeners.drain(..) {
2575 let hitbox = hitbox.clone();
2576 window.on_mouse_event(move |event: &FileDropEvent, phase, window, cx| {
2577 listener(event, phase, &hitbox, window, cx);
2578 })
2579 }
2580
2581 for listener in self.scroll_wheel_listeners.drain(..) {
2582 let hitbox = hitbox.clone();
2583 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
2584 listener(event, phase, &hitbox, window, cx);
2585 })
2586 }
2587
2588 for listener in self.pinch_listeners.drain(..) {
2589 let hitbox = hitbox.clone();
2590 window.on_mouse_event(move |event: &PinchEvent, phase, window, cx| {
2591 listener(event, phase, &hitbox, window, cx);
2592 })
2593 }
2594
2595 if self.hover_style.is_some()
2596 || self.base_style.mouse_cursor.is_some()
2597 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
2598 {
2599 let hitbox = hitbox.clone();
2600 let was_hovered = hitbox.is_hovered(window);
2601 let current_view = window.current_view();
2602 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2603 let hovered = hitbox.is_hovered(window);
2604 if phase == DispatchPhase::Capture && hovered != was_hovered {
2605 cx.notify(current_view);
2606 }
2607 });
2608 }
2609 let drag_cursor_style = self.base_style.as_ref().mouse_cursor;
2610
2611 let mut drag_listener = mem::take(&mut self.drag_listener);
2612 let drop_listeners = mem::take(&mut self.drop_listeners);
2613 let click_listeners = mem::take(&mut self.click_listeners);
2614 let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
2615
2616 if !drop_listeners.is_empty() {
2617 let hitbox = hitbox.clone();
2618 window.on_mouse_event({
2619 move |_: &MouseUpEvent, phase, window, cx| {
2620 if let Some(drag) = &cx.active_drag
2621 && phase == DispatchPhase::Bubble
2622 && hitbox.is_hovered(window)
2623 {
2624 let drag_state_type = drag.value.as_ref().type_id();
2625 for (drop_state_type, listener) in &drop_listeners {
2626 if *drop_state_type == drag_state_type {
2627 let drag = cx
2628 .active_drag
2629 .take()
2630 .expect("checked for type drag state type above");
2631
2632 let mut can_drop = true;
2633 if let Some(predicate) = &can_drop_predicate {
2634 can_drop = predicate(drag.value.as_ref(), window, cx);
2635 }
2636
2637 if can_drop {
2638 listener(drag.value.as_ref(), window, cx);
2639 window.refresh();
2640 cx.stop_propagation();
2641 }
2642 }
2643 }
2644 }
2645 }
2646 });
2647 }
2648
2649 if let Some(element_state) = element_state {
2650 if !click_listeners.is_empty() || drag_listener.is_some() {
2651 let pending_mouse_down = element_state
2652 .pending_mouse_down
2653 .get_or_insert_with(Default::default)
2654 .clone();
2655
2656 let clicked_state = element_state
2657 .clicked_state
2658 .get_or_insert_with(Default::default)
2659 .clone();
2660
2661 window.on_mouse_event({
2662 let pending_mouse_down = pending_mouse_down.clone();
2663 let hitbox = hitbox.clone();
2664 move |event: &MouseDownEvent, phase, window, _cx| {
2665 if phase == DispatchPhase::Bubble
2666 && event.button == MouseButton::Left
2667 && hitbox.is_hovered(window)
2668 {
2669 *pending_mouse_down.borrow_mut() = Some(event.clone());
2670 window.refresh();
2671 }
2672 }
2673 });
2674
2675 window.on_mouse_event({
2676 let pending_mouse_down = pending_mouse_down.clone();
2677 let hitbox = hitbox.clone();
2678 move |event: &MouseMoveEvent, phase, window, cx| {
2679 if phase == DispatchPhase::Capture {
2680 return;
2681 }
2682
2683 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2684 if let Some(mouse_down) = pending_mouse_down.clone()
2685 && !cx.has_active_drag()
2686 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
2687 && let Some((drag_value, drag_listener)) = drag_listener.take()
2688 {
2689 *clicked_state.borrow_mut() = ElementClickedState::default();
2690 let cursor_offset = event.position - hitbox.origin;
2691 let drag =
2692 (drag_listener)(drag_value.as_ref(), cursor_offset, window, cx);
2693 cx.active_drag = Some(AnyDrag {
2694 view: drag,
2695 value: drag_value,
2696 cursor_offset,
2697 cursor_style: drag_cursor_style,
2698 });
2699 pending_mouse_down.take();
2700 window.refresh();
2701 cx.stop_propagation();
2702 }
2703 }
2704 });
2705
2706 if is_focused {
2707 window.on_key_event({
2709 let click_listeners = click_listeners.clone();
2710 let hitbox = hitbox.clone();
2711 move |event: &KeyUpEvent, phase, window, cx| {
2712 if phase.bubble() && !window.default_prevented() {
2713 let stroke = &event.keystroke;
2714 let keyboard_button = if stroke.key.eq("enter") {
2715 Some(KeyboardButton::Enter)
2716 } else if stroke.key.eq("space") {
2717 Some(KeyboardButton::Space)
2718 } else {
2719 None
2720 };
2721
2722 if let Some(button) = keyboard_button
2723 && !stroke.modifiers.modified()
2724 {
2725 let click_event = ClickEvent::Keyboard(KeyboardClickEvent {
2726 button,
2727 bounds: hitbox.bounds,
2728 });
2729
2730 for listener in &click_listeners {
2731 listener(&click_event, window, cx);
2732 }
2733 }
2734 }
2735 }
2736 });
2737 }
2738
2739 window.on_mouse_event({
2740 let mut captured_mouse_down = None;
2741 let hitbox = hitbox.clone();
2742 move |event: &MouseUpEvent, phase, window, cx| match phase {
2743 DispatchPhase::Capture => {
2747 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2748 if pending_mouse_down.is_some() && hitbox.is_hovered(window) {
2749 captured_mouse_down = pending_mouse_down.take();
2750 window.refresh();
2751 } else if pending_mouse_down.is_some() {
2752 pending_mouse_down.take();
2758 window.refresh();
2759 }
2760 }
2761 DispatchPhase::Bubble => {
2763 if let Some(mouse_down) = captured_mouse_down.take() {
2764 let mouse_click = ClickEvent::Mouse(MouseClickEvent {
2765 down: mouse_down,
2766 up: event.clone(),
2767 });
2768 for listener in &click_listeners {
2769 listener(&mouse_click, window, cx);
2770 }
2771 }
2772 }
2773 }
2774 });
2775 }
2776
2777 if let Some(hover_listener) = self.hover_listener.take() {
2778 let was_hovered = element_state
2779 .hover_state
2780 .get_or_insert_with(Default::default)
2781 .clone();
2782 let has_mouse_down = element_state
2783 .pending_mouse_down
2784 .get_or_insert_with(Default::default)
2785 .clone();
2786 let hover_listener = Rc::new(hover_listener);
2787 let hover_listener_state = was_hovered.clone();
2788 let update_hover = move |is_hovered: bool, window: &mut Window, cx: &mut App| {
2789 let mut was_hovered = hover_listener_state.borrow_mut();
2790 if is_hovered != *was_hovered {
2791 *was_hovered = is_hovered;
2792 drop(was_hovered);
2793 hover_listener(&is_hovered, window, cx);
2794 }
2795 };
2796 let hover_listener_mode = self.hover_listener_mode;
2797
2798 if has_mouse_down.borrow().is_none() {
2799 let is_hovered =
2800 !cx.has_active_drag() && hover_listener_mode.is_hovered(hitbox, window);
2801 if is_hovered != *was_hovered.borrow() {
2802 let update_hover = update_hover.clone();
2803 window.defer(cx, move |window, cx| {
2804 update_hover(is_hovered, window, cx);
2805 });
2806 }
2807 }
2808
2809 window.on_mouse_event({
2810 let update_hover = update_hover.clone();
2811 let hitbox = hitbox.clone();
2812 move |_: &MouseMoveEvent, phase, window, cx| {
2813 if phase == DispatchPhase::Bubble {
2814 let is_hovered = has_mouse_down.borrow().is_none()
2815 && !cx.has_active_drag()
2816 && hover_listener_mode.is_hovered(&hitbox, window);
2817 update_hover(is_hovered, window, cx);
2818 }
2819 }
2820 });
2821
2822 window.on_mouse_event(move |_: &MouseExitEvent, phase, window, cx| {
2825 if phase == DispatchPhase::Bubble {
2826 update_hover(false, window, cx);
2827 }
2828 });
2829 }
2830
2831 if let Some(tooltip_builder) = self.tooltip_builder.take() {
2832 let active_tooltip = element_state
2833 .active_tooltip
2834 .get_or_insert_with(Default::default)
2835 .clone();
2836 let pending_mouse_down = element_state
2837 .pending_mouse_down
2838 .get_or_insert_with(Default::default)
2839 .clone();
2840
2841 let tooltip_is_hoverable = tooltip_builder.hoverable;
2842 let build_tooltip = Rc::new(move |window: &mut Window, cx: &mut App| {
2843 Some(((tooltip_builder.build)(window, cx), tooltip_is_hoverable))
2844 });
2845 let check_is_hovered_during_prepaint = Rc::new({
2847 let pending_mouse_down = pending_mouse_down.clone();
2848 let source_bounds = hitbox.bounds;
2849 move |window: &Window| {
2850 pending_mouse_down.borrow().is_none()
2851 && source_bounds.contains(&window.mouse_position())
2852 }
2853 });
2854 let check_is_hovered = Rc::new({
2855 let hitbox = hitbox.clone();
2856 move |window: &Window| {
2857 pending_mouse_down.borrow().is_none() && hitbox.is_hovered(window)
2858 }
2859 });
2860 register_tooltip_mouse_handlers(
2861 &active_tooltip,
2862 self.tooltip_id,
2863 build_tooltip,
2864 check_is_hovered,
2865 check_is_hovered_during_prepaint,
2866 self.tooltip_show_delay,
2867 window,
2868 );
2869 }
2870
2871 let active_state = element_state
2872 .clicked_state
2873 .get_or_insert_with(Default::default)
2874 .clone();
2875 if active_state.borrow().is_clicked() {
2876 window.on_mouse_event(move |_: &MouseUpEvent, phase, window, _cx| {
2877 if phase == DispatchPhase::Capture {
2878 *active_state.borrow_mut() = ElementClickedState::default();
2879 window.refresh();
2880 }
2881 });
2882 } else {
2883 let active_group_hitbox = self
2884 .group_active_style
2885 .as_ref()
2886 .and_then(|group_active| GroupHitboxes::get(&group_active.group, cx));
2887 let hitbox = hitbox.clone();
2888 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _cx| {
2889 if phase == DispatchPhase::Bubble && !window.default_prevented() {
2890 let group_hovered = active_group_hitbox
2891 .is_some_and(|group_hitbox_id| group_hitbox_id.is_hovered(window));
2892 let element_hovered = hitbox.is_hovered(window);
2893 if group_hovered || element_hovered {
2894 *active_state.borrow_mut() = ElementClickedState {
2895 group: group_hovered,
2896 element: element_hovered,
2897 };
2898 window.refresh();
2899 }
2900 }
2901 });
2902 }
2903 }
2904 }
2905
2906 fn paint_keyboard_listeners(&mut self, window: &mut Window, _cx: &mut App) {
2907 let key_down_listeners = mem::take(&mut self.key_down_listeners);
2908 let key_up_listeners = mem::take(&mut self.key_up_listeners);
2909 let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners);
2910 let action_listeners = mem::take(&mut self.action_listeners);
2911 if let Some(context) = self.key_context.clone() {
2912 window.set_key_context(context);
2913 }
2914
2915 for listener in key_down_listeners {
2916 window.on_key_event(move |event: &KeyDownEvent, phase, window, cx| {
2917 listener(event, phase, window, cx);
2918 })
2919 }
2920
2921 for listener in key_up_listeners {
2922 window.on_key_event(move |event: &KeyUpEvent, phase, window, cx| {
2923 listener(event, phase, window, cx);
2924 })
2925 }
2926
2927 for listener in modifiers_changed_listeners {
2928 window.on_modifiers_changed(move |event: &ModifiersChangedEvent, window, cx| {
2929 listener(event, window, cx);
2930 })
2931 }
2932
2933 for (action_type, listener) in action_listeners {
2934 window.on_action(action_type, listener)
2935 }
2936 }
2937
2938 fn paint_hover_group_handler(&self, window: &mut Window, cx: &mut App) {
2939 let group_hitbox = self
2940 .group_hover_style
2941 .as_ref()
2942 .and_then(|group_hover| GroupHitboxes::get(&group_hover.group, cx));
2943
2944 if let Some(group_hitbox) = group_hitbox {
2945 let was_hovered = group_hitbox.is_hovered(window);
2946 let current_view = window.current_view();
2947 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2948 let hovered = group_hitbox.is_hovered(window);
2949 if phase == DispatchPhase::Capture && hovered != was_hovered {
2950 cx.notify(current_view);
2951 }
2952 });
2953 }
2954 }
2955
2956 fn paint_scroll_listener(
2957 &self,
2958 hitbox: &Hitbox,
2959 style: &Style,
2960 window: &mut Window,
2961 _cx: &mut App,
2962 ) {
2963 if let Some(scroll_offset) = self.scroll_offset.clone() {
2964 let ongoing_scroll = self.ongoing_scroll.clone();
2965 let overflow = style.overflow;
2966 let allow_concurrent_scroll = style.allow_concurrent_scroll;
2967 let restrict_scroll_to_axis = style.restrict_scroll_to_axis;
2968 let line_height = window.line_height();
2969 let hitbox = hitbox.clone();
2970 let current_view = window.current_view();
2971 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
2972 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
2973 let mut scroll_offset = scroll_offset.borrow_mut();
2974 let old_scroll_offset = *scroll_offset;
2975 let mut delta = event.delta.pixel_delta(line_height);
2976
2977 if restrict_scroll_to_axis
2978 && event.delta.precise()
2979 && let Some(ongoing_scroll) = &ongoing_scroll
2980 {
2981 ongoing_scroll
2982 .borrow_mut()
2983 .filter(&mut delta, event.touch_phase);
2984 }
2985
2986 let mut delta_x = match overflow.x {
2987 Overflow::Scroll if !delta.x.is_zero() => delta.x,
2988 Overflow::Scroll
2989 if !restrict_scroll_to_axis && overflow.y != Overflow::Scroll =>
2990 {
2991 delta.y
2992 }
2993 _ => Pixels::ZERO,
2994 };
2995 let mut delta_y = match overflow.y {
2996 Overflow::Scroll if !delta.y.is_zero() => delta.y,
2997 Overflow::Scroll
2998 if !restrict_scroll_to_axis && overflow.x != Overflow::Scroll =>
2999 {
3000 delta.x
3001 }
3002 _ => Pixels::ZERO,
3003 };
3004 if !allow_concurrent_scroll && !delta_x.is_zero() && !delta_y.is_zero() {
3005 if delta_x.abs() > delta_y.abs() {
3006 delta_y = Pixels::ZERO;
3007 } else {
3008 delta_x = Pixels::ZERO;
3009 }
3010 }
3011 scroll_offset.y += delta_y;
3012 scroll_offset.x += delta_x;
3013 if *scroll_offset != old_scroll_offset {
3014 cx.notify(current_view);
3015 }
3016 }
3017 });
3018 }
3019 }
3020
3021 pub fn compute_style(
3023 &self,
3024 global_id: Option<&GlobalElementId>,
3025 hitbox: Option<&Hitbox>,
3026 window: &mut Window,
3027 cx: &mut App,
3028 ) -> Style {
3029 window.with_optional_element_state(global_id, |element_state, window| {
3030 let mut element_state =
3031 element_state.map(|element_state| element_state.unwrap_or_default());
3032 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
3033 (style, element_state)
3034 })
3035 }
3036
3037 fn compute_style_internal(
3039 &self,
3040 hitbox: Option<&Hitbox>,
3041 element_state: Option<&mut InteractiveElementState>,
3042 window: &mut Window,
3043 cx: &mut App,
3044 ) -> Style {
3045 let mut style = Style::default();
3046 style.refine(&self.base_style);
3047
3048 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
3049 if let Some(in_focus_style) = self.in_focus_style.as_ref()
3050 && focus_handle.within_focused(window, cx)
3051 {
3052 style.refine(in_focus_style);
3053 }
3054
3055 if let Some(focus_style) = self.focus_style.as_ref()
3056 && focus_handle.is_focused(window)
3057 {
3058 style.refine(focus_style);
3059 }
3060 }
3061
3062 if let Some(hitbox) = hitbox {
3063 if !cx.has_active_drag() {
3064 if let Some(group_hover) = self.group_hover_style.as_ref()
3065 && let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx)
3066 && group_hitbox_id.is_hovered(window)
3067 {
3068 style.refine(&group_hover.style);
3069 }
3070
3071 if let Some(hover_style) = self.hover_style.as_ref()
3072 && hitbox.is_hovered(window)
3073 {
3074 style.refine(hover_style);
3075 }
3076 }
3077
3078 if let Some(drag) = cx.active_drag.take() {
3079 let mut can_drop = true;
3080 if let Some(can_drop_predicate) = &self.can_drop_predicate {
3081 can_drop = can_drop_predicate(drag.value.as_ref(), window, cx);
3082 }
3083
3084 if can_drop {
3085 for (state_type, group_drag_style) in &self.group_drag_over_styles {
3086 if let Some(group_hitbox_id) =
3087 GroupHitboxes::get(&group_drag_style.group, cx)
3088 && *state_type == drag.value.as_ref().type_id()
3089 && group_hitbox_id.is_hovered(window)
3090 {
3091 style.refine(&group_drag_style.style);
3092 }
3093 }
3094
3095 for (state_type, build_drag_over_style) in &self.drag_over_styles {
3096 if *state_type == drag.value.as_ref().type_id() && hitbox.is_hovered(window)
3097 {
3098 style.refine(&build_drag_over_style(drag.value.as_ref(), window, cx));
3099 }
3100 }
3101 }
3102
3103 style.mouse_cursor = drag.cursor_style;
3104 cx.active_drag = Some(drag);
3105 }
3106 }
3107
3108 if let Some(element_state) = element_state {
3109 let clicked_state = element_state
3110 .clicked_state
3111 .get_or_insert_with(Default::default)
3112 .borrow();
3113 if clicked_state.group
3114 && let Some(group) = self.group_active_style.as_ref()
3115 {
3116 style.refine(&group.style)
3117 }
3118
3119 if let Some(active_style) = self.active_style.as_ref()
3120 && clicked_state.element
3121 {
3122 style.refine(active_style)
3123 }
3124 }
3125
3126 style
3127 }
3128
3129 pub(crate) fn write_a11y_info(&self, node: &mut accesskit::Node) {
3130 if let Some(id) = &self.accessibility_id {
3131 node.set_author_id(id.to_string());
3132 }
3133 if let Some(label) = &self.aria_label {
3134 node.set_label(label.to_string());
3135 }
3136 if let Some(description) = &self.aria_description {
3137 node.set_description(description.to_string());
3138 }
3139 if let Some(value) = &self.aria_value {
3140 node.set_value(value.to_string());
3141 }
3142 if let Some(placeholder) = &self.aria_placeholder {
3143 node.set_placeholder(placeholder.to_string());
3144 }
3145 if self.aria_disabled {
3146 node.set_disabled();
3147 }
3148 if self.aria_required {
3149 node.set_required();
3150 }
3151 if let Some(invalid) = self.aria_invalid {
3152 node.set_invalid(invalid);
3153 }
3154 if self.aria_modal {
3155 node.set_modal();
3156 }
3157 if let Some(keyshortcuts) = &self.aria_keyshortcuts {
3158 node.set_keyboard_shortcut(keyshortcuts.to_string());
3159 }
3160 if let Some(selected) = self.aria_selected {
3161 node.set_selected(selected);
3162 }
3163 if let Some(expanded) = self.aria_expanded {
3164 node.set_expanded(expanded);
3165 }
3166 if let Some(toggled) = self.aria_toggled {
3167 node.set_toggled(toggled);
3168 }
3169 if let Some(value) = self.aria_numeric_value {
3170 node.set_numeric_value(value);
3171 }
3172 if let Some(value) = self.aria_min_numeric_value {
3173 node.set_min_numeric_value(value);
3174 }
3175 if let Some(value) = self.aria_max_numeric_value {
3176 node.set_max_numeric_value(value);
3177 }
3178 if let Some(orientation) = self.aria_orientation {
3179 node.set_orientation(orientation);
3180 }
3181 if let Some(level) = self.aria_level {
3182 node.set_level(level);
3183 }
3184 if let Some(position) = self.aria_position_in_set {
3185 node.set_position_in_set(position);
3186 }
3187 if let Some(size) = self.aria_size_of_set {
3188 node.set_size_of_set(size);
3189 }
3190 if let Some(index) = self.aria_row_index {
3191 node.set_row_index(index);
3192 }
3193 if let Some(index) = self.aria_column_index {
3194 node.set_column_index(index);
3195 }
3196 if let Some(count) = self.aria_row_count {
3197 node.set_row_count(count);
3198 }
3199 if let Some(count) = self.aria_column_count {
3200 node.set_column_count(count);
3201 }
3202 if !self.click_listeners.is_empty() {
3203 node.add_action(accesskit::Action::Click);
3204 }
3205 if self.tracked_focus_handle.is_some() || self.focusable {
3206 node.add_action(accesskit::Action::Focus);
3207 }
3208 for (action, _) in &self.a11y_action_listeners {
3209 node.add_action(*action);
3210 }
3211 }
3212}
3213
3214#[derive(Default)]
3217pub struct InteractiveElementState {
3218 pub(crate) focus_handle: Option<FocusHandle>,
3219 pub(crate) clicked_state: Option<Rc<RefCell<ElementClickedState>>>,
3220 pub(crate) hover_state: Option<Rc<RefCell<bool>>>,
3221 pub(crate) pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
3222 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
3223 ongoing_scroll: Option<Rc<RefCell<OngoingScroll>>>,
3224 pub(crate) active_tooltip: Option<Rc<RefCell<Option<ActiveTooltip>>>>,
3225 pub(crate) prev_bounds: Option<Bounds<Pixels>>,
3226}
3227
3228#[derive(Copy, Clone, Default, Eq, PartialEq)]
3230pub struct ElementClickedState {
3231 pub group: bool,
3233
3234 pub element: bool,
3236}
3237
3238impl ElementClickedState {
3239 fn is_clicked(&self) -> bool {
3240 self.group || self.element
3241 }
3242}
3243
3244pub(crate) enum ActiveTooltip {
3245 WaitingForShow { _task: Task<()> },
3247 Visible {
3249 tooltip: AnyTooltip,
3250 is_hoverable: bool,
3251 },
3252 WaitingForHide {
3255 tooltip: AnyTooltip,
3256 _task: Task<()>,
3257 },
3258}
3259
3260pub(crate) fn clear_active_tooltip(
3261 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3262 window: &mut Window,
3263) {
3264 match active_tooltip.borrow_mut().take() {
3265 None => {}
3266 Some(ActiveTooltip::WaitingForShow { .. }) => {}
3267 Some(ActiveTooltip::Visible { .. }) => window.refresh(),
3268 Some(ActiveTooltip::WaitingForHide { .. }) => window.refresh(),
3269 }
3270}
3271
3272pub(crate) fn clear_active_tooltip_if_not_hoverable(
3273 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3274 window: &mut Window,
3275) {
3276 let should_clear = match active_tooltip.borrow().as_ref() {
3277 None => false,
3278 Some(ActiveTooltip::WaitingForShow { .. }) => false,
3279 Some(ActiveTooltip::Visible { is_hoverable, .. }) => !is_hoverable,
3280 Some(ActiveTooltip::WaitingForHide { .. }) => false,
3281 };
3282 if should_clear {
3283 active_tooltip.borrow_mut().take();
3284 window.refresh();
3285 }
3286}
3287
3288pub(crate) fn set_tooltip_on_window(
3289 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3290 window: &mut Window,
3291) -> Option<TooltipId> {
3292 let tooltip = match active_tooltip.borrow().as_ref() {
3293 None => return None,
3294 Some(ActiveTooltip::WaitingForShow { .. }) => return None,
3295 Some(ActiveTooltip::Visible { tooltip, .. }) => tooltip.clone(),
3296 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => tooltip.clone(),
3297 };
3298 Some(window.set_tooltip(tooltip))
3299}
3300
3301pub(crate) fn register_tooltip_mouse_handlers(
3302 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3303 tooltip_id: Option<TooltipId>,
3304 build_tooltip: Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3305 check_is_hovered: Rc<dyn Fn(&Window) -> bool>,
3306 check_is_hovered_during_prepaint: Rc<dyn Fn(&Window) -> bool>,
3307 show_delay: Option<Duration>,
3308 window: &mut Window,
3309) {
3310 let current_view = window.current_view();
3311 let show_delay = show_delay.unwrap_or(DEFAULT_TOOLTIP_SHOW_DELAY);
3312
3313 window.on_mouse_event({
3314 let active_tooltip = active_tooltip.clone();
3315 let build_tooltip = build_tooltip.clone();
3316 let check_is_hovered = check_is_hovered.clone();
3317 move |_: &MouseMoveEvent, phase, window, cx| {
3318 handle_tooltip_mouse_move(
3319 &active_tooltip,
3320 &build_tooltip,
3321 &check_is_hovered,
3322 &check_is_hovered_during_prepaint,
3323 tooltip_id,
3324 current_view,
3325 phase,
3326 show_delay,
3327 window,
3328 cx,
3329 )
3330 }
3331 });
3332
3333 window.on_mouse_event({
3334 let active_tooltip = active_tooltip.clone();
3335 move |_: &MouseDownEvent, _phase, window: &mut Window, _cx| {
3336 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3337 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3338 }
3339 }
3340 });
3341
3342 window.on_mouse_event({
3343 let active_tooltip = active_tooltip.clone();
3344 move |_: &ScrollWheelEvent, _phase, window: &mut Window, _cx| {
3345 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3346 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3347 }
3348 }
3349 });
3350}
3351
3352fn handle_tooltip_mouse_move(
3364 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3365 build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3366 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3367 check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
3368 tooltip_id: Option<TooltipId>,
3369 current_view: EntityId,
3370 phase: DispatchPhase,
3371 show_delay: Duration,
3372 window: &mut Window,
3373 cx: &mut App,
3374) {
3375 enum Action {
3378 None,
3379 CancelShow,
3380 ScheduleShow,
3381 CheckVisible,
3382 }
3383
3384 let action = match active_tooltip.borrow().as_ref() {
3385 None => {
3386 let is_hovered = check_is_hovered(window);
3387 if is_hovered && phase.bubble() {
3388 Action::ScheduleShow
3389 } else {
3390 Action::None
3391 }
3392 }
3393 Some(ActiveTooltip::WaitingForShow { .. }) => {
3394 let is_hovered = check_is_hovered(window);
3395 if is_hovered {
3396 Action::None
3397 } else {
3398 Action::CancelShow
3399 }
3400 }
3401 Some(ActiveTooltip::Visible { is_hoverable, .. }) => {
3402 if phase.capture()
3403 && !check_is_hovered(window)
3404 && (!*is_hoverable
3405 || !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3406 {
3407 Action::CheckVisible
3408 } else {
3409 Action::None
3410 }
3411 }
3412 Some(ActiveTooltip::WaitingForHide { .. }) => {
3413 if phase.capture()
3414 && (check_is_hovered(window)
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 };
3423
3424 match action {
3425 Action::None => {}
3426 Action::CancelShow => {
3427 active_tooltip.borrow_mut().take();
3429 }
3430 Action::ScheduleShow => {
3431 let delayed_show_task = window.spawn(cx, {
3432 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3433 let build_tooltip = build_tooltip.clone();
3434 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3435 async move |cx| {
3436 cx.background_executor().timer(show_delay).await;
3437 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3438 return;
3439 };
3440 cx.update(|window, cx| {
3441 let new_tooltip =
3442 build_tooltip(window, cx).map(|(view, tooltip_is_hoverable)| {
3443 let weak_active_tooltip = Rc::downgrade(&active_tooltip);
3444 ActiveTooltip::Visible {
3445 tooltip: AnyTooltip {
3446 view,
3447 mouse_position: window.mouse_position(),
3448 check_visible_and_update: Rc::new(
3449 move |tooltip_bounds, window, cx| {
3450 let Some(active_tooltip) =
3451 weak_active_tooltip.upgrade()
3452 else {
3453 return false;
3454 };
3455 handle_tooltip_check_visible_and_update(
3456 &active_tooltip,
3457 tooltip_is_hoverable,
3458 &check_is_hovered_during_prepaint,
3459 tooltip_bounds,
3460 window,
3461 cx,
3462 )
3463 },
3464 ),
3465 },
3466 is_hoverable: tooltip_is_hoverable,
3467 }
3468 });
3469 *active_tooltip.borrow_mut() = new_tooltip;
3470 window.refresh();
3471 })
3472 .ok();
3473 }
3474 });
3475 active_tooltip
3476 .borrow_mut()
3477 .replace(ActiveTooltip::WaitingForShow {
3478 _task: delayed_show_task,
3479 });
3480 }
3481 Action::CheckVisible => cx.notify(current_view),
3482 }
3483}
3484
3485fn handle_tooltip_check_visible_and_update(
3489 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3490 tooltip_is_hoverable: bool,
3491 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3492 tooltip_bounds: Bounds<Pixels>,
3493 window: &mut Window,
3494 cx: &mut App,
3495) -> bool {
3496 enum Action {
3499 None,
3500 Hide,
3501 ScheduleHide(AnyTooltip),
3502 CancelHide(AnyTooltip),
3503 }
3504
3505 let is_hovered = check_is_hovered(window)
3506 || (tooltip_is_hoverable && tooltip_bounds.contains(&window.mouse_position()));
3507 let action = match active_tooltip.borrow().as_ref() {
3508 Some(ActiveTooltip::Visible { tooltip, .. }) => {
3509 if is_hovered {
3510 Action::None
3511 } else {
3512 if tooltip_is_hoverable {
3513 Action::ScheduleHide(tooltip.clone())
3514 } else {
3515 Action::Hide
3516 }
3517 }
3518 }
3519 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => {
3520 if is_hovered {
3521 Action::CancelHide(tooltip.clone())
3522 } else {
3523 Action::None
3524 }
3525 }
3526 None | Some(ActiveTooltip::WaitingForShow { .. }) => Action::None,
3527 };
3528
3529 match action {
3530 Action::None => {}
3531 Action::Hide => clear_active_tooltip(active_tooltip, window),
3532 Action::ScheduleHide(tooltip) => {
3533 let delayed_hide_task = window.spawn(cx, {
3534 let active_tooltip = active_tooltip.clone();
3535 async move |cx| {
3536 cx.background_executor()
3537 .timer(HOVERABLE_TOOLTIP_HIDE_DELAY)
3538 .await;
3539 if active_tooltip.borrow_mut().take().is_some() {
3540 cx.update(|window, _cx| window.refresh()).ok();
3541 }
3542 }
3543 });
3544 active_tooltip
3545 .borrow_mut()
3546 .replace(ActiveTooltip::WaitingForHide {
3547 tooltip,
3548 _task: delayed_hide_task,
3549 });
3550 }
3551 Action::CancelHide(tooltip) => {
3552 active_tooltip.borrow_mut().replace(ActiveTooltip::Visible {
3554 tooltip,
3555 is_hoverable: true,
3556 });
3557 }
3558 }
3559
3560 active_tooltip.borrow().is_some()
3561}
3562
3563#[derive(Default)]
3564pub(crate) struct GroupHitboxes(HashMap<SharedString, SmallVec<[HitboxId; 1]>>);
3565
3566impl Global for GroupHitboxes {}
3567
3568impl GroupHitboxes {
3569 pub fn get(name: &SharedString, cx: &mut App) -> Option<HitboxId> {
3570 cx.default_global::<Self>()
3571 .0
3572 .get(name)
3573 .and_then(|bounds_stack| bounds_stack.last())
3574 .cloned()
3575 }
3576
3577 pub fn push(name: SharedString, hitbox_id: HitboxId, cx: &mut App) {
3578 cx.default_global::<Self>()
3579 .0
3580 .entry(name)
3581 .or_default()
3582 .push(hitbox_id);
3583 }
3584
3585 pub fn pop(name: &SharedString, cx: &mut App) {
3586 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
3587 }
3588}
3589
3590pub struct Stateful<E> {
3592 pub(crate) element: E,
3593}
3594
3595impl<E> Styled for Stateful<E>
3596where
3597 E: Styled,
3598{
3599 fn style(&mut self) -> &mut StyleRefinement {
3600 self.element.style()
3601 }
3602}
3603
3604impl<E> StatefulInteractiveElement for Stateful<E>
3605where
3606 E: Element,
3607 Self: InteractiveElement,
3608{
3609}
3610
3611impl<E> InteractiveElement for Stateful<E>
3612where
3613 E: InteractiveElement,
3614{
3615 fn interactivity(&mut self) -> &mut Interactivity {
3616 self.element.interactivity()
3617 }
3618}
3619
3620impl<E> Element for Stateful<E>
3621where
3622 E: Element,
3623{
3624 type RequestLayoutState = E::RequestLayoutState;
3625 type PrepaintState = E::PrepaintState;
3626
3627 fn id(&self) -> Option<ElementId> {
3628 self.element.id()
3629 }
3630
3631 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
3632 self.element.source_location()
3633 }
3634
3635 fn a11y_role(&self) -> Option<accesskit::Role> {
3636 self.element.a11y_role()
3637 }
3638
3639 fn write_a11y_info(&self, node: &mut accesskit::Node) {
3640 self.element.write_a11y_info(node);
3641 }
3642
3643 fn request_layout(
3644 &mut self,
3645 id: Option<&GlobalElementId>,
3646 inspector_id: Option<&InspectorElementId>,
3647 window: &mut Window,
3648 cx: &mut App,
3649 ) -> (LayoutId, Self::RequestLayoutState) {
3650 self.element.request_layout(id, inspector_id, window, cx)
3651 }
3652
3653 fn prepaint(
3654 &mut self,
3655 id: Option<&GlobalElementId>,
3656 inspector_id: Option<&InspectorElementId>,
3657 bounds: Bounds<Pixels>,
3658 state: &mut Self::RequestLayoutState,
3659 window: &mut Window,
3660 cx: &mut App,
3661 ) -> E::PrepaintState {
3662 self.element
3663 .prepaint(id, inspector_id, bounds, state, window, cx)
3664 }
3665
3666 fn paint(
3667 &mut self,
3668 id: Option<&GlobalElementId>,
3669 inspector_id: Option<&InspectorElementId>,
3670 bounds: Bounds<Pixels>,
3671 request_layout: &mut Self::RequestLayoutState,
3672 prepaint: &mut Self::PrepaintState,
3673 window: &mut Window,
3674 cx: &mut App,
3675 ) {
3676 self.element.paint(
3677 id,
3678 inspector_id,
3679 bounds,
3680 request_layout,
3681 prepaint,
3682 window,
3683 cx,
3684 );
3685 }
3686}
3687
3688impl<E> IntoElement for Stateful<E>
3689where
3690 E: Element,
3691{
3692 type Element = Self;
3693
3694 fn into_element(self) -> Self::Element {
3695 self
3696 }
3697}
3698
3699impl<E> ParentElement for Stateful<E>
3700where
3701 E: ParentElement,
3702{
3703 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
3704 self.element.extend(elements)
3705 }
3706}
3707
3708#[derive(Clone)]
3711pub struct ScrollAnchor {
3712 handle: ScrollHandle,
3713 last_origin: Rc<RefCell<Point<Pixels>>>,
3714}
3715
3716impl ScrollAnchor {
3717 pub fn for_handle(handle: ScrollHandle) -> Self {
3719 Self {
3720 handle,
3721 last_origin: Default::default(),
3722 }
3723 }
3724 pub fn scroll_to(&self, window: &mut Window, _cx: &mut App) {
3726 let this = self.clone();
3727
3728 window.on_next_frame(move |_, _| {
3729 let viewport_bounds = this.handle.bounds();
3730 let self_bounds = *this.last_origin.borrow();
3731 this.handle.set_offset(viewport_bounds.origin - self_bounds);
3732 });
3733 }
3734}
3735
3736#[derive(Default, Debug)]
3737struct ScrollHandleState {
3738 offset: Rc<RefCell<Point<Pixels>>>,
3739 ongoing_scroll: Rc<RefCell<OngoingScroll>>,
3740 bounds: Bounds<Pixels>,
3741 max_offset: Size<Pixels>,
3742 child_bounds: Vec<Bounds<Pixels>>,
3743 scroll_to_bottom: bool,
3744 overflow: Point<Overflow>,
3745 active_item: Option<ScrollActiveItem>,
3746}
3747
3748#[derive(Default, Debug, Clone, Copy)]
3749struct ScrollActiveItem {
3750 index: usize,
3751 strategy: ScrollStrategy,
3752}
3753
3754#[derive(Default, Debug, Clone, Copy)]
3755enum ScrollStrategy {
3756 #[default]
3757 FirstVisible,
3758 Top,
3759}
3760
3761#[derive(Clone, Debug)]
3765pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
3766
3767impl Default for ScrollHandle {
3768 fn default() -> Self {
3769 Self::new()
3770 }
3771}
3772
3773impl ScrollHandle {
3774 pub fn new() -> Self {
3776 Self(Rc::default())
3777 }
3778
3779 pub fn offset(&self) -> Point<Pixels> {
3781 *self.0.borrow().offset.borrow()
3782 }
3783
3784 pub fn max_offset(&self) -> Size<Pixels> {
3786 self.0.borrow().max_offset
3787 }
3788
3789 pub fn top_item(&self) -> usize {
3791 let state = self.0.borrow();
3792 let top = state.bounds.top() - state.offset.borrow().y;
3793
3794 match state.child_bounds.binary_search_by(|bounds| {
3795 if top < bounds.top() {
3796 Ordering::Greater
3797 } else if top > bounds.bottom() {
3798 Ordering::Less
3799 } else {
3800 Ordering::Equal
3801 }
3802 }) {
3803 Ok(ix) => ix,
3804 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
3805 }
3806 }
3807
3808 pub fn bottom_item(&self) -> usize {
3810 let state = self.0.borrow();
3811 let bottom = state.bounds.bottom() - state.offset.borrow().y;
3812
3813 match state.child_bounds.binary_search_by(|bounds| {
3814 if bottom < bounds.top() {
3815 Ordering::Greater
3816 } else if bottom > bounds.bottom() {
3817 Ordering::Less
3818 } else {
3819 Ordering::Equal
3820 }
3821 }) {
3822 Ok(ix) => ix,
3823 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
3824 }
3825 }
3826
3827 pub fn bounds(&self) -> Bounds<Pixels> {
3829 self.0.borrow().bounds
3830 }
3831
3832 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
3834 self.0.borrow().child_bounds.get(ix).cloned()
3835 }
3836
3837 pub fn scroll_to_item(&self, ix: usize) {
3839 let mut state = self.0.borrow_mut();
3840 state.active_item = Some(ScrollActiveItem {
3841 index: ix,
3842 strategy: ScrollStrategy::default(),
3843 });
3844 }
3845
3846 pub fn scroll_to_top_of_item(&self, ix: usize) {
3849 let mut state = self.0.borrow_mut();
3850 state.active_item = Some(ScrollActiveItem {
3851 index: ix,
3852 strategy: ScrollStrategy::Top,
3853 });
3854 }
3855
3856 fn scroll_to_active_item(&self) {
3860 let mut state = self.0.borrow_mut();
3861
3862 let Some(active_item) = state.active_item else {
3863 return;
3864 };
3865
3866 let active_item = match state.child_bounds.get(active_item.index) {
3867 Some(bounds) => {
3868 let mut scroll_offset = state.offset.borrow_mut();
3869
3870 match active_item.strategy {
3871 ScrollStrategy::FirstVisible => {
3872 if state.overflow.y == Overflow::Scroll {
3873 if bounds.top() + scroll_offset.y < state.bounds.top() {
3874 scroll_offset.y = state.bounds.top() - bounds.top();
3875 } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
3876 scroll_offset.y = state.bounds.bottom() - bounds.bottom();
3877 }
3878 }
3879 }
3880 ScrollStrategy::Top => {
3881 scroll_offset.y = state.bounds.top() - bounds.top();
3882 }
3883 }
3884
3885 if state.overflow.x == Overflow::Scroll {
3886 if bounds.left() + scroll_offset.x < state.bounds.left() {
3887 scroll_offset.x = state.bounds.left() - bounds.left();
3888 } else if bounds.right() + scroll_offset.x > state.bounds.right() {
3889 scroll_offset.x = state.bounds.right() - bounds.right();
3890 }
3891 }
3892 None
3893 }
3894 None => Some(active_item),
3895 };
3896 state.active_item = active_item;
3897 }
3898
3899 pub fn scroll_to_bottom(&self) {
3901 let mut state = self.0.borrow_mut();
3902 state.scroll_to_bottom = true;
3903 }
3904
3905 pub fn set_offset(&self, mut position: Point<Pixels>) {
3909 let state = self.0.borrow();
3910 *state.offset.borrow_mut() = position;
3911 }
3912
3913 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
3915 let ix = self.top_item();
3916 let state = self.0.borrow();
3917
3918 if let Some(child_bounds) = state.child_bounds.get(ix) {
3919 (
3920 ix,
3921 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
3922 )
3923 } else {
3924 (ix, px(0.))
3925 }
3926 }
3927
3928 pub fn logical_scroll_bottom(&self) -> (usize, Pixels) {
3930 let ix = self.bottom_item();
3931 let state = self.0.borrow();
3932
3933 if let Some(child_bounds) = state.child_bounds.get(ix) {
3934 (
3935 ix,
3936 child_bounds.bottom() + state.offset.borrow().y - state.bounds.bottom(),
3937 )
3938 } else {
3939 (ix, px(0.))
3940 }
3941 }
3942
3943 pub fn children_count(&self) -> usize {
3945 self.0.borrow().child_bounds.len()
3946 }
3947}
3948
3949#[cfg(test)]
3950mod tests {
3951 use super::*;
3952 use crate::{
3953 AnyWindowHandle, AppContext as _, Context, InputEvent as _, MouseButton, Render,
3954 ScrollDelta, ScrollWheelEvent, TestAppContext, fallback_prompt_renderer, hsla, point, size,
3955 };
3956 use std::{
3957 cell::{Cell, RefCell},
3958 rc::Rc,
3959 };
3960
3961 struct MouseDownOutOwner {
3962 mouse_down_out_count: Rc<Cell<usize>>,
3963 }
3964
3965 impl Render for MouseDownOutOwner {
3966 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
3967 let mouse_down_out_count = self.mouse_down_out_count.clone();
3968 div()
3969 .size_full()
3970 .child(
3971 div()
3972 .id("target")
3973 .size(px(50.))
3974 .on_mouse_down_out(move |_, _, _| {
3975 mouse_down_out_count.set(mouse_down_out_count.get() + 1);
3976 }),
3977 )
3978 }
3979 }
3980
3981 #[test]
3982 fn mouse_down_out_is_suppressed_while_window_prompt_is_active() {
3983 let mut test_app = TestAppContext::single();
3984 let mouse_down_out_count = Rc::new(Cell::new(0));
3985 let (_, cx) = test_app.add_window_view({
3986 let mouse_down_out_count = mouse_down_out_count.clone();
3987 move |_, _| MouseDownOutOwner {
3988 mouse_down_out_count,
3989 }
3990 });
3991
3992 cx.simulate_mouse_down(
3993 point(px(75.), px(75.)),
3994 MouseButton::Left,
3995 Default::default(),
3996 );
3997 assert_eq!(mouse_down_out_count.get(), 1);
3998
3999 cx.update(|window, cx| {
4000 cx.set_prompt_builder(fallback_prompt_renderer);
4001 let _receiver =
4002 window.prompt(crate::PromptLevel::Warning, "message", None, &["Ok"], cx);
4003 });
4004 cx.run_until_parked();
4005
4006 cx.simulate_mouse_down(
4007 point(px(75.), px(75.)),
4008 MouseButton::Left,
4009 Default::default(),
4010 );
4011 assert_eq!(
4012 mouse_down_out_count.get(),
4013 1,
4014 "mouse down over an active prompt must not reach underlying listeners"
4015 );
4016 }
4017
4018 struct HoverListenerLayoutTestView {
4019 target_left: Pixels,
4020 hover_transitions: Rc<RefCell<Vec<bool>>>,
4021 }
4022
4023 impl Render for HoverListenerLayoutTestView {
4024 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4025 let hover_transitions = self.hover_transitions.clone();
4026 div().relative().size_full().child(
4027 div()
4028 .id("hover-target")
4029 .absolute()
4030 .left(self.target_left)
4031 .top_0()
4032 .size(px(20.))
4033 .on_click(|_, _, _| {})
4034 .on_hover(move |is_hovered, _, _| {
4035 hover_transitions.borrow_mut().push(*is_hovered);
4036 }),
4037 )
4038 }
4039 }
4040
4041 #[gpui::test]
4042 fn default_hover_listener_updates_when_layout_changes_under_stationary_mouse(
4043 cx: &mut TestAppContext,
4044 ) {
4045 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4046 let window = cx.add_window({
4047 let hover_transitions = hover_transitions.clone();
4048 move |_, _| HoverListenerLayoutTestView {
4049 target_left: px(40.),
4050 hover_transitions,
4051 }
4052 });
4053 let any_window = AnyWindowHandle::from(window);
4054
4055 cx.update_window(any_window, |_, window, cx| {
4056 window.draw(cx).clear();
4057 window.dispatch_event(
4058 MouseMoveEvent {
4059 position: point(px(10.), px(10.)),
4060 modifiers: Default::default(),
4061 pressed_button: None,
4062 }
4063 .to_platform_input(),
4064 cx,
4065 );
4066 })
4067 .unwrap();
4068 assert!(hover_transitions.borrow().is_empty());
4069
4070 window
4071 .update(cx, |view, _, cx| {
4072 view.target_left = px(0.);
4073 cx.notify();
4074 })
4075 .unwrap();
4076 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
4077 .unwrap();
4078 assert_eq!(*hover_transitions.borrow(), [true]);
4079
4080 window
4081 .update(cx, |view, _, cx| {
4082 view.target_left = px(40.);
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, false]);
4089 }
4090
4091 #[gpui::test]
4092 fn default_hover_listener_ends_after_key_press(cx: &mut TestAppContext) {
4093 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4094 let window = cx.add_window({
4095 let hover_transitions = hover_transitions.clone();
4096 move |_, _| HoverListenerLayoutTestView {
4097 target_left: px(0.),
4098 hover_transitions,
4099 }
4100 });
4101 let any_window = AnyWindowHandle::from(window);
4102
4103 cx.update_window(any_window, |_, window, cx| {
4104 window.draw(cx).clear();
4105 window.dispatch_event(
4106 MouseMoveEvent {
4107 position: point(px(10.), px(10.)),
4108 modifiers: Default::default(),
4109 pressed_button: None,
4110 }
4111 .to_platform_input(),
4112 cx,
4113 );
4114 })
4115 .unwrap();
4116 assert_eq!(*hover_transitions.borrow(), [true]);
4117
4118 key_down(cx, any_window, "a");
4119 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
4120 .unwrap();
4121 assert_eq!(*hover_transitions.borrow(), [true, false]);
4122 }
4123
4124 struct HoverListenerModeLayoutTestView {
4125 target_left: Pixels,
4126 hover_transitions: Rc<RefCell<Vec<bool>>>,
4127 }
4128
4129 impl Render for HoverListenerModeLayoutTestView {
4130 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4131 let hover_transitions = self.hover_transitions.clone();
4132 div().relative().size_full().child(
4133 div()
4134 .id("hover-target")
4135 .absolute()
4136 .left(self.target_left)
4137 .top_0()
4138 .size(px(20.))
4139 .hover_listener_mode(HoverListenerMode::InputModalityIndependent)
4140 .on_hover(move |is_hovered, _, _| {
4141 hover_transitions.borrow_mut().push(*is_hovered);
4142 }),
4143 )
4144 }
4145 }
4146
4147 #[gpui::test]
4148 fn input_modality_independent_hover_listener_updates_after_key_press(cx: &mut TestAppContext) {
4149 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4150 let window = cx.add_window({
4151 let hover_transitions = hover_transitions.clone();
4152 move |_, _| HoverListenerModeLayoutTestView {
4153 target_left: px(40.),
4154 hover_transitions,
4155 }
4156 });
4157 let any_window = AnyWindowHandle::from(window);
4158 let pointer_position = point(px(10.), px(10.));
4159
4160 cx.update_window(any_window, |_, window, cx| {
4161 window.draw(cx).clear();
4162 window.dispatch_event(
4163 MouseMoveEvent {
4164 position: pointer_position,
4165 modifiers: Default::default(),
4166 pressed_button: None,
4167 }
4168 .to_platform_input(),
4169 cx,
4170 );
4171 })
4172 .unwrap();
4173 assert!(hover_transitions.borrow().is_empty());
4174
4175 key_down(cx, any_window, "a");
4176 window
4177 .update(cx, |view, _, cx| {
4178 view.target_left = px(0.);
4179 cx.notify();
4180 })
4181 .unwrap();
4182 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
4183 .unwrap();
4184 assert_eq!(*hover_transitions.borrow(), [true]);
4185
4186 key_down(cx, any_window, "b");
4187 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
4188 .unwrap();
4189 assert_eq!(*hover_transitions.borrow(), [true]);
4190
4191 cx.update_window(any_window, |_, window, cx| {
4192 window.dispatch_event(
4193 MouseMoveEvent {
4194 position: point(px(30.), px(10.)),
4195 modifiers: Default::default(),
4196 pressed_button: None,
4197 }
4198 .to_platform_input(),
4199 cx,
4200 );
4201 })
4202 .unwrap();
4203 assert_eq!(*hover_transitions.borrow(), [true, false]);
4204
4205 cx.update_window(any_window, |_, window, cx| {
4206 window.dispatch_event(
4207 MouseMoveEvent {
4208 position: pointer_position,
4209 modifiers: Default::default(),
4210 pressed_button: None,
4211 }
4212 .to_platform_input(),
4213 cx,
4214 );
4215 window.dispatch_event(
4216 MouseExitEvent {
4217 position: pointer_position,
4218 ..Default::default()
4219 }
4220 .to_platform_input(),
4221 cx,
4222 );
4223 })
4224 .unwrap();
4225 assert_eq!(*hover_transitions.borrow(), [true, false, true, false]);
4226 }
4227
4228 #[gpui::test]
4229 fn default_hover_listener_remains_hovered_during_stationary_mouse_press(
4230 cx: &mut TestAppContext,
4231 ) {
4232 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4233 let window = cx.add_window({
4234 let hover_transitions = hover_transitions.clone();
4235 move |_, _| HoverListenerLayoutTestView {
4236 target_left: px(0.),
4237 hover_transitions,
4238 }
4239 });
4240 let any_window = AnyWindowHandle::from(window);
4241 let mouse_position = point(px(10.), px(10.));
4242
4243 cx.update_window(any_window, |_, window, cx| {
4244 window.draw(cx).clear();
4245 window.dispatch_event(
4246 MouseMoveEvent {
4247 position: mouse_position,
4248 modifiers: Default::default(),
4249 pressed_button: None,
4250 }
4251 .to_platform_input(),
4252 cx,
4253 );
4254 })
4255 .unwrap();
4256 assert_eq!(*hover_transitions.borrow(), [true]);
4257
4258 cx.update_window(any_window, |_, window, cx| {
4259 window.dispatch_event(
4260 MouseDownEvent {
4261 position: mouse_position,
4262 button: MouseButton::Left,
4263 modifiers: Default::default(),
4264 click_count: 1,
4265 first_mouse: false,
4266 }
4267 .to_platform_input(),
4268 cx,
4269 );
4270 window.draw(cx).clear();
4271 })
4272 .unwrap();
4273 assert_eq!(*hover_transitions.borrow(), [true]);
4274
4275 cx.update_window(any_window, |_, window, cx| {
4276 window.dispatch_event(
4277 MouseUpEvent {
4278 position: mouse_position,
4279 button: MouseButton::Left,
4280 modifiers: Default::default(),
4281 click_count: 1,
4282 }
4283 .to_platform_input(),
4284 cx,
4285 );
4286 window.draw(cx).clear();
4287 })
4288 .unwrap();
4289 assert_eq!(*hover_transitions.borrow(), [true]);
4290 }
4291
4292 fn key_down(cx: &mut TestAppContext, window: AnyWindowHandle, key: &str) {
4293 let keystroke = crate::Keystroke::parse(key).unwrap();
4294 cx.update_window(window, |_, window, cx| {
4295 window.dispatch_event(
4296 KeyDownEvent {
4297 keystroke,
4298 is_held: false,
4299 prefer_character_input: false,
4300 }
4301 .to_platform_input(),
4302 cx,
4303 );
4304 })
4305 .unwrap();
4306 }
4307
4308 #[test]
4309 fn test_tooltip_show_delay_sets_interactivity_state() {
4310 let delay = Duration::from_millis(125);
4311 let element = div().id("tooltip-target").tooltip_show_delay(delay);
4312
4313 assert_eq!(
4314 element.element.interactivity.tooltip_show_delay,
4315 Some(delay)
4316 );
4317 }
4318
4319 #[test]
4320 fn aria_description_and_keyshortcuts_are_written_to_accesskit() {
4321 let element = div()
4322 .id("save-button")
4323 .role(accesskit::Role::Button)
4324 .aria_description("Save the active document")
4325 .aria_keyshortcuts("Ctrl+S");
4326 let mut node = accesskit::Node::new(accesskit::Role::Button);
4327
4328 element.element.interactivity.write_a11y_info(&mut node);
4329
4330 assert_eq!(node.description(), Some("Save the active document"));
4331 assert_eq!(node.keyboard_shortcut(), Some("Ctrl+S"));
4332 }
4333
4334 #[test]
4335 fn accessibility_id_is_written_to_accesskit() {
4336 let element = div()
4337 .id("buffer-font-size")
4338 .accessibility_id("settings.buffer-font-size");
4339 let mut node = accesskit::Node::new(accesskit::Role::SpinButton);
4340
4341 element.element.interactivity.write_a11y_info(&mut node);
4342
4343 assert_eq!(node.author_id(), Some("settings.buffer-font-size"));
4344 }
4345
4346 #[test]
4347 fn form_control_properties_are_written_to_accesskit() {
4348 let element = div()
4349 .id("email")
4350 .role(accesskit::Role::TextInput)
4351 .aria_value("alice@example.com")
4352 .aria_placeholder("name@example.com")
4353 .aria_disabled(true)
4354 .aria_required(true)
4355 .aria_invalid(accesskit::Invalid::True)
4356 .aria_modal(true);
4357 let mut node = accesskit::Node::new(accesskit::Role::TextInput);
4358
4359 element.element.interactivity.write_a11y_info(&mut node);
4360
4361 assert_eq!(node.value(), Some("alice@example.com"));
4362 assert_eq!(node.placeholder(), Some("name@example.com"));
4363 assert!(node.is_disabled());
4364 assert!(node.is_required());
4365 assert_eq!(node.invalid(), Some(accesskit::Invalid::True));
4366 assert!(node.is_modal());
4367 }
4368
4369 #[test]
4370 fn test_shadow_sm_preset_preserves_shadow_fields() {
4371 let mut element = div().shadow_sm();
4372 let shadows = element
4373 .style()
4374 .box_shadow
4375 .as_ref()
4376 .expect("shadow_sm should set box shadows");
4377
4378 assert_eq!(shadows.len(), 2);
4379 assert_eq!(shadows[0].color, hsla(0., 0., 0., 0.1));
4380 assert_eq!(shadows[0].offset, point(px(0.), px(1.)));
4381 assert_eq!(shadows[0].blur_radius, px(3.));
4382 assert_eq!(shadows[0].spread_radius, px(0.));
4383 assert!(!shadows[0].inset);
4384
4385 assert_eq!(shadows[1].color, hsla(0., 0., 0., 0.1));
4386 assert_eq!(shadows[1].offset, point(px(0.), px(1.)));
4387 assert_eq!(shadows[1].blur_radius, px(2.));
4388 assert_eq!(shadows[1].spread_radius, px(-1.));
4389 assert!(!shadows[1].inset);
4390 }
4391
4392 #[gpui::test]
4393 fn test_fractional_padding_does_not_make_a_fitting_container_scrollable(
4394 cx: &mut TestAppContext,
4395 ) {
4396 struct PaddedContainer {
4397 scroll_handle: ScrollHandle,
4398 }
4399
4400 impl Render for PaddedContainer {
4401 fn render(
4402 &mut self,
4403 _window: &mut Window,
4404 _cx: &mut Context<Self>,
4405 ) -> impl IntoElement {
4406 div().size_full().child(
4409 div()
4410 .id("container")
4411 .h(px(50.))
4412 .w(px(100.))
4413 .py(px(4.25))
4414 .overflow_y_scroll()
4415 .track_scroll(&self.scroll_handle)
4416 .child(div().w_full().h(px(42.))),
4417 )
4418 }
4419 }
4420
4421 let scroll_handle = ScrollHandle::new();
4422 let window: AnyWindowHandle = cx
4423 .add_window({
4424 let scroll_handle = scroll_handle.clone();
4425 move |_, _| PaddedContainer { scroll_handle }
4426 })
4427 .into();
4428 cx.update_window(window, |_, window, cx| window.draw(cx).clear())
4429 .unwrap();
4430
4431 assert_eq!(scroll_handle.max_offset().height, px(0.));
4432 }
4433
4434 struct ContentSizedGrid;
4435
4436 impl Render for ContentSizedGrid {
4437 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4438 let widths = [px(100.), px(200.), px(50.)];
4439 div().size_full().child(
4440 div()
4441 .w_full()
4442 .grid()
4443 .grid_cols_max_content(widths.len() as u16)
4444 .children(widths.into_iter().enumerate().map(|(index, width)| {
4445 div()
4446 .debug_selector(move || format!("cell-{index}"))
4447 .w(width)
4448 .h(px(10.))
4449 })),
4450 )
4451 }
4452 }
4453
4454 #[gpui::test]
4455 fn grid_cols_max_content_sizes_columns_to_their_content(cx: &mut TestAppContext) {
4456 let window = cx.add_window(|_, _| ContentSizedGrid);
4457 cx.update_window(window.into(), |_, window, cx| window.draw(cx).clear())
4458 .unwrap();
4459
4460 let mut bounds = |selector: &'static str| {
4461 cx.update_window(window.into(), |_, window, _| {
4462 window.rendered_frame.debug_bounds.get(selector).copied()
4463 })
4464 .unwrap()
4465 .unwrap_or_else(|| panic!("{selector} was not rendered"))
4466 };
4467
4468 assert_eq!(bounds("cell-0").origin.x, px(0.));
4469 assert_eq!(bounds("cell-0").size.width, px(100.));
4470 assert_eq!(bounds("cell-1").origin.x, px(100.));
4471 assert_eq!(bounds("cell-1").size.width, px(200.));
4472 assert_eq!(bounds("cell-2").origin.x, px(300.));
4473 assert_eq!(bounds("cell-2").size.width, px(50.));
4474 }
4475
4476 #[test]
4477 fn restrict_scroll_to_axis_sets_style_flag() {
4478 let mut element = div().id("axis-lock").restrict_scroll_to_axis();
4479 assert_eq!(
4480 element
4481 .element
4482 .interactivity
4483 .base_style
4484 .restrict_scroll_to_axis,
4485 Some(true)
4486 );
4487 }
4488
4489 struct AxisLockedScrollView {
4490 handle: ScrollHandle,
4491 }
4492
4493 impl Render for AxisLockedScrollView {
4494 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4495 div()
4496 .id("axis-lock-scroller")
4497 .size_full()
4498 .overflow_scroll()
4499 .restrict_scroll_to_axis()
4500 .track_scroll(&self.handle)
4501 .child(div().size(px(2000.)))
4502 }
4503 }
4504
4505 #[gpui::test]
4506 fn restrict_scroll_to_axis_locks_precise_gestures_to_start_axis(cx: &mut TestAppContext) {
4507 let handle = ScrollHandle::new();
4508 let (_, cx) = cx.add_window_view({
4509 let handle = handle.clone();
4510 move |_, _| AxisLockedScrollView { handle }
4511 });
4512 cx.simulate_resize(size(px(200.), px(200.)));
4513
4514 cx.update(|window, cx| {
4515 window.dispatch_event(
4516 ScrollWheelEvent {
4517 position: point(px(50.), px(50.)),
4518 delta: ScrollDelta::Pixels(point(px(-20.), px(-4.))),
4519 touch_phase: crate::TouchPhase::Started,
4520 ..Default::default()
4521 }
4522 .to_platform_input(),
4523 cx,
4524 );
4525 });
4526 cx.run_until_parked();
4527 let after_start = handle.offset();
4528 assert!(after_start.x < px(0.), "horizontal start should scroll x");
4529 assert_eq!(
4530 after_start.y,
4531 px(0.),
4532 "sticky start axis should drop the weaker vertical component"
4533 );
4534
4535 cx.update(|window, cx| {
4536 window.dispatch_event(
4537 ScrollWheelEvent {
4538 position: point(px(50.), px(50.)),
4539 delta: ScrollDelta::Pixels(point(px(-3.), px(-2.))),
4540 touch_phase: crate::TouchPhase::Moved,
4541 ..Default::default()
4542 }
4543 .to_platform_input(),
4544 cx,
4545 );
4546 });
4547 cx.run_until_parked();
4548 let after_continue = handle.offset();
4549 assert!(after_continue.x < after_start.x);
4550 assert_eq!(after_continue.y, px(0.));
4551
4552 cx.update(|window, cx| {
4553 window.dispatch_event(
4554 ScrollWheelEvent {
4555 position: point(px(50.), px(50.)),
4556 delta: ScrollDelta::Pixels(point(px(-2.), px(-20.))),
4557 touch_phase: crate::TouchPhase::Moved,
4558 ..Default::default()
4559 }
4560 .to_platform_input(),
4561 cx,
4562 );
4563 });
4564 cx.run_until_parked();
4565 let after_unlock = handle.offset();
4566 assert!(
4567 after_unlock.y < px(0.),
4568 "a strong perpendicular delta should break the axis lock"
4569 );
4570 }
4571
4572 #[gpui::test]
4573 fn restrict_scroll_to_axis_style_flag_still_blocks_axis_remap(cx: &mut TestAppContext) {
4574 let handle = ScrollHandle::new();
4575 struct VerticalOnly {
4576 handle: ScrollHandle,
4577 }
4578 impl Render for VerticalOnly {
4579 fn render(
4580 &mut self,
4581 _window: &mut Window,
4582 _cx: &mut Context<Self>,
4583 ) -> impl IntoElement {
4584 let mut scroller = div()
4585 .id("vertical-only")
4586 .size_full()
4587 .overflow_y_scroll()
4588 .track_scroll(&self.handle)
4589 .child(div().h(px(2000.)).w_full());
4590 scroller.style().restrict_scroll_to_axis = Some(true);
4591 scroller
4592 }
4593 }
4594
4595 let (_, cx) = cx.add_window_view({
4596 let handle = handle.clone();
4597 move |_, _| VerticalOnly { handle }
4598 });
4599 cx.simulate_resize(size(px(200.), px(200.)));
4600
4601 cx.update(|window, cx| {
4602 window.dispatch_event(
4603 ScrollWheelEvent {
4604 position: point(px(50.), px(50.)),
4605 delta: ScrollDelta::Pixels(point(px(-20.), px(0.))),
4606 ..Default::default()
4607 }
4608 .to_platform_input(),
4609 cx,
4610 );
4611 });
4612 cx.run_until_parked();
4613 assert_eq!(
4614 handle.offset(),
4615 point(px(0.), px(0.)),
4616 "existing restrict_scroll_to_axis callers must not have horizontal input remapped onto a vertical-only scroller"
4617 );
4618 }
4619}