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