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