1use crate::{
19 AbsoluteLength, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent,
20 DispatchPhase, Element, ElementId, Entity, EntityId, FocusHandle, Global, GlobalElementId,
21 Hitbox, HitboxBehavior, HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext,
22 KeyDownEvent, KeyUpEvent, KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent,
23 MouseButton, MouseClickEvent, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent,
24 OngoingScroll, Overflow, ParentElement, PinchEvent, Pixels, Point, Render, ScrollWheelEvent,
25 SharedString, Size, Style, StyleRefinement, Styled, Task, TooltipId, Visibility, Window,
26 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_drag_move<T>(
311 &mut self,
312 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
313 ) where
314 T: 'static,
315 {
316 self.mouse_move_listeners
317 .push(Box::new(move |event, phase, hitbox, window, cx| {
318 if phase == DispatchPhase::Capture
319 && let Some(drag) = &cx.active_drag
320 && drag.value.as_ref().type_id() == TypeId::of::<T>()
321 {
322 (listener)(
323 &DragMoveEvent {
324 event: event.clone(),
325 bounds: hitbox.bounds,
326 drag: PhantomData,
327 dragged_item: Arc::clone(&drag.value),
328 },
329 window,
330 cx,
331 );
332 }
333 }));
334 }
335
336 pub fn on_scroll_wheel(
341 &mut self,
342 listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
343 ) {
344 self.scroll_wheel_listeners
345 .push(Box::new(move |event, phase, hitbox, window, cx| {
346 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
347 (listener)(event, window, cx);
348 }
349 }));
350 }
351
352 pub fn on_pinch(&mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) {
357 self.pinch_listeners
358 .push(Box::new(move |event, phase, hitbox, window, cx| {
359 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
360 (listener)(event, window, cx);
361 }
362 }));
363 }
364
365 pub fn capture_pinch(
370 &mut self,
371 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
372 ) {
373 self.pinch_listeners
374 .push(Box::new(move |event, phase, _hitbox, window, cx| {
375 if phase == DispatchPhase::Capture {
376 (listener)(event, window, cx);
377 } else {
378 cx.propagate();
379 }
380 }));
381 }
382
383 pub fn capture_action<A: Action>(
388 &mut self,
389 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
390 ) {
391 self.action_listeners.push((
392 TypeId::of::<A>(),
393 Box::new(move |action, phase, window, cx| {
394 let action = action.downcast_ref().unwrap();
395 if phase == DispatchPhase::Capture {
396 (listener)(action, window, cx)
397 } else {
398 cx.propagate();
399 }
400 }),
401 ));
402 }
403
404 pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Window, &mut App) + 'static) {
409 self.action_listeners.push((
410 TypeId::of::<A>(),
411 Box::new(move |action, phase, window, cx| {
412 let action = action.downcast_ref().unwrap();
413 if phase == DispatchPhase::Bubble {
414 (listener)(action, window, cx)
415 }
416 }),
417 ));
418 }
419
420 pub fn on_boxed_action(
427 &mut self,
428 action: &dyn Action,
429 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
430 ) {
431 let action = action.boxed_clone();
432 self.action_listeners.push((
433 (*action).type_id(),
434 Box::new(move |_, phase, window, cx| {
435 if phase == DispatchPhase::Bubble {
436 (listener)(&*action, window, cx)
437 }
438 }),
439 ));
440 }
441
442 pub fn on_key_down(
447 &mut self,
448 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
449 ) {
450 self.key_down_listeners
451 .push(Box::new(move |event, phase, window, cx| {
452 if phase == DispatchPhase::Bubble {
453 (listener)(event, window, cx)
454 }
455 }));
456 }
457
458 pub fn capture_key_down(
463 &mut self,
464 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
465 ) {
466 self.key_down_listeners
467 .push(Box::new(move |event, phase, window, cx| {
468 if phase == DispatchPhase::Capture {
469 listener(event, window, cx)
470 }
471 }));
472 }
473
474 pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static) {
479 self.key_up_listeners
480 .push(Box::new(move |event, phase, window, cx| {
481 if phase == DispatchPhase::Bubble {
482 listener(event, window, cx)
483 }
484 }));
485 }
486
487 pub fn capture_key_up(
492 &mut self,
493 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
494 ) {
495 self.key_up_listeners
496 .push(Box::new(move |event, phase, window, cx| {
497 if phase == DispatchPhase::Capture {
498 listener(event, window, cx)
499 }
500 }));
501 }
502
503 pub fn on_modifiers_changed(
508 &mut self,
509 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
510 ) {
511 self.modifiers_changed_listeners
512 .push(Box::new(move |event, window, cx| {
513 listener(event, window, cx)
514 }));
515 }
516
517 pub fn on_drop<T: 'static>(&mut self, listener: impl Fn(&T, &mut Window, &mut App) + 'static) {
522 self.drop_listeners.push((
523 TypeId::of::<T>(),
524 Box::new(move |dragged_value, window, cx| {
525 listener(dragged_value.downcast_ref().unwrap(), window, cx);
526 }),
527 ));
528 }
529
530 pub fn can_drop(
533 &mut self,
534 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
535 ) {
536 self.can_drop_predicate = Some(Box::new(predicate));
537 }
538
539 pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
544 where
545 Self: Sized,
546 {
547 self.click_listeners.push(Rc::new(move |event, window, cx| {
548 listener(event, window, cx)
549 }));
550 }
551
552 pub fn on_drag<T, W>(
559 &mut self,
560 value: T,
561 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
562 ) where
563 Self: Sized,
564 T: 'static,
565 W: 'static + Render,
566 {
567 debug_assert!(
568 self.drag_listener.is_none(),
569 "calling on_drag more than once on the same element is not supported"
570 );
571 self.drag_listener = Some((
572 Arc::new(value),
573 Box::new(move |value, offset, window, cx| {
574 constructor(value.downcast_ref().unwrap(), offset, window, cx).into()
575 }),
576 ));
577 }
578
579 pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static)
586 where
587 Self: Sized,
588 {
589 debug_assert!(
590 self.hover_listener.is_none(),
591 "calling on_hover more than once on the same element is not supported"
592 );
593 self.hover_listener = Some(Box::new(listener));
594 }
595
596 pub fn on_resize(&mut self, listener: impl Fn(&ResizeEvent, &mut Window, &mut App) + 'static)
601 where
602 Self: Sized,
603 {
604 self.resize_listeners.push(Box::new(listener));
605 }
606
607 pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static)
610 where
611 Self: Sized,
612 {
613 debug_assert!(
614 self.tooltip_builder.is_none(),
615 "calling tooltip more than once on the same element is not supported"
616 );
617 self.tooltip_builder = Some(TooltipBuilder {
618 build: Rc::new(build_tooltip),
619 hoverable: false,
620 });
621 }
622
623 pub fn hoverable_tooltip(
627 &mut self,
628 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
629 ) where
630 Self: Sized,
631 {
632 debug_assert!(
633 self.tooltip_builder.is_none(),
634 "calling tooltip more than once on the same element is not supported"
635 );
636 self.tooltip_builder = Some(TooltipBuilder {
637 build: Rc::new(build_tooltip),
638 hoverable: true,
639 });
640 }
641
642 pub fn tooltip_show_delay(&mut self, delay: Duration) {
645 self.tooltip_show_delay = Some(delay);
646 }
647
648 pub fn occlude_mouse(&mut self) {
653 self.hitbox_behavior = HitboxBehavior::BlockMouse;
654 }
655
656 pub fn window_control_area(&mut self, area: WindowControlArea) {
659 self.window_control = Some(area);
660 }
661
662 pub fn block_mouse_except_scroll(&mut self) {
667 self.hitbox_behavior = HitboxBehavior::BlockMouseExceptScroll;
668 }
669}
670
671pub trait InteractiveElement: Sized {
674 fn interactivity(&mut self) -> &mut Interactivity;
676
677 fn group(mut self, group: impl Into<SharedString>) -> Self {
679 self.interactivity().group = Some(group.into());
680 self
681 }
682
683 fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
685 self.interactivity().element_id = Some(id.into());
686
687 Stateful { element: self }
688 }
689
690 fn track_focus(mut self, focus_handle: &FocusHandle) -> Self {
694 self.interactivity().focusable = true;
695 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
696 self
697 }
698
699 fn tab_stop(mut self, tab_stop: bool) -> Self {
706 self.interactivity().tab_stop = tab_stop;
707 self
708 }
709
710 fn tab_index(mut self, index: isize) -> Self {
715 self.interactivity().focusable = true;
716 self.interactivity().tab_index = Some(index);
717 self.interactivity().tab_stop = true;
718 self
719 }
720
721 fn tab_group(mut self) -> Self {
726 self.interactivity().tab_group = true;
727 if self.interactivity().tab_index.is_none() {
728 self.interactivity().tab_index = Some(0);
729 }
730 self
731 }
732
733 fn key_context<C, E>(mut self, key_context: C) -> Self
736 where
737 C: TryInto<KeyContext, Error = E>,
738 E: Debug,
739 {
740 if let Some(key_context) = key_context.try_into().log_err() {
741 self.interactivity().key_context = Some(key_context);
742 }
743 self
744 }
745
746 fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
748 debug_assert!(
749 self.interactivity().hover_style.is_none(),
750 "hover style already set"
751 );
752 self.interactivity().hover_style = Some(Box::new(f(StyleRefinement::default())));
753 self
754 }
755
756 fn group_hover(
758 mut self,
759 group_name: impl Into<SharedString>,
760 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
761 ) -> Self {
762 self.interactivity().group_hover_style = Some(GroupStyle {
763 group: group_name.into(),
764 style: Box::new(f(StyleRefinement::default())),
765 });
766 self
767 }
768
769 fn on_mouse_down(
774 mut self,
775 button: MouseButton,
776 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
777 ) -> Self {
778 self.interactivity().on_mouse_down(button, listener);
779 self
780 }
781
782 #[cfg(any(test, feature = "test-support"))]
783 fn debug_selector(mut self, f: impl FnOnce() -> String) -> Self {
787 self.interactivity().debug_selector = Some(f());
788 self
789 }
790
791 #[cfg(not(any(test, feature = "test-support")))]
792 #[inline]
796 fn debug_selector(self, _: impl FnOnce() -> String) -> Self {
797 self
798 }
799
800 fn capture_any_mouse_down(
805 mut self,
806 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
807 ) -> Self {
808 self.interactivity().capture_any_mouse_down(listener);
809 self
810 }
811
812 fn on_any_mouse_down(
817 mut self,
818 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
819 ) -> Self {
820 self.interactivity().on_any_mouse_down(listener);
821 self
822 }
823
824 fn on_mouse_up(
829 mut self,
830 button: MouseButton,
831 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
832 ) -> Self {
833 self.interactivity().on_mouse_up(button, listener);
834 self
835 }
836
837 fn capture_any_mouse_up(
842 mut self,
843 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
844 ) -> Self {
845 self.interactivity().capture_any_mouse_up(listener);
846 self
847 }
848
849 fn on_mouse_down_out(
855 mut self,
856 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
857 ) -> Self {
858 self.interactivity().on_mouse_down_out(listener);
859 self
860 }
861
862 fn on_mouse_up_out(
868 mut self,
869 button: MouseButton,
870 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
871 ) -> Self {
872 self.interactivity().on_mouse_up_out(button, listener);
873 self
874 }
875
876 fn on_mouse_move(
881 mut self,
882 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
883 ) -> Self {
884 self.interactivity().on_mouse_move(listener);
885 self
886 }
887
888 fn on_mouse_exit(
893 mut self,
894 listener: impl Fn(&MouseExitEvent, &mut Window, &mut App) + 'static,
895 ) -> Self {
896 self.interactivity().on_mouse_exit(listener);
897 self
898 }
899
900 fn on_drag_move<T: 'static>(
908 mut self,
909 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
910 ) -> Self {
911 self.interactivity().on_drag_move(listener);
912 self
913 }
914
915 fn on_scroll_wheel(
920 mut self,
921 listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
922 ) -> Self {
923 self.interactivity().on_scroll_wheel(listener);
924 self
925 }
926
927 fn on_pinch(mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) -> Self {
932 self.interactivity().on_pinch(listener);
933 self
934 }
935
936 fn capture_pinch(
941 mut self,
942 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
943 ) -> Self {
944 self.interactivity().capture_pinch(listener);
945 self
946 }
947
948 fn capture_action<A: Action>(
953 mut self,
954 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
955 ) -> Self {
956 self.interactivity().capture_action(listener);
957 self
958 }
959
960 fn on_action<A: Action>(
965 mut self,
966 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
967 ) -> Self {
968 self.interactivity().on_action(listener);
969 self
970 }
971
972 fn on_boxed_action(
979 mut self,
980 action: &dyn Action,
981 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
982 ) -> Self {
983 self.interactivity().on_boxed_action(action, listener);
984 self
985 }
986
987 fn on_key_down(
992 mut self,
993 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
994 ) -> Self {
995 self.interactivity().on_key_down(listener);
996 self
997 }
998
999 fn capture_key_down(
1004 mut self,
1005 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1006 ) -> Self {
1007 self.interactivity().capture_key_down(listener);
1008 self
1009 }
1010
1011 fn on_key_up(
1016 mut self,
1017 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1018 ) -> Self {
1019 self.interactivity().on_key_up(listener);
1020 self
1021 }
1022
1023 fn capture_key_up(
1028 mut self,
1029 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1030 ) -> Self {
1031 self.interactivity().capture_key_up(listener);
1032 self
1033 }
1034
1035 fn on_modifiers_changed(
1040 mut self,
1041 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
1042 ) -> Self {
1043 self.interactivity().on_modifiers_changed(listener);
1044 self
1045 }
1046
1047 fn drag_over<S: 'static>(
1049 mut self,
1050 f: impl 'static + Fn(StyleRefinement, &S, &mut Window, &mut App) -> StyleRefinement,
1051 ) -> Self {
1052 self.interactivity().drag_over_styles.push((
1053 TypeId::of::<S>(),
1054 Box::new(move |currently_dragged: &dyn Any, window, cx| {
1055 f(
1056 StyleRefinement::default(),
1057 currently_dragged.downcast_ref::<S>().unwrap(),
1058 window,
1059 cx,
1060 )
1061 }),
1062 ));
1063 self
1064 }
1065
1066 fn group_drag_over<S: 'static>(
1068 mut self,
1069 group_name: impl Into<SharedString>,
1070 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1071 ) -> Self {
1072 self.interactivity().group_drag_over_styles.push((
1073 TypeId::of::<S>(),
1074 GroupStyle {
1075 group: group_name.into(),
1076 style: Box::new(f(StyleRefinement::default())),
1077 },
1078 ));
1079 self
1080 }
1081
1082 fn on_drop<T: 'static>(
1087 mut self,
1088 listener: impl Fn(&T, &mut Window, &mut App) + 'static,
1089 ) -> Self {
1090 self.interactivity().on_drop(listener);
1091 self
1092 }
1093
1094 fn can_drop(
1097 mut self,
1098 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
1099 ) -> Self {
1100 self.interactivity().can_drop(predicate);
1101 self
1102 }
1103
1104 fn occlude(mut self) -> Self {
1108 self.interactivity().occlude_mouse();
1109 self
1110 }
1111
1112 fn window_control_area(mut self, area: WindowControlArea) -> Self {
1115 self.interactivity().window_control_area(area);
1116 self
1117 }
1118
1119 fn block_mouse_except_scroll(mut self) -> Self {
1124 self.interactivity().block_mouse_except_scroll();
1125 self
1126 }
1127
1128 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1131 where
1132 Self: Sized,
1133 {
1134 self.interactivity().focus_style = Some(Box::new(f(StyleRefinement::default())));
1135 self
1136 }
1137
1138 fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1141 where
1142 Self: Sized,
1143 {
1144 self.interactivity().in_focus_style = Some(Box::new(f(StyleRefinement::default())));
1145 self
1146 }
1147}
1148
1149pub trait StatefulInteractiveElement: InteractiveElement {
1152 fn role(mut self, role: accesskit::Role) -> Self {
1154 debug_assert!(
1155 role != accesskit::Role::GenericContainer,
1156 "GenericContainer is filtered out of the a11y tree and has no effect"
1157 );
1158 self.interactivity().override_role = Some(role);
1159 self
1160 }
1161
1162 fn accessibility_id(mut self, id: impl Into<SharedString>) -> Self {
1167 self.interactivity().accessibility_id = Some(id.into());
1168 self
1169 }
1170
1171 fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
1173 self.interactivity().aria_label = Some(label.into());
1174 self
1175 }
1176
1177 fn aria_description(mut self, description: impl Into<SharedString>) -> Self {
1179 self.interactivity().aria_description = Some(description.into());
1180 self
1181 }
1182
1183 fn aria_value(mut self, value: impl Into<SharedString>) -> Self {
1185 self.interactivity().aria_value = Some(value.into());
1186 self
1187 }
1188
1189 fn aria_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
1191 self.interactivity().aria_placeholder = Some(placeholder.into());
1192 self
1193 }
1194
1195 fn aria_disabled(mut self, disabled: bool) -> Self {
1197 self.interactivity().aria_disabled = disabled;
1198 self
1199 }
1200
1201 fn aria_required(mut self, required: bool) -> Self {
1203 self.interactivity().aria_required = required;
1204 self
1205 }
1206
1207 fn aria_invalid(mut self, invalid: accesskit::Invalid) -> Self {
1209 self.interactivity().aria_invalid = Some(invalid);
1210 self
1211 }
1212
1213 fn aria_modal(mut self, modal: bool) -> Self {
1215 self.interactivity().aria_modal = modal;
1216 self
1217 }
1218
1219 fn aria_keyshortcuts(mut self, keyshortcuts: impl Into<SharedString>) -> Self {
1223 self.interactivity().aria_keyshortcuts = Some(keyshortcuts.into());
1224 self
1225 }
1226
1227 fn aria_selected(mut self, selected: bool) -> Self {
1229 self.interactivity().aria_selected = Some(selected);
1230 self
1231 }
1232
1233 fn aria_expanded(mut self, expanded: bool) -> Self {
1235 self.interactivity().aria_expanded = Some(expanded);
1236 self
1237 }
1238
1239 fn aria_toggled(mut self, toggled: accesskit::Toggled) -> Self {
1241 self.interactivity().aria_toggled = Some(toggled);
1242 self
1243 }
1244
1245 fn aria_numeric_value(mut self, value: f64) -> Self {
1247 self.interactivity().aria_numeric_value = Some(value);
1248 self
1249 }
1250
1251 fn aria_min_numeric_value(mut self, value: f64) -> Self {
1253 self.interactivity().aria_min_numeric_value = Some(value);
1254 self
1255 }
1256
1257 fn aria_max_numeric_value(mut self, value: f64) -> Self {
1259 self.interactivity().aria_max_numeric_value = Some(value);
1260 self
1261 }
1262
1263 fn aria_orientation(mut self, orientation: accesskit::Orientation) -> Self {
1265 self.interactivity().aria_orientation = Some(orientation);
1266 self
1267 }
1268
1269 fn aria_level(mut self, level: usize) -> Self {
1271 self.interactivity().aria_level = Some(level);
1272 self
1273 }
1274
1275 fn aria_position_in_set(mut self, position: usize) -> Self {
1277 self.interactivity().aria_position_in_set = Some(position);
1278 self
1279 }
1280
1281 fn aria_size_of_set(mut self, size: usize) -> Self {
1283 self.interactivity().aria_size_of_set = Some(size);
1284 self
1285 }
1286
1287 fn aria_row_index(mut self, index: usize) -> Self {
1289 self.interactivity().aria_row_index = Some(index);
1290 self
1291 }
1292
1293 fn aria_column_index(mut self, index: usize) -> Self {
1295 self.interactivity().aria_column_index = Some(index);
1296 self
1297 }
1298
1299 fn aria_row_count(mut self, count: usize) -> Self {
1301 self.interactivity().aria_row_count = Some(count);
1302 self
1303 }
1304
1305 fn aria_column_count(mut self, count: usize) -> Self {
1307 self.interactivity().aria_column_count = Some(count);
1308 self
1309 }
1310
1311 fn on_a11y_action(
1313 mut self,
1314 action: accesskit::Action,
1315 listener: impl FnMut(Option<&accesskit::ActionData>, &mut Window, &mut App) + 'static,
1316 ) -> Self {
1317 self.interactivity()
1318 .a11y_action_listeners
1319 .push((action, Box::new(listener)));
1320 self
1321 }
1322
1323 fn focusable(mut self) -> Self {
1325 self.interactivity().focusable = true;
1326 self
1327 }
1328
1329 fn overflow_scroll(mut self) -> Self {
1331 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1332 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1333 self
1334 }
1335
1336 fn overflow_x_scroll(mut self) -> Self {
1338 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1339 self
1340 }
1341
1342 fn overflow_y_scroll(mut self) -> Self {
1344 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1345 self
1346 }
1347
1348 fn restrict_scroll_to_axis(mut self) -> Self {
1352 self.interactivity().base_style.restrict_scroll_to_axis = Some(true);
1353 self
1354 }
1355
1356 fn scrollbar_width(mut self, width: impl Into<AbsoluteLength>) -> Self {
1361 self.interactivity().base_style.scrollbar_width = Some(width.into());
1362 self
1363 }
1364
1365 fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
1367 self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone());
1368 self
1369 }
1370
1371 fn anchor_scroll(mut self, scroll_anchor: Option<ScrollAnchor>) -> Self {
1373 self.interactivity().scroll_anchor = scroll_anchor;
1374 self
1375 }
1376
1377 fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1379 where
1380 Self: Sized,
1381 {
1382 self.interactivity().active_style = Some(Box::new(f(StyleRefinement::default())));
1383 self
1384 }
1385
1386 fn group_active(
1388 mut self,
1389 group_name: impl Into<SharedString>,
1390 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1391 ) -> Self
1392 where
1393 Self: Sized,
1394 {
1395 self.interactivity().group_active_style = Some(GroupStyle {
1396 group: group_name.into(),
1397 style: Box::new(f(StyleRefinement::default())),
1398 });
1399 self
1400 }
1401
1402 fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self
1407 where
1408 Self: Sized,
1409 {
1410 self.interactivity().on_click(listener);
1411 self
1412 }
1413
1414 fn on_drag<T, W>(
1422 mut self,
1423 value: T,
1424 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
1425 ) -> Self
1426 where
1427 Self: Sized,
1428 T: 'static,
1429 W: 'static + Render,
1430 {
1431 self.interactivity().on_drag(value, constructor);
1432 self
1433 }
1434
1435 fn on_hover(mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self
1442 where
1443 Self: Sized,
1444 {
1445 self.interactivity().on_hover(listener);
1446 self
1447 }
1448
1449 fn tooltip(mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self
1452 where
1453 Self: Sized,
1454 {
1455 self.interactivity().tooltip(build_tooltip);
1456 self
1457 }
1458
1459 fn hoverable_tooltip(
1463 mut self,
1464 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
1465 ) -> Self
1466 where
1467 Self: Sized,
1468 {
1469 self.interactivity().hoverable_tooltip(build_tooltip);
1470 self
1471 }
1472
1473 fn tooltip_show_delay(mut self, delay: Duration) -> Self
1476 where
1477 Self: Sized,
1478 {
1479 self.interactivity().tooltip_show_delay(delay);
1480 self
1481 }
1482
1483 fn on_resize(mut self, listener: impl Fn(&ResizeEvent, &mut Window, &mut App) + 'static) -> Self
1488 where
1489 Self: Sized,
1490 {
1491 self.interactivity().on_resize(listener);
1492 self
1493 }
1494}
1495
1496pub(crate) type MouseDownListener =
1497 Box<dyn Fn(&MouseDownEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1498pub(crate) type MouseUpListener =
1499 Box<dyn Fn(&MouseUpEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1500
1501pub(crate) type MouseMoveListener =
1502 Box<dyn Fn(&MouseMoveEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1503pub(crate) type MouseExitListener =
1504 Box<dyn Fn(&MouseExitEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1505
1506pub(crate) type ScrollWheelListener =
1507 Box<dyn Fn(&ScrollWheelEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1508
1509pub(crate) type PinchListener =
1510 Box<dyn Fn(&PinchEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1511
1512pub(crate) type ClickListener = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>;
1513
1514pub(crate) type DragListener =
1515 Box<dyn Fn(&dyn Any, Point<Pixels>, &mut Window, &mut App) -> AnyView + 'static>;
1516
1517type DropListener = Box<dyn Fn(&dyn Any, &mut Window, &mut App) + 'static>;
1518
1519type CanDropPredicate = Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static>;
1520
1521pub(crate) struct TooltipBuilder {
1522 build: Rc<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>,
1523 hoverable: bool,
1524}
1525
1526pub(crate) type KeyDownListener =
1527 Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1528
1529pub(crate) type KeyUpListener =
1530 Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1531
1532pub(crate) type ModifiersChangedListener =
1533 Box<dyn Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static>;
1534
1535pub(crate) type ActionListener =
1536 Box<dyn Fn(&dyn Any, DispatchPhase, &mut Window, &mut App) + 'static>;
1537
1538pub(crate) type ResizeListener = Box<dyn Fn(&ResizeEvent, &mut Window, &mut App) + 'static>;
1539
1540#[track_caller]
1542pub fn div() -> Div {
1543 Div {
1544 interactivity: Interactivity::new(),
1545 children: SmallVec::default(),
1546 prepaint_listener: None,
1547 image_cache: None,
1548 }
1549}
1550
1551pub struct Div {
1553 interactivity: Interactivity,
1554 children: SmallVec<[StackSafe<AnyElement>; 2]>,
1555 prepaint_listener: Option<Box<dyn Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static>>,
1556 image_cache: Option<Box<dyn ImageCacheProvider>>,
1557}
1558
1559impl Div {
1560 pub fn on_children_prepainted(
1563 mut self,
1564 listener: impl Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static,
1565 ) -> Self {
1566 self.prepaint_listener = Some(Box::new(listener));
1567 self
1568 }
1569
1570 pub fn image_cache(mut self, cache: impl ImageCacheProvider) -> Self {
1572 self.image_cache = Some(Box::new(cache));
1573 self
1574 }
1575}
1576
1577pub struct DivFrameState {
1584 child_layout_ids: SmallVec<[LayoutId; 2]>,
1585}
1586
1587#[derive(Clone)]
1589pub struct DivInspectorState {
1590 #[cfg(any(feature = "inspector", debug_assertions))]
1594 pub base_style: Box<StyleRefinement>,
1595 pub bounds: Bounds<Pixels>,
1597 pub content_size: Size<Pixels>,
1599}
1600
1601impl Styled for Div {
1602 fn style(&mut self) -> &mut StyleRefinement {
1603 &mut self.interactivity.base_style
1604 }
1605}
1606
1607impl InteractiveElement for Div {
1608 fn interactivity(&mut self) -> &mut Interactivity {
1609 &mut self.interactivity
1610 }
1611}
1612
1613impl ParentElement for Div {
1614 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1615 self.children
1616 .extend(elements.into_iter().map(StackSafe::new))
1617 }
1618}
1619
1620impl Element for Div {
1621 type RequestLayoutState = DivFrameState;
1622 type PrepaintState = Option<Hitbox>;
1623
1624 fn id(&self) -> Option<ElementId> {
1625 self.interactivity.element_id.clone()
1626 }
1627
1628 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
1629 self.interactivity.source_location()
1630 }
1631
1632 fn a11y_role(&self) -> Option<accesskit::Role> {
1633 self.interactivity
1634 .override_role
1635 .filter(|role| *role != accesskit::Role::GenericContainer)
1636 }
1637
1638 fn write_a11y_info(&self, node: &mut accesskit::Node) {
1639 self.interactivity.write_a11y_info(node);
1640 }
1641
1642 #[stacksafe]
1643 fn request_layout(
1644 &mut self,
1645 global_id: Option<&GlobalElementId>,
1646 inspector_id: Option<&InspectorElementId>,
1647 window: &mut Window,
1648 cx: &mut App,
1649 ) -> (LayoutId, Self::RequestLayoutState) {
1650 let mut child_layout_ids = SmallVec::new();
1651 let image_cache = self
1652 .image_cache
1653 .as_mut()
1654 .map(|provider| provider.provide(window, cx));
1655
1656 let layout_id = window.with_image_cache(image_cache, |window| {
1657 self.interactivity.request_layout(
1658 global_id,
1659 inspector_id,
1660 window,
1661 cx,
1662 |style, window, cx| {
1663 window.with_text_style(style.text_style().cloned(), |window| {
1664 child_layout_ids = self
1665 .children
1666 .iter_mut()
1667 .map(|child| child.request_layout(window, cx))
1668 .collect::<SmallVec<_>>();
1669 window.request_layout(style, child_layout_ids.iter().copied(), cx)
1670 })
1671 },
1672 )
1673 });
1674
1675 (layout_id, DivFrameState { child_layout_ids })
1676 }
1677
1678 #[stacksafe]
1679 fn prepaint(
1680 &mut self,
1681 global_id: Option<&GlobalElementId>,
1682 inspector_id: Option<&InspectorElementId>,
1683 bounds: Bounds<Pixels>,
1684 request_layout: &mut Self::RequestLayoutState,
1685 window: &mut Window,
1686 cx: &mut App,
1687 ) -> Option<Hitbox> {
1688 let has_prepaint_listener = self.prepaint_listener.is_some();
1689 let mut children_bounds = Vec::with_capacity(if has_prepaint_listener {
1690 request_layout.child_layout_ids.len()
1691 } else {
1692 0
1693 });
1694
1695 let mut child_min = point(Pixels::MAX, Pixels::MAX);
1696 let mut child_max = Point::default();
1697 if let Some(handle) = self.interactivity.scroll_anchor.as_ref() {
1698 *handle.last_origin.borrow_mut() = bounds.origin - window.element_offset();
1699 }
1700 let content_size = if request_layout.child_layout_ids.is_empty() {
1701 bounds.size
1702 } else if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
1703 let mut state = scroll_handle.0.borrow_mut();
1704 state.child_bounds = Vec::with_capacity(request_layout.child_layout_ids.len());
1705 for child_layout_id in &request_layout.child_layout_ids {
1706 let child_bounds = window.layout_bounds(*child_layout_id);
1707 child_min = child_min.min(&child_bounds.origin);
1708 child_max = child_max.max(&child_bounds.bottom_right());
1709 state.child_bounds.push(child_bounds);
1710 }
1711 (child_max - child_min).into()
1712 } else {
1713 for child_layout_id in &request_layout.child_layout_ids {
1714 let child_bounds = window.layout_bounds(*child_layout_id);
1715 child_min = child_min.min(&child_bounds.origin);
1716 child_max = child_max.max(&child_bounds.bottom_right());
1717
1718 if has_prepaint_listener {
1719 children_bounds.push(child_bounds);
1720 }
1721 }
1722 (child_max - child_min).into()
1723 };
1724
1725 if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
1726 scroll_handle.scroll_to_active_item();
1727 }
1728
1729 self.interactivity.prepaint(
1730 global_id,
1731 inspector_id,
1732 bounds,
1733 content_size,
1734 window,
1735 cx,
1736 |_style, scroll_offset, hitbox, window, cx| {
1737 window.with_element_offset(scroll_offset, |window| {
1738 for child in &mut self.children {
1739 child.prepaint(window, cx);
1740 }
1741 });
1742
1743 if let Some(listener) = self.prepaint_listener.as_ref() {
1744 listener(children_bounds, window, cx);
1745 }
1746
1747 hitbox
1748 },
1749 )
1750 }
1751
1752 #[stacksafe]
1753 fn paint(
1754 &mut self,
1755 global_id: Option<&GlobalElementId>,
1756 inspector_id: Option<&InspectorElementId>,
1757 bounds: Bounds<Pixels>,
1758 _request_layout: &mut Self::RequestLayoutState,
1759 hitbox: &mut Option<Hitbox>,
1760 window: &mut Window,
1761 cx: &mut App,
1762 ) {
1763 let image_cache = self
1764 .image_cache
1765 .as_mut()
1766 .map(|provider| provider.provide(window, cx));
1767
1768 window.with_image_cache(image_cache, |window| {
1769 self.interactivity.paint(
1770 global_id,
1771 inspector_id,
1772 bounds,
1773 hitbox.as_ref(),
1774 window,
1775 cx,
1776 |_style, window, cx| {
1777 for child in &mut self.children {
1778 child.paint(window, cx);
1779 }
1780 },
1781 )
1782 });
1783 }
1784}
1785
1786impl IntoElement for Div {
1787 type Element = Self;
1788
1789 fn into_element(self) -> Self::Element {
1790 self
1791 }
1792}
1793
1794#[derive(Default)]
1797pub struct Interactivity {
1798 pub element_id: Option<ElementId>,
1800 pub active: Option<bool>,
1802 pub hovered: Option<bool>,
1805 pub(crate) tooltip_id: Option<TooltipId>,
1806 pub(crate) content_size: Size<Pixels>,
1807 pub(crate) key_context: Option<KeyContext>,
1808 pub(crate) focusable: bool,
1809 pub(crate) tracked_focus_handle: Option<FocusHandle>,
1810 pub(crate) tracked_scroll_handle: Option<ScrollHandle>,
1811 pub(crate) scroll_anchor: Option<ScrollAnchor>,
1812 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
1813 pub(crate) ongoing_scroll: Option<Rc<RefCell<OngoingScroll>>>,
1814 pub(crate) group: Option<SharedString>,
1815 pub base_style: Box<StyleRefinement>,
1818 pub(crate) focus_style: Option<Box<StyleRefinement>>,
1819 pub(crate) in_focus_style: Option<Box<StyleRefinement>>,
1820 pub(crate) hover_style: Option<Box<StyleRefinement>>,
1821 pub(crate) group_hover_style: Option<GroupStyle>,
1822 pub(crate) active_style: Option<Box<StyleRefinement>>,
1823 pub(crate) group_active_style: Option<GroupStyle>,
1824 pub(crate) drag_over_styles: Vec<(
1825 TypeId,
1826 Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> StyleRefinement>,
1827 )>,
1828 pub(crate) group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
1829 pub(crate) mouse_down_listeners: Vec<MouseDownListener>,
1830 pub(crate) mouse_up_listeners: Vec<MouseUpListener>,
1831 pub(crate) mouse_move_listeners: Vec<MouseMoveListener>,
1832 pub(crate) mouse_exit_listeners: Vec<MouseExitListener>,
1833 pub(crate) scroll_wheel_listeners: Vec<ScrollWheelListener>,
1834 pub(crate) pinch_listeners: Vec<PinchListener>,
1835 pub(crate) key_down_listeners: Vec<KeyDownListener>,
1836 pub(crate) key_up_listeners: Vec<KeyUpListener>,
1837 pub(crate) modifiers_changed_listeners: Vec<ModifiersChangedListener>,
1838 pub(crate) action_listeners: Vec<(TypeId, ActionListener)>,
1839 pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
1840 pub(crate) can_drop_predicate: Option<CanDropPredicate>,
1841 pub(crate) click_listeners: Vec<ClickListener>,
1842 pub(crate) drag_listener: Option<(Arc<dyn Any>, DragListener)>,
1843 pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut Window, &mut App)>>,
1844 pub(crate) resize_listeners: Vec<ResizeListener>,
1845 pub(crate) tooltip_builder: Option<TooltipBuilder>,
1846 pub(crate) tooltip_show_delay: Option<Duration>,
1847 pub(crate) window_control: Option<WindowControlArea>,
1848 pub(crate) hitbox_behavior: HitboxBehavior,
1849 pub(crate) tab_index: Option<isize>,
1850 pub(crate) tab_group: bool,
1851 pub(crate) tab_stop: bool,
1852
1853 pub(crate) a11y_action_listeners:
1854 Vec<(accesskit::Action, crate::window::a11y::A11yActionListener)>,
1855 pub(crate) override_role: Option<accesskit::Role>,
1856 pub(crate) accessibility_id: Option<SharedString>,
1857 pub(crate) aria_label: Option<SharedString>,
1858 pub(crate) aria_description: Option<SharedString>,
1859 pub(crate) aria_value: Option<SharedString>,
1860 pub(crate) aria_placeholder: Option<SharedString>,
1861 pub(crate) aria_disabled: bool,
1862 pub(crate) aria_required: bool,
1863 pub(crate) aria_invalid: Option<accesskit::Invalid>,
1864 pub(crate) aria_modal: bool,
1865 pub(crate) aria_keyshortcuts: Option<SharedString>,
1866 pub(crate) aria_selected: Option<bool>,
1867 pub(crate) aria_expanded: Option<bool>,
1868 pub(crate) aria_toggled: Option<accesskit::Toggled>,
1869 pub(crate) aria_numeric_value: Option<f64>,
1870 pub(crate) aria_min_numeric_value: Option<f64>,
1871 pub(crate) aria_max_numeric_value: Option<f64>,
1872 pub(crate) aria_orientation: Option<accesskit::Orientation>,
1873 pub(crate) aria_level: Option<usize>,
1874 pub(crate) aria_position_in_set: Option<usize>,
1875 pub(crate) aria_size_of_set: Option<usize>,
1876 pub(crate) aria_row_index: Option<usize>,
1877 pub(crate) aria_column_index: Option<usize>,
1878 pub(crate) aria_row_count: Option<usize>,
1879 pub(crate) aria_column_count: Option<usize>,
1880
1881 #[cfg(any(feature = "inspector", debug_assertions))]
1882 pub(crate) source_location: Option<&'static core::panic::Location<'static>>,
1883
1884 #[cfg(any(test, feature = "test-support"))]
1885 pub(crate) debug_selector: Option<String>,
1886}
1887
1888impl Interactivity {
1889 pub fn request_layout(
1891 &mut self,
1892 global_id: Option<&GlobalElementId>,
1893 _inspector_id: Option<&InspectorElementId>,
1894 window: &mut Window,
1895 cx: &mut App,
1896 f: impl FnOnce(Style, &mut Window, &mut App) -> LayoutId,
1897 ) -> LayoutId {
1898 #[cfg(any(feature = "inspector", debug_assertions))]
1899 window.with_inspector_state(
1900 _inspector_id,
1901 cx,
1902 |inspector_state: &mut Option<DivInspectorState>, _window| {
1903 if let Some(inspector_state) = inspector_state {
1904 self.base_style = inspector_state.base_style.clone();
1905 } else {
1906 *inspector_state = Some(DivInspectorState {
1907 base_style: self.base_style.clone(),
1908 bounds: Default::default(),
1909 content_size: Default::default(),
1910 })
1911 }
1912 },
1913 );
1914
1915 window.with_optional_element_state::<InteractiveElementState, _>(
1916 global_id,
1917 |element_state, window| {
1918 let mut element_state =
1919 element_state.map(|element_state| element_state.unwrap_or_default());
1920
1921 if let Some(element_state) = element_state.as_ref()
1922 && cx.has_active_drag()
1923 {
1924 if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() {
1925 *pending_mouse_down.borrow_mut() = None;
1926 }
1927 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
1928 *clicked_state.borrow_mut() = ElementClickedState::default();
1929 }
1930 }
1931
1932 if self.focusable
1937 && self.tracked_focus_handle.is_none()
1938 && let Some(element_state) = element_state.as_mut()
1939 {
1940 let mut handle = element_state
1941 .focus_handle
1942 .get_or_insert_with(|| cx.focus_handle())
1943 .clone()
1944 .tab_stop(self.tab_stop);
1945
1946 if let Some(index) = self.tab_index {
1947 handle = handle.tab_index(index);
1948 }
1949
1950 self.tracked_focus_handle = Some(handle);
1951 }
1952
1953 if let Some(scroll_handle) = self.tracked_scroll_handle.as_ref() {
1954 let scroll_handle_state = scroll_handle.0.borrow();
1955 self.scroll_offset = Some(scroll_handle_state.offset.clone());
1956 self.ongoing_scroll = Some(scroll_handle_state.ongoing_scroll.clone());
1957 } else if (self.base_style.overflow.x == Some(Overflow::Scroll)
1958 || self.base_style.overflow.y == Some(Overflow::Scroll))
1959 && let Some(element_state) = element_state.as_mut()
1960 {
1961 self.scroll_offset = Some(
1962 element_state
1963 .scroll_offset
1964 .get_or_insert_with(Rc::default)
1965 .clone(),
1966 );
1967 self.ongoing_scroll = Some(
1968 element_state
1969 .ongoing_scroll
1970 .get_or_insert_with(|| Rc::new(RefCell::new(OngoingScroll::default())))
1971 .clone(),
1972 );
1973 }
1974
1975 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
1976 let layout_id = f(style, window, cx);
1977 (layout_id, element_state)
1978 },
1979 )
1980 }
1981
1982 pub fn prepaint<R>(
1984 &mut self,
1985 global_id: Option<&GlobalElementId>,
1986 _inspector_id: Option<&InspectorElementId>,
1987 bounds: Bounds<Pixels>,
1988 content_size: Size<Pixels>,
1989 window: &mut Window,
1990 cx: &mut App,
1991 f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut Window, &mut App) -> R,
1992 ) -> R {
1993 self.content_size = content_size;
1994
1995 #[cfg(any(feature = "inspector", debug_assertions))]
1996 window.with_inspector_state(
1997 _inspector_id,
1998 cx,
1999 |inspector_state: &mut Option<DivInspectorState>, _window| {
2000 if let Some(inspector_state) = inspector_state {
2001 inspector_state.bounds = bounds;
2002 inspector_state.content_size = content_size;
2003 }
2004 },
2005 );
2006
2007 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
2008 window.set_focus_handle(focus_handle, cx);
2009
2010 if window.a11y.is_active()
2011 && let Some(global_id) = global_id
2012 {
2013 let node_id = global_id.accesskit_node_id();
2014 window.a11y.focus_ids.insert(node_id, focus_handle.id);
2015 if focus_handle.is_focused(window) && window.a11y.nodes.has_node(node_id) {
2016 window.a11y.nodes.set_focus(node_id);
2017 }
2018 }
2019 }
2020 window.with_optional_element_state::<InteractiveElementState, _>(
2021 global_id,
2022 |element_state, window| {
2023 let mut element_state =
2024 element_state.map(|element_state| element_state.unwrap_or_default());
2025 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
2026
2027 if let Some(element_state) = element_state.as_mut() {
2028 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
2029 let clicked_state = clicked_state.borrow();
2030 self.active = Some(clicked_state.element);
2031 }
2032 if let Some(active_tooltip) = element_state.active_tooltip.as_ref() {
2033 if self.tooltip_builder.is_some() {
2034 self.tooltip_id = set_tooltip_on_window(active_tooltip, window);
2035 } else {
2036 element_state.active_tooltip.take();
2038 }
2039 }
2040 }
2041
2042 window.with_text_style(style.text_style().cloned(), |window| {
2043 window.with_content_mask(
2044 style.overflow_mask(bounds, window.rem_size()),
2045 |window| {
2046 let hitbox = if self.should_insert_hitbox(&style, window, cx) {
2047 Some(window.insert_hitbox(bounds, self.hitbox_behavior))
2048 } else {
2049 None
2050 };
2051
2052 let scroll_offset =
2053 self.clamp_scroll_position(bounds, &style, window, cx);
2054 let result = f(&style, scroll_offset, hitbox, window, cx);
2055 (result, element_state)
2056 },
2057 )
2058 })
2059 },
2060 )
2061 }
2062
2063 fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
2064 self.hitbox_behavior != HitboxBehavior::Normal
2065 || self.window_control.is_some()
2066 || style.mouse_cursor.is_some()
2067 || self.group.is_some()
2068 || self.scroll_offset.is_some()
2069 || self.tracked_focus_handle.is_some()
2070 || self.hover_style.is_some()
2071 || self.group_hover_style.is_some()
2072 || self.hover_listener.is_some()
2073 || !self.mouse_up_listeners.is_empty()
2074 || !self.mouse_down_listeners.is_empty()
2075 || !self.mouse_move_listeners.is_empty()
2076 || !self.mouse_exit_listeners.is_empty()
2077 || !self.click_listeners.is_empty()
2078 || !self.scroll_wheel_listeners.is_empty()
2079 || !self.pinch_listeners.is_empty()
2080 || self.drag_listener.is_some()
2081 || !self.drop_listeners.is_empty()
2082 || self.tooltip_builder.is_some()
2083 || window.is_inspector_picking(cx)
2084 }
2085
2086 fn clamp_scroll_position(
2087 &self,
2088 bounds: Bounds<Pixels>,
2089 style: &Style,
2090 window: &mut Window,
2091 _cx: &mut App,
2092 ) -> Point<Pixels> {
2093 fn round_to_two_decimals(pixels: Pixels) -> Pixels {
2094 const ROUNDING_FACTOR: f32 = 100.0;
2095 (pixels * ROUNDING_FACTOR).round() / ROUNDING_FACTOR
2096 }
2097
2098 if let Some(scroll_offset) = self.scroll_offset.as_ref() {
2099 let mut scroll_to_bottom = false;
2100 let mut tracked_scroll_handle = self
2101 .tracked_scroll_handle
2102 .as_ref()
2103 .map(|handle| handle.0.borrow_mut());
2104 if let Some(mut scroll_handle_state) = tracked_scroll_handle.as_deref_mut() {
2105 scroll_handle_state.overflow = style.overflow;
2106 scroll_to_bottom = mem::take(&mut scroll_handle_state.scroll_to_bottom);
2107 }
2108
2109 let rem_size = window.rem_size();
2110 let padding = style.padding.to_pixels(bounds.size.into(), rem_size);
2111 let padding_size = size(padding.left + padding.right, padding.top + padding.bottom);
2112 let padded_content_size = self.content_size + padding_size;
2119 let scroll_max = (padded_content_size - bounds.size)
2120 .map(round_to_two_decimals)
2121 .max(&Default::default());
2122 let mut scroll_offset = scroll_offset.borrow_mut();
2125
2126 scroll_offset.x = scroll_offset.x.clamp(-scroll_max.width, px(0.));
2127 if scroll_to_bottom {
2128 scroll_offset.y = -scroll_max.height;
2129 } else {
2130 scroll_offset.y = scroll_offset.y.clamp(-scroll_max.height, px(0.));
2131 }
2132
2133 if let Some(mut scroll_handle_state) = tracked_scroll_handle {
2134 scroll_handle_state.max_offset = scroll_max;
2135 scroll_handle_state.bounds = bounds;
2136 }
2137
2138 *scroll_offset
2139 } else {
2140 Point::default()
2141 }
2142 }
2143
2144 pub fn paint(
2153 &mut self,
2154 global_id: Option<&GlobalElementId>,
2155 _inspector_id: Option<&InspectorElementId>,
2156 bounds: Bounds<Pixels>,
2157 hitbox: Option<&Hitbox>,
2158 window: &mut Window,
2159 cx: &mut App,
2160 f: impl FnOnce(&Style, &mut Window, &mut App),
2161 ) {
2162 self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(window));
2163 window.with_optional_element_state::<InteractiveElementState, _>(
2164 global_id,
2165 |element_state, window| {
2166 let mut element_state =
2167 element_state.map(|element_state| element_state.unwrap_or_default());
2168
2169 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
2170
2171 #[cfg(any(feature = "test-support", test))]
2172 if let Some(debug_selector) = &self.debug_selector {
2173 window
2174 .next_frame
2175 .debug_bounds
2176 .insert(debug_selector.clone(), bounds);
2177 }
2178
2179 self.paint_hover_group_handler(window, cx);
2180
2181 if !self.resize_listeners.is_empty() {
2182 if let Some(element_state) = element_state.as_mut() {
2183 let size_changed = element_state
2184 .prev_bounds
2185 .map(|prev| prev.size != bounds.size)
2186 .unwrap_or(true);
2187
2188 if size_changed {
2189 element_state.prev_bounds = Some(bounds);
2190 let resize_event = ResizeEvent {
2191 size: bounds.size,
2192 bounds,
2193 };
2194 for listener in &self.resize_listeners {
2195 listener(&resize_event, window, cx);
2196 }
2197 }
2198 }
2199 }
2200
2201 if style.visibility == Visibility::Hidden {
2202 return ((), element_state);
2203 }
2204
2205 let mut tab_group = None;
2206 if self.tab_group {
2207 tab_group = self.tab_index;
2208 }
2209 if let Some(focus_handle) = &self.tracked_focus_handle {
2210 window.next_frame.tab_stops.insert(focus_handle);
2211 }
2212
2213 window.with_element_opacity(style.opacity, |window| {
2214 style.paint(bounds, window, cx, |window: &mut Window, cx: &mut App| {
2215 window.with_text_style(style.text_style().cloned(), |window| {
2216 window.with_content_mask(
2217 style.overflow_mask(bounds, window.rem_size()),
2218 |window| {
2219 window.with_tab_group(tab_group, |window| {
2220 if let Some(hitbox) = hitbox {
2221 #[cfg(debug_assertions)]
2222 self.paint_debug_info(
2223 global_id, hitbox, &style, window, cx,
2224 );
2225
2226 if let Some(drag) = cx.active_drag.as_ref() {
2227 if let Some(mouse_cursor) = drag.cursor_style {
2228 window.set_window_cursor_style(mouse_cursor);
2229 }
2230 } else {
2231 if let Some(mouse_cursor) = style.mouse_cursor {
2232 window.set_cursor_style(mouse_cursor, hitbox);
2233 }
2234 }
2235
2236 if let Some(group) = self.group.clone() {
2237 GroupHitboxes::push(group, hitbox.id, cx);
2238 }
2239
2240 if let Some(area) = self.window_control {
2241 window.insert_window_control_hitbox(
2242 area,
2243 hitbox.clone(),
2244 );
2245 }
2246
2247 self.paint_mouse_listeners(
2248 hitbox,
2249 element_state.as_mut(),
2250 window,
2251 cx,
2252 );
2253 self.paint_scroll_listener(hitbox, &style, window, cx);
2254 }
2255
2256 self.paint_keyboard_listeners(window, cx);
2257
2258 if window.a11y.is_active()
2259 && let Some(global_id) = global_id
2260 && !self.a11y_action_listeners.is_empty()
2261 {
2262 let node_id = global_id.accesskit_node_id();
2263 for (action, listener) in
2264 self.a11y_action_listeners.drain(..)
2265 {
2266 window.on_a11y_action(node_id, action, listener);
2267 }
2268 }
2269
2270 f(&style, window, cx);
2271
2272 if let Some(_hitbox) = hitbox {
2273 #[cfg(any(feature = "inspector", debug_assertions))]
2274 window.insert_inspector_hitbox(
2275 _hitbox.id,
2276 _inspector_id,
2277 cx,
2278 );
2279
2280 if let Some(group) = self.group.as_ref() {
2281 GroupHitboxes::pop(group, cx);
2282 }
2283 }
2284 })
2285 },
2286 );
2287 });
2288 });
2289 });
2290
2291 ((), element_state)
2292 },
2293 );
2294 }
2295
2296 #[cfg(debug_assertions)]
2297 fn paint_debug_info(
2298 &self,
2299 global_id: Option<&GlobalElementId>,
2300 hitbox: &Hitbox,
2301 style: &Style,
2302 window: &mut Window,
2303 cx: &mut App,
2304 ) {
2305 use crate::{BorderStyle, TextAlign};
2306
2307 if let Some(global_id) = global_id.as_ref().filter(|_| {
2308 (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
2309 && hitbox.is_hovered(window)
2310 }) {
2311 const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
2312 let element_id = format!("{:?}", global_id);
2313 let str_len = element_id.len();
2314
2315 let render_debug_text = |window: &mut Window| {
2316 if let Some(text) = window
2317 .text_system()
2318 .shape_text(
2319 element_id.into(),
2320 FONT_SIZE,
2321 &[window.text_style().to_run(str_len)],
2322 None,
2323 None,
2324 )
2325 .ok()
2326 .and_then(|mut text| text.pop())
2327 {
2328 text.paint(hitbox.origin, FONT_SIZE, TextAlign::Left, None, window, cx)
2329 .ok();
2330
2331 let text_bounds = crate::Bounds {
2332 origin: hitbox.origin,
2333 size: text.size(FONT_SIZE),
2334 };
2335 if let Some(source_location) = self.source_location.filter(|_| {
2336 text_bounds.contains(&window.mouse_position())
2337 && window.modifiers().secondary()
2338 }) {
2339 let secondary_held = window.modifiers().secondary();
2340 window.on_key_event({
2341 move |e: &crate::ModifiersChangedEvent, _phase, window, _cx| {
2342 if e.modifiers.secondary() != secondary_held
2343 && text_bounds.contains(&window.mouse_position())
2344 {
2345 window.refresh();
2346 }
2347 }
2348 });
2349
2350 let was_hovered = hitbox.is_hovered(window);
2351 let current_view = window.current_view();
2352 window.on_mouse_event({
2353 let hitbox = hitbox.clone();
2354 move |_: &MouseMoveEvent, phase, window, cx| {
2355 if phase == DispatchPhase::Capture {
2356 let hovered = hitbox.is_hovered(window);
2357 if hovered != was_hovered {
2358 cx.notify(current_view)
2359 }
2360 }
2361 }
2362 });
2363
2364 window.on_mouse_event({
2365 let hitbox = hitbox.clone();
2366 let location = source_location;
2367 move |e: &crate::MouseDownEvent, phase, window, cx| {
2368 if text_bounds.contains(&e.position)
2369 && phase.capture()
2370 && hitbox.is_hovered(window)
2371 {
2372 cx.stop_propagation();
2373 let Ok(dir) = std::env::current_dir() else {
2374 return;
2375 };
2376
2377 eprintln!(
2378 "This element was created at:\n{}:{}:{}",
2379 dir.join(location.file()).to_string_lossy(),
2380 location.line(),
2381 location.column()
2382 );
2383 }
2384 }
2385 });
2386 window.paint_quad(crate::outline(
2387 crate::Bounds {
2388 origin: hitbox.origin
2389 + crate::point(crate::px(0.), FONT_SIZE - px(2.)),
2390 size: crate::Size {
2391 width: text_bounds.size.width,
2392 height: crate::px(1.),
2393 },
2394 },
2395 crate::red(),
2396 BorderStyle::default(),
2397 ))
2398 }
2399 }
2400 };
2401
2402 window.with_text_style(
2403 Some(crate::TextStyleRefinement {
2404 color: Some(crate::red()),
2405 line_height: Some(FONT_SIZE.into()),
2406 background_color: Some(crate::white()),
2407 ..Default::default()
2408 }),
2409 render_debug_text,
2410 )
2411 }
2412 }
2413
2414 fn paint_mouse_listeners(
2415 &mut self,
2416 hitbox: &Hitbox,
2417 element_state: Option<&mut InteractiveElementState>,
2418 window: &mut Window,
2419 cx: &mut App,
2420 ) {
2421 let is_focused = self
2422 .tracked_focus_handle
2423 .as_ref()
2424 .map(|handle| handle.is_focused(window))
2425 .unwrap_or(false);
2426
2427 if let Some(focus_handle) = self.tracked_focus_handle.clone() {
2431 let hitbox = hitbox.clone();
2432 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _| {
2433 if phase == DispatchPhase::Bubble
2434 && hitbox.is_hovered(window)
2435 && !window.default_prevented()
2436 {
2437 window.focus(&focus_handle);
2438 window.prevent_default();
2441 }
2442 });
2443 }
2444
2445 for listener in self.mouse_down_listeners.drain(..) {
2446 let hitbox = hitbox.clone();
2447 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
2448 listener(event, phase, &hitbox, window, cx);
2449 })
2450 }
2451
2452 for listener in self.mouse_up_listeners.drain(..) {
2453 let hitbox = hitbox.clone();
2454 window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
2455 listener(event, phase, &hitbox, window, cx);
2456 })
2457 }
2458
2459 for listener in self.mouse_move_listeners.drain(..) {
2460 let hitbox = hitbox.clone();
2461 window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
2462 listener(event, phase, &hitbox, window, cx);
2463 })
2464 }
2465
2466 for listener in self.mouse_exit_listeners.drain(..) {
2467 let hitbox = hitbox.clone();
2468 window.on_mouse_event(move |event: &MouseExitEvent, phase, window, cx| {
2469 listener(event, phase, &hitbox, window, cx);
2470 })
2471 }
2472
2473 for listener in self.scroll_wheel_listeners.drain(..) {
2474 let hitbox = hitbox.clone();
2475 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
2476 listener(event, phase, &hitbox, window, cx);
2477 })
2478 }
2479
2480 for listener in self.pinch_listeners.drain(..) {
2481 let hitbox = hitbox.clone();
2482 window.on_mouse_event(move |event: &PinchEvent, phase, window, cx| {
2483 listener(event, phase, &hitbox, window, cx);
2484 })
2485 }
2486
2487 if self.hover_style.is_some()
2488 || self.base_style.mouse_cursor.is_some()
2489 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
2490 {
2491 let hitbox = hitbox.clone();
2492 let was_hovered = hitbox.is_hovered(window);
2493 let current_view = window.current_view();
2494 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2495 let hovered = hitbox.is_hovered(window);
2496 if phase == DispatchPhase::Capture && hovered != was_hovered {
2497 cx.notify(current_view);
2498 }
2499 });
2500 }
2501 let drag_cursor_style = self.base_style.as_ref().mouse_cursor;
2502
2503 let mut drag_listener = mem::take(&mut self.drag_listener);
2504 let drop_listeners = mem::take(&mut self.drop_listeners);
2505 let click_listeners = mem::take(&mut self.click_listeners);
2506 let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
2507
2508 if !drop_listeners.is_empty() {
2509 let hitbox = hitbox.clone();
2510 window.on_mouse_event({
2511 move |_: &MouseUpEvent, phase, window, cx| {
2512 if let Some(drag) = &cx.active_drag
2513 && phase == DispatchPhase::Bubble
2514 && hitbox.is_hovered(window)
2515 {
2516 let drag_state_type = drag.value.as_ref().type_id();
2517 for (drop_state_type, listener) in &drop_listeners {
2518 if *drop_state_type == drag_state_type {
2519 let drag = cx
2520 .active_drag
2521 .take()
2522 .expect("checked for type drag state type above");
2523
2524 let mut can_drop = true;
2525 if let Some(predicate) = &can_drop_predicate {
2526 can_drop = predicate(drag.value.as_ref(), window, cx);
2527 }
2528
2529 if can_drop {
2530 listener(drag.value.as_ref(), window, cx);
2531 window.refresh();
2532 cx.stop_propagation();
2533 }
2534 }
2535 }
2536 }
2537 }
2538 });
2539 }
2540
2541 if let Some(element_state) = element_state {
2542 if !click_listeners.is_empty() || drag_listener.is_some() {
2543 let pending_mouse_down = element_state
2544 .pending_mouse_down
2545 .get_or_insert_with(Default::default)
2546 .clone();
2547
2548 let clicked_state = element_state
2549 .clicked_state
2550 .get_or_insert_with(Default::default)
2551 .clone();
2552
2553 window.on_mouse_event({
2554 let pending_mouse_down = pending_mouse_down.clone();
2555 let hitbox = hitbox.clone();
2556 move |event: &MouseDownEvent, phase, window, _cx| {
2557 if phase == DispatchPhase::Bubble
2558 && event.button == MouseButton::Left
2559 && hitbox.is_hovered(window)
2560 {
2561 *pending_mouse_down.borrow_mut() = Some(event.clone());
2562 window.refresh();
2563 }
2564 }
2565 });
2566
2567 window.on_mouse_event({
2568 let pending_mouse_down = pending_mouse_down.clone();
2569 let hitbox = hitbox.clone();
2570 move |event: &MouseMoveEvent, phase, window, cx| {
2571 if phase == DispatchPhase::Capture {
2572 return;
2573 }
2574
2575 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2576 if let Some(mouse_down) = pending_mouse_down.clone()
2577 && !cx.has_active_drag()
2578 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
2579 && let Some((drag_value, drag_listener)) = drag_listener.take()
2580 {
2581 *clicked_state.borrow_mut() = ElementClickedState::default();
2582 let cursor_offset = event.position - hitbox.origin;
2583 let drag =
2584 (drag_listener)(drag_value.as_ref(), cursor_offset, window, cx);
2585 cx.active_drag = Some(AnyDrag {
2586 view: drag,
2587 value: drag_value,
2588 cursor_offset,
2589 cursor_style: drag_cursor_style,
2590 });
2591 pending_mouse_down.take();
2592 window.refresh();
2593 cx.stop_propagation();
2594 }
2595 }
2596 });
2597
2598 if is_focused {
2599 window.on_key_event({
2601 let click_listeners = click_listeners.clone();
2602 let hitbox = hitbox.clone();
2603 move |event: &KeyUpEvent, phase, window, cx| {
2604 if phase.bubble() && !window.default_prevented() {
2605 let stroke = &event.keystroke;
2606 let keyboard_button = if stroke.key.eq("enter") {
2607 Some(KeyboardButton::Enter)
2608 } else if stroke.key.eq("space") {
2609 Some(KeyboardButton::Space)
2610 } else {
2611 None
2612 };
2613
2614 if let Some(button) = keyboard_button
2615 && !stroke.modifiers.modified()
2616 {
2617 let click_event = ClickEvent::Keyboard(KeyboardClickEvent {
2618 button,
2619 bounds: hitbox.bounds,
2620 });
2621
2622 for listener in &click_listeners {
2623 listener(&click_event, window, cx);
2624 }
2625 }
2626 }
2627 }
2628 });
2629 }
2630
2631 window.on_mouse_event({
2632 let mut captured_mouse_down = None;
2633 let hitbox = hitbox.clone();
2634 move |event: &MouseUpEvent, phase, window, cx| match phase {
2635 DispatchPhase::Capture => {
2639 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2640 if pending_mouse_down.is_some() && hitbox.is_hovered(window) {
2641 captured_mouse_down = pending_mouse_down.take();
2642 window.refresh();
2643 } else if pending_mouse_down.is_some() {
2644 pending_mouse_down.take();
2650 window.refresh();
2651 }
2652 }
2653 DispatchPhase::Bubble => {
2655 if let Some(mouse_down) = captured_mouse_down.take() {
2656 let mouse_click = ClickEvent::Mouse(MouseClickEvent {
2657 down: mouse_down,
2658 up: event.clone(),
2659 });
2660 for listener in &click_listeners {
2661 listener(&mouse_click, window, cx);
2662 }
2663 }
2664 }
2665 }
2666 });
2667 }
2668
2669 if let Some(hover_listener) = self.hover_listener.take() {
2670 let was_hovered = element_state
2671 .hover_state
2672 .get_or_insert_with(Default::default)
2673 .clone();
2674 let has_mouse_down = element_state
2675 .pending_mouse_down
2676 .get_or_insert_with(Default::default)
2677 .clone();
2678 let hover_listener = Rc::new(hover_listener);
2679 let hover_listener_state = was_hovered.clone();
2680 let update_hover = move |is_hovered: bool, window: &mut Window, cx: &mut App| {
2681 let mut was_hovered = hover_listener_state.borrow_mut();
2682 if is_hovered != *was_hovered {
2683 *was_hovered = is_hovered;
2684 drop(was_hovered);
2685 hover_listener(&is_hovered, window, cx);
2686 }
2687 };
2688
2689 if has_mouse_down.borrow().is_none() {
2690 let is_hovered = !cx.has_active_drag() && hitbox.is_hovered(window);
2691 if is_hovered != *was_hovered.borrow() {
2692 let update_hover = update_hover.clone();
2693 window.defer(cx, move |window, cx| {
2694 update_hover(is_hovered, window, cx);
2695 });
2696 }
2697 }
2698
2699 window.on_mouse_event({
2700 let update_hover = update_hover.clone();
2701 let hitbox = hitbox.clone();
2702 move |_: &MouseMoveEvent, phase, window, cx| {
2703 if phase == DispatchPhase::Bubble {
2704 let is_hovered = has_mouse_down.borrow().is_none()
2705 && !cx.has_active_drag()
2706 && hitbox.is_hovered(window);
2707 update_hover(is_hovered, window, cx);
2708 }
2709 }
2710 });
2711
2712 window.on_mouse_event(move |_: &MouseExitEvent, phase, window, cx| {
2715 if phase == DispatchPhase::Bubble {
2716 update_hover(false, window, cx);
2717 }
2718 });
2719 }
2720
2721 if let Some(tooltip_builder) = self.tooltip_builder.take() {
2722 let active_tooltip = element_state
2723 .active_tooltip
2724 .get_or_insert_with(Default::default)
2725 .clone();
2726 let pending_mouse_down = element_state
2727 .pending_mouse_down
2728 .get_or_insert_with(Default::default)
2729 .clone();
2730
2731 let tooltip_is_hoverable = tooltip_builder.hoverable;
2732 let build_tooltip = Rc::new(move |window: &mut Window, cx: &mut App| {
2733 Some(((tooltip_builder.build)(window, cx), tooltip_is_hoverable))
2734 });
2735 let check_is_hovered_during_prepaint = Rc::new({
2737 let pending_mouse_down = pending_mouse_down.clone();
2738 let source_bounds = hitbox.bounds;
2739 move |window: &Window| {
2740 pending_mouse_down.borrow().is_none()
2741 && source_bounds.contains(&window.mouse_position())
2742 }
2743 });
2744 let check_is_hovered = Rc::new({
2745 let hitbox = hitbox.clone();
2746 move |window: &Window| {
2747 pending_mouse_down.borrow().is_none() && hitbox.is_hovered(window)
2748 }
2749 });
2750 register_tooltip_mouse_handlers(
2751 &active_tooltip,
2752 self.tooltip_id,
2753 build_tooltip,
2754 check_is_hovered,
2755 check_is_hovered_during_prepaint,
2756 self.tooltip_show_delay,
2757 window,
2758 );
2759 }
2760
2761 let active_state = element_state
2762 .clicked_state
2763 .get_or_insert_with(Default::default)
2764 .clone();
2765 if active_state.borrow().is_clicked() {
2766 window.on_mouse_event(move |_: &MouseUpEvent, phase, window, _cx| {
2767 if phase == DispatchPhase::Capture {
2768 *active_state.borrow_mut() = ElementClickedState::default();
2769 window.refresh();
2770 }
2771 });
2772 } else {
2773 let active_group_hitbox = self
2774 .group_active_style
2775 .as_ref()
2776 .and_then(|group_active| GroupHitboxes::get(&group_active.group, cx));
2777 let hitbox = hitbox.clone();
2778 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _cx| {
2779 if phase == DispatchPhase::Bubble && !window.default_prevented() {
2780 let group_hovered = active_group_hitbox
2781 .is_some_and(|group_hitbox_id| group_hitbox_id.is_hovered(window));
2782 let element_hovered = hitbox.is_hovered(window);
2783 if group_hovered || element_hovered {
2784 *active_state.borrow_mut() = ElementClickedState {
2785 group: group_hovered,
2786 element: element_hovered,
2787 };
2788 window.refresh();
2789 }
2790 }
2791 });
2792 }
2793 }
2794 }
2795
2796 fn paint_keyboard_listeners(&mut self, window: &mut Window, _cx: &mut App) {
2797 let key_down_listeners = mem::take(&mut self.key_down_listeners);
2798 let key_up_listeners = mem::take(&mut self.key_up_listeners);
2799 let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners);
2800 let action_listeners = mem::take(&mut self.action_listeners);
2801 if let Some(context) = self.key_context.clone() {
2802 window.set_key_context(context);
2803 }
2804
2805 for listener in key_down_listeners {
2806 window.on_key_event(move |event: &KeyDownEvent, phase, window, cx| {
2807 listener(event, phase, window, cx);
2808 })
2809 }
2810
2811 for listener in key_up_listeners {
2812 window.on_key_event(move |event: &KeyUpEvent, phase, window, cx| {
2813 listener(event, phase, window, cx);
2814 })
2815 }
2816
2817 for listener in modifiers_changed_listeners {
2818 window.on_modifiers_changed(move |event: &ModifiersChangedEvent, window, cx| {
2819 listener(event, window, cx);
2820 })
2821 }
2822
2823 for (action_type, listener) in action_listeners {
2824 window.on_action(action_type, listener)
2825 }
2826 }
2827
2828 fn paint_hover_group_handler(&self, window: &mut Window, cx: &mut App) {
2829 let group_hitbox = self
2830 .group_hover_style
2831 .as_ref()
2832 .and_then(|group_hover| GroupHitboxes::get(&group_hover.group, cx));
2833
2834 if let Some(group_hitbox) = group_hitbox {
2835 let was_hovered = group_hitbox.is_hovered(window);
2836 let current_view = window.current_view();
2837 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2838 let hovered = group_hitbox.is_hovered(window);
2839 if phase == DispatchPhase::Capture && hovered != was_hovered {
2840 cx.notify(current_view);
2841 }
2842 });
2843 }
2844 }
2845
2846 fn paint_scroll_listener(
2847 &self,
2848 hitbox: &Hitbox,
2849 style: &Style,
2850 window: &mut Window,
2851 _cx: &mut App,
2852 ) {
2853 if let Some(scroll_offset) = self.scroll_offset.clone() {
2854 let ongoing_scroll = self.ongoing_scroll.clone();
2855 let overflow = style.overflow;
2856 let allow_concurrent_scroll = style.allow_concurrent_scroll;
2857 let restrict_scroll_to_axis = style.restrict_scroll_to_axis;
2858 let line_height = window.line_height();
2859 let hitbox = hitbox.clone();
2860 let current_view = window.current_view();
2861 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
2862 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
2863 let mut scroll_offset = scroll_offset.borrow_mut();
2864 let old_scroll_offset = *scroll_offset;
2865 let mut delta = event.delta.pixel_delta(line_height);
2866
2867 if restrict_scroll_to_axis
2868 && event.delta.precise()
2869 && let Some(ongoing_scroll) = &ongoing_scroll
2870 {
2871 ongoing_scroll
2872 .borrow_mut()
2873 .filter(&mut delta, event.touch_phase);
2874 }
2875
2876 let mut delta_x = match overflow.x {
2877 Overflow::Scroll if !delta.x.is_zero() => delta.x,
2878 Overflow::Scroll
2879 if !restrict_scroll_to_axis && overflow.y != Overflow::Scroll =>
2880 {
2881 delta.y
2882 }
2883 _ => Pixels::ZERO,
2884 };
2885 let mut delta_y = match overflow.y {
2886 Overflow::Scroll if !delta.y.is_zero() => delta.y,
2887 Overflow::Scroll
2888 if !restrict_scroll_to_axis && overflow.x != Overflow::Scroll =>
2889 {
2890 delta.x
2891 }
2892 _ => Pixels::ZERO,
2893 };
2894 if !allow_concurrent_scroll && !delta_x.is_zero() && !delta_y.is_zero() {
2895 if delta_x.abs() > delta_y.abs() {
2896 delta_y = Pixels::ZERO;
2897 } else {
2898 delta_x = Pixels::ZERO;
2899 }
2900 }
2901 scroll_offset.y += delta_y;
2902 scroll_offset.x += delta_x;
2903 if *scroll_offset != old_scroll_offset {
2904 cx.notify(current_view);
2905 }
2906 }
2907 });
2908 }
2909 }
2910
2911 pub fn compute_style(
2913 &self,
2914 global_id: Option<&GlobalElementId>,
2915 hitbox: Option<&Hitbox>,
2916 window: &mut Window,
2917 cx: &mut App,
2918 ) -> Style {
2919 window.with_optional_element_state(global_id, |element_state, window| {
2920 let mut element_state =
2921 element_state.map(|element_state| element_state.unwrap_or_default());
2922 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
2923 (style, element_state)
2924 })
2925 }
2926
2927 fn compute_style_internal(
2929 &self,
2930 hitbox: Option<&Hitbox>,
2931 element_state: Option<&mut InteractiveElementState>,
2932 window: &mut Window,
2933 cx: &mut App,
2934 ) -> Style {
2935 let mut style = Style::default();
2936 style.refine(&self.base_style);
2937
2938 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
2939 if let Some(in_focus_style) = self.in_focus_style.as_ref()
2940 && focus_handle.within_focused(window, cx)
2941 {
2942 style.refine(in_focus_style);
2943 }
2944
2945 if let Some(focus_style) = self.focus_style.as_ref()
2946 && focus_handle.is_focused(window)
2947 {
2948 style.refine(focus_style);
2949 }
2950 }
2951
2952 if let Some(hitbox) = hitbox {
2953 if !cx.has_active_drag() {
2954 if let Some(group_hover) = self.group_hover_style.as_ref()
2955 && let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx)
2956 && group_hitbox_id.is_hovered(window)
2957 {
2958 style.refine(&group_hover.style);
2959 }
2960
2961 if let Some(hover_style) = self.hover_style.as_ref()
2962 && hitbox.is_hovered(window)
2963 {
2964 style.refine(hover_style);
2965 }
2966 }
2967
2968 if let Some(drag) = cx.active_drag.take() {
2969 let mut can_drop = true;
2970 if let Some(can_drop_predicate) = &self.can_drop_predicate {
2971 can_drop = can_drop_predicate(drag.value.as_ref(), window, cx);
2972 }
2973
2974 if can_drop {
2975 for (state_type, group_drag_style) in &self.group_drag_over_styles {
2976 if let Some(group_hitbox_id) =
2977 GroupHitboxes::get(&group_drag_style.group, cx)
2978 && *state_type == drag.value.as_ref().type_id()
2979 && group_hitbox_id.is_hovered(window)
2980 {
2981 style.refine(&group_drag_style.style);
2982 }
2983 }
2984
2985 for (state_type, build_drag_over_style) in &self.drag_over_styles {
2986 if *state_type == drag.value.as_ref().type_id() && hitbox.is_hovered(window)
2987 {
2988 style.refine(&build_drag_over_style(drag.value.as_ref(), window, cx));
2989 }
2990 }
2991 }
2992
2993 style.mouse_cursor = drag.cursor_style;
2994 cx.active_drag = Some(drag);
2995 }
2996 }
2997
2998 if let Some(element_state) = element_state {
2999 let clicked_state = element_state
3000 .clicked_state
3001 .get_or_insert_with(Default::default)
3002 .borrow();
3003 if clicked_state.group
3004 && let Some(group) = self.group_active_style.as_ref()
3005 {
3006 style.refine(&group.style)
3007 }
3008
3009 if let Some(active_style) = self.active_style.as_ref()
3010 && clicked_state.element
3011 {
3012 style.refine(active_style)
3013 }
3014 }
3015
3016 style
3017 }
3018
3019 pub(crate) fn write_a11y_info(&self, node: &mut accesskit::Node) {
3020 if let Some(id) = &self.accessibility_id {
3021 node.set_author_id(id.to_string());
3022 }
3023 if let Some(label) = &self.aria_label {
3024 node.set_label(label.to_string());
3025 }
3026 if let Some(description) = &self.aria_description {
3027 node.set_description(description.to_string());
3028 }
3029 if let Some(value) = &self.aria_value {
3030 node.set_value(value.to_string());
3031 }
3032 if let Some(placeholder) = &self.aria_placeholder {
3033 node.set_placeholder(placeholder.to_string());
3034 }
3035 if self.aria_disabled {
3036 node.set_disabled();
3037 }
3038 if self.aria_required {
3039 node.set_required();
3040 }
3041 if let Some(invalid) = self.aria_invalid {
3042 node.set_invalid(invalid);
3043 }
3044 if self.aria_modal {
3045 node.set_modal();
3046 }
3047 if let Some(keyshortcuts) = &self.aria_keyshortcuts {
3048 node.set_keyboard_shortcut(keyshortcuts.to_string());
3049 }
3050 if let Some(selected) = self.aria_selected {
3051 node.set_selected(selected);
3052 }
3053 if let Some(expanded) = self.aria_expanded {
3054 node.set_expanded(expanded);
3055 }
3056 if let Some(toggled) = self.aria_toggled {
3057 node.set_toggled(toggled);
3058 }
3059 if let Some(value) = self.aria_numeric_value {
3060 node.set_numeric_value(value);
3061 }
3062 if let Some(value) = self.aria_min_numeric_value {
3063 node.set_min_numeric_value(value);
3064 }
3065 if let Some(value) = self.aria_max_numeric_value {
3066 node.set_max_numeric_value(value);
3067 }
3068 if let Some(orientation) = self.aria_orientation {
3069 node.set_orientation(orientation);
3070 }
3071 if let Some(level) = self.aria_level {
3072 node.set_level(level);
3073 }
3074 if let Some(position) = self.aria_position_in_set {
3075 node.set_position_in_set(position);
3076 }
3077 if let Some(size) = self.aria_size_of_set {
3078 node.set_size_of_set(size);
3079 }
3080 if let Some(index) = self.aria_row_index {
3081 node.set_row_index(index);
3082 }
3083 if let Some(index) = self.aria_column_index {
3084 node.set_column_index(index);
3085 }
3086 if let Some(count) = self.aria_row_count {
3087 node.set_row_count(count);
3088 }
3089 if let Some(count) = self.aria_column_count {
3090 node.set_column_count(count);
3091 }
3092 if !self.click_listeners.is_empty() {
3093 node.add_action(accesskit::Action::Click);
3094 }
3095 if self.tracked_focus_handle.is_some() || self.focusable {
3096 node.add_action(accesskit::Action::Focus);
3097 }
3098 for (action, _) in &self.a11y_action_listeners {
3099 node.add_action(*action);
3100 }
3101 }
3102}
3103
3104#[derive(Default)]
3107pub struct InteractiveElementState {
3108 pub(crate) focus_handle: Option<FocusHandle>,
3109 pub(crate) clicked_state: Option<Rc<RefCell<ElementClickedState>>>,
3110 pub(crate) hover_state: Option<Rc<RefCell<bool>>>,
3111 pub(crate) pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
3112 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
3113 ongoing_scroll: Option<Rc<RefCell<OngoingScroll>>>,
3114 pub(crate) active_tooltip: Option<Rc<RefCell<Option<ActiveTooltip>>>>,
3115 pub(crate) prev_bounds: Option<Bounds<Pixels>>,
3116}
3117
3118#[derive(Copy, Clone, Default, Eq, PartialEq)]
3120pub struct ElementClickedState {
3121 pub group: bool,
3123
3124 pub element: bool,
3126}
3127
3128impl ElementClickedState {
3129 fn is_clicked(&self) -> bool {
3130 self.group || self.element
3131 }
3132}
3133
3134pub(crate) enum ActiveTooltip {
3135 WaitingForShow { _task: Task<()> },
3137 Visible {
3139 tooltip: AnyTooltip,
3140 is_hoverable: bool,
3141 },
3142 WaitingForHide {
3145 tooltip: AnyTooltip,
3146 _task: Task<()>,
3147 },
3148}
3149
3150pub(crate) fn clear_active_tooltip(
3151 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3152 window: &mut Window,
3153) {
3154 match active_tooltip.borrow_mut().take() {
3155 None => {}
3156 Some(ActiveTooltip::WaitingForShow { .. }) => {}
3157 Some(ActiveTooltip::Visible { .. }) => window.refresh(),
3158 Some(ActiveTooltip::WaitingForHide { .. }) => window.refresh(),
3159 }
3160}
3161
3162pub(crate) fn clear_active_tooltip_if_not_hoverable(
3163 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3164 window: &mut Window,
3165) {
3166 let should_clear = match active_tooltip.borrow().as_ref() {
3167 None => false,
3168 Some(ActiveTooltip::WaitingForShow { .. }) => false,
3169 Some(ActiveTooltip::Visible { is_hoverable, .. }) => !is_hoverable,
3170 Some(ActiveTooltip::WaitingForHide { .. }) => false,
3171 };
3172 if should_clear {
3173 active_tooltip.borrow_mut().take();
3174 window.refresh();
3175 }
3176}
3177
3178pub(crate) fn set_tooltip_on_window(
3179 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3180 window: &mut Window,
3181) -> Option<TooltipId> {
3182 let tooltip = match active_tooltip.borrow().as_ref() {
3183 None => return None,
3184 Some(ActiveTooltip::WaitingForShow { .. }) => return None,
3185 Some(ActiveTooltip::Visible { tooltip, .. }) => tooltip.clone(),
3186 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => tooltip.clone(),
3187 };
3188 Some(window.set_tooltip(tooltip))
3189}
3190
3191pub(crate) fn register_tooltip_mouse_handlers(
3192 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3193 tooltip_id: Option<TooltipId>,
3194 build_tooltip: Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3195 check_is_hovered: Rc<dyn Fn(&Window) -> bool>,
3196 check_is_hovered_during_prepaint: Rc<dyn Fn(&Window) -> bool>,
3197 show_delay: Option<Duration>,
3198 window: &mut Window,
3199) {
3200 let current_view = window.current_view();
3201 let show_delay = show_delay.unwrap_or(DEFAULT_TOOLTIP_SHOW_DELAY);
3202
3203 window.on_mouse_event({
3204 let active_tooltip = active_tooltip.clone();
3205 let build_tooltip = build_tooltip.clone();
3206 let check_is_hovered = check_is_hovered.clone();
3207 move |_: &MouseMoveEvent, phase, window, cx| {
3208 handle_tooltip_mouse_move(
3209 &active_tooltip,
3210 &build_tooltip,
3211 &check_is_hovered,
3212 &check_is_hovered_during_prepaint,
3213 tooltip_id,
3214 current_view,
3215 phase,
3216 show_delay,
3217 window,
3218 cx,
3219 )
3220 }
3221 });
3222
3223 window.on_mouse_event({
3224 let active_tooltip = active_tooltip.clone();
3225 move |_: &MouseDownEvent, _phase, window: &mut Window, _cx| {
3226 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3227 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3228 }
3229 }
3230 });
3231
3232 window.on_mouse_event({
3233 let active_tooltip = active_tooltip.clone();
3234 move |_: &ScrollWheelEvent, _phase, window: &mut Window, _cx| {
3235 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3236 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3237 }
3238 }
3239 });
3240}
3241
3242fn handle_tooltip_mouse_move(
3254 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3255 build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3256 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3257 check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
3258 tooltip_id: Option<TooltipId>,
3259 current_view: EntityId,
3260 phase: DispatchPhase,
3261 show_delay: Duration,
3262 window: &mut Window,
3263 cx: &mut App,
3264) {
3265 enum Action {
3268 None,
3269 CancelShow,
3270 ScheduleShow,
3271 CheckVisible,
3272 }
3273
3274 let action = match active_tooltip.borrow().as_ref() {
3275 None => {
3276 let is_hovered = check_is_hovered(window);
3277 if is_hovered && phase.bubble() {
3278 Action::ScheduleShow
3279 } else {
3280 Action::None
3281 }
3282 }
3283 Some(ActiveTooltip::WaitingForShow { .. }) => {
3284 let is_hovered = check_is_hovered(window);
3285 if is_hovered {
3286 Action::None
3287 } else {
3288 Action::CancelShow
3289 }
3290 }
3291 Some(ActiveTooltip::Visible { is_hoverable, .. }) => {
3292 if phase.capture()
3293 && !check_is_hovered(window)
3294 && (!*is_hoverable
3295 || !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3296 {
3297 Action::CheckVisible
3298 } else {
3299 Action::None
3300 }
3301 }
3302 Some(ActiveTooltip::WaitingForHide { .. }) => {
3303 if phase.capture()
3304 && (check_is_hovered(window)
3305 || tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3306 {
3307 Action::CheckVisible
3308 } else {
3309 Action::None
3310 }
3311 }
3312 };
3313
3314 match action {
3315 Action::None => {}
3316 Action::CancelShow => {
3317 active_tooltip.borrow_mut().take();
3319 }
3320 Action::ScheduleShow => {
3321 let delayed_show_task = window.spawn(cx, {
3322 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3323 let build_tooltip = build_tooltip.clone();
3324 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3325 async move |cx| {
3326 cx.background_executor().timer(show_delay).await;
3327 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3328 return;
3329 };
3330 cx.update(|window, cx| {
3331 let new_tooltip =
3332 build_tooltip(window, cx).map(|(view, tooltip_is_hoverable)| {
3333 let weak_active_tooltip = Rc::downgrade(&active_tooltip);
3334 ActiveTooltip::Visible {
3335 tooltip: AnyTooltip {
3336 view,
3337 mouse_position: window.mouse_position(),
3338 check_visible_and_update: Rc::new(
3339 move |tooltip_bounds, window, cx| {
3340 let Some(active_tooltip) =
3341 weak_active_tooltip.upgrade()
3342 else {
3343 return false;
3344 };
3345 handle_tooltip_check_visible_and_update(
3346 &active_tooltip,
3347 tooltip_is_hoverable,
3348 &check_is_hovered_during_prepaint,
3349 tooltip_bounds,
3350 window,
3351 cx,
3352 )
3353 },
3354 ),
3355 },
3356 is_hoverable: tooltip_is_hoverable,
3357 }
3358 });
3359 *active_tooltip.borrow_mut() = new_tooltip;
3360 window.refresh();
3361 })
3362 .ok();
3363 }
3364 });
3365 active_tooltip
3366 .borrow_mut()
3367 .replace(ActiveTooltip::WaitingForShow {
3368 _task: delayed_show_task,
3369 });
3370 }
3371 Action::CheckVisible => cx.notify(current_view),
3372 }
3373}
3374
3375fn handle_tooltip_check_visible_and_update(
3379 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3380 tooltip_is_hoverable: bool,
3381 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3382 tooltip_bounds: Bounds<Pixels>,
3383 window: &mut Window,
3384 cx: &mut App,
3385) -> bool {
3386 enum Action {
3389 None,
3390 Hide,
3391 ScheduleHide(AnyTooltip),
3392 CancelHide(AnyTooltip),
3393 }
3394
3395 let is_hovered = check_is_hovered(window)
3396 || (tooltip_is_hoverable && tooltip_bounds.contains(&window.mouse_position()));
3397 let action = match active_tooltip.borrow().as_ref() {
3398 Some(ActiveTooltip::Visible { tooltip, .. }) => {
3399 if is_hovered {
3400 Action::None
3401 } else {
3402 if tooltip_is_hoverable {
3403 Action::ScheduleHide(tooltip.clone())
3404 } else {
3405 Action::Hide
3406 }
3407 }
3408 }
3409 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => {
3410 if is_hovered {
3411 Action::CancelHide(tooltip.clone())
3412 } else {
3413 Action::None
3414 }
3415 }
3416 None | Some(ActiveTooltip::WaitingForShow { .. }) => Action::None,
3417 };
3418
3419 match action {
3420 Action::None => {}
3421 Action::Hide => clear_active_tooltip(active_tooltip, window),
3422 Action::ScheduleHide(tooltip) => {
3423 let delayed_hide_task = window.spawn(cx, {
3424 let active_tooltip = active_tooltip.clone();
3425 async move |cx| {
3426 cx.background_executor()
3427 .timer(HOVERABLE_TOOLTIP_HIDE_DELAY)
3428 .await;
3429 if active_tooltip.borrow_mut().take().is_some() {
3430 cx.update(|window, _cx| window.refresh()).ok();
3431 }
3432 }
3433 });
3434 active_tooltip
3435 .borrow_mut()
3436 .replace(ActiveTooltip::WaitingForHide {
3437 tooltip,
3438 _task: delayed_hide_task,
3439 });
3440 }
3441 Action::CancelHide(tooltip) => {
3442 active_tooltip.borrow_mut().replace(ActiveTooltip::Visible {
3444 tooltip,
3445 is_hoverable: true,
3446 });
3447 }
3448 }
3449
3450 active_tooltip.borrow().is_some()
3451}
3452
3453#[derive(Default)]
3454pub(crate) struct GroupHitboxes(HashMap<SharedString, SmallVec<[HitboxId; 1]>>);
3455
3456impl Global for GroupHitboxes {}
3457
3458impl GroupHitboxes {
3459 pub fn get(name: &SharedString, cx: &mut App) -> Option<HitboxId> {
3460 cx.default_global::<Self>()
3461 .0
3462 .get(name)
3463 .and_then(|bounds_stack| bounds_stack.last())
3464 .cloned()
3465 }
3466
3467 pub fn push(name: SharedString, hitbox_id: HitboxId, cx: &mut App) {
3468 cx.default_global::<Self>()
3469 .0
3470 .entry(name)
3471 .or_default()
3472 .push(hitbox_id);
3473 }
3474
3475 pub fn pop(name: &SharedString, cx: &mut App) {
3476 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
3477 }
3478}
3479
3480pub struct Stateful<E> {
3482 pub(crate) element: E,
3483}
3484
3485impl<E> Styled for Stateful<E>
3486where
3487 E: Styled,
3488{
3489 fn style(&mut self) -> &mut StyleRefinement {
3490 self.element.style()
3491 }
3492}
3493
3494impl<E> StatefulInteractiveElement for Stateful<E>
3495where
3496 E: Element,
3497 Self: InteractiveElement,
3498{
3499}
3500
3501impl<E> InteractiveElement for Stateful<E>
3502where
3503 E: InteractiveElement,
3504{
3505 fn interactivity(&mut self) -> &mut Interactivity {
3506 self.element.interactivity()
3507 }
3508}
3509
3510impl<E> Element for Stateful<E>
3511where
3512 E: Element,
3513{
3514 type RequestLayoutState = E::RequestLayoutState;
3515 type PrepaintState = E::PrepaintState;
3516
3517 fn id(&self) -> Option<ElementId> {
3518 self.element.id()
3519 }
3520
3521 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
3522 self.element.source_location()
3523 }
3524
3525 fn a11y_role(&self) -> Option<accesskit::Role> {
3526 self.element.a11y_role()
3527 }
3528
3529 fn write_a11y_info(&self, node: &mut accesskit::Node) {
3530 self.element.write_a11y_info(node);
3531 }
3532
3533 fn request_layout(
3534 &mut self,
3535 id: Option<&GlobalElementId>,
3536 inspector_id: Option<&InspectorElementId>,
3537 window: &mut Window,
3538 cx: &mut App,
3539 ) -> (LayoutId, Self::RequestLayoutState) {
3540 self.element.request_layout(id, inspector_id, window, cx)
3541 }
3542
3543 fn prepaint(
3544 &mut self,
3545 id: Option<&GlobalElementId>,
3546 inspector_id: Option<&InspectorElementId>,
3547 bounds: Bounds<Pixels>,
3548 state: &mut Self::RequestLayoutState,
3549 window: &mut Window,
3550 cx: &mut App,
3551 ) -> E::PrepaintState {
3552 self.element
3553 .prepaint(id, inspector_id, bounds, state, window, cx)
3554 }
3555
3556 fn paint(
3557 &mut self,
3558 id: Option<&GlobalElementId>,
3559 inspector_id: Option<&InspectorElementId>,
3560 bounds: Bounds<Pixels>,
3561 request_layout: &mut Self::RequestLayoutState,
3562 prepaint: &mut Self::PrepaintState,
3563 window: &mut Window,
3564 cx: &mut App,
3565 ) {
3566 self.element.paint(
3567 id,
3568 inspector_id,
3569 bounds,
3570 request_layout,
3571 prepaint,
3572 window,
3573 cx,
3574 );
3575 }
3576}
3577
3578impl<E> IntoElement for Stateful<E>
3579where
3580 E: Element,
3581{
3582 type Element = Self;
3583
3584 fn into_element(self) -> Self::Element {
3585 self
3586 }
3587}
3588
3589impl<E> ParentElement for Stateful<E>
3590where
3591 E: ParentElement,
3592{
3593 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
3594 self.element.extend(elements)
3595 }
3596}
3597
3598#[derive(Clone)]
3601pub struct ScrollAnchor {
3602 handle: ScrollHandle,
3603 last_origin: Rc<RefCell<Point<Pixels>>>,
3604}
3605
3606impl ScrollAnchor {
3607 pub fn for_handle(handle: ScrollHandle) -> Self {
3609 Self {
3610 handle,
3611 last_origin: Default::default(),
3612 }
3613 }
3614 pub fn scroll_to(&self, window: &mut Window, _cx: &mut App) {
3616 let this = self.clone();
3617
3618 window.on_next_frame(move |_, _| {
3619 let viewport_bounds = this.handle.bounds();
3620 let self_bounds = *this.last_origin.borrow();
3621 this.handle.set_offset(viewport_bounds.origin - self_bounds);
3622 });
3623 }
3624}
3625
3626#[derive(Default, Debug)]
3627struct ScrollHandleState {
3628 offset: Rc<RefCell<Point<Pixels>>>,
3629 ongoing_scroll: Rc<RefCell<OngoingScroll>>,
3630 bounds: Bounds<Pixels>,
3631 max_offset: Size<Pixels>,
3632 child_bounds: Vec<Bounds<Pixels>>,
3633 scroll_to_bottom: bool,
3634 overflow: Point<Overflow>,
3635 active_item: Option<ScrollActiveItem>,
3636}
3637
3638#[derive(Default, Debug, Clone, Copy)]
3639struct ScrollActiveItem {
3640 index: usize,
3641 strategy: ScrollStrategy,
3642}
3643
3644#[derive(Default, Debug, Clone, Copy)]
3645enum ScrollStrategy {
3646 #[default]
3647 FirstVisible,
3648 Top,
3649}
3650
3651#[derive(Clone, Debug)]
3655pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
3656
3657impl Default for ScrollHandle {
3658 fn default() -> Self {
3659 Self::new()
3660 }
3661}
3662
3663impl ScrollHandle {
3664 pub fn new() -> Self {
3666 Self(Rc::default())
3667 }
3668
3669 pub fn offset(&self) -> Point<Pixels> {
3671 *self.0.borrow().offset.borrow()
3672 }
3673
3674 pub fn max_offset(&self) -> Size<Pixels> {
3676 self.0.borrow().max_offset
3677 }
3678
3679 pub fn top_item(&self) -> usize {
3681 let state = self.0.borrow();
3682 let top = state.bounds.top() - state.offset.borrow().y;
3683
3684 match state.child_bounds.binary_search_by(|bounds| {
3685 if top < bounds.top() {
3686 Ordering::Greater
3687 } else if top > bounds.bottom() {
3688 Ordering::Less
3689 } else {
3690 Ordering::Equal
3691 }
3692 }) {
3693 Ok(ix) => ix,
3694 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
3695 }
3696 }
3697
3698 pub fn bottom_item(&self) -> usize {
3700 let state = self.0.borrow();
3701 let bottom = state.bounds.bottom() - state.offset.borrow().y;
3702
3703 match state.child_bounds.binary_search_by(|bounds| {
3704 if bottom < bounds.top() {
3705 Ordering::Greater
3706 } else if bottom > bounds.bottom() {
3707 Ordering::Less
3708 } else {
3709 Ordering::Equal
3710 }
3711 }) {
3712 Ok(ix) => ix,
3713 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
3714 }
3715 }
3716
3717 pub fn bounds(&self) -> Bounds<Pixels> {
3719 self.0.borrow().bounds
3720 }
3721
3722 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
3724 self.0.borrow().child_bounds.get(ix).cloned()
3725 }
3726
3727 pub fn scroll_to_item(&self, ix: usize) {
3729 let mut state = self.0.borrow_mut();
3730 state.active_item = Some(ScrollActiveItem {
3731 index: ix,
3732 strategy: ScrollStrategy::default(),
3733 });
3734 }
3735
3736 pub fn scroll_to_top_of_item(&self, ix: usize) {
3739 let mut state = self.0.borrow_mut();
3740 state.active_item = Some(ScrollActiveItem {
3741 index: ix,
3742 strategy: ScrollStrategy::Top,
3743 });
3744 }
3745
3746 fn scroll_to_active_item(&self) {
3750 let mut state = self.0.borrow_mut();
3751
3752 let Some(active_item) = state.active_item else {
3753 return;
3754 };
3755
3756 let active_item = match state.child_bounds.get(active_item.index) {
3757 Some(bounds) => {
3758 let mut scroll_offset = state.offset.borrow_mut();
3759
3760 match active_item.strategy {
3761 ScrollStrategy::FirstVisible => {
3762 if state.overflow.y == Overflow::Scroll {
3763 if bounds.top() + scroll_offset.y < state.bounds.top() {
3764 scroll_offset.y = state.bounds.top() - bounds.top();
3765 } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
3766 scroll_offset.y = state.bounds.bottom() - bounds.bottom();
3767 }
3768 }
3769 }
3770 ScrollStrategy::Top => {
3771 scroll_offset.y = state.bounds.top() - bounds.top();
3772 }
3773 }
3774
3775 if state.overflow.x == Overflow::Scroll {
3776 if bounds.left() + scroll_offset.x < state.bounds.left() {
3777 scroll_offset.x = state.bounds.left() - bounds.left();
3778 } else if bounds.right() + scroll_offset.x > state.bounds.right() {
3779 scroll_offset.x = state.bounds.right() - bounds.right();
3780 }
3781 }
3782 None
3783 }
3784 None => Some(active_item),
3785 };
3786 state.active_item = active_item;
3787 }
3788
3789 pub fn scroll_to_bottom(&self) {
3791 let mut state = self.0.borrow_mut();
3792 state.scroll_to_bottom = true;
3793 }
3794
3795 pub fn set_offset(&self, mut position: Point<Pixels>) {
3799 let state = self.0.borrow();
3800 *state.offset.borrow_mut() = position;
3801 }
3802
3803 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
3805 let ix = self.top_item();
3806 let state = self.0.borrow();
3807
3808 if let Some(child_bounds) = state.child_bounds.get(ix) {
3809 (
3810 ix,
3811 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
3812 )
3813 } else {
3814 (ix, px(0.))
3815 }
3816 }
3817
3818 pub fn logical_scroll_bottom(&self) -> (usize, Pixels) {
3820 let ix = self.bottom_item();
3821 let state = self.0.borrow();
3822
3823 if let Some(child_bounds) = state.child_bounds.get(ix) {
3824 (
3825 ix,
3826 child_bounds.bottom() + state.offset.borrow().y - state.bounds.bottom(),
3827 )
3828 } else {
3829 (ix, px(0.))
3830 }
3831 }
3832
3833 pub fn children_count(&self) -> usize {
3835 self.0.borrow().child_bounds.len()
3836 }
3837}
3838
3839#[cfg(test)]
3840mod tests {
3841 use super::*;
3842 use crate::{
3843 AnyWindowHandle, AppContext as _, Context, InputEvent as _, MouseButton, Render,
3844 ScrollDelta, ScrollWheelEvent, TestAppContext, fallback_prompt_renderer, hsla, point, size,
3845 };
3846 use std::{
3847 cell::{Cell, RefCell},
3848 rc::Rc,
3849 };
3850
3851 struct MouseDownOutOwner {
3852 mouse_down_out_count: Rc<Cell<usize>>,
3853 }
3854
3855 impl Render for MouseDownOutOwner {
3856 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
3857 let mouse_down_out_count = self.mouse_down_out_count.clone();
3858 div()
3859 .size_full()
3860 .child(
3861 div()
3862 .id("target")
3863 .size(px(50.))
3864 .on_mouse_down_out(move |_, _, _| {
3865 mouse_down_out_count.set(mouse_down_out_count.get() + 1);
3866 }),
3867 )
3868 }
3869 }
3870
3871 #[test]
3872 fn mouse_down_out_is_suppressed_while_window_prompt_is_active() {
3873 let mut test_app = TestAppContext::single();
3874 let mouse_down_out_count = Rc::new(Cell::new(0));
3875 let (_, cx) = test_app.add_window_view({
3876 let mouse_down_out_count = mouse_down_out_count.clone();
3877 move |_, _| MouseDownOutOwner {
3878 mouse_down_out_count,
3879 }
3880 });
3881
3882 cx.simulate_mouse_down(
3883 point(px(75.), px(75.)),
3884 MouseButton::Left,
3885 Default::default(),
3886 );
3887 assert_eq!(mouse_down_out_count.get(), 1);
3888
3889 cx.update(|window, cx| {
3890 cx.set_prompt_builder(fallback_prompt_renderer);
3891 let _receiver =
3892 window.prompt(crate::PromptLevel::Warning, "message", None, &["Ok"], cx);
3893 });
3894 cx.run_until_parked();
3895
3896 cx.simulate_mouse_down(
3897 point(px(75.), px(75.)),
3898 MouseButton::Left,
3899 Default::default(),
3900 );
3901 assert_eq!(
3902 mouse_down_out_count.get(),
3903 1,
3904 "mouse down over an active prompt must not reach underlying listeners"
3905 );
3906 }
3907
3908 struct HoverListenerLayoutTestView {
3909 target_left: Pixels,
3910 hover_transitions: Rc<RefCell<Vec<bool>>>,
3911 }
3912
3913 impl Render for HoverListenerLayoutTestView {
3914 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
3915 let hover_transitions = self.hover_transitions.clone();
3916 div().relative().size_full().child(
3917 div()
3918 .id("hover-target")
3919 .absolute()
3920 .left(self.target_left)
3921 .top_0()
3922 .size(px(20.))
3923 .on_click(|_, _, _| {})
3924 .on_hover(move |is_hovered, _, _| {
3925 hover_transitions.borrow_mut().push(*is_hovered);
3926 }),
3927 )
3928 }
3929 }
3930
3931 #[gpui::test]
3932 fn hover_listeners_update_when_layout_changes_under_stationary_mouse(cx: &mut TestAppContext) {
3933 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
3934 let window = cx.add_window({
3935 let hover_transitions = hover_transitions.clone();
3936 move |_, _| HoverListenerLayoutTestView {
3937 target_left: px(40.),
3938 hover_transitions,
3939 }
3940 });
3941 let any_window = AnyWindowHandle::from(window);
3942
3943 cx.update_window(any_window, |_, window, cx| {
3944 window.draw(cx).clear();
3945 window.dispatch_event(
3946 MouseMoveEvent {
3947 position: point(px(10.), px(10.)),
3948 modifiers: Default::default(),
3949 pressed_button: None,
3950 }
3951 .to_platform_input(),
3952 cx,
3953 );
3954 })
3955 .unwrap();
3956 assert!(hover_transitions.borrow().is_empty());
3957
3958 window
3959 .update(cx, |view, _, cx| {
3960 view.target_left = px(0.);
3961 cx.notify();
3962 })
3963 .unwrap();
3964 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
3965 .unwrap();
3966 assert_eq!(*hover_transitions.borrow(), [true]);
3967
3968 window
3969 .update(cx, |view, _, cx| {
3970 view.target_left = px(40.);
3971 cx.notify();
3972 })
3973 .unwrap();
3974 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear())
3975 .unwrap();
3976 assert_eq!(*hover_transitions.borrow(), [true, false]);
3977 }
3978
3979 #[gpui::test]
3980 fn hover_listeners_remain_hovered_during_stationary_mouse_press(cx: &mut TestAppContext) {
3981 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
3982 let window = cx.add_window({
3983 let hover_transitions = hover_transitions.clone();
3984 move |_, _| HoverListenerLayoutTestView {
3985 target_left: px(0.),
3986 hover_transitions,
3987 }
3988 });
3989 let any_window = AnyWindowHandle::from(window);
3990 let mouse_position = point(px(10.), px(10.));
3991
3992 cx.update_window(any_window, |_, window, cx| {
3993 window.draw(cx).clear();
3994 window.dispatch_event(
3995 MouseMoveEvent {
3996 position: mouse_position,
3997 modifiers: Default::default(),
3998 pressed_button: None,
3999 }
4000 .to_platform_input(),
4001 cx,
4002 );
4003 })
4004 .unwrap();
4005 assert_eq!(*hover_transitions.borrow(), [true]);
4006
4007 cx.update_window(any_window, |_, window, cx| {
4008 window.dispatch_event(
4009 MouseDownEvent {
4010 position: mouse_position,
4011 button: MouseButton::Left,
4012 modifiers: Default::default(),
4013 click_count: 1,
4014 first_mouse: false,
4015 }
4016 .to_platform_input(),
4017 cx,
4018 );
4019 window.draw(cx).clear();
4020 })
4021 .unwrap();
4022 assert_eq!(*hover_transitions.borrow(), [true]);
4023
4024 cx.update_window(any_window, |_, window, cx| {
4025 window.dispatch_event(
4026 MouseUpEvent {
4027 position: mouse_position,
4028 button: MouseButton::Left,
4029 modifiers: Default::default(),
4030 click_count: 1,
4031 }
4032 .to_platform_input(),
4033 cx,
4034 );
4035 window.draw(cx).clear();
4036 })
4037 .unwrap();
4038 assert_eq!(*hover_transitions.borrow(), [true]);
4039 }
4040
4041 #[test]
4042 fn test_tooltip_show_delay_sets_interactivity_state() {
4043 let delay = Duration::from_millis(125);
4044 let element = div().id("tooltip-target").tooltip_show_delay(delay);
4045
4046 assert_eq!(
4047 element.element.interactivity.tooltip_show_delay,
4048 Some(delay)
4049 );
4050 }
4051
4052 #[test]
4053 fn aria_description_and_keyshortcuts_are_written_to_accesskit() {
4054 let element = div()
4055 .id("save-button")
4056 .role(accesskit::Role::Button)
4057 .aria_description("Save the active document")
4058 .aria_keyshortcuts("Ctrl+S");
4059 let mut node = accesskit::Node::new(accesskit::Role::Button);
4060
4061 element.element.interactivity.write_a11y_info(&mut node);
4062
4063 assert_eq!(node.description(), Some("Save the active document"));
4064 assert_eq!(node.keyboard_shortcut(), Some("Ctrl+S"));
4065 }
4066
4067 #[test]
4068 fn accessibility_id_is_written_to_accesskit() {
4069 let element = div()
4070 .id("buffer-font-size")
4071 .accessibility_id("settings.buffer-font-size");
4072 let mut node = accesskit::Node::new(accesskit::Role::SpinButton);
4073
4074 element.element.interactivity.write_a11y_info(&mut node);
4075
4076 assert_eq!(node.author_id(), Some("settings.buffer-font-size"));
4077 }
4078
4079 #[test]
4080 fn form_control_properties_are_written_to_accesskit() {
4081 let element = div()
4082 .id("email")
4083 .role(accesskit::Role::TextInput)
4084 .aria_value("alice@example.com")
4085 .aria_placeholder("name@example.com")
4086 .aria_disabled(true)
4087 .aria_required(true)
4088 .aria_invalid(accesskit::Invalid::True)
4089 .aria_modal(true);
4090 let mut node = accesskit::Node::new(accesskit::Role::TextInput);
4091
4092 element.element.interactivity.write_a11y_info(&mut node);
4093
4094 assert_eq!(node.value(), Some("alice@example.com"));
4095 assert_eq!(node.placeholder(), Some("name@example.com"));
4096 assert!(node.is_disabled());
4097 assert!(node.is_required());
4098 assert_eq!(node.invalid(), Some(accesskit::Invalid::True));
4099 assert!(node.is_modal());
4100 }
4101
4102 #[test]
4103 fn test_shadow_sm_preset_preserves_shadow_fields() {
4104 let mut element = div().shadow_sm();
4105 let shadows = element
4106 .style()
4107 .box_shadow
4108 .as_ref()
4109 .expect("shadow_sm should set box shadows");
4110
4111 assert_eq!(shadows.len(), 2);
4112 assert_eq!(shadows[0].color, hsla(0., 0., 0., 0.1));
4113 assert_eq!(shadows[0].offset, point(px(0.), px(1.)));
4114 assert_eq!(shadows[0].blur_radius, px(3.));
4115 assert_eq!(shadows[0].spread_radius, px(0.));
4116 assert!(!shadows[0].inset);
4117
4118 assert_eq!(shadows[1].color, hsla(0., 0., 0., 0.1));
4119 assert_eq!(shadows[1].offset, point(px(0.), px(1.)));
4120 assert_eq!(shadows[1].blur_radius, px(2.));
4121 assert_eq!(shadows[1].spread_radius, px(-1.));
4122 assert!(!shadows[1].inset);
4123 }
4124
4125 struct ContentSizedGrid;
4126
4127 impl Render for ContentSizedGrid {
4128 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4129 let widths = [px(100.), px(200.), px(50.)];
4130 div().size_full().child(
4131 div()
4132 .w_full()
4133 .grid()
4134 .grid_cols_max_content(widths.len() as u16)
4135 .children(widths.into_iter().enumerate().map(|(index, width)| {
4136 div()
4137 .debug_selector(move || format!("cell-{index}"))
4138 .w(width)
4139 .h(px(10.))
4140 })),
4141 )
4142 }
4143 }
4144
4145 #[gpui::test]
4146 fn grid_cols_max_content_sizes_columns_to_their_content(cx: &mut TestAppContext) {
4147 let window = cx.add_window(|_, _| ContentSizedGrid);
4148 cx.update_window(window.into(), |_, window, cx| window.draw(cx).clear())
4149 .unwrap();
4150
4151 let mut bounds = |selector: &'static str| {
4152 cx.update_window(window.into(), |_, window, _| {
4153 window.rendered_frame.debug_bounds.get(selector).copied()
4154 })
4155 .unwrap()
4156 .unwrap_or_else(|| panic!("{selector} was not rendered"))
4157 };
4158
4159 assert_eq!(bounds("cell-0").origin.x, px(0.));
4160 assert_eq!(bounds("cell-0").size.width, px(100.));
4161 assert_eq!(bounds("cell-1").origin.x, px(100.));
4162 assert_eq!(bounds("cell-1").size.width, px(200.));
4163 assert_eq!(bounds("cell-2").origin.x, px(300.));
4164 assert_eq!(bounds("cell-2").size.width, px(50.));
4165 }
4166
4167 #[test]
4168 fn restrict_scroll_to_axis_sets_style_flag() {
4169 let mut element = div().id("axis-lock").restrict_scroll_to_axis();
4170 assert_eq!(
4171 element
4172 .element
4173 .interactivity
4174 .base_style
4175 .restrict_scroll_to_axis,
4176 Some(true)
4177 );
4178 }
4179
4180 struct AxisLockedScrollView {
4181 handle: ScrollHandle,
4182 }
4183
4184 impl Render for AxisLockedScrollView {
4185 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4186 div()
4187 .id("axis-lock-scroller")
4188 .size_full()
4189 .overflow_scroll()
4190 .restrict_scroll_to_axis()
4191 .track_scroll(&self.handle)
4192 .child(div().size(px(2000.)))
4193 }
4194 }
4195
4196 #[gpui::test]
4197 fn restrict_scroll_to_axis_locks_precise_gestures_to_start_axis(cx: &mut TestAppContext) {
4198 let handle = ScrollHandle::new();
4199 let (_, cx) = cx.add_window_view({
4200 let handle = handle.clone();
4201 move |_, _| AxisLockedScrollView { handle }
4202 });
4203 cx.simulate_resize(size(px(200.), px(200.)));
4204
4205 cx.update(|window, cx| {
4206 window.dispatch_event(
4207 ScrollWheelEvent {
4208 position: point(px(50.), px(50.)),
4209 delta: ScrollDelta::Pixels(point(px(-20.), px(-4.))),
4210 touch_phase: crate::TouchPhase::Started,
4211 ..Default::default()
4212 }
4213 .to_platform_input(),
4214 cx,
4215 );
4216 });
4217 cx.run_until_parked();
4218 let after_start = handle.offset();
4219 assert!(after_start.x < px(0.), "horizontal start should scroll x");
4220 assert_eq!(
4221 after_start.y,
4222 px(0.),
4223 "sticky start axis should drop the weaker vertical component"
4224 );
4225
4226 cx.update(|window, cx| {
4227 window.dispatch_event(
4228 ScrollWheelEvent {
4229 position: point(px(50.), px(50.)),
4230 delta: ScrollDelta::Pixels(point(px(-3.), px(-2.))),
4231 touch_phase: crate::TouchPhase::Moved,
4232 ..Default::default()
4233 }
4234 .to_platform_input(),
4235 cx,
4236 );
4237 });
4238 cx.run_until_parked();
4239 let after_continue = handle.offset();
4240 assert!(after_continue.x < after_start.x);
4241 assert_eq!(after_continue.y, px(0.));
4242
4243 cx.update(|window, cx| {
4244 window.dispatch_event(
4245 ScrollWheelEvent {
4246 position: point(px(50.), px(50.)),
4247 delta: ScrollDelta::Pixels(point(px(-2.), px(-20.))),
4248 touch_phase: crate::TouchPhase::Moved,
4249 ..Default::default()
4250 }
4251 .to_platform_input(),
4252 cx,
4253 );
4254 });
4255 cx.run_until_parked();
4256 let after_unlock = handle.offset();
4257 assert!(
4258 after_unlock.y < px(0.),
4259 "a strong perpendicular delta should break the axis lock"
4260 );
4261 }
4262
4263 #[gpui::test]
4264 fn restrict_scroll_to_axis_style_flag_still_blocks_axis_remap(cx: &mut TestAppContext) {
4265 let handle = ScrollHandle::new();
4266 struct VerticalOnly {
4267 handle: ScrollHandle,
4268 }
4269 impl Render for VerticalOnly {
4270 fn render(
4271 &mut self,
4272 _window: &mut Window,
4273 _cx: &mut Context<Self>,
4274 ) -> impl IntoElement {
4275 let mut scroller = div()
4276 .id("vertical-only")
4277 .size_full()
4278 .overflow_y_scroll()
4279 .track_scroll(&self.handle)
4280 .child(div().h(px(2000.)).w_full());
4281 scroller.style().restrict_scroll_to_axis = Some(true);
4282 scroller
4283 }
4284 }
4285
4286 let (_, cx) = cx.add_window_view({
4287 let handle = handle.clone();
4288 move |_, _| VerticalOnly { handle }
4289 });
4290 cx.simulate_resize(size(px(200.), px(200.)));
4291
4292 cx.update(|window, cx| {
4293 window.dispatch_event(
4294 ScrollWheelEvent {
4295 position: point(px(50.), px(50.)),
4296 delta: ScrollDelta::Pixels(point(px(-20.), px(0.))),
4297 ..Default::default()
4298 }
4299 .to_platform_input(),
4300 cx,
4301 );
4302 });
4303 cx.run_until_parked();
4304 assert_eq!(
4305 handle.offset(),
4306 point(px(0.), px(0.)),
4307 "existing restrict_scroll_to_axis callers must not have horizontal input remapped onto a vertical-only scroller"
4308 );
4309 }
4310}