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 = f(&style, scroll_offset, hitbox, window, cx);
2450 (result, element_state)
2451 },
2452 )
2453 })
2454 },
2455 )
2456 }
2457
2458 fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
2459 self.hitbox_behavior != HitboxBehavior::Normal
2460 || self.window_control.is_some()
2461 || style.mouse_cursor.is_some()
2462 || self.group.is_some()
2463 || self.scroll_offset.is_some()
2464 || self.tracked_focus_handle.is_some()
2465 || self.hover_style.is_some()
2466 || self.group_hover_style.is_some()
2467 || self.hover_listener.is_some()
2468 || !self.mouse_up_listeners.is_empty()
2469 || !self.mouse_pressure_listeners.is_empty()
2470 || !self.mouse_down_listeners.is_empty()
2471 || !self.mouse_move_listeners.is_empty()
2472 || !self.mouse_exit_listeners.is_empty()
2473 || !self.file_drop_exit_listeners.is_empty()
2474 || !self.click_listeners.is_empty()
2475 || !self.aux_click_listeners.is_empty()
2476 || !self.scroll_listeners.is_empty()
2477 || self.has_pinch_listeners()
2478 || !self.back_gesture_listeners.is_empty()
2479 || self.drag_listener.is_some()
2480 || !self.drop_listeners.is_empty()
2481 || !self.drag_over_styles.is_empty()
2482 || self.tooltip_builder.is_some()
2483 || window.is_inspector_picking(cx)
2484 }
2485
2486 fn clamp_scroll_position(
2487 &self,
2488 bounds: Bounds<Pixels>,
2489 style: &Style,
2490 window: &mut Window,
2491 _cx: &mut App,
2492 ) -> Point<Pixels> {
2493 fn round_to_two_decimals(pixels: Pixels) -> Pixels {
2494 const ROUNDING_FACTOR: f32 = 100.0;
2495 (pixels * ROUNDING_FACTOR).round() / ROUNDING_FACTOR
2496 }
2497
2498 if let Some(scroll_offset) = self.scroll_offset.as_ref() {
2499 let mut scroll_to_bottom = false;
2500 let mut tracked_scroll_handle = self
2501 .tracked_scroll_handle
2502 .as_ref()
2503 .map(|handle| handle.0.borrow_mut());
2504 if let Some(mut scroll_handle_state) = tracked_scroll_handle.as_deref_mut() {
2505 scroll_handle_state.overflow = style.overflow;
2506 scroll_to_bottom = mem::take(&mut scroll_handle_state.scroll_to_bottom);
2507 }
2508
2509 let rem_size = window.rem_size();
2510 let padding = style
2515 .padding
2516 .to_pixels(bounds.size.into(), rem_size)
2517 .map(|edge| window.pixel_snap(*edge));
2518 let padding_size = size(padding.left + padding.right, padding.top + padding.bottom);
2519 let padded_content_size = self.content_size + padding_size;
2526 let scroll_max = Point::from(padded_content_size - bounds.size)
2527 .map(round_to_two_decimals)
2528 .max(&Default::default());
2529 let mut scroll_offset = scroll_offset.borrow_mut();
2532
2533 scroll_offset.x = scroll_offset.x.clamp(-scroll_max.x, px(0.));
2534 if scroll_to_bottom {
2535 scroll_offset.y = -scroll_max.y;
2536 } else {
2537 scroll_offset.y = scroll_offset.y.clamp(-scroll_max.y, px(0.));
2538 }
2539
2540 if let Some(mut scroll_handle_state) = tracked_scroll_handle {
2541 scroll_handle_state.max_offset = scroll_max;
2542 scroll_handle_state.bounds = bounds;
2543 }
2544
2545 *scroll_offset
2546 } else {
2547 Point::default()
2548 }
2549 }
2550
2551 pub fn paint(
2560 &mut self,
2561 global_id: Option<&GlobalElementId>,
2562 _inspector_id: Option<&InspectorElementId>,
2563 bounds: Bounds<Pixels>,
2564 hitbox: Option<&Hitbox>,
2565 window: &mut Window,
2566 cx: &mut App,
2567 f: impl FnOnce(&Style, &mut Window, &mut App),
2568 ) {
2569 self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(window));
2570 window.with_optional_element_state::<InteractiveElementState, _>(
2571 global_id,
2572 |element_state, window| {
2573 let mut element_state =
2574 element_state.map(|element_state| element_state.unwrap_or_default());
2575
2576 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
2577
2578 #[cfg(any(test, all(debug_assertions, feature = "test-support")))]
2579 if let Some(debug_selector) = &self.debug_selector {
2580 window
2581 .next_frame
2582 .record_debug_bounds(debug_selector.clone(), bounds);
2583 }
2584
2585 self.paint_hover_group_handler(window, cx);
2586
2587 if style.visibility == Visibility::Hidden {
2588 return ((), element_state);
2589 }
2590
2591 let mut tab_group = None;
2592 if self.tab_group {
2593 tab_group = self.tab_index;
2594 }
2595
2596 window.with_element_opacity(style.opacity, |window| {
2597 style.paint(bounds, window, cx, |window: &mut Window, cx: &mut App| {
2598 window.with_text_style(style.text_style().cloned(), |window| {
2599 window.with_content_mask(
2600 style.overflow_mask(bounds, window.rem_size()),
2601 |window| {
2602 window.with_tab_group(tab_group, |window| {
2603 if let Some(focus_handle) = &self.tracked_focus_handle {
2612 window.next_frame.tab_stops.insert(focus_handle);
2613 }
2614 if let Some(hitbox) = hitbox {
2615 #[cfg(debug_assertions)]
2616 self.paint_debug_info(
2617 global_id, hitbox, &style, window, cx,
2618 );
2619
2620 if let Some(drag) = cx.active_drag.as_ref() {
2621 if let Some(mouse_cursor) = drag.cursor_style {
2622 window.set_window_cursor_style(mouse_cursor);
2623 }
2624 } else {
2625 if let Some(mouse_cursor) = style.mouse_cursor {
2626 window.set_cursor_style(mouse_cursor, hitbox);
2627 }
2628 }
2629
2630 if let Some(group) = self.group.clone() {
2631 GroupHitboxes::push(group, hitbox.id, cx);
2632 }
2633
2634 if let Some(area) = self.window_control {
2635 window.insert_window_control_hitbox(
2636 area,
2637 hitbox.clone(),
2638 );
2639 }
2640
2641 self.paint_mouse_listeners(
2642 hitbox,
2643 element_state.as_mut(),
2644 window,
2645 cx,
2646 );
2647 self.paint_scroll_listener(hitbox, &style, window, cx);
2648 }
2649
2650 self.paint_keyboard_listeners(window, cx);
2651
2652 if window.a11y.is_active() {
2653 if let Some(global_id) = global_id {
2654 if !self.a11y_action_listeners.is_empty() {
2655 let node_id = global_id.accesskit_node_id();
2656 for (action, listener) in
2657 self.a11y_action_listeners.drain(..)
2658 {
2659 window.on_a11y_action(
2660 node_id, action, listener,
2661 );
2662 }
2663 }
2664 }
2665 }
2666
2667 f(&style, window, cx);
2668
2669 if let Some(_hitbox) = hitbox {
2670 #[cfg(any(feature = "inspector", debug_assertions))]
2671 window.insert_inspector_hitbox(
2672 _hitbox.id,
2673 _inspector_id,
2674 cx,
2675 );
2676
2677 if let Some(group) = self.group.as_ref() {
2678 GroupHitboxes::pop(group, cx);
2679 }
2680 }
2681 })
2682 },
2683 );
2684 });
2685 });
2686 });
2687
2688 ((), element_state)
2689 },
2690 );
2691 }
2692
2693 #[cfg(debug_assertions)]
2694 fn paint_debug_info(
2695 &self,
2696 global_id: Option<&GlobalElementId>,
2697 hitbox: &Hitbox,
2698 style: &Style,
2699 window: &mut Window,
2700 cx: &mut App,
2701 ) {
2702 use crate::{BorderStyle, TextAlign};
2703
2704 if let Some(global_id) = global_id
2705 && (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
2706 && hitbox.is_hovered(window)
2707 {
2708 const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
2709 let element_id = format!("{global_id:?}");
2710 let str_len = element_id.len();
2711
2712 let render_debug_text = |window: &mut Window| {
2713 if let Some(text) = window
2714 .text_system()
2715 .shape_text(
2716 element_id.into(),
2717 FONT_SIZE,
2718 &[window.text_style().to_run(str_len)],
2719 None,
2720 None,
2721 )
2722 .ok()
2723 .and_then(|mut text| text.pop())
2724 {
2725 text.paint(hitbox.origin, FONT_SIZE, TextAlign::Left, None, window, cx)
2726 .ok();
2727
2728 let text_bounds = crate::Bounds {
2729 origin: hitbox.origin,
2730 size: text.size(FONT_SIZE),
2731 };
2732 if let Some(source_location) = self.source_location
2733 && text_bounds.contains(&window.mouse_position())
2734 && window.modifiers().secondary(cx)
2735 {
2736 let secondary_held = window.modifiers().secondary(cx);
2737 window.on_key_event({
2738 move |e: &crate::ModifiersChangedEvent, _phase, window, cx| {
2739 if e.modifiers.secondary(cx) != secondary_held
2740 && text_bounds.contains(&window.mouse_position())
2741 {
2742 window.refresh();
2743 }
2744 }
2745 });
2746
2747 let was_hovered = hitbox.is_hovered(window);
2748 let current_view = window.current_view();
2749 window.on_mouse_event({
2750 let hitbox = hitbox.clone();
2751 move |_: &MouseMoveEvent, phase, window, cx| {
2752 if phase == DispatchPhase::Capture {
2753 let hovered = hitbox.is_hovered(window);
2754 if hovered != was_hovered {
2755 cx.notify(current_view)
2756 }
2757 }
2758 }
2759 });
2760
2761 window.on_mouse_event({
2762 let hitbox = hitbox.clone();
2763 move |e: &crate::MouseDownEvent, phase, window, cx| {
2764 if text_bounds.contains(&e.position)
2765 && phase.capture()
2766 && hitbox.is_hovered(window)
2767 {
2768 cx.stop_propagation();
2769 let Ok(dir) = std::env::current_dir() else {
2770 return;
2771 };
2772
2773 eprintln!(
2774 "This element was created at:\n{}:{}:{}",
2775 dir.join(source_location.file()).to_string_lossy(),
2776 source_location.line(),
2777 source_location.column()
2778 );
2779 }
2780 }
2781 });
2782 window.paint_quad(crate::outline(
2783 crate::Bounds {
2784 origin: hitbox.origin
2785 + crate::point(crate::px(0.), FONT_SIZE - px(2.)),
2786 size: crate::Size {
2787 width: text_bounds.size.width,
2788 height: crate::px(1.),
2789 },
2790 },
2791 crate::red(),
2792 BorderStyle::default(),
2793 ))
2794 }
2795 }
2796 };
2797
2798 window.with_text_style(
2799 Some(crate::TextStyleRefinement {
2800 color: Some(crate::red()),
2801 line_height: Some(FONT_SIZE.into()),
2802 background_color: Some(crate::white()),
2803 ..Default::default()
2804 }),
2805 render_debug_text,
2806 )
2807 }
2808 }
2809
2810 fn paint_mouse_listeners(
2811 &mut self,
2812 hitbox: &Hitbox,
2813 element_state: Option<&mut InteractiveElementState>,
2814 window: &mut Window,
2815 cx: &mut App,
2816 ) {
2817 let is_focused = self
2818 .tracked_focus_handle
2819 .as_ref()
2820 .map(|handle| handle.is_focused(window))
2821 .unwrap_or(false);
2822
2823 if let Some(focus_handle) = self.tracked_focus_handle.clone() {
2827 let hitbox = hitbox.clone();
2828 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, cx| {
2829 if phase == DispatchPhase::Bubble
2830 && hitbox.is_hovered(window)
2831 && !window.default_prevented()
2832 {
2833 window.focus(&focus_handle, cx);
2834 window.prevent_default();
2837 }
2838 });
2839 }
2840
2841 for listener in self.mouse_down_listeners.drain(..) {
2842 let hitbox = hitbox.clone();
2843 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
2844 listener(event, phase, &hitbox, window, cx);
2845 })
2846 }
2847
2848 for listener in self.mouse_up_listeners.drain(..) {
2849 let hitbox = hitbox.clone();
2850 window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
2851 listener(event, phase, &hitbox, window, cx);
2852 })
2853 }
2854
2855 for listener in self.mouse_pressure_listeners.drain(..) {
2856 let hitbox = hitbox.clone();
2857 window.on_mouse_event(move |event: &MousePressureEvent, phase, window, cx| {
2858 listener(event, phase, &hitbox, window, cx);
2859 })
2860 }
2861
2862 for listener in self.mouse_move_listeners.drain(..) {
2863 let hitbox = hitbox.clone();
2864 window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
2865 listener(event, phase, &hitbox, window, cx);
2866 })
2867 }
2868
2869 for listener in self.mouse_exit_listeners.drain(..) {
2870 let hitbox = hitbox.clone();
2871 window.on_mouse_event(move |event: &MouseExitEvent, phase, window, cx| {
2872 listener(event, phase, &hitbox, window, cx);
2873 })
2874 }
2875
2876 for listener in self.file_drop_exit_listeners.drain(..) {
2877 let hitbox = hitbox.clone();
2878 window.on_mouse_event(move |event: &FileDropEvent, phase, window, cx| {
2879 listener(event, phase, &hitbox, window, cx);
2880 })
2881 }
2882
2883 for listener in self.scroll_listeners.drain(..) {
2884 let hitbox = hitbox.clone();
2885 window.on_mouse_event(move |event: &ScrollEvent, phase, window, cx| {
2886 listener(event, phase, &hitbox, window, cx);
2887 })
2888 }
2889
2890 for listener in self.pinch_listeners.drain(..) {
2891 let hitbox = hitbox.clone();
2892 window.on_mouse_event(move |event: &PinchEvent, phase, window, cx| {
2893 listener(event, phase, &hitbox, window, cx);
2894 })
2895 }
2896
2897 for listener in self.back_gesture_listeners.drain(..) {
2898 let hitbox = hitbox.clone();
2899 window.on_mouse_event(move |event: &BackGestureEvent, phase, window, cx| {
2900 listener(event, phase, &hitbox, window, cx);
2901 })
2902 }
2903
2904 if self.hover_style.is_some()
2905 || self.base_style.mouse_cursor.is_some()
2906 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
2907 {
2908 let hitbox = hitbox.clone();
2909 let hover_state = self.hover_style.as_ref().and_then(|_| {
2910 element_state
2911 .as_ref()
2912 .and_then(|state| state.hover_state.as_ref())
2913 .cloned()
2914 });
2915 let current_view = window.current_view();
2916
2917 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2918 let hovered = hitbox.is_hovered(window);
2919 let was_hovered = hover_state
2920 .as_ref()
2921 .is_some_and(|state| state.borrow().element);
2922 if phase == DispatchPhase::Capture && hovered != was_hovered {
2923 if let Some(hover_state) = &hover_state {
2924 hover_state.borrow_mut().element = hovered;
2925 cx.notify(current_view);
2926 }
2927 }
2928 });
2929 }
2930
2931 if let Some(group_hover) = self.group_hover_style.as_ref() {
2932 if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
2933 let hover_state = element_state
2934 .as_ref()
2935 .and_then(|element| element.hover_state.as_ref())
2936 .cloned();
2937 let current_view = window.current_view();
2938
2939 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2940 let group_hovered = group_hitbox_id.is_hovered(window);
2941 let was_group_hovered = hover_state
2942 .as_ref()
2943 .is_some_and(|state| state.borrow().group);
2944 if phase == DispatchPhase::Capture && group_hovered != was_group_hovered {
2945 if let Some(hover_state) = &hover_state {
2946 hover_state.borrow_mut().group = group_hovered;
2947 cx.notify(current_view);
2948 }
2949 }
2950 });
2951 }
2952 }
2953
2954 let drag_cursor_style = self.base_style.as_ref().mouse_cursor;
2955
2956 let mut drag_listener = mem::take(&mut self.drag_listener);
2957 let drop_listeners = mem::take(&mut self.drop_listeners);
2958 let click_listeners = mem::take(&mut self.click_listeners);
2959 let aux_click_listeners = mem::take(&mut self.aux_click_listeners);
2960 let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
2961
2962 if !drop_listeners.is_empty() {
2963 let hitbox = hitbox.clone();
2964 window.on_mouse_event({
2965 move |_: &MouseUpEvent, phase, window, cx| {
2966 if let Some(drag) = &cx.active_drag
2967 && phase == DispatchPhase::Bubble
2968 && hitbox.is_hovered(window)
2969 {
2970 let drag_state_type = drag.value.as_ref().type_id();
2971 for (drop_state_type, listener) in &drop_listeners {
2972 if *drop_state_type == drag_state_type {
2973 let drag = cx
2974 .active_drag
2975 .take()
2976 .expect("checked for type drag state type above");
2977
2978 let mut can_drop = true;
2979 if let Some(predicate) = &can_drop_predicate {
2980 can_drop = predicate(drag.value.as_ref(), window, cx);
2981 }
2982
2983 if can_drop {
2984 listener(drag.value.as_ref(), window, cx);
2985 window.refresh();
2986 cx.stop_propagation();
2987 }
2988 }
2989 }
2990 }
2991 }
2992 });
2993 }
2994
2995 if let Some(element_state) = element_state {
2996 if !click_listeners.is_empty()
2997 || !aux_click_listeners.is_empty()
2998 || drag_listener.is_some()
2999 {
3000 let pending_mouse_down = element_state
3001 .pending_mouse_down
3002 .get_or_insert_with(Default::default)
3003 .clone();
3004
3005 let pending_keyboard_down = element_state
3006 .pending_keyboard_down
3007 .get_or_insert_with(Default::default)
3008 .clone();
3009
3010 let clicked_state = element_state
3011 .clicked_state
3012 .get_or_insert_with(Default::default)
3013 .clone();
3014
3015 window.on_mouse_event({
3016 let pending_mouse_down = pending_mouse_down.clone();
3017 let hitbox = hitbox.clone();
3018 let has_aux_click_listeners = !aux_click_listeners.is_empty();
3019 move |event: &MouseDownEvent, phase, window, _cx| {
3020 if phase == DispatchPhase::Bubble
3021 && (event.button == MouseButton::Left || has_aux_click_listeners)
3022 && hitbox.is_hovered(window)
3023 {
3024 *pending_mouse_down.borrow_mut() = Some(event.clone());
3025 window.refresh();
3026 }
3027 }
3028 });
3029
3030 window.on_mouse_event({
3031 let pending_mouse_down = pending_mouse_down.clone();
3032 let hitbox = hitbox.clone();
3033 move |event: &MouseMoveEvent, phase, window, cx| {
3034 if phase == DispatchPhase::Capture {
3035 return;
3036 }
3037
3038 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
3039 if let Some(mouse_down) = pending_mouse_down.clone()
3040 && !cx.has_active_drag()
3041 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
3042 && let Some(listener) = drag_listener.take()
3043 && mouse_down.button == MouseButton::Left
3044 {
3045 *clicked_state.borrow_mut() = ElementClickedState::default();
3046 let cursor_offset = event.position - hitbox.origin;
3047 let drag = (listener.render)(
3048 listener.value.as_ref(),
3049 cursor_offset,
3050 window,
3051 cx,
3052 );
3053 let external_payload_source =
3054 listener.external_payload.map(|external_payload| {
3055 let value = listener.value.clone();
3056 Box::new(move |window: &mut Window, cx: &mut App| {
3057 external_payload(value.as_ref(), window, cx)
3058 })
3059 as ExternalDragPayloadSource
3060 });
3061 cx.active_drag = Some(AnyDrag {
3062 view: drag,
3063 value: listener.value,
3064 cursor_offset,
3065 cursor_style: drag_cursor_style,
3066 external_payload_source,
3067 });
3068 pending_mouse_down.take();
3069 window.refresh();
3070 cx.stop_propagation();
3071 }
3072 }
3073 });
3074
3075 if is_focused {
3076 window.on_key_event({
3086 let pending_keyboard_down = pending_keyboard_down.clone();
3087 move |event: &KeyDownEvent, phase, window, _cx| {
3088 if phase.bubble() && !window.default_prevented() {
3089 let stroke = &event.keystroke;
3090 let is_activation_key = (stroke.key.eq("enter")
3091 || stroke.key.eq("space"))
3092 && !stroke.modifiers.modified();
3093 *pending_keyboard_down.borrow_mut() =
3094 is_activation_key.then_some(window.focus_generation);
3095 }
3096 }
3097 });
3098
3099 window.on_key_event({
3101 let click_listeners = click_listeners.clone();
3102 let hitbox = hitbox.clone();
3103 move |event: &KeyUpEvent, phase, window, cx| {
3104 if phase.bubble() && !window.default_prevented() {
3105 let stroke = &event.keystroke;
3106 let keyboard_button = if stroke.key.eq("enter") {
3107 Some(KeyboardButton::Enter)
3108 } else if stroke.key.eq("space") {
3109 Some(KeyboardButton::Space)
3110 } else {
3111 None
3112 };
3113
3114 if let Some(button) = keyboard_button
3115 && !stroke.modifiers.modified()
3116 {
3117 let pending =
3118 std::mem::take(&mut *pending_keyboard_down.borrow_mut());
3119 if pending != Some(window.focus_generation) {
3120 return;
3121 }
3122
3123 let click_event = ClickEvent::Keyboard(KeyboardClickEvent {
3124 button,
3125 bounds: hitbox.bounds,
3126 });
3127
3128 for listener in &click_listeners {
3129 listener(&click_event, window, cx);
3130 }
3131 } else {
3132 *pending_keyboard_down.borrow_mut() = None;
3136 }
3137 }
3138 }
3139 });
3140 }
3141
3142 window.on_mouse_event({
3143 let mut captured_mouse_down = None;
3144 let hitbox = hitbox.clone();
3145 move |event: &MouseUpEvent, phase, window, cx| match phase {
3146 DispatchPhase::Capture => {
3150 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
3151 if pending_mouse_down.is_some() && hitbox.is_hovered(window) {
3152 captured_mouse_down = pending_mouse_down.take();
3153 window.refresh();
3154 } else if pending_mouse_down.is_some() {
3155 pending_mouse_down.take();
3161 window.refresh();
3162 }
3163 }
3164 DispatchPhase::Bubble => {
3166 if let Some(mouse_down) = captured_mouse_down.take() {
3167 let btn = mouse_down.button;
3168
3169 let mouse_click = ClickEvent::Mouse(MouseClickEvent {
3170 down: mouse_down,
3171 up: event.clone(),
3172 });
3173
3174 match btn {
3175 MouseButton::Left => {
3176 for listener in &click_listeners {
3177 listener(&mouse_click, window, cx);
3178 }
3179 }
3180 _ => {
3181 for listener in &aux_click_listeners {
3182 listener(&mouse_click, window, cx);
3183 }
3184 }
3185 }
3186 }
3187 }
3188 }
3189 });
3190 }
3191
3192 if let Some(hover_listener) = self.hover_listener.take() {
3193 let was_hovered = element_state
3194 .hover_listener_state
3195 .get_or_insert_with(Default::default)
3196 .clone();
3197 let has_mouse_down = element_state
3198 .pending_mouse_down
3199 .get_or_insert_with(Default::default)
3200 .clone();
3201 let hover_listener = Rc::new(hover_listener);
3202 let hover_listener_state = was_hovered.clone();
3203 let update_hover = move |is_hovered: bool, window: &mut Window, cx: &mut App| {
3204 let mut was_hovered = hover_listener_state.borrow_mut();
3205 if is_hovered != *was_hovered {
3206 *was_hovered = is_hovered;
3207 drop(was_hovered);
3208 hover_listener(&is_hovered, window, cx);
3209 }
3210 };
3211 let hover_listener_mode = self.hover_listener_mode;
3212
3213 if has_mouse_down.borrow().is_none() {
3214 let is_hovered =
3215 !cx.has_active_drag() && hover_listener_mode.is_hovered(hitbox, window);
3216 if is_hovered != *was_hovered.borrow() {
3217 let update_hover = update_hover.clone();
3218 window.defer(cx, move |window, cx| {
3219 update_hover(is_hovered, window, cx);
3220 });
3221 }
3222 }
3223
3224 window.on_mouse_event({
3225 let update_hover = update_hover.clone();
3226 let hitbox = hitbox.clone();
3227 move |_: &MouseMoveEvent, phase, window, cx| {
3228 if phase == DispatchPhase::Bubble {
3229 let is_hovered = has_mouse_down.borrow().is_none()
3230 && !cx.has_active_drag()
3231 && hover_listener_mode.is_hovered(&hitbox, window);
3232 update_hover(is_hovered, window, cx);
3233 }
3234 }
3235 });
3236
3237 window.on_mouse_event(move |_: &MouseExitEvent, phase, window, cx| {
3240 if phase == DispatchPhase::Bubble {
3241 update_hover(false, window, cx);
3242 }
3243 });
3244 }
3245
3246 if let Some(tooltip_builder) = self.tooltip_builder.take() {
3247 let active_tooltip = element_state
3248 .active_tooltip
3249 .get_or_insert_with(Default::default)
3250 .clone();
3251 let pending_mouse_down = element_state
3252 .pending_mouse_down
3253 .get_or_insert_with(Default::default)
3254 .clone();
3255 let long_press_tooltip_active = element_state
3256 .long_press_tooltip_active
3257 .get_or_insert_with(Default::default)
3258 .clone();
3259
3260 let tooltip_is_hoverable = tooltip_builder.hoverable;
3261 let build_tooltip = Rc::new(move |window: &mut Window, cx: &mut App| {
3262 Some(((tooltip_builder.build)(window, cx), tooltip_is_hoverable))
3263 });
3264 let check_is_hovered_during_prepaint = Rc::new({
3266 let pending_mouse_down = pending_mouse_down.clone();
3267 let source_bounds = hitbox.bounds;
3268 move |window: &Window| {
3269 !window.last_input_was_keyboard()
3270 && pending_mouse_down.borrow().is_none()
3271 && source_bounds.contains(&window.mouse_position())
3272 }
3273 });
3274 let check_is_hovered = Rc::new({
3275 let hitbox = hitbox.clone();
3276 move |window: &Window| {
3277 pending_mouse_down.borrow().is_none() && hitbox.is_hovered(window)
3278 }
3279 });
3280 register_tooltip_mouse_handlers(
3281 &active_tooltip,
3282 self.tooltip_id,
3283 build_tooltip,
3284 check_is_hovered,
3285 check_is_hovered_during_prepaint,
3286 long_press_tooltip_active,
3287 self.tooltip_show_delay,
3288 window,
3289 );
3290 }
3291
3292 let active_state = element_state
3295 .clicked_state
3296 .get_or_insert_with(Default::default)
3297 .clone();
3298
3299 {
3300 let active_state = active_state.clone();
3301 window.on_mouse_event(move |_: &MouseUpEvent, phase, window, _cx| {
3302 if phase == DispatchPhase::Capture && active_state.borrow().is_clicked() {
3303 *active_state.borrow_mut() = ElementClickedState::default();
3304 window.refresh();
3305 }
3306 });
3307 }
3308
3309 {
3310 let active_group_hitbox = self
3311 .group_active_style
3312 .as_ref()
3313 .and_then(|group_active| GroupHitboxes::get(&group_active.group, cx));
3314 let hitbox = hitbox.clone();
3315 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _cx| {
3316 if phase == DispatchPhase::Bubble && !window.default_prevented() {
3317 let group_hovered = active_group_hitbox
3318 .is_some_and(|group_hitbox_id| group_hitbox_id.is_hovered(window));
3319 let element_hovered = hitbox.is_hovered(window);
3320 if group_hovered || element_hovered {
3321 *active_state.borrow_mut() = ElementClickedState {
3322 group: group_hovered,
3323 element: element_hovered,
3324 };
3325 window.refresh();
3326 }
3327 }
3328 });
3329 }
3330 }
3331 }
3332
3333 fn paint_keyboard_listeners(&mut self, window: &mut Window, _cx: &mut App) {
3334 let key_down_listeners = mem::take(&mut self.key_down_listeners);
3335 let key_up_listeners = mem::take(&mut self.key_up_listeners);
3336 let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners);
3337 let action_listeners = mem::take(&mut self.action_listeners);
3338 if let Some(context) = self.key_context.clone() {
3339 window.set_key_context(context);
3340 }
3341
3342 for listener in key_down_listeners {
3343 window.on_key_event(move |event: &KeyDownEvent, phase, window, cx| {
3344 listener(event, phase, window, cx);
3345 })
3346 }
3347
3348 for listener in key_up_listeners {
3349 window.on_key_event(move |event: &KeyUpEvent, phase, window, cx| {
3350 listener(event, phase, window, cx);
3351 })
3352 }
3353
3354 for listener in modifiers_changed_listeners {
3355 window.on_modifiers_changed(move |event: &ModifiersChangedEvent, window, cx| {
3356 listener(event, window, cx);
3357 })
3358 }
3359
3360 for (action_type, listener) in action_listeners {
3361 window.on_action(action_type, listener)
3362 }
3363 }
3364
3365 fn paint_hover_group_handler(&self, window: &mut Window, cx: &mut App) {
3366 let group_hitbox = self
3367 .group_hover_style
3368 .as_ref()
3369 .and_then(|group_hover| GroupHitboxes::get(&group_hover.group, cx));
3370
3371 if let Some(group_hitbox) = group_hitbox {
3372 let was_hovered = group_hitbox.is_hovered(window);
3373 let current_view = window.current_view();
3374 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
3375 let hovered = group_hitbox.is_hovered(window);
3376 if phase == DispatchPhase::Capture && hovered != was_hovered {
3377 cx.notify(current_view);
3378 }
3379 });
3380 }
3381 }
3382
3383 fn paint_scroll_listener(
3384 &self,
3385 hitbox: &Hitbox,
3386 style: &Style,
3387 window: &mut Window,
3388 _cx: &mut App,
3389 ) {
3390 if let Some(scroll_offset) = self.scroll_offset.clone() {
3391 let ongoing_scroll = self.ongoing_scroll.clone();
3392 let overflow = style.overflow;
3393 let allow_concurrent_scroll = style.allow_concurrent_scroll;
3394 let restrict_scroll_to_axis = style.restrict_scroll_to_axis;
3395 let line_height = window.line_height();
3396 let hitbox = hitbox.clone();
3397 let current_view = window.current_view();
3398 window.on_mouse_event(move |event: &ScrollEvent, phase, window, cx| {
3399 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
3400 let mut scroll_offset = scroll_offset.borrow_mut();
3401 let old_scroll_offset = *scroll_offset;
3402 let mut delta = event.delta.pixel_delta(line_height);
3403
3404 if restrict_scroll_to_axis
3405 && event.delta.precise()
3406 && let Some(ongoing_scroll) = &ongoing_scroll
3407 {
3408 ongoing_scroll
3409 .borrow_mut()
3410 .filter(&mut delta, event.touch_phase);
3411 }
3412
3413 let mut delta_x = match overflow.x {
3414 Overflow::Scroll if !delta.x.is_zero() => delta.x,
3415 Overflow::Scroll
3416 if !restrict_scroll_to_axis && overflow.y != Overflow::Scroll =>
3417 {
3418 delta.y
3419 }
3420 _ => Pixels::ZERO,
3421 };
3422 let mut delta_y = match overflow.y {
3423 Overflow::Scroll if !delta.y.is_zero() => delta.y,
3424 Overflow::Scroll
3425 if !restrict_scroll_to_axis && overflow.x != Overflow::Scroll =>
3426 {
3427 delta.x
3428 }
3429 _ => Pixels::ZERO,
3430 };
3431 if !allow_concurrent_scroll && !delta_x.is_zero() && !delta_y.is_zero() {
3432 if delta_x.abs() > delta_y.abs() {
3433 delta_y = Pixels::ZERO;
3434 } else {
3435 delta_x = Pixels::ZERO;
3436 }
3437 }
3438 scroll_offset.y += delta_y;
3439 scroll_offset.x += delta_x;
3440 if *scroll_offset != old_scroll_offset {
3441 cx.notify(current_view);
3442 }
3443 }
3444 });
3445 }
3446 }
3447
3448 pub fn compute_style(
3450 &self,
3451 global_id: Option<&GlobalElementId>,
3452 hitbox: Option<&Hitbox>,
3453 window: &mut Window,
3454 cx: &mut App,
3455 ) -> Style {
3456 window.with_optional_element_state(global_id, |element_state, window| {
3457 let mut element_state =
3458 element_state.map(|element_state| element_state.unwrap_or_default());
3459 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
3460 (style, element_state)
3461 })
3462 }
3463
3464 fn compute_style_internal(
3466 &self,
3467 hitbox: Option<&Hitbox>,
3468 element_state: Option<&mut InteractiveElementState>,
3469 window: &mut Window,
3470 cx: &mut App,
3471 ) -> Style {
3472 let mut style = Style::default();
3473 style.refine(&self.base_style);
3474
3475 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
3476 if let Some(in_focus_style) = self.in_focus_style.as_ref()
3477 && focus_handle.within_focused(window, cx)
3478 {
3479 style.refine(in_focus_style);
3480 }
3481
3482 if let Some(focus_style) = self.focus_style.as_ref()
3483 && focus_handle.is_focused(window)
3484 {
3485 style.refine(focus_style);
3486 }
3487
3488 if let Some(focus_visible_style) = self.focus_visible_style.as_ref()
3489 && focus_handle.is_focused(window)
3490 && window.last_input_was_keyboard()
3491 {
3492 style.refine(focus_visible_style);
3493 }
3494 }
3495
3496 if !cx.has_active_drag() {
3497 if let Some(group_hover) = self.group_hover_style.as_ref() {
3498 let is_group_hovered =
3499 if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
3500 !window.last_input_was_touch() && group_hitbox_id.is_hovered(window)
3501 } else if let Some(element_state) = element_state.as_ref() {
3502 !window.last_input_was_touch()
3503 && element_state
3504 .hover_state
3505 .as_ref()
3506 .map(|state| state.borrow().group)
3507 .unwrap_or(false)
3508 } else {
3509 false
3510 };
3511
3512 if is_group_hovered {
3513 style.refine(&group_hover.style);
3514 }
3515 }
3516
3517 if let Some(hover_style) = self.hover_style.as_ref() {
3518 let is_hovered = if let Some(hitbox) = hitbox {
3519 !window.last_input_was_touch() && hitbox.is_hovered(window)
3520 } else if let Some(element_state) = element_state.as_ref() {
3521 !window.last_input_was_touch()
3522 && element_state
3523 .hover_state
3524 .as_ref()
3525 .map(|state| state.borrow().element)
3526 .unwrap_or(false)
3527 } else {
3528 false
3529 };
3530
3531 if is_hovered {
3532 style.refine(hover_style);
3533 }
3534 }
3535 }
3536
3537 if let Some(hitbox) = hitbox {
3538 if let Some(drag) = cx.active_drag.take() {
3539 let mut can_drop = true;
3540 if let Some(can_drop_predicate) = &self.can_drop_predicate {
3541 can_drop = can_drop_predicate(drag.value.as_ref(), window, cx);
3542 }
3543
3544 if can_drop {
3545 for (state_type, group_drag_style) in &self.group_drag_over_styles {
3546 if let Some(group_hitbox_id) =
3547 GroupHitboxes::get(&group_drag_style.group, cx)
3548 && *state_type == drag.value.as_ref().type_id()
3549 && group_hitbox_id.is_hovered(window)
3550 {
3551 style.refine(&group_drag_style.style);
3552 }
3553 }
3554
3555 for (state_type, build_drag_over_style) in &self.drag_over_styles {
3556 if *state_type == drag.value.as_ref().type_id() && hitbox.is_hovered(window)
3557 {
3558 style.refine(&build_drag_over_style(drag.value.as_ref(), window, cx));
3559 }
3560 }
3561 }
3562
3563 style.mouse_cursor = drag.cursor_style;
3564 cx.active_drag = Some(drag);
3565 }
3566 }
3567
3568 if let Some(element_state) = element_state {
3569 let clicked_state = element_state
3570 .clicked_state
3571 .get_or_insert_with(Default::default)
3572 .borrow();
3573 if clicked_state.group
3574 && let Some(group) = self.group_active_style.as_ref()
3575 {
3576 style.refine(&group.style)
3577 }
3578
3579 if let Some(active_style) = self.active_style.as_ref()
3580 && clicked_state.element
3581 {
3582 style.refine(active_style)
3583 }
3584 }
3585
3586 style
3587 }
3588
3589 pub(crate) fn write_a11y_info(&self, node: &mut accesskit::Node) {
3590 if let Some(id) = &self.aria.author_id {
3591 node.set_author_id(id.to_string());
3592 }
3593 if let Some(label) = &self.aria.label {
3594 node.set_label(label.to_string());
3595 }
3596 if let Some(description) = &self.aria.description {
3597 node.set_description(description.to_string());
3598 }
3599 if let Some(keyshortcuts) = &self.aria.keyshortcuts {
3600 node.set_keyboard_shortcut(keyshortcuts.to_string());
3601 }
3602 if let Some(selected) = self.aria.selected {
3603 node.set_selected(selected);
3604 }
3605 if let Some(expanded) = self.aria.expanded {
3606 node.set_expanded(expanded);
3607 }
3608 if let Some(toggled) = self.aria.toggled {
3609 node.set_toggled(toggled);
3610 }
3611 if let Some(value) = self.aria.numeric_value {
3612 node.set_numeric_value(value);
3613 }
3614 if let Some(value) = self.aria.min_numeric_value {
3615 node.set_min_numeric_value(value);
3616 }
3617 if let Some(value) = self.aria.max_numeric_value {
3618 node.set_max_numeric_value(value);
3619 }
3620 if let Some(step) = self.aria.numeric_value_step {
3621 node.set_numeric_value_step(step);
3622 }
3623 if let Some(value) = &self.aria.value {
3624 node.set_value(value.to_string());
3625 }
3626 if let Some(placeholder) = &self.aria.placeholder {
3627 node.set_placeholder(placeholder.to_string());
3628 }
3629 if let Some(orientation) = self.aria.orientation {
3630 node.set_orientation(orientation);
3631 }
3632 if let Some(level) = self.aria.level {
3633 node.set_level(level);
3634 }
3635 if let Some(position) = self.aria.position_in_set {
3636 node.set_position_in_set(position);
3637 }
3638 if let Some(size) = self.aria.size_of_set {
3639 node.set_size_of_set(size);
3640 }
3641 if let Some(index) = self.aria.row_index {
3642 node.set_row_index(index);
3643 }
3644 if let Some(index) = self.aria.column_index {
3645 node.set_column_index(index);
3646 }
3647 if let Some(count) = self.aria.row_count {
3648 node.set_row_count(count);
3649 }
3650 if let Some(count) = self.aria.column_count {
3651 node.set_column_count(count);
3652 }
3653 if !self.click_listeners.is_empty() {
3654 node.add_action(accesskit::Action::Click);
3655 }
3656 if self.tracked_focus_handle.is_some() || self.focusable {
3657 node.add_action(accesskit::Action::Focus);
3658 }
3659 for (action, _) in &self.a11y_action_listeners {
3660 node.add_action(*action);
3661 }
3662 }
3663}
3664
3665#[derive(Default)]
3668pub struct InteractiveElementState {
3669 pub(crate) focus_handle: Option<FocusHandle>,
3670 pub(crate) clicked_state: Option<Rc<RefCell<ElementClickedState>>>,
3671 pub(crate) hover_state: Option<Rc<RefCell<ElementHoverState>>>,
3672 pub(crate) hover_listener_state: Option<Rc<RefCell<bool>>>,
3673 pub(crate) pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
3674 pub(crate) pending_keyboard_down: Option<Rc<RefCell<Option<u64>>>>,
3682 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
3683 ongoing_scroll: Option<Rc<RefCell<OngoingScroll>>>,
3684 pub(crate) active_tooltip: Option<Rc<RefCell<Option<ActiveTooltip>>>>,
3685 long_press_tooltip_active: Option<Rc<Cell<bool>>>,
3686}
3687
3688#[derive(Copy, Clone, Default, Eq, PartialEq)]
3690pub struct ElementClickedState {
3691 pub group: bool,
3693
3694 pub element: bool,
3696}
3697
3698impl ElementClickedState {
3699 fn is_clicked(&self) -> bool {
3700 self.group || self.element
3701 }
3702}
3703
3704#[derive(Copy, Clone, Default, Eq, PartialEq)]
3706pub struct ElementHoverState {
3707 pub group: bool,
3709
3710 pub element: bool,
3712}
3713
3714pub(crate) enum ActiveTooltip {
3715 WaitingForShow { _task: Task<()> },
3717 Visible {
3719 tooltip: AnyTooltip,
3720 is_hoverable: bool,
3721 },
3722 WaitingForHide {
3725 tooltip: AnyTooltip,
3726 _task: Task<()>,
3727 },
3728}
3729
3730pub(crate) fn clear_active_tooltip(
3731 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3732 window: &mut Window,
3733) {
3734 match active_tooltip.borrow_mut().take() {
3735 None => {}
3736 Some(ActiveTooltip::WaitingForShow { .. }) => {}
3737 Some(ActiveTooltip::Visible { .. }) => window.refresh(),
3738 Some(ActiveTooltip::WaitingForHide { .. }) => window.refresh(),
3739 }
3740}
3741
3742pub(crate) fn clear_active_tooltip_if_not_hoverable(
3743 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3744 window: &mut Window,
3745) {
3746 let should_clear = match active_tooltip.borrow().as_ref() {
3747 None => false,
3748 Some(ActiveTooltip::WaitingForShow { .. }) => false,
3749 Some(ActiveTooltip::Visible { is_hoverable, .. }) => !is_hoverable,
3750 Some(ActiveTooltip::WaitingForHide { .. }) => false,
3751 };
3752 if should_clear {
3753 active_tooltip.borrow_mut().take();
3754 window.refresh();
3755 }
3756}
3757
3758pub(crate) fn set_tooltip_on_window(
3759 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3760 window: &mut Window,
3761) -> Option<TooltipId> {
3762 let tooltip = match active_tooltip.borrow().as_ref() {
3763 None => return None,
3764 Some(ActiveTooltip::WaitingForShow { .. }) => return None,
3765 Some(ActiveTooltip::Visible { tooltip, .. }) => tooltip.clone(),
3766 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => tooltip.clone(),
3767 };
3768 Some(window.set_tooltip(tooltip))
3769}
3770
3771pub(crate) fn register_tooltip_mouse_handlers(
3772 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3773 tooltip_id: Option<TooltipId>,
3774 build_tooltip: Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3775 check_is_hovered: Rc<dyn Fn(&Window) -> bool>,
3776 check_is_hovered_during_prepaint: Rc<dyn Fn(&Window) -> bool>,
3777 long_press_tooltip_active: Rc<Cell<bool>>,
3778 show_delay: Option<Duration>,
3779 window: &mut Window,
3780) {
3781 let current_view = window.current_view();
3782 let show_delay = show_delay.unwrap_or(DEFAULT_TOOLTIP_SHOW_DELAY);
3783
3784 window.on_mouse_event({
3785 let active_tooltip = active_tooltip.clone();
3786 let build_tooltip = build_tooltip.clone();
3787 let check_is_hovered = check_is_hovered.clone();
3788 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3789 move |_: &MouseMoveEvent, phase, window, cx| {
3790 handle_tooltip_mouse_move(
3791 &active_tooltip,
3792 &build_tooltip,
3793 &check_is_hovered,
3794 &check_is_hovered_during_prepaint,
3795 tooltip_id,
3796 current_view,
3797 phase,
3798 show_delay,
3799 window,
3800 cx,
3801 )
3802 }
3803 });
3804
3805 window.on_mouse_event({
3806 let active_tooltip = active_tooltip.clone();
3807 move |_: &MouseDownEvent, _phase, window: &mut Window, _cx| {
3808 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3809 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3810 }
3811 }
3812 });
3813
3814 window.on_mouse_event({
3815 let active_tooltip = active_tooltip.clone();
3816 let build_tooltip = build_tooltip.clone();
3817 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3818 move |event: &LongPressEvent, phase, window, cx| {
3819 if !phase.bubble() {
3820 return;
3821 }
3822
3823 match event.phase {
3824 TouchPhase::Started if !window.default_prevented() && check_is_hovered(window) => {
3825 if show_tooltip(
3826 &active_tooltip,
3827 &build_tooltip,
3828 &check_is_hovered_during_prepaint,
3829 Some(long_press_tooltip_active.clone()),
3830 window,
3831 cx,
3832 ) {
3833 long_press_tooltip_active.set(true);
3834 window.prevent_default();
3835 cx.stop_propagation();
3836 }
3837 }
3838 TouchPhase::Ended | TouchPhase::Cancelled
3839 if long_press_tooltip_active.replace(false) =>
3840 {
3841 clear_active_tooltip(&active_tooltip, window);
3842 cx.stop_propagation();
3843 }
3844 TouchPhase::Started
3845 | TouchPhase::Moved
3846 | TouchPhase::Ended
3847 | TouchPhase::Cancelled => {}
3848 }
3849 }
3850 });
3851
3852 window.on_mouse_event({
3853 let active_tooltip = active_tooltip.clone();
3854 move |_: &ScrollEvent, _phase, window: &mut Window, _cx| {
3855 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3856 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3857 }
3858 }
3859 });
3860}
3861
3862fn handle_tooltip_mouse_move(
3874 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3875 build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3876 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3877 check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
3878 tooltip_id: Option<TooltipId>,
3879 current_view: EntityId,
3880 phase: DispatchPhase,
3881 show_delay: Duration,
3882 window: &mut Window,
3883 cx: &mut App,
3884) {
3885 enum Action {
3888 None,
3889 CancelShow,
3890 ScheduleShow,
3891 CheckVisible,
3892 }
3893
3894 let action = match active_tooltip.borrow().as_ref() {
3895 None => {
3896 let is_hovered = check_is_hovered(window);
3897 if is_hovered && phase.bubble() {
3898 Action::ScheduleShow
3899 } else {
3900 Action::None
3901 }
3902 }
3903 Some(ActiveTooltip::WaitingForShow { .. }) => {
3904 let is_hovered = check_is_hovered(window);
3905 if is_hovered {
3906 Action::None
3907 } else {
3908 Action::CancelShow
3909 }
3910 }
3911 Some(ActiveTooltip::Visible { is_hoverable, .. }) => {
3912 if phase.capture()
3913 && !check_is_hovered(window)
3914 && (!*is_hoverable
3915 || !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3916 {
3917 Action::CheckVisible
3918 } else {
3919 Action::None
3920 }
3921 }
3922 Some(ActiveTooltip::WaitingForHide { .. }) => {
3923 if phase.capture()
3924 && (check_is_hovered(window)
3925 || tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3926 {
3927 Action::CheckVisible
3928 } else {
3929 Action::None
3930 }
3931 }
3932 };
3933
3934 match action {
3935 Action::None => {}
3936 Action::CancelShow => {
3937 active_tooltip.borrow_mut().take();
3939 }
3940 Action::ScheduleShow => {
3941 let delayed_show_task = window.spawn(cx, {
3942 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3943 let build_tooltip = build_tooltip.clone();
3944 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3945 async move |cx| {
3946 cx.background_executor().timer(show_delay).await;
3947 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3948 return;
3949 };
3950 cx.update(|window, cx| {
3951 show_tooltip(
3952 &active_tooltip,
3953 &build_tooltip,
3954 &check_is_hovered_during_prepaint,
3955 None,
3956 window,
3957 cx,
3958 );
3959 })
3960 .ok();
3961 }
3962 });
3963 active_tooltip
3964 .borrow_mut()
3965 .replace(ActiveTooltip::WaitingForShow {
3966 _task: delayed_show_task,
3967 });
3968 }
3969 Action::CheckVisible => cx.notify(current_view),
3970 }
3971}
3972
3973fn show_tooltip(
3974 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3975 build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3976 check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
3977 long_press_tooltip_active: Option<Rc<Cell<bool>>>,
3978 window: &mut Window,
3979 cx: &mut App,
3980) -> bool {
3981 let new_tooltip = build_tooltip(window, cx).map(|(view, tooltip_is_hoverable)| {
3982 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3983 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3984 ActiveTooltip::Visible {
3985 tooltip: AnyTooltip {
3986 view,
3987 mouse_position: window.mouse_position(),
3988 check_visible_and_update: Rc::new(move |tooltip_bounds, window, cx| {
3989 if long_press_tooltip_active
3990 .as_ref()
3991 .is_some_and(|active| active.get())
3992 {
3993 return true;
3994 }
3995 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3996 return false;
3997 };
3998 handle_tooltip_check_visible_and_update(
3999 &active_tooltip,
4000 tooltip_is_hoverable,
4001 &check_is_hovered_during_prepaint,
4002 tooltip_bounds,
4003 window,
4004 cx,
4005 )
4006 }),
4007 },
4008 is_hoverable: tooltip_is_hoverable,
4009 }
4010 });
4011 let did_show = new_tooltip.is_some();
4012 *active_tooltip.borrow_mut() = new_tooltip;
4013 window.refresh();
4014 did_show
4015}
4016
4017fn handle_tooltip_check_visible_and_update(
4021 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
4022 tooltip_is_hoverable: bool,
4023 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
4024 tooltip_bounds: Bounds<Pixels>,
4025 window: &mut Window,
4026 cx: &mut App,
4027) -> bool {
4028 enum Action {
4031 None,
4032 Hide,
4033 ScheduleHide(AnyTooltip),
4034 CancelHide(AnyTooltip),
4035 }
4036
4037 let is_hovered = check_is_hovered(window)
4038 || (tooltip_is_hoverable && tooltip_bounds.contains(&window.mouse_position()));
4039 let action = match active_tooltip.borrow().as_ref() {
4040 Some(ActiveTooltip::Visible { tooltip, .. }) => {
4041 if is_hovered {
4042 Action::None
4043 } else {
4044 if tooltip_is_hoverable {
4045 Action::ScheduleHide(tooltip.clone())
4046 } else {
4047 Action::Hide
4048 }
4049 }
4050 }
4051 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => {
4052 if is_hovered {
4053 Action::CancelHide(tooltip.clone())
4054 } else {
4055 Action::None
4056 }
4057 }
4058 None | Some(ActiveTooltip::WaitingForShow { .. }) => Action::None,
4059 };
4060
4061 match action {
4062 Action::None => {}
4063 Action::Hide => clear_active_tooltip(active_tooltip, window),
4064 Action::ScheduleHide(tooltip) => {
4065 let delayed_hide_task = window.spawn(cx, {
4066 let weak_active_tooltip = Rc::downgrade(active_tooltip);
4067 async move |cx| {
4068 cx.background_executor()
4069 .timer(HOVERABLE_TOOLTIP_HIDE_DELAY)
4070 .await;
4071 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
4072 return;
4073 };
4074 if active_tooltip.borrow_mut().take().is_some() {
4075 cx.update(|window, _cx| window.refresh()).ok();
4076 }
4077 }
4078 });
4079 active_tooltip
4080 .borrow_mut()
4081 .replace(ActiveTooltip::WaitingForHide {
4082 tooltip,
4083 _task: delayed_hide_task,
4084 });
4085 }
4086 Action::CancelHide(tooltip) => {
4087 active_tooltip.borrow_mut().replace(ActiveTooltip::Visible {
4089 tooltip,
4090 is_hoverable: true,
4091 });
4092 }
4093 }
4094
4095 active_tooltip.borrow().is_some()
4096}
4097
4098#[derive(Default)]
4099pub(crate) struct GroupHitboxes(HashMap<SharedString, SmallVec<[HitboxId; 1]>>);
4100
4101impl Global for GroupHitboxes {}
4102
4103impl GroupHitboxes {
4104 pub fn get(name: &SharedString, cx: &mut App) -> Option<HitboxId> {
4105 cx.default_global::<Self>()
4106 .0
4107 .get(name)
4108 .and_then(|bounds_stack| bounds_stack.last())
4109 .cloned()
4110 }
4111
4112 pub fn push(name: SharedString, hitbox_id: HitboxId, cx: &mut App) {
4113 cx.default_global::<Self>()
4114 .0
4115 .entry(name)
4116 .or_default()
4117 .push(hitbox_id);
4118 }
4119
4120 pub fn pop(name: &SharedString, cx: &mut App) {
4121 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
4122 }
4123}
4124
4125pub struct Stateful<E> {
4127 pub(crate) element: E,
4128}
4129
4130impl<E> Styled for Stateful<E>
4131where
4132 E: Styled,
4133{
4134 fn style(&mut self) -> &mut StyleRefinement {
4135 self.element.style()
4136 }
4137}
4138
4139impl<E> StatefulInteractiveElement for Stateful<E>
4140where
4141 E: Element,
4142 Self: InteractiveElement,
4143{
4144}
4145
4146impl<E> InteractiveElement for Stateful<E>
4147where
4148 E: InteractiveElement,
4149{
4150 fn interactivity(&mut self) -> &mut Interactivity {
4151 self.element.interactivity()
4152 }
4153}
4154
4155impl<E> Element for Stateful<E>
4156where
4157 E: Element,
4158{
4159 type RequestLayoutState = E::RequestLayoutState;
4160 type PrepaintState = E::PrepaintState;
4161
4162 fn id(&self) -> Option<ElementId> {
4163 self.element.id()
4164 }
4165
4166 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
4167 self.element.source_location()
4168 }
4169
4170 fn a11y_role(&self) -> Option<accesskit::Role> {
4171 self.element.a11y_role()
4172 }
4173
4174 fn write_a11y_info(&self, node: &mut accesskit::Node) {
4175 self.element.write_a11y_info(node);
4176 }
4177
4178 fn a11y_synthetic_children(
4179 &mut self,
4180 prepaint: &mut Self::PrepaintState,
4181 builder: &mut crate::A11ySubtreeBuilder,
4182 ) {
4183 self.element.a11y_synthetic_children(prepaint, builder);
4184 }
4185
4186 fn request_layout(
4187 &mut self,
4188 id: Option<&GlobalElementId>,
4189 inspector_id: Option<&InspectorElementId>,
4190 window: &mut Window,
4191 cx: &mut App,
4192 ) -> (LayoutId, Self::RequestLayoutState) {
4193 self.element.request_layout(id, inspector_id, window, cx)
4194 }
4195
4196 fn prepaint(
4197 &mut self,
4198 id: Option<&GlobalElementId>,
4199 inspector_id: Option<&InspectorElementId>,
4200 bounds: Bounds<Pixels>,
4201 state: &mut Self::RequestLayoutState,
4202 window: &mut Window,
4203 cx: &mut App,
4204 ) -> E::PrepaintState {
4205 self.element
4206 .prepaint(id, inspector_id, bounds, state, window, cx)
4207 }
4208
4209 fn paint(
4210 &mut self,
4211 id: Option<&GlobalElementId>,
4212 inspector_id: Option<&InspectorElementId>,
4213 bounds: Bounds<Pixels>,
4214 request_layout: &mut Self::RequestLayoutState,
4215 prepaint: &mut Self::PrepaintState,
4216 window: &mut Window,
4217 cx: &mut App,
4218 ) {
4219 self.element.paint(
4220 id,
4221 inspector_id,
4222 bounds,
4223 request_layout,
4224 prepaint,
4225 window,
4226 cx,
4227 );
4228 }
4229}
4230
4231impl<E> IntoElement for Stateful<E>
4232where
4233 E: Element,
4234{
4235 type Element = Self;
4236
4237 fn into_element(self) -> Self::Element {
4238 self
4239 }
4240}
4241
4242impl<E> ParentElement for Stateful<E>
4243where
4244 E: ParentElement,
4245{
4246 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
4247 self.element.extend(elements)
4248 }
4249}
4250
4251#[derive(Clone)]
4254pub struct ScrollAnchor {
4255 handle: ScrollHandle,
4256 last_origin: Rc<RefCell<Point<Pixels>>>,
4257}
4258
4259impl ScrollAnchor {
4260 pub fn for_handle(handle: ScrollHandle) -> Self {
4262 Self {
4263 handle,
4264 last_origin: Default::default(),
4265 }
4266 }
4267 pub fn scroll_to(&self, window: &mut Window, _cx: &mut App) {
4269 let this = self.clone();
4270
4271 window.on_next_frame(move |_, _| {
4272 let viewport_bounds = this.handle.bounds();
4273 let self_bounds = *this.last_origin.borrow();
4274 this.handle.set_offset(viewport_bounds.origin - self_bounds);
4275 });
4276 }
4277}
4278
4279#[derive(Default, Debug)]
4280struct ScrollHandleState {
4281 offset: Rc<RefCell<Point<Pixels>>>,
4282 ongoing_scroll: Rc<RefCell<OngoingScroll>>,
4283 bounds: Bounds<Pixels>,
4284 max_offset: Point<Pixels>,
4285 child_bounds: Vec<Bounds<Pixels>>,
4286 scroll_to_bottom: bool,
4287 overflow: Point<Overflow>,
4288 active_item: Option<ScrollActiveItem>,
4289}
4290
4291#[derive(Default, Debug, Clone, Copy)]
4292struct ScrollActiveItem {
4293 index: usize,
4294 strategy: ScrollStrategy,
4295}
4296
4297#[derive(Default, Debug, Clone, Copy)]
4298enum ScrollStrategy {
4299 #[default]
4300 FirstVisible,
4301 Top,
4302}
4303
4304#[derive(Clone, Debug)]
4308pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
4309
4310impl Default for ScrollHandle {
4311 fn default() -> Self {
4312 Self::new()
4313 }
4314}
4315
4316impl ScrollHandle {
4317 pub fn new() -> Self {
4319 Self(Rc::default())
4320 }
4321
4322 pub fn offset(&self) -> Point<Pixels> {
4324 *self.0.borrow().offset.borrow()
4325 }
4326
4327 pub fn max_offset(&self) -> Point<Pixels> {
4329 self.0.borrow().max_offset
4330 }
4331
4332 pub fn top_item(&self) -> usize {
4334 let state = self.0.borrow();
4335 let top = state.bounds.top() - state.offset.borrow().y;
4336
4337 match state.child_bounds.binary_search_by(|bounds| {
4338 if top < bounds.top() {
4339 Ordering::Greater
4340 } else if top > bounds.bottom() {
4341 Ordering::Less
4342 } else {
4343 Ordering::Equal
4344 }
4345 }) {
4346 Ok(ix) => ix,
4347 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
4348 }
4349 }
4350
4351 pub fn bottom_item(&self) -> usize {
4353 let state = self.0.borrow();
4354 let bottom = state.bounds.bottom() - state.offset.borrow().y;
4355
4356 match state.child_bounds.binary_search_by(|bounds| {
4357 if bottom < bounds.top() {
4358 Ordering::Greater
4359 } else if bottom > bounds.bottom() {
4360 Ordering::Less
4361 } else {
4362 Ordering::Equal
4363 }
4364 }) {
4365 Ok(ix) => ix,
4366 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
4367 }
4368 }
4369
4370 pub fn bounds(&self) -> Bounds<Pixels> {
4372 self.0.borrow().bounds
4373 }
4374
4375 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
4377 self.0.borrow().child_bounds.get(ix).cloned()
4378 }
4379
4380 pub fn scroll_to_item(&self, ix: usize) {
4382 let mut state = self.0.borrow_mut();
4383 state.active_item = Some(ScrollActiveItem {
4384 index: ix,
4385 strategy: ScrollStrategy::default(),
4386 });
4387 }
4388
4389 pub fn scroll_to_top_of_item(&self, ix: usize) {
4392 let mut state = self.0.borrow_mut();
4393 state.active_item = Some(ScrollActiveItem {
4394 index: ix,
4395 strategy: ScrollStrategy::Top,
4396 });
4397 }
4398
4399 fn scroll_to_active_item(&self) {
4403 let mut state = self.0.borrow_mut();
4404
4405 let Some(active_item) = state.active_item else {
4406 return;
4407 };
4408
4409 let active_item = match state.child_bounds.get(active_item.index) {
4410 Some(bounds) => {
4411 let mut scroll_offset = state.offset.borrow_mut();
4412
4413 match active_item.strategy {
4414 ScrollStrategy::FirstVisible => {
4415 if state.overflow.y == Overflow::Scroll {
4416 let child_height = bounds.size.height;
4417 let viewport_height = state.bounds.size.height;
4418 if child_height > viewport_height {
4419 scroll_offset.y = state.bounds.top() - bounds.top();
4420 } else if bounds.top() + scroll_offset.y < state.bounds.top() {
4421 scroll_offset.y = state.bounds.top() - bounds.top();
4422 } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
4423 scroll_offset.y = state.bounds.bottom() - bounds.bottom();
4424 }
4425 }
4426 }
4427 ScrollStrategy::Top => {
4428 scroll_offset.y = state.bounds.top() - bounds.top();
4429 }
4430 }
4431
4432 if state.overflow.x == Overflow::Scroll {
4433 let child_width = bounds.size.width;
4434 let viewport_width = state.bounds.size.width;
4435 if child_width > viewport_width {
4436 scroll_offset.x = state.bounds.left() - bounds.left();
4437 } else if bounds.left() + scroll_offset.x < state.bounds.left() {
4438 scroll_offset.x = state.bounds.left() - bounds.left();
4439 } else if bounds.right() + scroll_offset.x > state.bounds.right() {
4440 scroll_offset.x = state.bounds.right() - bounds.right();
4441 }
4442 }
4443 None
4444 }
4445 None => Some(active_item),
4446 };
4447 state.active_item = active_item;
4448 }
4449
4450 pub fn scroll_to_bottom(&self) {
4452 let mut state = self.0.borrow_mut();
4453 state.scroll_to_bottom = true;
4454 }
4455
4456 pub fn set_offset(&self, mut position: Point<Pixels>) {
4460 let state = self.0.borrow();
4461 *state.offset.borrow_mut() = position;
4462 }
4463
4464 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
4466 let ix = self.top_item();
4467 let state = self.0.borrow();
4468
4469 if let Some(child_bounds) = state.child_bounds.get(ix) {
4470 (
4471 ix,
4472 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
4473 )
4474 } else {
4475 (ix, px(0.))
4476 }
4477 }
4478
4479 pub fn logical_scroll_bottom(&self) -> (usize, Pixels) {
4481 let ix = self.bottom_item();
4482 let state = self.0.borrow();
4483
4484 if let Some(child_bounds) = state.child_bounds.get(ix) {
4485 (
4486 ix,
4487 child_bounds.bottom() + state.offset.borrow().y - state.bounds.bottom(),
4488 )
4489 } else {
4490 (ix, px(0.))
4491 }
4492 }
4493
4494 pub fn children_count(&self) -> usize {
4496 self.0.borrow().child_bounds.len()
4497 }
4498}
4499
4500#[cfg(test)]
4501mod tests {
4502 use super::*;
4503 use crate::{
4504 AnyWindowHandle, AppContext as _, Context, GestureTuning, InputEvent, Keystroke,
4505 MouseMoveEvent, TestAppContext, TouchEvent, TouchId, canvas, util::FluentBuilder as _,
4506 };
4507 use std::{cell::Cell, rc::Weak};
4508
4509 struct GroupHoverTestView {
4510 render_count: Rc<Cell<usize>>,
4511 anonymous_paint_count: Rc<Cell<usize>>,
4512 stateful_width: Rc<Cell<Pixels>>,
4513 }
4514
4515 impl Render for GroupHoverTestView {
4516 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4517 self.render_count.set(self.render_count.get() + 1);
4518 let anonymous_paint_count = self.anonymous_paint_count.clone();
4519 let stateful_width = self.stateful_width.clone();
4520 div().size_full().child(
4521 div()
4522 .ml(px(20.))
4523 .mt(px(20.))
4524 .size(px(50.))
4525 .relative()
4526 .group("hover-group")
4527 .child(
4528 div()
4529 .absolute()
4530 .size_full()
4531 .invisible()
4532 .group_hover("hover-group", |style| style.visible())
4533 .child(canvas(
4534 |_, _, _| {},
4535 move |_, _, _, _| {
4536 anonymous_paint_count.set(anonymous_paint_count.get() + 1)
4537 },
4538 )),
4539 )
4540 .child(
4541 div()
4542 .id("stateful-group-hover-target")
4543 .absolute()
4544 .top_0()
4545 .left_0()
4546 .size(px(10.))
4547 .group_hover("hover-group", |style| style.size(px(20.)))
4548 .child(canvas(
4549 move |bounds, _, _| stateful_width.set(bounds.size.width),
4550 |_, _, _, _| {},
4551 )),
4552 ),
4553 )
4554 }
4555 }
4556
4557 #[gpui::test]
4558 fn group_hover_styles_update_only_on_transitions(cx: &mut TestAppContext) {
4559 let render_count = Rc::new(Cell::new(0));
4560 let anonymous_paint_count = Rc::new(Cell::new(0));
4561 let stateful_width = Rc::new(Cell::new(px(0.)));
4562 let window = cx.add_window({
4563 let render_count = render_count.clone();
4564 let anonymous_paint_count = anonymous_paint_count.clone();
4565 let stateful_width = stateful_width.clone();
4566 move |_, _| GroupHoverTestView {
4567 render_count,
4568 anonymous_paint_count,
4569 stateful_width,
4570 }
4571 });
4572 let window = AnyWindowHandle::from(window);
4573
4574 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
4575 .unwrap();
4576 assert_eq!(anonymous_paint_count.get(), 0);
4577 assert_eq!(stateful_width.get(), px(10.));
4578
4579 let move_mouse = |cx: &mut TestAppContext, position| {
4580 cx.update_window(window, |_, window, cx| {
4581 window.simulate_mouse_move(position, cx)
4582 })
4583 .unwrap();
4584 };
4585
4586 let initial_render_count = render_count.get();
4587 move_mouse(cx, point(px(25.), px(25.)));
4588 assert_eq!(render_count.get(), initial_render_count + 1);
4589 assert_eq!(anonymous_paint_count.get(), 1);
4590 assert_eq!(stateful_width.get(), px(20.));
4591
4592 move_mouse(cx, point(px(30.), px(30.)));
4593 assert_eq!(render_count.get(), initial_render_count + 1);
4594 assert_eq!(anonymous_paint_count.get(), 1);
4595 assert_eq!(stateful_width.get(), px(20.));
4596
4597 move_mouse(cx, point(px(5.), px(5.)));
4598 assert_eq!(render_count.get(), initial_render_count + 2);
4599 assert_eq!(anonymous_paint_count.get(), 1);
4600 assert_eq!(stateful_width.get(), px(10.));
4601
4602 move_mouse(cx, point(px(10.), px(10.)));
4603 assert_eq!(render_count.get(), initial_render_count + 2);
4604 assert_eq!(anonymous_paint_count.get(), 1);
4605 assert_eq!(stateful_width.get(), px(10.));
4606 }
4607
4608 struct HoverListenerLayoutTestView {
4609 target_left: Pixels,
4610 hover_transitions: Rc<RefCell<Vec<bool>>>,
4611 }
4612
4613 impl Render for HoverListenerLayoutTestView {
4614 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4615 let hover_transitions = self.hover_transitions.clone();
4616 div().relative().size_full().child(
4617 div()
4618 .id("hover-target")
4619 .absolute()
4620 .left(self.target_left)
4621 .top_0()
4622 .size(px(20.))
4623 .on_click(|_, _, _| {})
4624 .on_hover(move |is_hovered, _, _| {
4625 hover_transitions.borrow_mut().push(*is_hovered);
4626 }),
4627 )
4628 }
4629 }
4630
4631 #[gpui::test]
4632 fn default_hover_listener_updates_when_layout_changes_under_stationary_mouse(
4633 cx: &mut TestAppContext,
4634 ) {
4635 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4636 let window = cx.add_window({
4637 let hover_transitions = hover_transitions.clone();
4638 move |_, _| HoverListenerLayoutTestView {
4639 target_left: px(40.),
4640 hover_transitions,
4641 }
4642 });
4643 let any_window = AnyWindowHandle::from(window);
4644
4645 cx.update_window(any_window, |_, window, cx| {
4646 window.draw(cx).clear(cx);
4647 window.simulate_mouse_move(point(px(10.), px(10.)), cx);
4648 })
4649 .unwrap();
4650 assert!(hover_transitions.borrow().is_empty());
4651
4652 window
4653 .update(cx, |view, _, cx| {
4654 view.target_left = px(0.);
4655 cx.notify();
4656 })
4657 .unwrap();
4658 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
4659 .unwrap();
4660 assert_eq!(*hover_transitions.borrow(), [true]);
4661
4662 window
4663 .update(cx, |view, _, cx| {
4664 view.target_left = px(40.);
4665 cx.notify();
4666 })
4667 .unwrap();
4668 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
4669 .unwrap();
4670 assert_eq!(*hover_transitions.borrow(), [true, false]);
4671 }
4672
4673 #[gpui::test]
4674 fn default_hover_listener_ends_after_key_press(cx: &mut TestAppContext) {
4675 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4676 let window = cx.add_window({
4677 let hover_transitions = hover_transitions.clone();
4678 move |_, _| HoverListenerLayoutTestView {
4679 target_left: px(0.),
4680 hover_transitions,
4681 }
4682 });
4683 let any_window = AnyWindowHandle::from(window);
4684
4685 cx.update_window(any_window, |_, window, cx| {
4686 window.draw(cx).clear(cx);
4687 window.simulate_mouse_move(point(px(10.), px(10.)), cx);
4688 })
4689 .unwrap();
4690 assert_eq!(*hover_transitions.borrow(), [true]);
4691
4692 key_down(cx, any_window, "a");
4693 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
4694 .unwrap();
4695 assert_eq!(*hover_transitions.borrow(), [true, false]);
4696 }
4697
4698 struct HoverListenerModeLayoutTestView {
4699 target_left: Pixels,
4700 hover_transitions: Rc<RefCell<Vec<bool>>>,
4701 }
4702
4703 impl Render for HoverListenerModeLayoutTestView {
4704 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4705 let hover_transitions = self.hover_transitions.clone();
4706 div().relative().size_full().child(
4707 div()
4708 .id("hover-target")
4709 .absolute()
4710 .left(self.target_left)
4711 .top_0()
4712 .size(px(20.))
4713 .hover_listener_mode(HoverListenerMode::InputModalityIndependent)
4714 .on_hover(move |is_hovered, _, _| {
4715 hover_transitions.borrow_mut().push(*is_hovered);
4716 }),
4717 )
4718 }
4719 }
4720
4721 #[gpui::test]
4722 fn input_modality_independent_hover_listener_updates_after_key_press(cx: &mut TestAppContext) {
4723 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4724 let window = cx.add_window({
4725 let hover_transitions = hover_transitions.clone();
4726 move |_, _| HoverListenerModeLayoutTestView {
4727 target_left: px(40.),
4728 hover_transitions,
4729 }
4730 });
4731 let any_window = AnyWindowHandle::from(window);
4732 let pointer_position = point(px(10.), px(10.));
4733
4734 cx.update_window(any_window, |_, window, cx| {
4735 window.draw(cx).clear(cx);
4736 window.simulate_mouse_move(pointer_position, cx);
4737 })
4738 .unwrap();
4739 assert!(hover_transitions.borrow().is_empty());
4740
4741 key_down(cx, any_window, "a");
4742 window
4743 .update(cx, |view, _, cx| {
4744 view.target_left = px(0.);
4745 cx.notify();
4746 })
4747 .unwrap();
4748 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
4749 .unwrap();
4750 assert_eq!(*hover_transitions.borrow(), [true]);
4751
4752 key_down(cx, any_window, "b");
4753 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
4754 .unwrap();
4755 assert_eq!(*hover_transitions.borrow(), [true]);
4756
4757 cx.update_window(any_window, |_, window, cx| {
4758 window.simulate_mouse_move(point(px(30.), px(10.)), cx);
4759 })
4760 .unwrap();
4761 assert_eq!(*hover_transitions.borrow(), [true, false]);
4762
4763 cx.update_window(any_window, |_, window, cx| {
4764 window.simulate_mouse_move(pointer_position, cx);
4765 window.dispatch_event(
4766 MouseExitEvent {
4767 position: pointer_position,
4768 ..Default::default()
4769 }
4770 .to_platform_input(),
4771 cx,
4772 );
4773 })
4774 .unwrap();
4775 assert_eq!(*hover_transitions.borrow(), [true, false, true, false]);
4776 }
4777
4778 #[gpui::test]
4779 fn default_hover_listener_remains_hovered_during_stationary_mouse_press(
4780 cx: &mut TestAppContext,
4781 ) {
4782 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4783 let window = cx.add_window({
4784 let hover_transitions = hover_transitions.clone();
4785 move |_, _| HoverListenerLayoutTestView {
4786 target_left: px(0.),
4787 hover_transitions,
4788 }
4789 });
4790 let any_window = AnyWindowHandle::from(window);
4791 let mouse_position = point(px(10.), px(10.));
4792
4793 cx.update_window(any_window, |_, window, cx| {
4794 window.draw(cx).clear(cx);
4795 window.simulate_mouse_move(mouse_position, cx);
4796 })
4797 .unwrap();
4798 assert_eq!(*hover_transitions.borrow(), [true]);
4799
4800 cx.update_window(any_window, |_, window, cx| {
4801 window.dispatch_event(
4802 MouseDownEvent {
4803 position: mouse_position,
4804 button: MouseButton::Left,
4805 modifiers: Default::default(),
4806 click_count: 1,
4807 first_mouse: false,
4808 }
4809 .to_platform_input(),
4810 cx,
4811 );
4812 window.draw(cx).clear(cx);
4813 })
4814 .unwrap();
4815 assert_eq!(*hover_transitions.borrow(), [true]);
4816
4817 cx.update_window(any_window, |_, window, cx| {
4818 window.dispatch_event(
4819 MouseUpEvent {
4820 position: mouse_position,
4821 button: MouseButton::Left,
4822 modifiers: Default::default(),
4823 click_count: 1,
4824 }
4825 .to_platform_input(),
4826 cx,
4827 );
4828 window.draw(cx).clear(cx);
4829 })
4830 .unwrap();
4831 assert_eq!(*hover_transitions.borrow(), [true]);
4832 }
4833
4834 struct TestTooltipView;
4835
4836 impl Render for TestTooltipView {
4837 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4838 div().w(px(20.)).h(px(20.)).child("tooltip")
4839 }
4840 }
4841
4842 type CapturedActiveTooltip = Rc<RefCell<Option<Weak<RefCell<Option<ActiveTooltip>>>>>>;
4843
4844 struct TooltipCaptureElement {
4845 child: AnyElement,
4846 captured_active_tooltip: CapturedActiveTooltip,
4847 }
4848
4849 impl IntoElement for TooltipCaptureElement {
4850 type Element = Self;
4851
4852 fn into_element(self) -> Self::Element {
4853 self
4854 }
4855 }
4856
4857 impl Element for TooltipCaptureElement {
4858 type RequestLayoutState = ();
4859 type PrepaintState = ();
4860
4861 fn id(&self) -> Option<ElementId> {
4862 None
4863 }
4864
4865 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
4866 None
4867 }
4868
4869 fn request_layout(
4870 &mut self,
4871 _id: Option<&GlobalElementId>,
4872 _inspector_id: Option<&InspectorElementId>,
4873 window: &mut Window,
4874 cx: &mut App,
4875 ) -> (LayoutId, Self::RequestLayoutState) {
4876 (self.child.request_layout(window, cx), ())
4877 }
4878
4879 fn prepaint(
4880 &mut self,
4881 _id: Option<&GlobalElementId>,
4882 _inspector_id: Option<&InspectorElementId>,
4883 _bounds: Bounds<Pixels>,
4884 _request_layout: &mut Self::RequestLayoutState,
4885 window: &mut Window,
4886 cx: &mut App,
4887 ) -> Self::PrepaintState {
4888 self.child.prepaint(window, cx);
4889 }
4890
4891 fn paint(
4892 &mut self,
4893 _id: Option<&GlobalElementId>,
4894 _inspector_id: Option<&InspectorElementId>,
4895 _bounds: Bounds<Pixels>,
4896 _request_layout: &mut Self::RequestLayoutState,
4897 _prepaint: &mut Self::PrepaintState,
4898 window: &mut Window,
4899 cx: &mut App,
4900 ) {
4901 self.child.paint(window, cx);
4902 window.with_global_id("target".into(), |global_id, window| {
4903 window.with_element_state::<InteractiveElementState, _>(
4904 global_id,
4905 |state, _window| {
4906 let state = state.unwrap();
4907 *self.captured_active_tooltip.borrow_mut() =
4908 state.active_tooltip.as_ref().map(Rc::downgrade);
4909 ((), state)
4910 },
4911 )
4912 });
4913 }
4914 }
4915
4916 struct TooltipOwner {
4917 captured_active_tooltip: CapturedActiveTooltip,
4918 show_delay_override: Option<Duration>,
4919 }
4920
4921 impl Render for TooltipOwner {
4922 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4923 TooltipCaptureElement {
4924 child: div()
4925 .size_full()
4926 .child(
4927 div()
4928 .id("target")
4929 .w(px(50.))
4930 .h(px(50.))
4931 .tooltip(|_, cx| cx.new(|_| TestTooltipView).into())
4932 .when_some(self.show_delay_override, |this, delay| {
4933 this.tooltip_show_delay(delay)
4934 }),
4935 )
4936 .into_any_element(),
4937 captured_active_tooltip: self.captured_active_tooltip.clone(),
4938 }
4939 }
4940 }
4941
4942 #[test]
4943 fn scroll_handle_aligns_wide_children_to_left_edge() {
4944 let handle = ScrollHandle::new();
4945 {
4946 let mut state = handle.0.borrow_mut();
4947 state.bounds = Bounds::new(point(px(0.), px(0.)), size(px(80.), px(20.)));
4948 state.child_bounds = vec![Bounds::new(point(px(25.), px(0.)), size(px(200.), px(20.)))];
4949 state.overflow.x = Overflow::Scroll;
4950 state.active_item = Some(ScrollActiveItem {
4951 index: 0,
4952 strategy: ScrollStrategy::default(),
4953 });
4954 }
4955
4956 handle.scroll_to_active_item();
4957
4958 assert_eq!(handle.offset().x, px(-25.));
4959 }
4960
4961 #[test]
4962 fn scroll_handle_aligns_tall_children_to_top_edge() {
4963 let handle = ScrollHandle::new();
4964 {
4965 let mut state = handle.0.borrow_mut();
4966 state.bounds = Bounds::new(point(px(0.), px(0.)), size(px(20.), px(80.)));
4967 state.child_bounds = vec![Bounds::new(point(px(0.), px(25.)), size(px(20.), px(200.)))];
4968 state.overflow.y = Overflow::Scroll;
4969 state.active_item = Some(ScrollActiveItem {
4970 index: 0,
4971 strategy: ScrollStrategy::default(),
4972 });
4973 }
4974
4975 handle.scroll_to_active_item();
4976
4977 assert_eq!(handle.offset().y, px(-25.));
4978 }
4979
4980 #[test]
4981 fn long_press_shows_tooltip_until_touch_ends() {
4982 let (mut test_app, any_window, captured_active_tooltip) = create_tooltip_owner_test(None);
4983 let active_tooltip = captured_active_tooltip
4984 .borrow()
4985 .clone()
4986 .unwrap()
4987 .upgrade()
4988 .unwrap();
4989 let touch_id = TouchId(1);
4990 let touch_position = point(px(49.), px(10.));
4991
4992 test_app
4993 .update_window(any_window, |_, window, cx| {
4994 window.dispatch_event(
4995 TouchEvent {
4996 id: touch_id,
4997 phase: TouchPhase::Started,
4998 position: touch_position,
4999 predicted_position: None,
5000 force: None,
5001 }
5002 .to_platform_input(),
5003 cx,
5004 );
5005 })
5006 .unwrap();
5007 test_app
5008 .dispatcher
5009 .advance_clock(GestureTuning::default().long_press_duration + Duration::from_millis(1));
5010 test_app.run_until_parked();
5011
5012 assert!(matches!(
5013 active_tooltip.borrow().as_ref(),
5014 Some(ActiveTooltip::Visible { .. })
5015 ));
5016
5017 let moved_position = point(px(55.), px(10.));
5018 test_app
5019 .update_window(any_window, |_, window, cx| {
5020 window.dispatch_event(
5021 TouchEvent {
5022 id: touch_id,
5023 phase: TouchPhase::Moved,
5024 position: moved_position,
5025 predicted_position: None,
5026 force: None,
5027 }
5028 .to_platform_input(),
5029 cx,
5030 );
5031 window.draw(cx).clear(cx);
5032 })
5033 .unwrap();
5034 assert!(matches!(
5035 active_tooltip.borrow().as_ref(),
5036 Some(ActiveTooltip::Visible { .. })
5037 ));
5038
5039 test_app
5040 .update_window(any_window, |_, window, cx| {
5041 window.dispatch_event(
5042 TouchEvent {
5043 id: touch_id,
5044 phase: TouchPhase::Ended,
5045 position: moved_position,
5046 predicted_position: None,
5047 force: None,
5048 }
5049 .to_platform_input(),
5050 cx,
5051 );
5052 })
5053 .unwrap();
5054
5055 assert!(active_tooltip.borrow().is_none());
5056 }
5057
5058 fn setup_tooltip_owner_test(
5059 show_delay_override: Option<Duration>,
5060 ) -> (
5061 TestAppContext,
5062 crate::AnyWindowHandle,
5063 CapturedActiveTooltip,
5064 ) {
5065 let (mut test_app, any_window, captured_active_tooltip) =
5066 create_tooltip_owner_test(show_delay_override);
5067
5068 test_app
5069 .update_window(any_window, |_, window, cx| {
5070 window.dispatch_event(
5071 MouseMoveEvent {
5072 position: point(px(10.), px(10.)),
5073 modifiers: Default::default(),
5074 pressed_button: None,
5075 }
5076 .to_platform_input(),
5077 cx,
5078 );
5079 })
5080 .unwrap();
5081
5082 test_app
5083 .update_window(any_window, |_, window, cx| {
5084 window.draw(cx).clear(cx);
5085 })
5086 .unwrap();
5087
5088 (test_app, any_window, captured_active_tooltip)
5089 }
5090
5091 fn create_tooltip_owner_test(
5092 show_delay_override: Option<Duration>,
5093 ) -> (
5094 TestAppContext,
5095 crate::AnyWindowHandle,
5096 CapturedActiveTooltip,
5097 ) {
5098 let mut test_app = TestAppContext::single();
5099 let captured_active_tooltip: CapturedActiveTooltip = Rc::new(RefCell::new(None));
5100 let window = test_app.add_window({
5101 let captured_active_tooltip = captured_active_tooltip.clone();
5102 move |_, _| TooltipOwner {
5103 captured_active_tooltip,
5104 show_delay_override,
5105 }
5106 });
5107 let any_window = window.into();
5108
5109 test_app
5110 .update_window(any_window, |_, window, cx| {
5111 window.draw(cx).clear(cx);
5112 })
5113 .unwrap();
5114
5115 (test_app, any_window, captured_active_tooltip)
5116 }
5117
5118 #[test]
5119 fn tooltip_waiting_for_show_is_released_when_its_owner_disappears() {
5120 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
5121
5122 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
5123 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
5124 assert!(matches!(
5125 active_tooltip.borrow().as_ref(),
5126 Some(ActiveTooltip::WaitingForShow { .. })
5127 ));
5128
5129 test_app
5130 .update_window(any_window, |_, window, _| {
5131 window.remove_window();
5132 })
5133 .unwrap();
5134 test_app.run_until_parked();
5135 drop(active_tooltip);
5136
5137 assert!(weak_active_tooltip.upgrade().is_none());
5138 }
5139
5140 #[test]
5141 fn tooltip_respects_custom_show_delay() {
5142 let extra_delay = Duration::from_secs(1);
5143 let show_delay_override = DEFAULT_TOOLTIP_SHOW_DELAY + extra_delay;
5144 let (mut test_app, _any_window, captured_active_tooltip) =
5145 setup_tooltip_owner_test(Some(show_delay_override));
5146
5147 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
5148 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
5149
5150 test_app
5151 .dispatcher
5152 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
5153 test_app.run_until_parked();
5154
5155 assert!(matches!(
5156 active_tooltip.borrow().as_ref(),
5157 Some(ActiveTooltip::WaitingForShow { .. })
5158 ));
5159
5160 test_app.dispatcher.advance_clock(extra_delay);
5161 test_app.run_until_parked();
5162
5163 assert!(matches!(
5164 active_tooltip.borrow().as_ref(),
5165 Some(ActiveTooltip::Visible { .. })
5166 ));
5167 }
5168
5169 #[test]
5170 fn tooltip_is_released_when_its_owner_disappears() {
5171 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
5172
5173 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
5174 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
5175
5176 test_app
5177 .dispatcher
5178 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
5179 test_app.run_until_parked();
5180
5181 assert!(matches!(
5182 active_tooltip.borrow().as_ref(),
5183 Some(ActiveTooltip::Visible { .. })
5184 ));
5185
5186 test_app
5187 .update_window(any_window, |_, window, _| {
5188 window.remove_window();
5189 })
5190 .unwrap();
5191 test_app.run_until_parked();
5192 drop(active_tooltip);
5193
5194 assert!(weak_active_tooltip.upgrade().is_none());
5195 }
5196
5197 #[test]
5198 fn tooltip_hides_after_mouse_leaves_origin() {
5199 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
5200
5201 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
5202 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
5203
5204 test_app
5205 .dispatcher
5206 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
5207 test_app.run_until_parked();
5208
5209 assert!(matches!(
5210 active_tooltip.borrow().as_ref(),
5211 Some(ActiveTooltip::Visible { .. })
5212 ));
5213
5214 test_app
5215 .update_window(any_window, |_, window, cx| {
5216 window.dispatch_event(
5217 MouseMoveEvent {
5218 position: point(px(75.), px(75.)),
5219 modifiers: Default::default(),
5220 pressed_button: None,
5221 }
5222 .to_platform_input(),
5223 cx,
5224 );
5225 })
5226 .unwrap();
5227
5228 assert!(active_tooltip.borrow().is_none());
5229 }
5230
5231 struct MouseDownOutOwner {
5232 mouse_down_out_count: Rc<RefCell<usize>>,
5233 }
5234
5235 impl Render for MouseDownOutOwner {
5236 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
5237 let mouse_down_out_count = self.mouse_down_out_count.clone();
5238 div()
5239 .size_full()
5240 .child(div().id("target").w(px(50.)).h(px(50.)).on_mouse_down_out(
5241 move |_, _, _| {
5242 *mouse_down_out_count.borrow_mut() += 1;
5243 },
5244 ))
5245 }
5246 }
5247
5248 #[test]
5249 fn mouse_down_out_is_suppressed_while_window_prompt_is_active() {
5250 let mut test_app = TestAppContext::single();
5251 let mouse_down_out_count = Rc::new(RefCell::new(0));
5252 let window = test_app.add_window({
5253 let mouse_down_out_count = mouse_down_out_count.clone();
5254 move |_, _| MouseDownOutOwner {
5255 mouse_down_out_count,
5256 }
5257 });
5258 let any_window: AnyWindowHandle = window.into();
5259
5260 fn dispatch_mouse_down_outside_target(
5261 test_app: &mut TestAppContext,
5262 any_window: AnyWindowHandle,
5263 ) {
5264 test_app
5265 .update_window(any_window, |_, window, cx| {
5266 window.dispatch_event(
5267 MouseDownEvent {
5268 position: point(px(75.), px(75.)),
5269 button: MouseButton::Left,
5270 modifiers: Default::default(),
5271 click_count: 1,
5272 first_mouse: false,
5273 }
5274 .to_platform_input(),
5275 cx,
5276 );
5277 })
5278 .unwrap();
5279 }
5280
5281 test_app
5282 .update_window(any_window, |_, window, cx| {
5283 window.draw(cx).clear(cx);
5284 })
5285 .unwrap();
5286
5287 dispatch_mouse_down_outside_target(&mut test_app, any_window);
5288 assert_eq!(
5289 *mouse_down_out_count.borrow(),
5290 1,
5291 "mouse down outside the element should fire mouse-down-out listeners"
5292 );
5293
5294 test_app
5295 .update_window(any_window, |_, window, cx| {
5296 cx.set_prompt_builder(crate::fallback_prompt_renderer);
5297 let _receiver =
5298 window.prompt(crate::PromptLevel::Warning, "message", None, &["Ok"], cx);
5299 assert!(window.has_active_prompt());
5300 window.draw(cx).clear(cx);
5301 })
5302 .unwrap();
5303
5304 dispatch_mouse_down_outside_target(&mut test_app, any_window);
5305 assert_eq!(
5306 *mouse_down_out_count.borrow(),
5307 1,
5308 "mouse down over an active prompt should not fire mouse-down-out listeners"
5309 );
5310 }
5311
5312 #[test]
5313 fn touch_tap_out_fires_once_after_release() {
5314 let mut test_app = TestAppContext::single();
5315 let mouse_down_out_count = Rc::new(RefCell::new(0));
5316 let window = test_app.add_window({
5317 let mouse_down_out_count = mouse_down_out_count.clone();
5318 move |_, _| MouseDownOutOwner {
5319 mouse_down_out_count,
5320 }
5321 });
5322 let any_window: AnyWindowHandle = window.into();
5323
5324 test_app
5325 .update_window(any_window, |_, window, cx| {
5326 window.draw(cx).clear(cx);
5327 let started = TouchEvent {
5328 id: TouchId(1),
5329 phase: TouchPhase::Started,
5330 position: point(px(75.), px(75.)),
5331 force: None,
5332 predicted_position: None,
5333 };
5334 window.dispatch_event(started.clone().to_platform_input(), cx);
5335 assert_eq!(*mouse_down_out_count.borrow(), 0);
5336 window.dispatch_event(
5337 TouchEvent {
5338 phase: TouchPhase::Ended,
5339 ..started
5340 }
5341 .to_platform_input(),
5342 cx,
5343 );
5344 })
5345 .unwrap();
5346
5347 assert_eq!(
5348 *mouse_down_out_count.borrow(),
5349 1,
5350 "a touch tap outside the element should use mouse-down-out listeners"
5351 );
5352 }
5353
5354 #[crate::test]
5355 fn touch_pan_and_cancel_do_not_fire_mouse_down_out(cx: &mut TestAppContext) {
5356 let mouse_down_out_count = Rc::new(RefCell::new(0));
5357 let window = cx.add_window({
5358 let mouse_down_out_count = mouse_down_out_count.clone();
5359 move |_, _| MouseDownOutOwner {
5360 mouse_down_out_count,
5361 }
5362 });
5363 cx.update_window(window.into(), |_, window, cx| {
5364 window.draw(cx).clear(cx);
5365 for (id, samples) in [
5366 (
5367 TouchId(1),
5368 [
5369 (TouchPhase::Started, 100.),
5370 (TouchPhase::Moved, 75.),
5371 (TouchPhase::Ended, 75.),
5372 ],
5373 ),
5374 (
5375 TouchId(2),
5376 [
5377 (TouchPhase::Started, 100.),
5378 (TouchPhase::Moved, 100.),
5379 (TouchPhase::Cancelled, 100.),
5380 ],
5381 ),
5382 ] {
5383 for (phase, vertical_position) in samples {
5384 window.dispatch_event(
5385 TouchEvent {
5386 id,
5387 phase,
5388 position: point(px(75.), px(vertical_position)),
5389 predicted_position: None,
5390 force: None,
5391 }
5392 .to_platform_input(),
5393 cx,
5394 );
5395 }
5396 }
5397 })
5398 .unwrap();
5399 assert_eq!(*mouse_down_out_count.borrow(), 0);
5400 }
5401
5402 #[test]
5403 fn test_accessibility_id_builder_writes_author_id() {
5404 let mut element = div()
5405 .id("buffer-font-size")
5406 .accessibility_id("settings.buffer-font-size");
5407 let mut node = accesskit::Node::new(accesskit::Role::SpinButton);
5408
5409 element.interactivity().write_a11y_info(&mut node);
5410
5411 assert_eq!(node.author_id(), Some("settings.buffer-font-size"));
5412 }
5413
5414 #[test]
5415 fn test_write_a11y_info_string_and_numeric_properties() {
5416 let mut interactivity = Interactivity::default();
5417 interactivity.aria.author_id = Some("settings.buffer-font-size".into());
5418 interactivity.aria.label = Some("Buffer Font Size".into());
5419 interactivity.aria.value = Some("15".into());
5420 interactivity.aria.placeholder = Some("Search".into());
5421 interactivity.aria.numeric_value = Some(15.0);
5422 interactivity.aria.min_numeric_value = Some(6.0);
5423 interactivity.aria.max_numeric_value = Some(72.0);
5424 interactivity.aria.numeric_value_step = Some(1.0);
5425
5426 let mut node = accesskit::Node::new(accesskit::Role::SpinButton);
5427 interactivity.write_a11y_info(&mut node);
5428
5429 assert_eq!(node.author_id(), Some("settings.buffer-font-size"));
5430 assert_eq!(node.label(), Some("Buffer Font Size"));
5431 assert_eq!(node.value(), Some("15"));
5432 assert_eq!(node.placeholder(), Some("Search"));
5433 assert_eq!(node.numeric_value(), Some(15.0));
5434 assert_eq!(node.min_numeric_value(), Some(6.0));
5435 assert_eq!(node.max_numeric_value(), Some(72.0));
5436 assert_eq!(node.numeric_value_step(), Some(1.0));
5437 }
5438
5439 struct KeyboardActivationTest {
5442 focus_a: FocusHandle,
5443 focus_b: FocusHandle,
5444 clicks: Rc<RefCell<Vec<&'static str>>>,
5445 }
5446
5447 impl Render for KeyboardActivationTest {
5448 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
5449 let clicks_a = self.clicks.clone();
5450 let clicks_b = self.clicks.clone();
5451 div()
5452 .size_full()
5453 .child(
5454 div()
5455 .id("a")
5456 .w(px(50.))
5457 .h(px(50.))
5458 .track_focus(&self.focus_a)
5459 .on_click(move |_, _, _| clicks_a.borrow_mut().push("a")),
5460 )
5461 .child(
5462 div()
5463 .id("b")
5464 .w(px(50.))
5465 .h(px(50.))
5466 .track_focus(&self.focus_b)
5467 .on_click(move |_, _, _| clicks_b.borrow_mut().push("b")),
5468 )
5469 }
5470 }
5471
5472 fn setup_keyboard_activation_test() -> (
5473 TestAppContext,
5474 AnyWindowHandle,
5475 Rc<RefCell<Vec<&'static str>>>,
5476 FocusHandle,
5477 FocusHandle,
5478 ) {
5479 let mut cx = TestAppContext::single();
5480 let (focus_a, focus_b) = cx.update(|cx| (cx.focus_handle(), cx.focus_handle()));
5481 let clicks: Rc<RefCell<Vec<&'static str>>> = Rc::new(RefCell::new(Vec::new()));
5482 let window = cx.add_window({
5483 let focus_a = focus_a.clone();
5484 let focus_b = focus_b.clone();
5485 let clicks = clicks.clone();
5486 move |_, _| KeyboardActivationTest {
5487 focus_a,
5488 focus_b,
5489 clicks,
5490 }
5491 });
5492 (cx, window.into(), clicks, focus_a, focus_b)
5493 }
5494
5495 fn focus_and_draw(cx: &mut TestAppContext, window: AnyWindowHandle, handle: &FocusHandle) {
5498 cx.update_window(window, |_, window, cx| window.focus(handle, cx))
5499 .unwrap();
5500 cx.run_until_parked();
5501 cx.update_window(window, |_, window, cx| {
5502 window.draw(cx).clear(cx);
5503 })
5504 .unwrap();
5505 }
5506
5507 fn key_down(cx: &mut TestAppContext, window: AnyWindowHandle, key: &str) {
5508 let os = cx.read(|cx| cx.platform_info().os);
5509 let keystroke = Keystroke::parse_with_os(key, os).unwrap();
5510 cx.update_window(window, |_, window, cx| {
5511 window.dispatch_event(
5512 KeyDownEvent {
5513 keystroke,
5514 is_held: false,
5515 prefer_character_input: false,
5516 }
5517 .to_platform_input(),
5518 cx,
5519 );
5520 })
5521 .unwrap();
5522 }
5523
5524 fn key_up(cx: &mut TestAppContext, window: AnyWindowHandle, key: &str) {
5525 let os = cx.read(|cx| cx.platform_info().os);
5526 let keystroke = Keystroke::parse_with_os(key, os).unwrap();
5527 cx.update_window(window, |_, window, cx| {
5528 window.dispatch_event(KeyUpEvent { keystroke }.to_platform_input(), cx);
5529 })
5530 .unwrap();
5531 }
5532
5533 #[test]
5535 fn keyboard_activation_fires_click_on_same_element() {
5536 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5537
5538 focus_and_draw(&mut cx, window, &focus_a);
5539 key_down(&mut cx, window, "enter");
5540 key_up(&mut cx, window, "enter");
5541
5542 assert_eq!(*clicks.borrow(), vec!["a"]);
5543 }
5544
5545 #[test]
5550 fn keyboard_activation_does_not_leak_across_focus_change() {
5551 let (mut cx, window, clicks, focus_a, focus_b) = setup_keyboard_activation_test();
5552
5553 focus_and_draw(&mut cx, window, &focus_a);
5555 key_down(&mut cx, window, "enter");
5556
5557 focus_and_draw(&mut cx, window, &focus_b);
5559 key_up(&mut cx, window, "enter");
5560
5561 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
5564 }
5565
5566 #[test]
5571 fn keyboard_activation_does_not_leak_when_focus_returns() {
5572 let (mut cx, window, clicks, focus_a, focus_b) = setup_keyboard_activation_test();
5573
5574 focus_and_draw(&mut cx, window, &focus_a);
5576 key_down(&mut cx, window, "enter");
5577
5578 focus_and_draw(&mut cx, window, &focus_b);
5581 focus_and_draw(&mut cx, window, &focus_a);
5582 key_up(&mut cx, window, "enter");
5583
5584 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
5586 }
5587
5588 #[test]
5593 fn keyboard_activation_cleared_by_intervening_key_release() {
5594 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5595
5596 focus_and_draw(&mut cx, window, &focus_a);
5597 key_down(&mut cx, window, "escape");
5598 key_down(&mut cx, window, "space");
5599 key_up(&mut cx, window, "escape");
5600 key_up(&mut cx, window, "space");
5601
5602 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
5603 }
5604
5605 #[test]
5609 fn keyboard_activation_does_not_distinguish_space_and_enter() {
5610 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5611
5612 focus_and_draw(&mut cx, window, &focus_a);
5613 key_down(&mut cx, window, "space");
5614 key_up(&mut cx, window, "enter");
5615
5616 assert_eq!(*clicks.borrow(), vec!["a"]);
5617 }
5618
5619 #[test]
5622 fn keyboard_activation_cleared_by_intervening_keydown() {
5623 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5624
5625 focus_and_draw(&mut cx, window, &focus_a);
5626 key_down(&mut cx, window, "enter");
5627 key_down(&mut cx, window, "a");
5628 key_up(&mut cx, window, "enter");
5629
5630 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
5631 }
5632
5633 #[test]
5636 fn keyboard_activation_ignores_modified_keys() {
5637 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5638
5639 focus_and_draw(&mut cx, window, &focus_a);
5640 key_down(&mut cx, window, "cmd-enter");
5641 key_up(&mut cx, window, "cmd-enter");
5642
5643 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
5644 }
5645
5646 struct TabGroupFocus {
5650 group_a: FocusHandle,
5651 item_a: FocusHandle,
5652 group_b: FocusHandle,
5653 item_b: FocusHandle,
5654 }
5655
5656 impl Render for TabGroupFocus {
5657 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5658 fn group(container: &FocusHandle, item: &FocusHandle) -> Div {
5659 div()
5660 .track_focus(container)
5661 .tab_group()
5662 .child(div().track_focus(item))
5663 }
5664 div()
5665 .child(group(&self.group_a, &self.item_a))
5666 .child(group(&self.group_b, &self.item_b))
5667 }
5668 }
5669
5670 #[test]
5674 fn focus_next_from_tab_group_container_enters_that_group() {
5675 let mut cx = TestAppContext::single();
5676 let (group_a, item_a, group_b, item_b) = cx.update(|cx| {
5677 (
5678 cx.focus_handle(),
5679 cx.focus_handle().tab_stop(true),
5680 cx.focus_handle(),
5681 cx.focus_handle().tab_stop(true),
5682 )
5683 });
5684 let window: AnyWindowHandle = cx
5685 .add_window({
5686 let (group_a, item_a, group_b, item_b) =
5687 (group_a, item_a, group_b.clone(), item_b.clone());
5688 move |_, _| TabGroupFocus {
5689 group_a,
5690 item_a,
5691 group_b,
5692 item_b,
5693 }
5694 })
5695 .into();
5696 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
5697 .unwrap();
5698
5699 let focused = cx
5701 .update_window(window, |_, window, cx| {
5702 window.focus(&group_b, cx);
5703 window.focus_next(cx);
5704 window.focused(cx).map(|handle| handle.id)
5705 })
5706 .unwrap();
5707
5708 assert_eq!(focused, Some(item_b.id));
5709 }
5710
5711 #[gpui::test]
5712 fn test_fractional_padding_does_not_make_a_fitting_container_scrollable(
5713 cx: &mut TestAppContext,
5714 ) {
5715 struct PaddedContainer {
5716 scroll_handle: ScrollHandle,
5717 }
5718
5719 impl Render for PaddedContainer {
5720 fn render(
5721 &mut self,
5722 _window: &mut Window,
5723 _cx: &mut Context<Self>,
5724 ) -> impl IntoElement {
5725 div().size_full().child(
5728 div()
5729 .id("container")
5730 .h(px(50.))
5731 .w(px(100.))
5732 .py(px(4.25))
5733 .overflow_y_scroll()
5734 .track_scroll(&self.scroll_handle)
5735 .child(div().w_full().h(px(42.))),
5736 )
5737 }
5738 }
5739
5740 let scroll_handle = ScrollHandle::new();
5741 let window: AnyWindowHandle = cx
5742 .add_window({
5743 let scroll_handle = scroll_handle.clone();
5744 move |_, _| PaddedContainer { scroll_handle }
5745 })
5746 .into();
5747 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
5748 .unwrap();
5749
5750 assert_eq!(scroll_handle.max_offset().y, px(0.));
5751 }
5752
5753 struct ContentSizedGrid;
5754
5755 impl Render for ContentSizedGrid {
5756 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
5757 let widths = [px(100.), px(200.), px(50.)];
5758 div().size_full().child(
5759 div()
5760 .w_full()
5761 .grid()
5762 .grid_cols_max_content(widths.len() as u16)
5763 .children(widths.into_iter().enumerate().map(|(index, width)| {
5764 div()
5765 .debug_selector(move || format!("cell-{index}"))
5766 .w(width)
5767 .h(px(10.))
5768 })),
5769 )
5770 }
5771 }
5772
5773 #[gpui::test]
5774 fn grid_cols_max_content_sizes_columns_to_their_content(cx: &mut TestAppContext) {
5775 let window = cx.add_window(|_, _| ContentSizedGrid);
5776 cx.update_window(window.into(), |_, window, cx| window.draw(cx).clear(cx))
5777 .unwrap();
5778
5779 let mut bounds = |selector: &'static str| {
5780 cx.update_window(window.into(), |_, window, _| {
5781 window.rendered_frame.debug_bounds.get(selector).copied()
5782 })
5783 .unwrap()
5784 .unwrap_or_else(|| panic!("{selector} was not rendered"))
5785 };
5786
5787 assert_eq!(bounds("cell-0").origin.x, px(0.));
5788 assert_eq!(bounds("cell-1").origin.x, px(100.));
5789 assert_eq!(bounds("cell-2").origin.x, px(300.));
5790 }
5791}