1use crate::{
19 Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent, DispatchPhase,
20 Display, Element, ElementId, Entity, EntityId, ExternalDragPayload, ExternalDragPayloadSource,
21 FocusHandle, Global, GlobalElementId, Hitbox, HitboxBehavior, HitboxId, InspectorElementId,
22 IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, KeyboardButton, KeyboardClickEvent,
23 LayoutId, ModifiersChangedEvent, MouseButton, MouseClickEvent, MouseDownEvent, MouseExitEvent,
24 MouseMoveEvent, MousePressureEvent, MouseUpEvent, OngoingScroll, Overflow, ParentElement,
25 PinchEvent, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
26 StyleRefinement, Styled, Task, TooltipId, Visibility, Window, WindowControlArea, point, px,
27 size,
28};
29use collections::HashMap;
30use gpui_util::ResultExt;
31use refineable::Refineable;
32use smallvec::SmallVec;
33use stacksafe::{StackSafe, stacksafe};
34use std::{
35 any::{Any, TypeId},
36 cell::RefCell,
37 cmp::Ordering,
38 fmt::Debug,
39 marker::PhantomData,
40 mem,
41 rc::Rc,
42 sync::Arc,
43 time::Duration,
44};
45
46use super::ImageCacheProvider;
47
48const DRAG_THRESHOLD: f64 = 2.;
49const DEFAULT_TOOLTIP_SHOW_DELAY: Duration = Duration::from_millis(500);
50const HOVERABLE_TOOLTIP_HIDE_DELAY: Duration = Duration::from_millis(500);
51
52pub struct GroupStyle {
54 pub group: SharedString,
56
57 pub style: Box<StyleRefinement>,
60}
61
62pub struct DragMoveEvent<T> {
64 pub event: MouseMoveEvent,
66
67 pub bounds: Bounds<Pixels>,
69 drag: PhantomData<T>,
70 dragged_item: Arc<dyn Any>,
71}
72
73impl<T: 'static> DragMoveEvent<T> {
74 pub fn drag<'b>(&self, cx: &'b App) -> &'b T {
76 cx.active_drag
77 .as_ref()
78 .and_then(|drag| drag.value.downcast_ref::<T>())
79 .expect("DragMoveEvent is only valid when the stored active drag is of the same type.")
80 }
81
82 pub fn dragged_item(&self) -> &dyn Any {
84 self.dragged_item.as_ref()
85 }
86}
87
88impl Interactivity {
89 #[cfg(any(feature = "inspector", debug_assertions))]
91 #[track_caller]
92 pub fn new() -> Interactivity {
93 Interactivity {
94 source_location: Some(core::panic::Location::caller()),
95 ..Default::default()
96 }
97 }
98
99 #[cfg(not(any(feature = "inspector", debug_assertions)))]
101 pub fn new() -> Interactivity {
102 Interactivity::default()
103 }
104
105 pub fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
107 #[cfg(any(feature = "inspector", debug_assertions))]
108 {
109 self.source_location
110 }
111
112 #[cfg(not(any(feature = "inspector", debug_assertions)))]
113 {
114 None
115 }
116 }
117
118 pub fn on_mouse_down(
123 &mut self,
124 button: MouseButton,
125 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
126 ) {
127 self.mouse_down_listeners
128 .push(Box::new(move |event, phase, hitbox, window, cx| {
129 if phase == DispatchPhase::Bubble
130 && event.button == button
131 && hitbox.is_hovered(window)
132 {
133 (listener)(event, window, cx)
134 }
135 }));
136 }
137
138 pub fn on_mouse_down_with_pointer_capture(
149 &mut self,
150 button: MouseButton,
151 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
152 ) {
153 self.mouse_down_listeners
154 .push(Box::new(move |event, phase, hitbox, window, cx| {
155 if phase == DispatchPhase::Bubble
156 && event.button == button
157 && hitbox.is_hovered(window)
158 {
159 window.capture_pointer(hitbox.id);
160 (listener)(event, window, cx)
161 }
162 }));
163 }
164
165 pub fn capture_any_mouse_down(
170 &mut self,
171 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
172 ) {
173 self.mouse_down_listeners
174 .push(Box::new(move |event, phase, hitbox, window, cx| {
175 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
176 (listener)(event, window, cx)
177 }
178 }));
179 }
180
181 pub fn on_any_mouse_down(
186 &mut self,
187 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
188 ) {
189 self.mouse_down_listeners
190 .push(Box::new(move |event, phase, hitbox, window, cx| {
191 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
192 (listener)(event, window, cx)
193 }
194 }));
195 }
196
197 pub fn on_mouse_pressure(
202 &mut self,
203 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
204 ) {
205 self.mouse_pressure_listeners
206 .push(Box::new(move |event, phase, hitbox, window, cx| {
207 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
208 (listener)(event, window, cx)
209 }
210 }));
211 }
212
213 pub fn capture_mouse_pressure(
218 &mut self,
219 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
220 ) {
221 self.mouse_pressure_listeners
222 .push(Box::new(move |event, phase, hitbox, window, cx| {
223 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
224 (listener)(event, window, cx)
225 }
226 }));
227 }
228
229 pub fn on_mouse_up(
234 &mut self,
235 button: MouseButton,
236 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
237 ) {
238 self.mouse_up_listeners
239 .push(Box::new(move |event, phase, hitbox, window, cx| {
240 if phase == DispatchPhase::Bubble
241 && event.button == button
242 && hitbox.is_hovered(window)
243 {
244 (listener)(event, window, cx)
245 }
246 }));
247 }
248
249 pub fn capture_any_mouse_up(
254 &mut self,
255 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
256 ) {
257 self.mouse_up_listeners
258 .push(Box::new(move |event, phase, hitbox, window, cx| {
259 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
260 (listener)(event, window, cx)
261 }
262 }));
263 }
264
265 pub fn on_any_mouse_up(
270 &mut self,
271 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
272 ) {
273 self.mouse_up_listeners
274 .push(Box::new(move |event, phase, hitbox, window, cx| {
275 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
276 (listener)(event, window, cx)
277 }
278 }));
279 }
280
281 pub fn on_mouse_down_out(
287 &mut self,
288 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
289 ) {
290 self.mouse_down_listeners
291 .push(Box::new(move |event, phase, hitbox, window, cx| {
292 if phase == DispatchPhase::Capture
293 && !window.has_active_prompt()
294 && !hitbox.contains(&window.mouse_position())
295 {
296 (listener)(event, window, cx)
297 }
298 }));
299 }
300
301 pub fn on_mouse_up_out(
307 &mut self,
308 button: MouseButton,
309 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
310 ) {
311 self.mouse_up_listeners
312 .push(Box::new(move |event, phase, hitbox, window, cx| {
313 if phase == DispatchPhase::Capture
314 && event.button == button
315 && !hitbox.is_hovered(window)
316 {
317 (listener)(event, window, cx);
318 }
319 }));
320 }
321
322 pub fn on_mouse_move(
327 &mut self,
328 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
329 ) {
330 self.mouse_move_listeners
331 .push(Box::new(move |event, phase, hitbox, window, cx| {
332 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
333 (listener)(event, window, cx);
334 }
335 }));
336 }
337
338 pub fn on_mouse_exit(
343 &mut self,
344 listener: impl Fn(&MouseExitEvent, &mut Window, &mut App) + 'static,
345 ) {
346 self.mouse_exit_listeners
347 .push(Box::new(move |event, phase, hitbox, window, cx| {
348 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
349 (listener)(event, window, cx);
350 }
351 }));
352 }
353
354 pub fn on_drag_move<T>(
362 &mut self,
363 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
364 ) where
365 T: 'static,
366 {
367 self.mouse_move_listeners
368 .push(Box::new(move |event, phase, hitbox, window, cx| {
369 if phase == DispatchPhase::Capture
370 && let Some(drag) = &cx.active_drag
371 && drag.value.as_ref().type_id() == TypeId::of::<T>()
372 {
373 (listener)(
374 &DragMoveEvent {
375 event: event.clone(),
376 bounds: hitbox.bounds,
377 drag: PhantomData,
378 dragged_item: Arc::clone(&drag.value),
379 },
380 window,
381 cx,
382 );
383 }
384 }));
385 }
386
387 pub fn on_scroll_wheel(
392 &mut self,
393 listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
394 ) {
395 self.scroll_wheel_listeners
396 .push(Box::new(move |event, phase, hitbox, window, cx| {
397 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
398 (listener)(event, window, cx);
399 }
400 }));
401 }
402
403 pub fn on_pinch(&mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) {
407 self.pinch_listeners
408 .push(Box::new(move |event, phase, hitbox, window, cx| {
409 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
410 (listener)(event, window, cx);
411 }
412 }));
413 }
414
415 pub fn capture_pinch(
419 &mut self,
420 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
421 ) {
422 self.pinch_listeners
423 .push(Box::new(move |event, phase, _hitbox, window, cx| {
424 if phase == DispatchPhase::Capture {
425 (listener)(event, window, cx);
426 } else {
427 cx.propagate();
428 }
429 }));
430 }
431
432 pub fn capture_action<A: Action>(
437 &mut self,
438 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
439 ) {
440 self.action_listeners.push((
441 TypeId::of::<A>(),
442 Box::new(move |action, phase, window, cx| {
443 let action = action.downcast_ref().unwrap();
444 if phase == DispatchPhase::Capture {
445 (listener)(action, window, cx)
446 } else {
447 cx.propagate();
448 }
449 }),
450 ));
451 }
452
453 #[track_caller]
458 pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Window, &mut App) + 'static) {
459 self.action_listeners.push((
460 TypeId::of::<A>(),
461 Box::new(move |action, phase, window, cx| {
462 let action = action.downcast_ref().unwrap();
463 if phase == DispatchPhase::Bubble {
464 (listener)(action, window, cx)
465 }
466 }),
467 ));
468 }
469
470 pub fn on_boxed_action(
477 &mut self,
478 action: &dyn Action,
479 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
480 ) {
481 let action = action.boxed_clone();
482 self.action_listeners.push((
483 (*action).type_id(),
484 Box::new(move |_, phase, window, cx| {
485 if phase == DispatchPhase::Bubble {
486 (listener)(&*action, window, cx)
487 }
488 }),
489 ));
490 }
491
492 pub fn on_key_down(
497 &mut self,
498 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
499 ) {
500 self.key_down_listeners
501 .push(Box::new(move |event, phase, window, cx| {
502 if phase == DispatchPhase::Bubble {
503 (listener)(event, window, cx)
504 }
505 }));
506 }
507
508 pub fn capture_key_down(
513 &mut self,
514 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
515 ) {
516 self.key_down_listeners
517 .push(Box::new(move |event, phase, window, cx| {
518 if phase == DispatchPhase::Capture {
519 listener(event, window, cx)
520 }
521 }));
522 }
523
524 pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static) {
529 self.key_up_listeners
530 .push(Box::new(move |event, phase, window, cx| {
531 if phase == DispatchPhase::Bubble {
532 listener(event, window, cx)
533 }
534 }));
535 }
536
537 pub fn capture_key_up(
542 &mut self,
543 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
544 ) {
545 self.key_up_listeners
546 .push(Box::new(move |event, phase, window, cx| {
547 if phase == DispatchPhase::Capture {
548 listener(event, window, cx)
549 }
550 }));
551 }
552
553 pub fn on_modifiers_changed(
558 &mut self,
559 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
560 ) {
561 self.modifiers_changed_listeners
562 .push(Box::new(move |event, window, cx| {
563 listener(event, window, cx)
564 }));
565 }
566
567 pub fn on_drop<T: 'static>(&mut self, listener: impl Fn(&T, &mut Window, &mut App) + 'static) {
572 self.drop_listeners.push((
573 TypeId::of::<T>(),
574 Box::new(move |dragged_value, window, cx| {
575 listener(dragged_value.downcast_ref().unwrap(), window, cx);
576 }),
577 ));
578 }
579
580 pub fn can_drop(
583 &mut self,
584 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
585 ) {
586 self.can_drop_predicate = Some(Box::new(predicate));
587 }
588
589 pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
594 where
595 Self: Sized,
596 {
597 self.click_listeners.push(Rc::new(move |event, window, cx| {
598 listener(event, window, cx)
599 }));
600 }
601
602 pub fn on_aux_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
607 where
608 Self: Sized,
609 {
610 self.aux_click_listeners
611 .push(Rc::new(move |event, window, cx| {
612 listener(event, window, cx)
613 }));
614 }
615
616 pub fn on_drag<T, W>(
623 &mut self,
624 value: T,
625 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
626 ) where
627 Self: Sized,
628 T: 'static,
629 W: 'static + Render,
630 {
631 debug_assert!(
632 self.drag_listener.is_none(),
633 "calling on_drag more than once on the same element is not supported"
634 );
635 self.drag_listener = Some(DragListener {
636 value: Arc::new(value),
637 render: Box::new(move |value, offset, window, cx| {
638 constructor(value.downcast_ref().unwrap(), offset, window, cx).into()
639 }),
640 external_payload: None,
641 });
642 }
643
644 pub fn external_drag_payload<T>(
649 &mut self,
650 resolver: impl Fn(&T, &mut Window, &mut App) -> Option<ExternalDragPayload> + 'static,
651 ) where
652 Self: Sized,
653 T: 'static,
654 {
655 let Some(drag_listener) = self.drag_listener.as_mut() else {
656 debug_assert!(false, "external_drag_payload must be called after on_drag");
657 return;
658 };
659 debug_assert!(
660 drag_listener.value.as_ref().type_id() == TypeId::of::<T>(),
661 "external_drag_payload must use the same dragged value type as on_drag"
662 );
663 debug_assert!(
664 drag_listener.external_payload.is_none(),
665 "calling external_drag_payload more than once on the same element is not supported"
666 );
667 drag_listener.external_payload = Some(Box::new(move |value, window, cx| {
668 resolver(value.downcast_ref::<T>()?, window, cx)
669 }));
670 }
671
672 pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static)
678 where
679 Self: Sized,
680 {
681 debug_assert!(
682 self.hover_listener.is_none(),
683 "calling on_hover more than once on the same element is not supported"
684 );
685 self.hover_listener = Some(Box::new(listener));
686 }
687
688 pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static)
691 where
692 Self: Sized,
693 {
694 debug_assert!(
695 self.tooltip_builder.is_none(),
696 "calling tooltip more than once on the same element is not supported"
697 );
698 self.tooltip_builder = Some(TooltipBuilder {
699 build: Rc::new(build_tooltip),
700 hoverable: false,
701 });
702 }
703
704 pub fn hoverable_tooltip(
708 &mut self,
709 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
710 ) where
711 Self: Sized,
712 {
713 debug_assert!(
714 self.tooltip_builder.is_none(),
715 "calling tooltip more than once on the same element is not supported"
716 );
717 self.tooltip_builder = Some(TooltipBuilder {
718 build: Rc::new(build_tooltip),
719 hoverable: true,
720 });
721 }
722
723 pub fn tooltip_show_delay(&mut self, delay: Duration) {
726 self.tooltip_show_delay = Some(delay);
727 }
728
729 pub fn occlude_mouse(&mut self) {
734 self.hitbox_behavior = HitboxBehavior::BlockMouse;
735 }
736
737 pub fn window_control_area(&mut self, area: WindowControlArea) {
740 self.window_control = Some(area);
741 }
742
743 pub fn block_mouse_except_scroll(&mut self) {
748 self.hitbox_behavior = HitboxBehavior::BlockMouseExceptScroll;
749 }
750
751 fn has_pinch_listeners(&self) -> bool {
752 !self.pinch_listeners.is_empty()
753 }
754}
755
756pub trait InteractiveElement: Sized {
759 fn interactivity(&mut self) -> &mut Interactivity;
761
762 fn group(mut self, group: impl Into<SharedString>) -> Self {
764 self.interactivity().group = Some(group.into());
765 self
766 }
767
768 fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
770 self.interactivity().element_id = Some(id.into());
771
772 Stateful { element: self }
773 }
774
775 fn track_focus(mut self, focus_handle: &FocusHandle) -> Self {
779 self.interactivity().focusable = true;
780 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
781 self
782 }
783
784 fn tab_stop(mut self, tab_stop: bool) -> Self {
791 self.interactivity().tab_stop = tab_stop;
792 self
793 }
794
795 fn tab_index(mut self, index: isize) -> Self {
800 self.interactivity().focusable = true;
801 self.interactivity().tab_index = Some(index);
802 self.interactivity().tab_stop = true;
803 self
804 }
805
806 fn tab_group(mut self) -> Self {
811 self.interactivity().tab_group = true;
812 if self.interactivity().tab_index.is_none() {
813 self.interactivity().tab_index = Some(0);
814 }
815 self
816 }
817
818 fn key_context<C, E>(mut self, key_context: C) -> Self
821 where
822 C: TryInto<KeyContext, Error = E>,
823 E: std::fmt::Display,
824 {
825 if let Some(key_context) = key_context.try_into().log_err() {
826 self.interactivity().key_context = Some(key_context);
827 }
828 self
829 }
830
831 fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
833 debug_assert!(
834 self.interactivity().hover_style.is_none(),
835 "hover style already set"
836 );
837 self.interactivity().hover_style = Some(Box::new(f(StyleRefinement::default())));
838 self
839 }
840
841 fn group_hover(
843 mut self,
844 group_name: impl Into<SharedString>,
845 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
846 ) -> Self {
847 self.interactivity().group_hover_style = Some(GroupStyle {
848 group: group_name.into(),
849 style: Box::new(f(StyleRefinement::default())),
850 });
851 self
852 }
853
854 fn on_mouse_down(
859 mut self,
860 button: MouseButton,
861 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
862 ) -> Self {
863 self.interactivity().on_mouse_down(button, listener);
864 self
865 }
866
867 fn on_mouse_down_with_pointer_capture(
877 mut self,
878 button: MouseButton,
879 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
880 ) -> Self {
881 self.interactivity()
882 .on_mouse_down_with_pointer_capture(button, listener);
883 self
884 }
885
886 #[cfg(any(test, feature = "test-support"))]
887 fn debug_selector(mut self, f: impl FnOnce() -> String) -> Self {
891 self.interactivity().debug_selector = Some(f());
892 self
893 }
894
895 #[cfg(not(any(test, feature = "test-support")))]
896 #[inline]
900 fn debug_selector(self, _: impl FnOnce() -> String) -> Self {
901 self
902 }
903
904 fn capture_any_mouse_down(
909 mut self,
910 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
911 ) -> Self {
912 self.interactivity().capture_any_mouse_down(listener);
913 self
914 }
915
916 fn on_any_mouse_down(
921 mut self,
922 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
923 ) -> Self {
924 self.interactivity().on_any_mouse_down(listener);
925 self
926 }
927
928 fn on_mouse_up(
933 mut self,
934 button: MouseButton,
935 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
936 ) -> Self {
937 self.interactivity().on_mouse_up(button, listener);
938 self
939 }
940
941 fn capture_any_mouse_up(
946 mut self,
947 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
948 ) -> Self {
949 self.interactivity().capture_any_mouse_up(listener);
950 self
951 }
952
953 fn on_mouse_pressure(
958 mut self,
959 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
960 ) -> Self {
961 self.interactivity().on_mouse_pressure(listener);
962 self
963 }
964
965 fn capture_mouse_pressure(
970 mut self,
971 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
972 ) -> Self {
973 self.interactivity().capture_mouse_pressure(listener);
974 self
975 }
976
977 fn on_mouse_down_out(
983 mut self,
984 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
985 ) -> Self {
986 self.interactivity().on_mouse_down_out(listener);
987 self
988 }
989
990 fn on_mouse_up_out(
996 mut self,
997 button: MouseButton,
998 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
999 ) -> Self {
1000 self.interactivity().on_mouse_up_out(button, listener);
1001 self
1002 }
1003
1004 fn on_mouse_move(
1009 mut self,
1010 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
1011 ) -> Self {
1012 self.interactivity().on_mouse_move(listener);
1013 self
1014 }
1015
1016 fn on_mouse_exit(
1021 mut self,
1022 listener: impl Fn(&MouseExitEvent, &mut Window, &mut App) + 'static,
1023 ) -> Self {
1024 self.interactivity().on_mouse_exit(listener);
1025 self
1026 }
1027
1028 fn on_drag_move<T: 'static>(
1036 mut self,
1037 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
1038 ) -> Self {
1039 self.interactivity().on_drag_move(listener);
1040 self
1041 }
1042
1043 fn on_scroll_wheel(
1048 mut self,
1049 listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
1050 ) -> Self {
1051 self.interactivity().on_scroll_wheel(listener);
1052 self
1053 }
1054
1055 fn on_pinch(mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) -> Self {
1060 self.interactivity().on_pinch(listener);
1061 self
1062 }
1063
1064 fn capture_pinch(
1069 mut self,
1070 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
1071 ) -> Self {
1072 self.interactivity().capture_pinch(listener);
1073 self
1074 }
1075 fn capture_action<A: Action>(
1080 mut self,
1081 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
1082 ) -> Self {
1083 self.interactivity().capture_action(listener);
1084 self
1085 }
1086
1087 #[track_caller]
1092 fn on_action<A: Action>(
1093 mut self,
1094 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
1095 ) -> Self {
1096 self.interactivity().on_action(listener);
1097 self
1098 }
1099
1100 fn on_boxed_action(
1107 mut self,
1108 action: &dyn Action,
1109 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
1110 ) -> Self {
1111 self.interactivity().on_boxed_action(action, listener);
1112 self
1113 }
1114
1115 fn on_key_down(
1120 mut self,
1121 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1122 ) -> Self {
1123 self.interactivity().on_key_down(listener);
1124 self
1125 }
1126
1127 fn capture_key_down(
1132 mut self,
1133 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1134 ) -> Self {
1135 self.interactivity().capture_key_down(listener);
1136 self
1137 }
1138
1139 fn on_key_up(
1144 mut self,
1145 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1146 ) -> Self {
1147 self.interactivity().on_key_up(listener);
1148 self
1149 }
1150
1151 fn capture_key_up(
1156 mut self,
1157 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1158 ) -> Self {
1159 self.interactivity().capture_key_up(listener);
1160 self
1161 }
1162
1163 fn on_modifiers_changed(
1168 mut self,
1169 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
1170 ) -> Self {
1171 self.interactivity().on_modifiers_changed(listener);
1172 self
1173 }
1174
1175 fn drag_over<S: 'static>(
1177 mut self,
1178 f: impl 'static + Fn(StyleRefinement, &S, &mut Window, &mut App) -> StyleRefinement,
1179 ) -> Self {
1180 self.interactivity().drag_over_styles.push((
1181 TypeId::of::<S>(),
1182 Box::new(move |currently_dragged: &dyn Any, window, cx| {
1183 f(
1184 StyleRefinement::default(),
1185 currently_dragged.downcast_ref::<S>().unwrap(),
1186 window,
1187 cx,
1188 )
1189 }),
1190 ));
1191 self
1192 }
1193
1194 fn group_drag_over<S: 'static>(
1196 mut self,
1197 group_name: impl Into<SharedString>,
1198 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1199 ) -> Self {
1200 self.interactivity().group_drag_over_styles.push((
1201 TypeId::of::<S>(),
1202 GroupStyle {
1203 group: group_name.into(),
1204 style: Box::new(f(StyleRefinement::default())),
1205 },
1206 ));
1207 self
1208 }
1209
1210 fn on_drop<T: 'static>(
1215 mut self,
1216 listener: impl Fn(&T, &mut Window, &mut App) + 'static,
1217 ) -> Self {
1218 self.interactivity().on_drop(listener);
1219 self
1220 }
1221
1222 fn can_drop(
1225 mut self,
1226 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
1227 ) -> Self {
1228 self.interactivity().can_drop(predicate);
1229 self
1230 }
1231
1232 fn occlude(mut self) -> Self {
1236 self.interactivity().occlude_mouse();
1237 self
1238 }
1239
1240 fn window_control_area(mut self, area: WindowControlArea) -> Self {
1243 self.interactivity().window_control_area(area);
1244 self
1245 }
1246
1247 fn block_mouse_except_scroll(mut self) -> Self {
1252 self.interactivity().block_mouse_except_scroll();
1253 self
1254 }
1255
1256 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1259 where
1260 Self: Sized,
1261 {
1262 self.interactivity().focus_style = Some(Box::new(f(StyleRefinement::default())));
1263 self
1264 }
1265
1266 fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1269 where
1270 Self: Sized,
1271 {
1272 self.interactivity().in_focus_style = Some(Box::new(f(StyleRefinement::default())));
1273 self
1274 }
1275
1276 fn focus_visible(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1281 where
1282 Self: Sized,
1283 {
1284 self.interactivity().focus_visible_style = Some(Box::new(f(StyleRefinement::default())));
1285 self
1286 }
1287}
1288
1289pub trait StatefulInteractiveElement: InteractiveElement {
1292 fn role(mut self, role: accesskit::Role) -> Self {
1296 debug_assert!(
1297 role != accesskit::Role::GenericContainer,
1298 "GenericContainer is filtered out of the a11y tree and has no effect"
1299 );
1300 self.interactivity().override_role = Some(role);
1301 self
1302 }
1303
1304 fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
1306 self.interactivity().aria.label = Some(label.into());
1307 self
1308 }
1309
1310 fn aria_description(mut self, description: impl Into<SharedString>) -> Self {
1315 self.interactivity().aria.description = Some(description.into());
1316 self
1317 }
1318
1319 fn aria_keyshortcuts(mut self, keyshortcuts: impl Into<SharedString>) -> Self {
1325 self.interactivity().aria.keyshortcuts = Some(keyshortcuts.into());
1326 self
1327 }
1328
1329 fn aria_active_descendant(mut self) -> Self {
1345 self.interactivity().report_active_descendant_focus = true;
1346 self
1347 }
1348
1349 fn a11y_synthetic_children(
1359 mut self,
1360 f: impl FnOnce(&mut crate::A11ySubtreeBuilder) + 'static,
1361 ) -> Self {
1362 self.interactivity().a11y_synthetic_children = Some(Box::new(f));
1363 self
1364 }
1365
1366 fn aria_selected(mut self, selected: bool) -> Self {
1368 self.interactivity().aria.selected = Some(selected);
1369 self
1370 }
1371
1372 fn aria_expanded(mut self, expanded: bool) -> Self {
1374 self.interactivity().aria.expanded = Some(expanded);
1375 self
1376 }
1377
1378 fn aria_disabled(mut self, disabled: bool) -> Self {
1380 self.interactivity().aria.disabled = disabled;
1381 self
1382 }
1383
1384 fn aria_read_only(mut self, read_only: bool) -> Self {
1386 self.interactivity().aria.read_only = read_only;
1387 self
1388 }
1389
1390 fn aria_modal(mut self, modal: bool) -> Self {
1392 self.interactivity().aria.modal = modal;
1393 self
1394 }
1395
1396 fn aria_invalid(mut self, invalid: bool) -> Self {
1398 self.interactivity().aria.invalid = invalid;
1399 self
1400 }
1401
1402 fn aria_required(mut self, required: bool) -> Self {
1404 self.interactivity().aria.required = required;
1405 self
1406 }
1407
1408 fn aria_busy(mut self, busy: bool) -> Self {
1410 self.interactivity().aria.busy = busy;
1411 self
1412 }
1413
1414 fn aria_live(mut self, politeness: accesskit::Live) -> Self {
1416 self.interactivity().aria.live = Some(politeness);
1417 self
1418 }
1419
1420 fn aria_live_atomic(mut self, atomic: bool) -> Self {
1422 self.interactivity().aria.live_atomic = atomic;
1423 self
1424 }
1425
1426 fn aria_toggled(mut self, toggled: accesskit::Toggled) -> Self {
1428 self.interactivity().aria.toggled = Some(toggled);
1429 self
1430 }
1431
1432 fn aria_numeric_value(mut self, value: f64) -> Self {
1434 self.interactivity().aria.numeric_value = Some(value);
1435 self
1436 }
1437
1438 fn aria_numeric_value_step(mut self, step: f64) -> Self {
1441 self.interactivity().aria.numeric_value_step = Some(step);
1442 self
1443 }
1444
1445 fn aria_value(mut self, value: impl Into<SharedString>) -> Self {
1448 self.interactivity().aria.value = Some(value.into());
1449 self
1450 }
1451
1452 fn aria_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
1455 self.interactivity().aria.placeholder = Some(placeholder.into());
1456 self
1457 }
1458
1459 fn aria_min_numeric_value(mut self, value: f64) -> Self {
1461 self.interactivity().aria.min_numeric_value = Some(value);
1462 self
1463 }
1464
1465 fn aria_max_numeric_value(mut self, value: f64) -> Self {
1467 self.interactivity().aria.max_numeric_value = Some(value);
1468 self
1469 }
1470
1471 fn aria_orientation(mut self, orientation: accesskit::Orientation) -> Self {
1473 self.interactivity().aria.orientation = Some(orientation);
1474 self
1475 }
1476
1477 fn aria_level(mut self, level: usize) -> Self {
1479 self.interactivity().aria.level = Some(level);
1480 self
1481 }
1482
1483 fn aria_position_in_set(mut self, position: usize) -> Self {
1485 self.interactivity().aria.position_in_set = Some(position);
1486 self
1487 }
1488
1489 fn aria_size_of_set(mut self, size: usize) -> Self {
1491 self.interactivity().aria.size_of_set = Some(size);
1492 self
1493 }
1494
1495 fn aria_row_index(mut self, index: usize) -> Self {
1497 self.interactivity().aria.row_index = Some(index);
1498 self
1499 }
1500
1501 fn aria_column_index(mut self, index: usize) -> Self {
1503 self.interactivity().aria.column_index = Some(index);
1504 self
1505 }
1506
1507 fn aria_row_count(mut self, count: usize) -> Self {
1509 self.interactivity().aria.row_count = Some(count);
1510 self
1511 }
1512
1513 fn aria_column_count(mut self, count: usize) -> Self {
1515 self.interactivity().aria.column_count = Some(count);
1516 self
1517 }
1518
1519 fn on_a11y_action(
1524 mut self,
1525 action: accesskit::Action,
1526 listener: impl FnMut(Option<&accesskit::ActionData>, &mut crate::Window, &mut crate::App)
1527 + 'static,
1528 ) -> Self {
1529 self.interactivity()
1530 .a11y_action_listeners
1531 .push((action, Box::new(listener)));
1532 self
1533 }
1534
1535 fn focusable(mut self) -> Self {
1537 self.interactivity().focusable = true;
1538 self
1539 }
1540
1541 fn overflow_scroll(mut self) -> Self {
1543 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1544 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1545 self
1546 }
1547
1548 fn overflow_x_scroll(mut self) -> Self {
1550 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1551 self
1552 }
1553
1554 fn overflow_y_scroll(mut self) -> Self {
1556 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1557 self
1558 }
1559
1560 fn restrict_scroll_to_axis(mut self) -> Self {
1564 self.interactivity().base_style.restrict_scroll_to_axis = Some(true);
1565 self
1566 }
1567
1568 fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
1570 self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone());
1571 self
1572 }
1573
1574 fn anchor_scroll(mut self, scroll_anchor: Option<ScrollAnchor>) -> Self {
1576 self.interactivity().scroll_anchor = scroll_anchor;
1577 self
1578 }
1579
1580 fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1582 where
1583 Self: Sized,
1584 {
1585 self.interactivity().active_style = Some(Box::new(f(StyleRefinement::default())));
1586 self
1587 }
1588
1589 fn group_active(
1591 mut self,
1592 group_name: impl Into<SharedString>,
1593 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1594 ) -> Self
1595 where
1596 Self: Sized,
1597 {
1598 self.interactivity().group_active_style = Some(GroupStyle {
1599 group: group_name.into(),
1600 style: Box::new(f(StyleRefinement::default())),
1601 });
1602 self
1603 }
1604
1605 fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self
1610 where
1611 Self: Sized,
1612 {
1613 self.interactivity().on_click(listener);
1614 self
1615 }
1616
1617 fn on_aux_click(
1622 mut self,
1623 listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
1624 ) -> Self
1625 where
1626 Self: Sized,
1627 {
1628 self.interactivity().on_aux_click(listener);
1629 self
1630 }
1631
1632 fn on_drag<T, W>(
1640 mut self,
1641 value: T,
1642 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
1643 ) -> Self
1644 where
1645 Self: Sized,
1646 T: 'static,
1647 W: 'static + Render,
1648 {
1649 self.interactivity().on_drag(value, constructor);
1650 self
1651 }
1652
1653 fn external_drag_payload<T>(
1659 mut self,
1660 resolver: impl Fn(&T, &mut Window, &mut App) -> Option<ExternalDragPayload> + 'static,
1661 ) -> Self
1662 where
1663 Self: Sized,
1664 T: 'static,
1665 {
1666 self.interactivity().external_drag_payload(resolver);
1667 self
1668 }
1669
1670 fn on_hover(mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self
1676 where
1677 Self: Sized,
1678 {
1679 self.interactivity().on_hover(listener);
1680 self
1681 }
1682
1683 fn tooltip(mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self
1686 where
1687 Self: Sized,
1688 {
1689 self.interactivity().tooltip(build_tooltip);
1690 self
1691 }
1692
1693 fn hoverable_tooltip(
1697 mut self,
1698 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
1699 ) -> Self
1700 where
1701 Self: Sized,
1702 {
1703 self.interactivity().hoverable_tooltip(build_tooltip);
1704 self
1705 }
1706
1707 fn tooltip_show_delay(mut self, delay: Duration) -> Self
1710 where
1711 Self: Sized,
1712 {
1713 self.interactivity().tooltip_show_delay(delay);
1714 self
1715 }
1716}
1717
1718pub(crate) type MouseDownListener =
1719 Box<dyn Fn(&MouseDownEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1720pub(crate) type MouseUpListener =
1721 Box<dyn Fn(&MouseUpEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1722pub(crate) type MousePressureListener =
1723 Box<dyn Fn(&MousePressureEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1724pub(crate) type MouseMoveListener =
1725 Box<dyn Fn(&MouseMoveEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1726pub(crate) type MouseExitListener =
1727 Box<dyn Fn(&MouseExitEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1728
1729pub(crate) type ScrollWheelListener =
1730 Box<dyn Fn(&ScrollWheelEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1731
1732pub(crate) type PinchListener =
1733 Box<dyn Fn(&PinchEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1734
1735pub(crate) type ClickListener = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>;
1736
1737pub(crate) struct DragListener {
1738 value: Arc<dyn Any>,
1739 render: Box<dyn Fn(&dyn Any, Point<Pixels>, &mut Window, &mut App) -> AnyView + 'static>,
1740 external_payload: Option<ExternalDragPayloadResolver>,
1741}
1742
1743type ExternalDragPayloadResolver =
1744 Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> Option<ExternalDragPayload> + 'static>;
1745
1746type DropListener = Box<dyn Fn(&dyn Any, &mut Window, &mut App) + 'static>;
1747
1748type CanDropPredicate = Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static>;
1749
1750pub(crate) struct TooltipBuilder {
1751 build: Rc<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>,
1752 hoverable: bool,
1753}
1754
1755pub(crate) type KeyDownListener =
1756 Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1757
1758pub(crate) type KeyUpListener =
1759 Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1760
1761pub(crate) type ModifiersChangedListener =
1762 Box<dyn Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static>;
1763
1764pub(crate) type ActionListener =
1765 Box<dyn Fn(&dyn Any, DispatchPhase, &mut Window, &mut App) + 'static>;
1766
1767#[track_caller]
1769pub fn div() -> Div {
1770 Div {
1771 interactivity: Interactivity::new(),
1772 children: SmallVec::default(),
1773 prepaint_listener: None,
1774 image_cache: None,
1775 prepaint_order_fn: None,
1776 }
1777}
1778
1779pub struct Div {
1781 interactivity: Interactivity,
1782 children: SmallVec<[StackSafe<AnyElement>; 2]>,
1783 prepaint_listener: Option<Box<dyn Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static>>,
1784 image_cache: Option<Box<dyn ImageCacheProvider>>,
1785 prepaint_order_fn: Option<Box<dyn Fn(&mut Window, &mut App) -> SmallVec<[usize; 8]>>>,
1786}
1787
1788impl Div {
1789 pub fn on_children_prepainted(
1792 mut self,
1793 listener: impl Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static,
1794 ) -> Self {
1795 self.prepaint_listener = Some(Box::new(listener));
1796 self
1797 }
1798
1799 pub fn image_cache(mut self, cache: impl ImageCacheProvider) -> Self {
1801 self.image_cache = Some(Box::new(cache));
1802 self
1803 }
1804
1805 pub fn with_dynamic_prepaint_order(
1814 mut self,
1815 order_fn: impl Fn(&mut Window, &mut App) -> SmallVec<[usize; 8]> + 'static,
1816 ) -> Self {
1817 self.prepaint_order_fn = Some(Box::new(order_fn));
1818 self
1819 }
1820}
1821
1822pub struct DivFrameState {
1829 child_layout_ids: SmallVec<[LayoutId; 2]>,
1830}
1831
1832#[derive(Clone)]
1834pub struct DivInspectorState {
1835 #[cfg(any(feature = "inspector", debug_assertions))]
1839 pub base_style: Box<StyleRefinement>,
1840 pub bounds: Bounds<Pixels>,
1842 pub content_size: Size<Pixels>,
1844}
1845
1846impl Styled for Div {
1847 fn style(&mut self) -> &mut StyleRefinement {
1848 &mut self.interactivity.base_style
1849 }
1850}
1851
1852impl InteractiveElement for Div {
1853 fn interactivity(&mut self) -> &mut Interactivity {
1854 &mut self.interactivity
1855 }
1856}
1857
1858impl ParentElement for Div {
1859 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1860 self.children
1861 .extend(elements.into_iter().map(StackSafe::new))
1862 }
1863}
1864
1865impl Element for Div {
1866 type RequestLayoutState = DivFrameState;
1867 type PrepaintState = Option<Hitbox>;
1868
1869 fn id(&self) -> Option<ElementId> {
1870 self.interactivity.element_id.clone()
1871 }
1872
1873 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
1874 self.interactivity.source_location()
1875 }
1876
1877 fn a11y_role(&self) -> Option<accesskit::Role> {
1878 self.interactivity
1881 .override_role
1882 .filter(|role| *role != accesskit::Role::GenericContainer)
1883 }
1884
1885 fn write_a11y_info(&self, node: &mut accesskit::Node) {
1886 self.interactivity.write_a11y_info(node);
1887 }
1888
1889 fn a11y_synthetic_children(
1890 &mut self,
1891 _prepaint: &mut Self::PrepaintState,
1892 builder: &mut crate::A11ySubtreeBuilder,
1893 ) {
1894 if let Some(f) = self.interactivity.a11y_synthetic_children.take() {
1895 f(builder);
1896 }
1897 }
1898
1899 #[stacksafe]
1900 fn request_layout(
1901 &mut self,
1902 global_id: Option<&GlobalElementId>,
1903 inspector_id: Option<&InspectorElementId>,
1904 window: &mut Window,
1905 cx: &mut App,
1906 ) -> (LayoutId, Self::RequestLayoutState) {
1907 let mut child_layout_ids = SmallVec::new();
1908 let image_cache = self
1909 .image_cache
1910 .as_mut()
1911 .map(|provider| provider.provide(window, cx));
1912
1913 let layout_id = window.with_image_cache(image_cache, |window| {
1914 self.interactivity.request_layout(
1915 global_id,
1916 inspector_id,
1917 window,
1918 cx,
1919 |style, window, cx| {
1920 window.with_text_style(style.text_style().cloned(), |window| {
1921 child_layout_ids = self
1922 .children
1923 .iter_mut()
1924 .map(|child| child.request_layout(window, cx))
1925 .collect::<SmallVec<_>>();
1926 window.request_layout(style, child_layout_ids.iter().copied(), cx)
1927 })
1928 },
1929 )
1930 });
1931
1932 (layout_id, DivFrameState { child_layout_ids })
1933 }
1934
1935 #[stacksafe]
1936 fn prepaint(
1937 &mut self,
1938 global_id: Option<&GlobalElementId>,
1939 inspector_id: Option<&InspectorElementId>,
1940 bounds: Bounds<Pixels>,
1941 request_layout: &mut Self::RequestLayoutState,
1942 window: &mut Window,
1943 cx: &mut App,
1944 ) -> Option<Hitbox> {
1945 let image_cache = self
1946 .image_cache
1947 .as_mut()
1948 .map(|provider| provider.provide(window, cx));
1949
1950 let has_prepaint_listener = self.prepaint_listener.is_some();
1951 let mut children_bounds = Vec::with_capacity(if has_prepaint_listener {
1952 request_layout.child_layout_ids.len()
1953 } else {
1954 0
1955 });
1956
1957 let mut child_min = point(Pixels::MAX, Pixels::MAX);
1958 let mut child_max = Point::default();
1959 if let Some(handle) = self.interactivity.scroll_anchor.as_ref() {
1960 *handle.last_origin.borrow_mut() = bounds.origin - window.element_offset();
1961 }
1962 let content_size = if request_layout.child_layout_ids.is_empty() {
1963 bounds.size
1964 } else if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
1965 let mut state = scroll_handle.0.borrow_mut();
1966 state.child_bounds = Vec::with_capacity(request_layout.child_layout_ids.len());
1967 for child_layout_id in &request_layout.child_layout_ids {
1968 let child_bounds = window.layout_bounds(*child_layout_id);
1969 child_min = child_min.min(&child_bounds.origin);
1970 child_max = child_max.max(&child_bounds.bottom_right());
1971 state.child_bounds.push(child_bounds);
1972 }
1973 (child_max - child_min).into()
1974 } else {
1975 for child_layout_id in &request_layout.child_layout_ids {
1976 let child_bounds = window.layout_bounds(*child_layout_id);
1977 child_min = child_min.min(&child_bounds.origin);
1978 child_max = child_max.max(&child_bounds.bottom_right());
1979
1980 if has_prepaint_listener {
1981 children_bounds.push(child_bounds);
1982 }
1983 }
1984 (child_max - child_min).into()
1985 };
1986
1987 self.interactivity.prepaint(
1988 global_id,
1989 inspector_id,
1990 bounds,
1991 content_size,
1992 window,
1993 cx,
1994 |style, scroll_offset, hitbox, window, cx| {
1995 if style.display == Display::None {
1997 return hitbox;
1998 }
1999
2000 window.with_image_cache(image_cache, |window| {
2001 window.with_element_offset(scroll_offset, |window| {
2002 if let Some(order_fn) = &self.prepaint_order_fn {
2003 let order = order_fn(window, cx);
2004 for idx in order {
2005 if let Some(child) = self.children.get_mut(idx) {
2006 child.prepaint(window, cx);
2007 }
2008 }
2009 } else {
2010 for child in &mut self.children {
2011 child.prepaint(window, cx);
2012 }
2013 }
2014 });
2015
2016 if let Some(listener) = self.prepaint_listener.as_ref() {
2017 listener(children_bounds, window, cx);
2018 }
2019 });
2020
2021 hitbox
2022 },
2023 )
2024 }
2025
2026 #[stacksafe]
2027 fn paint(
2028 &mut self,
2029 global_id: Option<&GlobalElementId>,
2030 inspector_id: Option<&InspectorElementId>,
2031 bounds: Bounds<Pixels>,
2032 _request_layout: &mut Self::RequestLayoutState,
2033 hitbox: &mut Option<Hitbox>,
2034 window: &mut Window,
2035 cx: &mut App,
2036 ) {
2037 let image_cache = self
2038 .image_cache
2039 .as_mut()
2040 .map(|provider| provider.provide(window, cx));
2041
2042 window.with_image_cache(image_cache, |window| {
2043 self.interactivity.paint(
2044 global_id,
2045 inspector_id,
2046 bounds,
2047 hitbox.as_ref(),
2048 window,
2049 cx,
2050 |style, window, cx| {
2051 if style.display == Display::None {
2053 return;
2054 }
2055
2056 for child in &mut self.children {
2057 child.paint(window, cx);
2058 }
2059 },
2060 )
2061 });
2062 }
2063}
2064
2065impl IntoElement for Div {
2066 type Element = Self;
2067
2068 fn into_element(self) -> Self::Element {
2069 self
2070 }
2071}
2072
2073#[derive(Default)]
2074pub(crate) struct AriaProperties {
2075 pub(crate) label: Option<SharedString>,
2076 pub(crate) description: Option<SharedString>,
2077 pub(crate) keyshortcuts: Option<SharedString>,
2078 pub(crate) selected: Option<bool>,
2079 pub(crate) expanded: Option<bool>,
2080 pub(crate) disabled: bool,
2081 pub(crate) read_only: bool,
2082 pub(crate) modal: bool,
2083 pub(crate) invalid: bool,
2084 pub(crate) required: bool,
2085 pub(crate) busy: bool,
2086 pub(crate) live: Option<accesskit::Live>,
2087 pub(crate) live_atomic: bool,
2088 pub(crate) toggled: Option<accesskit::Toggled>,
2089 pub(crate) numeric_value: Option<f64>,
2090 pub(crate) min_numeric_value: Option<f64>,
2091 pub(crate) max_numeric_value: Option<f64>,
2092 pub(crate) numeric_value_step: Option<f64>,
2093 pub(crate) value: Option<SharedString>,
2094 pub(crate) placeholder: Option<SharedString>,
2095 pub(crate) orientation: Option<accesskit::Orientation>,
2096 pub(crate) level: Option<usize>,
2097 pub(crate) position_in_set: Option<usize>,
2098 pub(crate) size_of_set: Option<usize>,
2099 pub(crate) row_index: Option<usize>,
2100 pub(crate) column_index: Option<usize>,
2101 pub(crate) row_count: Option<usize>,
2102 pub(crate) column_count: Option<usize>,
2103}
2104
2105#[derive(Default)]
2108pub struct Interactivity {
2109 pub element_id: Option<ElementId>,
2111 pub active: Option<bool>,
2113 pub hovered: Option<bool>,
2116 pub(crate) tooltip_id: Option<TooltipId>,
2117 pub(crate) content_size: Size<Pixels>,
2118 pub(crate) key_context: Option<KeyContext>,
2119 pub(crate) focusable: bool,
2120 pub(crate) tracked_focus_handle: Option<FocusHandle>,
2121 pub(crate) tracked_scroll_handle: Option<ScrollHandle>,
2122 pub(crate) scroll_anchor: Option<ScrollAnchor>,
2123 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
2124 pub(crate) ongoing_scroll: Option<Rc<RefCell<OngoingScroll>>>,
2125 pub(crate) group: Option<SharedString>,
2126 pub base_style: Box<StyleRefinement>,
2129 pub(crate) focus_style: Option<Box<StyleRefinement>>,
2130 pub(crate) in_focus_style: Option<Box<StyleRefinement>>,
2131 pub(crate) focus_visible_style: Option<Box<StyleRefinement>>,
2132 pub(crate) hover_style: Option<Box<StyleRefinement>>,
2133 pub(crate) group_hover_style: Option<GroupStyle>,
2134 pub(crate) active_style: Option<Box<StyleRefinement>>,
2135 pub(crate) group_active_style: Option<GroupStyle>,
2136 pub(crate) drag_over_styles: Vec<(
2137 TypeId,
2138 Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> StyleRefinement>,
2139 )>,
2140 pub(crate) group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
2141 pub(crate) mouse_down_listeners: Vec<MouseDownListener>,
2142 pub(crate) mouse_up_listeners: Vec<MouseUpListener>,
2143 pub(crate) mouse_pressure_listeners: Vec<MousePressureListener>,
2144 pub(crate) mouse_move_listeners: Vec<MouseMoveListener>,
2145 pub(crate) mouse_exit_listeners: Vec<MouseExitListener>,
2146 pub(crate) scroll_wheel_listeners: Vec<ScrollWheelListener>,
2147 pub(crate) pinch_listeners: Vec<PinchListener>,
2148 pub(crate) key_down_listeners: Vec<KeyDownListener>,
2149 pub(crate) key_up_listeners: Vec<KeyUpListener>,
2150 pub(crate) modifiers_changed_listeners: Vec<ModifiersChangedListener>,
2151 pub(crate) action_listeners: Vec<(TypeId, ActionListener)>,
2152 pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
2153 pub(crate) can_drop_predicate: Option<CanDropPredicate>,
2154 pub(crate) click_listeners: Vec<ClickListener>,
2155 pub(crate) aux_click_listeners: Vec<ClickListener>,
2156 pub(crate) drag_listener: Option<DragListener>,
2157 pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut Window, &mut App)>>,
2158 pub(crate) tooltip_builder: Option<TooltipBuilder>,
2159 pub(crate) tooltip_show_delay: Option<Duration>,
2160 pub(crate) window_control: Option<WindowControlArea>,
2161 pub(crate) hitbox_behavior: HitboxBehavior,
2162 pub(crate) tab_index: Option<isize>,
2163 pub(crate) tab_group: bool,
2164 pub(crate) tab_stop: bool,
2165
2166 pub(crate) a11y_action_listeners:
2167 Vec<(accesskit::Action, crate::window::a11y::A11yActionListener)>,
2168 pub(crate) a11y_synthetic_children: Option<Box<dyn FnOnce(&mut crate::A11ySubtreeBuilder)>>,
2169 pub(crate) report_active_descendant_focus: bool,
2170 pub(crate) override_role: Option<accesskit::Role>,
2171 pub(crate) aria: AriaProperties,
2172
2173 #[cfg(any(feature = "inspector", debug_assertions))]
2174 pub(crate) source_location: Option<&'static core::panic::Location<'static>>,
2175
2176 #[cfg(any(test, feature = "test-support"))]
2177 pub(crate) debug_selector: Option<String>,
2178}
2179
2180impl Interactivity {
2181 pub fn request_layout(
2183 &mut self,
2184 global_id: Option<&GlobalElementId>,
2185 _inspector_id: Option<&InspectorElementId>,
2186 window: &mut Window,
2187 cx: &mut App,
2188 f: impl FnOnce(Style, &mut Window, &mut App) -> LayoutId,
2189 ) -> LayoutId {
2190 #[cfg(any(feature = "inspector", debug_assertions))]
2191 window.with_inspector_state(
2192 _inspector_id,
2193 cx,
2194 |inspector_state: &mut Option<DivInspectorState>, _window| {
2195 if let Some(inspector_state) = inspector_state {
2196 self.base_style = inspector_state.base_style.clone();
2197 } else {
2198 *inspector_state = Some(DivInspectorState {
2199 base_style: self.base_style.clone(),
2200 bounds: Default::default(),
2201 content_size: Default::default(),
2202 })
2203 }
2204 },
2205 );
2206
2207 window.with_optional_element_state::<InteractiveElementState, _>(
2208 global_id,
2209 |element_state, window| {
2210 let mut element_state =
2211 element_state.map(|element_state| element_state.unwrap_or_default());
2212
2213 if let Some(element_state) = element_state.as_ref()
2214 && cx.has_active_drag()
2215 {
2216 if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() {
2217 *pending_mouse_down.borrow_mut() = None;
2218 }
2219 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
2220 *clicked_state.borrow_mut() = ElementClickedState::default();
2221 }
2222 }
2223
2224 if self.focusable
2229 && self.tracked_focus_handle.is_none()
2230 && let Some(element_state) = element_state.as_mut()
2231 {
2232 let mut handle = element_state
2233 .focus_handle
2234 .get_or_insert_with(|| cx.focus_handle())
2235 .clone()
2236 .tab_stop(self.tab_stop);
2237
2238 if let Some(index) = self.tab_index {
2239 handle = handle.tab_index(index);
2240 }
2241
2242 self.tracked_focus_handle = Some(handle);
2243 }
2244
2245 if let Some(scroll_handle) = self.tracked_scroll_handle.as_ref() {
2246 let scroll_handle_state = scroll_handle.0.borrow();
2247 self.scroll_offset = Some(scroll_handle_state.offset.clone());
2248 self.ongoing_scroll = Some(scroll_handle_state.ongoing_scroll.clone());
2249 } else if (self.base_style.overflow.x == Some(Overflow::Scroll)
2250 || self.base_style.overflow.y == Some(Overflow::Scroll))
2251 && let Some(element_state) = element_state.as_mut()
2252 {
2253 self.scroll_offset = Some(
2254 element_state
2255 .scroll_offset
2256 .get_or_insert_with(Rc::default)
2257 .clone(),
2258 );
2259 self.ongoing_scroll = Some(
2260 element_state
2261 .ongoing_scroll
2262 .get_or_insert_with(|| Rc::new(RefCell::new(OngoingScroll::default())))
2263 .clone(),
2264 );
2265 }
2266
2267 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
2268 let layout_id = f(style, window, cx);
2269 (layout_id, element_state)
2270 },
2271 )
2272 }
2273
2274 pub fn prepaint<R>(
2276 &mut self,
2277 global_id: Option<&GlobalElementId>,
2278 _inspector_id: Option<&InspectorElementId>,
2279 bounds: Bounds<Pixels>,
2280 content_size: Size<Pixels>,
2281 window: &mut Window,
2282 cx: &mut App,
2283 f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut Window, &mut App) -> R,
2284 ) -> R {
2285 self.content_size = content_size;
2286
2287 #[cfg(any(feature = "inspector", debug_assertions))]
2288 window.with_inspector_state(
2289 _inspector_id,
2290 cx,
2291 |inspector_state: &mut Option<DivInspectorState>, _window| {
2292 if let Some(inspector_state) = inspector_state {
2293 inspector_state.bounds = bounds;
2294 inspector_state.content_size = content_size;
2295 }
2296 },
2297 );
2298
2299 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
2300 window.set_focus_handle(focus_handle, cx);
2301
2302 if window.a11y.is_active() {
2303 if let Some(global_id) = global_id {
2304 let node_id = global_id.accesskit_node_id();
2305 window.a11y.set_focusable(node_id, focus_handle.id);
2306 if focus_handle.is_focused(window) {
2307 window.a11y.set_focus(node_id);
2308 }
2309 } else if focus_handle.is_focused(window) {
2310 window
2314 .a11y
2315 .note_focus_without_node(focus_handle.id, "it has no element id");
2316 }
2317 }
2318 }
2319
2320 if self.report_active_descendant_focus && window.a11y.is_active() {
2321 if let Some(global_id) = global_id {
2322 window
2323 .a11y
2324 .set_active_descendant(global_id.accesskit_node_id());
2325 }
2326 }
2327 window.with_optional_element_state::<InteractiveElementState, _>(
2328 global_id,
2329 |element_state, window| {
2330 let mut element_state =
2331 element_state.map(|element_state| element_state.unwrap_or_default());
2332 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
2333
2334 if let Some(element_state) = element_state.as_mut() {
2335 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
2336 let clicked_state = clicked_state.borrow();
2337 self.active = Some(clicked_state.element);
2338 }
2339 if self.hover_style.is_some() || self.group_hover_style.is_some() {
2340 element_state
2341 .hover_state
2342 .get_or_insert_with(Default::default);
2343 }
2344 if let Some(active_tooltip) = element_state.active_tooltip.as_ref() {
2345 if self.tooltip_builder.is_some() {
2346 self.tooltip_id = set_tooltip_on_window(active_tooltip, window);
2347 } else {
2348 element_state.active_tooltip.take();
2350 }
2351 }
2352 }
2353
2354 window.with_text_style(style.text_style().cloned(), |window| {
2355 window.with_content_mask(
2356 style.overflow_mask(bounds, window.rem_size()),
2357 |window| {
2358 let hitbox = if self.should_insert_hitbox(&style, window, cx) {
2359 Some(window.insert_hitbox(bounds, self.hitbox_behavior))
2360 } else {
2361 None
2362 };
2363 if let (Some(global_id), Some(hitbox)) = (global_id, hitbox.as_ref()) {
2364 window.register_pointer_capture_hitbox(global_id, hitbox.id);
2365 }
2366
2367 let scroll_offset =
2368 self.clamp_scroll_position(bounds, &style, window, cx);
2369 let result = f(&style, scroll_offset, hitbox, window, cx);
2370 (result, element_state)
2371 },
2372 )
2373 })
2374 },
2375 )
2376 }
2377
2378 fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
2379 self.hitbox_behavior != HitboxBehavior::Normal
2380 || self.window_control.is_some()
2381 || style.mouse_cursor.is_some()
2382 || self.group.is_some()
2383 || self.scroll_offset.is_some()
2384 || self.tracked_focus_handle.is_some()
2385 || self.hover_style.is_some()
2386 || self.group_hover_style.is_some()
2387 || self.hover_listener.is_some()
2388 || !self.mouse_up_listeners.is_empty()
2389 || !self.mouse_pressure_listeners.is_empty()
2390 || !self.mouse_down_listeners.is_empty()
2391 || !self.mouse_move_listeners.is_empty()
2392 || !self.mouse_exit_listeners.is_empty()
2393 || !self.click_listeners.is_empty()
2394 || !self.aux_click_listeners.is_empty()
2395 || !self.scroll_wheel_listeners.is_empty()
2396 || self.has_pinch_listeners()
2397 || self.drag_listener.is_some()
2398 || !self.drop_listeners.is_empty()
2399 || !self.drag_over_styles.is_empty()
2400 || self.tooltip_builder.is_some()
2401 || window.is_inspector_picking(cx)
2402 }
2403
2404 fn clamp_scroll_position(
2405 &self,
2406 bounds: Bounds<Pixels>,
2407 style: &Style,
2408 window: &mut Window,
2409 _cx: &mut App,
2410 ) -> Point<Pixels> {
2411 fn round_to_two_decimals(pixels: Pixels) -> Pixels {
2412 const ROUNDING_FACTOR: f32 = 100.0;
2413 (pixels * ROUNDING_FACTOR).round() / ROUNDING_FACTOR
2414 }
2415
2416 if let Some(scroll_offset) = self.scroll_offset.as_ref() {
2417 let scroll_to_bottom = if let Some(scroll_handle) = &self.tracked_scroll_handle {
2418 let mut scroll_handle_state = scroll_handle.0.borrow_mut();
2419 scroll_handle_state.overflow = style.overflow;
2420 mem::take(&mut scroll_handle_state.scroll_to_bottom)
2421 } else {
2422 false
2423 };
2424
2425 let rem_size = window.rem_size();
2426 let padding = style.padding.to_pixels(bounds.size.into(), rem_size);
2427 let padding_size = size(padding.left + padding.right, padding.top + padding.bottom);
2428 let padded_content_size = self.content_size + padding_size;
2435 let scroll_max = Point::from(padded_content_size - bounds.size)
2436 .map(round_to_two_decimals)
2437 .max(&Default::default());
2438 if let Some(scroll_handle) = &self.tracked_scroll_handle {
2439 {
2440 let mut scroll_handle_state = scroll_handle.0.borrow_mut();
2441 scroll_handle_state.max_offset = scroll_max;
2442 scroll_handle_state.bounds = bounds;
2443 }
2444 scroll_handle.scroll_to_active_item();
2445 }
2446 let mut scroll_offset = scroll_offset.borrow_mut();
2449
2450 scroll_offset.x = scroll_offset.x.clamp(-scroll_max.x, px(0.));
2451 if scroll_to_bottom {
2452 scroll_offset.y = -scroll_max.y;
2453 } else {
2454 scroll_offset.y = scroll_offset.y.clamp(-scroll_max.y, px(0.));
2455 }
2456
2457 *scroll_offset
2458 } else {
2459 Point::default()
2460 }
2461 }
2462
2463 pub fn paint(
2472 &mut self,
2473 global_id: Option<&GlobalElementId>,
2474 _inspector_id: Option<&InspectorElementId>,
2475 bounds: Bounds<Pixels>,
2476 hitbox: Option<&Hitbox>,
2477 window: &mut Window,
2478 cx: &mut App,
2479 f: impl FnOnce(&Style, &mut Window, &mut App),
2480 ) {
2481 self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(window));
2482 window.with_optional_element_state::<InteractiveElementState, _>(
2483 global_id,
2484 |element_state, window| {
2485 let mut element_state =
2486 element_state.map(|element_state| element_state.unwrap_or_default());
2487
2488 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
2489
2490 #[cfg(any(feature = "test-support", test))]
2491 if let Some(debug_selector) = &self.debug_selector {
2492 window
2493 .next_frame
2494 .debug_bounds
2495 .insert(debug_selector.clone(), bounds);
2496 }
2497
2498 self.paint_hover_group_handler(window, cx);
2499
2500 if style.visibility == Visibility::Hidden {
2501 return ((), element_state);
2502 }
2503
2504 let mut tab_group = None;
2505 if self.tab_group {
2506 tab_group = self.tab_index;
2507 }
2508
2509 window.with_element_opacity(style.opacity, |window| {
2510 style.paint(bounds, window, cx, |window: &mut Window, cx: &mut App| {
2511 window.with_text_style(style.text_style().cloned(), |window| {
2512 window.with_content_mask(
2513 style.overflow_mask(bounds, window.rem_size()),
2514 |window| {
2515 window.with_tab_group(tab_group, |window| {
2516 if let Some(focus_handle) = &self.tracked_focus_handle {
2525 window.next_frame.tab_stops.insert(focus_handle);
2526 }
2527 if let Some(hitbox) = hitbox {
2528 #[cfg(debug_assertions)]
2529 self.paint_debug_info(
2530 global_id, hitbox, &style, window, cx,
2531 );
2532
2533 if let Some(drag) = cx.active_drag.as_ref() {
2534 if let Some(mouse_cursor) = drag.cursor_style {
2535 window.set_window_cursor_style(mouse_cursor);
2536 }
2537 } else {
2538 if let Some(mouse_cursor) = style.mouse_cursor {
2539 window.set_cursor_style(mouse_cursor, hitbox);
2540 }
2541 }
2542
2543 if let Some(group) = self.group.clone() {
2544 GroupHitboxes::push(group, hitbox.id, cx);
2545 }
2546
2547 if let Some(area) = self.window_control {
2548 window.insert_window_control_hitbox(
2549 area,
2550 hitbox.clone(),
2551 );
2552 }
2553
2554 self.paint_mouse_listeners(
2555 hitbox,
2556 element_state.as_mut(),
2557 window,
2558 cx,
2559 );
2560 self.paint_scroll_listener(hitbox, &style, window, cx);
2561 }
2562
2563 self.paint_keyboard_listeners(window, cx);
2564
2565 if window.a11y.is_active() {
2566 if let Some(global_id) = global_id {
2567 if !self.a11y_action_listeners.is_empty() {
2568 let node_id = global_id.accesskit_node_id();
2569 for (action, listener) in
2570 self.a11y_action_listeners.drain(..)
2571 {
2572 window.on_a11y_action(
2573 node_id, action, listener,
2574 );
2575 }
2576 }
2577 }
2578 }
2579
2580 f(&style, window, cx);
2581
2582 if let Some(_hitbox) = hitbox {
2583 #[cfg(any(feature = "inspector", debug_assertions))]
2584 window.insert_inspector_hitbox(
2585 _hitbox.id,
2586 _inspector_id,
2587 cx,
2588 );
2589
2590 if let Some(group) = self.group.as_ref() {
2591 GroupHitboxes::pop(group, cx);
2592 }
2593 }
2594 })
2595 },
2596 );
2597 });
2598 });
2599 });
2600
2601 ((), element_state)
2602 },
2603 );
2604 }
2605
2606 #[cfg(debug_assertions)]
2607 fn paint_debug_info(
2608 &self,
2609 global_id: Option<&GlobalElementId>,
2610 hitbox: &Hitbox,
2611 style: &Style,
2612 window: &mut Window,
2613 cx: &mut App,
2614 ) {
2615 use crate::{BorderStyle, TextAlign};
2616
2617 if let Some(global_id) = global_id
2618 && (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
2619 && hitbox.is_hovered(window)
2620 {
2621 const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
2622 let element_id = format!("{global_id:?}");
2623 let str_len = element_id.len();
2624
2625 let render_debug_text = |window: &mut Window| {
2626 if let Some(text) = window
2627 .text_system()
2628 .shape_text(
2629 element_id.into(),
2630 FONT_SIZE,
2631 &[window.text_style().to_run(str_len)],
2632 None,
2633 None,
2634 )
2635 .ok()
2636 .and_then(|mut text| text.pop())
2637 {
2638 text.paint(hitbox.origin, FONT_SIZE, TextAlign::Left, None, window, cx)
2639 .ok();
2640
2641 let text_bounds = crate::Bounds {
2642 origin: hitbox.origin,
2643 size: text.size(FONT_SIZE),
2644 };
2645 if let Some(source_location) = self.source_location
2646 && text_bounds.contains(&window.mouse_position())
2647 && window.modifiers().secondary()
2648 {
2649 let secondary_held = window.modifiers().secondary();
2650 window.on_key_event({
2651 move |e: &crate::ModifiersChangedEvent, _phase, window, _cx| {
2652 if e.modifiers.secondary() != secondary_held
2653 && text_bounds.contains(&window.mouse_position())
2654 {
2655 window.refresh();
2656 }
2657 }
2658 });
2659
2660 let was_hovered = hitbox.is_hovered(window);
2661 let current_view = window.current_view();
2662 window.on_mouse_event({
2663 let hitbox = hitbox.clone();
2664 move |_: &MouseMoveEvent, phase, window, cx| {
2665 if phase == DispatchPhase::Capture {
2666 let hovered = hitbox.is_hovered(window);
2667 if hovered != was_hovered {
2668 cx.notify(current_view)
2669 }
2670 }
2671 }
2672 });
2673
2674 window.on_mouse_event({
2675 let hitbox = hitbox.clone();
2676 move |e: &crate::MouseDownEvent, phase, window, cx| {
2677 if text_bounds.contains(&e.position)
2678 && phase.capture()
2679 && hitbox.is_hovered(window)
2680 {
2681 cx.stop_propagation();
2682 let Ok(dir) = std::env::current_dir() else {
2683 return;
2684 };
2685
2686 eprintln!(
2687 "This element was created at:\n{}:{}:{}",
2688 dir.join(source_location.file()).to_string_lossy(),
2689 source_location.line(),
2690 source_location.column()
2691 );
2692 }
2693 }
2694 });
2695 window.paint_quad(crate::outline(
2696 crate::Bounds {
2697 origin: hitbox.origin
2698 + crate::point(crate::px(0.), FONT_SIZE - px(2.)),
2699 size: crate::Size {
2700 width: text_bounds.size.width,
2701 height: crate::px(1.),
2702 },
2703 },
2704 crate::red(),
2705 BorderStyle::default(),
2706 ))
2707 }
2708 }
2709 };
2710
2711 window.with_text_style(
2712 Some(crate::TextStyleRefinement {
2713 color: Some(crate::red()),
2714 line_height: Some(FONT_SIZE.into()),
2715 background_color: Some(crate::white()),
2716 ..Default::default()
2717 }),
2718 render_debug_text,
2719 )
2720 }
2721 }
2722
2723 fn paint_mouse_listeners(
2724 &mut self,
2725 hitbox: &Hitbox,
2726 element_state: Option<&mut InteractiveElementState>,
2727 window: &mut Window,
2728 cx: &mut App,
2729 ) {
2730 let is_focused = self
2731 .tracked_focus_handle
2732 .as_ref()
2733 .map(|handle| handle.is_focused(window))
2734 .unwrap_or(false);
2735
2736 if let Some(focus_handle) = self.tracked_focus_handle.clone() {
2740 let hitbox = hitbox.clone();
2741 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, cx| {
2742 if phase == DispatchPhase::Bubble
2743 && hitbox.is_hovered(window)
2744 && !window.default_prevented()
2745 {
2746 window.focus(&focus_handle, cx);
2747 window.prevent_default();
2750 }
2751 });
2752 }
2753
2754 for listener in self.mouse_down_listeners.drain(..) {
2755 let hitbox = hitbox.clone();
2756 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
2757 listener(event, phase, &hitbox, window, cx);
2758 })
2759 }
2760
2761 for listener in self.mouse_up_listeners.drain(..) {
2762 let hitbox = hitbox.clone();
2763 window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
2764 listener(event, phase, &hitbox, window, cx);
2765 })
2766 }
2767
2768 for listener in self.mouse_pressure_listeners.drain(..) {
2769 let hitbox = hitbox.clone();
2770 window.on_mouse_event(move |event: &MousePressureEvent, phase, window, cx| {
2771 listener(event, phase, &hitbox, window, cx);
2772 })
2773 }
2774
2775 for listener in self.mouse_move_listeners.drain(..) {
2776 let hitbox = hitbox.clone();
2777 window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
2778 listener(event, phase, &hitbox, window, cx);
2779 })
2780 }
2781
2782 for listener in self.mouse_exit_listeners.drain(..) {
2783 let hitbox = hitbox.clone();
2784 window.on_mouse_event(move |event: &MouseExitEvent, phase, window, cx| {
2785 listener(event, phase, &hitbox, window, cx);
2786 })
2787 }
2788
2789 for listener in self.scroll_wheel_listeners.drain(..) {
2790 let hitbox = hitbox.clone();
2791 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
2792 listener(event, phase, &hitbox, window, cx);
2793 })
2794 }
2795
2796 for listener in self.pinch_listeners.drain(..) {
2797 let hitbox = hitbox.clone();
2798 window.on_mouse_event(move |event: &PinchEvent, phase, window, cx| {
2799 listener(event, phase, &hitbox, window, cx);
2800 })
2801 }
2802
2803 if self.hover_style.is_some()
2804 || self.base_style.mouse_cursor.is_some()
2805 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
2806 {
2807 let hitbox = hitbox.clone();
2808 let hover_state = self.hover_style.as_ref().and_then(|_| {
2809 element_state
2810 .as_ref()
2811 .and_then(|state| state.hover_state.as_ref())
2812 .cloned()
2813 });
2814 let current_view = window.current_view();
2815
2816 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2817 let hovered = hitbox.is_hovered(window);
2818 let was_hovered = hover_state
2819 .as_ref()
2820 .is_some_and(|state| state.borrow().element);
2821 if phase == DispatchPhase::Capture && hovered != was_hovered {
2822 if let Some(hover_state) = &hover_state {
2823 hover_state.borrow_mut().element = hovered;
2824 cx.notify(current_view);
2825 }
2826 }
2827 });
2828 }
2829
2830 if let Some(group_hover) = self.group_hover_style.as_ref() {
2831 if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
2832 let hover_state = element_state
2833 .as_ref()
2834 .and_then(|element| element.hover_state.as_ref())
2835 .cloned();
2836 let current_view = window.current_view();
2837
2838 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2839 let group_hovered = group_hitbox_id.is_hovered(window);
2840 let was_group_hovered = hover_state
2841 .as_ref()
2842 .is_some_and(|state| state.borrow().group);
2843 if phase == DispatchPhase::Capture && group_hovered != was_group_hovered {
2844 if let Some(hover_state) = &hover_state {
2845 hover_state.borrow_mut().group = group_hovered;
2846 cx.notify(current_view);
2847 }
2848 }
2849 });
2850 }
2851 }
2852
2853 let drag_cursor_style = self.base_style.as_ref().mouse_cursor;
2854
2855 let mut drag_listener = mem::take(&mut self.drag_listener);
2856 let drop_listeners = mem::take(&mut self.drop_listeners);
2857 let click_listeners = mem::take(&mut self.click_listeners);
2858 let aux_click_listeners = mem::take(&mut self.aux_click_listeners);
2859 let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
2860
2861 if !drop_listeners.is_empty() {
2862 let hitbox = hitbox.clone();
2863 window.on_mouse_event({
2864 move |_: &MouseUpEvent, phase, window, cx| {
2865 if let Some(drag) = &cx.active_drag
2866 && phase == DispatchPhase::Bubble
2867 && hitbox.is_hovered(window)
2868 {
2869 let drag_state_type = drag.value.as_ref().type_id();
2870 for (drop_state_type, listener) in &drop_listeners {
2871 if *drop_state_type == drag_state_type {
2872 let drag = cx
2873 .active_drag
2874 .take()
2875 .expect("checked for type drag state type above");
2876
2877 let mut can_drop = true;
2878 if let Some(predicate) = &can_drop_predicate {
2879 can_drop = predicate(drag.value.as_ref(), window, cx);
2880 }
2881
2882 if can_drop {
2883 listener(drag.value.as_ref(), window, cx);
2884 window.refresh();
2885 cx.stop_propagation();
2886 }
2887 }
2888 }
2889 }
2890 }
2891 });
2892 }
2893
2894 if let Some(element_state) = element_state {
2895 if !click_listeners.is_empty()
2896 || !aux_click_listeners.is_empty()
2897 || drag_listener.is_some()
2898 {
2899 let pending_mouse_down = element_state
2900 .pending_mouse_down
2901 .get_or_insert_with(Default::default)
2902 .clone();
2903
2904 let pending_keyboard_down = element_state
2905 .pending_keyboard_down
2906 .get_or_insert_with(Default::default)
2907 .clone();
2908
2909 let clicked_state = element_state
2910 .clicked_state
2911 .get_or_insert_with(Default::default)
2912 .clone();
2913
2914 window.on_mouse_event({
2915 let pending_mouse_down = pending_mouse_down.clone();
2916 let hitbox = hitbox.clone();
2917 let has_aux_click_listeners = !aux_click_listeners.is_empty();
2918 move |event: &MouseDownEvent, phase, window, _cx| {
2919 if phase == DispatchPhase::Bubble
2920 && (event.button == MouseButton::Left || has_aux_click_listeners)
2921 && hitbox.is_hovered(window)
2922 {
2923 *pending_mouse_down.borrow_mut() = Some(event.clone());
2924 window.refresh();
2925 }
2926 }
2927 });
2928
2929 window.on_mouse_event({
2930 let pending_mouse_down = pending_mouse_down.clone();
2931 let hitbox = hitbox.clone();
2932 move |event: &MouseMoveEvent, phase, window, cx| {
2933 if phase == DispatchPhase::Capture {
2934 return;
2935 }
2936
2937 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2938 if let Some(mouse_down) = pending_mouse_down.clone()
2939 && !cx.has_active_drag()
2940 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
2941 && let Some(listener) = drag_listener.take()
2942 && mouse_down.button == MouseButton::Left
2943 {
2944 *clicked_state.borrow_mut() = ElementClickedState::default();
2945 let cursor_offset = event.position - hitbox.origin;
2946 let drag = (listener.render)(
2947 listener.value.as_ref(),
2948 cursor_offset,
2949 window,
2950 cx,
2951 );
2952 let external_payload_source =
2953 listener.external_payload.map(|external_payload| {
2954 let value = listener.value.clone();
2955 Box::new(move |window: &mut Window, cx: &mut App| {
2956 external_payload(value.as_ref(), window, cx)
2957 })
2958 as ExternalDragPayloadSource
2959 });
2960 cx.active_drag = Some(AnyDrag {
2961 view: drag,
2962 value: listener.value,
2963 cursor_offset,
2964 cursor_style: drag_cursor_style,
2965 external_payload_source,
2966 });
2967 pending_mouse_down.take();
2968 window.refresh();
2969 cx.stop_propagation();
2970 }
2971 }
2972 });
2973
2974 if is_focused {
2975 window.on_key_event({
2985 let pending_keyboard_down = pending_keyboard_down.clone();
2986 move |event: &KeyDownEvent, phase, window, _cx| {
2987 if phase.bubble() && !window.default_prevented() {
2988 let stroke = &event.keystroke;
2989 let is_activation_key = (stroke.key.eq("enter")
2990 || stroke.key.eq("space"))
2991 && !stroke.modifiers.modified();
2992 *pending_keyboard_down.borrow_mut() =
2993 is_activation_key.then_some(window.focus_generation);
2994 }
2995 }
2996 });
2997
2998 window.on_key_event({
3000 let click_listeners = click_listeners.clone();
3001 let hitbox = hitbox.clone();
3002 move |event: &KeyUpEvent, phase, window, cx| {
3003 if phase.bubble() && !window.default_prevented() {
3004 let stroke = &event.keystroke;
3005 let keyboard_button = if stroke.key.eq("enter") {
3006 Some(KeyboardButton::Enter)
3007 } else if stroke.key.eq("space") {
3008 Some(KeyboardButton::Space)
3009 } else {
3010 None
3011 };
3012
3013 if let Some(button) = keyboard_button
3014 && !stroke.modifiers.modified()
3015 {
3016 let pending =
3017 std::mem::take(&mut *pending_keyboard_down.borrow_mut());
3018 if pending != Some(window.focus_generation) {
3019 return;
3020 }
3021
3022 let click_event = ClickEvent::Keyboard(KeyboardClickEvent {
3023 button,
3024 bounds: hitbox.bounds,
3025 });
3026
3027 for listener in &click_listeners {
3028 listener(&click_event, window, cx);
3029 }
3030 } else {
3031 *pending_keyboard_down.borrow_mut() = None;
3035 }
3036 }
3037 }
3038 });
3039 }
3040
3041 window.on_mouse_event({
3042 let mut captured_mouse_down = None;
3043 let hitbox = hitbox.clone();
3044 move |event: &MouseUpEvent, phase, window, cx| match phase {
3045 DispatchPhase::Capture => {
3049 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
3050 if pending_mouse_down.is_some() && hitbox.is_hovered(window) {
3051 captured_mouse_down = pending_mouse_down.take();
3052 window.refresh();
3053 } else if pending_mouse_down.is_some() {
3054 pending_mouse_down.take();
3060 window.refresh();
3061 }
3062 }
3063 DispatchPhase::Bubble => {
3065 if let Some(mouse_down) = captured_mouse_down.take() {
3066 let btn = mouse_down.button;
3067
3068 let mouse_click = ClickEvent::Mouse(MouseClickEvent {
3069 down: mouse_down,
3070 up: event.clone(),
3071 });
3072
3073 match btn {
3074 MouseButton::Left => {
3075 for listener in &click_listeners {
3076 listener(&mouse_click, window, cx);
3077 }
3078 }
3079 _ => {
3080 for listener in &aux_click_listeners {
3081 listener(&mouse_click, window, cx);
3082 }
3083 }
3084 }
3085 }
3086 }
3087 }
3088 });
3089 }
3090
3091 if let Some(hover_listener) = self.hover_listener.take() {
3092 let was_hovered = element_state
3093 .hover_listener_state
3094 .get_or_insert_with(Default::default)
3095 .clone();
3096 let has_mouse_down = element_state
3097 .pending_mouse_down
3098 .get_or_insert_with(Default::default)
3099 .clone();
3100 let hover_listener = Rc::new(hover_listener);
3101 let update_hover = move |is_hovered: bool, window: &mut Window, cx: &mut App| {
3102 let mut was_hovered = was_hovered.borrow_mut();
3103 if is_hovered != *was_hovered {
3104 *was_hovered = is_hovered;
3105 drop(was_hovered);
3106 hover_listener(&is_hovered, window, cx);
3107 }
3108 };
3109
3110 window.on_mouse_event({
3111 let update_hover = update_hover.clone();
3112 let hitbox = hitbox.clone();
3113 move |_: &MouseMoveEvent, phase, window, cx| {
3114 if phase == DispatchPhase::Bubble {
3115 let is_hovered = has_mouse_down.borrow().is_none()
3116 && !cx.has_active_drag()
3117 && hitbox.is_hovered(window);
3118 update_hover(is_hovered, window, cx);
3119 }
3120 }
3121 });
3122
3123 window.on_mouse_event(move |_: &MouseExitEvent, phase, window, cx| {
3126 if phase == DispatchPhase::Bubble {
3127 update_hover(false, window, cx);
3128 }
3129 });
3130 }
3131
3132 if let Some(tooltip_builder) = self.tooltip_builder.take() {
3133 let active_tooltip = element_state
3134 .active_tooltip
3135 .get_or_insert_with(Default::default)
3136 .clone();
3137 let pending_mouse_down = element_state
3138 .pending_mouse_down
3139 .get_or_insert_with(Default::default)
3140 .clone();
3141
3142 let tooltip_is_hoverable = tooltip_builder.hoverable;
3143 let build_tooltip = Rc::new(move |window: &mut Window, cx: &mut App| {
3144 Some(((tooltip_builder.build)(window, cx), tooltip_is_hoverable))
3145 });
3146 let check_is_hovered_during_prepaint = Rc::new({
3148 let pending_mouse_down = pending_mouse_down.clone();
3149 let source_bounds = hitbox.bounds;
3150 move |window: &Window| {
3151 !window.last_input_was_keyboard()
3152 && pending_mouse_down.borrow().is_none()
3153 && source_bounds.contains(&window.mouse_position())
3154 }
3155 });
3156 let check_is_hovered = Rc::new({
3157 let hitbox = hitbox.clone();
3158 move |window: &Window| {
3159 pending_mouse_down.borrow().is_none() && hitbox.is_hovered(window)
3160 }
3161 });
3162 register_tooltip_mouse_handlers(
3163 &active_tooltip,
3164 self.tooltip_id,
3165 build_tooltip,
3166 check_is_hovered,
3167 check_is_hovered_during_prepaint,
3168 self.tooltip_show_delay,
3169 window,
3170 );
3171 }
3172
3173 let active_state = element_state
3176 .clicked_state
3177 .get_or_insert_with(Default::default)
3178 .clone();
3179
3180 {
3181 let active_state = active_state.clone();
3182 window.on_mouse_event(move |_: &MouseUpEvent, phase, window, _cx| {
3183 if phase == DispatchPhase::Capture && active_state.borrow().is_clicked() {
3184 *active_state.borrow_mut() = ElementClickedState::default();
3185 window.refresh();
3186 }
3187 });
3188 }
3189
3190 {
3191 let active_group_hitbox = self
3192 .group_active_style
3193 .as_ref()
3194 .and_then(|group_active| GroupHitboxes::get(&group_active.group, cx));
3195 let hitbox = hitbox.clone();
3196 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _cx| {
3197 if phase == DispatchPhase::Bubble && !window.default_prevented() {
3198 let group_hovered = active_group_hitbox
3199 .is_some_and(|group_hitbox_id| group_hitbox_id.is_hovered(window));
3200 let element_hovered = hitbox.is_hovered(window);
3201 if group_hovered || element_hovered {
3202 *active_state.borrow_mut() = ElementClickedState {
3203 group: group_hovered,
3204 element: element_hovered,
3205 };
3206 window.refresh();
3207 }
3208 }
3209 });
3210 }
3211 }
3212 }
3213
3214 fn paint_keyboard_listeners(&mut self, window: &mut Window, _cx: &mut App) {
3215 let key_down_listeners = mem::take(&mut self.key_down_listeners);
3216 let key_up_listeners = mem::take(&mut self.key_up_listeners);
3217 let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners);
3218 let action_listeners = mem::take(&mut self.action_listeners);
3219 if let Some(context) = self.key_context.clone() {
3220 window.set_key_context(context);
3221 }
3222
3223 for listener in key_down_listeners {
3224 window.on_key_event(move |event: &KeyDownEvent, phase, window, cx| {
3225 listener(event, phase, window, cx);
3226 })
3227 }
3228
3229 for listener in key_up_listeners {
3230 window.on_key_event(move |event: &KeyUpEvent, phase, window, cx| {
3231 listener(event, phase, window, cx);
3232 })
3233 }
3234
3235 for listener in modifiers_changed_listeners {
3236 window.on_modifiers_changed(move |event: &ModifiersChangedEvent, window, cx| {
3237 listener(event, window, cx);
3238 })
3239 }
3240
3241 for (action_type, listener) in action_listeners {
3242 window.on_action(action_type, listener)
3243 }
3244 }
3245
3246 fn paint_hover_group_handler(&self, window: &mut Window, cx: &mut App) {
3247 let group_hitbox = self
3248 .group_hover_style
3249 .as_ref()
3250 .and_then(|group_hover| GroupHitboxes::get(&group_hover.group, cx));
3251
3252 if let Some(group_hitbox) = group_hitbox {
3253 let was_hovered = group_hitbox.is_hovered(window);
3254 let current_view = window.current_view();
3255 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
3256 let hovered = group_hitbox.is_hovered(window);
3257 if phase == DispatchPhase::Capture && hovered != was_hovered {
3258 cx.notify(current_view);
3259 }
3260 });
3261 }
3262 }
3263
3264 fn paint_scroll_listener(
3265 &self,
3266 hitbox: &Hitbox,
3267 style: &Style,
3268 window: &mut Window,
3269 _cx: &mut App,
3270 ) {
3271 if let Some(scroll_offset) = self.scroll_offset.clone() {
3272 let ongoing_scroll = self.ongoing_scroll.clone();
3273 let overflow = style.overflow;
3274 let allow_concurrent_scroll = style.allow_concurrent_scroll;
3275 let restrict_scroll_to_axis = style.restrict_scroll_to_axis;
3276 let line_height = window.line_height();
3277 let hitbox = hitbox.clone();
3278 let current_view = window.current_view();
3279 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
3280 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
3281 let mut scroll_offset = scroll_offset.borrow_mut();
3282 let old_scroll_offset = *scroll_offset;
3283 let mut delta = event.delta.pixel_delta(line_height);
3284
3285 if restrict_scroll_to_axis
3286 && event.delta.precise()
3287 && let Some(ongoing_scroll) = &ongoing_scroll
3288 {
3289 ongoing_scroll
3290 .borrow_mut()
3291 .filter(&mut delta, event.touch_phase);
3292 }
3293
3294 let mut delta_x = match overflow.x {
3295 Overflow::Scroll if !delta.x.is_zero() => delta.x,
3296 Overflow::Scroll
3297 if !restrict_scroll_to_axis && overflow.y != Overflow::Scroll =>
3298 {
3299 delta.y
3300 }
3301 _ => Pixels::ZERO,
3302 };
3303 let mut delta_y = match overflow.y {
3304 Overflow::Scroll if !delta.y.is_zero() => delta.y,
3305 Overflow::Scroll
3306 if !restrict_scroll_to_axis && overflow.x != Overflow::Scroll =>
3307 {
3308 delta.x
3309 }
3310 _ => Pixels::ZERO,
3311 };
3312 if !allow_concurrent_scroll && !delta_x.is_zero() && !delta_y.is_zero() {
3313 if delta_x.abs() > delta_y.abs() {
3314 delta_y = Pixels::ZERO;
3315 } else {
3316 delta_x = Pixels::ZERO;
3317 }
3318 }
3319 scroll_offset.y += delta_y;
3320 scroll_offset.x += delta_x;
3321 if *scroll_offset != old_scroll_offset {
3322 cx.notify(current_view);
3323 }
3324 }
3325 });
3326 }
3327 }
3328
3329 pub fn compute_style(
3331 &self,
3332 global_id: Option<&GlobalElementId>,
3333 hitbox: Option<&Hitbox>,
3334 window: &mut Window,
3335 cx: &mut App,
3336 ) -> Style {
3337 window.with_optional_element_state(global_id, |element_state, window| {
3338 let mut element_state =
3339 element_state.map(|element_state| element_state.unwrap_or_default());
3340 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
3341 (style, element_state)
3342 })
3343 }
3344
3345 fn compute_style_internal(
3347 &self,
3348 hitbox: Option<&Hitbox>,
3349 element_state: Option<&mut InteractiveElementState>,
3350 window: &mut Window,
3351 cx: &mut App,
3352 ) -> Style {
3353 let mut style = Style::default();
3354 style.refine(&self.base_style);
3355
3356 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
3357 if let Some(in_focus_style) = self.in_focus_style.as_ref()
3358 && focus_handle.within_focused(window, cx)
3359 {
3360 style.refine(in_focus_style);
3361 }
3362
3363 if let Some(focus_style) = self.focus_style.as_ref()
3364 && focus_handle.is_focused(window)
3365 {
3366 style.refine(focus_style);
3367 }
3368
3369 if let Some(focus_visible_style) = self.focus_visible_style.as_ref()
3370 && focus_handle.is_focused(window)
3371 && window.last_input_was_keyboard()
3372 {
3373 style.refine(focus_visible_style);
3374 }
3375 }
3376
3377 if !cx.has_active_drag() {
3378 if let Some(group_hover) = self.group_hover_style.as_ref() {
3379 let is_group_hovered =
3380 if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
3381 group_hitbox_id.is_hovered(window)
3382 } else if let Some(element_state) = element_state.as_ref() {
3383 element_state
3384 .hover_state
3385 .as_ref()
3386 .map(|state| state.borrow().group)
3387 .unwrap_or(false)
3388 } else {
3389 false
3390 };
3391
3392 if is_group_hovered {
3393 style.refine(&group_hover.style);
3394 }
3395 }
3396
3397 if let Some(hover_style) = self.hover_style.as_ref() {
3398 let is_hovered = if let Some(hitbox) = hitbox {
3399 hitbox.is_hovered(window)
3400 } else if let Some(element_state) = element_state.as_ref() {
3401 element_state
3402 .hover_state
3403 .as_ref()
3404 .map(|state| state.borrow().element)
3405 .unwrap_or(false)
3406 } else {
3407 false
3408 };
3409
3410 if is_hovered {
3411 style.refine(hover_style);
3412 }
3413 }
3414 }
3415
3416 if let Some(hitbox) = hitbox {
3417 if let Some(drag) = cx.active_drag.take() {
3418 let mut can_drop = true;
3419 if let Some(can_drop_predicate) = &self.can_drop_predicate {
3420 can_drop = can_drop_predicate(drag.value.as_ref(), window, cx);
3421 }
3422
3423 if can_drop {
3424 for (state_type, group_drag_style) in &self.group_drag_over_styles {
3425 if let Some(group_hitbox_id) =
3426 GroupHitboxes::get(&group_drag_style.group, cx)
3427 && *state_type == drag.value.as_ref().type_id()
3428 && group_hitbox_id.is_hovered(window)
3429 {
3430 style.refine(&group_drag_style.style);
3431 }
3432 }
3433
3434 for (state_type, build_drag_over_style) in &self.drag_over_styles {
3435 if *state_type == drag.value.as_ref().type_id() && hitbox.is_hovered(window)
3436 {
3437 style.refine(&build_drag_over_style(drag.value.as_ref(), window, cx));
3438 }
3439 }
3440 }
3441
3442 style.mouse_cursor = drag.cursor_style;
3443 cx.active_drag = Some(drag);
3444 }
3445 }
3446
3447 if let Some(element_state) = element_state {
3448 let clicked_state = element_state
3449 .clicked_state
3450 .get_or_insert_with(Default::default)
3451 .borrow();
3452 if clicked_state.group
3453 && let Some(group) = self.group_active_style.as_ref()
3454 {
3455 style.refine(&group.style)
3456 }
3457
3458 if let Some(active_style) = self.active_style.as_ref()
3459 && clicked_state.element
3460 {
3461 style.refine(active_style)
3462 }
3463 }
3464
3465 style
3466 }
3467
3468 pub(crate) fn write_a11y_info(&self, node: &mut accesskit::Node) {
3469 if let Some(label) = &self.aria.label {
3470 node.set_label(label.to_string());
3471 }
3472 if let Some(description) = &self.aria.description {
3473 node.set_description(description.to_string());
3474 }
3475 if let Some(keyshortcuts) = &self.aria.keyshortcuts {
3476 node.set_keyboard_shortcut(keyshortcuts.to_string());
3477 }
3478 if let Some(selected) = self.aria.selected {
3479 node.set_selected(selected);
3480 }
3481 if let Some(expanded) = self.aria.expanded {
3482 node.set_expanded(expanded);
3483 }
3484 if self.aria.disabled {
3485 node.set_disabled();
3486 }
3487 if self.aria.read_only {
3488 node.set_read_only();
3489 }
3490 if self.aria.modal {
3491 node.set_modal();
3492 }
3493 if self.aria.invalid {
3494 node.set_invalid(accesskit::Invalid::True);
3495 }
3496 if self.aria.required {
3497 node.set_required();
3498 }
3499 if self.aria.busy {
3500 node.set_busy();
3501 }
3502 if let Some(live) = self.aria.live {
3503 node.set_live(live);
3504 }
3505 if self.aria.live_atomic {
3506 node.set_live_atomic();
3507 }
3508 if let Some(toggled) = self.aria.toggled {
3509 node.set_toggled(toggled);
3510 }
3511 if let Some(value) = self.aria.numeric_value {
3512 node.set_numeric_value(value);
3513 }
3514 if let Some(value) = self.aria.min_numeric_value {
3515 node.set_min_numeric_value(value);
3516 }
3517 if let Some(value) = self.aria.max_numeric_value {
3518 node.set_max_numeric_value(value);
3519 }
3520 if let Some(step) = self.aria.numeric_value_step {
3521 node.set_numeric_value_step(step);
3522 }
3523 if let Some(value) = &self.aria.value {
3524 node.set_value(value.to_string());
3525 }
3526 if let Some(placeholder) = &self.aria.placeholder {
3527 node.set_placeholder(placeholder.to_string());
3528 }
3529 if let Some(orientation) = self.aria.orientation {
3530 node.set_orientation(orientation);
3531 }
3532 if let Some(level) = self.aria.level {
3533 node.set_level(level);
3534 }
3535 if let Some(position) = self.aria.position_in_set {
3536 node.set_position_in_set(position);
3537 }
3538 if let Some(size) = self.aria.size_of_set {
3539 node.set_size_of_set(size);
3540 }
3541 if let Some(index) = self.aria.row_index {
3542 node.set_row_index(index);
3543 }
3544 if let Some(index) = self.aria.column_index {
3545 node.set_column_index(index);
3546 }
3547 if let Some(count) = self.aria.row_count {
3548 node.set_row_count(count);
3549 }
3550 if let Some(count) = self.aria.column_count {
3551 node.set_column_count(count);
3552 }
3553 if !self.click_listeners.is_empty() {
3554 node.add_action(accesskit::Action::Click);
3555 }
3556 if self.tracked_focus_handle.is_some() || self.focusable {
3557 node.add_action(accesskit::Action::Focus);
3558 }
3559 for (action, _) in &self.a11y_action_listeners {
3560 node.add_action(*action);
3561 }
3562 }
3563}
3564
3565#[derive(Default)]
3568pub struct InteractiveElementState {
3569 pub(crate) focus_handle: Option<FocusHandle>,
3570 pub(crate) clicked_state: Option<Rc<RefCell<ElementClickedState>>>,
3571 pub(crate) hover_state: Option<Rc<RefCell<ElementHoverState>>>,
3572 pub(crate) hover_listener_state: Option<Rc<RefCell<bool>>>,
3573 pub(crate) pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
3574 pub(crate) pending_keyboard_down: Option<Rc<RefCell<Option<u64>>>>,
3582 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
3583 ongoing_scroll: Option<Rc<RefCell<OngoingScroll>>>,
3584 pub(crate) active_tooltip: Option<Rc<RefCell<Option<ActiveTooltip>>>>,
3585}
3586
3587#[derive(Copy, Clone, Default, Eq, PartialEq)]
3589pub struct ElementClickedState {
3590 pub group: bool,
3592
3593 pub element: bool,
3595}
3596
3597impl ElementClickedState {
3598 fn is_clicked(&self) -> bool {
3599 self.group || self.element
3600 }
3601}
3602
3603#[derive(Copy, Clone, Default, Eq, PartialEq)]
3605pub struct ElementHoverState {
3606 pub group: bool,
3608
3609 pub element: bool,
3611}
3612
3613pub(crate) enum ActiveTooltip {
3614 WaitingForShow { _task: Task<()> },
3616 Visible {
3618 tooltip: AnyTooltip,
3619 is_hoverable: bool,
3620 },
3621 WaitingForHide {
3624 tooltip: AnyTooltip,
3625 _task: Task<()>,
3626 },
3627}
3628
3629pub(crate) fn clear_active_tooltip(
3630 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3631 window: &mut Window,
3632) {
3633 match active_tooltip.borrow_mut().take() {
3634 None => {}
3635 Some(ActiveTooltip::WaitingForShow { .. }) => {}
3636 Some(ActiveTooltip::Visible { .. }) => window.refresh(),
3637 Some(ActiveTooltip::WaitingForHide { .. }) => window.refresh(),
3638 }
3639}
3640
3641pub(crate) fn clear_active_tooltip_if_not_hoverable(
3642 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3643 window: &mut Window,
3644) {
3645 let should_clear = match active_tooltip.borrow().as_ref() {
3646 None => false,
3647 Some(ActiveTooltip::WaitingForShow { .. }) => false,
3648 Some(ActiveTooltip::Visible { is_hoverable, .. }) => !is_hoverable,
3649 Some(ActiveTooltip::WaitingForHide { .. }) => false,
3650 };
3651 if should_clear {
3652 active_tooltip.borrow_mut().take();
3653 window.refresh();
3654 }
3655}
3656
3657pub(crate) fn set_tooltip_on_window(
3658 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3659 window: &mut Window,
3660) -> Option<TooltipId> {
3661 let tooltip = match active_tooltip.borrow().as_ref() {
3662 None => return None,
3663 Some(ActiveTooltip::WaitingForShow { .. }) => return None,
3664 Some(ActiveTooltip::Visible { tooltip, .. }) => tooltip.clone(),
3665 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => tooltip.clone(),
3666 };
3667 Some(window.set_tooltip(tooltip))
3668}
3669
3670pub(crate) fn register_tooltip_mouse_handlers(
3671 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3672 tooltip_id: Option<TooltipId>,
3673 build_tooltip: Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3674 check_is_hovered: Rc<dyn Fn(&Window) -> bool>,
3675 check_is_hovered_during_prepaint: Rc<dyn Fn(&Window) -> bool>,
3676 show_delay: Option<Duration>,
3677 window: &mut Window,
3678) {
3679 let current_view = window.current_view();
3680 let show_delay = show_delay.unwrap_or(DEFAULT_TOOLTIP_SHOW_DELAY);
3681
3682 window.on_mouse_event({
3683 let active_tooltip = active_tooltip.clone();
3684 let build_tooltip = build_tooltip.clone();
3685 let check_is_hovered = check_is_hovered.clone();
3686 move |_: &MouseMoveEvent, phase, window, cx| {
3687 handle_tooltip_mouse_move(
3688 &active_tooltip,
3689 &build_tooltip,
3690 &check_is_hovered,
3691 &check_is_hovered_during_prepaint,
3692 tooltip_id,
3693 current_view,
3694 phase,
3695 show_delay,
3696 window,
3697 cx,
3698 )
3699 }
3700 });
3701
3702 window.on_mouse_event({
3703 let active_tooltip = active_tooltip.clone();
3704 move |_: &MouseExitEvent, phase, window, _cx| {
3705 if phase == DispatchPhase::Capture {
3706 clear_active_tooltip(&active_tooltip, window);
3707 }
3708 }
3709 });
3710
3711 window.on_mouse_event({
3712 let active_tooltip = active_tooltip.clone();
3713 move |_: &MouseDownEvent, _phase, window: &mut Window, _cx| {
3714 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3715 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3716 }
3717 }
3718 });
3719
3720 window.on_mouse_event({
3721 let active_tooltip = active_tooltip.clone();
3722 move |_: &ScrollWheelEvent, _phase, window: &mut Window, _cx| {
3723 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3724 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3725 }
3726 }
3727 });
3728}
3729
3730fn handle_tooltip_mouse_move(
3742 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3743 build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3744 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3745 check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
3746 tooltip_id: Option<TooltipId>,
3747 current_view: EntityId,
3748 phase: DispatchPhase,
3749 show_delay: Duration,
3750 window: &mut Window,
3751 cx: &mut App,
3752) {
3753 enum Action {
3756 None,
3757 CancelShow,
3758 ScheduleShow,
3759 CheckVisible,
3760 }
3761
3762 let action = match active_tooltip.borrow().as_ref() {
3763 None => {
3764 let is_hovered = check_is_hovered(window);
3765 if is_hovered && phase.bubble() {
3766 Action::ScheduleShow
3767 } else {
3768 Action::None
3769 }
3770 }
3771 Some(ActiveTooltip::WaitingForShow { .. }) => {
3772 let is_hovered = check_is_hovered(window);
3773 if is_hovered {
3774 Action::None
3775 } else {
3776 Action::CancelShow
3777 }
3778 }
3779 Some(ActiveTooltip::Visible { is_hoverable, .. }) => {
3780 if phase.capture()
3781 && !check_is_hovered(window)
3782 && (!*is_hoverable
3783 || !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3784 {
3785 Action::CheckVisible
3786 } else {
3787 Action::None
3788 }
3789 }
3790 Some(ActiveTooltip::WaitingForHide { .. }) => {
3791 if phase.capture()
3792 && (check_is_hovered(window)
3793 || tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3794 {
3795 Action::CheckVisible
3796 } else {
3797 Action::None
3798 }
3799 }
3800 };
3801
3802 match action {
3803 Action::None => {}
3804 Action::CancelShow => {
3805 active_tooltip.borrow_mut().take();
3807 }
3808 Action::ScheduleShow => {
3809 let delayed_show_task = window.spawn(cx, {
3810 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3811 let build_tooltip = build_tooltip.clone();
3812 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3813 async move |cx| {
3814 cx.background_executor().timer(show_delay).await;
3815 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3816 return;
3817 };
3818 cx.update(|window, cx| {
3819 let new_tooltip =
3820 build_tooltip(window, cx).map(|(view, tooltip_is_hoverable)| {
3821 let weak_active_tooltip = Rc::downgrade(&active_tooltip);
3822 ActiveTooltip::Visible {
3823 tooltip: AnyTooltip {
3824 view,
3825 mouse_position: window.mouse_position(),
3826 check_visible_and_update: Rc::new(
3827 move |tooltip_bounds, window, cx| {
3828 let Some(active_tooltip) =
3829 weak_active_tooltip.upgrade()
3830 else {
3831 return false;
3832 };
3833 handle_tooltip_check_visible_and_update(
3834 &active_tooltip,
3835 tooltip_is_hoverable,
3836 &check_is_hovered_during_prepaint,
3837 tooltip_bounds,
3838 window,
3839 cx,
3840 )
3841 },
3842 ),
3843 },
3844 is_hoverable: tooltip_is_hoverable,
3845 }
3846 });
3847 *active_tooltip.borrow_mut() = new_tooltip;
3848 window.refresh();
3849 })
3850 .ok();
3851 }
3852 });
3853 active_tooltip
3854 .borrow_mut()
3855 .replace(ActiveTooltip::WaitingForShow {
3856 _task: delayed_show_task,
3857 });
3858 }
3859 Action::CheckVisible => cx.notify(current_view),
3860 }
3861}
3862
3863fn handle_tooltip_check_visible_and_update(
3867 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3868 tooltip_is_hoverable: bool,
3869 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3870 tooltip_bounds: Bounds<Pixels>,
3871 window: &mut Window,
3872 cx: &mut App,
3873) -> bool {
3874 enum Action {
3877 None,
3878 Hide,
3879 ScheduleHide(AnyTooltip),
3880 CancelHide(AnyTooltip),
3881 }
3882
3883 let is_hovered = check_is_hovered(window)
3884 || (tooltip_is_hoverable && tooltip_bounds.contains(&window.mouse_position()));
3885 let action = match active_tooltip.borrow().as_ref() {
3886 Some(ActiveTooltip::Visible { tooltip, .. }) => {
3887 if is_hovered {
3888 Action::None
3889 } else {
3890 if tooltip_is_hoverable {
3891 Action::ScheduleHide(tooltip.clone())
3892 } else {
3893 Action::Hide
3894 }
3895 }
3896 }
3897 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => {
3898 if is_hovered {
3899 Action::CancelHide(tooltip.clone())
3900 } else {
3901 Action::None
3902 }
3903 }
3904 None | Some(ActiveTooltip::WaitingForShow { .. }) => Action::None,
3905 };
3906
3907 match action {
3908 Action::None => {}
3909 Action::Hide => clear_active_tooltip(active_tooltip, window),
3910 Action::ScheduleHide(tooltip) => {
3911 let delayed_hide_task = window.spawn(cx, {
3912 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3913 async move |cx| {
3914 cx.background_executor()
3915 .timer(HOVERABLE_TOOLTIP_HIDE_DELAY)
3916 .await;
3917 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3918 return;
3919 };
3920 if active_tooltip.borrow_mut().take().is_some() {
3921 cx.update(|window, _cx| window.refresh()).ok();
3922 }
3923 }
3924 });
3925 active_tooltip
3926 .borrow_mut()
3927 .replace(ActiveTooltip::WaitingForHide {
3928 tooltip,
3929 _task: delayed_hide_task,
3930 });
3931 }
3932 Action::CancelHide(tooltip) => {
3933 active_tooltip.borrow_mut().replace(ActiveTooltip::Visible {
3935 tooltip,
3936 is_hoverable: true,
3937 });
3938 }
3939 }
3940
3941 active_tooltip.borrow().is_some()
3942}
3943
3944#[derive(Default)]
3945pub(crate) struct GroupHitboxes(HashMap<SharedString, SmallVec<[HitboxId; 1]>>);
3946
3947impl Global for GroupHitboxes {}
3948
3949impl GroupHitboxes {
3950 pub fn get(name: &SharedString, cx: &mut App) -> Option<HitboxId> {
3951 cx.default_global::<Self>()
3952 .0
3953 .get(name)
3954 .and_then(|bounds_stack| bounds_stack.last())
3955 .cloned()
3956 }
3957
3958 pub fn push(name: SharedString, hitbox_id: HitboxId, cx: &mut App) {
3959 cx.default_global::<Self>()
3960 .0
3961 .entry(name)
3962 .or_default()
3963 .push(hitbox_id);
3964 }
3965
3966 pub fn pop(name: &SharedString, cx: &mut App) {
3967 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
3968 }
3969}
3970
3971pub struct Stateful<E> {
3973 pub(crate) element: E,
3974}
3975
3976impl<E> Styled for Stateful<E>
3977where
3978 E: Styled,
3979{
3980 fn style(&mut self) -> &mut StyleRefinement {
3981 self.element.style()
3982 }
3983}
3984
3985impl<E> StatefulInteractiveElement for Stateful<E>
3986where
3987 E: Element,
3988 Self: InteractiveElement,
3989{
3990}
3991
3992impl<E> InteractiveElement for Stateful<E>
3993where
3994 E: InteractiveElement,
3995{
3996 fn interactivity(&mut self) -> &mut Interactivity {
3997 self.element.interactivity()
3998 }
3999}
4000
4001impl<E> Element for Stateful<E>
4002where
4003 E: Element,
4004{
4005 type RequestLayoutState = E::RequestLayoutState;
4006 type PrepaintState = E::PrepaintState;
4007
4008 fn id(&self) -> Option<ElementId> {
4009 self.element.id()
4010 }
4011
4012 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
4013 self.element.source_location()
4014 }
4015
4016 fn a11y_role(&self) -> Option<accesskit::Role> {
4017 self.element.a11y_role()
4018 }
4019
4020 fn write_a11y_info(&self, node: &mut accesskit::Node) {
4021 self.element.write_a11y_info(node);
4022 }
4023
4024 fn a11y_synthetic_children(
4025 &mut self,
4026 prepaint: &mut Self::PrepaintState,
4027 builder: &mut crate::A11ySubtreeBuilder,
4028 ) {
4029 self.element.a11y_synthetic_children(prepaint, builder);
4030 }
4031
4032 fn request_layout(
4033 &mut self,
4034 id: Option<&GlobalElementId>,
4035 inspector_id: Option<&InspectorElementId>,
4036 window: &mut Window,
4037 cx: &mut App,
4038 ) -> (LayoutId, Self::RequestLayoutState) {
4039 self.element.request_layout(id, inspector_id, window, cx)
4040 }
4041
4042 fn prepaint(
4043 &mut self,
4044 id: Option<&GlobalElementId>,
4045 inspector_id: Option<&InspectorElementId>,
4046 bounds: Bounds<Pixels>,
4047 state: &mut Self::RequestLayoutState,
4048 window: &mut Window,
4049 cx: &mut App,
4050 ) -> E::PrepaintState {
4051 self.element
4052 .prepaint(id, inspector_id, bounds, state, window, cx)
4053 }
4054
4055 fn paint(
4056 &mut self,
4057 id: Option<&GlobalElementId>,
4058 inspector_id: Option<&InspectorElementId>,
4059 bounds: Bounds<Pixels>,
4060 request_layout: &mut Self::RequestLayoutState,
4061 prepaint: &mut Self::PrepaintState,
4062 window: &mut Window,
4063 cx: &mut App,
4064 ) {
4065 self.element.paint(
4066 id,
4067 inspector_id,
4068 bounds,
4069 request_layout,
4070 prepaint,
4071 window,
4072 cx,
4073 );
4074 }
4075}
4076
4077impl<E> IntoElement for Stateful<E>
4078where
4079 E: Element,
4080{
4081 type Element = Self;
4082
4083 fn into_element(self) -> Self::Element {
4084 self
4085 }
4086}
4087
4088impl<E> ParentElement for Stateful<E>
4089where
4090 E: ParentElement,
4091{
4092 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
4093 self.element.extend(elements)
4094 }
4095}
4096
4097#[derive(Clone)]
4101pub struct ScrollAnchor {
4102 handle: ScrollHandle,
4103 last_origin: Rc<RefCell<Point<Pixels>>>,
4104}
4105
4106impl ScrollAnchor {
4107 pub fn for_handle(handle: ScrollHandle) -> Self {
4109 Self {
4110 handle,
4111 last_origin: Default::default(),
4112 }
4113 }
4114 pub fn scroll_to(&self, window: &mut Window, _cx: &mut App) {
4116 let this = self.clone();
4117
4118 window.on_next_frame(move |_, _| {
4119 let viewport_bounds = this.handle.bounds();
4120 let self_bounds = *this.last_origin.borrow();
4121 this.handle.set_offset(viewport_bounds.origin - self_bounds);
4122 });
4123 }
4124}
4125
4126#[derive(Default, Debug)]
4127struct ScrollHandleState {
4128 offset: Rc<RefCell<Point<Pixels>>>,
4129 ongoing_scroll: Rc<RefCell<OngoingScroll>>,
4130 bounds: Bounds<Pixels>,
4131 max_offset: Point<Pixels>,
4132 child_bounds: Vec<Bounds<Pixels>>,
4133 scroll_to_bottom: bool,
4134 overflow: Point<Overflow>,
4135 active_item: Option<ScrollActiveItem>,
4136}
4137
4138#[derive(Default, Debug, Clone, Copy)]
4139struct ScrollActiveItem {
4140 index: usize,
4141 strategy: ScrollStrategy,
4142}
4143
4144#[derive(Default, Debug, Clone, Copy)]
4145enum ScrollStrategy {
4146 #[default]
4147 FirstVisible,
4148 Top,
4149}
4150
4151#[derive(Clone, Debug)]
4155pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
4156
4157impl Default for ScrollHandle {
4158 fn default() -> Self {
4159 Self::new()
4160 }
4161}
4162
4163impl ScrollHandle {
4164 pub fn new() -> Self {
4166 Self(Rc::default())
4167 }
4168
4169 pub fn offset(&self) -> Point<Pixels> {
4171 *self.0.borrow().offset.borrow()
4172 }
4173
4174 pub fn max_offset(&self) -> Point<Pixels> {
4176 self.0.borrow().max_offset
4177 }
4178
4179 pub fn top_item(&self) -> usize {
4181 let state = self.0.borrow();
4182 let top = state.bounds.top() - state.offset.borrow().y;
4183
4184 match state.child_bounds.binary_search_by(|bounds| {
4185 if top < bounds.top() {
4186 Ordering::Greater
4187 } else if top > bounds.bottom() {
4188 Ordering::Less
4189 } else {
4190 Ordering::Equal
4191 }
4192 }) {
4193 Ok(ix) => ix,
4194 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
4195 }
4196 }
4197
4198 pub fn bottom_item(&self) -> usize {
4200 let state = self.0.borrow();
4201 let bottom = state.bounds.bottom() - state.offset.borrow().y;
4202
4203 match state.child_bounds.binary_search_by(|bounds| {
4204 if bottom < bounds.top() {
4205 Ordering::Greater
4206 } else if bottom > bounds.bottom() {
4207 Ordering::Less
4208 } else {
4209 Ordering::Equal
4210 }
4211 }) {
4212 Ok(ix) => ix,
4213 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
4214 }
4215 }
4216
4217 pub fn bounds(&self) -> Bounds<Pixels> {
4219 self.0.borrow().bounds
4220 }
4221
4222 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
4224 self.0.borrow().child_bounds.get(ix).cloned()
4225 }
4226
4227 pub fn scroll_to_item(&self, ix: usize) {
4229 let mut state = self.0.borrow_mut();
4230 state.active_item = Some(ScrollActiveItem {
4231 index: ix,
4232 strategy: ScrollStrategy::default(),
4233 });
4234 }
4235
4236 pub fn scroll_to_top_of_item(&self, ix: usize) {
4239 let mut state = self.0.borrow_mut();
4240 state.active_item = Some(ScrollActiveItem {
4241 index: ix,
4242 strategy: ScrollStrategy::Top,
4243 });
4244 }
4245
4246 fn scroll_to_active_item(&self) {
4250 let mut state = self.0.borrow_mut();
4251
4252 let Some(active_item) = state.active_item else {
4253 return;
4254 };
4255
4256 let active_item = match state.child_bounds.get(active_item.index) {
4257 Some(bounds) => {
4258 let mut scroll_offset = state.offset.borrow_mut();
4259
4260 match active_item.strategy {
4261 ScrollStrategy::FirstVisible => {
4262 if state.overflow.y == Overflow::Scroll {
4263 let child_height = bounds.size.height;
4264 let viewport_height = state.bounds.size.height;
4265 if child_height > viewport_height {
4266 scroll_offset.y = state.bounds.top() - bounds.top();
4267 } else if bounds.top() + scroll_offset.y < state.bounds.top() {
4268 scroll_offset.y = state.bounds.top() - bounds.top();
4269 } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
4270 scroll_offset.y = state.bounds.bottom() - bounds.bottom();
4271 }
4272 }
4273 }
4274 ScrollStrategy::Top => {
4275 scroll_offset.y = state.bounds.top() - bounds.top();
4276 }
4277 }
4278
4279 if state.overflow.x == Overflow::Scroll {
4280 let child_width = bounds.size.width;
4281 let viewport_width = state.bounds.size.width;
4282 if child_width > viewport_width {
4283 scroll_offset.x = state.bounds.left() - bounds.left();
4284 } else if bounds.left() + scroll_offset.x < state.bounds.left() {
4285 scroll_offset.x = state.bounds.left() - bounds.left();
4286 } else if bounds.right() + scroll_offset.x > state.bounds.right() {
4287 scroll_offset.x = state.bounds.right() - bounds.right();
4288 }
4289 }
4290 None
4291 }
4292 None => Some(active_item),
4293 };
4294 state.active_item = active_item;
4295 }
4296
4297 pub fn scroll_to_bottom(&self) {
4299 let mut state = self.0.borrow_mut();
4300 state.scroll_to_bottom = true;
4301 }
4302
4303 pub fn set_offset(&self, mut position: Point<Pixels>) {
4307 let state = self.0.borrow();
4308 *state.offset.borrow_mut() = position;
4309 }
4310
4311 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
4313 let ix = self.top_item();
4314 let state = self.0.borrow();
4315
4316 if let Some(child_bounds) = state.child_bounds.get(ix) {
4317 (
4318 ix,
4319 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
4320 )
4321 } else {
4322 (ix, px(0.))
4323 }
4324 }
4325
4326 pub fn logical_scroll_bottom(&self) -> (usize, Pixels) {
4328 let ix = self.bottom_item();
4329 let state = self.0.borrow();
4330
4331 if let Some(child_bounds) = state.child_bounds.get(ix) {
4332 (
4333 ix,
4334 child_bounds.bottom() + state.offset.borrow().y - state.bounds.bottom(),
4335 )
4336 } else {
4337 (ix, px(0.))
4338 }
4339 }
4340
4341 pub fn children_count(&self) -> usize {
4343 self.0.borrow().child_bounds.len()
4344 }
4345}
4346
4347#[cfg(test)]
4348mod tests {
4349 use super::*;
4350 use crate::{
4351 AnyWindowHandle, AppContext as _, Context, InputEvent, Keystroke, Modifiers,
4352 MouseDownEvent, MouseMoveEvent, MouseUpEvent, TestAppContext, canvas,
4353 util::FluentBuilder as _,
4354 };
4355 use std::{cell::Cell, rc::Weak};
4356
4357 struct PointerCaptureTestView {
4358 moves: Rc<Cell<usize>>,
4359 ups: Rc<Cell<usize>>,
4360 }
4361
4362 impl Render for PointerCaptureTestView {
4363 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4364 let moves = self.moves.clone();
4365 let ups = self.ups.clone();
4366 div().size_full().child(
4367 div()
4368 .id("pointer-capture-target")
4369 .size(px(50.))
4370 .on_mouse_down_with_pointer_capture(MouseButton::Left, |_, _, _| {})
4371 .on_mouse_move(move |_, _, _| moves.set(moves.get() + 1))
4372 .on_mouse_up(MouseButton::Left, move |_, _, _| ups.set(ups.get() + 1)),
4373 )
4374 }
4375 }
4376
4377 #[test]
4378 fn pointer_capture_survives_a_redraw_and_delivers_events_outside_the_element() {
4379 let mut cx = TestAppContext::single();
4380 let moves = Rc::new(Cell::new(0));
4381 let ups = Rc::new(Cell::new(0));
4382 let window: AnyWindowHandle = cx
4383 .add_window({
4384 let moves = moves.clone();
4385 let ups = ups.clone();
4386 move |_, _| PointerCaptureTestView { moves, ups }
4387 })
4388 .into();
4389
4390 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
4391 .unwrap();
4392 cx.update_window(window, |_, window, cx| {
4393 window.dispatch_event(
4394 MouseDownEvent {
4395 position: point(px(25.), px(25.)),
4396 button: MouseButton::Left,
4397 modifiers: Modifiers::none(),
4398 click_count: 1,
4399 first_mouse: false,
4400 }
4401 .to_platform_input(),
4402 cx,
4403 );
4404 let before_redraw = window.captured_hitbox().expect("captured");
4405 window.draw(cx).clear(cx);
4406 let after_redraw = window.captured_hitbox().expect("still captured");
4407 assert_ne!(
4408 before_redraw, after_redraw,
4409 "the next frame has a new hitbox"
4410 );
4411 window.dispatch_event(
4412 MouseMoveEvent {
4413 position: point(px(75.), px(75.)),
4414 modifiers: Modifiers::none(),
4415 pressed_button: Some(MouseButton::Left),
4416 }
4417 .to_platform_input(),
4418 cx,
4419 );
4420 window.dispatch_event(
4421 MouseUpEvent {
4422 position: point(px(75.), px(75.)),
4423 button: MouseButton::Left,
4424 modifiers: Modifiers::none(),
4425 click_count: 1,
4426 }
4427 .to_platform_input(),
4428 cx,
4429 );
4430 assert!(window.captured_hitbox().is_none());
4431 })
4432 .unwrap();
4433
4434 assert_eq!(moves.get(), 1);
4435 assert_eq!(ups.get(), 1);
4436 }
4437
4438 struct GroupHoverTestView {
4439 render_count: Rc<Cell<usize>>,
4440 anonymous_paint_count: Rc<Cell<usize>>,
4441 stateful_width: Rc<Cell<Pixels>>,
4442 }
4443
4444 impl Render for GroupHoverTestView {
4445 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4446 self.render_count.set(self.render_count.get() + 1);
4447 let anonymous_paint_count = self.anonymous_paint_count.clone();
4448 let stateful_width = self.stateful_width.clone();
4449 div().size_full().child(
4450 div()
4451 .ml(px(20.))
4452 .mt(px(20.))
4453 .size(px(50.))
4454 .relative()
4455 .group("hover-group")
4456 .child(
4457 div()
4458 .absolute()
4459 .size_full()
4460 .invisible()
4461 .group_hover("hover-group", |style| style.visible())
4462 .child(canvas(
4463 |_, _, _| {},
4464 move |_, _, _, _| {
4465 anonymous_paint_count.set(anonymous_paint_count.get() + 1)
4466 },
4467 )),
4468 )
4469 .child(
4470 div()
4471 .id("stateful-group-hover-target")
4472 .absolute()
4473 .top_0()
4474 .left_0()
4475 .size(px(10.))
4476 .group_hover("hover-group", |style| style.size(px(20.)))
4477 .child(canvas(
4478 move |bounds, _, _| stateful_width.set(bounds.size.width),
4479 |_, _, _, _| {},
4480 )),
4481 ),
4482 )
4483 }
4484 }
4485
4486 #[gpui::test]
4487 fn group_hover_styles_update_only_on_transitions(cx: &mut TestAppContext) {
4488 let render_count = Rc::new(Cell::new(0));
4489 let anonymous_paint_count = Rc::new(Cell::new(0));
4490 let stateful_width = Rc::new(Cell::new(px(0.)));
4491 let window = cx.add_window({
4492 let render_count = render_count.clone();
4493 let anonymous_paint_count = anonymous_paint_count.clone();
4494 let stateful_width = stateful_width.clone();
4495 move |_, _| GroupHoverTestView {
4496 render_count,
4497 anonymous_paint_count,
4498 stateful_width,
4499 }
4500 });
4501 let window = AnyWindowHandle::from(window);
4502
4503 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
4504 .unwrap();
4505 assert_eq!(anonymous_paint_count.get(), 0);
4506 assert_eq!(stateful_width.get(), px(10.));
4507
4508 let move_mouse = |cx: &mut TestAppContext, position| {
4509 cx.update_window(window, |_, window, cx| {
4510 window.simulate_mouse_move(position, cx)
4511 })
4512 .unwrap();
4513 };
4514
4515 let initial_render_count = render_count.get();
4516 move_mouse(cx, point(px(25.), px(25.)));
4517 assert_eq!(render_count.get(), initial_render_count + 1);
4518 assert_eq!(anonymous_paint_count.get(), 1);
4519 assert_eq!(stateful_width.get(), px(20.));
4520
4521 move_mouse(cx, point(px(30.), px(30.)));
4522 assert_eq!(render_count.get(), initial_render_count + 1);
4523 assert_eq!(anonymous_paint_count.get(), 1);
4524 assert_eq!(stateful_width.get(), px(20.));
4525
4526 move_mouse(cx, point(px(5.), px(5.)));
4527 assert_eq!(render_count.get(), initial_render_count + 2);
4528 assert_eq!(anonymous_paint_count.get(), 1);
4529 assert_eq!(stateful_width.get(), px(10.));
4530
4531 move_mouse(cx, point(px(10.), px(10.)));
4532 assert_eq!(render_count.get(), initial_render_count + 2);
4533 assert_eq!(anonymous_paint_count.get(), 1);
4534 assert_eq!(stateful_width.get(), px(10.));
4535 }
4536
4537 struct TestTooltipView;
4538
4539 impl Render for TestTooltipView {
4540 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4541 div().w(px(20.)).h(px(20.)).child("tooltip")
4542 }
4543 }
4544
4545 type CapturedActiveTooltip = Rc<RefCell<Option<Weak<RefCell<Option<ActiveTooltip>>>>>>;
4546
4547 struct TooltipCaptureElement {
4548 child: AnyElement,
4549 captured_active_tooltip: CapturedActiveTooltip,
4550 }
4551
4552 impl IntoElement for TooltipCaptureElement {
4553 type Element = Self;
4554
4555 fn into_element(self) -> Self::Element {
4556 self
4557 }
4558 }
4559
4560 impl Element for TooltipCaptureElement {
4561 type RequestLayoutState = ();
4562 type PrepaintState = ();
4563
4564 fn id(&self) -> Option<ElementId> {
4565 None
4566 }
4567
4568 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
4569 None
4570 }
4571
4572 fn request_layout(
4573 &mut self,
4574 _id: Option<&GlobalElementId>,
4575 _inspector_id: Option<&InspectorElementId>,
4576 window: &mut Window,
4577 cx: &mut App,
4578 ) -> (LayoutId, Self::RequestLayoutState) {
4579 (self.child.request_layout(window, cx), ())
4580 }
4581
4582 fn prepaint(
4583 &mut self,
4584 _id: Option<&GlobalElementId>,
4585 _inspector_id: Option<&InspectorElementId>,
4586 _bounds: Bounds<Pixels>,
4587 _request_layout: &mut Self::RequestLayoutState,
4588 window: &mut Window,
4589 cx: &mut App,
4590 ) -> Self::PrepaintState {
4591 self.child.prepaint(window, cx);
4592 }
4593
4594 fn paint(
4595 &mut self,
4596 _id: Option<&GlobalElementId>,
4597 _inspector_id: Option<&InspectorElementId>,
4598 _bounds: Bounds<Pixels>,
4599 _request_layout: &mut Self::RequestLayoutState,
4600 _prepaint: &mut Self::PrepaintState,
4601 window: &mut Window,
4602 cx: &mut App,
4603 ) {
4604 self.child.paint(window, cx);
4605 window.with_global_id("target".into(), |global_id, window| {
4606 window.with_element_state::<InteractiveElementState, _>(
4607 global_id,
4608 |state, _window| {
4609 let state = state.unwrap();
4610 *self.captured_active_tooltip.borrow_mut() =
4611 state.active_tooltip.as_ref().map(Rc::downgrade);
4612 ((), state)
4613 },
4614 )
4615 });
4616 }
4617 }
4618
4619 struct TooltipOwner {
4620 captured_active_tooltip: CapturedActiveTooltip,
4621 show_delay_override: Option<Duration>,
4622 }
4623
4624 impl Render for TooltipOwner {
4625 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4626 TooltipCaptureElement {
4627 child: div()
4628 .size_full()
4629 .child(
4630 div()
4631 .id("target")
4632 .w(px(50.))
4633 .h(px(50.))
4634 .tooltip(|_, cx| cx.new(|_| TestTooltipView).into())
4635 .when_some(self.show_delay_override, |this, delay| {
4636 this.tooltip_show_delay(delay)
4637 }),
4638 )
4639 .into_any_element(),
4640 captured_active_tooltip: self.captured_active_tooltip.clone(),
4641 }
4642 }
4643 }
4644
4645 #[test]
4646 fn scroll_handle_aligns_wide_children_to_left_edge() {
4647 let handle = ScrollHandle::new();
4648 {
4649 let mut state = handle.0.borrow_mut();
4650 state.bounds = Bounds::new(point(px(0.), px(0.)), size(px(80.), px(20.)));
4651 state.child_bounds = vec![Bounds::new(point(px(25.), px(0.)), size(px(200.), px(20.)))];
4652 state.overflow.x = Overflow::Scroll;
4653 state.active_item = Some(ScrollActiveItem {
4654 index: 0,
4655 strategy: ScrollStrategy::default(),
4656 });
4657 }
4658
4659 handle.scroll_to_active_item();
4660
4661 assert_eq!(handle.offset().x, px(-25.));
4662 }
4663
4664 #[test]
4665 fn scroll_handle_aligns_tall_children_to_top_edge() {
4666 let handle = ScrollHandle::new();
4667 {
4668 let mut state = handle.0.borrow_mut();
4669 state.bounds = Bounds::new(point(px(0.), px(0.)), size(px(20.), px(80.)));
4670 state.child_bounds = vec![Bounds::new(point(px(0.), px(25.)), size(px(20.), px(200.)))];
4671 state.overflow.y = Overflow::Scroll;
4672 state.active_item = Some(ScrollActiveItem {
4673 index: 0,
4674 strategy: ScrollStrategy::default(),
4675 });
4676 }
4677
4678 handle.scroll_to_active_item();
4679
4680 assert_eq!(handle.offset().y, px(-25.));
4681 }
4682
4683 struct InitiallyScrolledVariableList {
4684 handle: ScrollHandle,
4685 }
4686
4687 impl Render for InitiallyScrolledVariableList {
4688 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4689 div()
4690 .w(px(100.))
4691 .h(px(80.))
4692 .flex()
4693 .flex_col()
4694 .id("initially-scrolled-variable-list")
4695 .overflow_y_scroll()
4696 .track_scroll(&self.handle)
4697 .children([
4698 div().h(px(30.)).flex_none(),
4699 div().h(px(40.)).flex_none(),
4700 div().h(px(50.)).flex_none(),
4701 ])
4702 }
4703 }
4704
4705 #[gpui::test]
4706 fn scroll_handle_reveals_a_variable_height_child_on_first_prepaint(cx: &mut TestAppContext) {
4707 let handle = ScrollHandle::new();
4708 handle.scroll_to_item(2);
4709 let window: AnyWindowHandle = cx
4710 .add_window({
4711 let handle = handle.clone();
4712 move |_, _| InitiallyScrolledVariableList { handle }
4713 })
4714 .into();
4715
4716 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
4717 .expect("draw list");
4718
4719 assert_eq!(handle.offset().y, px(-40.));
4720 }
4721
4722 fn setup_tooltip_owner_test(
4723 show_delay_override: Option<Duration>,
4724 ) -> (
4725 TestAppContext,
4726 crate::AnyWindowHandle,
4727 CapturedActiveTooltip,
4728 ) {
4729 let mut test_app = TestAppContext::single();
4730 let captured_active_tooltip: CapturedActiveTooltip = Rc::new(RefCell::new(None));
4731 let window = test_app.add_window({
4732 let captured_active_tooltip = captured_active_tooltip.clone();
4733 move |_, _| TooltipOwner {
4734 captured_active_tooltip,
4735 show_delay_override,
4736 }
4737 });
4738 let any_window = window.into();
4739
4740 test_app
4741 .update_window(any_window, |_, window, cx| {
4742 window.draw(cx).clear(cx);
4743 })
4744 .unwrap();
4745
4746 test_app
4747 .update_window(any_window, |_, window, cx| {
4748 window.dispatch_event(
4749 MouseMoveEvent {
4750 position: point(px(10.), px(10.)),
4751 modifiers: Default::default(),
4752 pressed_button: None,
4753 }
4754 .to_platform_input(),
4755 cx,
4756 );
4757 })
4758 .unwrap();
4759
4760 test_app
4761 .update_window(any_window, |_, window, cx| {
4762 window.draw(cx).clear(cx);
4763 })
4764 .unwrap();
4765
4766 (test_app, any_window, captured_active_tooltip)
4767 }
4768
4769 #[test]
4770 fn tooltip_waiting_for_show_is_released_when_its_owner_disappears() {
4771 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
4772
4773 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4774 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4775 assert!(matches!(
4776 active_tooltip.borrow().as_ref(),
4777 Some(ActiveTooltip::WaitingForShow { .. })
4778 ));
4779
4780 test_app
4781 .update_window(any_window, |_, window, _| {
4782 window.remove_window();
4783 })
4784 .unwrap();
4785 test_app.run_until_parked();
4786 drop(active_tooltip);
4787
4788 assert!(weak_active_tooltip.upgrade().is_none());
4789 }
4790
4791 #[test]
4792 fn tooltip_respects_custom_show_delay() {
4793 let extra_delay = Duration::from_secs(1);
4794 let show_delay_override = DEFAULT_TOOLTIP_SHOW_DELAY + extra_delay;
4795 let (mut test_app, _any_window, captured_active_tooltip) =
4796 setup_tooltip_owner_test(Some(show_delay_override));
4797
4798 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4799 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4800
4801 test_app
4802 .dispatcher
4803 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
4804 test_app.run_until_parked();
4805
4806 assert!(matches!(
4807 active_tooltip.borrow().as_ref(),
4808 Some(ActiveTooltip::WaitingForShow { .. })
4809 ));
4810
4811 test_app.dispatcher.advance_clock(extra_delay);
4812 test_app.run_until_parked();
4813
4814 assert!(matches!(
4815 active_tooltip.borrow().as_ref(),
4816 Some(ActiveTooltip::Visible { .. })
4817 ));
4818 }
4819
4820 #[test]
4821 fn tooltip_is_released_when_its_owner_disappears() {
4822 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
4823
4824 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4825 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4826
4827 test_app
4828 .dispatcher
4829 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
4830 test_app.run_until_parked();
4831
4832 assert!(matches!(
4833 active_tooltip.borrow().as_ref(),
4834 Some(ActiveTooltip::Visible { .. })
4835 ));
4836
4837 test_app
4838 .update_window(any_window, |_, window, _| {
4839 window.remove_window();
4840 })
4841 .unwrap();
4842 test_app.run_until_parked();
4843 drop(active_tooltip);
4844
4845 assert!(weak_active_tooltip.upgrade().is_none());
4846 }
4847
4848 #[test]
4849 fn tooltip_hides_after_mouse_leaves_origin() {
4850 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
4851
4852 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4853 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4854
4855 test_app
4856 .dispatcher
4857 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
4858 test_app.run_until_parked();
4859
4860 assert!(matches!(
4861 active_tooltip.borrow().as_ref(),
4862 Some(ActiveTooltip::Visible { .. })
4863 ));
4864
4865 test_app
4866 .update_window(any_window, |_, window, cx| {
4867 window.dispatch_event(
4868 MouseMoveEvent {
4869 position: point(px(75.), px(75.)),
4870 modifiers: Default::default(),
4871 pressed_button: None,
4872 }
4873 .to_platform_input(),
4874 cx,
4875 );
4876 })
4877 .unwrap();
4878
4879 assert!(active_tooltip.borrow().is_none());
4880 }
4881
4882 #[test]
4883 fn tooltip_hides_after_mouse_exits_window() {
4884 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
4885
4886 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4887 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4888
4889 test_app
4890 .dispatcher
4891 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
4892 test_app.run_until_parked();
4893
4894 assert!(matches!(
4895 active_tooltip.borrow().as_ref(),
4896 Some(ActiveTooltip::Visible { .. })
4897 ));
4898
4899 test_app
4900 .update_window(any_window, |_, window, cx| {
4901 window.dispatch_event(
4902 MouseExitEvent {
4903 position: point(px(-1.), px(-1.)),
4904 modifiers: Default::default(),
4905 pressed_button: None,
4906 }
4907 .to_platform_input(),
4908 cx,
4909 );
4910 })
4911 .unwrap();
4912
4913 assert!(active_tooltip.borrow().is_none());
4914 }
4915
4916 #[test]
4917 fn tooltip_does_not_show_after_mouse_exits_window() {
4918 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
4919
4920 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4921 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4922 assert!(matches!(
4923 active_tooltip.borrow().as_ref(),
4924 Some(ActiveTooltip::WaitingForShow { .. })
4925 ));
4926
4927 test_app
4928 .update_window(any_window, |_, window, cx| {
4929 window.dispatch_event(
4930 MouseExitEvent {
4931 position: point(px(-1.), px(-1.)),
4932 modifiers: Default::default(),
4933 pressed_button: None,
4934 }
4935 .to_platform_input(),
4936 cx,
4937 );
4938 })
4939 .unwrap();
4940
4941 test_app
4942 .dispatcher
4943 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
4944 test_app.run_until_parked();
4945
4946 assert!(active_tooltip.borrow().is_none());
4947 }
4948
4949 struct MouseDownOutOwner {
4950 mouse_down_out_count: Rc<RefCell<usize>>,
4951 }
4952
4953 impl Render for MouseDownOutOwner {
4954 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4955 let mouse_down_out_count = self.mouse_down_out_count.clone();
4956 div()
4957 .size_full()
4958 .child(div().id("target").w(px(50.)).h(px(50.)).on_mouse_down_out(
4959 move |_, _, _| {
4960 *mouse_down_out_count.borrow_mut() += 1;
4961 },
4962 ))
4963 }
4964 }
4965
4966 #[test]
4967 fn mouse_down_out_is_suppressed_while_window_prompt_is_active() {
4968 let mut test_app = TestAppContext::single();
4969 let mouse_down_out_count = Rc::new(RefCell::new(0));
4970 let window = test_app.add_window({
4971 let mouse_down_out_count = mouse_down_out_count.clone();
4972 move |_, _| MouseDownOutOwner {
4973 mouse_down_out_count,
4974 }
4975 });
4976 let any_window: AnyWindowHandle = window.into();
4977
4978 fn dispatch_mouse_down_outside_target(
4979 test_app: &mut TestAppContext,
4980 any_window: AnyWindowHandle,
4981 ) {
4982 test_app
4983 .update_window(any_window, |_, window, cx| {
4984 window.dispatch_event(
4985 MouseDownEvent {
4986 position: point(px(75.), px(75.)),
4987 button: MouseButton::Left,
4988 modifiers: Default::default(),
4989 click_count: 1,
4990 first_mouse: false,
4991 }
4992 .to_platform_input(),
4993 cx,
4994 );
4995 })
4996 .unwrap();
4997 }
4998
4999 test_app
5000 .update_window(any_window, |_, window, cx| {
5001 window.draw(cx).clear(cx);
5002 })
5003 .unwrap();
5004
5005 dispatch_mouse_down_outside_target(&mut test_app, any_window);
5006 assert_eq!(
5007 *mouse_down_out_count.borrow(),
5008 1,
5009 "mouse down outside the element should fire mouse-down-out listeners"
5010 );
5011
5012 test_app
5013 .update_window(any_window, |_, window, cx| {
5014 cx.set_prompt_builder(crate::fallback_prompt_renderer);
5015 let _receiver =
5016 window.prompt(crate::PromptLevel::Warning, "message", None, &["Ok"], cx);
5017 assert!(window.has_active_prompt());
5018 window.draw(cx).clear(cx);
5019 })
5020 .unwrap();
5021
5022 dispatch_mouse_down_outside_target(&mut test_app, any_window);
5023 assert_eq!(
5024 *mouse_down_out_count.borrow(),
5025 1,
5026 "mouse down over an active prompt should not fire mouse-down-out listeners"
5027 );
5028 }
5029
5030 #[test]
5031 fn test_write_a11y_info_string_and_numeric_properties() {
5032 let mut interactivity = Interactivity::default();
5033 interactivity.aria.label = Some("Buffer Font Size".into());
5034 interactivity.aria.value = Some("15".into());
5035 interactivity.aria.placeholder = Some("Search".into());
5036 interactivity.aria.numeric_value = Some(15.0);
5037 interactivity.aria.min_numeric_value = Some(6.0);
5038 interactivity.aria.max_numeric_value = Some(72.0);
5039 interactivity.aria.numeric_value_step = Some(1.0);
5040 interactivity.aria.disabled = true;
5041 interactivity.aria.read_only = true;
5042 interactivity.aria.modal = true;
5043 interactivity.aria.invalid = true;
5044 interactivity.aria.required = true;
5045 interactivity.aria.busy = true;
5046 interactivity.aria.live = Some(accesskit::Live::Polite);
5047 interactivity.aria.live_atomic = true;
5048
5049 let mut node = accesskit::Node::new(accesskit::Role::SpinButton);
5050 interactivity.write_a11y_info(&mut node);
5051
5052 assert_eq!(node.label(), Some("Buffer Font Size"));
5053 assert_eq!(node.value(), Some("15"));
5054 assert_eq!(node.placeholder(), Some("Search"));
5055 assert_eq!(node.numeric_value(), Some(15.0));
5056 assert_eq!(node.min_numeric_value(), Some(6.0));
5057 assert_eq!(node.max_numeric_value(), Some(72.0));
5058 assert_eq!(node.numeric_value_step(), Some(1.0));
5059 assert!(node.is_disabled());
5060 assert!(node.is_read_only());
5061 assert!(node.is_modal());
5062 assert_eq!(node.invalid(), Some(accesskit::Invalid::True));
5063 assert!(node.is_required());
5064 assert!(node.is_busy());
5065 assert_eq!(node.live(), Some(accesskit::Live::Polite));
5066 assert!(node.is_live_atomic());
5067 }
5068
5069 struct KeyboardActivationTest {
5072 focus_a: FocusHandle,
5073 focus_b: FocusHandle,
5074 clicks: Rc<RefCell<Vec<&'static str>>>,
5075 }
5076
5077 impl Render for KeyboardActivationTest {
5078 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
5079 let clicks_a = self.clicks.clone();
5080 let clicks_b = self.clicks.clone();
5081 div()
5082 .size_full()
5083 .child(
5084 div()
5085 .id("a")
5086 .w(px(50.))
5087 .h(px(50.))
5088 .track_focus(&self.focus_a)
5089 .role(crate::Role::Button)
5090 .child(
5091 div()
5092 .id("a-child")
5093 .track_focus(&self.focus_a)
5094 .role(crate::Role::Label),
5095 )
5096 .on_click(move |_, _, _| clicks_a.borrow_mut().push("a")),
5097 )
5098 .child(
5099 div()
5100 .id("b")
5101 .w(px(50.))
5102 .h(px(50.))
5103 .track_focus(&self.focus_b)
5104 .role(crate::Role::Button)
5105 .on_click(move |_, _, _| clicks_b.borrow_mut().push("b")),
5106 )
5107 .child(
5108 div()
5109 .id("generated")
5110 .w(px(50.))
5111 .h(px(50.))
5112 .tab_index(0)
5113 .role(crate::Role::Button),
5114 )
5115 }
5116 }
5117
5118 fn setup_keyboard_activation_test() -> (
5119 TestAppContext,
5120 AnyWindowHandle,
5121 Rc<RefCell<Vec<&'static str>>>,
5122 FocusHandle,
5123 FocusHandle,
5124 ) {
5125 let mut cx = TestAppContext::single();
5126 let (focus_a, focus_b) = cx.update(|cx| (cx.focus_handle(), cx.focus_handle()));
5127 let clicks: Rc<RefCell<Vec<&'static str>>> = Rc::new(RefCell::new(Vec::new()));
5128 let window = cx.add_window({
5129 let focus_a = focus_a.clone();
5130 let focus_b = focus_b.clone();
5131 let clicks = clicks.clone();
5132 move |_, _| KeyboardActivationTest {
5133 focus_a,
5134 focus_b,
5135 clicks,
5136 }
5137 });
5138 (cx, window.into(), clicks, focus_a, focus_b)
5139 }
5140
5141 fn focus_and_draw(cx: &mut TestAppContext, window: AnyWindowHandle, handle: &FocusHandle) {
5144 cx.update_window(window, |_, window, cx| window.focus(handle, cx))
5145 .unwrap();
5146 cx.run_until_parked();
5147 cx.update_window(window, |_, window, cx| {
5148 window.draw(cx).clear(cx);
5149 })
5150 .unwrap();
5151 }
5152
5153 #[test]
5154 fn accessibility_focus_action_focuses_owning_handle() {
5155 let (mut cx, window, _clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5156 cx.activate_accessibility(window);
5157 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
5158 .unwrap();
5159 let target = cx
5160 .update_window(window, |_, window, _| {
5161 let tree: serde_json::Value = serde_json::from_str(
5162 &window
5163 .debug_a11y_tree_json()
5164 .expect("accessibility adapter is active"),
5165 )
5166 .expect("valid accessibility debug tree");
5167 let node = tree["nodes"]
5168 .as_object()
5169 .and_then(|nodes| {
5170 nodes
5171 .values()
5172 .find(|node| node["element_id"] == "Name(\"a\")")
5173 })
5174 .expect("focusable role-bearing node");
5175 accesskit::NodeId(
5176 node["accesskit_id"]
5177 .as_str()
5178 .expect("node id")
5179 .parse()
5180 .expect("numeric node id"),
5181 )
5182 })
5183 .unwrap();
5184 cx.dispatch_accessibility_action(
5185 window,
5186 accesskit::ActionRequest {
5187 action: accesskit::Action::Focus,
5188 target_tree: accesskit::TreeId::ROOT,
5189 target_node: target,
5190 data: None,
5191 },
5192 );
5193
5194 cx.update_window(window, |_, window, _| {
5195 assert!(focus_a.is_focused(window));
5196 })
5197 .unwrap();
5198 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
5199 .unwrap();
5200 cx.update_window(window, |_, window, _| {
5201 let tree: serde_json::Value = serde_json::from_str(
5202 &window
5203 .debug_a11y_tree_json()
5204 .expect("accessibility adapter is active"),
5205 )
5206 .expect("valid accessibility debug tree");
5207 let focused = tree["gpui_focus"].as_str().expect("focused debug id");
5208 assert_eq!(tree["nodes"][focused]["element_id"], "Name(\"a-child\")");
5209 })
5210 .unwrap();
5211 }
5212
5213 #[test]
5214 fn accessibility_focus_action_focuses_generated_tab_handle() {
5215 let (mut cx, window, _clicks, _focus_a, _focus_b) = setup_keyboard_activation_test();
5216 cx.activate_accessibility(window);
5217 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
5218 .unwrap();
5219 let (target, expected_debug_focus) = cx
5220 .update_window(window, |_, window, _| {
5221 let tree: serde_json::Value = serde_json::from_str(
5222 &window
5223 .debug_a11y_tree_json()
5224 .expect("accessibility adapter is active"),
5225 )
5226 .expect("valid accessibility debug tree");
5227 let (debug_id, node) = tree["nodes"]
5228 .as_object()
5229 .and_then(|nodes| {
5230 nodes
5231 .iter()
5232 .find(|(_, node)| node["element_id"] == "Name(\"generated\")")
5233 })
5234 .expect("generated focusable role-bearing node");
5235 (
5236 accesskit::NodeId(
5237 node["accesskit_id"]
5238 .as_str()
5239 .expect("node id")
5240 .parse()
5241 .expect("numeric node id"),
5242 ),
5243 debug_id.clone(),
5244 )
5245 })
5246 .unwrap();
5247 cx.dispatch_accessibility_action(
5248 window,
5249 accesskit::ActionRequest {
5250 action: accesskit::Action::Focus,
5251 target_tree: accesskit::TreeId::ROOT,
5252 target_node: target,
5253 data: None,
5254 },
5255 );
5256 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
5257 .unwrap();
5258
5259 cx.update_window(window, |_, window, _| {
5260 let tree: serde_json::Value = serde_json::from_str(
5261 &window
5262 .debug_a11y_tree_json()
5263 .expect("accessibility adapter is active"),
5264 )
5265 .expect("valid accessibility debug tree");
5266 assert_eq!(tree["gpui_focus"], expected_debug_focus);
5267 })
5268 .unwrap();
5269 }
5270
5271 fn key_down(cx: &mut TestAppContext, window: AnyWindowHandle, key: &str) {
5272 let keystroke = Keystroke::parse(key).unwrap();
5273 cx.update_window(window, |_, window, cx| {
5274 window.dispatch_event(
5275 KeyDownEvent {
5276 keystroke,
5277 is_held: false,
5278 prefer_character_input: false,
5279 }
5280 .to_platform_input(),
5281 cx,
5282 );
5283 })
5284 .unwrap();
5285 }
5286
5287 fn key_up(cx: &mut TestAppContext, window: AnyWindowHandle, key: &str) {
5288 let keystroke = Keystroke::parse(key).unwrap();
5289 cx.update_window(window, |_, window, cx| {
5290 window.dispatch_event(KeyUpEvent { keystroke }.to_platform_input(), cx);
5291 })
5292 .unwrap();
5293 }
5294
5295 #[test]
5297 fn keyboard_activation_fires_click_on_same_element() {
5298 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5299
5300 focus_and_draw(&mut cx, window, &focus_a);
5301 key_down(&mut cx, window, "enter");
5302 key_up(&mut cx, window, "enter");
5303
5304 assert_eq!(*clicks.borrow(), vec!["a"]);
5305 }
5306
5307 #[test]
5312 fn keyboard_activation_does_not_leak_across_focus_change() {
5313 let (mut cx, window, clicks, focus_a, focus_b) = setup_keyboard_activation_test();
5314
5315 focus_and_draw(&mut cx, window, &focus_a);
5317 key_down(&mut cx, window, "enter");
5318
5319 focus_and_draw(&mut cx, window, &focus_b);
5321 key_up(&mut cx, window, "enter");
5322
5323 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
5326 }
5327
5328 #[test]
5333 fn keyboard_activation_does_not_leak_when_focus_returns() {
5334 let (mut cx, window, clicks, focus_a, focus_b) = setup_keyboard_activation_test();
5335
5336 focus_and_draw(&mut cx, window, &focus_a);
5338 key_down(&mut cx, window, "enter");
5339
5340 focus_and_draw(&mut cx, window, &focus_b);
5343 focus_and_draw(&mut cx, window, &focus_a);
5344 key_up(&mut cx, window, "enter");
5345
5346 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
5348 }
5349
5350 #[test]
5355 fn keyboard_activation_cleared_by_intervening_key_release() {
5356 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5357
5358 focus_and_draw(&mut cx, window, &focus_a);
5359 key_down(&mut cx, window, "escape");
5360 key_down(&mut cx, window, "space");
5361 key_up(&mut cx, window, "escape");
5362 key_up(&mut cx, window, "space");
5363
5364 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
5365 }
5366
5367 #[test]
5371 fn keyboard_activation_does_not_distinguish_space_and_enter() {
5372 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5373
5374 focus_and_draw(&mut cx, window, &focus_a);
5375 key_down(&mut cx, window, "space");
5376 key_up(&mut cx, window, "enter");
5377
5378 assert_eq!(*clicks.borrow(), vec!["a"]);
5379 }
5380
5381 #[test]
5384 fn keyboard_activation_cleared_by_intervening_keydown() {
5385 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5386
5387 focus_and_draw(&mut cx, window, &focus_a);
5388 key_down(&mut cx, window, "enter");
5389 key_down(&mut cx, window, "a");
5390 key_up(&mut cx, window, "enter");
5391
5392 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
5393 }
5394
5395 #[test]
5398 fn keyboard_activation_ignores_modified_keys() {
5399 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
5400
5401 focus_and_draw(&mut cx, window, &focus_a);
5402 key_down(&mut cx, window, "cmd-enter");
5403 key_up(&mut cx, window, "cmd-enter");
5404
5405 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
5406 }
5407
5408 struct TabGroupFocus {
5412 group_a: FocusHandle,
5413 item_a: FocusHandle,
5414 group_b: FocusHandle,
5415 item_b: FocusHandle,
5416 }
5417
5418 impl Render for TabGroupFocus {
5419 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5420 fn group(container: &FocusHandle, item: &FocusHandle) -> Div {
5421 div()
5422 .track_focus(container)
5423 .tab_group()
5424 .child(div().track_focus(item))
5425 }
5426 div()
5427 .child(group(&self.group_a, &self.item_a))
5428 .child(group(&self.group_b, &self.item_b))
5429 }
5430 }
5431
5432 #[test]
5436 fn focus_next_from_tab_group_container_enters_that_group() {
5437 let mut cx = TestAppContext::single();
5438 let (group_a, item_a, group_b, item_b) = cx.update(|cx| {
5439 (
5440 cx.focus_handle(),
5441 cx.focus_handle().tab_stop(true),
5442 cx.focus_handle(),
5443 cx.focus_handle().tab_stop(true),
5444 )
5445 });
5446 let window: AnyWindowHandle = cx
5447 .add_window({
5448 let (group_a, item_a, group_b, item_b) =
5449 (group_a, item_a, group_b.clone(), item_b.clone());
5450 move |_, _| TabGroupFocus {
5451 group_a,
5452 item_a,
5453 group_b,
5454 item_b,
5455 }
5456 })
5457 .into();
5458 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
5459 .unwrap();
5460
5461 let focused = cx
5463 .update_window(window, |_, window, cx| {
5464 window.focus(&group_b, cx);
5465 window.focus_next(cx);
5466 window.focused(cx).map(|handle| handle.id)
5467 })
5468 .unwrap();
5469
5470 assert_eq!(focused, Some(item_b.id));
5471 }
5472}