1use crate::{
19 Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, BackGestureEvent, Bounds, ClickEvent,
20 DispatchPhase, Display, Element, ElementId, Entity, EntityId, ExternalDragPayload,
21 ExternalDragPayloadSource, FileDropEvent, FocusHandle, Global, GlobalElementId, Hitbox,
22 HitboxBehavior, HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent,
23 KeyUpEvent, KeyboardButton, KeyboardClickEvent, LayoutId, LongPressEvent,
24 ModifiersChangedEvent, MouseButton, MouseClickEvent, MouseDownEvent, MouseExitEvent,
25 MouseMoveEvent, MousePressureEvent, MouseUpEvent, OngoingScroll, Overflow, ParentElement,
26 PinchEvent, Pixels, Point, Render, ScrollEvent, SharedString, Size, Style, StyleRefinement,
27 Styled, Task, TooltipId, TouchPhase, Visibility, Window, WindowControlArea, point, px, size,
28};
29use collections::HashMap;
30use gpui_util::ResultExt;
31use refineable::Refineable;
32use smallvec::SmallVec;
33use std::{
34 any::{Any, TypeId},
35 cell::{Cell, RefCell},
36 cmp::Ordering,
37 fmt::Debug,
38 marker::PhantomData,
39 mem,
40 rc::Rc,
41 sync::Arc,
42 time::Duration,
43};
44
45use super::ImageCacheProvider;
46
47#[cfg(feature = "stacker")]
48type StackSafe<T> = stacksafe::StackSafe<T>;
49#[cfg(not(feature = "stacker"))]
50type StackSafe<T> = T;
51
52const DRAG_THRESHOLD: f64 = 2.;
53const DEFAULT_TOOLTIP_SHOW_DELAY: Duration = Duration::from_millis(500);
54const HOVERABLE_TOOLTIP_HIDE_DELAY: Duration = Duration::from_millis(500);
55
56pub struct GroupStyle {
58 pub group: SharedString,
60
61 pub style: Box<StyleRefinement>,
64}
65
66pub struct DragMoveEvent<T> {
68 pub event: MouseMoveEvent,
70
71 pub bounds: Bounds<Pixels>,
73 drag: PhantomData<T>,
74 dragged_item: Arc<dyn Any>,
75}
76
77impl<T: 'static> DragMoveEvent<T> {
78 pub fn drag<'b>(&self, cx: &'b App) -> &'b T {
80 cx.active_drag
81 .as_ref()
82 .and_then(|drag| drag.value.downcast_ref::<T>())
83 .expect("DragMoveEvent is only valid when the stored active drag is of the same type.")
84 }
85
86 pub fn dragged_item(&self) -> &dyn Any {
88 self.dragged_item.as_ref()
89 }
90}
91
92impl Interactivity {
93 #[cfg(any(feature = "inspector", debug_assertions))]
95 #[track_caller]
96 pub fn new() -> Interactivity {
97 Interactivity {
98 source_location: Some(core::panic::Location::caller()),
99 ..Default::default()
100 }
101 }
102
103 #[cfg(not(any(feature = "inspector", debug_assertions)))]
105 pub fn new() -> Interactivity {
106 Interactivity::default()
107 }
108
109 pub fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
111 #[cfg(any(feature = "inspector", debug_assertions))]
112 {
113 self.source_location
114 }
115
116 #[cfg(not(any(feature = "inspector", debug_assertions)))]
117 {
118 None
119 }
120 }
121
122 pub fn on_mouse_down(
127 &mut self,
128 button: MouseButton,
129 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
130 ) {
131 self.mouse_down_listeners
132 .push(Box::new(move |event, phase, hitbox, window, cx| {
133 if phase == DispatchPhase::Bubble
134 && event.button == button
135 && hitbox.is_hovered(window)
136 {
137 (listener)(event, window, cx)
138 }
139 }));
140 }
141
142 pub fn capture_any_mouse_down(
147 &mut self,
148 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
149 ) {
150 self.mouse_down_listeners
151 .push(Box::new(move |event, phase, hitbox, window, cx| {
152 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
153 (listener)(event, window, cx)
154 }
155 }));
156 }
157
158 pub fn on_any_mouse_down(
163 &mut self,
164 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
165 ) {
166 self.mouse_down_listeners
167 .push(Box::new(move |event, phase, hitbox, window, cx| {
168 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
169 (listener)(event, window, cx)
170 }
171 }));
172 }
173
174 pub fn on_mouse_pressure(
179 &mut self,
180 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
181 ) {
182 self.mouse_pressure_listeners
183 .push(Box::new(move |event, phase, hitbox, window, cx| {
184 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
185 (listener)(event, window, cx)
186 }
187 }));
188 }
189
190 pub fn capture_mouse_pressure(
195 &mut self,
196 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
197 ) {
198 self.mouse_pressure_listeners
199 .push(Box::new(move |event, phase, hitbox, window, cx| {
200 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
201 (listener)(event, window, cx)
202 }
203 }));
204 }
205
206 pub fn on_mouse_up(
211 &mut self,
212 button: MouseButton,
213 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
214 ) {
215 self.mouse_up_listeners
216 .push(Box::new(move |event, phase, hitbox, window, cx| {
217 if phase == DispatchPhase::Bubble
218 && event.button == button
219 && hitbox.is_hovered(window)
220 {
221 (listener)(event, window, cx)
222 }
223 }));
224 }
225
226 pub fn capture_any_mouse_up(
231 &mut self,
232 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
233 ) {
234 self.mouse_up_listeners
235 .push(Box::new(move |event, phase, hitbox, window, cx| {
236 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
237 (listener)(event, window, cx)
238 }
239 }));
240 }
241
242 pub fn on_any_mouse_up(
247 &mut self,
248 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
249 ) {
250 self.mouse_up_listeners
251 .push(Box::new(move |event, phase, hitbox, window, cx| {
252 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
253 (listener)(event, window, cx)
254 }
255 }));
256 }
257
258 pub fn on_mouse_down_out(
264 &mut self,
265 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
266 ) {
267 self.mouse_down_listeners
268 .push(Box::new(move |event, phase, hitbox, window, cx| {
269 if phase == DispatchPhase::Capture
270 && !window.has_active_prompt()
271 && !hitbox.contains(&window.mouse_position())
272 {
273 (listener)(event, window, cx)
274 }
275 }));
276 }
277
278 pub fn on_mouse_up_out(
284 &mut self,
285 button: MouseButton,
286 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
287 ) {
288 self.mouse_up_listeners
289 .push(Box::new(move |event, phase, hitbox, window, cx| {
290 if phase == DispatchPhase::Capture
291 && event.button == button
292 && !hitbox.is_hovered(window)
293 {
294 (listener)(event, window, cx);
295 }
296 }));
297 }
298
299 pub fn on_mouse_move(
304 &mut self,
305 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
306 ) {
307 self.mouse_move_listeners
308 .push(Box::new(move |event, phase, hitbox, window, cx| {
309 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
310 (listener)(event, window, cx);
311 }
312 }));
313 }
314
315 pub fn on_mouse_exit(
320 &mut self,
321 listener: impl Fn(&MouseExitEvent, &mut Window, &mut App) + 'static,
322 ) {
323 self.mouse_exit_listeners
324 .push(Box::new(move |event, phase, hitbox, window, cx| {
325 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
326 (listener)(event, window, cx);
327 }
328 }));
329 }
330
331 pub fn on_file_drop_exit(
339 &mut self,
340 listener: impl Fn(&FileDropEvent, &mut Window, &mut App) + 'static,
341 ) {
342 self.file_drop_exit_listeners
343 .push(Box::new(move |event, phase, hitbox, window, cx| {
344 if phase == DispatchPhase::Bubble
345 && matches!(event, FileDropEvent::Exited)
346 && hitbox.id.is_hovered_ignoring_last_input(window)
347 {
348 (listener)(event, window, cx);
349 }
350 }));
351 }
352
353 pub fn on_drag_move<T>(
361 &mut self,
362 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
363 ) where
364 T: 'static,
365 {
366 self.mouse_move_listeners
367 .push(Box::new(move |event, phase, hitbox, window, cx| {
368 if phase == DispatchPhase::Capture
369 && let Some(drag) = &cx.active_drag
370 && drag.value.as_ref().type_id() == TypeId::of::<T>()
371 {
372 (listener)(
373 &DragMoveEvent {
374 event: event.clone(),
375 bounds: hitbox.bounds,
376 drag: PhantomData,
377 dragged_item: Arc::clone(&drag.value),
378 },
379 window,
380 cx,
381 );
382 }
383 }));
384 }
385
386 pub fn on_scroll(&mut self, listener: impl Fn(&ScrollEvent, &mut Window, &mut App) + 'static) {
391 self.scroll_listeners
392 .push(Box::new(move |event, phase, hitbox, window, cx| {
393 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
394 (listener)(event, window, cx);
395 }
396 }));
397 }
398
399 #[deprecated(note = "renamed to `on_scroll`")]
401 pub fn on_scroll_wheel(
402 &mut self,
403 listener: impl Fn(&ScrollEvent, &mut Window, &mut App) + 'static,
404 ) {
405 self.on_scroll(listener);
406 }
407
408 pub fn on_pinch(&mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) {
412 self.pinch_listeners
413 .push(Box::new(move |event, phase, hitbox, window, cx| {
414 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
415 (listener)(event, window, cx);
416 }
417 }));
418 }
419
420 pub fn on_back_gesture(
425 &mut self,
426 listener: impl Fn(&BackGestureEvent, &mut Window, &mut App) + 'static,
427 ) {
428 self.back_gesture_listeners
429 .push(Box::new(move |event, phase, hitbox, window, cx| {
430 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
431 (listener)(event, window, cx);
432 window.prevent_default();
433 cx.stop_propagation();
434 }
435 }));
436 }
437
438 pub fn capture_pinch(
442 &mut self,
443 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
444 ) {
445 self.pinch_listeners
446 .push(Box::new(move |event, phase, _hitbox, window, cx| {
447 if phase == DispatchPhase::Capture {
448 (listener)(event, window, cx);
449 } else {
450 cx.propagate();
451 }
452 }));
453 }
454
455 pub fn capture_action<A: Action>(
460 &mut self,
461 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
462 ) {
463 self.action_listeners.push((
464 TypeId::of::<A>(),
465 Box::new(move |action, phase, window, cx| {
466 let action = action.downcast_ref().unwrap();
467 if phase == DispatchPhase::Capture {
468 (listener)(action, window, cx)
469 } else {
470 cx.propagate();
471 }
472 }),
473 ));
474 }
475
476 #[track_caller]
481 #[inline(always)]
482 pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Window, &mut App) + 'static) {
483 self.action_listeners.push((
484 TypeId::of::<A>(),
485 Box::new(move |action, phase, window, cx| {
486 let action = action.downcast_ref().unwrap();
487 if phase == DispatchPhase::Bubble {
488 (listener)(action, window, cx)
489 }
490 }),
491 ));
492 }
493
494 pub fn on_boxed_action(
501 &mut self,
502 action: &dyn Action,
503 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
504 ) {
505 let action = action.boxed_clone();
506 self.action_listeners.push((
507 (*action).type_id(),
508 Box::new(move |_, phase, window, cx| {
509 if phase == DispatchPhase::Bubble {
510 (listener)(&*action, window, cx)
511 }
512 }),
513 ));
514 }
515
516 pub fn on_key_down(
521 &mut self,
522 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
523 ) {
524 self.key_down_listeners
525 .push(Box::new(move |event, phase, window, cx| {
526 if phase == DispatchPhase::Bubble {
527 (listener)(event, window, cx)
528 }
529 }));
530 }
531
532 pub fn capture_key_down(
537 &mut self,
538 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
539 ) {
540 self.key_down_listeners
541 .push(Box::new(move |event, phase, window, cx| {
542 if phase == DispatchPhase::Capture {
543 listener(event, window, cx)
544 }
545 }));
546 }
547
548 pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static) {
553 self.key_up_listeners
554 .push(Box::new(move |event, phase, window, cx| {
555 if phase == DispatchPhase::Bubble {
556 listener(event, window, cx)
557 }
558 }));
559 }
560
561 pub fn capture_key_up(
566 &mut self,
567 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
568 ) {
569 self.key_up_listeners
570 .push(Box::new(move |event, phase, window, cx| {
571 if phase == DispatchPhase::Capture {
572 listener(event, window, cx)
573 }
574 }));
575 }
576
577 pub fn on_modifiers_changed(
582 &mut self,
583 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
584 ) {
585 self.modifiers_changed_listeners.push(Box::new(listener));
586 }
587
588 pub fn on_drop<T: 'static>(&mut self, listener: impl Fn(&T, &mut Window, &mut App) + 'static) {
593 self.drop_listeners.push((
594 TypeId::of::<T>(),
595 Box::new(move |dragged_value, window, cx| {
596 listener(dragged_value.downcast_ref().unwrap(), window, cx);
597 }),
598 ));
599 }
600
601 pub fn can_drop(
604 &mut self,
605 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
606 ) {
607 self.can_drop_predicate = Some(Box::new(predicate));
608 }
609
610 #[inline(always)]
615 pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
616 where
617 Self: Sized,
618 {
619 self.click_listeners.push(Rc::new(listener));
620 }
621
622 pub fn on_aux_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
627 where
628 Self: Sized,
629 {
630 self.aux_click_listeners.push(Rc::new(listener));
631 }
632
633 pub fn on_drag<T, W>(
640 &mut self,
641 value: T,
642 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
643 ) where
644 Self: Sized,
645 T: 'static,
646 W: 'static + Render,
647 {
648 debug_assert!(
649 self.drag_listener.is_none(),
650 "calling on_drag more than once on the same element is not supported"
651 );
652 self.drag_listener = Some(DragListener {
653 value: Arc::new(value),
654 render: Box::new(move |value, offset, window, cx| {
655 constructor(value.downcast_ref().unwrap(), offset, window, cx).into()
656 }),
657 external_payload: None,
658 });
659 }
660
661 pub fn external_drag_payload<T>(
666 &mut self,
667 resolver: impl Fn(&T, &mut Window, &mut App) -> Option<ExternalDragPayload> + 'static,
668 ) where
669 Self: Sized,
670 T: 'static,
671 {
672 let Some(drag_listener) = self.drag_listener.as_mut() else {
673 debug_assert!(false, "external_drag_payload must be called after on_drag");
674 return;
675 };
676 debug_assert!(
677 drag_listener.value.as_ref().type_id() == TypeId::of::<T>(),
678 "external_drag_payload must use the same dragged value type as on_drag"
679 );
680 debug_assert!(
681 drag_listener.external_payload.is_none(),
682 "calling external_drag_payload more than once on the same element is not supported"
683 );
684 drag_listener.external_payload = Some(Box::new(move |value, window, cx| {
685 resolver(value.downcast_ref::<T>()?, window, cx)
686 }));
687 }
688
689 pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static)
700 where
701 Self: Sized,
702 {
703 debug_assert!(
704 self.hover_listener.is_none(),
705 "calling on_hover more than once on the same element is not supported"
706 );
707 self.hover_listener = Some(Box::new(listener));
708 }
709
710 pub fn hover_listener_mode(&mut self, mode: HoverListenerMode)
714 where
715 Self: Sized,
716 {
717 self.hover_listener_mode = mode;
718 }
719
720 pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static)
723 where
724 Self: Sized,
725 {
726 debug_assert!(
727 self.tooltip_builder.is_none(),
728 "calling tooltip more than once on the same element is not supported"
729 );
730 self.tooltip_builder = Some(TooltipBuilder {
731 build: Rc::new(build_tooltip),
732 hoverable: false,
733 });
734 }
735
736 pub fn hoverable_tooltip(
740 &mut self,
741 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
742 ) where
743 Self: Sized,
744 {
745 debug_assert!(
746 self.tooltip_builder.is_none(),
747 "calling tooltip more than once on the same element is not supported"
748 );
749 self.tooltip_builder = Some(TooltipBuilder {
750 build: Rc::new(build_tooltip),
751 hoverable: true,
752 });
753 }
754
755 pub fn tooltip_show_delay(&mut self, delay: Duration) {
760 self.tooltip_show_delay = Some(delay);
761 }
762
763 pub fn occlude_mouse(&mut self) {
768 self.hitbox_behavior = HitboxBehavior::BlockMouse;
769 }
770
771 pub fn window_control_area(&mut self, area: WindowControlArea) {
774 self.window_control = Some(area);
775 }
776
777 pub fn block_mouse_except_scroll(&mut self) {
782 self.hitbox_behavior = HitboxBehavior::BlockMouseExceptScroll;
783 }
784
785 fn has_pinch_listeners(&self) -> bool {
786 !self.pinch_listeners.is_empty()
787 }
788}
789
790pub trait InteractiveElement: Sized {
793 fn interactivity(&mut self) -> &mut Interactivity;
795
796 fn group(mut self, group: impl Into<SharedString>) -> Self {
798 self.interactivity().group = Some(group.into());
799 self
800 }
801
802 fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
804 self.interactivity().element_id = Some(id.into());
805
806 Stateful { element: self }
807 }
808
809 fn track_focus(mut self, focus_handle: &FocusHandle) -> Self {
813 self.interactivity().focusable = true;
814 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
815 self
816 }
817
818 fn tab_stop(mut self, tab_stop: bool) -> Self {
825 self.interactivity().tab_stop = tab_stop;
826 self
827 }
828
829 fn tab_index(mut self, index: isize) -> Self {
834 self.interactivity().focusable = true;
835 self.interactivity().tab_index = Some(index);
836 self.interactivity().tab_stop = true;
837 self
838 }
839
840 fn tab_group(mut self) -> Self {
845 self.interactivity().tab_group = true;
846 if self.interactivity().tab_index.is_none() {
847 self.interactivity().tab_index = Some(0);
848 }
849 self
850 }
851
852 fn key_context<C, E>(mut self, key_context: C) -> Self
855 where
856 C: TryInto<KeyContext, Error = E>,
857 E: std::fmt::Display,
858 {
859 if let Some(key_context) = key_context.try_into().log_err() {
860 self.interactivity().key_context = Some(key_context);
861 }
862 self
863 }
864
865 fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
867 debug_assert!(
868 self.interactivity().hover_style.is_none(),
869 "hover style already set"
870 );
871 self.interactivity().hover_style = Some(Box::new(f(StyleRefinement::default())));
872 self
873 }
874
875 fn group_hover(
877 mut self,
878 group_name: impl Into<SharedString>,
879 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
880 ) -> Self {
881 self.interactivity().group_hover_style = Some(GroupStyle {
882 group: group_name.into(),
883 style: Box::new(f(StyleRefinement::default())),
884 });
885 self
886 }
887
888 #[inline(always)]
893 fn on_mouse_down(
894 mut self,
895 button: MouseButton,
896 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
897 ) -> Self {
898 self.interactivity().on_mouse_down(button, listener);
899 self
900 }
901
902 #[cfg(any(test, all(debug_assertions, feature = "test-support")))]
909 fn debug_selector(mut self, f: impl FnOnce() -> String) -> Self {
910 self.interactivity().debug_selector = Some(f());
911 self
912 }
913
914 #[cfg(not(any(test, all(debug_assertions, feature = "test-support"))))]
921 #[inline]
922 fn debug_selector(self, _: impl FnOnce() -> String) -> Self {
923 self
924 }
925
926 fn capture_any_mouse_down(
931 mut self,
932 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
933 ) -> Self {
934 self.interactivity().capture_any_mouse_down(listener);
935 self
936 }
937
938 fn on_any_mouse_down(
943 mut self,
944 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
945 ) -> Self {
946 self.interactivity().on_any_mouse_down(listener);
947 self
948 }
949
950 fn on_mouse_up(
955 mut self,
956 button: MouseButton,
957 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
958 ) -> Self {
959 self.interactivity().on_mouse_up(button, listener);
960 self
961 }
962
963 fn capture_any_mouse_up(
968 mut self,
969 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
970 ) -> Self {
971 self.interactivity().capture_any_mouse_up(listener);
972 self
973 }
974
975 fn on_mouse_pressure(
980 mut self,
981 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
982 ) -> Self {
983 self.interactivity().on_mouse_pressure(listener);
984 self
985 }
986
987 fn capture_mouse_pressure(
992 mut self,
993 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
994 ) -> Self {
995 self.interactivity().capture_mouse_pressure(listener);
996 self
997 }
998
999 fn on_mouse_down_out(
1005 mut self,
1006 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
1007 ) -> Self {
1008 self.interactivity().on_mouse_down_out(listener);
1009 self
1010 }
1011
1012 fn on_mouse_up_out(
1018 mut self,
1019 button: MouseButton,
1020 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
1021 ) -> Self {
1022 self.interactivity().on_mouse_up_out(button, listener);
1023 self
1024 }
1025
1026 fn on_mouse_move(
1031 mut self,
1032 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
1033 ) -> Self {
1034 self.interactivity().on_mouse_move(listener);
1035 self
1036 }
1037
1038 fn on_mouse_exit(
1043 mut self,
1044 listener: impl Fn(&MouseExitEvent, &mut Window, &mut App) + 'static,
1045 ) -> Self {
1046 self.interactivity().on_mouse_exit(listener);
1047 self
1048 }
1049
1050 fn on_file_drop_exit(
1058 mut self,
1059 listener: impl Fn(&FileDropEvent, &mut Window, &mut App) + 'static,
1060 ) -> Self {
1061 self.interactivity().on_file_drop_exit(listener);
1062 self
1063 }
1064
1065 fn on_drag_move<T: 'static>(
1073 mut self,
1074 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
1075 ) -> Self {
1076 self.interactivity().on_drag_move(listener);
1077 self
1078 }
1079
1080 fn on_scroll(
1085 mut self,
1086 listener: impl Fn(&ScrollEvent, &mut Window, &mut App) + 'static,
1087 ) -> Self {
1088 self.interactivity().on_scroll(listener);
1089 self
1090 }
1091
1092 #[deprecated(note = "renamed to `on_scroll`")]
1094 fn on_scroll_wheel(
1095 self,
1096 listener: impl Fn(&ScrollEvent, &mut Window, &mut App) + 'static,
1097 ) -> Self {
1098 self.on_scroll(listener)
1099 }
1100
1101 fn on_pinch(mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) -> Self {
1106 self.interactivity().on_pinch(listener);
1107 self
1108 }
1109
1110 fn on_back_gesture(
1117 mut self,
1118 listener: impl Fn(&BackGestureEvent, &mut Window, &mut App) + 'static,
1119 ) -> Self {
1120 self.interactivity().on_back_gesture(listener);
1121 self
1122 }
1123
1124 fn capture_pinch(
1129 mut self,
1130 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
1131 ) -> Self {
1132 self.interactivity().capture_pinch(listener);
1133 self
1134 }
1135 fn capture_action<A: Action>(
1140 mut self,
1141 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
1142 ) -> Self {
1143 self.interactivity().capture_action(listener);
1144 self
1145 }
1146
1147 #[track_caller]
1152 #[inline(always)]
1153 fn on_action<A: Action>(
1154 mut self,
1155 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
1156 ) -> Self {
1157 self.interactivity().on_action(listener);
1158 self
1159 }
1160
1161 fn on_boxed_action(
1168 mut self,
1169 action: &dyn Action,
1170 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
1171 ) -> Self {
1172 self.interactivity().on_boxed_action(action, listener);
1173 self
1174 }
1175
1176 fn on_key_down(
1181 mut self,
1182 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1183 ) -> Self {
1184 self.interactivity().on_key_down(listener);
1185 self
1186 }
1187
1188 fn capture_key_down(
1193 mut self,
1194 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1195 ) -> Self {
1196 self.interactivity().capture_key_down(listener);
1197 self
1198 }
1199
1200 fn on_key_up(
1205 mut self,
1206 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1207 ) -> Self {
1208 self.interactivity().on_key_up(listener);
1209 self
1210 }
1211
1212 fn capture_key_up(
1217 mut self,
1218 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1219 ) -> Self {
1220 self.interactivity().capture_key_up(listener);
1221 self
1222 }
1223
1224 #[inline(always)]
1229 fn on_modifiers_changed(
1230 mut self,
1231 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
1232 ) -> Self {
1233 self.interactivity().on_modifiers_changed(listener);
1234 self
1235 }
1236
1237 fn drag_over<S: 'static>(
1239 mut self,
1240 f: impl 'static + Fn(StyleRefinement, &S, &mut Window, &mut App) -> StyleRefinement,
1241 ) -> Self {
1242 self.interactivity().drag_over_styles.push((
1243 TypeId::of::<S>(),
1244 Box::new(move |currently_dragged: &dyn Any, window, cx| {
1245 f(
1246 StyleRefinement::default(),
1247 currently_dragged.downcast_ref::<S>().unwrap(),
1248 window,
1249 cx,
1250 )
1251 }),
1252 ));
1253 self
1254 }
1255
1256 fn group_drag_over<S: 'static>(
1258 mut self,
1259 group_name: impl Into<SharedString>,
1260 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1261 ) -> Self {
1262 self.interactivity().group_drag_over_styles.push((
1263 TypeId::of::<S>(),
1264 GroupStyle {
1265 group: group_name.into(),
1266 style: Box::new(f(StyleRefinement::default())),
1267 },
1268 ));
1269 self
1270 }
1271
1272 fn on_drop<T: 'static>(
1277 mut self,
1278 listener: impl Fn(&T, &mut Window, &mut App) + 'static,
1279 ) -> Self {
1280 self.interactivity().on_drop(listener);
1281 self
1282 }
1283
1284 fn can_drop(
1287 mut self,
1288 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
1289 ) -> Self {
1290 self.interactivity().can_drop(predicate);
1291 self
1292 }
1293
1294 fn occlude(mut self) -> Self {
1298 self.interactivity().occlude_mouse();
1299 self
1300 }
1301
1302 fn window_control_area(mut self, area: WindowControlArea) -> Self {
1305 self.interactivity().window_control_area(area);
1306 self
1307 }
1308
1309 fn block_mouse_except_scroll(mut self) -> Self {
1314 self.interactivity().block_mouse_except_scroll();
1315 self
1316 }
1317
1318 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1321 where
1322 Self: Sized,
1323 {
1324 self.interactivity().focus_style = Some(Box::new(f(StyleRefinement::default())));
1325 self
1326 }
1327
1328 fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1331 where
1332 Self: Sized,
1333 {
1334 self.interactivity().in_focus_style = Some(Box::new(f(StyleRefinement::default())));
1335 self
1336 }
1337
1338 fn focus_visible(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1343 where
1344 Self: Sized,
1345 {
1346 self.interactivity().focus_visible_style = Some(Box::new(f(StyleRefinement::default())));
1347 self
1348 }
1349}
1350
1351pub trait StatefulInteractiveElement: InteractiveElement {
1354 fn role(mut self, role: accesskit::Role) -> Self {
1358 debug_assert!(
1359 role != accesskit::Role::GenericContainer,
1360 "GenericContainer is filtered out of the a11y tree and has no effect"
1361 );
1362 self.interactivity().override_role = Some(role);
1363 self
1364 }
1365
1366 fn accessibility_id(mut self, id: impl Into<SharedString>) -> Self {
1374 self.interactivity().aria.author_id = Some(id.into());
1375 self
1376 }
1377
1378 fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
1380 self.interactivity().aria.label = Some(label.into());
1381 self
1382 }
1383
1384 fn aria_description(mut self, description: impl Into<SharedString>) -> Self {
1389 self.interactivity().aria.description = Some(description.into());
1390 self
1391 }
1392
1393 fn aria_keyshortcuts(mut self, keyshortcuts: impl Into<SharedString>) -> Self {
1399 self.interactivity().aria.keyshortcuts = Some(keyshortcuts.into());
1400 self
1401 }
1402
1403 fn aria_active_descendant(mut self) -> Self {
1419 self.interactivity().report_active_descendant_focus = true;
1420 self
1421 }
1422
1423 fn a11y_synthetic_children(
1433 mut self,
1434 f: impl FnOnce(&mut crate::A11ySubtreeBuilder) + 'static,
1435 ) -> Self {
1436 self.interactivity().a11y_synthetic_children = Some(Box::new(f));
1437 self
1438 }
1439
1440 fn aria_selected(mut self, selected: bool) -> Self {
1442 self.interactivity().aria.selected = Some(selected);
1443 self
1444 }
1445
1446 fn aria_expanded(mut self, expanded: bool) -> Self {
1448 self.interactivity().aria.expanded = Some(expanded);
1449 self
1450 }
1451
1452 fn aria_toggled(mut self, toggled: accesskit::Toggled) -> Self {
1454 self.interactivity().aria.toggled = Some(toggled);
1455 self
1456 }
1457
1458 fn aria_numeric_value(mut self, value: f64) -> Self {
1460 self.interactivity().aria.numeric_value = Some(value);
1461 self
1462 }
1463
1464 fn aria_numeric_value_step(mut self, step: f64) -> Self {
1467 self.interactivity().aria.numeric_value_step = Some(step);
1468 self
1469 }
1470
1471 fn aria_value(mut self, value: impl Into<SharedString>) -> Self {
1474 self.interactivity().aria.value = Some(value.into());
1475 self
1476 }
1477
1478 fn aria_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
1481 self.interactivity().aria.placeholder = Some(placeholder.into());
1482 self
1483 }
1484
1485 fn aria_min_numeric_value(mut self, value: f64) -> Self {
1487 self.interactivity().aria.min_numeric_value = Some(value);
1488 self
1489 }
1490
1491 fn aria_max_numeric_value(mut self, value: f64) -> Self {
1493 self.interactivity().aria.max_numeric_value = Some(value);
1494 self
1495 }
1496
1497 fn aria_orientation(mut self, orientation: accesskit::Orientation) -> Self {
1499 self.interactivity().aria.orientation = Some(orientation);
1500 self
1501 }
1502
1503 fn aria_level(mut self, level: usize) -> Self {
1505 self.interactivity().aria.level = Some(level);
1506 self
1507 }
1508
1509 fn aria_position_in_set(mut self, position: usize) -> Self {
1511 self.interactivity().aria.position_in_set = Some(position);
1512 self
1513 }
1514
1515 fn aria_size_of_set(mut self, size: usize) -> Self {
1517 self.interactivity().aria.size_of_set = Some(size);
1518 self
1519 }
1520
1521 fn aria_row_index(mut self, index: usize) -> Self {
1523 self.interactivity().aria.row_index = Some(index);
1524 self
1525 }
1526
1527 fn aria_column_index(mut self, index: usize) -> Self {
1529 self.interactivity().aria.column_index = Some(index);
1530 self
1531 }
1532
1533 fn aria_row_count(mut self, count: usize) -> Self {
1535 self.interactivity().aria.row_count = Some(count);
1536 self
1537 }
1538
1539 fn aria_column_count(mut self, count: usize) -> Self {
1541 self.interactivity().aria.column_count = Some(count);
1542 self
1543 }
1544
1545 #[inline(always)]
1550 fn on_a11y_action(
1551 mut self,
1552 action: accesskit::Action,
1553 listener: impl FnMut(Option<&accesskit::ActionData>, &mut crate::Window, &mut crate::App)
1554 + 'static,
1555 ) -> Self {
1556 self.interactivity()
1557 .a11y_action_listeners
1558 .push((action, Box::new(listener)));
1559 self
1560 }
1561
1562 fn focusable(mut self) -> Self {
1564 self.interactivity().focusable = true;
1565 self
1566 }
1567
1568 fn overflow_scroll(mut self) -> Self {
1570 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1571 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1572 self
1573 }
1574
1575 fn overflow_x_scroll(mut self) -> Self {
1577 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1578 self
1579 }
1580
1581 fn overflow_y_scroll(mut self) -> Self {
1583 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1584 self
1585 }
1586
1587 fn restrict_scroll_to_axis(mut self) -> Self {
1591 self.interactivity().base_style.restrict_scroll_to_axis = Some(true);
1592 self
1593 }
1594
1595 fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
1597 self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone());
1598 self
1599 }
1600
1601 fn anchor_scroll(mut self, scroll_anchor: Option<ScrollAnchor>) -> Self {
1603 self.interactivity().scroll_anchor = scroll_anchor;
1604 self
1605 }
1606
1607 fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1609 where
1610 Self: Sized,
1611 {
1612 self.interactivity().active_style = Some(Box::new(f(StyleRefinement::default())));
1613 self
1614 }
1615
1616 fn group_active(
1618 mut self,
1619 group_name: impl Into<SharedString>,
1620 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1621 ) -> Self
1622 where
1623 Self: Sized,
1624 {
1625 self.interactivity().group_active_style = Some(GroupStyle {
1626 group: group_name.into(),
1627 style: Box::new(f(StyleRefinement::default())),
1628 });
1629 self
1630 }
1631
1632 #[inline(always)]
1637 fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self
1638 where
1639 Self: Sized,
1640 {
1641 self.interactivity().on_click(listener);
1642 self
1643 }
1644
1645 fn on_aux_click(
1650 mut self,
1651 listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
1652 ) -> Self
1653 where
1654 Self: Sized,
1655 {
1656 self.interactivity().on_aux_click(listener);
1657 self
1658 }
1659
1660 fn on_drag<T, W>(
1668 mut self,
1669 value: T,
1670 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
1671 ) -> Self
1672 where
1673 Self: Sized,
1674 T: 'static,
1675 W: 'static + Render,
1676 {
1677 self.interactivity().on_drag(value, constructor);
1678 self
1679 }
1680
1681 fn external_drag_payload<T>(
1687 mut self,
1688 resolver: impl Fn(&T, &mut Window, &mut App) -> Option<ExternalDragPayload> + 'static,
1689 ) -> Self
1690 where
1691 Self: Sized,
1692 T: 'static,
1693 {
1694 self.interactivity().external_drag_payload(resolver);
1695 self
1696 }
1697
1698 fn on_hover(mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self
1709 where
1710 Self: Sized,
1711 {
1712 self.interactivity().on_hover(listener);
1713 self
1714 }
1715
1716 fn hover_listener_mode(mut self, mode: HoverListenerMode) -> Self
1720 where
1721 Self: Sized,
1722 {
1723 self.interactivity().hover_listener_mode(mode);
1724 self
1725 }
1726
1727 fn tooltip(mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self
1730 where
1731 Self: Sized,
1732 {
1733 self.interactivity().tooltip(build_tooltip);
1734 self
1735 }
1736
1737 fn hoverable_tooltip(
1741 mut self,
1742 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
1743 ) -> Self
1744 where
1745 Self: Sized,
1746 {
1747 self.interactivity().hoverable_tooltip(build_tooltip);
1748 self
1749 }
1750
1751 fn tooltip_show_delay(mut self, delay: Duration) -> Self
1756 where
1757 Self: Sized,
1758 {
1759 self.interactivity().tooltip_show_delay(delay);
1760 self
1761 }
1762}
1763
1764pub(crate) type MouseDownListener =
1765 Box<dyn Fn(&MouseDownEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1766pub(crate) type MouseUpListener =
1767 Box<dyn Fn(&MouseUpEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1768pub(crate) type MousePressureListener =
1769 Box<dyn Fn(&MousePressureEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1770pub(crate) type MouseMoveListener =
1771 Box<dyn Fn(&MouseMoveEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1772pub(crate) type MouseExitListener =
1773 Box<dyn Fn(&MouseExitEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1774
1775pub(crate) type FileDropExitListener =
1776 Box<dyn Fn(&FileDropEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1777
1778pub(crate) type ScrollListener =
1779 Box<dyn Fn(&ScrollEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1780
1781pub(crate) type PinchListener =
1782 Box<dyn Fn(&PinchEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1783
1784pub(crate) type BackGestureListener =
1785 Box<dyn Fn(&BackGestureEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1786
1787pub(crate) type ClickListener = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>;
1788
1789#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1792pub enum HoverListenerMode {
1793 #[default]
1796 InputModalityAware,
1797 InputModalityIndependent,
1801}
1802
1803impl HoverListenerMode {
1804 fn is_hovered(self, hitbox: &Hitbox, window: &Window) -> bool {
1805 match self {
1806 Self::InputModalityAware => hitbox.is_hovered(window),
1807 Self::InputModalityIndependent => hitbox.id.is_hovered_ignoring_last_input(window),
1808 }
1809 }
1810}
1811
1812pub(crate) struct DragListener {
1813 value: Arc<dyn Any>,
1814 render: Box<dyn Fn(&dyn Any, Point<Pixels>, &mut Window, &mut App) -> AnyView + 'static>,
1815 external_payload: Option<ExternalDragPayloadResolver>,
1816}
1817
1818type ExternalDragPayloadResolver =
1819 Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> Option<ExternalDragPayload> + 'static>;
1820
1821type DropListener = Box<dyn Fn(&dyn Any, &mut Window, &mut App) + 'static>;
1822
1823type CanDropPredicate = Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static>;
1824
1825pub(crate) struct TooltipBuilder {
1826 build: Rc<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>,
1827 hoverable: bool,
1828}
1829
1830pub(crate) type KeyDownListener =
1831 Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1832
1833pub(crate) type KeyUpListener =
1834 Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1835
1836pub(crate) type ModifiersChangedListener =
1837 Box<dyn Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static>;
1838
1839pub(crate) type ActionListener =
1840 Box<dyn Fn(&dyn Any, DispatchPhase, &mut Window, &mut App) + 'static>;
1841
1842#[track_caller]
1844pub fn div() -> Div {
1845 Div {
1846 interactivity: Interactivity::new(),
1847 children: SmallVec::default(),
1848 prepaint_listener: None,
1849 image_cache: None,
1850 prepaint_order_fn: None,
1851 }
1852}
1853
1854pub struct Div {
1856 interactivity: Interactivity,
1857 children: SmallVec<[StackSafe<AnyElement>; 2]>,
1858 prepaint_listener: Option<Box<dyn Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static>>,
1859 image_cache: Option<Box<dyn ImageCacheProvider>>,
1860 prepaint_order_fn: Option<Box<dyn Fn(&mut Window, &mut App) -> SmallVec<[usize; 8]>>>,
1861}
1862
1863impl Div {
1864 pub fn on_children_prepainted(
1867 mut self,
1868 listener: impl Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static,
1869 ) -> Self {
1870 self.prepaint_listener = Some(Box::new(listener));
1871 self
1872 }
1873
1874 pub fn image_cache(mut self, cache: impl ImageCacheProvider) -> Self {
1876 self.image_cache = Some(Box::new(cache));
1877 self
1878 }
1879
1880 pub fn with_dynamic_prepaint_order(
1889 mut self,
1890 order_fn: impl Fn(&mut Window, &mut App) -> SmallVec<[usize; 8]> + 'static,
1891 ) -> Self {
1892 self.prepaint_order_fn = Some(Box::new(order_fn));
1893 self
1894 }
1895}
1896
1897pub struct DivFrameState {
1904 child_layout_ids: SmallVec<[LayoutId; 2]>,
1905}
1906
1907#[derive(Clone)]
1909pub struct DivInspectorState {
1910 #[cfg(any(feature = "inspector", debug_assertions))]
1914 pub base_style: Box<StyleRefinement>,
1915 pub bounds: Bounds<Pixels>,
1917 pub content_size: Size<Pixels>,
1919}
1920
1921impl Styled for Div {
1922 fn style(&mut self) -> &mut StyleRefinement {
1923 &mut self.interactivity.base_style
1924 }
1925}
1926
1927impl InteractiveElement for Div {
1928 fn interactivity(&mut self) -> &mut Interactivity {
1929 &mut self.interactivity
1930 }
1931}
1932
1933impl ParentElement for Div {
1934 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1935 #[cfg(feature = "stacker")]
1936 self.children
1937 .extend(elements.into_iter().map(StackSafe::new));
1938 #[cfg(not(feature = "stacker"))]
1939 self.children.extend(elements);
1940 }
1941}
1942
1943impl Element for Div {
1944 type RequestLayoutState = DivFrameState;
1945 type PrepaintState = Option<Hitbox>;
1946
1947 fn id(&self) -> Option<ElementId> {
1948 self.interactivity.element_id.clone()
1949 }
1950
1951 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
1952 self.interactivity.source_location()
1953 }
1954
1955 fn a11y_role(&self) -> Option<accesskit::Role> {
1956 self.interactivity
1959 .override_role
1960 .filter(|role| *role != accesskit::Role::GenericContainer)
1961 }
1962
1963 fn write_a11y_info(&self, node: &mut accesskit::Node) {
1964 self.interactivity.write_a11y_info(node);
1965 }
1966
1967 fn a11y_synthetic_children(
1968 &mut self,
1969 _prepaint: &mut Self::PrepaintState,
1970 builder: &mut crate::A11ySubtreeBuilder,
1971 ) {
1972 if let Some(f) = self.interactivity.a11y_synthetic_children.take() {
1973 f(builder);
1974 }
1975 }
1976
1977 #[cfg_attr(feature = "stacker", stacksafe::stacksafe)]
1978 fn request_layout(
1979 &mut self,
1980 global_id: Option<&GlobalElementId>,
1981 inspector_id: Option<&InspectorElementId>,
1982 window: &mut Window,
1983 cx: &mut App,
1984 ) -> (LayoutId, Self::RequestLayoutState) {
1985 let mut child_layout_ids = SmallVec::new();
1986 let image_cache = self
1987 .image_cache
1988 .as_mut()
1989 .map(|provider| provider.provide(window, cx));
1990
1991 let layout_id = window.with_image_cache(image_cache, |window| {
1992 self.interactivity.request_layout(
1993 global_id,
1994 inspector_id,
1995 window,
1996 cx,
1997 |style, window, cx| {
1998 window.with_text_style(style.text_style().cloned(), |window| {
1999 child_layout_ids = self
2000 .children
2001 .iter_mut()
2002 .map(|child| child.request_layout(window, cx))
2003 .collect::<SmallVec<_>>();
2004 window.request_layout(style, child_layout_ids.iter().copied(), cx)
2005 })
2006 },
2007 )
2008 });
2009
2010 (layout_id, DivFrameState { child_layout_ids })
2011 }
2012
2013 #[cfg_attr(feature = "stacker", stacksafe::stacksafe)]
2014 fn prepaint(
2015 &mut self,
2016 global_id: Option<&GlobalElementId>,
2017 inspector_id: Option<&InspectorElementId>,
2018 bounds: Bounds<Pixels>,
2019 request_layout: &mut Self::RequestLayoutState,
2020 window: &mut Window,
2021 cx: &mut App,
2022 ) -> Option<Hitbox> {
2023 let image_cache = self
2024 .image_cache
2025 .as_mut()
2026 .map(|provider| provider.provide(window, cx));
2027
2028 let has_prepaint_listener = self.prepaint_listener.is_some();
2029 let mut children_bounds = Vec::with_capacity(if has_prepaint_listener {
2030 request_layout.child_layout_ids.len()
2031 } else {
2032 0
2033 });
2034
2035 let mut child_min = point(Pixels::MAX, Pixels::MAX);
2036 let mut child_max = Point::default();
2037 if let Some(handle) = self.interactivity.scroll_anchor.as_ref() {
2038 *handle.last_origin.borrow_mut() = bounds.origin - window.element_offset();
2039 }
2040 let content_size = if request_layout.child_layout_ids.is_empty() {
2041 bounds.size
2042 } else if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
2043 let mut state = scroll_handle.0.borrow_mut();
2044 state.child_bounds = Vec::with_capacity(request_layout.child_layout_ids.len());
2045 for child_layout_id in &request_layout.child_layout_ids {
2046 let child_bounds = window.layout_bounds(*child_layout_id);
2047 child_min = child_min.min(&child_bounds.origin);
2048 child_max = child_max.max(&child_bounds.bottom_right());
2049 state.child_bounds.push(child_bounds);
2050 }
2051 (child_max - child_min).into()
2052 } else {
2053 for child_layout_id in &request_layout.child_layout_ids {
2054 let child_bounds = window.layout_bounds(*child_layout_id);
2055 child_min = child_min.min(&child_bounds.origin);
2056 child_max = child_max.max(&child_bounds.bottom_right());
2057
2058 if has_prepaint_listener {
2059 children_bounds.push(child_bounds);
2060 }
2061 }
2062 (child_max - child_min).into()
2063 };
2064
2065 if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
2066 scroll_handle.scroll_to_active_item();
2067 }
2068
2069 self.interactivity.prepaint(
2070 global_id,
2071 inspector_id,
2072 bounds,
2073 content_size,
2074 window,
2075 cx,
2076 |style, scroll_offset, hitbox, window, cx| {
2077 if style.display == Display::None {
2079 return hitbox;
2080 }
2081
2082 window.with_image_cache(image_cache, |window| {
2083 window.with_element_offset(scroll_offset, |window| {
2084 if let Some(order_fn) = &self.prepaint_order_fn {
2085 let order = order_fn(window, cx);
2086 for idx in order {
2087 if let Some(child) = self.children.get_mut(idx) {
2088 child.prepaint(window, cx);
2089 }
2090 }
2091 } else {
2092 for child in &mut self.children {
2093 child.prepaint(window, cx);
2094 }
2095 }
2096 });
2097
2098 if let Some(listener) = self.prepaint_listener.as_ref() {
2099 listener(children_bounds, window, cx);
2100 }
2101 });
2102
2103 hitbox
2104 },
2105 )
2106 }
2107
2108 #[cfg_attr(feature = "stacker", stacksafe::stacksafe)]
2109 fn paint(
2110 &mut self,
2111 global_id: Option<&GlobalElementId>,
2112 inspector_id: Option<&InspectorElementId>,
2113 bounds: Bounds<Pixels>,
2114 _request_layout: &mut Self::RequestLayoutState,
2115 hitbox: &mut Option<Hitbox>,
2116 window: &mut Window,
2117 cx: &mut App,
2118 ) {
2119 let image_cache = self
2120 .image_cache
2121 .as_mut()
2122 .map(|provider| provider.provide(window, cx));
2123
2124 window.with_image_cache(image_cache, |window| {
2125 self.interactivity.paint(
2126 global_id,
2127 inspector_id,
2128 bounds,
2129 hitbox.as_ref(),
2130 window,
2131 cx,
2132 |style, window, cx| {
2133 if style.display == Display::None {
2135 return;
2136 }
2137
2138 for child in &mut self.children {
2139 child.paint(window, cx);
2140 }
2141 },
2142 )
2143 });
2144 }
2145}
2146
2147impl IntoElement for Div {
2148 type Element = Self;
2149
2150 fn into_element(self) -> Self::Element {
2151 self
2152 }
2153}
2154
2155#[derive(Default)]
2156pub(crate) struct AriaProperties {
2157 pub(crate) author_id: Option<SharedString>,
2158 pub(crate) label: Option<SharedString>,
2159 pub(crate) description: Option<SharedString>,
2160 pub(crate) keyshortcuts: Option<SharedString>,
2161 pub(crate) selected: Option<bool>,
2162 pub(crate) expanded: Option<bool>,
2163 pub(crate) toggled: Option<accesskit::Toggled>,
2164 pub(crate) numeric_value: Option<f64>,
2165 pub(crate) min_numeric_value: Option<f64>,
2166 pub(crate) max_numeric_value: Option<f64>,
2167 pub(crate) numeric_value_step: Option<f64>,
2168 pub(crate) value: Option<SharedString>,
2169 pub(crate) placeholder: Option<SharedString>,
2170 pub(crate) orientation: Option<accesskit::Orientation>,
2171 pub(crate) level: Option<usize>,
2172 pub(crate) position_in_set: Option<usize>,
2173 pub(crate) size_of_set: Option<usize>,
2174 pub(crate) row_index: Option<usize>,
2175 pub(crate) column_index: Option<usize>,
2176 pub(crate) row_count: Option<usize>,
2177 pub(crate) column_count: Option<usize>,
2178}
2179
2180#[derive(Default)]
2183pub struct Interactivity {
2184 pub element_id: Option<ElementId>,
2186 pub active: Option<bool>,
2188 pub hovered: Option<bool>,
2191 pub(crate) tooltip_id: Option<TooltipId>,
2192 pub(crate) content_size: Size<Pixels>,
2193 pub(crate) key_context: Option<KeyContext>,
2194 pub(crate) focusable: bool,
2195 pub(crate) tracked_focus_handle: Option<FocusHandle>,
2196 pub(crate) tracked_scroll_handle: Option<ScrollHandle>,
2197 pub(crate) scroll_anchor: Option<ScrollAnchor>,
2198 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
2199 pub(crate) ongoing_scroll: Option<Rc<RefCell<OngoingScroll>>>,
2200 pub(crate) group: Option<SharedString>,
2201 pub base_style: Box<StyleRefinement>,
2204 pub(crate) focus_style: Option<Box<StyleRefinement>>,
2205 pub(crate) in_focus_style: Option<Box<StyleRefinement>>,
2206 pub(crate) focus_visible_style: Option<Box<StyleRefinement>>,
2207 pub(crate) hover_style: Option<Box<StyleRefinement>>,
2208 pub(crate) group_hover_style: Option<GroupStyle>,
2209 pub(crate) active_style: Option<Box<StyleRefinement>>,
2210 pub(crate) group_active_style: Option<GroupStyle>,
2211 pub(crate) drag_over_styles: Vec<(
2212 TypeId,
2213 Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> StyleRefinement>,
2214 )>,
2215 pub(crate) group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
2216 pub(crate) mouse_down_listeners: Vec<MouseDownListener>,
2217 pub(crate) mouse_up_listeners: Vec<MouseUpListener>,
2218 pub(crate) mouse_pressure_listeners: Vec<MousePressureListener>,
2219 pub(crate) mouse_move_listeners: Vec<MouseMoveListener>,
2220 pub(crate) mouse_exit_listeners: Vec<MouseExitListener>,
2221 pub(crate) file_drop_exit_listeners: Vec<FileDropExitListener>,
2222 pub(crate) scroll_listeners: Vec<ScrollListener>,
2223 pub(crate) pinch_listeners: Vec<PinchListener>,
2224 pub(crate) back_gesture_listeners: Vec<BackGestureListener>,
2225 pub(crate) key_down_listeners: Vec<KeyDownListener>,
2226 pub(crate) key_up_listeners: Vec<KeyUpListener>,
2227 pub(crate) modifiers_changed_listeners: Vec<ModifiersChangedListener>,
2228 pub(crate) action_listeners: Vec<(TypeId, ActionListener)>,
2229 pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
2230 pub(crate) can_drop_predicate: Option<CanDropPredicate>,
2231 pub(crate) click_listeners: Vec<ClickListener>,
2232 pub(crate) aux_click_listeners: Vec<ClickListener>,
2233 pub(crate) drag_listener: Option<DragListener>,
2234 pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut Window, &mut App)>>,
2235 pub(crate) hover_listener_mode: HoverListenerMode,
2236 pub(crate) tooltip_builder: Option<TooltipBuilder>,
2237 pub(crate) tooltip_show_delay: Option<Duration>,
2238 pub(crate) window_control: Option<WindowControlArea>,
2239 pub(crate) hitbox_behavior: HitboxBehavior,
2240 pub(crate) tab_index: Option<isize>,
2241 pub(crate) tab_group: bool,
2242 pub(crate) tab_stop: bool,
2243
2244 pub(crate) a11y_action_listeners:
2245 Vec<(accesskit::Action, crate::window::a11y::A11yActionListener)>,
2246 pub(crate) a11y_synthetic_children: Option<Box<dyn FnOnce(&mut crate::A11ySubtreeBuilder)>>,
2247 pub(crate) report_active_descendant_focus: bool,
2248 pub(crate) override_role: Option<accesskit::Role>,
2249 pub(crate) aria: AriaProperties,
2250
2251 #[cfg(any(feature = "inspector", debug_assertions))]
2252 pub(crate) source_location: Option<&'static core::panic::Location<'static>>,
2253
2254 #[cfg(any(test, all(debug_assertions, feature = "test-support")))]
2255 pub(crate) debug_selector: Option<String>,
2256}
2257
2258impl Interactivity {
2259 pub fn request_layout(
2261 &mut self,
2262 global_id: Option<&GlobalElementId>,
2263 _inspector_id: Option<&InspectorElementId>,
2264 window: &mut Window,
2265 cx: &mut App,
2266 f: impl FnOnce(Style, &mut Window, &mut App) -> LayoutId,
2267 ) -> LayoutId {
2268 #[cfg(any(feature = "inspector", debug_assertions))]
2269 window.with_inspector_state(
2270 _inspector_id,
2271 cx,
2272 |inspector_state: &mut Option<DivInspectorState>, _window| {
2273 if let Some(inspector_state) = inspector_state {
2274 self.base_style = inspector_state.base_style.clone();
2275 } else {
2276 *inspector_state = Some(DivInspectorState {
2277 base_style: self.base_style.clone(),
2278 bounds: Default::default(),
2279 content_size: Default::default(),
2280 })
2281 }
2282 },
2283 );
2284
2285 window.with_optional_element_state::<InteractiveElementState, _>(
2286 global_id,
2287 |element_state, window| {
2288 let mut element_state =
2289 element_state.map(|element_state| element_state.unwrap_or_default());
2290
2291 if let Some(element_state) = element_state.as_ref()
2292 && cx.has_active_drag()
2293 {
2294 if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() {
2295 *pending_mouse_down.borrow_mut() = None;
2296 }
2297 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
2298 *clicked_state.borrow_mut() = ElementClickedState::default();
2299 }
2300 }
2301
2302 if self.focusable
2307 && self.tracked_focus_handle.is_none()
2308 && let Some(element_state) = element_state.as_mut()
2309 {
2310 let mut handle = element_state
2311 .focus_handle
2312 .get_or_insert_with(|| cx.focus_handle())
2313 .clone()
2314 .tab_stop(self.tab_stop);
2315
2316 if let Some(index) = self.tab_index {
2317 handle = handle.tab_index(index);
2318 }
2319
2320 self.tracked_focus_handle = Some(handle);
2321 }
2322
2323 if let Some(scroll_handle) = self.tracked_scroll_handle.as_ref() {
2324 let scroll_handle_state = scroll_handle.0.borrow();
2325 self.scroll_offset = Some(scroll_handle_state.offset.clone());
2326 self.ongoing_scroll = Some(scroll_handle_state.ongoing_scroll.clone());
2327 } else if (self.base_style.overflow.x == Some(Overflow::Scroll)
2328 || self.base_style.overflow.y == Some(Overflow::Scroll))
2329 && let Some(element_state) = element_state.as_mut()
2330 {
2331 self.scroll_offset = Some(
2332 element_state
2333 .scroll_offset
2334 .get_or_insert_with(Rc::default)
2335 .clone(),
2336 );
2337 self.ongoing_scroll = Some(
2338 element_state
2339 .ongoing_scroll
2340 .get_or_insert_with(|| Rc::new(RefCell::new(OngoingScroll::default())))
2341 .clone(),
2342 );
2343 }
2344
2345 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
2346 let layout_id = f(style, window, cx);
2347 (layout_id, element_state)
2348 },
2349 )
2350 }
2351
2352 pub fn prepaint<R>(
2354 &mut self,
2355 global_id: Option<&GlobalElementId>,
2356 _inspector_id: Option<&InspectorElementId>,
2357 bounds: Bounds<Pixels>,
2358 content_size: Size<Pixels>,
2359 window: &mut Window,
2360 cx: &mut App,
2361 f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut Window, &mut App) -> R,
2362 ) -> R {
2363 self.content_size = content_size;
2364
2365 #[cfg(any(feature = "inspector", debug_assertions))]
2366 window.with_inspector_state(
2367 _inspector_id,
2368 cx,
2369 |inspector_state: &mut Option<DivInspectorState>, _window| {
2370 if let Some(inspector_state) = inspector_state {
2371 inspector_state.bounds = bounds;
2372 inspector_state.content_size = content_size;
2373 }
2374 },
2375 );
2376
2377 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
2378 window.set_focus_handle(focus_handle, cx);
2379
2380 if window.a11y.is_active() {
2381 if let Some(global_id) = global_id {
2382 let node_id = global_id.accesskit_node_id();
2383 window.a11y.set_focusable(node_id, focus_handle.id);
2384 if focus_handle.is_focused(window) {
2385 window.a11y.set_focus(node_id);
2386 }
2387 } else if focus_handle.is_focused(window) {
2388 window
2392 .a11y
2393 .note_focus_without_node(focus_handle.id, "it has no element id");
2394 }
2395 }
2396 }
2397
2398 if self.report_active_descendant_focus && window.a11y.is_active() {
2399 if let Some(global_id) = global_id {
2400 window
2401 .a11y
2402 .set_active_descendant(global_id.accesskit_node_id());
2403 }
2404 }
2405 window.with_optional_element_state::<InteractiveElementState, _>(
2406 global_id,
2407 |element_state, window| {
2408 let mut element_state =
2409 element_state.map(|element_state| element_state.unwrap_or_default());
2410 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
2411
2412 if let Some(element_state) = element_state.as_mut() {
2413 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
2414 let clicked_state = clicked_state.borrow();
2415 self.active = Some(clicked_state.element);
2416 }
2417 if self.hover_style.is_some() || self.group_hover_style.is_some() {
2418 element_state
2419 .hover_state
2420 .get_or_insert_with(Default::default);
2421 }
2422 if let Some(active_tooltip) = element_state.active_tooltip.as_ref() {
2423 if self.tooltip_builder.is_some() {
2424 self.tooltip_id = set_tooltip_on_window(active_tooltip, window);
2425 } else {
2426 if let Some(long_press_tooltip_active) =
2428 element_state.long_press_tooltip_active.as_ref()
2429 {
2430 long_press_tooltip_active.set(false);
2431 }
2432 element_state.active_tooltip.take();
2433 }
2434 }
2435 }
2436
2437 window.with_text_style(style.text_style().cloned(), |window| {
2438 window.with_content_mask(
2439 style.overflow_mask(bounds, window.rem_size()),
2440 |window| {
2441 let hitbox = if self.should_insert_hitbox(&style, window, cx) {
2442 Some(window.insert_hitbox(bounds, self.hitbox_behavior))
2443 } else {
2444 None
2445 };
2446
2447 let scroll_offset =
2448 self.clamp_scroll_position(bounds, &style, window, cx);
2449 let result = window.with_element_opacity(style.opacity, |window| {
2450 f(&style, scroll_offset, hitbox, window, cx)
2451 });
2452 (result, element_state)
2453 },
2454 )
2455 })
2456 },
2457 )
2458 }
2459
2460 fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
2461 self.hitbox_behavior != HitboxBehavior::Normal
2462 || self.window_control.is_some()
2463 || style.mouse_cursor.is_some()
2464 || self.group.is_some()
2465 || self.scroll_offset.is_some()
2466 || self.tracked_focus_handle.is_some()
2467 || self.hover_style.is_some()
2468 || self.group_hover_style.is_some()
2469 || self.hover_listener.is_some()
2470 || !self.mouse_up_listeners.is_empty()
2471 || !self.mouse_pressure_listeners.is_empty()
2472 || !self.mouse_down_listeners.is_empty()
2473 || !self.mouse_move_listeners.is_empty()
2474 || !self.mouse_exit_listeners.is_empty()
2475 || !self.file_drop_exit_listeners.is_empty()
2476 || !self.click_listeners.is_empty()
2477 || !self.aux_click_listeners.is_empty()
2478 || !self.scroll_listeners.is_empty()
2479 || self.has_pinch_listeners()
2480 || !self.back_gesture_listeners.is_empty()
2481 || self.drag_listener.is_some()
2482 || !self.drop_listeners.is_empty()
2483 || !self.drag_over_styles.is_empty()
2484 || self.tooltip_builder.is_some()
2485 || window.is_inspector_picking(cx)
2486 }
2487
2488 fn clamp_scroll_position(
2489 &self,
2490 bounds: Bounds<Pixels>,
2491 style: &Style,
2492 window: &mut Window,
2493 _cx: &mut App,
2494 ) -> Point<Pixels> {
2495 fn round_to_two_decimals(pixels: Pixels) -> Pixels {
2496 const ROUNDING_FACTOR: f32 = 100.0;
2497 (pixels * ROUNDING_FACTOR).round() / ROUNDING_FACTOR
2498 }
2499
2500 if let Some(scroll_offset) = self.scroll_offset.as_ref() {
2501 let mut scroll_to_bottom = false;
2502 let mut tracked_scroll_handle = self
2503 .tracked_scroll_handle
2504 .as_ref()
2505 .map(|handle| handle.0.borrow_mut());
2506 if let Some(scroll_handle_state) = tracked_scroll_handle.as_deref_mut() {
2507 scroll_handle_state.overflow = style.overflow;
2508 scroll_to_bottom = mem::take(&mut scroll_handle_state.scroll_to_bottom);
2509 }
2510
2511 let rem_size = window.rem_size();
2512 let padding = style
2517 .padding
2518 .to_pixels(bounds.size.into(), rem_size)
2519 .map(|edge| window.pixel_snap(*edge));
2520 let padding_size = size(padding.left + padding.right, padding.top + padding.bottom);
2521 let padded_content_size = self.content_size + padding_size;
2528 let scroll_max = Point::from(padded_content_size - bounds.size)
2529 .map(round_to_two_decimals)
2530 .max(&Default::default());
2531 let mut scroll_offset = scroll_offset.borrow_mut();
2534
2535 scroll_offset.x = scroll_offset.x.clamp(-scroll_max.x, px(0.));
2536 if scroll_to_bottom {
2537 scroll_offset.y = -scroll_max.y;
2538 } else {
2539 scroll_offset.y = scroll_offset.y.clamp(-scroll_max.y, px(0.));
2540 }
2541
2542 if let Some(mut scroll_handle_state) = tracked_scroll_handle {
2543 scroll_handle_state.max_offset = scroll_max;
2544 scroll_handle_state.bounds = bounds;
2545 }
2546
2547 *scroll_offset
2548 } else {
2549 Point::default()
2550 }
2551 }
2552
2553 pub fn paint(
2562 &mut self,
2563 global_id: Option<&GlobalElementId>,
2564 _inspector_id: Option<&InspectorElementId>,
2565 bounds: Bounds<Pixels>,
2566 hitbox: Option<&Hitbox>,
2567 window: &mut Window,
2568 cx: &mut App,
2569 f: impl FnOnce(&Style, &mut Window, &mut App),
2570 ) {
2571 self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(window));
2572 window.with_optional_element_state::<InteractiveElementState, _>(
2573 global_id,
2574 |element_state, window| {
2575 let mut element_state =
2576 element_state.map(|element_state| element_state.unwrap_or_default());
2577
2578 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
2579
2580 #[cfg(any(test, all(debug_assertions, feature = "test-support")))]
2581 if let Some(debug_selector) = &self.debug_selector {
2582 window
2583 .next_frame
2584 .record_debug_bounds(debug_selector.clone(), bounds);
2585 }
2586
2587 self.paint_hover_group_handler(window, cx);
2588
2589 if style.visibility == Visibility::Hidden {
2590 return ((), element_state);
2591 }
2592
2593 let mut tab_group = None;
2594 if self.tab_group {
2595 tab_group = self.tab_index;
2596 }
2597
2598 window.with_element_opacity(style.opacity, |window| {
2599 style.paint(bounds, window, cx, |window: &mut Window, cx: &mut App| {
2600 window.with_text_style(style.text_style().cloned(), |window| {
2601 window.with_content_mask(
2602 style.overflow_mask(bounds, window.rem_size()),
2603 |window| {
2604 window.with_tab_group(tab_group, |window| {
2605 if let Some(focus_handle) = &self.tracked_focus_handle {
2614 window.next_frame.tab_stops.insert(focus_handle);
2615 }
2616 if let Some(hitbox) = hitbox {
2617 #[cfg(debug_assertions)]
2618 self.paint_debug_info(
2619 global_id, hitbox, &style, window, cx,
2620 );
2621
2622 if let Some(drag) = cx.active_drag.as_ref() {
2623 if let Some(mouse_cursor) = drag.cursor_style {
2624 window.set_window_cursor_style(mouse_cursor);
2625 }
2626 } else {
2627 if let Some(mouse_cursor) = style.mouse_cursor {
2628 window.set_cursor_style(mouse_cursor, hitbox);
2629 }
2630 }
2631
2632 if let Some(group) = self.group.clone() {
2633 GroupHitboxes::push(group, hitbox.id, cx);
2634 }
2635
2636 if let Some(area) = self.window_control {
2637 window.insert_window_control_hitbox(
2638 area,
2639 hitbox.clone(),
2640 );
2641 }
2642
2643 self.paint_mouse_listeners(
2644 hitbox,
2645 element_state.as_mut(),
2646 window,
2647 cx,
2648 );
2649 self.paint_scroll_listener(hitbox, &style, window, cx);
2650 }
2651
2652 self.paint_keyboard_listeners(window, cx);
2653
2654 if window.a11y.is_active() {
2655 if let Some(global_id) = global_id {
2656 if !self.a11y_action_listeners.is_empty() {
2657 let node_id = global_id.accesskit_node_id();
2658 for (action, listener) in
2659 self.a11y_action_listeners.drain(..)
2660 {
2661 window.on_a11y_action(
2662 node_id, action, listener,
2663 );
2664 }
2665 }
2666 }
2667 }
2668
2669 f(&style, window, cx);
2670
2671 if let Some(_hitbox) = hitbox {
2672 #[cfg(any(feature = "inspector", debug_assertions))]
2673 window.insert_inspector_hitbox(
2674 _hitbox.id,
2675 _inspector_id,
2676 cx,
2677 );
2678
2679 if let Some(group) = self.group.as_ref() {
2680 GroupHitboxes::pop(group, cx);
2681 }
2682 }
2683 })
2684 },
2685 );
2686 });
2687 });
2688 });
2689
2690 ((), element_state)
2691 },
2692 );
2693 }
2694
2695 #[cfg(debug_assertions)]
2696 fn paint_debug_info(
2697 &self,
2698 global_id: Option<&GlobalElementId>,
2699 hitbox: &Hitbox,
2700 style: &Style,
2701 window: &mut Window,
2702 cx: &mut App,
2703 ) {
2704 use crate::{BorderStyle, TextAlign};
2705
2706 if let Some(global_id) = global_id
2707 && (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
2708 && hitbox.is_hovered(window)
2709 {
2710 const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
2711 let element_id = format!("{global_id:?}");
2712 let str_len = element_id.len();
2713
2714 let render_debug_text = |window: &mut Window| {
2715 if let Some(text) = window
2716 .text_system()
2717 .shape_text(
2718 element_id.into(),
2719 FONT_SIZE,
2720 &[window.text_style().to_run(str_len)],
2721 None,
2722 None,
2723 )
2724 .ok()
2725 .and_then(|mut text| text.pop())
2726 {
2727 text.paint(hitbox.origin, FONT_SIZE, TextAlign::Left, None, window, cx)
2728 .ok();
2729
2730 let text_bounds = crate::Bounds {
2731 origin: hitbox.origin,
2732 size: text.size(FONT_SIZE),
2733 };
2734 if let Some(source_location) = self.source_location
2735 && text_bounds.contains(&window.mouse_position())
2736 && window.modifiers().secondary(cx)
2737 {
2738 let secondary_held = window.modifiers().secondary(cx);
2739 window.on_key_event({
2740 move |e: &crate::ModifiersChangedEvent, _phase, window, cx| {
2741 if e.modifiers.secondary(cx) != secondary_held
2742 && text_bounds.contains(&window.mouse_position())
2743 {
2744 window.refresh();
2745 }
2746 }
2747 });
2748
2749 let was_hovered = hitbox.is_hovered(window);
2750 let current_view = window.current_view();
2751 window.on_mouse_event({
2752 let hitbox = hitbox.clone();
2753 move |_: &MouseMoveEvent, phase, window, cx| {
2754 if phase == DispatchPhase::Capture {
2755 let hovered = hitbox.is_hovered(window);
2756 if hovered != was_hovered {
2757 cx.notify(current_view)
2758 }
2759 }
2760 }
2761 });
2762
2763 window.on_mouse_event({
2764 let hitbox = hitbox.clone();
2765 move |e: &crate::MouseDownEvent, phase, window, cx| {
2766 if text_bounds.contains(&e.position)
2767 && phase.capture()
2768 && hitbox.is_hovered(window)
2769 {
2770 cx.stop_propagation();
2771 let Ok(dir) = std::env::current_dir() else {
2772 return;
2773 };
2774
2775 eprintln!(
2776 "This element was created at:\n{}:{}:{}",
2777 dir.join(source_location.file()).to_string_lossy(),
2778 source_location.line(),
2779 source_location.column()
2780 );
2781 }
2782 }
2783 });
2784 window.paint_quad(crate::outline(
2785 crate::Bounds {
2786 origin: hitbox.origin
2787 + crate::point(crate::px(0.), FONT_SIZE - px(2.)),
2788 size: crate::Size {
2789 width: text_bounds.size.width,
2790 height: crate::px(1.),
2791 },
2792 },
2793 crate::red(),
2794 BorderStyle::default(),
2795 ))
2796 }
2797 }
2798 };
2799
2800 window.with_text_style(
2801 Some(crate::TextStyleRefinement {
2802 color: Some(crate::red()),
2803 line_height: Some(FONT_SIZE.into()),
2804 background_color: Some(crate::white()),
2805 ..Default::default()
2806 }),
2807 render_debug_text,
2808 )
2809 }
2810 }
2811
2812 fn paint_mouse_listeners(
2813 &mut self,
2814 hitbox: &Hitbox,
2815 element_state: Option<&mut InteractiveElementState>,
2816 window: &mut Window,
2817 cx: &mut App,
2818 ) {
2819 let is_focused = self
2820 .tracked_focus_handle
2821 .as_ref()
2822 .map(|handle| handle.is_focused(window))
2823 .unwrap_or(false);
2824
2825 if let Some(focus_handle) = self.tracked_focus_handle.clone() {
2829 let hitbox = hitbox.clone();
2830 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, cx| {
2831 if phase == DispatchPhase::Bubble
2832 && hitbox.is_hovered(window)
2833 && !window.default_prevented()
2834 {
2835 window.focus(&focus_handle, cx);
2836 window.prevent_default();
2839 }
2840 });
2841 }
2842
2843 for listener in self.mouse_down_listeners.drain(..) {
2844 let hitbox = hitbox.clone();
2845 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
2846 listener(event, phase, &hitbox, window, cx);
2847 })
2848 }
2849
2850 for listener in self.mouse_up_listeners.drain(..) {
2851 let hitbox = hitbox.clone();
2852 window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
2853 listener(event, phase, &hitbox, window, cx);
2854 })
2855 }
2856
2857 for listener in self.mouse_pressure_listeners.drain(..) {
2858 let hitbox = hitbox.clone();
2859 window.on_mouse_event(move |event: &MousePressureEvent, phase, window, cx| {
2860 listener(event, phase, &hitbox, window, cx);
2861 })
2862 }
2863
2864 for listener in self.mouse_move_listeners.drain(..) {
2865 let hitbox = hitbox.clone();
2866 window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
2867 listener(event, phase, &hitbox, window, cx);
2868 })
2869 }
2870
2871 for listener in self.mouse_exit_listeners.drain(..) {
2872 let hitbox = hitbox.clone();
2873 window.on_mouse_event(move |event: &MouseExitEvent, phase, window, cx| {
2874 listener(event, phase, &hitbox, window, cx);
2875 })
2876 }
2877
2878 for listener in self.file_drop_exit_listeners.drain(..) {
2879 let hitbox = hitbox.clone();
2880 window.on_mouse_event(move |event: &FileDropEvent, phase, window, cx| {
2881 listener(event, phase, &hitbox, window, cx);
2882 })
2883 }
2884
2885 for listener in self.scroll_listeners.drain(..) {
2886 let hitbox = hitbox.clone();
2887 window.on_mouse_event(move |event: &ScrollEvent, phase, window, cx| {
2888 listener(event, phase, &hitbox, window, cx);
2889 })
2890 }
2891
2892 for listener in self.pinch_listeners.drain(..) {
2893 let hitbox = hitbox.clone();
2894 window.on_mouse_event(move |event: &PinchEvent, phase, window, cx| {
2895 listener(event, phase, &hitbox, window, cx);
2896 })
2897 }
2898
2899 for listener in self.back_gesture_listeners.drain(..) {
2900 let hitbox = hitbox.clone();
2901 window.on_mouse_event(move |event: &BackGestureEvent, phase, window, cx| {
2902 listener(event, phase, &hitbox, window, cx);
2903 })
2904 }
2905
2906 if self.hover_style.is_some()
2907 || self.base_style.mouse_cursor.is_some()
2908 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
2909 {
2910 let hitbox = hitbox.clone();
2911 let hover_state = self.hover_style.as_ref().and_then(|_| {
2912 element_state
2913 .as_ref()
2914 .and_then(|state| state.hover_state.as_ref())
2915 .cloned()
2916 });
2917 let current_view = window.current_view();
2918
2919 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2920 let hovered = hitbox.is_hovered(window);
2921 let was_hovered = hover_state
2922 .as_ref()
2923 .is_some_and(|state| state.borrow().element);
2924 if phase == DispatchPhase::Capture && hovered != was_hovered {
2925 if let Some(hover_state) = &hover_state {
2926 hover_state.borrow_mut().element = hovered;
2927 cx.notify(current_view);
2928 }
2929 }
2930 });
2931 }
2932
2933 if let Some(group_hover) = self.group_hover_style.as_ref() {
2934 if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
2935 let hover_state = element_state
2936 .as_ref()
2937 .and_then(|element| element.hover_state.as_ref())
2938 .cloned();
2939 let current_view = window.current_view();
2940
2941 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2942 let group_hovered = group_hitbox_id.is_hovered(window);
2943 let was_group_hovered = hover_state
2944 .as_ref()
2945 .is_some_and(|state| state.borrow().group);
2946 if phase == DispatchPhase::Capture && group_hovered != was_group_hovered {
2947 if let Some(hover_state) = &hover_state {
2948 hover_state.borrow_mut().group = group_hovered;
2949 cx.notify(current_view);
2950 }
2951 }
2952 });
2953 }
2954 }
2955
2956 let drag_cursor_style = self.base_style.as_ref().mouse_cursor;
2957
2958 let mut drag_listener = mem::take(&mut self.drag_listener);
2959 let drop_listeners = mem::take(&mut self.drop_listeners);
2960 let click_listeners = mem::take(&mut self.click_listeners);
2961 let aux_click_listeners = mem::take(&mut self.aux_click_listeners);
2962 let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
2963
2964 if !drop_listeners.is_empty() {
2965 let hitbox = hitbox.clone();
2966 window.on_mouse_event({
2967 move |_: &MouseUpEvent, phase, window, cx| {
2968 if let Some(drag) = &cx.active_drag
2969 && phase == DispatchPhase::Bubble
2970 && hitbox.is_hovered(window)
2971 {
2972 let drag_state_type = drag.value.as_ref().type_id();
2973 for (drop_state_type, listener) in &drop_listeners {
2974 if *drop_state_type == drag_state_type {
2975 let drag = cx
2976 .active_drag
2977 .take()
2978 .expect("checked for type drag state type above");
2979
2980 let mut can_drop = true;
2981 if let Some(predicate) = &can_drop_predicate {
2982 can_drop = predicate(drag.value.as_ref(), window, cx);
2983 }
2984
2985 if can_drop {
2986 listener(drag.value.as_ref(), window, cx);
2987 window.refresh();
2988 cx.stop_propagation();
2989 }
2990 }
2991 }
2992 }
2993 }
2994 });
2995 }
2996
2997 if let Some(element_state) = element_state {
2998 if !click_listeners.is_empty()
2999 || !aux_click_listeners.is_empty()
3000 || drag_listener.is_some()
3001 {
3002 let pending_mouse_down = element_state
3003 .pending_mouse_down
3004 .get_or_insert_with(Default::default)
3005 .clone();
3006
3007 let pending_keyboard_down = element_state
3008 .pending_keyboard_down
3009 .get_or_insert_with(Default::default)
3010 .clone();
3011
3012 let clicked_state = element_state
3013 .clicked_state
3014 .get_or_insert_with(Default::default)
3015 .clone();
3016
3017 window.on_mouse_event({
3018 let pending_mouse_down = pending_mouse_down.clone();
3019 let hitbox = hitbox.clone();
3020 let has_aux_click_listeners = !aux_click_listeners.is_empty();
3021 move |event: &MouseDownEvent, phase, window, _cx| {
3022 if phase == DispatchPhase::Bubble
3023 && (event.button == MouseButton::Left || has_aux_click_listeners)
3024 && hitbox.is_hovered(window)
3025 {
3026 *pending_mouse_down.borrow_mut() = Some(event.clone());
3027 window.refresh();
3028 }
3029 }
3030 });
3031
3032 window.on_mouse_event({
3033 let pending_mouse_down = pending_mouse_down.clone();
3034 let hitbox = hitbox.clone();
3035 move |event: &MouseMoveEvent, phase, window, cx| {
3036 if phase == DispatchPhase::Capture {
3037 return;
3038 }
3039
3040 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
3041 if let Some(mouse_down) = pending_mouse_down.clone()
3042 && !cx.has_active_drag()
3043 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
3044 && let Some(listener) = drag_listener.take()
3045 && mouse_down.button == MouseButton::Left
3046 {
3047 *clicked_state.borrow_mut() = ElementClickedState::default();
3048 let cursor_offset = event.position - hitbox.origin;
3049 let drag = (listener.render)(
3050 listener.value.as_ref(),
3051 cursor_offset,
3052 window,
3053 cx,
3054 );
3055 let external_payload_source =
3056 listener.external_payload.map(|external_payload| {
3057 let value = listener.value.clone();
3058 Box::new(move |window: &mut Window, cx: &mut App| {
3059 external_payload(value.as_ref(), window, cx)
3060 })
3061 as ExternalDragPayloadSource
3062 });
3063 cx.active_drag = Some(AnyDrag {
3064 view: drag,
3065 value: listener.value,
3066 cursor_offset,
3067 cursor_style: drag_cursor_style,
3068 external_payload_source,
3069 });
3070 pending_mouse_down.take();
3071 window.refresh();
3072 cx.stop_propagation();
3073 }
3074 }
3075 });
3076
3077 if is_focused {
3078 window.on_key_event({
3088 let pending_keyboard_down = pending_keyboard_down.clone();
3089 move |event: &KeyDownEvent, phase, window, _cx| {
3090 if phase.bubble() && !window.default_prevented() {
3091 let stroke = &event.keystroke;
3092 let is_activation_key = (stroke.key.eq("enter")
3093 || stroke.key.eq("space"))
3094 && !stroke.modifiers.modified();
3095 *pending_keyboard_down.borrow_mut() =
3096 is_activation_key.then_some(window.focus_generation);
3097 }
3098 }
3099 });
3100
3101 window.on_key_event({
3103 let click_listeners = click_listeners.clone();
3104 let hitbox = hitbox.clone();
3105 move |event: &KeyUpEvent, phase, window, cx| {
3106 if phase.bubble() && !window.default_prevented() {
3107 let stroke = &event.keystroke;
3108 let keyboard_button = if stroke.key.eq("enter") {
3109 Some(KeyboardButton::Enter)
3110 } else if stroke.key.eq("space") {
3111 Some(KeyboardButton::Space)
3112 } else {
3113 None
3114 };
3115
3116 if let Some(button) = keyboard_button
3117 && !stroke.modifiers.modified()
3118 {
3119 let pending =
3120 std::mem::take(&mut *pending_keyboard_down.borrow_mut());
3121 if pending != Some(window.focus_generation) {
3122 return;
3123 }
3124
3125 let click_event = ClickEvent::Keyboard(KeyboardClickEvent {
3126 button,
3127 bounds: hitbox.bounds,
3128 });
3129
3130 for listener in &click_listeners {
3131 listener(&click_event, window, cx);
3132 }
3133 } else {
3134 *pending_keyboard_down.borrow_mut() = None;
3138 }
3139 }
3140 }
3141 });
3142 }
3143
3144 window.on_mouse_event({
3145 let mut captured_mouse_down = None;
3146 let hitbox = hitbox.clone();
3147 move |event: &MouseUpEvent, phase, window, cx| match phase {
3148 DispatchPhase::Capture => {
3152 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
3153 if pending_mouse_down.is_some() && hitbox.is_hovered(window) {
3154 captured_mouse_down = pending_mouse_down.take();
3155 window.refresh();
3156 } else if pending_mouse_down.is_some() {
3157 pending_mouse_down.take();
3163 window.refresh();
3164 }
3165 }
3166 DispatchPhase::Bubble => {
3168 if let Some(mouse_down) = captured_mouse_down.take() {
3169 let btn = mouse_down.button;
3170
3171 let mouse_click = ClickEvent::Mouse(MouseClickEvent {
3172 down: mouse_down,
3173 up: event.clone(),
3174 });
3175
3176 match btn {
3177 MouseButton::Left => {
3178 for listener in &click_listeners {
3179 listener(&mouse_click, window, cx);
3180 }
3181 }
3182 _ => {
3183 for listener in &aux_click_listeners {
3184 listener(&mouse_click, window, cx);
3185 }
3186 }
3187 }
3188 }
3189 }
3190 }
3191 });
3192 }
3193
3194 if let Some(hover_listener) = self.hover_listener.take() {
3195 let was_hovered = element_state
3196 .hover_listener_state
3197 .get_or_insert_with(Default::default)
3198 .clone();
3199 let has_mouse_down = element_state
3200 .pending_mouse_down
3201 .get_or_insert_with(Default::default)
3202 .clone();
3203 let hover_listener = Rc::new(hover_listener);
3204 let hover_listener_state = was_hovered.clone();
3205 let update_hover = move |is_hovered: bool, window: &mut Window, cx: &mut App| {
3206 let mut was_hovered = hover_listener_state.borrow_mut();
3207 if is_hovered != *was_hovered {
3208 *was_hovered = is_hovered;
3209 drop(was_hovered);
3210 hover_listener(&is_hovered, window, cx);
3211 }
3212 };
3213 let hover_listener_mode = self.hover_listener_mode;
3214
3215 if has_mouse_down.borrow().is_none() {
3216 let is_hovered =
3217 !cx.has_active_drag() && hover_listener_mode.is_hovered(hitbox, window);
3218 if is_hovered != *was_hovered.borrow() {
3219 let update_hover = update_hover.clone();
3220 window.defer(cx, move |window, cx| {
3221 update_hover(is_hovered, window, cx);
3222 });
3223 }
3224 }
3225
3226 window.on_mouse_event({
3227 let update_hover = update_hover.clone();
3228 let hitbox = hitbox.clone();
3229 move |_: &MouseMoveEvent, phase, window, cx| {
3230 if phase == DispatchPhase::Bubble {
3231 let is_hovered = has_mouse_down.borrow().is_none()
3232 && !cx.has_active_drag()
3233 && hover_listener_mode.is_hovered(&hitbox, window);
3234 update_hover(is_hovered, window, cx);
3235 }
3236 }
3237 });
3238
3239 window.on_mouse_event(move |_: &MouseExitEvent, phase, window, cx| {
3242 if phase == DispatchPhase::Bubble {
3243 update_hover(false, window, cx);
3244 }
3245 });
3246 }
3247
3248 if let Some(tooltip_builder) = self.tooltip_builder.take() {
3249 let active_tooltip = element_state
3250 .active_tooltip
3251 .get_or_insert_with(Default::default)
3252 .clone();
3253 let pending_mouse_down = element_state
3254 .pending_mouse_down
3255 .get_or_insert_with(Default::default)
3256 .clone();
3257 let long_press_tooltip_active = element_state
3258 .long_press_tooltip_active
3259 .get_or_insert_with(Default::default)
3260 .clone();
3261
3262 let tooltip_is_hoverable = tooltip_builder.hoverable;
3263 let build_tooltip = Rc::new(move |window: &mut Window, cx: &mut App| {
3264 Some(((tooltip_builder.build)(window, cx), tooltip_is_hoverable))
3265 });
3266 let check_is_hovered_during_prepaint = Rc::new({
3268 let pending_mouse_down = pending_mouse_down.clone();
3269 let source_bounds = hitbox.bounds;
3270 move |window: &Window| {
3271 !window.last_input_was_keyboard()
3272 && pending_mouse_down.borrow().is_none()
3273 && source_bounds.contains(&window.mouse_position())
3274 }
3275 });
3276 let check_is_hovered = Rc::new({
3277 let hitbox = hitbox.clone();
3278 move |window: &Window| {
3279 pending_mouse_down.borrow().is_none() && hitbox.is_hovered(window)
3280 }
3281 });
3282 register_tooltip_mouse_handlers(
3283 &active_tooltip,
3284 self.tooltip_id,
3285 build_tooltip,
3286 check_is_hovered,
3287 check_is_hovered_during_prepaint,
3288 long_press_tooltip_active,
3289 self.tooltip_show_delay,
3290 window,
3291 );
3292 }
3293
3294 let active_state = element_state
3297 .clicked_state
3298 .get_or_insert_with(Default::default)
3299 .clone();
3300
3301 {
3302 let active_state = active_state.clone();
3303 window.on_mouse_event(move |_: &MouseUpEvent, phase, window, _cx| {
3304 if phase == DispatchPhase::Capture && active_state.borrow().is_clicked() {
3305 *active_state.borrow_mut() = ElementClickedState::default();
3306 window.refresh();
3307 }
3308 });
3309 }
3310
3311 {
3312 let active_group_hitbox = self
3313 .group_active_style
3314 .as_ref()
3315 .and_then(|group_active| GroupHitboxes::get(&group_active.group, cx));
3316 let hitbox = hitbox.clone();
3317 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _cx| {
3318 if phase == DispatchPhase::Bubble && !window.default_prevented() {
3319 let group_hovered = active_group_hitbox
3320 .is_some_and(|group_hitbox_id| group_hitbox_id.is_hovered(window));
3321 let element_hovered = hitbox.is_hovered(window);
3322 if group_hovered || element_hovered {
3323 *active_state.borrow_mut() = ElementClickedState {
3324 group: group_hovered,
3325 element: element_hovered,
3326 };
3327 window.refresh();
3328 }
3329 }
3330 });
3331 }
3332 }
3333 }
3334
3335 fn paint_keyboard_listeners(&mut self, window: &mut Window, _cx: &mut App) {
3336 let key_down_listeners = mem::take(&mut self.key_down_listeners);
3337 let key_up_listeners = mem::take(&mut self.key_up_listeners);
3338 let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners);
3339 let action_listeners = mem::take(&mut self.action_listeners);
3340 if let Some(context) = self.key_context.clone() {
3341 window.set_key_context(context);
3342 }
3343
3344 for listener in key_down_listeners {
3345 window.on_key_event(move |event: &KeyDownEvent, phase, window, cx| {
3346 listener(event, phase, window, cx);
3347 })
3348 }
3349
3350 for listener in key_up_listeners {
3351 window.on_key_event(move |event: &KeyUpEvent, phase, window, cx| {
3352 listener(event, phase, window, cx);
3353 })
3354 }
3355
3356 for listener in modifiers_changed_listeners {
3357 window.on_modifiers_changed(move |event: &ModifiersChangedEvent, window, cx| {
3358 listener(event, window, cx);
3359 })
3360 }
3361
3362 for (action_type, listener) in action_listeners {
3363 window.on_action(action_type, listener)
3364 }
3365 }
3366
3367 fn paint_hover_group_handler(&self, window: &mut Window, cx: &mut App) {
3368 let group_hitbox = self
3369 .group_hover_style
3370 .as_ref()
3371 .and_then(|group_hover| GroupHitboxes::get(&group_hover.group, cx));
3372
3373 if let Some(group_hitbox) = group_hitbox {
3374 let was_hovered = group_hitbox.is_hovered(window);
3375 let current_view = window.current_view();
3376 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
3377 let hovered = group_hitbox.is_hovered(window);
3378 if phase == DispatchPhase::Capture && hovered != was_hovered {
3379 cx.notify(current_view);
3380 }
3381 });
3382 }
3383 }
3384
3385 fn paint_scroll_listener(
3386 &self,
3387 hitbox: &Hitbox,
3388 style: &Style,
3389 window: &mut Window,
3390 _cx: &mut App,
3391 ) {
3392 if let Some(scroll_offset) = self.scroll_offset.clone() {
3393 let ongoing_scroll = self.ongoing_scroll.clone();
3394 let overflow = style.overflow;
3395 let allow_concurrent_scroll = style.allow_concurrent_scroll;
3396 let restrict_scroll_to_axis = style.restrict_scroll_to_axis;
3397 let line_height = window.line_height();
3398 let hitbox = hitbox.clone();
3399 let current_view = window.current_view();
3400 window.on_mouse_event(move |event: &ScrollEvent, phase, window, cx| {
3401 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
3402 let mut scroll_offset = scroll_offset.borrow_mut();
3403 let old_scroll_offset = *scroll_offset;
3404 let mut delta = event.delta.pixel_delta(line_height);
3405
3406 if restrict_scroll_to_axis
3407 && event.delta.precise()
3408 && let Some(ongoing_scroll) = &ongoing_scroll
3409 {
3410 ongoing_scroll
3411 .borrow_mut()
3412 .filter(&mut delta, event.touch_phase);
3413 }
3414
3415 let mut delta_x = match overflow.x {
3416 Overflow::Scroll if !delta.x.is_zero() => delta.x,
3417 Overflow::Scroll
3418 if !restrict_scroll_to_axis && overflow.y != Overflow::Scroll =>
3419 {
3420 delta.y
3421 }
3422 _ => Pixels::ZERO,
3423 };
3424 let mut delta_y = match overflow.y {
3425 Overflow::Scroll if !delta.y.is_zero() => delta.y,
3426 Overflow::Scroll
3427 if !restrict_scroll_to_axis && overflow.x != Overflow::Scroll =>
3428 {
3429 delta.x
3430 }
3431 _ => Pixels::ZERO,
3432 };
3433 if !allow_concurrent_scroll && !delta_x.is_zero() && !delta_y.is_zero() {
3434 if delta_x.abs() > delta_y.abs() {
3435 delta_y = Pixels::ZERO;
3436 } else {
3437 delta_x = Pixels::ZERO;
3438 }
3439 }
3440 scroll_offset.y += delta_y;
3441 scroll_offset.x += delta_x;
3442 if *scroll_offset != old_scroll_offset {
3443 cx.notify(current_view);
3444 }
3445 }
3446 });
3447 }
3448 }
3449
3450 pub fn compute_style(
3452 &self,
3453 global_id: Option<&GlobalElementId>,
3454 hitbox: Option<&Hitbox>,
3455 window: &mut Window,
3456 cx: &mut App,
3457 ) -> Style {
3458 window.with_optional_element_state(global_id, |element_state, window| {
3459 let mut element_state =
3460 element_state.map(|element_state| element_state.unwrap_or_default());
3461 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
3462 (style, element_state)
3463 })
3464 }
3465
3466 fn compute_style_internal(
3468 &self,
3469 hitbox: Option<&Hitbox>,
3470 element_state: Option<&mut InteractiveElementState>,
3471 window: &mut Window,
3472 cx: &mut App,
3473 ) -> Style {
3474 let mut style = Style::default();
3475 style.refine(&self.base_style);
3476
3477 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
3478 if let Some(in_focus_style) = self.in_focus_style.as_ref()
3479 && focus_handle.within_focused(window, cx)
3480 {
3481 style.refine(in_focus_style);
3482 }
3483
3484 if let Some(focus_style) = self.focus_style.as_ref()
3485 && focus_handle.is_focused(window)
3486 {
3487 style.refine(focus_style);
3488 }
3489
3490 if let Some(focus_visible_style) = self.focus_visible_style.as_ref()
3491 && focus_handle.is_focused(window)
3492 && window.last_input_was_keyboard()
3493 {
3494 style.refine(focus_visible_style);
3495 }
3496 }
3497
3498 if !cx.has_active_drag() {
3499 if let Some(group_hover) = self.group_hover_style.as_ref() {
3500 let is_group_hovered =
3501 if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
3502 !window.last_input_was_touch() && group_hitbox_id.is_hovered(window)
3503 } else if let Some(element_state) = element_state.as_ref() {
3504 !window.last_input_was_touch()
3505 && element_state
3506 .hover_state
3507 .as_ref()
3508 .map(|state| state.borrow().group)
3509 .unwrap_or(false)
3510 } else {
3511 false
3512 };
3513
3514 if is_group_hovered {
3515 style.refine(&group_hover.style);
3516 }
3517 }
3518
3519 if let Some(hover_style) = self.hover_style.as_ref() {
3520 let is_hovered = if let Some(hitbox) = hitbox {
3521 !window.last_input_was_touch() && hitbox.is_hovered(window)
3522 } else if let Some(element_state) = element_state.as_ref() {
3523 !window.last_input_was_touch()
3524 && element_state
3525 .hover_state
3526 .as_ref()
3527 .map(|state| state.borrow().element)
3528 .unwrap_or(false)
3529 } else {
3530 false
3531 };
3532
3533 if is_hovered {
3534 style.refine(hover_style);
3535 }
3536 }
3537 }
3538
3539 if let Some(hitbox) = hitbox {
3540 if let Some(drag) = cx.active_drag.take() {
3541 let mut can_drop = true;
3542 if let Some(can_drop_predicate) = &self.can_drop_predicate {
3543 can_drop = can_drop_predicate(drag.value.as_ref(), window, cx);
3544 }
3545
3546 if can_drop {
3547 for (state_type, group_drag_style) in &self.group_drag_over_styles {
3548 if let Some(group_hitbox_id) =
3549 GroupHitboxes::get(&group_drag_style.group, cx)
3550 && *state_type == drag.value.as_ref().type_id()
3551 && group_hitbox_id.is_hovered(window)
3552 {
3553 style.refine(&group_drag_style.style);
3554 }
3555 }
3556
3557 for (state_type, build_drag_over_style) in &self.drag_over_styles {
3558 if *state_type == drag.value.as_ref().type_id() && hitbox.is_hovered(window)
3559 {
3560 style.refine(&build_drag_over_style(drag.value.as_ref(), window, cx));
3561 }
3562 }
3563 }
3564
3565 style.mouse_cursor = drag.cursor_style;
3566 cx.active_drag = Some(drag);
3567 }
3568 }
3569
3570 if let Some(element_state) = element_state {
3571 let clicked_state = element_state
3572 .clicked_state
3573 .get_or_insert_with(Default::default)
3574 .borrow();
3575 if clicked_state.group
3576 && let Some(group) = self.group_active_style.as_ref()
3577 {
3578 style.refine(&group.style)
3579 }
3580
3581 if let Some(active_style) = self.active_style.as_ref()
3582 && clicked_state.element
3583 {
3584 style.refine(active_style)
3585 }
3586 }
3587
3588 style
3589 }
3590
3591 pub(crate) fn write_a11y_info(&self, node: &mut accesskit::Node) {
3592 if let Some(id) = &self.aria.author_id {
3593 node.set_author_id(id.to_string());
3594 }
3595 if let Some(label) = &self.aria.label {
3596 node.set_label(label.to_string());
3597 }
3598 if let Some(description) = &self.aria.description {
3599 node.set_description(description.to_string());
3600 }
3601 if let Some(keyshortcuts) = &self.aria.keyshortcuts {
3602 node.set_keyboard_shortcut(keyshortcuts.to_string());
3603 }
3604 if let Some(selected) = self.aria.selected {
3605 node.set_selected(selected);
3606 }
3607 if let Some(expanded) = self.aria.expanded {
3608 node.set_expanded(expanded);
3609 }
3610 if let Some(toggled) = self.aria.toggled {
3611 node.set_toggled(toggled);
3612 }
3613 if let Some(value) = self.aria.numeric_value {
3614 node.set_numeric_value(value);
3615 }
3616 if let Some(value) = self.aria.min_numeric_value {
3617 node.set_min_numeric_value(value);
3618 }
3619 if let Some(value) = self.aria.max_numeric_value {
3620 node.set_max_numeric_value(value);
3621 }
3622 if let Some(step) = self.aria.numeric_value_step {
3623 node.set_numeric_value_step(step);
3624 }
3625 if let Some(value) = &self.aria.value {
3626 node.set_value(value.to_string());
3627 }
3628 if let Some(placeholder) = &self.aria.placeholder {
3629 node.set_placeholder(placeholder.to_string());
3630 }
3631 if let Some(orientation) = self.aria.orientation {
3632 node.set_orientation(orientation);
3633 }
3634 if let Some(level) = self.aria.level {
3635 node.set_level(level);
3636 }
3637 if let Some(position) = self.aria.position_in_set {
3638 node.set_position_in_set(position);
3639 }
3640 if let Some(size) = self.aria.size_of_set {
3641 node.set_size_of_set(size);
3642 }
3643 if let Some(index) = self.aria.row_index {
3644 node.set_row_index(index);
3645 }
3646 if let Some(index) = self.aria.column_index {
3647 node.set_column_index(index);
3648 }
3649 if let Some(count) = self.aria.row_count {
3650 node.set_row_count(count);
3651 }
3652 if let Some(count) = self.aria.column_count {
3653 node.set_column_count(count);
3654 }
3655 if !self.click_listeners.is_empty() {
3656 node.add_action(accesskit::Action::Click);
3657 }
3658 if self.tracked_focus_handle.is_some() || self.focusable {
3659 node.add_action(accesskit::Action::Focus);
3660 }
3661 for (action, _) in &self.a11y_action_listeners {
3662 node.add_action(*action);
3663 }
3664 }
3665}
3666
3667#[derive(Default)]
3670pub struct InteractiveElementState {
3671 pub(crate) focus_handle: Option<FocusHandle>,
3672 pub(crate) clicked_state: Option<Rc<RefCell<ElementClickedState>>>,
3673 pub(crate) hover_state: Option<Rc<RefCell<ElementHoverState>>>,
3674 pub(crate) hover_listener_state: Option<Rc<RefCell<bool>>>,
3675 pub(crate) pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
3676 pub(crate) pending_keyboard_down: Option<Rc<RefCell<Option<u64>>>>,
3684 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
3685 ongoing_scroll: Option<Rc<RefCell<OngoingScroll>>>,
3686 pub(crate) active_tooltip: Option<Rc<RefCell<Option<ActiveTooltip>>>>,
3687 long_press_tooltip_active: Option<Rc<Cell<bool>>>,
3688}
3689
3690#[derive(Copy, Clone, Default, Eq, PartialEq)]
3692pub struct ElementClickedState {
3693 pub group: bool,
3695
3696 pub element: bool,
3698}
3699
3700impl ElementClickedState {
3701 fn is_clicked(&self) -> bool {
3702 self.group || self.element
3703 }
3704}
3705
3706#[derive(Copy, Clone, Default, Eq, PartialEq)]
3708pub struct ElementHoverState {
3709 pub group: bool,
3711
3712 pub element: bool,
3714}
3715
3716pub(crate) enum ActiveTooltip {
3717 WaitingForShow { _task: Task<()> },
3719 Visible {
3721 tooltip: AnyTooltip,
3722 is_hoverable: bool,
3723 },
3724 WaitingForHide {
3727 tooltip: AnyTooltip,
3728 _task: Task<()>,
3729 },
3730}
3731
3732pub(crate) fn clear_active_tooltip(
3733 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3734 window: &mut Window,
3735) {
3736 match active_tooltip.borrow_mut().take() {
3737 None => {}
3738 Some(ActiveTooltip::WaitingForShow { .. }) => {}
3739 Some(ActiveTooltip::Visible { .. }) => window.refresh(),
3740 Some(ActiveTooltip::WaitingForHide { .. }) => window.refresh(),
3741 }
3742}
3743
3744pub(crate) fn clear_active_tooltip_if_not_hoverable(
3745 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3746 window: &mut Window,
3747) {
3748 let should_clear = match active_tooltip.borrow().as_ref() {
3749 None => false,
3750 Some(ActiveTooltip::WaitingForShow { .. }) => false,
3751 Some(ActiveTooltip::Visible { is_hoverable, .. }) => !is_hoverable,
3752 Some(ActiveTooltip::WaitingForHide { .. }) => false,
3753 };
3754 if should_clear {
3755 active_tooltip.borrow_mut().take();
3756 window.refresh();
3757 }
3758}
3759
3760pub(crate) fn set_tooltip_on_window(
3761 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3762 window: &mut Window,
3763) -> Option<TooltipId> {
3764 let tooltip = match active_tooltip.borrow().as_ref() {
3765 None => return None,
3766 Some(ActiveTooltip::WaitingForShow { .. }) => return None,
3767 Some(ActiveTooltip::Visible { tooltip, .. }) => tooltip.clone(),
3768 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => tooltip.clone(),
3769 };
3770 Some(window.set_tooltip(tooltip))
3771}
3772
3773pub(crate) fn register_tooltip_mouse_handlers(
3774 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3775 tooltip_id: Option<TooltipId>,
3776 build_tooltip: Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3777 check_is_hovered: Rc<dyn Fn(&Window) -> bool>,
3778 check_is_hovered_during_prepaint: Rc<dyn Fn(&Window) -> bool>,
3779 long_press_tooltip_active: Rc<Cell<bool>>,
3780 show_delay: Option<Duration>,
3781 window: &mut Window,
3782) {
3783 let current_view = window.current_view();
3784 let show_delay = show_delay.unwrap_or(DEFAULT_TOOLTIP_SHOW_DELAY);
3785
3786 window.on_mouse_event({
3787 let active_tooltip = active_tooltip.clone();
3788 let build_tooltip = build_tooltip.clone();
3789 let check_is_hovered = check_is_hovered.clone();
3790 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3791 move |_: &MouseMoveEvent, phase, window, cx| {
3792 handle_tooltip_mouse_move(
3793 &active_tooltip,
3794 &build_tooltip,
3795 &check_is_hovered,
3796 &check_is_hovered_during_prepaint,
3797 tooltip_id,
3798 current_view,
3799 phase,
3800 show_delay,
3801 window,
3802 cx,
3803 )
3804 }
3805 });
3806
3807 window.on_mouse_event({
3808 let active_tooltip = active_tooltip.clone();
3809 move |_: &MouseDownEvent, _phase, window: &mut Window, _cx| {
3810 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3811 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3812 }
3813 }
3814 });
3815
3816 window.on_mouse_event({
3817 let active_tooltip = active_tooltip.clone();
3818 let build_tooltip = build_tooltip.clone();
3819 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3820 move |event: &LongPressEvent, phase, window, cx| {
3821 if !phase.bubble() {
3822 return;
3823 }
3824
3825 match event.phase {
3826 TouchPhase::Started if !window.default_prevented() && check_is_hovered(window) => {
3827 if show_tooltip(
3828 &active_tooltip,
3829 &build_tooltip,
3830 &check_is_hovered_during_prepaint,
3831 Some(long_press_tooltip_active.clone()),
3832 window,
3833 cx,
3834 ) {
3835 long_press_tooltip_active.set(true);
3836 window.prevent_default();
3837 cx.stop_propagation();
3838 }
3839 }
3840 TouchPhase::Ended | TouchPhase::Cancelled
3841 if long_press_tooltip_active.replace(false) =>
3842 {
3843 clear_active_tooltip(&active_tooltip, window);
3844 cx.stop_propagation();
3845 }
3846 TouchPhase::Started
3847 | TouchPhase::Moved
3848 | TouchPhase::Ended
3849 | TouchPhase::Cancelled => {}
3850 }
3851 }
3852 });
3853
3854 window.on_mouse_event({
3855 let active_tooltip = active_tooltip.clone();
3856 move |_: &ScrollEvent, _phase, window: &mut Window, _cx| {
3857 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3858 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3859 }
3860 }
3861 });
3862}
3863
3864fn handle_tooltip_mouse_move(
3876 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3877 build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3878 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3879 check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
3880 tooltip_id: Option<TooltipId>,
3881 current_view: EntityId,
3882 phase: DispatchPhase,
3883 show_delay: Duration,
3884 window: &mut Window,
3885 cx: &mut App,
3886) {
3887 enum Action {
3890 None,
3891 CancelShow,
3892 ScheduleShow,
3893 CheckVisible,
3894 }
3895
3896 let action = match active_tooltip.borrow().as_ref() {
3897 None => {
3898 let is_hovered = check_is_hovered(window);
3899 if is_hovered && phase.bubble() {
3900 Action::ScheduleShow
3901 } else {
3902 Action::None
3903 }
3904 }
3905 Some(ActiveTooltip::WaitingForShow { .. }) => {
3906 let is_hovered = check_is_hovered(window);
3907 if is_hovered {
3908 Action::None
3909 } else {
3910 Action::CancelShow
3911 }
3912 }
3913 Some(ActiveTooltip::Visible { is_hoverable, .. }) => {
3914 if phase.capture()
3915 && !check_is_hovered(window)
3916 && (!*is_hoverable
3917 || !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3918 {
3919 Action::CheckVisible
3920 } else {
3921 Action::None
3922 }
3923 }
3924 Some(ActiveTooltip::WaitingForHide { .. }) => {
3925 if phase.capture()
3926 && (check_is_hovered(window)
3927 || tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3928 {
3929 Action::CheckVisible
3930 } else {
3931 Action::None
3932 }
3933 }
3934 };
3935
3936 match action {
3937 Action::None => {}
3938 Action::CancelShow => {
3939 active_tooltip.borrow_mut().take();
3941 }
3942 Action::ScheduleShow => {
3943 let delayed_show_task = window.spawn(cx, {
3944 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3945 let build_tooltip = build_tooltip.clone();
3946 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3947 async move |cx| {
3948 cx.background_executor().timer(show_delay).await;
3949 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3950 return;
3951 };
3952 cx.update(|window, cx| {
3953 show_tooltip(
3954 &active_tooltip,
3955 &build_tooltip,
3956 &check_is_hovered_during_prepaint,
3957 None,
3958 window,
3959 cx,
3960 );
3961 })
3962 .ok();
3963 }
3964 });
3965 active_tooltip
3966 .borrow_mut()
3967 .replace(ActiveTooltip::WaitingForShow {
3968 _task: delayed_show_task,
3969 });
3970 }
3971 Action::CheckVisible => cx.notify(current_view),
3972 }
3973}
3974
3975fn show_tooltip(
3976 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3977 build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3978 check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
3979 long_press_tooltip_active: Option<Rc<Cell<bool>>>,
3980 window: &mut Window,
3981 cx: &mut App,
3982) -> bool {
3983 let new_tooltip = build_tooltip(window, cx).map(|(view, tooltip_is_hoverable)| {
3984 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3985 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3986 ActiveTooltip::Visible {
3987 tooltip: AnyTooltip {
3988 view,
3989 mouse_position: window.mouse_position(),
3990 check_visible_and_update: Rc::new(move |tooltip_bounds, window, cx| {
3991 if long_press_tooltip_active
3992 .as_ref()
3993 .is_some_and(|active| active.get())
3994 {
3995 return true;
3996 }
3997 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3998 return false;
3999 };
4000 handle_tooltip_check_visible_and_update(
4001 &active_tooltip,
4002 tooltip_is_hoverable,
4003 &check_is_hovered_during_prepaint,
4004 tooltip_bounds,
4005 window,
4006 cx,
4007 )
4008 }),
4009 },
4010 is_hoverable: tooltip_is_hoverable,
4011 }
4012 });
4013 let did_show = new_tooltip.is_some();
4014 *active_tooltip.borrow_mut() = new_tooltip;
4015 window.refresh();
4016 did_show
4017}
4018
4019fn handle_tooltip_check_visible_and_update(
4023 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
4024 tooltip_is_hoverable: bool,
4025 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
4026 tooltip_bounds: Bounds<Pixels>,
4027 window: &mut Window,
4028 cx: &mut App,
4029) -> bool {
4030 enum Action {
4033 None,
4034 Hide,
4035 ScheduleHide(AnyTooltip),
4036 CancelHide(AnyTooltip),
4037 }
4038
4039 let is_hovered = check_is_hovered(window)
4040 || (tooltip_is_hoverable && tooltip_bounds.contains(&window.mouse_position()));
4041 let action = match active_tooltip.borrow().as_ref() {
4042 Some(ActiveTooltip::Visible { tooltip, .. }) => {
4043 if is_hovered {
4044 Action::None
4045 } else {
4046 if tooltip_is_hoverable {
4047 Action::ScheduleHide(tooltip.clone())
4048 } else {
4049 Action::Hide
4050 }
4051 }
4052 }
4053 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => {
4054 if is_hovered {
4055 Action::CancelHide(tooltip.clone())
4056 } else {
4057 Action::None
4058 }
4059 }
4060 None | Some(ActiveTooltip::WaitingForShow { .. }) => Action::None,
4061 };
4062
4063 match action {
4064 Action::None => {}
4065 Action::Hide => clear_active_tooltip(active_tooltip, window),
4066 Action::ScheduleHide(tooltip) => {
4067 let delayed_hide_task = window.spawn(cx, {
4068 let weak_active_tooltip = Rc::downgrade(active_tooltip);
4069 async move |cx| {
4070 cx.background_executor()
4071 .timer(HOVERABLE_TOOLTIP_HIDE_DELAY)
4072 .await;
4073 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
4074 return;
4075 };
4076 if active_tooltip.borrow_mut().take().is_some() {
4077 cx.update(|window, _cx| window.refresh()).ok();
4078 }
4079 }
4080 });
4081 active_tooltip
4082 .borrow_mut()
4083 .replace(ActiveTooltip::WaitingForHide {
4084 tooltip,
4085 _task: delayed_hide_task,
4086 });
4087 }
4088 Action::CancelHide(tooltip) => {
4089 active_tooltip.borrow_mut().replace(ActiveTooltip::Visible {
4091 tooltip,
4092 is_hoverable: true,
4093 });
4094 }
4095 }
4096
4097 active_tooltip.borrow().is_some()
4098}
4099
4100#[derive(Default)]
4101pub(crate) struct GroupHitboxes(HashMap<SharedString, SmallVec<[HitboxId; 1]>>);
4102
4103impl Global for GroupHitboxes {}
4104
4105impl GroupHitboxes {
4106 pub fn get(name: &SharedString, cx: &mut App) -> Option<HitboxId> {
4107 cx.default_global::<Self>()
4108 .0
4109 .get(name)
4110 .and_then(|bounds_stack| bounds_stack.last())
4111 .cloned()
4112 }
4113
4114 pub fn push(name: SharedString, hitbox_id: HitboxId, cx: &mut App) {
4115 cx.default_global::<Self>()
4116 .0
4117 .entry(name)
4118 .or_default()
4119 .push(hitbox_id);
4120 }
4121
4122 pub fn pop(name: &SharedString, cx: &mut App) {
4123 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
4124 }
4125}
4126
4127pub struct Stateful<E> {
4129 pub(crate) element: E,
4130}
4131
4132impl<E> Styled for Stateful<E>
4133where
4134 E: Styled,
4135{
4136 fn style(&mut self) -> &mut StyleRefinement {
4137 self.element.style()
4138 }
4139}
4140
4141impl<E> StatefulInteractiveElement for Stateful<E>
4142where
4143 E: Element,
4144 Self: InteractiveElement,
4145{
4146}
4147
4148impl<E> InteractiveElement for Stateful<E>
4149where
4150 E: InteractiveElement,
4151{
4152 fn interactivity(&mut self) -> &mut Interactivity {
4153 self.element.interactivity()
4154 }
4155}
4156
4157impl<E> Element for Stateful<E>
4158where
4159 E: Element,
4160{
4161 type RequestLayoutState = E::RequestLayoutState;
4162 type PrepaintState = E::PrepaintState;
4163
4164 fn id(&self) -> Option<ElementId> {
4165 self.element.id()
4166 }
4167
4168 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
4169 self.element.source_location()
4170 }
4171
4172 fn a11y_role(&self) -> Option<accesskit::Role> {
4173 self.element.a11y_role()
4174 }
4175
4176 fn write_a11y_info(&self, node: &mut accesskit::Node) {
4177 self.element.write_a11y_info(node);
4178 }
4179
4180 fn a11y_synthetic_children(
4181 &mut self,
4182 prepaint: &mut Self::PrepaintState,
4183 builder: &mut crate::A11ySubtreeBuilder,
4184 ) {
4185 self.element.a11y_synthetic_children(prepaint, builder);
4186 }
4187
4188 fn request_layout(
4189 &mut self,
4190 id: Option<&GlobalElementId>,
4191 inspector_id: Option<&InspectorElementId>,
4192 window: &mut Window,
4193 cx: &mut App,
4194 ) -> (LayoutId, Self::RequestLayoutState) {
4195 self.element.request_layout(id, inspector_id, window, cx)
4196 }
4197
4198 fn prepaint(
4199 &mut self,
4200 id: Option<&GlobalElementId>,
4201 inspector_id: Option<&InspectorElementId>,
4202 bounds: Bounds<Pixels>,
4203 state: &mut Self::RequestLayoutState,
4204 window: &mut Window,
4205 cx: &mut App,
4206 ) -> E::PrepaintState {
4207 self.element
4208 .prepaint(id, inspector_id, bounds, state, window, cx)
4209 }
4210
4211 fn paint(
4212 &mut self,
4213 id: Option<&GlobalElementId>,
4214 inspector_id: Option<&InspectorElementId>,
4215 bounds: Bounds<Pixels>,
4216 request_layout: &mut Self::RequestLayoutState,
4217 prepaint: &mut Self::PrepaintState,
4218 window: &mut Window,
4219 cx: &mut App,
4220 ) {
4221 self.element.paint(
4222 id,
4223 inspector_id,
4224 bounds,
4225 request_layout,
4226 prepaint,
4227 window,
4228 cx,
4229 );
4230 }
4231}
4232
4233impl<E> IntoElement for Stateful<E>
4234where
4235 E: Element,
4236{
4237 type Element = Self;
4238
4239 fn into_element(self) -> Self::Element {
4240 self
4241 }
4242}
4243
4244impl<E> ParentElement for Stateful<E>
4245where
4246 E: ParentElement,
4247{
4248 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
4249 self.element.extend(elements)
4250 }
4251}
4252
4253#[derive(Clone)]
4256pub struct ScrollAnchor {
4257 handle: ScrollHandle,
4258 last_origin: Rc<RefCell<Point<Pixels>>>,
4259}
4260
4261impl ScrollAnchor {
4262 pub fn for_handle(handle: ScrollHandle) -> Self {
4264 Self {
4265 handle,
4266 last_origin: Default::default(),
4267 }
4268 }
4269 pub fn scroll_to(&self, window: &mut Window, _cx: &mut App) {
4271 let this = self.clone();
4272
4273 window.on_next_frame(move |_, _| {
4274 let viewport_bounds = this.handle.bounds();
4275 let self_bounds = *this.last_origin.borrow();
4276 this.handle.set_offset(viewport_bounds.origin - self_bounds);
4277 });
4278 }
4279}
4280
4281#[derive(Default, Debug)]
4282struct ScrollHandleState {
4283 offset: Rc<RefCell<Point<Pixels>>>,
4284 ongoing_scroll: Rc<RefCell<OngoingScroll>>,
4285 bounds: Bounds<Pixels>,
4286 max_offset: Point<Pixels>,
4287 child_bounds: Vec<Bounds<Pixels>>,
4288 scroll_to_bottom: bool,
4289 overflow: Point<Overflow>,
4290 active_item: Option<ScrollActiveItem>,
4291}
4292
4293#[derive(Default, Debug, Clone, Copy)]
4294struct ScrollActiveItem {
4295 index: usize,
4296 strategy: ScrollStrategy,
4297}
4298
4299#[derive(Default, Debug, Clone, Copy)]
4300enum ScrollStrategy {
4301 #[default]
4302 FirstVisible,
4303 Top,
4304}
4305
4306#[derive(Clone, Debug)]
4310pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
4311
4312impl Default for ScrollHandle {
4313 fn default() -> Self {
4314 Self::new()
4315 }
4316}
4317
4318impl ScrollHandle {
4319 pub fn new() -> Self {
4321 Self(Rc::default())
4322 }
4323
4324 pub fn offset(&self) -> Point<Pixels> {
4326 *self.0.borrow().offset.borrow()
4327 }
4328
4329 pub fn max_offset(&self) -> Point<Pixels> {
4331 self.0.borrow().max_offset
4332 }
4333
4334 pub fn top_item(&self) -> usize {
4336 let state = self.0.borrow();
4337 let top = state.bounds.top() - state.offset.borrow().y;
4338
4339 match state.child_bounds.binary_search_by(|bounds| {
4340 if top < bounds.top() {
4341 Ordering::Greater
4342 } else if top > bounds.bottom() {
4343 Ordering::Less
4344 } else {
4345 Ordering::Equal
4346 }
4347 }) {
4348 Ok(ix) => ix,
4349 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
4350 }
4351 }
4352
4353 pub fn bottom_item(&self) -> usize {
4355 let state = self.0.borrow();
4356 let bottom = state.bounds.bottom() - state.offset.borrow().y;
4357
4358 match state.child_bounds.binary_search_by(|bounds| {
4359 if bottom < bounds.top() {
4360 Ordering::Greater
4361 } else if bottom > bounds.bottom() {
4362 Ordering::Less
4363 } else {
4364 Ordering::Equal
4365 }
4366 }) {
4367 Ok(ix) => ix,
4368 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
4369 }
4370 }
4371
4372 pub fn bounds(&self) -> Bounds<Pixels> {
4374 self.0.borrow().bounds
4375 }
4376
4377 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
4379 self.0.borrow().child_bounds.get(ix).cloned()
4380 }
4381
4382 pub fn scroll_to_item(&self, ix: usize) {
4384 let mut state = self.0.borrow_mut();
4385 state.active_item = Some(ScrollActiveItem {
4386 index: ix,
4387 strategy: ScrollStrategy::default(),
4388 });
4389 }
4390
4391 pub fn scroll_to_top_of_item(&self, ix: usize) {
4394 let mut state = self.0.borrow_mut();
4395 state.active_item = Some(ScrollActiveItem {
4396 index: ix,
4397 strategy: ScrollStrategy::Top,
4398 });
4399 }
4400
4401 fn scroll_to_active_item(&self) {
4405 let mut state = self.0.borrow_mut();
4406
4407 let Some(active_item) = state.active_item else {
4408 return;
4409 };
4410
4411 let active_item = match state.child_bounds.get(active_item.index) {
4412 Some(bounds) => {
4413 let mut scroll_offset = state.offset.borrow_mut();
4414
4415 match active_item.strategy {
4416 ScrollStrategy::FirstVisible => {
4417 if state.overflow.y == Overflow::Scroll {
4418 let child_height = bounds.size.height;
4419 let viewport_height = state.bounds.size.height;
4420 if child_height > viewport_height {
4421 scroll_offset.y = state.bounds.top() - bounds.top();
4422 } else if bounds.top() + scroll_offset.y < state.bounds.top() {
4423 scroll_offset.y = state.bounds.top() - bounds.top();
4424 } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
4425 scroll_offset.y = state.bounds.bottom() - bounds.bottom();
4426 }
4427 }
4428 }
4429 ScrollStrategy::Top => {
4430 scroll_offset.y = state.bounds.top() - bounds.top();
4431 }
4432 }
4433
4434 if state.overflow.x == Overflow::Scroll {
4435 let child_width = bounds.size.width;
4436 let viewport_width = state.bounds.size.width;
4437 if child_width > viewport_width {
4438 scroll_offset.x = state.bounds.left() - bounds.left();
4439 } else if bounds.left() + scroll_offset.x < state.bounds.left() {
4440 scroll_offset.x = state.bounds.left() - bounds.left();
4441 } else if bounds.right() + scroll_offset.x > state.bounds.right() {
4442 scroll_offset.x = state.bounds.right() - bounds.right();
4443 }
4444 }
4445 None
4446 }
4447 None => Some(active_item),
4448 };
4449 state.active_item = active_item;
4450 }
4451
4452 pub fn scroll_to_bottom(&self) {
4454 let mut state = self.0.borrow_mut();
4455 state.scroll_to_bottom = true;
4456 }
4457
4458 pub fn set_offset(&self, position: Point<Pixels>) {
4462 let state = self.0.borrow();
4463 *state.offset.borrow_mut() = position;
4464 }
4465
4466 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
4468 let ix = self.top_item();
4469 let state = self.0.borrow();
4470
4471 if let Some(child_bounds) = state.child_bounds.get(ix) {
4472 (
4473 ix,
4474 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
4475 )
4476 } else {
4477 (ix, px(0.))
4478 }
4479 }
4480
4481 pub fn logical_scroll_bottom(&self) -> (usize, Pixels) {
4483 let ix = self.bottom_item();
4484 let state = self.0.borrow();
4485
4486 if let Some(child_bounds) = state.child_bounds.get(ix) {
4487 (
4488 ix,
4489 child_bounds.bottom() + state.offset.borrow().y - state.bounds.bottom(),
4490 )
4491 } else {
4492 (ix, px(0.))
4493 }
4494 }
4495
4496 pub fn children_count(&self) -> usize {
4498 self.0.borrow().child_bounds.len()
4499 }
4500}
4501
4502#[cfg(test)]
4503mod tests {
4504 use super::*;
4505 use crate::{
4506 AnyWindowHandle, AppContext as _, Context, GestureTuning, InputEvent, Keystroke,
4507 MouseMoveEvent, TestAppContext, TouchEvent, TouchId, canvas, util::FluentBuilder as _,
4508 };
4509 use std::{cell::Cell, rc::Weak};
4510
4511 struct GroupHoverTestView {
4512 render_count: Rc<Cell<usize>>,
4513 anonymous_paint_count: Rc<Cell<usize>>,
4514 stateful_width: Rc<Cell<Pixels>>,
4515 }
4516
4517 impl Render for GroupHoverTestView {
4518 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4519 self.render_count.set(self.render_count.get() + 1);
4520 let anonymous_paint_count = self.anonymous_paint_count.clone();
4521 let stateful_width = self.stateful_width.clone();
4522 div().size_full().child(
4523 div()
4524 .ml(px(20.))
4525 .mt(px(20.))
4526 .size(px(50.))
4527 .relative()
4528 .group("hover-group")
4529 .child(
4530 div()
4531 .absolute()
4532 .size_full()
4533 .invisible()
4534 .group_hover("hover-group", |style| style.visible())
4535 .child(canvas(
4536 |_, _, _| {},
4537 move |_, _, _, _| {
4538 anonymous_paint_count.set(anonymous_paint_count.get() + 1)
4539 },
4540 )),
4541 )
4542 .child(
4543 div()
4544 .id("stateful-group-hover-target")
4545 .absolute()
4546 .top_0()
4547 .left_0()
4548 .size(px(10.))
4549 .group_hover("hover-group", |style| style.size(px(20.)))
4550 .child(canvas(
4551 move |bounds, _, _| stateful_width.set(bounds.size.width),
4552 |_, _, _, _| {},
4553 )),
4554 ),
4555 )
4556 }
4557 }
4558
4559 #[gpui::test]
4560 fn group_hover_styles_update_only_on_transitions(cx: &mut TestAppContext) {
4561 let render_count = Rc::new(Cell::new(0));
4562 let anonymous_paint_count = Rc::new(Cell::new(0));
4563 let stateful_width = Rc::new(Cell::new(px(0.)));
4564 let window = cx.add_window({
4565 let render_count = render_count.clone();
4566 let anonymous_paint_count = anonymous_paint_count.clone();
4567 let stateful_width = stateful_width.clone();
4568 move |_, _| GroupHoverTestView {
4569 render_count,
4570 anonymous_paint_count,
4571 stateful_width,
4572 }
4573 });
4574 let window = AnyWindowHandle::from(window);
4575
4576 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
4577 .unwrap();
4578 assert_eq!(anonymous_paint_count.get(), 0);
4579 assert_eq!(stateful_width.get(), px(10.));
4580
4581 let move_mouse = |cx: &mut TestAppContext, position| {
4582 cx.update_window(window, |_, window, cx| {
4583 window.simulate_mouse_move(position, cx)
4584 })
4585 .unwrap();
4586 };
4587
4588 let initial_render_count = render_count.get();
4589 move_mouse(cx, point(px(25.), px(25.)));
4590 assert_eq!(render_count.get(), initial_render_count + 1);
4591 assert_eq!(anonymous_paint_count.get(), 1);
4592 assert_eq!(stateful_width.get(), px(20.));
4593
4594 move_mouse(cx, point(px(30.), px(30.)));
4595 assert_eq!(render_count.get(), initial_render_count + 1);
4596 assert_eq!(anonymous_paint_count.get(), 1);
4597 assert_eq!(stateful_width.get(), px(20.));
4598
4599 move_mouse(cx, point(px(5.), px(5.)));
4600 assert_eq!(render_count.get(), initial_render_count + 2);
4601 assert_eq!(anonymous_paint_count.get(), 1);
4602 assert_eq!(stateful_width.get(), px(10.));
4603
4604 move_mouse(cx, point(px(10.), px(10.)));
4605 assert_eq!(render_count.get(), initial_render_count + 2);
4606 assert_eq!(anonymous_paint_count.get(), 1);
4607 assert_eq!(stateful_width.get(), px(10.));
4608 }
4609
4610 struct HoverListenerLayoutTestView {
4611 target_left: Pixels,
4612 hover_transitions: Rc<RefCell<Vec<bool>>>,
4613 }
4614
4615 impl Render for HoverListenerLayoutTestView {
4616 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4617 let hover_transitions = self.hover_transitions.clone();
4618 div().relative().size_full().child(
4619 div()
4620 .id("hover-target")
4621 .absolute()
4622 .left(self.target_left)
4623 .top_0()
4624 .size(px(20.))
4625 .on_click(|_, _, _| {})
4626 .on_hover(move |is_hovered, _, _| {
4627 hover_transitions.borrow_mut().push(*is_hovered);
4628 }),
4629 )
4630 }
4631 }
4632
4633 #[gpui::test]
4634 fn default_hover_listener_updates_when_layout_changes_under_stationary_mouse(
4635 cx: &mut TestAppContext,
4636 ) {
4637 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4638 let window = cx.add_window({
4639 let hover_transitions = hover_transitions.clone();
4640 move |_, _| HoverListenerLayoutTestView {
4641 target_left: px(40.),
4642 hover_transitions,
4643 }
4644 });
4645 let any_window = AnyWindowHandle::from(window);
4646
4647 cx.update_window(any_window, |_, window, cx| {
4648 window.draw(cx).clear(cx);
4649 window.simulate_mouse_move(point(px(10.), px(10.)), cx);
4650 })
4651 .unwrap();
4652 assert!(hover_transitions.borrow().is_empty());
4653
4654 window
4655 .update(cx, |view, _, cx| {
4656 view.target_left = px(0.);
4657 cx.notify();
4658 })
4659 .unwrap();
4660 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
4661 .unwrap();
4662 assert_eq!(*hover_transitions.borrow(), [true]);
4663
4664 window
4665 .update(cx, |view, _, cx| {
4666 view.target_left = px(40.);
4667 cx.notify();
4668 })
4669 .unwrap();
4670 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
4671 .unwrap();
4672 assert_eq!(*hover_transitions.borrow(), [true, false]);
4673 }
4674
4675 #[gpui::test]
4676 fn default_hover_listener_ends_after_key_press(cx: &mut TestAppContext) {
4677 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4678 let window = cx.add_window({
4679 let hover_transitions = hover_transitions.clone();
4680 move |_, _| HoverListenerLayoutTestView {
4681 target_left: px(0.),
4682 hover_transitions,
4683 }
4684 });
4685 let any_window = AnyWindowHandle::from(window);
4686
4687 cx.update_window(any_window, |_, window, cx| {
4688 window.draw(cx).clear(cx);
4689 window.simulate_mouse_move(point(px(10.), px(10.)), cx);
4690 })
4691 .unwrap();
4692 assert_eq!(*hover_transitions.borrow(), [true]);
4693
4694 key_down(cx, any_window, "a");
4695 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
4696 .unwrap();
4697 assert_eq!(*hover_transitions.borrow(), [true, false]);
4698 }
4699
4700 struct HoverListenerModeLayoutTestView {
4701 target_left: Pixels,
4702 hover_transitions: Rc<RefCell<Vec<bool>>>,
4703 }
4704
4705 impl Render for HoverListenerModeLayoutTestView {
4706 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4707 let hover_transitions = self.hover_transitions.clone();
4708 div().relative().size_full().child(
4709 div()
4710 .id("hover-target")
4711 .absolute()
4712 .left(self.target_left)
4713 .top_0()
4714 .size(px(20.))
4715 .hover_listener_mode(HoverListenerMode::InputModalityIndependent)
4716 .on_hover(move |is_hovered, _, _| {
4717 hover_transitions.borrow_mut().push(*is_hovered);
4718 }),
4719 )
4720 }
4721 }
4722
4723 #[gpui::test]
4724 fn input_modality_independent_hover_listener_updates_after_key_press(cx: &mut TestAppContext) {
4725 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4726 let window = cx.add_window({
4727 let hover_transitions = hover_transitions.clone();
4728 move |_, _| HoverListenerModeLayoutTestView {
4729 target_left: px(40.),
4730 hover_transitions,
4731 }
4732 });
4733 let any_window = AnyWindowHandle::from(window);
4734 let pointer_position = point(px(10.), px(10.));
4735
4736 cx.update_window(any_window, |_, window, cx| {
4737 window.draw(cx).clear(cx);
4738 window.simulate_mouse_move(pointer_position, cx);
4739 })
4740 .unwrap();
4741 assert!(hover_transitions.borrow().is_empty());
4742
4743 key_down(cx, any_window, "a");
4744 window
4745 .update(cx, |view, _, cx| {
4746 view.target_left = px(0.);
4747 cx.notify();
4748 })
4749 .unwrap();
4750 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
4751 .unwrap();
4752 assert_eq!(*hover_transitions.borrow(), [true]);
4753
4754 key_down(cx, any_window, "b");
4755 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
4756 .unwrap();
4757 assert_eq!(*hover_transitions.borrow(), [true]);
4758
4759 cx.update_window(any_window, |_, window, cx| {
4760 window.simulate_mouse_move(point(px(30.), px(10.)), cx);
4761 })
4762 .unwrap();
4763 assert_eq!(*hover_transitions.borrow(), [true, false]);
4764
4765 cx.update_window(any_window, |_, window, cx| {
4766 window.simulate_mouse_move(pointer_position, cx);
4767 window.dispatch_event(
4768 MouseExitEvent {
4769 position: pointer_position,
4770 ..Default::default()
4771 }
4772 .to_platform_input(),
4773 cx,
4774 );
4775 })
4776 .unwrap();
4777 assert_eq!(*hover_transitions.borrow(), [true, false, true, false]);
4778 }
4779
4780 #[gpui::test]
4781 fn default_hover_listener_remains_hovered_during_stationary_mouse_press(
4782 cx: &mut TestAppContext,
4783 ) {
4784 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4785 let window = cx.add_window({
4786 let hover_transitions = hover_transitions.clone();
4787 move |_, _| HoverListenerLayoutTestView {
4788 target_left: px(0.),
4789 hover_transitions,
4790 }
4791 });
4792 let any_window = AnyWindowHandle::from(window);
4793 let mouse_position = point(px(10.), px(10.));
4794
4795 cx.update_window(any_window, |_, window, cx| {
4796 window.draw(cx).clear(cx);
4797 window.simulate_mouse_move(mouse_position, cx);
4798 })
4799 .unwrap();
4800 assert_eq!(*hover_transitions.borrow(), [true]);
4801
4802 cx.update_window(any_window, |_, window, cx| {
4803 window.dispatch_event(
4804 MouseDownEvent {
4805 position: mouse_position,
4806 button: MouseButton::Left,
4807 modifiers: Default::default(),
4808 click_count: 1,
4809 first_mouse: false,
4810 }
4811 .to_platform_input(),
4812 cx,
4813 );
4814 window.draw(cx).clear(cx);
4815 })
4816 .unwrap();
4817 assert_eq!(*hover_transitions.borrow(), [true]);
4818
4819 cx.update_window(any_window, |_, window, cx| {
4820 window.dispatch_event(
4821 MouseUpEvent {
4822 position: mouse_position,
4823 button: MouseButton::Left,
4824 modifiers: Default::default(),
4825 click_count: 1,
4826 }
4827 .to_platform_input(),
4828 cx,
4829 );
4830 window.draw(cx).clear(cx);
4831 })
4832 .unwrap();
4833 assert_eq!(*hover_transitions.borrow(), [true]);
4834 }
4835
4836 struct TestTooltipView;
4837
4838 impl Render for TestTooltipView {
4839 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4840 div().w(px(20.)).h(px(20.)).child("tooltip")
4841 }
4842 }
4843
4844 type CapturedActiveTooltip = Rc<RefCell<Option<Weak<RefCell<Option<ActiveTooltip>>>>>>;
4845
4846 struct TooltipCaptureElement {
4847 child: AnyElement,
4848 captured_active_tooltip: CapturedActiveTooltip,
4849 }
4850
4851 impl IntoElement for TooltipCaptureElement {
4852 type Element = Self;
4853
4854 fn into_element(self) -> Self::Element {
4855 self
4856 }
4857 }
4858
4859 impl Element for TooltipCaptureElement {
4860 type RequestLayoutState = ();
4861 type PrepaintState = ();
4862
4863 fn id(&self) -> Option<ElementId> {
4864 None
4865 }
4866
4867 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
4868 None
4869 }
4870
4871 fn request_layout(
4872 &mut self,
4873 _id: Option<&GlobalElementId>,
4874 _inspector_id: Option<&InspectorElementId>,
4875 window: &mut Window,
4876 cx: &mut App,
4877 ) -> (LayoutId, Self::RequestLayoutState) {
4878 (self.child.request_layout(window, cx), ())
4879 }
4880
4881 fn prepaint(
4882 &mut self,
4883 _id: Option<&GlobalElementId>,
4884 _inspector_id: Option<&InspectorElementId>,
4885 _bounds: Bounds<Pixels>,
4886 _request_layout: &mut Self::RequestLayoutState,
4887 window: &mut Window,
4888 cx: &mut App,
4889 ) -> Self::PrepaintState {
4890 self.child.prepaint(window, cx);
4891 }
4892
4893 fn paint(
4894 &mut self,
4895 _id: Option<&GlobalElementId>,
4896 _inspector_id: Option<&InspectorElementId>,
4897 _bounds: Bounds<Pixels>,
4898 _request_layout: &mut Self::RequestLayoutState,
4899 _prepaint: &mut Self::PrepaintState,
4900 window: &mut Window,
4901 cx: &mut App,
4902 ) {
4903 self.child.paint(window, cx);
4904 window.with_global_id("target".into(), |global_id, window| {
4905 window.with_element_state::<InteractiveElementState, _>(
4906 global_id,
4907 |state, _window| {
4908 let state = state.unwrap();
4909 *self.captured_active_tooltip.borrow_mut() =
4910 state.active_tooltip.as_ref().map(Rc::downgrade);
4911 ((), state)
4912 },
4913 )
4914 });
4915 }
4916 }
4917
4918 struct TooltipOwner {
4919 captured_active_tooltip: CapturedActiveTooltip,
4920 show_delay_override: Option<Duration>,
4921 }
4922
4923 impl Render for TooltipOwner {
4924 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4925 TooltipCaptureElement {
4926 child: div()
4927 .size_full()
4928 .child(
4929 div()
4930 .id("target")
4931 .w(px(50.))
4932 .h(px(50.))
4933 .tooltip(|_, cx| cx.new(|_| TestTooltipView).into())
4934 .when_some(self.show_delay_override, |this, delay| {
4935 this.tooltip_show_delay(delay)
4936 }),
4937 )
4938 .into_any_element(),
4939 captured_active_tooltip: self.captured_active_tooltip.clone(),
4940 }
4941 }
4942 }
4943
4944 #[test]
4945 fn scroll_handle_aligns_wide_children_to_left_edge() {
4946 let handle = ScrollHandle::new();
4947 {
4948 let mut state = handle.0.borrow_mut();
4949 state.bounds = Bounds::new(point(px(0.), px(0.)), size(px(80.), px(20.)));
4950 state.child_bounds = vec![Bounds::new(point(px(25.), px(0.)), size(px(200.), px(20.)))];
4951 state.overflow.x = Overflow::Scroll;
4952 state.active_item = Some(ScrollActiveItem {
4953 index: 0,
4954 strategy: ScrollStrategy::default(),
4955 });
4956 }
4957
4958 handle.scroll_to_active_item();
4959
4960 assert_eq!(handle.offset().x, px(-25.));
4961 }
4962
4963 #[test]
4964 fn scroll_handle_aligns_tall_children_to_top_edge() {
4965 let handle = ScrollHandle::new();
4966 {
4967 let mut state = handle.0.borrow_mut();
4968 state.bounds = Bounds::new(point(px(0.), px(0.)), size(px(20.), px(80.)));
4969 state.child_bounds = vec![Bounds::new(point(px(0.), px(25.)), size(px(20.), px(200.)))];
4970 state.overflow.y = Overflow::Scroll;
4971 state.active_item = Some(ScrollActiveItem {
4972 index: 0,
4973 strategy: ScrollStrategy::default(),
4974 });
4975 }
4976
4977 handle.scroll_to_active_item();
4978
4979 assert_eq!(handle.offset().y, px(-25.));
4980 }
4981
4982 #[test]
4983 fn long_press_shows_tooltip_until_touch_ends() {
4984 let (mut test_app, any_window, captured_active_tooltip) = create_tooltip_owner_test(None);
4985 let active_tooltip = captured_active_tooltip
4986 .borrow()
4987 .clone()
4988 .unwrap()
4989 .upgrade()
4990 .unwrap();
4991 let touch_id = TouchId(1);
4992 let touch_position = point(px(49.), px(10.));
4993
4994 test_app
4995 .update_window(any_window, |_, window, cx| {
4996 window.dispatch_event(
4997 TouchEvent {
4998 id: touch_id,
4999 phase: TouchPhase::Started,
5000 position: touch_position,
5001 predicted_position: None,
5002 force: None,
5003 }
5004 .to_platform_input(),
5005 cx,
5006 );
5007 })
5008 .unwrap();
5009 test_app
5010 .dispatcher
5011 .advance_clock(GestureTuning::default().long_press_duration + Duration::from_millis(1));
5012 test_app.run_until_parked();
5013
5014 assert!(matches!(
5015 active_tooltip.borrow().as_ref(),
5016 Some(ActiveTooltip::Visible { .. })
5017 ));
5018
5019 let moved_position = point(px(55.), px(10.));
5020 test_app
5021 .update_window(any_window, |_, window, cx| {
5022 window.dispatch_event(
5023 TouchEvent {
5024 id: touch_id,
5025 phase: TouchPhase::Moved,
5026 position: moved_position,
5027 predicted_position: None,
5028 force: None,
5029 }
5030 .to_platform_input(),
5031 cx,
5032 );
5033 window.draw(cx).clear(cx);
5034 })
5035 .unwrap();
5036 assert!(matches!(
5037 active_tooltip.borrow().as_ref(),
5038 Some(ActiveTooltip::Visible { .. })
5039 ));
5040
5041 test_app
5042 .update_window(any_window, |_, window, cx| {
5043 window.dispatch_event(
5044 TouchEvent {
5045 id: touch_id,
5046 phase: TouchPhase::Ended,
5047 position: moved_position,
5048 predicted_position: None,
5049 force: None,
5050 }
5051 .to_platform_input(),
5052 cx,
5053 );
5054 })
5055 .unwrap();
5056
5057 assert!(active_tooltip.borrow().is_none());
5058 }
5059
5060 fn setup_tooltip_owner_test(
5061 show_delay_override: Option<Duration>,
5062 ) -> (
5063 TestAppContext,
5064 crate::AnyWindowHandle,
5065 CapturedActiveTooltip,
5066 ) {
5067 let (mut test_app, any_window, captured_active_tooltip) =
5068 create_tooltip_owner_test(show_delay_override);
5069
5070 test_app
5071 .update_window(any_window, |_, window, cx| {
5072 window.dispatch_event(
5073 MouseMoveEvent {
5074 position: point(px(10.), px(10.)),
5075 modifiers: Default::default(),
5076 pressed_button: None,
5077 }
5078 .to_platform_input(),
5079 cx,
5080 );
5081 })
5082 .unwrap();
5083
5084 test_app
5085 .update_window(any_window, |_, window, cx| {
5086 window.draw(cx).clear(cx);
5087 })
5088 .unwrap();
5089
5090 (test_app, any_window, captured_active_tooltip)
5091 }
5092
5093 fn create_tooltip_owner_test(
5094 show_delay_override: Option<Duration>,
5095 ) -> (
5096 TestAppContext,
5097 crate::AnyWindowHandle,
5098 CapturedActiveTooltip,
5099 ) {
5100 let mut test_app = TestAppContext::single();
5101 let captured_active_tooltip: CapturedActiveTooltip = Rc::new(RefCell::new(None));
5102 let window = test_app.add_window({
5103 let captured_active_tooltip = captured_active_tooltip.clone();
5104 move |_, _| TooltipOwner {
5105 captured_active_tooltip,
5106 show_delay_override,
5107 }
5108 });
5109 let any_window = window.into();
5110
5111 test_app
5112 .update_window(any_window, |_, window, cx| {
5113 window.draw(cx).clear(cx);
5114 })
5115 .unwrap();
5116
5117 (test_app, any_window, captured_active_tooltip)
5118 }
5119
5120 #[test]
5121 fn tooltip_waiting_for_show_is_released_when_its_owner_disappears() {
5122 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
5123
5124 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
5125 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
5126 assert!(matches!(
5127 active_tooltip.borrow().as_ref(),
5128 Some(ActiveTooltip::WaitingForShow { .. })
5129 ));
5130
5131 test_app
5132 .update_window(any_window, |_, window, _| {
5133 window.remove_window();
5134 })
5135 .unwrap();
5136 test_app.run_until_parked();
5137 drop(active_tooltip);
5138
5139 assert!(weak_active_tooltip.upgrade().is_none());
5140 }
5141
5142 #[test]
5143 fn tooltip_respects_custom_show_delay() {
5144 let extra_delay = Duration::from_secs(1);
5145 let show_delay_override = DEFAULT_TOOLTIP_SHOW_DELAY + extra_delay;
5146 let (test_app, _any_window, captured_active_tooltip) =
5147 setup_tooltip_owner_test(Some(show_delay_override));
5148
5149 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
5150 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
5151
5152 test_app
5153 .dispatcher
5154 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
5155 test_app.run_until_parked();
5156
5157 assert!(matches!(
5158 active_tooltip.borrow().as_ref(),
5159 Some(ActiveTooltip::WaitingForShow { .. })
5160 ));
5161
5162 test_app.dispatcher.advance_clock(extra_delay);
5163 test_app.run_until_parked();
5164
5165 assert!(matches!(
5166 active_tooltip.borrow().as_ref(),
5167 Some(ActiveTooltip::Visible { .. })
5168 ));
5169 }
5170
5171 #[test]
5172 fn tooltip_is_released_when_its_owner_disappears() {
5173 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
5174
5175 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
5176 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
5177
5178 test_app
5179 .dispatcher
5180 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
5181 test_app.run_until_parked();
5182
5183 assert!(matches!(
5184 active_tooltip.borrow().as_ref(),
5185 Some(ActiveTooltip::Visible { .. })
5186 ));
5187
5188 test_app
5189 .update_window(any_window, |_, window, _| {
5190 window.remove_window();
5191 })
5192 .unwrap();
5193 test_app.run_until_parked();
5194 drop(active_tooltip);
5195
5196 assert!(weak_active_tooltip.upgrade().is_none());
5197 }
5198
5199 #[test]
5200 fn tooltip_hides_after_mouse_leaves_origin() {
5201 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
5202
5203 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
5204 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
5205
5206 test_app
5207 .dispatcher
5208 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
5209 test_app.run_until_parked();
5210
5211 assert!(matches!(
5212 active_tooltip.borrow().as_ref(),
5213 Some(ActiveTooltip::Visible { .. })
5214 ));
5215
5216 test_app
5217 .update_window(any_window, |_, window, cx| {
5218 window.dispatch_event(
5219 MouseMoveEvent {
5220 position: point(px(75.), px(75.)),
5221 modifiers: Default::default(),
5222 pressed_button: None,
5223 }
5224 .to_platform_input(),
5225 cx,
5226 );
5227 })
5228 .unwrap();
5229
5230 assert!(active_tooltip.borrow().is_none());
5231 }
5232
5233 struct MouseDownOutOwner {
5234 mouse_down_out_count: Rc<RefCell<usize>>,
5235 }
5236
5237 impl Render for MouseDownOutOwner {
5238 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
5239 let mouse_down_out_count = self.mouse_down_out_count.clone();
5240 div()
5241 .size_full()
5242 .child(div().id("target").w(px(50.)).h(px(50.)).on_mouse_down_out(
5243 move |_, _, _| {
5244 *mouse_down_out_count.borrow_mut() += 1;
5245 },
5246 ))
5247 }
5248 }
5249
5250 #[test]
5251 fn mouse_down_out_is_suppressed_while_window_prompt_is_active() {
5252 let mut test_app = TestAppContext::single();
5253 let mouse_down_out_count = Rc::new(RefCell::new(0));
5254 let window = test_app.add_window({
5255 let mouse_down_out_count = mouse_down_out_count.clone();
5256 move |_, _| MouseDownOutOwner {
5257 mouse_down_out_count,
5258 }
5259 });
5260 let any_window: AnyWindowHandle = window.into();
5261
5262 fn dispatch_mouse_down_outside_target(
5263 test_app: &mut TestAppContext,
5264 any_window: AnyWindowHandle,
5265 ) {
5266 test_app
5267 .update_window(any_window, |_, window, cx| {
5268 window.dispatch_event(
5269 MouseDownEvent {
5270 position: point(px(75.), px(75.)),
5271 button: MouseButton::Left,
5272 modifiers: Default::default(),
5273 click_count: 1,
5274 first_mouse: false,
5275 }
5276 .to_platform_input(),
5277 cx,
5278 );
5279 })
5280 .unwrap();
5281 }
5282
5283 test_app
5284 .update_window(any_window, |_, window, cx| {
5285 window.draw(cx).clear(cx);
5286 })
5287 .unwrap();
5288
5289 dispatch_mouse_down_outside_target(&mut test_app, any_window);
5290 assert_eq!(
5291 *mouse_down_out_count.borrow(),
5292 1,
5293 "mouse down outside the element should fire mouse-down-out listeners"
5294 );
5295
5296 test_app
5297 .update_window(any_window, |_, window, cx| {
5298 cx.set_prompt_builder(crate::fallback_prompt_renderer);
5299 let _receiver =
5300 window.prompt(crate::PromptLevel::Warning, "message", None, &["Ok"], cx);
5301 assert!(window.has_active_prompt());
5302 window.draw(cx).clear(cx);
5303 })
5304 .unwrap();
5305
5306 dispatch_mouse_down_outside_target(&mut test_app, any_window);
5307 assert_eq!(
5308 *mouse_down_out_count.borrow(),
5309 1,
5310 "mouse down over an active prompt should not fire mouse-down-out listeners"
5311 );
5312 }
5313
5314 #[test]
5315 fn touch_tap_out_fires_once_after_release() {
5316 let mut test_app = TestAppContext::single();
5317 let mouse_down_out_count = Rc::new(RefCell::new(0));
5318 let window = test_app.add_window({
5319 let mouse_down_out_count = mouse_down_out_count.clone();
5320 move |_, _| MouseDownOutOwner {
5321 mouse_down_out_count,
5322 }
5323 });
5324 let any_window: AnyWindowHandle = window.into();
5325
5326 test_app
5327 .update_window(any_window, |_, window, cx| {
5328 window.draw(cx).clear(cx);
5329 let started = TouchEvent {
5330 id: TouchId(1),
5331 phase: TouchPhase::Started,
5332 position: point(px(75.), px(75.)),
5333 force: None,
5334 predicted_position: None,
5335 };
5336 window.dispatch_event(started.clone().to_platform_input(), cx);
5337 assert_eq!(*mouse_down_out_count.borrow(), 0);
5338 window.dispatch_event(
5339 TouchEvent {
5340 phase: TouchPhase::Ended,
5341 ..started
5342 }
5343 .to_platform_input(),
5344 cx,
5345 );
5346 })
5347 .unwrap();
5348
5349 assert_eq!(
5350 *mouse_down_out_count.borrow(),
5351 1,
5352 "a touch tap outside the element should use mouse-down-out listeners"
5353 );
5354 }
5355
5356 #[crate::test]
5357 fn touch_pan_and_cancel_do_not_fire_mouse_down_out(cx: &mut TestAppContext) {
5358 let mouse_down_out_count = Rc::new(RefCell::new(0));
5359 let window = cx.add_window({
5360 let mouse_down_out_count = mouse_down_out_count.clone();
5361 move |_, _| MouseDownOutOwner {
5362 mouse_down_out_count,
5363 }
5364 });
5365 cx.update_window(window.into(), |_, window, cx| {
5366 window.draw(cx).clear(cx);
5367 for (id, samples) in [
5368 (
5369 TouchId(1),
5370 [
5371 (TouchPhase::Started, 100.),
5372 (TouchPhase::Moved, 75.),
5373 (TouchPhase::Ended, 75.),
5374 ],
5375 ),
5376 (
5377 TouchId(2),
5378 [
5379 (TouchPhase::Started, 100.),
5380 (TouchPhase::Moved, 100.),
5381 (TouchPhase::Cancelled, 100.),
5382 ],
5383 ),
5384 ] {
5385 for (phase, vertical_position) in samples {
5386 window.dispatch_event(
5387 TouchEvent {
5388 id,
5389 phase,
5390 position: point(px(75.), px(vertical_position)),
5391 predicted_position: None,
5392 force: None,
5393 }
5394 .to_platform_input(),
5395 cx,
5396 );
5397 }
5398 }
5399 })
5400 .unwrap();
5401 assert_eq!(*mouse_down_out_count.borrow(), 0);
5402 }
5403
5404 #[test]
5405 fn test_accessibility_id_builder_writes_author_id() {
5406 let mut element = div()
5407 .id("buffer-font-size")
5408 .accessibility_id("settings.buffer-font-size");
5409 let mut node = accesskit::Node::new(accesskit::Role::SpinButton);
5410
5411 element.interactivity().write_a11y_info(&mut node);
5412
5413 assert_eq!(node.author_id(), Some("settings.buffer-font-size"));
5414 }
5415
5416 #[test]
5417 fn test_write_a11y_info_string_and_numeric_properties() {
5418 let mut interactivity = Interactivity::default();
5419 interactivity.aria.author_id = Some("settings.buffer-font-size".into());
5420 interactivity.aria.label = Some("Buffer Font Size".into());
5421 interactivity.aria.value = Some("15".into());
5422 interactivity.aria.placeholder = Some("Search".into());
5423 interactivity.aria.numeric_value = Some(15.0);
5424 interactivity.aria.min_numeric_value = Some(6.0);
5425 interactivity.aria.max_numeric_value = Some(72.0);
5426 interactivity.aria.numeric_value_step = Some(1.0);
5427
5428 let mut node = accesskit::Node::new(accesskit::Role::SpinButton);
5429 interactivity.write_a11y_info(&mut node);
5430
5431 assert_eq!(node.author_id(), Some("settings.buffer-font-size"));
5432 assert_eq!(node.label(), Some("Buffer Font Size"));
5433 assert_eq!(node.value(), Some("15"));
5434 assert_eq!(node.placeholder(), Some("Search"));
5435 assert_eq!(node.numeric_value(), Some(15.0));
5436 assert_eq!(node.min_numeric_value(), Some(6.0));
5437 assert_eq!(node.max_numeric_value(), Some(72.0));
5438 assert_eq!(node.numeric_value_step(), Some(1.0));
5439 }
5440
5441 struct KeyboardActivationTest {
5444 focus_a: FocusHandle,
5445 focus_b: FocusHandle,
5446 clicks: Rc<RefCell<Vec<&'static str>>>,
5447 }
5448
5449 impl Render for KeyboardActivationTest {
5450 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
5451 let clicks_a = self.clicks.clone();
5452 let clicks_b = self.clicks.clone();
5453 div()
5454 .size_full()
5455 .child(
5456 div()
5457 .id("a")
5458 .w(px(50.))
5459 .h(px(50.))
5460 .track_focus(&self.focus_a)
5461 .on_click(move |_, _, _| clicks_a.borrow_mut().push("a")),
5462 )
5463 .child(
5464 div()
5465 .id("b")
5466 .w(px(50.))
5467 .h(px(50.))
5468 .track_focus(&self.focus_b)
5469 .on_click(move |_, _, _| clicks_b.borrow_mut().push("b")),
5470 )
5471 }
5472 }
5473
5474 fn setup_keyboard_activation_test() -> (
5475 TestAppContext,
5476 AnyWindowHandle,
5477 Rc<RefCell<Vec<&'static str>>>,
5478 FocusHandle,
5479 FocusHandle,
5480 ) {
5481 let mut cx = TestAppContext::single();
5482 let (focus_a, focus_b) = cx.update(|cx| (cx.focus_handle(), cx.focus_handle()));
5483 let clicks: Rc<RefCell<Vec<&'static str>>> = Rc::new(RefCell::new(Vec::new()));
5484 let window = cx.add_window({
5485 let focus_a = focus_a.clone();
5486 let focus_b = focus_b.clone();
5487 let clicks = clicks.clone();
5488 move |_, _| KeyboardActivationTest {
5489 focus_a,
5490 focus_b,
5491 clicks,
5492 }
5493 });
5494 (cx, window.into(), clicks, focus_a, focus_b)
5495 }
5496
5497 fn focus_and_draw(cx: &mut TestAppContext, window: AnyWindowHandle, handle: &FocusHandle) {
5500 cx.update_window(window, |_, window, cx| window.focus(handle, cx))
5501 .unwrap();
5502 cx.run_until_parked();
5503 cx.update_window(window, |_, window, cx| {
5504 window.draw(cx).clear(cx);
5505 })
5506 .unwrap();
5507 }
5508
5509 fn key_down(cx: &mut TestAppContext, window: AnyWindowHandle, key: &str) {
5510 let os = cx.read(|cx| cx.platform_info().os);
5511 let keystroke = Keystroke::parse_with_os(key, os).unwrap();
5512 cx.update_window(window, |_, window, cx| {
5513 window.dispatch_event(
5514 KeyDownEvent {
5515 keystroke,
5516 is_held: false,
5517 prefer_character_input: false,
5518 }
5519 .to_platform_input(),
5520 cx,
5521 );
5522 })
5523 .unwrap();
5524 }
5525
5526 fn key_up(cx: &mut TestAppContext, window: AnyWindowHandle, key: &str) {
5527 let os = cx.read(|cx| cx.platform_info().os);
5528 let keystroke = Keystroke::parse_with_os(key, os).unwrap();
5529 cx.update_window(window, |_, window, cx| {
5530 window.dispatch_event(KeyUpEvent { keystroke }.to_platform_input(), cx);
5531 })
5532 .unwrap();
5533 }
5534
5535 #[test]
5537 fn keyboard_activation_fires_click_on_same_element() {
5538 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5539
5540 focus_and_draw(&mut cx, window, &focus_a);
5541 key_down(&mut cx, window, "enter");
5542 key_up(&mut cx, window, "enter");
5543
5544 assert_eq!(*clicks.borrow(), vec!["a"]);
5545 }
5546
5547 #[test]
5552 fn keyboard_activation_does_not_leak_across_focus_change() {
5553 let (mut cx, window, clicks, focus_a, focus_b) = setup_keyboard_activation_test();
5554
5555 focus_and_draw(&mut cx, window, &focus_a);
5557 key_down(&mut cx, window, "enter");
5558
5559 focus_and_draw(&mut cx, window, &focus_b);
5561 key_up(&mut cx, window, "enter");
5562
5563 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
5566 }
5567
5568 #[test]
5573 fn keyboard_activation_does_not_leak_when_focus_returns() {
5574 let (mut cx, window, clicks, focus_a, focus_b) = setup_keyboard_activation_test();
5575
5576 focus_and_draw(&mut cx, window, &focus_a);
5578 key_down(&mut cx, window, "enter");
5579
5580 focus_and_draw(&mut cx, window, &focus_b);
5583 focus_and_draw(&mut cx, window, &focus_a);
5584 key_up(&mut cx, window, "enter");
5585
5586 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
5588 }
5589
5590 #[test]
5595 fn keyboard_activation_cleared_by_intervening_key_release() {
5596 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5597
5598 focus_and_draw(&mut cx, window, &focus_a);
5599 key_down(&mut cx, window, "escape");
5600 key_down(&mut cx, window, "space");
5601 key_up(&mut cx, window, "escape");
5602 key_up(&mut cx, window, "space");
5603
5604 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
5605 }
5606
5607 #[test]
5611 fn keyboard_activation_does_not_distinguish_space_and_enter() {
5612 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5613
5614 focus_and_draw(&mut cx, window, &focus_a);
5615 key_down(&mut cx, window, "space");
5616 key_up(&mut cx, window, "enter");
5617
5618 assert_eq!(*clicks.borrow(), vec!["a"]);
5619 }
5620
5621 #[test]
5624 fn keyboard_activation_cleared_by_intervening_keydown() {
5625 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5626
5627 focus_and_draw(&mut cx, window, &focus_a);
5628 key_down(&mut cx, window, "enter");
5629 key_down(&mut cx, window, "a");
5630 key_up(&mut cx, window, "enter");
5631
5632 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
5633 }
5634
5635 #[test]
5638 fn keyboard_activation_ignores_modified_keys() {
5639 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5640
5641 focus_and_draw(&mut cx, window, &focus_a);
5642 key_down(&mut cx, window, "cmd-enter");
5643 key_up(&mut cx, window, "cmd-enter");
5644
5645 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
5646 }
5647
5648 struct TabGroupFocus {
5652 group_a: FocusHandle,
5653 item_a: FocusHandle,
5654 group_b: FocusHandle,
5655 item_b: FocusHandle,
5656 }
5657
5658 impl Render for TabGroupFocus {
5659 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5660 fn group(container: &FocusHandle, item: &FocusHandle) -> Div {
5661 div()
5662 .track_focus(container)
5663 .tab_group()
5664 .child(div().track_focus(item))
5665 }
5666 div()
5667 .child(group(&self.group_a, &self.item_a))
5668 .child(group(&self.group_b, &self.item_b))
5669 }
5670 }
5671
5672 #[test]
5676 fn focus_next_from_tab_group_container_enters_that_group() {
5677 let mut cx = TestAppContext::single();
5678 let (group_a, item_a, group_b, item_b) = cx.update(|cx| {
5679 (
5680 cx.focus_handle(),
5681 cx.focus_handle().tab_stop(true),
5682 cx.focus_handle(),
5683 cx.focus_handle().tab_stop(true),
5684 )
5685 });
5686 let window: AnyWindowHandle = cx
5687 .add_window({
5688 let (group_a, item_a, group_b, item_b) =
5689 (group_a, item_a, group_b.clone(), item_b.clone());
5690 move |_, _| TabGroupFocus {
5691 group_a,
5692 item_a,
5693 group_b,
5694 item_b,
5695 }
5696 })
5697 .into();
5698 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
5699 .unwrap();
5700
5701 let focused = cx
5703 .update_window(window, |_, window, cx| {
5704 window.focus(&group_b, cx);
5705 window.focus_next(cx);
5706 window.focused(cx).map(|handle| handle.id)
5707 })
5708 .unwrap();
5709
5710 assert_eq!(focused, Some(item_b.id));
5711 }
5712
5713 #[gpui::test]
5714 fn test_fractional_padding_does_not_make_a_fitting_container_scrollable(
5715 cx: &mut TestAppContext,
5716 ) {
5717 struct PaddedContainer {
5718 scroll_handle: ScrollHandle,
5719 }
5720
5721 impl Render for PaddedContainer {
5722 fn render(
5723 &mut self,
5724 _window: &mut Window,
5725 _cx: &mut Context<Self>,
5726 ) -> impl IntoElement {
5727 div().size_full().child(
5730 div()
5731 .id("container")
5732 .h(px(50.))
5733 .w(px(100.))
5734 .py(px(4.25))
5735 .overflow_y_scroll()
5736 .track_scroll(&self.scroll_handle)
5737 .child(div().w_full().h(px(42.))),
5738 )
5739 }
5740 }
5741
5742 let scroll_handle = ScrollHandle::new();
5743 let window: AnyWindowHandle = cx
5744 .add_window({
5745 let scroll_handle = scroll_handle.clone();
5746 move |_, _| PaddedContainer { scroll_handle }
5747 })
5748 .into();
5749 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
5750 .unwrap();
5751
5752 assert_eq!(scroll_handle.max_offset().y, px(0.));
5753 }
5754
5755 struct ContentSizedGrid;
5756
5757 impl Render for ContentSizedGrid {
5758 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
5759 let widths = [px(100.), px(200.), px(50.)];
5760 div().size_full().child(
5761 div()
5762 .w_full()
5763 .grid()
5764 .grid_cols_max_content(widths.len() as u16)
5765 .children(widths.into_iter().enumerate().map(|(index, width)| {
5766 div()
5767 .debug_selector(move || format!("cell-{index}"))
5768 .w(width)
5769 .h(px(10.))
5770 })),
5771 )
5772 }
5773 }
5774
5775 #[gpui::test]
5776 fn grid_cols_max_content_sizes_columns_to_their_content(cx: &mut TestAppContext) {
5777 let window = cx.add_window(|_, _| ContentSizedGrid);
5778 cx.update_window(window.into(), |_, window, cx| window.draw(cx).clear(cx))
5779 .unwrap();
5780
5781 let mut bounds = |selector: &'static str| {
5782 cx.update_window(window.into(), |_, window, _| {
5783 window.rendered_frame.debug_bounds.get(selector).copied()
5784 })
5785 .unwrap()
5786 .unwrap_or_else(|| panic!("{selector} was not rendered"))
5787 };
5788
5789 assert_eq!(bounds("cell-0").origin.x, px(0.));
5790 assert_eq!(bounds("cell-1").origin.x, px(100.));
5791 assert_eq!(bounds("cell-2").origin.x, px(300.));
5792 }
5793}