1#[cfg(debug_assertions)]
28use crate::{PaintQuad, TextAlign};
29
30#[cfg(test)]
31use crate::{HighlightStyle, StyledText, hsla};
32
33use crate::{
34 Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, AppContext, Bounds, ClickEvent,
35 CursorStyle, DispatchPhase, Display, Element, ElementId, Entity, EntityId, ExternalDragPayload,
36 FileDropEvent, FocusHandle, Global, GlobalElementId, Hitbox, HitboxBehavior, HitboxId,
37 InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, KeyboardButton,
38 KeyboardClickEvent, LayoutId, LongPressEvent, ModifiersChangedEvent, MouseButton,
39 MouseClickEvent, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MousePressureEvent,
40 MouseUpEvent, OngoingScroll, Overflow, ParentElement, PinchEvent, Pixels, Point,
41 PointerChangeCause, Render, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement,
42 StyleTransitionContext, StyleTransitionState, StyleTransitions, Styled, Task, TooltipId,
43 TouchPhase, Visibility, Window, WindowControlArea, point, px, size,
44};
45use collections::HashMap;
46use gpui_util::ResultExt;
47use itertools::Either;
48use refineable::Refineable;
49use smallvec::SmallVec;
50use std::{
51 any::{Any, TypeId},
52 cell::{Cell, RefCell},
53 cmp::Ordering,
54 fmt::Debug,
55 marker::PhantomData,
56 mem,
57 rc::Rc,
58 sync::Arc,
59 time::Duration,
60};
61
62use super::ImageCacheProvider;
63
64mod inline;
65pub(crate) use inline::InlineContent;
66use inline::InlineDivFrameState;
67#[cfg(feature = "stacker")]
68type StackSafe<T> = stacksafe::StackSafe<T>;
69#[cfg(not(feature = "stacker"))]
70type StackSafe<T> = T;
71
72const DRAG_THRESHOLD: f64 = 2.;
73const DEFAULT_TOOLTIP_SHOW_DELAY: Duration = Duration::from_millis(500);
74const HOVERABLE_TOOLTIP_HIDE_DELAY: Duration = Duration::from_millis(500);
75
76pub struct GroupStyle {
78 pub group: SharedString,
80
81 pub style: Box<StyleRefinement>,
83}
84
85pub struct DragMoveEvent<T> {
87 pub event: MouseMoveEvent,
89
90 pub bounds: Bounds<Pixels>,
92 drag: PhantomData<T>,
93 dragged_item: Arc<dyn Any>,
94}
95
96impl<T: 'static> DragMoveEvent<T> {
97 pub fn drag<'b>(&self, cx: &'b App) -> &'b T {
104 cx.active_drag
105 .as_ref()
106 .and_then(|drag| drag.value.downcast_ref::<T>())
107 .expect("DragMoveEvent is only valid when the stored active drag is of the same type.")
108 }
109
110 pub fn dragged_item(&self) -> &dyn Any {
112 self.dragged_item.as_ref()
113 }
114}
115
116impl Interactivity {
117 #[cfg(any(feature = "inspector", debug_assertions))]
119 #[track_caller]
120 pub fn new() -> Interactivity {
121 Interactivity {
122 source_location: Some(core::panic::Location::caller()),
123 ..Default::default()
124 }
125 }
126
127 #[cfg(not(any(feature = "inspector", debug_assertions)))]
129 pub fn new() -> Interactivity {
130 Interactivity::default()
131 }
132
133 pub fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
138 #[cfg(any(feature = "inspector", debug_assertions))]
139 {
140 self.source_location
141 }
142
143 #[cfg(not(any(feature = "inspector", debug_assertions)))]
144 {
145 None
146 }
147 }
148
149 pub fn on_mouse_down(
155 &mut self,
156 button: MouseButton,
157 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
158 ) {
159 self.mouse_down_listeners
160 .push(Box::new(move |event, phase, hitbox, window, cx| {
161 if phase == DispatchPhase::Bubble
162 && event.button == button
163 && hitbox.is_hovered(window)
164 {
165 (listener)(event, window, cx)
166 }
167 }));
168 }
169
170 pub fn capture_any_mouse_down(
175 &mut self,
176 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
177 ) {
178 self.mouse_down_listeners
179 .push(Box::new(move |event, phase, hitbox, window, cx| {
180 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
181 (listener)(event, window, cx)
182 }
183 }));
184 }
185
186 pub fn on_any_mouse_down(
190 &mut self,
191 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
192 ) {
193 self.mouse_down_listeners
194 .push(Box::new(move |event, phase, hitbox, window, cx| {
195 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
196 (listener)(event, window, cx)
197 }
198 }));
199 }
200
201 pub fn on_mouse_pressure(
206 &mut self,
207 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
208 ) {
209 self.mouse_pressure_listeners
210 .push(Box::new(move |event, phase, hitbox, window, cx| {
211 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
212 (listener)(event, window, cx)
213 }
214 }));
215 }
216
217 pub fn capture_mouse_pressure(
222 &mut self,
223 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
224 ) {
225 self.mouse_pressure_listeners
226 .push(Box::new(move |event, phase, hitbox, window, cx| {
227 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
228 (listener)(event, window, cx)
229 }
230 }));
231 }
232
233 pub fn on_mouse_up(
238 &mut self,
239 button: MouseButton,
240 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
241 ) {
242 self.mouse_up_listeners
243 .push(Box::new(move |event, phase, hitbox, window, cx| {
244 if phase == DispatchPhase::Bubble
245 && event.button == button
246 && hitbox.is_hovered(window)
247 {
248 (listener)(event, window, cx)
249 }
250 }));
251 }
252
253 pub fn capture_any_mouse_up(
258 &mut self,
259 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
260 ) {
261 self.mouse_up_listeners
262 .push(Box::new(move |event, phase, hitbox, window, cx| {
263 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
264 (listener)(event, window, cx)
265 }
266 }));
267 }
268
269 pub fn on_any_mouse_up(
273 &mut self,
274 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
275 ) {
276 self.mouse_up_listeners
277 .push(Box::new(move |event, phase, hitbox, window, cx| {
278 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
279 (listener)(event, window, cx)
280 }
281 }));
282 }
283
284 pub fn on_mouse_down_out(
289 &mut self,
290 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
291 ) {
292 self.mouse_down_listeners
293 .push(Box::new(move |event, phase, hitbox, window, cx| {
294 if phase == DispatchPhase::Capture
295 && !window.has_active_prompt()
296 && !hitbox.contains(&window.mouse_position())
297 {
298 (listener)(event, window, cx)
299 }
300 }));
301 }
302
303 pub fn on_mouse_down_all(
308 &mut self,
309 listener: impl Fn(&MouseDownEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static,
310 ) {
311 self.mouse_down_listeners.push(Box::new(listener));
312 }
313
314 pub fn on_mouse_up_out(
319 &mut self,
320 button: MouseButton,
321 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
322 ) {
323 self.mouse_up_listeners
324 .push(Box::new(move |event, phase, hitbox, window, cx| {
325 if phase == DispatchPhase::Capture
326 && event.button == button
327 && !hitbox.is_hovered(window)
328 {
329 (listener)(event, window, cx);
330 }
331 }));
332 }
333
334 pub fn on_mouse_up_all(
339 &mut self,
340 listener: impl Fn(&MouseUpEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static,
341 ) {
342 self.mouse_up_listeners.push(Box::new(listener));
343 }
344
345 pub fn on_mouse_move(
350 &mut self,
351 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
352 ) {
353 self.mouse_move_listeners
354 .push(Box::new(move |event, phase, hitbox, window, cx| {
355 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
356 (listener)(event, window, cx);
357 }
358 }));
359 }
360
361 pub fn on_mouse_move_all(
367 &mut self,
368 listener: impl Fn(&MouseMoveEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static,
369 ) {
370 self.mouse_move_listeners.push(Box::new(listener));
371 }
372
373 pub fn on_mouse_exit(
377 &mut self,
378 listener: impl Fn(&MouseExitEvent, &mut Window, &mut App) + 'static,
379 ) {
380 self.mouse_exit_listeners
381 .push(Box::new(move |event, phase, hitbox, window, cx| {
382 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
383 (listener)(event, window, cx);
384 }
385 }));
386 }
387
388 pub fn on_file_drop_exit(
396 &mut self,
397 listener: impl Fn(&FileDropEvent, &mut Window, &mut App) + 'static,
398 ) {
399 self.file_drop_exit_listeners
400 .push(Box::new(move |event, phase, hitbox, window, cx| {
401 if phase == DispatchPhase::Bubble
402 && matches!(event, FileDropEvent::Exited)
403 && hitbox.id.is_hovered_ignoring_last_input(window)
404 {
405 (listener)(event, window, cx);
406 }
407 }));
408 }
409
410 pub fn on_drag_move<T>(
415 &mut self,
416 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
417 ) where
418 T: 'static,
419 {
420 self.mouse_move_listeners
421 .push(Box::new(move |event, phase, hitbox, window, cx| {
422 if phase == DispatchPhase::Capture
423 && let Some(drag) = &cx.active_drag
424 && drag.value.as_ref().type_id() == TypeId::of::<T>()
425 {
426 (listener)(
427 &DragMoveEvent {
428 event: event.clone(),
429 bounds: hitbox.bounds,
430 drag: PhantomData,
431 dragged_item: Arc::clone(&drag.value),
432 },
433 window,
434 cx,
435 );
436 }
437 }));
438 }
439
440 pub fn on_scroll_wheel(
446 &mut self,
447 listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
448 ) {
449 self.scroll_wheel_listeners
450 .push(Box::new(move |event, phase, hitbox, window, cx| {
451 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
452 (listener)(event, window, cx);
453 }
454 }));
455 }
456
457 pub fn on_pinch(&mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) {
462 self.pinch_listeners
463 .push(Box::new(move |event, phase, hitbox, window, cx| {
464 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
465 (listener)(event, window, cx);
466 }
467 }));
468 }
469
470 pub fn capture_pinch(
474 &mut self,
475 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
476 ) {
477 self.pinch_listeners
478 .push(Box::new(move |event, phase, _hitbox, window, cx| {
479 if phase == DispatchPhase::Capture {
480 (listener)(event, window, cx);
481 } else {
482 cx.propagate();
483 }
484 }));
485 }
486
487 pub fn capture_action<A: Action>(
493 &mut self,
494 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
495 ) {
496 self.action_listeners.push((
497 TypeId::of::<A>(),
498 Box::new(move |action, phase, window, cx| {
499 let action = action.downcast_ref().unwrap();
500 if phase == DispatchPhase::Capture {
501 (listener)(action, window, cx)
502 } else {
503 cx.propagate();
504 }
505 }),
506 ));
507 }
508
509 #[track_caller]
536 #[inline(always)]
537 pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Window, &mut App) + 'static) {
538 self.action_listeners.push((
539 TypeId::of::<A>(),
540 Box::new(move |action, phase, window, cx| {
541 let action = action.downcast_ref().unwrap();
542 if phase == DispatchPhase::Bubble {
543 (listener)(action, window, cx)
544 }
545 }),
546 ));
547 }
548
549 pub fn on_boxed_action(
555 &mut self,
556 action: &dyn Action,
557 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
558 ) {
559 let action = action.boxed_clone();
560 self.action_listeners.push((
561 (*action).type_id(),
562 Box::new(move |_, phase, window, cx| {
563 if phase == DispatchPhase::Bubble {
564 (listener)(&*action, window, cx)
565 }
566 }),
567 ));
568 }
569
570 pub fn on_key_down(
575 &mut self,
576 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
577 ) {
578 self.key_down_listeners
579 .push(Box::new(move |event, phase, window, cx| {
580 if phase == DispatchPhase::Bubble {
581 (listener)(event, window, cx)
582 }
583 }));
584 }
585
586 pub fn capture_key_down(
591 &mut self,
592 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
593 ) {
594 self.key_down_listeners
595 .push(Box::new(move |event, phase, window, cx| {
596 if phase == DispatchPhase::Capture {
597 listener(event, window, cx)
598 }
599 }));
600 }
601
602 pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static) {
606 self.key_up_listeners
607 .push(Box::new(move |event, phase, window, cx| {
608 if phase == DispatchPhase::Bubble {
609 listener(event, window, cx)
610 }
611 }));
612 }
613
614 pub fn capture_key_up(
618 &mut self,
619 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
620 ) {
621 self.key_up_listeners
622 .push(Box::new(move |event, phase, window, cx| {
623 if phase == DispatchPhase::Capture {
624 listener(event, window, cx)
625 }
626 }));
627 }
628
629 pub fn on_modifiers_changed(
634 &mut self,
635 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
636 ) {
637 self.modifiers_changed_listeners.push(Box::new(listener));
638 }
639
640 pub fn on_drop<T: 'static>(&mut self, listener: impl Fn(&T, &mut Window, &mut App) + 'static) {
645 let type_id = TypeId::of::<T>();
646 let on_drop = Box::new(
647 move |dragged_any: &dyn Any, _evt: &MouseUpEvent, window: &mut Window, cx: &mut App| {
648 listener(dragged_any.downcast_ref().unwrap(), window, cx);
649 },
650 );
651 match self.get_mut_drop_listener(type_id) {
653 Some((_, drop_listener)) => {
654 drop_listener.on_drop = Some(on_drop);
655 }
656 None => {
657 self.drop_listeners.push((
658 TypeId::of::<T>(),
659 DropListener::<dyn Any> {
660 can_drop: None,
661 on_drag_over: None,
662 on_drop: Some(on_drop),
663 },
664 ));
665 }
666 }
667 }
668
669 pub fn on_drop_alt<T: 'static>(&mut self, listener: DropListener<T>) {
673 self.drop_listeners
674 .push((TypeId::of::<T>(), listener.into_any()));
675 }
676
677 fn get_mut_drop_listener(
679 &mut self,
680 type_id: TypeId,
681 ) -> Option<&mut (TypeId, DropListener<dyn Any>)> {
682 let mut iter = self.drop_listeners.iter_mut();
683 iter.find(|(existing_type_id, _listener)| *existing_type_id == type_id)
684 }
685
686 pub fn can_drop(
691 &mut self,
692 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
693 ) {
694 self.can_drop_predicate = Some(Box::new(move |value, hitbox, window, cx| {
695 hitbox.is_hovered(window) && predicate(value, window, cx)
696 }));
697 }
698
699 pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
706 where
707 Self: Sized,
708 {
709 self.click_listeners.push(Rc::new(listener));
710 }
711
712 pub fn on_aux_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
716 where
717 Self: Sized,
718 {
719 self.aux_click_listeners.push(Rc::new(listener));
720 }
721
722 pub fn on_drag<T, W>(
728 &mut self,
729 value: T,
730 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
731 ) where
732 Self: Sized,
733 T: 'static,
734 W: 'static + Render,
735 {
736 let value = Arc::new(value);
737 self.on_drag_alt(move |cursor_offset, cursor_style, window, cx| {
738 let view = constructor(value.as_ref(), cursor_offset, window, cx);
739 AnyDrag {
740 view: view.into(),
741 value: value.clone(),
742 cursor_offset,
743 cursor_style,
744 external_payload_source: None,
745 }
746 });
747 }
748
749 pub fn on_drag_alt<F>(&mut self, listener: F)
755 where
756 Self: Sized,
757 F: 'static + Fn(Point<Pixels>, Option<CursorStyle>, &mut Window, &mut App) -> AnyDrag,
758 {
759 debug_assert!(
760 self.drag_listener.is_none(),
761 "calling on_drag more than once on the same element is not supported"
762 );
763 self.drag_listener = Some(Box::new(listener));
764 }
765
766 pub fn external_drag_payload<T>(
777 &mut self,
778 resolver: impl Fn(&T, &mut Window, &mut App) -> Option<ExternalDragPayload> + 'static,
779 ) where
780 Self: Sized,
781 T: 'static,
782 {
783 let Some(drag_listener) = self.drag_listener.take() else {
784 debug_assert!(false, "external_drag_payload must be called after on_drag");
785 return;
786 };
787
788 let resolver = Rc::new(resolver);
789 self.on_drag_alt(move |cursor_offset, cursor_style, window, cx| {
790 let mut any_drag = drag_listener(cursor_offset, cursor_style, window, cx);
791 debug_assert!(
792 any_drag.external_payload_source.is_none(),
793 "calling external_drag_payload more than once on the same element is not supported"
794 );
795 any_drag.external_payload_source = Some(Box::new({
796 let value = any_drag.value.clone();
797 let resolver = resolver.clone();
798 move |window, cx| {
799 let value_ref = value
800 .downcast_ref::<T>()
801 .expect("drag value type does not match external payload type");
802 resolver(value_ref, window, cx)
803 }
804 }));
805 any_drag
806 });
807 }
808
809 pub fn on_drag_over_any(
815 &mut self,
816 predicate: impl Fn(StyleRefinement, &dyn Any, &mut Window, &mut App) -> StyleRefinement
817 + 'static,
818 ) {
819 self.drag_over_style = Some(Box::new(move |value, window, app| {
820 predicate(StyleRefinement::default(), value, window, app)
821 }));
822 }
823
824 #[inline(always)]
832 pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static)
833 where
834 Self: Sized,
835 {
836 self.on_hover_region(
837 |_, _| Some(()),
838 move |value, window, cx| listener(&value.is_some(), window, cx),
839 );
840 }
841
842 pub fn cursor_with(
845 &mut self,
846 resolver: impl Fn(Point<Pixels>, Bounds<Pixels>) -> Option<CursorStyle> + 'static,
847 ) where
848 Self: Sized,
849 {
850 self.cursor_style_resolver = Some(Rc::new(resolver));
851 }
852
853 pub fn on_hover_region<R: PartialEq + 'static>(
858 &mut self,
859 project: impl Fn(Point<Pixels>, Bounds<Pixels>) -> Option<R> + 'static,
860 listener: impl Fn(&Option<R>, &mut Window, &mut App) + 'static,
861 ) where
862 Self: Sized,
863 {
864 self.hover_region_listeners
865 .push(Box::new(TypedHoverRegionListener {
866 project: Rc::new(project),
867 callback: Rc::new(listener),
868 }));
869 }
870
871 pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static)
881 where
882 Self: Sized,
883 {
884 debug_assert!(
885 self.tooltip_builder.is_none(),
886 "calling tooltip more than once on the same element is not supported"
887 );
888 self.tooltip_builder = Some(TooltipBuilder {
889 build: Rc::new(build_tooltip),
890 hoverable: false,
891 });
892 }
893
894 pub fn hover_listener_mode(&mut self, mode: HoverListenerMode)
898 where
899 Self: Sized,
900 {
901 self.hover_listener_mode = mode;
902 }
903
904 pub fn hoverable_tooltip(
913 &mut self,
914 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
915 ) where
916 Self: Sized,
917 {
918 debug_assert!(
919 self.tooltip_builder.is_none(),
920 "calling tooltip more than once on the same element is not supported"
921 );
922 self.tooltip_builder = Some(TooltipBuilder {
923 build: Rc::new(build_tooltip),
924 hoverable: true,
925 });
926 }
927
928 pub fn tooltip_show_delay(&mut self, delay: Duration) {
933 self.tooltip_show_delay = Some(delay);
934 }
935
936 pub fn occlude_mouse(&mut self) {
941 self.hitbox_behavior = HitboxBehavior::BlockMouse;
942 }
943
944 pub fn window_control_area(&mut self, area: WindowControlArea) {
949 self.window_control = Some(area);
950 }
951
952 pub fn block_mouse_except_scroll(&mut self) {
956 self.hitbox_behavior = HitboxBehavior::BlockMouseExceptScroll;
957 }
958
959 fn has_pinch_listeners(&self) -> bool {
960 !self.pinch_listeners.is_empty()
961 }
962
963 pub fn on_prepaint(
968 &mut self,
969 listener: impl Fn(&InteractivityPrepaint, &mut Window, &mut App) + 'static,
970 ) where
971 Self: Sized,
972 {
973 self.prepaint_listeners
974 .push(Box::new(move |payload, window, cx| {
975 listener(payload, window, cx)
976 }));
977 }
978}
979
980pub trait InteractiveElement: Sized {
1007 fn interactivity(&mut self) -> &mut Interactivity;
1011
1012 fn on_file_drop_exit(
1020 mut self,
1021 listener: impl Fn(&FileDropEvent, &mut Window, &mut App) + 'static,
1022 ) -> Self {
1023 self.interactivity().on_file_drop_exit(listener);
1024 self
1025 }
1026
1027 #[track_caller]
1036 fn transitions(mut self, build: impl FnOnce(StyleTransitions) -> StyleTransitions) -> Self {
1037 let transitions = build(StyleTransitions::new());
1038 let interactivity = self.interactivity();
1039 interactivity
1040 .element_id
1041 .get_or_insert_with(|| ElementId::CodeLocation(*core::panic::Location::caller()));
1042 interactivity.style_transitions = Some(Box::new(transitions));
1043 self
1044 }
1045
1046 fn group(mut self, group: impl Into<SharedString>) -> Self {
1053 self.interactivity().group = Some(group.into());
1054 self
1055 }
1056
1057 fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
1073 self.interactivity().element_id = Some(id.into());
1074
1075 Stateful { element: self }
1076 }
1077
1078 fn track_focus(mut self, focus_handle: &FocusHandle) -> Self {
1085 self.interactivity().focusable = true;
1086 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
1087 self
1088 }
1089
1090 fn tab_stop(mut self, tab_stop: bool) -> Self {
1096 self.interactivity().tab_stop = tab_stop;
1097 self
1098 }
1099
1100 fn tab_index(mut self, index: isize) -> Self {
1107 self.interactivity().focusable = true;
1108 self.interactivity().tab_index = Some(index);
1109 self.interactivity().tab_stop = true;
1110 self
1111 }
1112
1113 fn tab_group(mut self) -> Self {
1119 self.interactivity().tab_group = true;
1120 if self.interactivity().tab_index.is_none() {
1121 self.interactivity().tab_index = Some(0);
1122 }
1123 self
1124 }
1125
1126 fn key_context<C, E>(mut self, key_context: C) -> Self
1132 where
1133 C: TryInto<KeyContext, Error = E>,
1134 E: std::fmt::Display,
1135 {
1136 if let Some(key_context) = key_context.try_into().log_err() {
1137 self.interactivity().key_context = Some(key_context);
1138 }
1139 self
1140 }
1141
1142 fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
1157 debug_assert!(
1158 self.interactivity().hover_style.is_none(),
1159 "hover style already set"
1160 );
1161 self.interactivity().hover_style = Some(Box::new(f(StyleRefinement::default())));
1162 self
1163 }
1164
1165 fn group_hover(
1170 mut self,
1171 group_name: impl Into<SharedString>,
1172 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1173 ) -> Self {
1174 self.interactivity().group_hover_style = Some(GroupStyle {
1175 group: group_name.into(),
1176 style: Box::new(f(StyleRefinement::default())),
1177 });
1178 self
1179 }
1180
1181 fn on_mouse_down(
1187 mut self,
1188 button: MouseButton,
1189 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
1190 ) -> Self {
1191 self.interactivity().on_mouse_down(button, listener);
1192 self
1193 }
1194
1195 #[cfg(any(test, all(debug_assertions, feature = "test-support")))]
1201 #[inline(always)]
1202 fn debug_selector(mut self, f: impl FnOnce() -> String) -> Self {
1203 self.interactivity().debug_selector = Some(f());
1204 self
1205 }
1206
1207 #[cfg(not(any(test, all(debug_assertions, feature = "test-support"))))]
1209 #[inline]
1210 fn debug_selector(self, _: impl FnOnce() -> String) -> Self {
1211 self
1212 }
1213
1214 fn capture_any_mouse_down(
1219 mut self,
1220 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
1221 ) -> Self {
1222 self.interactivity().capture_any_mouse_down(listener);
1223 self
1224 }
1225
1226 fn on_any_mouse_down(
1230 mut self,
1231 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
1232 ) -> Self {
1233 self.interactivity().on_any_mouse_down(listener);
1234 self
1235 }
1236
1237 fn on_mouse_up(
1242 mut self,
1243 button: MouseButton,
1244 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
1245 ) -> Self {
1246 self.interactivity().on_mouse_up(button, listener);
1247 self
1248 }
1249
1250 fn capture_any_mouse_up(
1254 mut self,
1255 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
1256 ) -> Self {
1257 self.interactivity().capture_any_mouse_up(listener);
1258 self
1259 }
1260
1261 fn on_mouse_pressure(
1265 mut self,
1266 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
1267 ) -> Self {
1268 self.interactivity().on_mouse_pressure(listener);
1269 self
1270 }
1271
1272 fn capture_mouse_pressure(
1276 mut self,
1277 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
1278 ) -> Self {
1279 self.interactivity().capture_mouse_pressure(listener);
1280 self
1281 }
1282
1283 fn on_mouse_down_out(
1288 mut self,
1289 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
1290 ) -> Self {
1291 self.interactivity().on_mouse_down_out(listener);
1292 self
1293 }
1294
1295 fn on_mouse_down_all(
1300 mut self,
1301 listener: impl Fn(&MouseDownEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static,
1302 ) -> Self {
1303 self.interactivity().on_mouse_down_all(listener);
1304 self
1305 }
1306
1307 fn on_mouse_up_out(
1312 mut self,
1313 button: MouseButton,
1314 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
1315 ) -> Self {
1316 self.interactivity().on_mouse_up_out(button, listener);
1317 self
1318 }
1319
1320 fn on_mouse_up_all(
1325 mut self,
1326 listener: impl Fn(&MouseUpEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static,
1327 ) -> Self {
1328 self.interactivity().on_mouse_up_all(listener);
1329 self
1330 }
1331
1332 fn on_mouse_move(
1336 mut self,
1337 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
1338 ) -> Self {
1339 self.interactivity().on_mouse_move(listener);
1340 self
1341 }
1342
1343 fn on_mouse_move_all(
1348 mut self,
1349 listener: impl Fn(&MouseMoveEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static,
1350 ) -> Self {
1351 self.interactivity().on_mouse_move_all(listener);
1352 self
1353 }
1354
1355 fn on_mouse_exit(
1359 mut self,
1360 listener: impl Fn(&MouseExitEvent, &mut Window, &mut App) + 'static,
1361 ) -> Self {
1362 self.interactivity().on_mouse_exit(listener);
1363 self
1364 }
1365
1366 fn on_drag_move<T: 'static>(
1372 mut self,
1373 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
1374 ) -> Self {
1375 self.interactivity().on_drag_move(listener);
1376 self
1377 }
1378
1379 fn on_scroll_wheel(
1385 mut self,
1386 listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
1387 ) -> Self {
1388 self.interactivity().on_scroll_wheel(listener);
1389 self
1390 }
1391
1392 fn on_pinch(mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) -> Self {
1396 self.interactivity().on_pinch(listener);
1397 self
1398 }
1399
1400 fn capture_pinch(
1404 mut self,
1405 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
1406 ) -> Self {
1407 self.interactivity().capture_pinch(listener);
1408 self
1409 }
1410
1411 fn capture_action<A: Action>(
1417 mut self,
1418 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
1419 ) -> Self {
1420 self.interactivity().capture_action(listener);
1421 self
1422 }
1423
1424 #[track_caller]
1451 #[inline(always)]
1452 fn on_action<A: Action>(
1453 mut self,
1454 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
1455 ) -> Self {
1456 self.interactivity().on_action(listener);
1457 self
1458 }
1459
1460 fn on_boxed_action(
1465 mut self,
1466 action: &dyn Action,
1467 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
1468 ) -> Self {
1469 self.interactivity().on_boxed_action(action, listener);
1470 self
1471 }
1472
1473 fn on_key_down(
1478 mut self,
1479 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1480 ) -> Self {
1481 self.interactivity().on_key_down(listener);
1482 self
1483 }
1484
1485 fn capture_key_down(
1490 mut self,
1491 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1492 ) -> Self {
1493 self.interactivity().capture_key_down(listener);
1494 self
1495 }
1496
1497 fn on_key_up(
1501 mut self,
1502 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1503 ) -> Self {
1504 self.interactivity().on_key_up(listener);
1505 self
1506 }
1507
1508 fn capture_key_up(
1512 mut self,
1513 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1514 ) -> Self {
1515 self.interactivity().capture_key_up(listener);
1516 self
1517 }
1518
1519 #[inline(always)]
1523 fn on_modifiers_changed(
1524 mut self,
1525 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
1526 ) -> Self {
1527 self.interactivity().on_modifiers_changed(listener);
1528 self
1529 }
1530
1531 fn drag_over<S: 'static>(
1535 mut self,
1536 f: impl 'static + Fn(StyleRefinement, &S, &mut Window, &mut App) -> StyleRefinement,
1537 ) -> Self {
1538 self.on_drag_over(f)
1539 }
1540
1541 fn on_drag_over<S: 'static>(
1560 mut self,
1561 f: impl 'static + Fn(StyleRefinement, &S, &mut Window, &mut App) -> StyleRefinement,
1562 ) -> Self {
1563 match self
1564 .interactivity()
1565 .get_mut_drop_listener(TypeId::of::<S>())
1566 {
1567 Some((_, drop_listener)) => {
1568 drop_listener.on_drag_over = Some(Box::new(move |value_any, window, cx| {
1569 let value = value_any.downcast_ref::<S>().unwrap();
1570 f(StyleRefinement::default(), value, window, cx)
1571 }))
1572 }
1573 None => {
1574 self.interactivity().on_drop_alt(DropListener::<S> {
1575 can_drop: None,
1576 on_drag_over: Some(Box::new(move |value, window, cx| {
1577 f(StyleRefinement::default(), value, window, cx)
1578 })),
1579 on_drop: None,
1580 });
1581 }
1582 }
1583 self
1584 }
1585
1586 fn on_drag_over_any(
1591 mut self,
1592 f: impl 'static + Fn(StyleRefinement, &dyn Any, &mut Window, &mut App) -> StyleRefinement,
1593 ) -> Self {
1594 self.interactivity().on_drag_over_any(f);
1595 self
1596 }
1597
1598 fn group_drag_over<S: 'static>(
1603 mut self,
1604 group_name: impl Into<SharedString>,
1605 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1606 ) -> Self {
1607 self.interactivity().group_drag_over_styles.push((
1608 TypeId::of::<S>(),
1609 GroupStyle {
1610 group: group_name.into(),
1611 style: Box::new(f(StyleRefinement::default())),
1612 },
1613 ));
1614 self
1615 }
1616
1617 fn on_drop<T: 'static>(
1622 mut self,
1623 listener: impl Fn(&T, &mut Window, &mut App) + 'static,
1624 ) -> Self {
1625 self.interactivity().on_drop(listener);
1626 self
1627 }
1628
1629 fn on_drop_alt<T: 'static>(mut self, listener: DropListener<T>) -> Self {
1634 self.interactivity().on_drop_alt(listener);
1635 self
1636 }
1637
1638 fn can_drop(
1643 mut self,
1644 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
1645 ) -> Self {
1646 self.interactivity().can_drop(predicate);
1647 self
1648 }
1649
1650 fn occlude(mut self) -> Self {
1656 self.interactivity().occlude_mouse();
1657 self
1658 }
1659
1660 fn window_control_area(mut self, area: WindowControlArea) -> Self {
1665 self.interactivity().window_control_area(area);
1666 self
1667 }
1668
1669 fn block_mouse_except_scroll(mut self) -> Self {
1674 self.interactivity().block_mouse_except_scroll();
1675 self
1676 }
1677
1678 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1684 where
1685 Self: Sized,
1686 {
1687 self.interactivity().focus_style = Some(Box::new(f(StyleRefinement::default())));
1688 self
1689 }
1690
1691 fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1697 where
1698 Self: Sized,
1699 {
1700 self.interactivity().in_focus_style = Some(Box::new(f(StyleRefinement::default())));
1701 self
1702 }
1703
1704 fn focus_visible(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1717 where
1718 Self: Sized,
1719 {
1720 self.interactivity().focus_visible_style = Some(Box::new(f(StyleRefinement::default())));
1721 self
1722 }
1723
1724 fn cursor_with(
1727 mut self,
1728 resolver: impl Fn(Point<Pixels>, Bounds<Pixels>) -> Option<CursorStyle> + 'static,
1729 ) -> Self
1730 where
1731 Self: Sized,
1732 {
1733 self.interactivity().cursor_with(resolver);
1734 self
1735 }
1736}
1737
1738pub trait StatefulInteractiveElement: InteractiveElement {
1746 fn role(mut self, role: accesskit::Role) -> Self {
1756 debug_assert!(
1757 role != accesskit::Role::GenericContainer,
1758 "GenericContainer is filtered out of the a11y tree and has no effect"
1759 );
1760 self.interactivity().override_role = Some(role);
1761 self
1762 }
1763
1764 fn accessibility_id(mut self, id: impl Into<SharedString>) -> Self {
1770 self.interactivity().aria.author_id = Some(id.into());
1771 self
1772 }
1773
1774 fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
1779 self.interactivity().aria.label = Some(label.into());
1780 self
1781 }
1782
1783 fn aria_description(mut self, description: impl Into<SharedString>) -> Self {
1788 self.interactivity().aria.description = Some(description.into());
1789 self
1790 }
1791
1792 fn aria_keyshortcuts(mut self, keyshortcuts: impl Into<SharedString>) -> Self {
1797 self.interactivity().aria.keyshortcuts = Some(keyshortcuts.into());
1798 self
1799 }
1800
1801 fn aria_hidden(mut self) -> Self {
1806 self.interactivity().aria.hidden = true;
1807 self
1808 }
1809
1810 fn aria_active_descendant(mut self) -> Self {
1817 self.interactivity().report_active_descendant_focus = true;
1818 self
1819 }
1820
1821 fn a11y_synthetic_children(
1827 mut self,
1828 f: impl FnOnce(&mut crate::A11ySubtreeBuilder) + 'static,
1829 ) -> Self {
1830 self.interactivity().a11y_synthetic_children = Some(Box::new(f));
1831 self
1832 }
1833
1834 fn aria_selected(mut self, selected: bool) -> Self {
1836 self.interactivity().aria.selected = Some(selected);
1837 self
1838 }
1839
1840 fn aria_expanded(mut self, expanded: bool) -> Self {
1842 self.interactivity().aria.expanded = Some(expanded);
1843 self
1844 }
1845
1846 fn aria_toggled(mut self, toggled: accesskit::Toggled) -> Self {
1848 self.interactivity().aria.toggled = Some(toggled);
1849 self
1850 }
1851
1852 fn aria_disabled(mut self, disabled: bool) -> Self {
1857 self.interactivity().aria.disabled = Some(disabled);
1858 self
1859 }
1860
1861 fn aria_numeric_value(mut self, value: f64) -> Self {
1863 self.interactivity().aria.numeric_value = Some(value);
1864 self
1865 }
1866
1867 fn aria_numeric_value_step(mut self, step: f64) -> Self {
1869 self.interactivity().aria.numeric_value_step = Some(step);
1870 self
1871 }
1872
1873 fn aria_value(mut self, value: impl Into<SharedString>) -> Self {
1875 self.interactivity().aria.value = Some(value.into());
1876 self
1877 }
1878
1879 fn aria_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
1881 self.interactivity().aria.placeholder = Some(placeholder.into());
1882 self
1883 }
1884
1885 fn aria_min_numeric_value(mut self, value: f64) -> Self {
1887 self.interactivity().aria.min_numeric_value = Some(value);
1888 self
1889 }
1890
1891 fn aria_max_numeric_value(mut self, value: f64) -> Self {
1893 self.interactivity().aria.max_numeric_value = Some(value);
1894 self
1895 }
1896
1897 fn aria_orientation(mut self, orientation: accesskit::Orientation) -> Self {
1899 self.interactivity().aria.orientation = Some(orientation);
1900 self
1901 }
1902
1903 fn aria_level(mut self, level: usize) -> Self {
1905 self.interactivity().aria.level = Some(level);
1906 self
1907 }
1908
1909 fn aria_position_in_set(mut self, position: usize) -> Self {
1913 self.interactivity().aria.position_in_set = Some(position);
1914 self
1915 }
1916
1917 fn aria_size_of_set(mut self, size: usize) -> Self {
1919 self.interactivity().aria.size_of_set = Some(size);
1920 self
1921 }
1922
1923 fn aria_row_index(mut self, index: usize) -> Self {
1925 self.interactivity().aria.row_index = Some(index);
1926 self
1927 }
1928
1929 fn aria_column_index(mut self, index: usize) -> Self {
1931 self.interactivity().aria.column_index = Some(index);
1932 self
1933 }
1934
1935 fn aria_row_count(mut self, count: usize) -> Self {
1937 self.interactivity().aria.row_count = Some(count);
1938 self
1939 }
1940
1941 fn aria_column_count(mut self, count: usize) -> Self {
1943 self.interactivity().aria.column_count = Some(count);
1944 self
1945 }
1946
1947 fn aria_modal(mut self, modal: bool) -> Self {
1951 self.interactivity().aria.modal = Some(modal);
1952 self
1953 }
1954
1955 fn aria_live(mut self, live: accesskit::Live) -> Self {
1959 self.interactivity().aria.live = Some(live);
1960 self
1961 }
1962
1963 fn aria_live_atomic(mut self, atomic: bool) -> Self {
1967 self.interactivity().aria.live_atomic = Some(atomic);
1968 self
1969 }
1970
1971 #[inline(always)]
1977 fn on_a11y_action(
1978 mut self,
1979 action: accesskit::Action,
1980 listener: impl FnMut(Option<&accesskit::ActionData>, &mut crate::Window, &mut crate::App)
1981 + 'static,
1982 ) -> Self {
1983 self.interactivity()
1984 .a11y_action_listeners
1985 .push((action, Box::new(listener)));
1986 self
1987 }
1988
1989 fn focusable(mut self) -> Self {
1994 self.interactivity().focusable = true;
1995 self
1996 }
1997
1998 fn overflow_scroll(mut self) -> Self {
2003 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
2004 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
2005 self
2006 }
2007
2008 fn overflow_x_scroll(mut self) -> Self {
2012 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
2013 self
2014 }
2015
2016 fn overflow_y_scroll(mut self) -> Self {
2030 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
2031 self
2032 }
2033
2034 fn restrict_scroll_to_axis(mut self) -> Self {
2038 self.interactivity().base_style.restrict_scroll_to_axis = Some(true);
2039 self
2040 }
2041
2042 fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
2047 self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone());
2048 self
2049 }
2050
2051 fn anchor_scroll(mut self, scroll_anchor: Option<ScrollAnchor>) -> Self {
2055 self.interactivity().scroll_anchor = scroll_anchor;
2056 self
2057 }
2058
2059 fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
2064 where
2065 Self: Sized,
2066 {
2067 self.interactivity().active_style = Some(Box::new(f(StyleRefinement::default())));
2068 self
2069 }
2070
2071 fn group_active(
2075 mut self,
2076 group_name: impl Into<SharedString>,
2077 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
2078 ) -> Self
2079 where
2080 Self: Sized,
2081 {
2082 self.interactivity().group_active_style = Some(GroupStyle {
2083 group: group_name.into(),
2084 style: Box::new(f(StyleRefinement::default())),
2085 });
2086 self
2087 }
2088
2089 #[inline(always)]
2111 fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self
2112 where
2113 Self: Sized,
2114 {
2115 self.interactivity().on_click(listener);
2116 self
2117 }
2118
2119 fn on_aux_click(
2123 mut self,
2124 listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
2125 ) -> Self
2126 where
2127 Self: Sized,
2128 {
2129 self.interactivity().on_aux_click(listener);
2130 self
2131 }
2132
2133 fn on_drag<T, W>(
2166 mut self,
2167 value: T,
2168 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
2169 ) -> Self
2170 where
2171 Self: Sized,
2172 T: 'static,
2173 W: 'static + Render,
2174 {
2175 self.interactivity().on_drag(value, constructor);
2176 self
2177 }
2178
2179 fn on_drag_alt<F>(mut self, listener: F) -> Self
2185 where
2186 Self: Sized,
2187 F: 'static + Fn(Point<Pixels>, Option<CursorStyle>, &mut Window, &mut App) -> AnyDrag,
2188 {
2189 self.interactivity().on_drag_alt(listener);
2190 self
2191 }
2192
2193 fn external_drag_payload<T>(
2203 mut self,
2204 resolver: impl Fn(&T, &mut Window, &mut App) -> Option<ExternalDragPayload> + 'static,
2205 ) -> Self
2206 where
2207 Self: Sized,
2208 T: 'static,
2209 {
2210 self.interactivity().external_drag_payload(resolver);
2211 self
2212 }
2213
2214 fn on_hover(mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self
2222 where
2223 Self: Sized,
2224 {
2225 self.interactivity().on_hover(listener);
2226 self
2227 }
2228
2229 fn hover_listener_mode(mut self, mode: HoverListenerMode) -> Self
2233 where
2234 Self: Sized,
2235 {
2236 self.interactivity().hover_listener_mode(mode);
2237 self
2238 }
2239
2240 fn on_hover_region<R: PartialEq + 'static>(
2242 mut self,
2243 project: impl Fn(Point<Pixels>, Bounds<Pixels>) -> Option<R> + 'static,
2244 listener: impl Fn(&Option<R>, &mut Window, &mut App) + 'static,
2245 ) -> Self
2246 where
2247 Self: Sized,
2248 {
2249 self.interactivity().on_hover_region(project, listener);
2250 self
2251 }
2252
2253 fn tooltip(mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self
2279 where
2280 Self: Sized,
2281 {
2282 self.interactivity().tooltip(build_tooltip);
2283 self
2284 }
2285
2286 fn hoverable_tooltip(
2292 mut self,
2293 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
2294 ) -> Self
2295 where
2296 Self: Sized,
2297 {
2298 self.interactivity().hoverable_tooltip(build_tooltip);
2299 self
2300 }
2301
2302 fn tooltip_show_delay(mut self, delay: Duration) -> Self
2306 where
2307 Self: Sized,
2308 {
2309 self.interactivity().tooltip_show_delay(delay);
2310 self
2311 }
2312
2313 fn on_prepaint(
2317 mut self,
2318 listener: impl Fn(&InteractivityPrepaint, &mut Window, &mut App) + 'static,
2319 ) -> Self
2320 where
2321 Self: Sized,
2322 {
2323 self.interactivity().on_prepaint(listener);
2324 self
2325 }
2326}
2327
2328#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
2330pub struct InteractivityPrepaint {
2331 pub bounds: Bounds<Pixels>,
2333 pub content_size: Size<Pixels>,
2335}
2336pub(crate) type PrepaintListener =
2337 Box<dyn Fn(&InteractivityPrepaint, &mut Window, &mut App) + 'static>;
2338
2339pub(crate) type MouseDownListener =
2340 Box<dyn Fn(&MouseDownEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
2341pub(crate) type MouseUpListener =
2342 Box<dyn Fn(&MouseUpEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
2343pub(crate) type MousePressureListener =
2344 Box<dyn Fn(&MousePressureEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
2345pub(crate) type MouseMoveListener =
2346 Box<dyn Fn(&MouseMoveEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
2347pub(crate) type MouseExitListener =
2348 Box<dyn Fn(&MouseExitEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
2349
2350pub(crate) type FileDropExitListener =
2351 Box<dyn Fn(&FileDropEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
2352
2353pub(crate) type ScrollWheelListener =
2354 Box<dyn Fn(&ScrollWheelEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
2355
2356pub(crate) type PinchListener =
2357 Box<dyn Fn(&PinchEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
2358
2359pub(crate) type ClickListener = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>;
2360type PointerProjector<R> = Rc<dyn Fn(Point<Pixels>, Bounds<Pixels>) -> Option<R>>;
2361pub(crate) type CursorStyleResolver = PointerProjector<CursorStyle>;
2362type HoverRegionCallback<R> = Rc<dyn Fn(&Option<R>, &mut Window, &mut App)>;
2363type HoverRegionObserver = Rc<dyn Fn(Option<Point<Pixels>>, Bounds<Pixels>, &mut Window, &mut App)>;
2364
2365#[derive(Default)]
2367pub(crate) struct HoverRegionState(Option<Rc<dyn Any>>);
2368
2369pub(crate) trait HoverRegionListener {
2370 fn bind(&self, state: &mut HoverRegionState) -> HoverRegionObserver;
2371}
2372
2373struct TypedHoverRegionListener<R> {
2374 project: PointerProjector<R>,
2375 callback: HoverRegionCallback<R>,
2376}
2377
2378impl<R: PartialEq + 'static> HoverRegionListener for TypedHoverRegionListener<R> {
2379 fn bind(&self, state: &mut HoverRegionState) -> HoverRegionObserver {
2380 let value = state
2382 .0
2383 .as_ref()
2384 .and_then(|state| state.clone().downcast::<RefCell<Rc<Option<R>>>>().ok())
2385 .unwrap_or_else(|| Rc::new(RefCell::new(Rc::new(None))));
2386 state.0 = Some(value.clone());
2387 let project = self.project.clone();
2388 let callback = self.callback.clone();
2389 Rc::new(move |position, bounds, window, cx| {
2390 let next = position.and_then(|position| project(position, bounds));
2391 let mut previous = value.borrow_mut();
2392 if **previous == next {
2393 return;
2394 }
2395 let next = Rc::new(next);
2396 *previous = next.clone();
2397 drop(previous);
2400 callback(&next, window, cx);
2401 })
2402 }
2403}
2404
2405pub(crate) type DragListener =
2406 Box<dyn Fn(Point<Pixels>, Option<CursorStyle>, &mut Window, &mut App) -> AnyDrag + 'static>;
2407
2408pub type CanDropListenerFn<T> = Box<dyn Fn(&T, &Hitbox, &mut Window, &mut App) -> bool + 'static>;
2410pub type OnDragOverFn<T> = Box<dyn Fn(&T, &mut Window, &mut App) -> StyleRefinement + 'static>;
2412pub type OnDropFn<T> = Box<dyn Fn(&T, &MouseUpEvent, &mut Window, &mut App) + 'static>;
2414
2415pub struct DropListener<T: ?Sized> {
2421 pub can_drop: Option<CanDropListenerFn<T>>,
2423 pub on_drag_over: Option<OnDragOverFn<T>>,
2425 pub on_drop: Option<OnDropFn<T>>,
2427}
2428
2429impl<T> Default for DropListener<T> {
2430 fn default() -> Self {
2431 Self {
2432 can_drop: None,
2433 on_drag_over: None,
2434 on_drop: None,
2435 }
2436 }
2437}
2438
2439impl<T: Sized + 'static> DropListener<T> {
2440 pub fn can_drop_within(
2445 mut self,
2446 predicate: impl Fn(&T, &mut Window, &mut App) -> bool + 'static,
2447 ) -> Self {
2448 self.can_drop_anywhere(move |value, hitbox, window, cx| {
2449 hitbox.is_hovered(window) && predicate(value, window, cx)
2450 })
2451 }
2452
2453 pub fn can_drop_anywhere(
2458 mut self,
2459 predicate: impl Fn(&T, &Hitbox, &mut Window, &mut App) -> bool + 'static,
2460 ) -> Self {
2461 self.can_drop = Some(Box::new(predicate));
2462 self
2463 }
2464
2465 pub fn on_drag_over(
2469 mut self,
2470 predicate: impl Fn(StyleRefinement, &T, &mut Window, &mut App) -> StyleRefinement + 'static,
2471 ) -> Self {
2472 self.on_drag_over = Some(Box::new(move |value, window, cx| {
2473 predicate(StyleRefinement::default(), value, window, cx)
2474 }));
2475 self
2476 }
2477
2478 pub fn on_drop(
2483 mut self,
2484 predicate: impl Fn(&T, &MouseUpEvent, &mut Window, &mut App) + 'static,
2485 ) -> Self {
2486 self.on_drop = Some(Box::new(predicate));
2487 self
2488 }
2489
2490 pub fn into_any(self) -> DropListener<dyn Any> {
2496 let DropListener {
2497 can_drop,
2498 on_drag_over,
2499 on_drop,
2500 } = self;
2501 let can_drop = can_drop.map(|predicate| {
2502 Box::new(
2503 move |any: &dyn Any, hitbox: &Hitbox, window: &mut Window, cx: &mut App| {
2504 let Some(value_ref) = any.downcast_ref::<T>() else {
2505 return false;
2506 };
2507 (&predicate)(value_ref, hitbox, window, cx)
2508 },
2509 ) as CanDropListenerFn<dyn Any>
2510 });
2511 let on_drag_over = on_drag_over.map(|predicate| {
2512 Box::new(move |any: &dyn Any, window: &mut Window, cx: &mut App| {
2513 let value_ref = any
2514 .downcast_ref::<T>()
2515 .expect("drop listener type was incorrect");
2516 (&predicate)(value_ref, window, cx)
2517 }) as OnDragOverFn<dyn Any>
2518 });
2519 let on_drop = on_drop.map(|predicate| {
2520 Box::new(
2521 move |any: &dyn Any, evt: &MouseUpEvent, window: &mut Window, cx: &mut App| {
2522 let value_ref = any
2523 .downcast_ref::<T>()
2524 .expect("drop listener type was incorrect");
2525 (&predicate)(value_ref, evt, window, cx)
2526 },
2527 ) as OnDropFn<dyn Any>
2528 });
2529 DropListener::<dyn Any> {
2530 can_drop,
2531 on_drag_over,
2532 on_drop,
2533 }
2534 }
2535}
2536
2537#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
2540pub enum HoverListenerMode {
2541 #[default]
2544 InputModalityAware,
2545 InputModalityIndependent,
2549}
2550
2551impl HoverListenerMode {
2552 fn is_hovered(self, hitbox: &Hitbox, window: &Window) -> bool {
2553 match self {
2554 Self::InputModalityAware => hitbox.is_hovered(window),
2555 Self::InputModalityIndependent => hitbox.id.is_hovered_ignoring_last_input(window),
2556 }
2557 }
2558}
2559
2560type CanDropPredicate = Box<dyn Fn(&dyn Any, &Hitbox, &mut Window, &mut App) -> bool + 'static>;
2561
2562pub(crate) struct TooltipBuilder {
2563 build: Rc<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>,
2564 hoverable: bool,
2565}
2566
2567pub(crate) type KeyDownListener =
2568 Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
2569
2570pub(crate) type KeyUpListener =
2571 Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
2572
2573pub(crate) type ModifiersChangedListener =
2574 Box<dyn Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static>;
2575
2576pub(crate) type ActionListener =
2577 Box<dyn Fn(&dyn Any, DispatchPhase, &mut Window, &mut App) + 'static>;
2578
2579#[track_caller]
2598pub fn div() -> Div {
2599 Div {
2600 interactivity: Interactivity::new(),
2601 children: SmallVec::default(),
2602 prepaint_listener: None,
2603 image_cache: None,
2604 prepaint_order_fn: None,
2605 }
2606}
2607
2608pub struct Div {
2614 interactivity: Interactivity,
2615 children: SmallVec<[StackSafe<AnyElement>; 2]>,
2616 prepaint_listener: Option<Box<dyn Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static>>,
2617 image_cache: Option<Box<dyn ImageCacheProvider>>,
2618 prepaint_order_fn: Option<Box<dyn Fn(&mut Window, &mut App) -> SmallVec<[usize; 8]>>>,
2619}
2620
2621impl Div {
2622 pub fn on_children_prepainted(
2627 mut self,
2628 listener: impl Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static,
2629 ) -> Self {
2630 self.prepaint_listener = Some(Box::new(listener));
2631 self
2632 }
2633
2634 pub fn image_cache(mut self, cache: impl ImageCacheProvider) -> Self {
2638 self.image_cache = Some(Box::new(cache));
2639 self
2640 }
2641
2642 pub fn with_dynamic_prepaint_order(
2648 mut self,
2649 order_fn: impl Fn(&mut Window, &mut App) -> SmallVec<[usize; 8]> + 'static,
2650 ) -> Self {
2651 self.prepaint_order_fn = Some(Box::new(order_fn));
2652 self
2653 }
2654}
2655
2656pub struct DivFrameState {
2661 child_layout_ids: SmallVec<[LayoutId; 2]>,
2662 inline: Option<InlineDivFrameState>,
2663 contents_in_parent_paragraph: bool,
2664}
2665
2666impl DivFrameState {
2667 fn standalone_inline(&self) -> Option<&InlineDivFrameState> {
2668 if self.contents_in_parent_paragraph {
2669 None
2670 } else {
2671 self.inline.as_ref()
2672 }
2673 }
2674
2675 fn standalone_inline_mut(&mut self) -> Option<&mut InlineDivFrameState> {
2676 if self.contents_in_parent_paragraph {
2677 None
2678 } else {
2679 self.inline.as_mut()
2680 }
2681 }
2682
2683 #[cfg(any(test, feature = "test-support"))]
2685 pub fn measured_inline_paragraphs(&self) -> Vec<(SharedString, Arc<crate::InlineLayout>)> {
2686 self.inline
2687 .as_ref()
2688 .map_or_else(Vec::new, |inline| inline.measured_paragraphs())
2689 }
2690}
2691
2692#[derive(Clone)]
2694pub struct DivInspectorState {
2695 #[cfg(any(feature = "inspector", debug_assertions))]
2697 pub base_style: Box<StyleRefinement>,
2698 pub bounds: Bounds<Pixels>,
2700 pub content_size: Size<Pixels>,
2702}
2703
2704impl Styled for Div {
2705 fn style(&mut self) -> &mut StyleRefinement {
2706 &mut self.interactivity.base_style
2707 }
2708}
2709
2710impl InteractiveElement for Div {
2711 fn interactivity(&mut self) -> &mut Interactivity {
2712 &mut self.interactivity
2713 }
2714}
2715
2716impl ParentElement for Div {
2717 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
2718 #[cfg(feature = "stacker")]
2719 self.children
2720 .extend(elements.into_iter().map(StackSafe::new));
2721 #[cfg(not(feature = "stacker"))]
2722 self.children.extend(elements);
2723 }
2724}
2725
2726impl Element for Div {
2727 type RequestLayoutState = DivFrameState;
2728 type PrepaintState = Option<Hitbox>;
2729
2730 fn id(&self) -> Option<ElementId> {
2731 self.interactivity.element_id.clone()
2732 }
2733
2734 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
2735 self.interactivity.source_location()
2736 }
2737
2738 fn a11y_role(&self) -> Option<accesskit::Role> {
2739 self.interactivity
2742 .override_role
2743 .filter(|role| *role != accesskit::Role::GenericContainer)
2744 }
2745
2746 fn is_a11y_hidden(&self) -> bool {
2747 self.interactivity.aria.hidden
2748 }
2749
2750 fn write_a11y_info(&self, node: &mut accesskit::Node) {
2751 self.interactivity.write_a11y_info(node);
2752 }
2753
2754 fn a11y_synthetic_children(
2755 &mut self,
2756 _prepaint: &mut Self::PrepaintState,
2757 builder: &mut crate::A11ySubtreeBuilder,
2758 ) {
2759 if let Some(f) = self.interactivity.a11y_synthetic_children.take() {
2760 f(builder);
2761 }
2762 }
2763
2764 #[cfg_attr(feature = "stacker", stacksafe::stacksafe)]
2765 fn request_layout(
2766 &mut self,
2767 global_id: Option<&GlobalElementId>,
2768 inspector_id: Option<&InspectorElementId>,
2769 window: &mut Window,
2770 cx: &mut App,
2771 ) -> (LayoutId, Self::RequestLayoutState) {
2772 let mut child_layout_ids = SmallVec::new();
2773 let mut inline = None;
2774 let image_cache = self
2775 .image_cache
2776 .as_mut()
2777 .map(|provider| provider.provide(window, cx));
2778
2779 let layout_id = window.with_image_cache(image_cache, |window| {
2780 self.interactivity.request_layout(
2781 global_id,
2782 inspector_id,
2783 window,
2784 cx,
2785 |style, window, cx| {
2786 window.with_text_style(style.text_style().cloned(), |window| {
2787 let display = style.display;
2788 let collected_by_ancestor =
2789 window.collecting_inline && display == Display::Inline;
2790 let previous_collecting_inline = std::mem::replace(
2791 &mut window.collecting_inline,
2792 matches!(display, Display::Block | Display::Inline),
2793 );
2794 child_layout_ids = self
2795 .children
2796 .iter_mut()
2797 .map(|child| child.request_layout(window, cx))
2798 .collect();
2799 window.collecting_inline = previous_collecting_inline;
2800
2801 let layout_id = if matches!(display, Display::Block | Display::Inline)
2802 && !collected_by_ancestor
2803 {
2804 let (node_id, state) = InlineDivFrameState::request_layout(
2805 &style,
2806 &child_layout_ids,
2807 window,
2808 cx,
2809 );
2810
2811 inline = Some(state);
2812 node_id
2813 } else if collected_by_ancestor {
2814 window.request_layout(style, std::iter::empty(), cx)
2815 } else {
2816 window.request_layout(style, child_layout_ids.iter().copied(), cx)
2817 };
2818
2819 if display == Display::Inline {
2820 window.publish_inline_content(
2821 layout_id,
2822 InlineContent::Container {
2823 children: child_layout_ids.clone(),
2824 },
2825 );
2826 }
2827
2828 window.set_layout_logical_children(layout_id, &child_layout_ids);
2829
2830 layout_id
2831 })
2832 },
2833 )
2834 });
2835
2836 (
2837 layout_id,
2838 DivFrameState {
2839 child_layout_ids,
2840 inline,
2841 contents_in_parent_paragraph: false,
2842 },
2843 )
2844 }
2845
2846 #[cfg_attr(feature = "stacker", stacksafe::stacksafe)]
2847 fn prepaint(
2848 &mut self,
2849 global_id: Option<&GlobalElementId>,
2850 inspector_id: Option<&InspectorElementId>,
2851 bounds: Bounds<Pixels>,
2852 request_layout: &mut Self::RequestLayoutState,
2853 window: &mut Window,
2854 cx: &mut App,
2855 ) -> Option<Hitbox> {
2856 let image_cache = self
2857 .image_cache
2858 .as_mut()
2859 .map(|provider| provider.provide(window, cx));
2860
2861 let has_prepaint_listener = self.prepaint_listener.is_some();
2862 let mut children_bounds = Vec::with_capacity(if has_prepaint_listener {
2863 request_layout.child_layout_ids.len()
2864 } else {
2865 0
2866 });
2867
2868 let mut child_min = point(Pixels::MAX, Pixels::MAX);
2869 let mut child_max = Point::default();
2870 if let Some(handle) = self.interactivity.scroll_anchor.as_ref() {
2871 *handle.last_origin.borrow_mut() = bounds.origin - window.element_offset();
2872 }
2873
2874 request_layout.contents_in_parent_paragraph = window.current_inline_fragments.is_some();
2875
2876 let content_size = if let Some(inline) = request_layout.standalone_inline() {
2877 inline.prepare_layout(
2878 bounds,
2879 self.interactivity.tracked_scroll_handle.as_ref(),
2880 &request_layout.child_layout_ids,
2881 window,
2882 )
2883 } else if request_layout.child_layout_ids.is_empty() {
2884 bounds.size
2885 } else if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
2886 let mut state = scroll_handle.0.borrow_mut();
2887 state.child_bounds = Vec::with_capacity(request_layout.child_layout_ids.len());
2888 for child_layout_id in &request_layout.child_layout_ids {
2889 let child_bounds = window.layout_bounds(*child_layout_id);
2890 child_min = child_min.min(&child_bounds.origin);
2891 child_max = child_max.max(&child_bounds.bottom_right());
2892 state.child_bounds.push(child_bounds);
2893 if has_prepaint_listener {
2894 children_bounds.push(child_bounds);
2895 }
2896 }
2897 (child_max - child_min).into()
2898 } else {
2899 for child_layout_id in &request_layout.child_layout_ids {
2900 let child_bounds = window.layout_bounds(*child_layout_id);
2901 child_min = child_min.min(&child_bounds.origin);
2902 child_max = child_max.max(&child_bounds.bottom_right());
2903
2904 if has_prepaint_listener {
2905 children_bounds.push(child_bounds);
2906 }
2907 }
2908 (child_max - child_min).into()
2909 };
2910
2911 self.interactivity.prepaint(
2912 global_id,
2913 inspector_id,
2914 bounds,
2915 content_size,
2916 window,
2917 cx,
2918 |style, scroll_offset, hitbox, window, cx| {
2919 if style.display == Display::None {
2921 return hitbox;
2922 }
2923
2924 #[inline]
2925 fn prepaint_children(
2926 children: &mut [StackSafe<AnyElement>],
2927 order: Option<&[usize]>,
2928 window: &mut Window,
2929 cx: &mut App,
2930 ) {
2931 let order = match order {
2932 Some(order) => Either::Left(order.iter().copied()),
2933 None => Either::Right(0..children.len()),
2934 };
2935
2936 for index in order {
2937 if let Some(child) = children.get_mut(index) {
2938 child.prepaint(window, cx);
2939 }
2940 }
2941 }
2942
2943 window.with_image_cache(image_cache, |window| {
2944 window.with_style_transition_containing_bounds(bounds, |window| {
2945 let has_standalone_inline = request_layout.standalone_inline().is_some();
2946 let inline_order = if has_standalone_inline {
2947 self.prepaint_order_fn
2948 .as_ref()
2949 .map(|order_fn| order_fn(window, cx))
2950 } else {
2951 None
2952 };
2953
2954 window.with_element_offset(scroll_offset, |window| {
2955 if let Some(inline) = request_layout.standalone_inline_mut() {
2956 inline.record_paragraph_origins(window);
2957 prepaint_children(
2958 &mut self.children,
2959 inline_order.as_deref(),
2960 window,
2961 cx,
2962 );
2963 } else {
2964 let order = self
2965 .prepaint_order_fn
2966 .as_ref()
2967 .map(|order_fn| order_fn(window, cx));
2968 prepaint_children(&mut self.children, order.as_deref(), window, cx);
2969 }
2970 });
2971
2972 if has_prepaint_listener && has_standalone_inline {
2973 children_bounds.extend(
2974 request_layout
2975 .child_layout_ids
2976 .iter()
2977 .map(|layout_id| window.layout_bounds(*layout_id)),
2978 );
2979 }
2980
2981 if let Some(listener) = self.prepaint_listener.as_ref() {
2982 listener(children_bounds, window, cx);
2983 }
2984 });
2985 });
2986
2987 hitbox
2988 },
2989 )
2990 }
2991
2992 #[cfg_attr(feature = "stacker", stacksafe::stacksafe)]
2993 fn paint(
2994 &mut self,
2995 global_id: Option<&GlobalElementId>,
2996 inspector_id: Option<&InspectorElementId>,
2997 bounds: Bounds<Pixels>,
2998 request_layout: &mut Self::RequestLayoutState,
2999 hitbox: &mut Option<Hitbox>,
3000 window: &mut Window,
3001 cx: &mut App,
3002 ) {
3003 let image_cache = self
3004 .image_cache
3005 .as_mut()
3006 .map(|provider| provider.provide(window, cx));
3007
3008 window.with_image_cache(image_cache, |window| {
3009 self.interactivity.paint(
3010 global_id,
3011 inspector_id,
3012 bounds,
3013 hitbox.as_ref(),
3014 window,
3015 cx,
3016 |style, window, cx| {
3017 if style.display == Display::None {
3019 return;
3020 }
3021
3022 for child in &mut self.children {
3023 child.paint(window, cx);
3024 }
3025
3026 if let Some(inline) = request_layout.standalone_inline() {
3027 inline.paint_paragraphs(window, cx);
3028 }
3029 },
3030 )
3031 });
3032 }
3033}
3034
3035impl IntoElement for Div {
3036 type Element = Self;
3037
3038 fn into_element(self) -> Self::Element {
3039 self
3040 }
3041}
3042
3043#[derive(Default)]
3044pub(crate) struct AriaProperties {
3045 pub(crate) author_id: Option<SharedString>,
3046 pub(crate) label: Option<SharedString>,
3047 pub(crate) description: Option<SharedString>,
3048 pub(crate) keyshortcuts: Option<SharedString>,
3049 pub(crate) hidden: bool,
3050 pub(crate) selected: Option<bool>,
3051 pub(crate) expanded: Option<bool>,
3052 pub(crate) toggled: Option<accesskit::Toggled>,
3053 pub(crate) disabled: Option<bool>,
3054 pub(crate) numeric_value: Option<f64>,
3055 pub(crate) min_numeric_value: Option<f64>,
3056 pub(crate) max_numeric_value: Option<f64>,
3057 pub(crate) numeric_value_step: Option<f64>,
3058 pub(crate) value: Option<SharedString>,
3059 pub(crate) placeholder: Option<SharedString>,
3060 pub(crate) orientation: Option<accesskit::Orientation>,
3061 pub(crate) level: Option<usize>,
3062 pub(crate) position_in_set: Option<usize>,
3063 pub(crate) size_of_set: Option<usize>,
3064 pub(crate) row_index: Option<usize>,
3065 pub(crate) column_index: Option<usize>,
3066 pub(crate) row_count: Option<usize>,
3067 pub(crate) column_count: Option<usize>,
3068 pub(crate) modal: Option<bool>,
3069 pub(crate) live: Option<accesskit::Live>,
3070 pub(crate) live_atomic: Option<bool>,
3071}
3072
3073#[derive(Default)]
3080pub struct Interactivity {
3081 pub element_id: Option<ElementId>,
3083 pub active: Option<bool>,
3088 pub hovered: Option<bool>,
3092 pub(crate) tooltip_id: Option<TooltipId>,
3093 pub(crate) content_size: Size<Pixels>,
3094 pub(crate) key_context: Option<KeyContext>,
3095 pub(crate) focusable: bool,
3096 pub(crate) tracked_focus_handle: Option<FocusHandle>,
3097 pub(crate) tracked_scroll_handle: Option<ScrollHandle>,
3098 pub(crate) scroll_anchor: Option<ScrollAnchor>,
3099 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
3100 pub(crate) ongoing_scroll: Option<Rc<RefCell<OngoingScroll>>>,
3101 pub(crate) group: Option<SharedString>,
3102 pub base_style: Box<StyleRefinement>,
3105 pub(crate) style_transitions: Option<Box<StyleTransitions>>,
3106 pub(crate) focus_style: Option<Box<StyleRefinement>>,
3107 pub(crate) in_focus_style: Option<Box<StyleRefinement>>,
3108 pub(crate) focus_visible_style: Option<Box<StyleRefinement>>,
3109 pub(crate) hover_style: Option<Box<StyleRefinement>>,
3110 pub(crate) group_hover_style: Option<GroupStyle>,
3111 pub(crate) active_style: Option<Box<StyleRefinement>>,
3112 pub(crate) group_active_style: Option<GroupStyle>,
3113 pub(crate) drag_over_style:
3114 Option<Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> StyleRefinement>>,
3115 pub(crate) group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
3116 pub(crate) prepaint_listeners: Vec<PrepaintListener>,
3117 pub(crate) mouse_down_listeners: Vec<MouseDownListener>,
3118 pub(crate) mouse_up_listeners: Vec<MouseUpListener>,
3119 pub(crate) mouse_pressure_listeners: Vec<MousePressureListener>,
3120 pub(crate) mouse_move_listeners: Vec<MouseMoveListener>,
3121 pub(crate) mouse_exit_listeners: Vec<MouseExitListener>,
3122 pub(crate) file_drop_exit_listeners: Vec<FileDropExitListener>,
3123 pub(crate) scroll_wheel_listeners: Vec<ScrollWheelListener>,
3124 pub(crate) pinch_listeners: Vec<PinchListener>,
3125 pub(crate) key_down_listeners: Vec<KeyDownListener>,
3126 pub(crate) key_up_listeners: Vec<KeyUpListener>,
3127 pub(crate) modifiers_changed_listeners: Vec<ModifiersChangedListener>,
3128 pub(crate) action_listeners: Vec<(TypeId, ActionListener)>,
3129 pub(crate) drop_listeners: Vec<(TypeId, DropListener<dyn Any>)>,
3130 pub(crate) can_drop_predicate: Option<CanDropPredicate>,
3131 pub(crate) click_listeners: Vec<ClickListener>,
3132 pub(crate) aux_click_listeners: Vec<ClickListener>,
3133 pub(crate) drag_listener: Option<DragListener>,
3134 pub(crate) hover_listener_mode: HoverListenerMode,
3135 pub(crate) hover_region_listeners: Vec<Box<dyn HoverRegionListener>>,
3136 pub(crate) cursor_style_resolver: Option<CursorStyleResolver>,
3138 pub(crate) tooltip_builder: Option<TooltipBuilder>,
3139 pub(crate) tooltip_show_delay: Option<Duration>,
3140 pub(crate) window_control: Option<WindowControlArea>,
3141 pub(crate) hitbox_behavior: HitboxBehavior,
3142 pub(crate) tab_index: Option<isize>,
3143 pub(crate) tab_group: bool,
3144 pub(crate) tab_stop: bool,
3145
3146 pub(crate) a11y_action_listeners:
3147 Vec<(accesskit::Action, crate::window::a11y::A11yActionListener)>,
3148 pub(crate) a11y_synthetic_children: Option<Box<dyn FnOnce(&mut crate::A11ySubtreeBuilder)>>,
3149 pub(crate) report_active_descendant_focus: bool,
3150 pub(crate) override_role: Option<accesskit::Role>,
3151 pub(crate) aria: AriaProperties,
3152
3153 #[cfg(any(feature = "inspector", debug_assertions))]
3154 pub(crate) source_location: Option<&'static core::panic::Location<'static>>,
3155
3156 #[cfg(any(test, feature = "test-support"))]
3157 pub(crate) debug_selector: Option<String>,
3158}
3159
3160impl Interactivity {
3161 pub fn set_scroll_offset(
3166 &self,
3167 global_id: Option<&GlobalElementId>,
3168 window: &mut Window,
3169 point: Point<Pixels>,
3170 ) {
3171 window.with_optional_element_state::<gpui::InteractiveElementState, _>(
3172 global_id,
3173 |element_state, _window| {
3174 let mut element_state =
3175 element_state.map(|element_state| element_state.unwrap_or_default());
3176 let overflow = &self.base_style.overflow;
3177 if (overflow.x == Some(Overflow::Scroll) || overflow.y == Some(Overflow::Scroll))
3178 && let Some(element_state) = element_state.as_mut()
3179 {
3180 let scroll_offset = element_state.scroll_offset.get_or_insert_with(Rc::default);
3181 *scroll_offset.borrow_mut() = point;
3182 }
3183 ((), element_state)
3184 },
3185 );
3186 }
3187}
3188
3189impl Interactivity {
3190 pub fn request_layout(
3195 &mut self,
3196 global_id: Option<&GlobalElementId>,
3197 _inspector_id: Option<&InspectorElementId>,
3198 window: &mut Window,
3199 cx: &mut App,
3200 f: impl FnOnce(Style, &mut Window, &mut App) -> LayoutId,
3201 ) -> LayoutId {
3202 if let Some(scroll_handle) = &self.tracked_scroll_handle {
3203 cx.note_revision(&scroll_handle.revision());
3204 }
3205
3206 #[cfg(any(feature = "inspector", debug_assertions))]
3207 window.with_inspector_state(
3208 _inspector_id,
3209 cx,
3210 |inspector_state: &mut Option<DivInspectorState>, _window| {
3211 if let Some(inspector_state) = inspector_state {
3212 self.base_style = inspector_state.base_style.clone();
3213 } else {
3214 *inspector_state = Some(DivInspectorState {
3215 base_style: self.base_style.clone(),
3216 bounds: Default::default(),
3217 content_size: Default::default(),
3218 })
3219 }
3220 },
3221 );
3222
3223 window.with_optional_element_state::<InteractiveElementState, _>(
3224 global_id,
3225 |element_state, window| {
3226 let mut element_state =
3227 element_state.map(|element_state| element_state.unwrap_or_default());
3228
3229 if let Some(element_state) = element_state.as_ref()
3230 && cx.has_active_drag()
3231 {
3232 if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() {
3233 *pending_mouse_down.borrow_mut() = None;
3234 }
3235 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
3236 *clicked_state.borrow_mut() = ElementClickedState::default();
3237 }
3238 }
3239
3240 if self.focusable
3245 && self.tracked_focus_handle.is_none()
3246 && let Some(element_state) = element_state.as_mut()
3247 {
3248 let mut handle = element_state
3249 .focus_handle
3250 .get_or_insert_with(|| cx.focus_handle())
3251 .clone()
3252 .tab_stop(self.tab_stop);
3253
3254 if let Some(index) = self.tab_index {
3255 handle = handle.tab_index(index);
3256 }
3257
3258 self.tracked_focus_handle = Some(handle);
3259 }
3260
3261 if let Some(scroll_handle) = self.tracked_scroll_handle.as_ref() {
3262 let scroll_handle_state = scroll_handle.0.borrow();
3263 self.scroll_offset = Some(scroll_handle_state.offset.clone());
3264 self.ongoing_scroll = Some(scroll_handle_state.ongoing_scroll.clone());
3265 } else if (self.base_style.overflow.x == Some(Overflow::Scroll)
3266 || self.base_style.overflow.y == Some(Overflow::Scroll))
3267 && let Some(element_state) = element_state.as_mut()
3268 {
3269 self.scroll_offset = Some(
3270 element_state
3271 .scroll_offset
3272 .get_or_insert_with(Rc::default)
3273 .clone(),
3274 );
3275 self.ongoing_scroll = Some(
3276 element_state
3277 .ongoing_scroll
3278 .get_or_insert_with(|| Rc::new(RefCell::new(OngoingScroll::default())))
3279 .clone(),
3280 );
3281 }
3282
3283 let style =
3284 self.compute_style_internal(None, None, element_state.as_mut(), window, cx);
3285 let layout_id = f(style, window, cx);
3286 (layout_id, element_state)
3287 },
3288 )
3289 }
3290
3291 pub fn prepaint<R>(
3296 &mut self,
3297 global_id: Option<&GlobalElementId>,
3298 _inspector_id: Option<&InspectorElementId>,
3299 bounds: Bounds<Pixels>,
3300 content_size: Size<Pixels>,
3301 window: &mut Window,
3302 cx: &mut App,
3303 f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut Window, &mut App) -> R,
3304 ) -> R {
3305 self.content_size = content_size;
3306
3307 if !self.prepaint_listeners.is_empty() {
3308 let payload = InteractivityPrepaint {
3309 bounds,
3310 content_size,
3311 };
3312 for listener in self.prepaint_listeners.drain(..) {
3313 listener(&payload, window, cx);
3314 }
3315 }
3316
3317 #[cfg(any(feature = "inspector", debug_assertions))]
3318 window.with_inspector_state(
3319 _inspector_id,
3320 cx,
3321 |inspector_state: &mut Option<DivInspectorState>, _window| {
3322 if let Some(inspector_state) = inspector_state {
3323 inspector_state.bounds = bounds;
3324 inspector_state.content_size = content_size;
3325 }
3326 },
3327 );
3328
3329 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
3330 window.set_focus_handle(focus_handle, cx);
3331
3332 if window.a11y.is_active() {
3333 if let Some(global_id) = global_id {
3334 let node_id = global_id.accesskit_node_id();
3335 window.a11y.set_focusable(node_id, focus_handle.id);
3336 if focus_handle.is_focused(window) {
3337 window.a11y.set_focus(node_id);
3338 }
3339 } else if focus_handle.is_focused(window) {
3340 window
3344 .a11y
3345 .note_focus_without_node(focus_handle.id, "it has no element id");
3346 }
3347 }
3348 }
3349
3350 if self.report_active_descendant_focus && window.a11y.is_active() {
3351 if let Some(global_id) = global_id {
3352 window
3353 .a11y
3354 .set_active_descendant(global_id.accesskit_node_id());
3355 }
3356 }
3357 window.with_optional_element_state::<InteractiveElementState, _>(
3358 global_id,
3359 |element_state, window| {
3360 let mut element_state =
3361 element_state.map(|element_state| element_state.unwrap_or_default());
3362 let style = self.compute_style_internal(
3363 None,
3364 Some(bounds),
3365 element_state.as_mut(),
3366 window,
3367 cx,
3368 );
3369
3370 if let Some(element_state) = element_state.as_mut() {
3371 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
3372 let clicked_state = clicked_state.borrow();
3373 self.active = Some(clicked_state.element);
3374 }
3375 if self.hover_style.is_some() || self.group_hover_style.is_some() {
3376 element_state
3377 .hover_state
3378 .get_or_insert_with(Default::default);
3379 }
3380 if let Some(active_tooltip) = element_state.active_tooltip.as_ref() {
3381 if self.tooltip_builder.is_some() {
3382 self.tooltip_id = set_tooltip_on_window(active_tooltip, window);
3383 } else {
3384 if let Some(long_press_tooltip_active) =
3386 element_state.long_press_tooltip_active.as_ref()
3387 {
3388 long_press_tooltip_active.set(false);
3389 }
3390 element_state.active_tooltip.take();
3391 }
3392 }
3393 }
3394
3395 window.with_text_style(style.text_style().cloned(), |window| {
3396 window.with_content_mask(
3397 style.overflow_mask(bounds, window.rem_size()),
3398 |window| {
3399 let hitbox = if self.should_insert_hitbox(&style, window, cx) {
3400 let hitbox = window.insert_hitbox_mut(bounds, self.hitbox_behavior);
3401 if let Some(group) = &self.group {
3403 hitbox.tags.push(group.clone());
3404 }
3405 Some(hitbox.clone())
3406 } else {
3407 None
3408 };
3409
3410 let scroll_offset =
3411 self.clamp_scroll_position(bounds, &style, window, cx);
3412 let result = f(&style, scroll_offset, hitbox, window, cx);
3413 (result, element_state)
3414 },
3415 )
3416 })
3417 },
3418 )
3419 }
3420
3421 fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
3422 self.hitbox_behavior != HitboxBehavior::Normal
3423 || self.window_control.is_some()
3424 || style.mouse_cursor.is_some()
3425 || self.cursor_style_resolver.is_some()
3426 || self.group.is_some()
3427 || self.scroll_offset.is_some()
3428 || self.tracked_focus_handle.is_some()
3429 || self.hover_style.is_some()
3430 || self.group_hover_style.is_some()
3431 || !self.hover_region_listeners.is_empty()
3432 || !self.mouse_up_listeners.is_empty()
3433 || !self.mouse_pressure_listeners.is_empty()
3434 || !self.mouse_down_listeners.is_empty()
3435 || !self.mouse_move_listeners.is_empty()
3436 || !self.mouse_exit_listeners.is_empty()
3437 || !self.file_drop_exit_listeners.is_empty()
3438 || !self.click_listeners.is_empty()
3439 || !self.aux_click_listeners.is_empty()
3440 || !self.scroll_wheel_listeners.is_empty()
3441 || self.has_pinch_listeners()
3442 || self.drag_listener.is_some()
3443 || !self.drop_listeners.is_empty()
3444 || self.drag_over_style.is_some()
3445 || self.tooltip_builder.is_some()
3446 || window.is_inspector_picking(cx)
3447 }
3448
3449 fn clamp_scroll_position(
3450 &self,
3451 bounds: Bounds<Pixels>,
3452 style: &Style,
3453 window: &mut Window,
3454 _cx: &mut App,
3455 ) -> Point<Pixels> {
3456 fn round_to_two_decimals(pixels: Pixels) -> Pixels {
3457 const ROUNDING_FACTOR: f32 = 100.0;
3458 (pixels * ROUNDING_FACTOR).round() / ROUNDING_FACTOR
3459 }
3460
3461 if let Some(scroll_offset) = self.scroll_offset.as_ref() {
3462 let mut scroll_to_bottom = false;
3463 let mut tracked_scroll_handle = self
3464 .tracked_scroll_handle
3465 .as_ref()
3466 .map(|handle| handle.0.borrow_mut());
3467 if let Some(mut scroll_handle_state) = tracked_scroll_handle.as_deref_mut() {
3468 scroll_handle_state.overflow = style.overflow;
3469 scroll_handle_state.bounds = bounds;
3470 scroll_to_bottom = mem::take(&mut scroll_handle_state.scroll_to_bottom);
3471 ScrollHandle::scroll_to_active_item_in_state(
3472 scroll_handle_state,
3473 bounds,
3474 style.overflow,
3475 );
3476 }
3477
3478 let rem_size = window.rem_size();
3479 let padding = style
3484 .padding
3485 .to_pixels(bounds.size.into(), rem_size)
3486 .map(|edge| window.pixel_snap(*edge));
3487 let padding_size = size(padding.left + padding.right, padding.top + padding.bottom);
3488 let padded_content_size = self.content_size + padding_size;
3495 let scroll_max = Point::from(padded_content_size - bounds.size)
3496 .map(round_to_two_decimals)
3497 .max(&Default::default());
3498 let mut scroll_offset = scroll_offset.borrow_mut();
3501
3502 scroll_offset.x = scroll_offset.x.clamp(-scroll_max.x, px(0.));
3503 if scroll_to_bottom {
3504 scroll_offset.y = -scroll_max.y;
3505 } else {
3506 scroll_offset.y = scroll_offset.y.clamp(-scroll_max.y, px(0.));
3507 }
3508
3509 if let Some(mut scroll_handle_state) = tracked_scroll_handle {
3510 scroll_handle_state.max_offset = scroll_max;
3511 scroll_handle_state.bounds = bounds;
3512 }
3513
3514 *scroll_offset
3515 } else {
3516 Point::default()
3517 }
3518 }
3519
3520 pub fn paint(
3525 &mut self,
3526 global_id: Option<&GlobalElementId>,
3527 _inspector_id: Option<&InspectorElementId>,
3528 bounds: Bounds<Pixels>,
3529 hitbox: Option<&Hitbox>,
3530 window: &mut Window,
3531 cx: &mut App,
3532 f: impl FnOnce(&Style, &mut Window, &mut App),
3533 ) {
3534 self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(window));
3535 window.with_optional_element_state::<InteractiveElementState, _>(
3536 global_id,
3537 |element_state, window| {
3538 let mut element_state =
3539 element_state.map(|element_state| element_state.unwrap_or_default());
3540
3541 let style = self.compute_style_internal(
3542 hitbox,
3543 Some(bounds),
3544 element_state.as_mut(),
3545 window,
3546 cx,
3547 );
3548
3549 if let Some(state) = element_state.as_mut() {
3550 state
3551 .hover_region_states
3552 .truncate(self.hover_region_listeners.len());
3553 }
3554
3555 #[cfg(any(feature = "test-support", test))]
3556 if let Some(debug_selector) = &self.debug_selector {
3557 window.record_debug_bounds(debug_selector.clone(), bounds);
3558 }
3559
3560 self.paint_hover_group_handler(window, cx);
3561
3562 if style.visibility == Visibility::Hidden {
3563 return ((), element_state);
3564 }
3565
3566 let tab_group = self.tab_group.then_some(self.tab_index).flatten();
3567
3568 let paint_content = |window: &mut Window, cx: &mut App| {
3569 if let Some(focus_handle) = &self.tracked_focus_handle {
3573 window.next_frame.tab_stops.insert(focus_handle);
3574 }
3575
3576 if let Some(hitbox) = hitbox {
3577 #[cfg(debug_assertions)]
3578 self.paint_debug_info(global_id, hitbox, &style, window, cx);
3579
3580 if let Some(drag) = cx.active_drag.as_ref() {
3581 if let Some(mouse_cursor) = drag.cursor_style {
3582 window.set_window_cursor_style(mouse_cursor);
3583 }
3584 } else {
3585 if let Some(mouse_cursor) = style.mouse_cursor {
3586 window.set_cursor_style(mouse_cursor, hitbox);
3587 }
3588
3589 if let Some(resolve_cursor) = self.cursor_style_resolver.clone() {
3590 window.set_cursor_style_with(hitbox, move |position| {
3591 resolve_cursor(position, bounds)
3592 });
3593 }
3594 }
3595
3596 if let Some(group) = self.group.clone() {
3597 GroupHitboxes::push(group, hitbox.id, cx);
3598 }
3599
3600 if let Some(area) = self.window_control {
3601 window.insert_window_control_hitbox(area, hitbox.clone());
3602 }
3603
3604 self.paint_mouse_listeners(hitbox, element_state.as_mut(), window, cx);
3605 self.paint_scroll_listener(hitbox, &style, window, cx);
3606 }
3607
3608 self.paint_keyboard_listeners(window, cx);
3609
3610 if window.a11y.is_active()
3611 && let Some(global_id) = global_id
3612 && !self.a11y_action_listeners.is_empty()
3613 {
3614 let node_id = global_id.accesskit_node_id();
3615
3616 for (action, listener) in self.a11y_action_listeners.drain(..) {
3617 window.on_a11y_action(node_id, action, listener);
3618 }
3619 }
3620
3621 f(&style, window, cx);
3622
3623 if let Some(_hitbox) = hitbox {
3624 #[cfg(any(feature = "inspector", debug_assertions))]
3625 window.insert_inspector_hitbox(_hitbox.id, _inspector_id, cx);
3626
3627 if let Some(group) = self.group.as_ref() {
3628 GroupHitboxes::pop(group, cx);
3629 }
3630 }
3631 };
3632
3633 let paint_tab_group = |window: &mut Window, cx: &mut App| {
3634 window.with_tab_group(tab_group, |window| paint_content(window, cx));
3635 };
3636
3637 let paint_content_mask = |window: &mut Window, cx: &mut App| {
3638 window.with_content_mask(
3639 style.overflow_mask(bounds, window.rem_size()),
3640 |window| paint_tab_group(window, cx),
3641 );
3642 };
3643
3644 let paint_text_style = |window: &mut Window, cx: &mut App| {
3645 window.with_text_style(style.text_style().cloned(), |window| {
3646 paint_content_mask(window, cx);
3647 });
3648 };
3649
3650 style.paint(bounds, window, cx, paint_text_style);
3651
3652 ((), element_state)
3653 },
3654 );
3655 }
3656
3657 #[cfg(debug_assertions)]
3658 fn paint_debug_info(
3659 &self,
3660 global_id: Option<&GlobalElementId>,
3661 hitbox: &Hitbox,
3662 style: &Style,
3663 window: &mut Window,
3664 cx: &mut App,
3665 ) {
3666 if let Some(global_id) = global_id
3667 && (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
3668 && hitbox.is_hovered(window)
3669 {
3670 const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
3671 let element_id = format!("{global_id:?}");
3672 let str_len = element_id.len();
3673
3674 let render_debug_text = |window: &mut Window| {
3675 if let Some(text) = window
3676 .text_system()
3677 .shape_text(
3678 &element_id,
3679 FONT_SIZE,
3680 &[window.text_style().to_run(str_len)],
3681 None,
3682 None,
3683 )
3684 .ok()
3685 {
3686 text.paint(hitbox.origin, FONT_SIZE, TextAlign::Left, None, window, cx)
3687 .ok();
3688
3689 let text_bounds = crate::Bounds {
3690 origin: hitbox.origin,
3691 size: text.size(FONT_SIZE),
3692 };
3693 if let Some(source_location) = self.source_location
3694 && text_bounds.contains(&window.mouse_position())
3695 && window.modifiers().secondary()
3696 {
3697 let secondary_held = window.modifiers().secondary();
3698 window.on_key_event({
3699 move |e: &crate::ModifiersChangedEvent, _phase, window, _cx| {
3700 if e.modifiers.secondary() != secondary_held
3701 && text_bounds.contains(&window.mouse_position())
3702 {
3703 window.refresh();
3704 }
3705 }
3706 });
3707
3708 let was_hovered = hitbox.is_hovered(window);
3709 let current_view = window.current_view();
3710 window.on_mouse_event({
3711 let hitbox = hitbox.clone();
3712 move |_: &MouseMoveEvent, phase, window, cx| {
3713 if phase == DispatchPhase::Capture {
3714 let hovered = hitbox.is_hovered(window);
3715 if hovered != was_hovered {
3716 cx.notify(current_view)
3717 }
3718 }
3719 }
3720 });
3721
3722 window.on_mouse_event({
3723 let hitbox = hitbox.clone();
3724 move |e: &crate::MouseDownEvent, phase, window, cx| {
3725 if text_bounds.contains(&e.position)
3726 && phase.capture()
3727 && hitbox.is_hovered(window)
3728 {
3729 cx.stop_propagation();
3730 let Ok(dir) = std::env::current_dir() else {
3731 return;
3732 };
3733
3734 eprintln!(
3735 "This element was created at:\n{}:{}:{}",
3736 dir.join(source_location.file()).to_string_lossy(),
3737 source_location.line(),
3738 source_location.column()
3739 );
3740 }
3741 }
3742 });
3743 window.paint_quad(
3744 PaintQuad::new(crate::Bounds {
3745 origin: hitbox.origin
3746 + crate::point(crate::px(0.), FONT_SIZE - px(2.)),
3747 size: crate::Size {
3748 width: text_bounds.size.width,
3749 height: crate::px(1.),
3750 },
3751 })
3752 .border_widths(px(1.))
3753 .border_color(crate::red()),
3754 )
3755 }
3756 }
3757 };
3758
3759 window.with_text_style(
3760 Some(crate::TextStyleRefinement {
3761 color: Some(crate::red()),
3762 line_height: Some(FONT_SIZE.into()),
3763 background_color: Some(crate::white()),
3764 ..Default::default()
3765 }),
3766 render_debug_text,
3767 )
3768 }
3769 }
3770
3771 fn has_drag_over_styles(&self) -> bool {
3772 self.drag_over_style.is_some() || {
3773 self.drop_listeners
3775 .iter()
3776 .find(|(_, listener)| listener.on_drag_over.is_some())
3777 .is_some()
3778 }
3779 }
3780
3781 fn paint_mouse_listeners(
3782 &mut self,
3783 hitbox: &Hitbox,
3784 element_state: Option<&mut InteractiveElementState>,
3785 window: &mut Window,
3786 cx: &mut App,
3787 ) {
3788 let is_focused = self
3789 .tracked_focus_handle
3790 .as_ref()
3791 .map(|handle| handle.is_focused(window))
3792 .unwrap_or(false);
3793
3794 if let Some(focus_handle) = self.tracked_focus_handle.clone() {
3798 let hitbox = hitbox.clone();
3799 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, cx| {
3800 if phase == DispatchPhase::Bubble
3801 && hitbox.is_hovered(window)
3802 && !window.default_prevented()
3803 {
3804 window.focus(&focus_handle, cx);
3805 window.prevent_default();
3808 }
3809 });
3810 }
3811
3812 for listener in self.mouse_down_listeners.drain(..) {
3813 let hitbox = hitbox.clone();
3814 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
3815 listener(event, phase, &hitbox, window, cx);
3816 })
3817 }
3818
3819 for listener in self.mouse_up_listeners.drain(..) {
3820 let hitbox = hitbox.clone();
3821 window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
3822 listener(event, phase, &hitbox, window, cx);
3823 })
3824 }
3825
3826 for listener in self.mouse_pressure_listeners.drain(..) {
3827 let hitbox = hitbox.clone();
3828 window.on_mouse_event(move |event: &MousePressureEvent, phase, window, cx| {
3829 listener(event, phase, &hitbox, window, cx);
3830 })
3831 }
3832
3833 for listener in self.mouse_move_listeners.drain(..) {
3834 let hitbox = hitbox.clone();
3835 window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
3836 listener(event, phase, &hitbox, window, cx);
3837 })
3838 }
3839
3840 for listener in self.mouse_exit_listeners.drain(..) {
3841 let hitbox = hitbox.clone();
3842 window.on_mouse_event(move |event: &MouseExitEvent, phase, window, cx| {
3843 listener(event, phase, &hitbox, window, cx);
3844 })
3845 }
3846
3847 for listener in self.file_drop_exit_listeners.drain(..) {
3848 let hitbox = hitbox.clone();
3849 window.on_mouse_event(move |event: &FileDropEvent, phase, window, cx| {
3850 listener(event, phase, &hitbox, window, cx);
3851 })
3852 }
3853
3854 for listener in self.scroll_wheel_listeners.drain(..) {
3855 let hitbox = hitbox.clone();
3856 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
3857 listener(event, phase, &hitbox, window, cx);
3858 })
3859 }
3860
3861 for listener in self.pinch_listeners.drain(..) {
3862 let hitbox = hitbox.clone();
3863 window.on_mouse_event(move |event: &PinchEvent, phase, window, cx| {
3864 listener(event, phase, &hitbox, window, cx);
3865 })
3866 }
3867
3868 if self.hover_style.is_some()
3869 || self.base_style.mouse_cursor.is_some()
3870 || cx.active_drag.is_some() && self.has_drag_over_styles()
3871 {
3872 let hitbox = hitbox.clone();
3873 let hover_state = self.hover_style.as_ref().and_then(|_| {
3874 element_state
3875 .as_ref()
3876 .and_then(|state| state.hover_state.as_ref())
3877 .cloned()
3878 });
3879 let current_view = window.current_view();
3880
3881 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
3882 let hovered = hitbox.is_hovered(window);
3883 let was_hovered = hover_state
3884 .as_ref()
3885 .is_some_and(|state| state.borrow().element);
3886 if phase == DispatchPhase::Capture && hovered != was_hovered {
3887 if let Some(hover_state) = &hover_state {
3888 hover_state.borrow_mut().element = hovered;
3889 cx.notify(current_view);
3890 }
3891 }
3892 });
3893 }
3894
3895 if let Some(group_hover) = self.group_hover_style.as_ref() {
3896 if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
3897 let hover_state = element_state
3898 .as_ref()
3899 .and_then(|element| element.hover_state.as_ref())
3900 .cloned();
3901 let current_view = window.current_view();
3902
3903 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
3904 let group_hovered = group_hitbox_id.is_hovered(window);
3905 let was_group_hovered = hover_state
3906 .as_ref()
3907 .is_some_and(|state| state.borrow().group);
3908 if phase == DispatchPhase::Capture && group_hovered != was_group_hovered {
3909 if let Some(hover_state) = &hover_state {
3910 hover_state.borrow_mut().group = group_hovered;
3911 cx.notify(current_view);
3912 }
3913 }
3914 });
3915 }
3916 }
3917
3918 let drag_cursor_style = self.base_style.as_ref().mouse_cursor;
3919
3920 let mut drag_listener = mem::take(&mut self.drag_listener);
3921 let drop_listeners = mem::take(&mut self.drop_listeners);
3922 let click_listeners = mem::take(&mut self.click_listeners);
3923 let aux_click_listeners = mem::take(&mut self.aux_click_listeners);
3924 let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
3925
3926 if !drop_listeners.is_empty() {
3927 let hitbox = hitbox.clone();
3928 window.on_mouse_event({
3929 move |evt: &MouseUpEvent, phase, window, cx| {
3930 if !phase.bubble() {
3931 return;
3932 }
3933
3934 let drag_value = match cx.active_drag() {
3935 None => return,
3936 Some(drag) => drag.value.clone(),
3937 };
3938
3939 if let Some(predicate) = &can_drop_predicate {
3942 if !predicate(drag_value.as_ref(), &hitbox, window, cx) {
3943 return;
3944 }
3945 }
3946
3947 let drag_state_type = drag_value.as_ref().type_id();
3948 for (recv_drop_type, listener) in &drop_listeners {
3949 if *recv_drop_type != drag_state_type {
3950 continue;
3951 }
3952
3953 if let Some(predicate) = &listener.can_drop {
3956 if !predicate(drag_value.as_ref(), &hitbox, window, cx) {
3957 continue;
3958 }
3959 }
3960 else if !hitbox.is_hovered(window) {
3965 continue;
3966 }
3967
3968 let drag_value =
3969 cx.stop_drag(window).expect("already confirmed drag exists");
3970
3971 if let Some(predicate) = &listener.on_drop {
3972 predicate(drag_value.as_ref(), evt, window, cx);
3973 }
3974 window.refresh();
3975 cx.stop_propagation();
3976 break;
3977 }
3978 }
3979 });
3980 }
3981
3982 if let Some(element_state) = element_state {
3983 if !click_listeners.is_empty()
3984 || !aux_click_listeners.is_empty()
3985 || drag_listener.is_some()
3986 {
3987 let pending_mouse_down = element_state
3988 .pending_mouse_down
3989 .get_or_insert_with(Default::default)
3990 .clone();
3991
3992 let pending_keyboard_down = element_state
3993 .pending_keyboard_down
3994 .get_or_insert_with(Default::default)
3995 .clone();
3996
3997 let clicked_state = element_state
3998 .clicked_state
3999 .get_or_insert_with(Default::default)
4000 .clone();
4001
4002 let current_view = window.current_view();
4003 window.on_mouse_event({
4004 let pending_mouse_down = pending_mouse_down.clone();
4005 let hitbox = hitbox.clone();
4006 let has_aux_click_listeners = !aux_click_listeners.is_empty();
4007 move |event: &MouseDownEvent, phase, window, cx| {
4008 if phase == DispatchPhase::Bubble
4009 && (event.button == MouseButton::Left || has_aux_click_listeners)
4010 && hitbox.is_hovered(window)
4011 {
4012 *pending_mouse_down.borrow_mut() = Some(event.clone());
4013 cx.notify(current_view);
4014 }
4015 }
4016 });
4017
4018 window.on_mouse_event({
4019 let pending_mouse_down = pending_mouse_down.clone();
4020 let hitbox = hitbox.clone();
4021 move |event: &MouseMoveEvent, phase, window, cx| {
4022 if phase == DispatchPhase::Capture || drag_listener.is_none() {
4023 return;
4024 }
4025
4026 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
4027 let Some(mouse_down) = pending_mouse_down.clone() else {
4028 return;
4029 };
4030 if cx.has_active_drag() || mouse_down.button != MouseButton::Left {
4031 return;
4032 }
4033 if (event.position - mouse_down.position).magnitude() <= DRAG_THRESHOLD {
4034 return;
4035 }
4036 if let Some(listener) = drag_listener.take() {
4037 *clicked_state.borrow_mut() = ElementClickedState::default();
4038 let cursor_offset = event.position - hitbox.origin;
4039 let drag = listener(cursor_offset, drag_cursor_style, window, cx);
4040 cx.start_drag(drag);
4041 pending_mouse_down.take();
4042 window.refresh();
4043 cx.stop_propagation();
4044 }
4045 }
4046 });
4047
4048 if is_focused {
4049 window.on_key_event({
4059 let pending_keyboard_down = pending_keyboard_down.clone();
4060 move |event: &KeyDownEvent, phase, window, _cx| {
4061 if phase.bubble() && !window.default_prevented() {
4062 let stroke = &event.keystroke;
4063 let is_activation_key = (stroke.key.eq("enter")
4064 || stroke.key.eq("space"))
4065 && !stroke.modifiers.modified();
4066 *pending_keyboard_down.borrow_mut() =
4067 is_activation_key.then_some(window.focus_generation);
4068 }
4069 }
4070 });
4071
4072 window.on_key_event({
4074 let click_listeners = click_listeners.clone();
4075 let hitbox = hitbox.clone();
4076 move |event: &KeyUpEvent, phase, window, cx| {
4077 if phase.bubble() && !window.default_prevented() {
4078 let stroke = &event.keystroke;
4079 let keyboard_button = if stroke.key.eq("enter") {
4080 Some(KeyboardButton::Enter)
4081 } else if stroke.key.eq("space") {
4082 Some(KeyboardButton::Space)
4083 } else {
4084 None
4085 };
4086
4087 if let Some(button) = keyboard_button
4088 && !stroke.modifiers.modified()
4089 {
4090 let pending =
4091 std::mem::take(&mut *pending_keyboard_down.borrow_mut());
4092 if pending != Some(window.focus_generation) {
4093 return;
4094 }
4095
4096 let click_event = ClickEvent::Keyboard(KeyboardClickEvent {
4097 button,
4098 bounds: hitbox.bounds,
4099 });
4100
4101 for listener in &click_listeners {
4102 listener(&click_event, window, cx);
4103 }
4104 } else {
4105 *pending_keyboard_down.borrow_mut() = None;
4109 }
4110 }
4111 }
4112 });
4113 }
4114
4115 window.on_mouse_event({
4116 let mut captured_mouse_down = None;
4117 let hitbox = hitbox.clone();
4118 move |event: &MouseUpEvent, phase, window, cx| match phase {
4119 DispatchPhase::Capture => {
4123 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
4124 if pending_mouse_down.is_some() && hitbox.is_hovered(window) {
4125 captured_mouse_down = pending_mouse_down.take();
4126 cx.notify(current_view);
4127 } else if pending_mouse_down.is_some() {
4128 pending_mouse_down.take();
4134 cx.notify(current_view);
4135 }
4136 }
4137 DispatchPhase::Bubble => {
4139 if let Some(mouse_down) = captured_mouse_down.take() {
4140 let btn = mouse_down.button;
4141
4142 let mouse_click = ClickEvent::Mouse(MouseClickEvent {
4143 down: mouse_down,
4144 up: event.clone(),
4145 });
4146
4147 match btn {
4148 MouseButton::Left => {
4149 for listener in &click_listeners {
4150 listener(&mouse_click, window, cx);
4151 }
4152 }
4153 _ => {
4154 for listener in &aux_click_listeners {
4155 listener(&mouse_click, window, cx);
4156 }
4157 }
4158 }
4159 }
4160 }
4161 }
4162 });
4163 }
4164
4165 if !self.hover_region_listeners.is_empty() {
4166 let states = &mut element_state.hover_region_states;
4167 states.resize_with(self.hover_region_listeners.len(), HoverRegionState::default);
4168 for (listener, state) in self.hover_region_listeners.iter().zip(states) {
4169 let observe = listener.bind(state);
4170 let region_hitbox = hitbox.clone();
4171 let hover_listener_mode = self.hover_listener_mode;
4172 let pending_mouse_down = element_state
4173 .pending_mouse_down
4174 .get_or_insert_with(Default::default)
4175 .clone();
4176 window.on_pointer_change(move |change, window, cx| {
4177 let layout_changed = change.cause == PointerChangeCause::Layout;
4178 if layout_changed && pending_mouse_down.borrow().is_some() {
4179 return;
4180 }
4181 let position = if cx.has_active_drag()
4182 || !layout_changed && pending_mouse_down.borrow().is_some()
4183 {
4184 None
4185 } else if change.position.is_some()
4186 && !hover_listener_mode.is_hovered(®ion_hitbox, window)
4187 {
4188 None
4189 } else {
4190 change.position
4191 };
4192 observe(position, region_hitbox.bounds, window, cx);
4193 });
4194 }
4195 }
4196
4197 if let Some(tooltip_builder) = self.tooltip_builder.take() {
4198 let active_tooltip = element_state
4199 .active_tooltip
4200 .get_or_insert_with(Default::default)
4201 .clone();
4202 let pending_mouse_down = element_state
4203 .pending_mouse_down
4204 .get_or_insert_with(Default::default)
4205 .clone();
4206 let long_press_tooltip_active = element_state
4207 .long_press_tooltip_active
4208 .get_or_insert_with(Default::default)
4209 .clone();
4210
4211 let tooltip_is_hoverable = tooltip_builder.hoverable;
4212 let build_tooltip = Rc::new(move |window: &mut Window, cx: &mut App| {
4213 Some(((tooltip_builder.build)(window, cx), tooltip_is_hoverable))
4214 });
4215
4216 let check_is_hovered_during_prepaint = Rc::new({
4218 let pending_mouse_down = pending_mouse_down.clone();
4219 let source_hitbox = hitbox.clone();
4220 move |window: &Window| {
4221 !window.last_input_was_keyboard()
4222 && pending_mouse_down.borrow().is_none()
4223 && source_hitbox.contains(&window.mouse_position())
4224 }
4225 });
4226 let check_is_hovered = Rc::new({
4227 let hitbox = hitbox.clone();
4228 move |window: &Window| {
4229 pending_mouse_down.borrow().is_none() && hitbox.is_hovered(window)
4230 }
4231 });
4232 register_tooltip_mouse_handlers(
4233 &active_tooltip,
4234 self.tooltip_id,
4235 build_tooltip,
4236 check_is_hovered,
4237 check_is_hovered_during_prepaint,
4238 long_press_tooltip_active,
4239 self.tooltip_show_delay,
4240 window,
4241 );
4242 }
4243
4244 let active_state = element_state
4247 .clicked_state
4248 .get_or_insert_with(Default::default)
4249 .clone();
4250
4251 let current_view = window.current_view();
4252 {
4253 let active_state = active_state.clone();
4254 window.on_mouse_event(move |_: &MouseUpEvent, phase, _window, cx| {
4255 if phase == DispatchPhase::Capture && active_state.borrow().is_clicked() {
4256 *active_state.borrow_mut() = ElementClickedState::default();
4257 cx.notify(current_view);
4258 }
4259 });
4260 }
4261
4262 {
4263 let active_group_hitbox = self
4264 .group_active_style
4265 .as_ref()
4266 .and_then(|group_active| GroupHitboxes::get(&group_active.group, cx));
4267 let hitbox = hitbox.clone();
4268 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, cx| {
4269 if phase == DispatchPhase::Bubble && !window.default_prevented() {
4270 let group_hovered = active_group_hitbox
4271 .is_some_and(|group_hitbox_id| group_hitbox_id.is_hovered(window));
4272 let element_hovered = hitbox.is_hovered(window);
4273 if group_hovered || element_hovered {
4274 *active_state.borrow_mut() = ElementClickedState {
4275 group: group_hovered,
4276 element: element_hovered,
4277 };
4278 cx.notify(current_view);
4279 }
4280 }
4281 });
4282 }
4283 }
4284 }
4285
4286 fn paint_keyboard_listeners(&mut self, window: &mut Window, _cx: &mut App) {
4287 let key_down_listeners = mem::take(&mut self.key_down_listeners);
4288 let key_up_listeners = mem::take(&mut self.key_up_listeners);
4289 let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners);
4290 let action_listeners = mem::take(&mut self.action_listeners);
4291 if let Some(context) = self.key_context.clone() {
4292 window.set_key_context(context);
4293 }
4294
4295 for listener in key_down_listeners {
4296 window.on_key_event(move |event: &KeyDownEvent, phase, window, cx| {
4297 listener(event, phase, window, cx);
4298 })
4299 }
4300
4301 for listener in key_up_listeners {
4302 window.on_key_event(move |event: &KeyUpEvent, phase, window, cx| {
4303 listener(event, phase, window, cx);
4304 })
4305 }
4306
4307 for listener in modifiers_changed_listeners {
4308 window.on_modifiers_changed(move |event: &ModifiersChangedEvent, window, cx| {
4309 listener(event, window, cx);
4310 })
4311 }
4312
4313 for (action_type, listener) in action_listeners {
4314 window.on_action(action_type, listener)
4315 }
4316 }
4317
4318 fn paint_hover_group_handler(&self, window: &mut Window, cx: &mut App) {
4319 let group_hitbox = self
4320 .group_hover_style
4321 .as_ref()
4322 .and_then(|group_hover| GroupHitboxes::get(&group_hover.group, cx));
4323
4324 if let Some(group_hitbox) = group_hitbox {
4325 let was_hovered = group_hitbox.is_hovered(window);
4326 let current_view = window.current_view();
4327 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
4328 let hovered = group_hitbox.is_hovered(window);
4329 if phase == DispatchPhase::Capture && hovered != was_hovered {
4330 cx.notify(current_view);
4331 }
4332 });
4333 }
4334 }
4335
4336 fn paint_scroll_listener(
4337 &self,
4338 hitbox: &Hitbox,
4339 style: &Style,
4340 window: &mut Window,
4341 _cx: &mut App,
4342 ) {
4343 if let Some(scroll_offset) = self.scroll_offset.clone() {
4344 let ongoing_scroll = self.ongoing_scroll.clone();
4345 let overflow = style.overflow;
4346 let allow_concurrent_scroll = style.allow_concurrent_scroll;
4347 let restrict_scroll_to_axis = style.restrict_scroll_to_axis;
4348 let line_height = window.line_height();
4349 let hitbox = hitbox.clone();
4350 let current_view = window.current_view();
4351 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
4352 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
4353 let mut scroll_offset = scroll_offset.borrow_mut();
4354 let old_scroll_offset = *scroll_offset;
4355 let mut delta = event.delta.pixel_delta(line_height);
4356
4357 if restrict_scroll_to_axis
4358 && event.delta.precise()
4359 && let Some(ongoing_scroll) = &ongoing_scroll
4360 {
4361 ongoing_scroll
4362 .borrow_mut()
4363 .filter(&mut delta, event.touch_phase);
4364 }
4365
4366 let mut delta_x = match overflow.x {
4367 Overflow::Scroll if !delta.x.is_zero() => delta.x,
4368 Overflow::Scroll
4369 if !restrict_scroll_to_axis && overflow.y != Overflow::Scroll =>
4370 {
4371 delta.y
4372 }
4373 _ => Pixels::ZERO,
4374 };
4375 let mut delta_y = match overflow.y {
4376 Overflow::Scroll if !delta.y.is_zero() => delta.y,
4377 Overflow::Scroll
4378 if !restrict_scroll_to_axis && overflow.x != Overflow::Scroll =>
4379 {
4380 delta.x
4381 }
4382 _ => Pixels::ZERO,
4383 };
4384 if !allow_concurrent_scroll && !delta_x.is_zero() && !delta_y.is_zero() {
4385 if delta_x.abs() > delta_y.abs() {
4386 delta_y = Pixels::ZERO;
4387 } else {
4388 delta_x = Pixels::ZERO;
4389 }
4390 }
4391 scroll_offset.y += delta_y;
4392 scroll_offset.x += delta_x;
4393 if *scroll_offset != old_scroll_offset {
4394 cx.notify(current_view);
4395 }
4396 }
4397 });
4398 }
4399 }
4400
4401 pub fn compute_style(
4405 &self,
4406 global_id: Option<&GlobalElementId>,
4407 hitbox: Option<&Hitbox>,
4408 window: &mut Window,
4409 cx: &mut App,
4410 ) -> Style {
4411 window.with_optional_element_state(global_id, |element_state, window| {
4412 let mut element_state =
4413 element_state.map(|element_state| element_state.unwrap_or_default());
4414 let style = self.compute_style_internal(
4415 hitbox,
4416 hitbox.map(|hitbox| hitbox.bounds),
4417 element_state.as_mut(),
4418 window,
4419 cx,
4420 );
4421 (style, element_state)
4422 })
4423 }
4424
4425 fn compute_style_internal(
4427 &self,
4428 hitbox: Option<&Hitbox>,
4429 bounds: Option<Bounds<Pixels>>,
4430 mut element_state: Option<&mut InteractiveElementState>,
4431 window: &mut Window,
4432 cx: &mut App,
4433 ) -> Style {
4434 let mut style = Style::default();
4435 style.refine(&self.base_style);
4436
4437 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
4438 if let Some(in_focus_style) = self.in_focus_style.as_ref()
4439 && focus_handle.within_focused(window, cx)
4440 {
4441 style.refine(in_focus_style);
4442 }
4443
4444 if let Some(focus_style) = self.focus_style.as_ref()
4445 && focus_handle.is_focused(window)
4446 {
4447 style.refine(focus_style);
4448 }
4449
4450 if let Some(focus_visible_style) = self.focus_visible_style.as_ref()
4451 && focus_handle.is_focused(window)
4452 && window.last_input_was_keyboard()
4453 {
4454 style.refine(focus_visible_style);
4455 }
4456 }
4457
4458 if !cx.has_active_drag() {
4459 if let Some(group_hover) = self.group_hover_style.as_ref() {
4460 let is_group_hovered =
4461 if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
4462 !window.last_input_was_touch() && group_hitbox_id.is_hovered(window)
4463 } else if let Some(element_state) = element_state.as_ref() {
4464 element_state
4465 .hover_state
4466 .as_ref()
4467 .is_some_and(|state| state.borrow().group_is_active(window))
4468 } else {
4469 false
4470 };
4471
4472 if is_group_hovered {
4473 style.refine(&group_hover.style);
4474 }
4475 }
4476
4477 if let Some(hover_style) = self.hover_style.as_ref() {
4478 let is_hovered = if let Some(hitbox) = hitbox {
4479 !window.last_input_was_touch() && hitbox.is_hovered(window)
4480 } else if let Some(element_state) = element_state.as_ref() {
4481 element_state
4482 .hover_state
4483 .as_ref()
4484 .is_some_and(|state| state.borrow().element_is_active(window))
4485 } else {
4486 false
4487 };
4488
4489 if is_hovered {
4490 style.refine(hover_style);
4491 }
4492 }
4493 }
4494
4495 if let Some(hitbox) = hitbox {
4496 if let Some(drag) = cx.active_drag.take() {
4497 let drag_state_type = drag.value.as_ref().type_id();
4498 let can_drop_without_listener = self
4499 .can_drop_predicate
4500 .as_ref()
4501 .map(|predicate| predicate(drag.value.as_ref(), hitbox, window, cx))
4502 .unwrap_or(true);
4503 let relevant_drop_listener = match can_drop_without_listener {
4504 false => None,
4505 true => {
4506 let mut iter = self.drop_listeners.iter();
4507 iter.find(|(drop_state_type, listener)| {
4508 if *drop_state_type != drag_state_type {
4509 return false;
4510 }
4511
4512 let mut can_drop = true;
4513 if let Some(predicate) = &listener.can_drop {
4514 can_drop = predicate(drag.value.as_ref(), hitbox, window, cx);
4515 }
4516 can_drop
4517 })
4518 }
4519 };
4520
4521 if let Some((_, drop_listener)) = relevant_drop_listener {
4522 for (state_type, group_drag_style) in &self.group_drag_over_styles {
4523 if let Some(group_hitbox_id) =
4524 GroupHitboxes::get(&group_drag_style.group, cx)
4525 && *state_type == drag.value.as_ref().type_id()
4526 && group_hitbox_id.is_hovered(window)
4527 {
4528 style.refine(&group_drag_style.style);
4529 }
4530 }
4531
4532 if hitbox.is_hovered(window) {
4533 if let Some(drag_over) = &self.drag_over_style {
4534 style.refine(&drag_over(drag.value.as_ref(), window, cx));
4535 }
4536 if let Some(drag_over) = &drop_listener.on_drag_over {
4537 style.refine(&drag_over(drag.value.as_ref(), window, cx));
4538 }
4539 }
4540 }
4541
4542 style.mouse_cursor = drag.cursor_style;
4543 cx.active_drag = Some(drag);
4544 }
4545 }
4546
4547 if let Some(element_state) = element_state.as_deref_mut() {
4548 let clicked_state = element_state
4549 .clicked_state
4550 .get_or_insert_with(Default::default)
4551 .borrow();
4552 if clicked_state.group
4553 && let Some(group) = self.group_active_style.as_ref()
4554 {
4555 style.refine(&group.style)
4556 }
4557
4558 if let Some(active_style) = self.active_style.as_ref()
4559 && clicked_state.element
4560 {
4561 style.refine(active_style)
4562 }
4563 }
4564
4565 if let Some(element_state) = element_state {
4566 if let Some(transitions) = self.style_transitions.as_ref() {
4567 if transitions.apply(
4568 &mut style,
4569 element_state
4570 .style_transitions
4571 .get_or_insert_with(Default::default),
4572 StyleTransitionContext::new(bounds, window.rem_size())
4573 .with_containing_bounds(window.style_transition_containing_bounds()),
4574 cx.background_executor().now(),
4575 cx.reduce_motion(),
4576 ) {
4577 window.request_animation_frame();
4578 }
4579 } else {
4580 element_state.style_transitions = None;
4581 }
4582 }
4583
4584 style
4585 }
4586
4587 pub fn write_a11y_info(&self, node: &mut accesskit::Node) {
4592 if let Some(id) = &self.aria.author_id {
4593 node.set_author_id(id.to_string());
4594 }
4595 if let Some(label) = &self.aria.label {
4596 node.set_label(label.to_string());
4597 }
4598 if let Some(description) = &self.aria.description {
4599 node.set_description(description.to_string());
4600 }
4601 if let Some(keyshortcuts) = &self.aria.keyshortcuts {
4602 node.set_keyboard_shortcut(keyshortcuts.to_string());
4603 }
4604 if let Some(selected) = self.aria.selected {
4605 node.set_selected(selected);
4606 }
4607 if let Some(expanded) = self.aria.expanded {
4608 node.set_expanded(expanded);
4609 }
4610 if let Some(toggled) = self.aria.toggled {
4611 node.set_toggled(toggled);
4612 }
4613 if let Some(disabled) = self.aria.disabled {
4614 if disabled {
4615 node.set_disabled();
4616 } else {
4617 node.clear_disabled();
4618 }
4619 }
4620 if let Some(value) = self.aria.numeric_value {
4621 node.set_numeric_value(value);
4622 }
4623 if let Some(value) = self.aria.min_numeric_value {
4624 node.set_min_numeric_value(value);
4625 }
4626 if let Some(value) = self.aria.max_numeric_value {
4627 node.set_max_numeric_value(value);
4628 }
4629 if let Some(step) = self.aria.numeric_value_step {
4630 node.set_numeric_value_step(step);
4631 }
4632 if let Some(value) = &self.aria.value {
4633 node.set_value(value.to_string());
4634 }
4635 if let Some(placeholder) = &self.aria.placeholder {
4636 node.set_placeholder(placeholder.to_string());
4637 }
4638 if let Some(orientation) = self.aria.orientation {
4639 node.set_orientation(orientation);
4640 }
4641 if let Some(level) = self.aria.level {
4642 node.set_level(level);
4643 }
4644 if let Some(position) = self.aria.position_in_set {
4645 node.set_position_in_set(position);
4646 }
4647 if let Some(size) = self.aria.size_of_set {
4648 node.set_size_of_set(size);
4649 }
4650 if let Some(index) = self.aria.row_index {
4651 node.set_row_index(index);
4652 }
4653 if let Some(index) = self.aria.column_index {
4654 node.set_column_index(index);
4655 }
4656 if let Some(count) = self.aria.row_count {
4657 node.set_row_count(count);
4658 }
4659 if let Some(count) = self.aria.column_count {
4660 node.set_column_count(count);
4661 }
4662 if let Some(modal) = self.aria.modal {
4663 if modal {
4664 node.set_modal();
4665 } else {
4666 node.clear_modal();
4667 }
4668 }
4669 if let Some(live) = self.aria.live {
4670 node.set_live(live);
4671 }
4672 if let Some(atomic) = self.aria.live_atomic {
4673 if atomic {
4674 node.set_live_atomic();
4675 } else {
4676 node.clear_live_atomic();
4677 }
4678 }
4679 if !self.click_listeners.is_empty() {
4680 node.add_action(accesskit::Action::Click);
4681 }
4682 if self.tracked_focus_handle.is_some() || self.focusable {
4683 node.add_action(accesskit::Action::Focus);
4684 }
4685 for (action, _) in &self.a11y_action_listeners {
4686 node.add_action(*action);
4687 }
4688 }
4689}
4690
4691#[derive(Default)]
4695pub struct InteractiveElementState {
4696 pub(crate) focus_handle: Option<FocusHandle>,
4697 pub(crate) clicked_state: Option<Rc<RefCell<ElementClickedState>>>,
4698 pub(crate) hover_state: Option<Rc<RefCell<ElementHoverState>>>,
4699 pub(crate) hover_region_states: Vec<HoverRegionState>,
4700 pub(crate) pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
4701 pub(crate) pending_keyboard_down: Option<Rc<RefCell<Option<u64>>>>,
4709 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
4710 ongoing_scroll: Option<Rc<RefCell<OngoingScroll>>>,
4711 pub(crate) active_tooltip: Option<Rc<RefCell<Option<ActiveTooltip>>>>,
4712 pub(crate) style_transitions: Option<Box<StyleTransitionState>>,
4713
4714 long_press_tooltip_active: Option<Rc<Cell<bool>>>,
4715}
4716
4717#[derive(Copy, Clone, Default, Eq, PartialEq)]
4719pub struct ElementClickedState {
4720 pub group: bool,
4722
4723 pub element: bool,
4725}
4726
4727impl ElementClickedState {
4728 fn is_clicked(&self) -> bool {
4729 self.group || self.element
4730 }
4731}
4732
4733#[derive(Copy, Clone, Default, Eq, PartialEq)]
4735pub struct ElementHoverState {
4736 pub group: bool,
4738
4739 pub element: bool,
4741}
4742
4743impl ElementHoverState {
4744 fn group_is_active(&self, window: &Window) -> bool {
4745 self.group && !window.last_input_was_keyboard() && !window.last_input_was_touch()
4746 }
4747
4748 fn element_is_active(&self, window: &Window) -> bool {
4749 self.element && !window.last_input_was_keyboard() && !window.last_input_was_touch()
4750 }
4751}
4752
4753pub(crate) enum ActiveTooltip {
4754 WaitingForShow { _task: Task<()> },
4756 Visible {
4758 tooltip: AnyTooltip,
4759 is_hoverable: bool,
4760 },
4761 WaitingForHide {
4764 tooltip: AnyTooltip,
4765 _task: Task<()>,
4766 },
4767}
4768
4769pub(crate) fn clear_active_tooltip(
4770 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
4771 window: &mut Window,
4772) {
4773 match active_tooltip.borrow_mut().take() {
4774 None => {}
4775 Some(ActiveTooltip::WaitingForShow { .. }) => {}
4776 Some(ActiveTooltip::Visible { .. }) => window.refresh(),
4777 Some(ActiveTooltip::WaitingForHide { .. }) => window.refresh(),
4778 }
4779}
4780
4781pub(crate) fn clear_active_tooltip_if_not_hoverable(
4782 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
4783 window: &mut Window,
4784) {
4785 let should_clear = match active_tooltip.borrow().as_ref() {
4786 None => false,
4787 Some(ActiveTooltip::WaitingForShow { .. }) => false,
4788 Some(ActiveTooltip::Visible { is_hoverable, .. }) => !is_hoverable,
4789 Some(ActiveTooltip::WaitingForHide { .. }) => false,
4790 };
4791 if should_clear {
4792 active_tooltip.borrow_mut().take();
4793 window.refresh();
4794 }
4795}
4796
4797pub(crate) fn set_tooltip_on_window(
4798 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
4799 window: &mut Window,
4800) -> Option<TooltipId> {
4801 let tooltip = match active_tooltip.borrow().as_ref() {
4802 None => return None,
4803 Some(ActiveTooltip::WaitingForShow { .. }) => return None,
4804 Some(ActiveTooltip::Visible { tooltip, .. }) => tooltip.clone(),
4805 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => tooltip.clone(),
4806 };
4807 Some(window.set_tooltip(tooltip))
4808}
4809
4810pub(crate) fn register_tooltip_mouse_handlers(
4811 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
4812 tooltip_id: Option<TooltipId>,
4813 build_tooltip: Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
4814 check_is_hovered: Rc<dyn Fn(&Window) -> bool>,
4815 check_is_hovered_during_prepaint: Rc<dyn Fn(&Window) -> bool>,
4816 long_press_tooltip_active: Rc<Cell<bool>>,
4817 show_delay: Option<Duration>,
4818 window: &mut Window,
4819) {
4820 let current_view = window.current_view();
4821 let show_delay = show_delay.unwrap_or(DEFAULT_TOOLTIP_SHOW_DELAY);
4822
4823 window.on_mouse_event({
4824 let active_tooltip = active_tooltip.clone();
4825 let build_tooltip = build_tooltip.clone();
4826 let check_is_hovered = check_is_hovered.clone();
4827 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
4828 move |_: &MouseMoveEvent, phase, window, cx| {
4829 handle_tooltip_mouse_move(
4830 &active_tooltip,
4831 &build_tooltip,
4832 &check_is_hovered,
4833 &check_is_hovered_during_prepaint,
4834 tooltip_id,
4835 current_view,
4836 phase,
4837 show_delay,
4838 window,
4839 cx,
4840 )
4841 }
4842 });
4843
4844 window.on_mouse_event({
4845 let active_tooltip = active_tooltip.clone();
4846 move |_: &MouseDownEvent, _phase, window: &mut Window, _cx| {
4847 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
4848 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
4849 }
4850 }
4851 });
4852
4853 window.on_mouse_event({
4854 let active_tooltip = active_tooltip.clone();
4855 let build_tooltip = build_tooltip.clone();
4856 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
4857 move |event: &LongPressEvent, phase, window, cx| {
4858 if !phase.bubble() {
4859 return;
4860 }
4861
4862 match event.phase {
4863 TouchPhase::Started if !window.default_prevented() && check_is_hovered(window) => {
4864 if show_tooltip(
4865 &active_tooltip,
4866 &build_tooltip,
4867 &check_is_hovered_during_prepaint,
4868 Some(long_press_tooltip_active.clone()),
4869 window,
4870 cx,
4871 ) {
4872 long_press_tooltip_active.set(true);
4873 window.prevent_default();
4874 cx.stop_propagation();
4875 }
4876 }
4877 TouchPhase::Ended | TouchPhase::Cancelled
4878 if long_press_tooltip_active.replace(false) =>
4879 {
4880 clear_active_tooltip(&active_tooltip, window);
4881 cx.stop_propagation();
4882 }
4883 TouchPhase::Started
4884 | TouchPhase::Moved
4885 | TouchPhase::Ended
4886 | TouchPhase::Cancelled => {}
4887 }
4888 }
4889 });
4890
4891 window.on_mouse_event({
4892 let active_tooltip = active_tooltip.clone();
4893 move |_: &ScrollWheelEvent, _phase, window: &mut Window, _cx| {
4894 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
4895 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
4896 }
4897 }
4898 });
4899}
4900
4901fn handle_tooltip_mouse_move(
4910 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
4911 build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
4912 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
4913 check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
4914 tooltip_id: Option<TooltipId>,
4915 current_view: EntityId,
4916 phase: DispatchPhase,
4917 show_delay: Duration,
4918 window: &mut Window,
4919 cx: &mut App,
4920) {
4921 enum Action {
4924 None,
4925 CancelShow,
4926 ScheduleShow,
4927 CheckVisible,
4928 }
4929
4930 let action = match active_tooltip.borrow().as_ref() {
4931 None => {
4932 let is_hovered = check_is_hovered(window);
4933 if is_hovered && phase.bubble() {
4934 Action::ScheduleShow
4935 } else {
4936 Action::None
4937 }
4938 }
4939 Some(ActiveTooltip::WaitingForShow { .. }) => {
4940 let is_hovered = check_is_hovered(window);
4941 if is_hovered {
4942 Action::None
4943 } else {
4944 Action::CancelShow
4945 }
4946 }
4947 Some(ActiveTooltip::Visible { is_hoverable, .. }) => {
4948 if phase.capture()
4949 && !check_is_hovered(window)
4950 && (!*is_hoverable
4951 || !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
4952 {
4953 Action::CheckVisible
4954 } else {
4955 Action::None
4956 }
4957 }
4958 Some(ActiveTooltip::WaitingForHide { .. }) => {
4959 if phase.capture()
4960 && (check_is_hovered(window)
4961 || tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
4962 {
4963 Action::CheckVisible
4964 } else {
4965 Action::None
4966 }
4967 }
4968 };
4969
4970 match action {
4971 Action::None => {}
4972 Action::CancelShow => {
4973 active_tooltip.borrow_mut().take();
4975 }
4976 Action::ScheduleShow => {
4977 let delayed_show_task = window.spawn(cx, {
4978 let weak_active_tooltip = Rc::downgrade(active_tooltip);
4979 let build_tooltip = build_tooltip.clone();
4980 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
4981 async move |cx| {
4982 cx.background_executor().timer(show_delay).await;
4983 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
4984 return;
4985 };
4986 cx.update(|window, cx| {
4987 show_tooltip(
4988 &active_tooltip,
4989 &build_tooltip,
4990 &check_is_hovered_during_prepaint,
4991 None,
4992 window,
4993 cx,
4994 );
4995 })
4996 .ok();
4997 }
4998 });
4999 active_tooltip
5000 .borrow_mut()
5001 .replace(ActiveTooltip::WaitingForShow {
5002 _task: delayed_show_task,
5003 });
5004 }
5005 Action::CheckVisible => cx.notify(current_view),
5006 }
5007}
5008
5009fn show_tooltip(
5010 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
5011 build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
5012 check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
5013 long_press_tooltip_active: Option<Rc<Cell<bool>>>,
5014 window: &mut Window,
5015 cx: &mut App,
5016) -> bool {
5017 let new_tooltip = build_tooltip(window, cx).map(|(view, tooltip_is_hoverable)| {
5018 let weak_active_tooltip = Rc::downgrade(active_tooltip);
5019 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
5020 ActiveTooltip::Visible {
5021 tooltip: AnyTooltip {
5022 view,
5023 mouse_position: window.mouse_position(),
5024 check_visible_and_update: Rc::new(move |tooltip_bounds, window, cx| {
5025 if long_press_tooltip_active
5026 .as_ref()
5027 .is_some_and(|active| active.get())
5028 {
5029 return true;
5030 }
5031 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
5032 return false;
5033 };
5034 handle_tooltip_check_visible_and_update(
5035 &active_tooltip,
5036 tooltip_is_hoverable,
5037 &check_is_hovered_during_prepaint,
5038 tooltip_bounds,
5039 window,
5040 cx,
5041 )
5042 }),
5043 },
5044 is_hoverable: tooltip_is_hoverable,
5045 }
5046 });
5047 let did_show = new_tooltip.is_some();
5048 *active_tooltip.borrow_mut() = new_tooltip;
5049 window.refresh();
5050 did_show
5051}
5052
5053fn handle_tooltip_check_visible_and_update(
5057 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
5058 tooltip_is_hoverable: bool,
5059 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
5060 tooltip_bounds: Bounds<Pixels>,
5061 window: &mut Window,
5062 cx: &mut App,
5063) -> bool {
5064 enum Action {
5067 None,
5068 Hide,
5069 ScheduleHide(AnyTooltip),
5070 CancelHide(AnyTooltip),
5071 }
5072
5073 let is_hovered = check_is_hovered(window)
5074 || (tooltip_is_hoverable && tooltip_bounds.contains(&window.mouse_position()));
5075 let action = match active_tooltip.borrow().as_ref() {
5076 Some(ActiveTooltip::Visible { tooltip, .. }) => {
5077 if is_hovered {
5078 Action::None
5079 } else {
5080 if tooltip_is_hoverable {
5081 Action::ScheduleHide(tooltip.clone())
5082 } else {
5083 Action::Hide
5084 }
5085 }
5086 }
5087 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => {
5088 if is_hovered {
5089 Action::CancelHide(tooltip.clone())
5090 } else {
5091 Action::None
5092 }
5093 }
5094 None | Some(ActiveTooltip::WaitingForShow { .. }) => Action::None,
5095 };
5096
5097 match action {
5098 Action::None => {}
5099 Action::Hide => clear_active_tooltip(active_tooltip, window),
5100 Action::ScheduleHide(tooltip) => {
5101 let delayed_hide_task = window.spawn(cx, {
5102 let weak_active_tooltip = Rc::downgrade(active_tooltip);
5103 async move |cx| {
5104 cx.background_executor()
5105 .timer(HOVERABLE_TOOLTIP_HIDE_DELAY)
5106 .await;
5107 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
5108 return;
5109 };
5110 if active_tooltip.borrow_mut().take().is_some() {
5111 cx.update(|window, _cx| window.refresh()).ok();
5112 }
5113 }
5114 });
5115 active_tooltip
5116 .borrow_mut()
5117 .replace(ActiveTooltip::WaitingForHide {
5118 tooltip,
5119 _task: delayed_hide_task,
5120 });
5121 }
5122 Action::CancelHide(tooltip) => {
5123 active_tooltip.borrow_mut().replace(ActiveTooltip::Visible {
5125 tooltip,
5126 is_hoverable: true,
5127 });
5128 }
5129 }
5130
5131 active_tooltip.borrow().is_some()
5132}
5133
5134#[derive(Default)]
5136pub(crate) struct GroupHitboxes(HashMap<SharedString, SmallVec<[HitboxId; 1]>>);
5137
5138impl Global for GroupHitboxes {}
5139
5140impl GroupHitboxes {
5141 pub fn get(name: &SharedString, cx: &mut App) -> Option<HitboxId> {
5142 cx.default_global::<Self>()
5143 .0
5144 .get(name)
5145 .and_then(|bounds_stack| bounds_stack.last())
5146 .cloned()
5147 }
5148
5149 pub fn push(name: SharedString, hitbox_id: HitboxId, cx: &mut App) {
5150 cx.default_global::<Self>()
5151 .0
5152 .entry(name)
5153 .or_default()
5154 .push(hitbox_id);
5155 }
5156
5157 pub fn pop(name: &SharedString, cx: &mut App) {
5158 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
5159 }
5160}
5161
5162pub struct Stateful<E> {
5167 pub(crate) element: E,
5168}
5169
5170impl<E> Styled for Stateful<E>
5171where
5172 E: Styled,
5173{
5174 fn style(&mut self) -> &mut StyleRefinement {
5175 self.element.style()
5176 }
5177}
5178
5179impl<E> StatefulInteractiveElement for Stateful<E> where Self: InteractiveElement {}
5180
5181impl<E> InteractiveElement for Stateful<E>
5182where
5183 E: InteractiveElement,
5184{
5185 fn interactivity(&mut self) -> &mut Interactivity {
5186 self.element.interactivity()
5187 }
5188}
5189
5190impl<E> Element for Stateful<E>
5191where
5192 E: Element,
5193{
5194 type RequestLayoutState = E::RequestLayoutState;
5195 type PrepaintState = E::PrepaintState;
5196
5197 fn id(&self) -> Option<ElementId> {
5198 self.element.id()
5199 }
5200
5201 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
5202 self.element.source_location()
5203 }
5204
5205 fn a11y_role(&self) -> Option<accesskit::Role> {
5206 self.element.a11y_role()
5207 }
5208
5209 fn is_a11y_hidden(&self) -> bool {
5210 self.element.is_a11y_hidden()
5211 }
5212
5213 fn write_a11y_info(&self, node: &mut accesskit::Node) {
5214 self.element.write_a11y_info(node);
5215 }
5216
5217 fn a11y_synthetic_children(
5218 &mut self,
5219 prepaint: &mut Self::PrepaintState,
5220 builder: &mut crate::A11ySubtreeBuilder,
5221 ) {
5222 self.element.a11y_synthetic_children(prepaint, builder);
5223 }
5224
5225 fn request_layout(
5226 &mut self,
5227 id: Option<&GlobalElementId>,
5228 inspector_id: Option<&InspectorElementId>,
5229 window: &mut Window,
5230 cx: &mut App,
5231 ) -> (LayoutId, Self::RequestLayoutState) {
5232 self.element.request_layout(id, inspector_id, window, cx)
5233 }
5234
5235 fn prepaint(
5236 &mut self,
5237 id: Option<&GlobalElementId>,
5238 inspector_id: Option<&InspectorElementId>,
5239 bounds: Bounds<Pixels>,
5240 state: &mut Self::RequestLayoutState,
5241 window: &mut Window,
5242 cx: &mut App,
5243 ) -> E::PrepaintState {
5244 self.element
5245 .prepaint(id, inspector_id, bounds, state, window, cx)
5246 }
5247
5248 fn paint(
5249 &mut self,
5250 id: Option<&GlobalElementId>,
5251 inspector_id: Option<&InspectorElementId>,
5252 bounds: Bounds<Pixels>,
5253 request_layout: &mut Self::RequestLayoutState,
5254 prepaint: &mut Self::PrepaintState,
5255 window: &mut Window,
5256 cx: &mut App,
5257 ) {
5258 self.element.paint(
5259 id,
5260 inspector_id,
5261 bounds,
5262 request_layout,
5263 prepaint,
5264 window,
5265 cx,
5266 );
5267 }
5268}
5269
5270impl<E: IntoElement> IntoElement for Stateful<E> {
5271 type Element = E::Element;
5272
5273 fn into_element(self) -> Self::Element {
5274 self.element.into_element()
5275 }
5276}
5277
5278impl<E> ParentElement for Stateful<E>
5279where
5280 E: ParentElement,
5281{
5282 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
5283 self.element.extend(elements)
5284 }
5285}
5286
5287#[derive(Clone)]
5294pub struct ScrollAnchor {
5295 handle: ScrollHandle,
5296 last_origin: Rc<RefCell<Point<Pixels>>>,
5297}
5298
5299impl ScrollAnchor {
5300 pub fn for_handle(handle: ScrollHandle) -> Self {
5302 Self {
5303 handle,
5304 last_origin: Default::default(),
5305 }
5306 }
5307
5308 pub fn scroll_to(&self, window: &mut Window, _cx: &mut App) {
5312 let this = self.clone();
5313
5314 window.on_next_frame(move |_, _| {
5315 let viewport_bounds = this.handle.bounds();
5316 let self_bounds = *this.last_origin.borrow();
5317 this.handle.set_offset(viewport_bounds.origin - self_bounds);
5318 });
5319 }
5320}
5321
5322#[derive(Default, Debug)]
5323struct ScrollHandleState {
5324 revision: crate::Revision,
5326 offset: Rc<RefCell<Point<Pixels>>>,
5327 ongoing_scroll: Rc<RefCell<OngoingScroll>>,
5328 bounds: Bounds<Pixels>,
5329 max_offset: Point<Pixels>,
5330 child_bounds: Vec<Bounds<Pixels>>,
5331 scroll_to_bottom: bool,
5332 overflow: Point<Overflow>,
5333 active_item: Option<ScrollActiveItem>,
5334}
5335
5336#[derive(Default, Debug, Clone, Copy)]
5337struct ScrollActiveItem {
5338 index: usize,
5339 strategy: ScrollStrategy,
5340}
5341
5342#[derive(Default, Debug, Clone, Copy)]
5343enum ScrollStrategy {
5344 #[default]
5345 FirstVisible,
5346 Top,
5347}
5348
5349#[derive(Clone, Debug)]
5363pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
5364
5365impl Default for ScrollHandle {
5366 fn default() -> Self {
5367 Self::new()
5368 }
5369}
5370
5371impl ScrollHandle {
5372 pub fn new() -> Self {
5374 Self(Rc::default())
5375 }
5376
5377 pub(crate) fn revision(&self) -> crate::Revision {
5378 self.0.borrow().revision.clone()
5379 }
5380
5381 pub fn offset(&self) -> Point<Pixels> {
5386 *self.0.borrow().offset.borrow()
5387 }
5388
5389 pub fn max_offset(&self) -> Point<Pixels> {
5391 self.0.borrow().max_offset
5392 }
5393
5394 pub fn top_item(&self) -> usize {
5398 let state = self.0.borrow();
5399 let top = state.bounds.top() - state.offset.borrow().y;
5400
5401 match state.child_bounds.binary_search_by(|bounds| {
5402 if top < bounds.top() {
5403 Ordering::Greater
5404 } else if top > bounds.bottom() {
5405 Ordering::Less
5406 } else {
5407 Ordering::Equal
5408 }
5409 }) {
5410 Ok(ix) => ix,
5411 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
5412 }
5413 }
5414
5415 pub fn bottom_item(&self) -> usize {
5419 let state = self.0.borrow();
5420 let bottom = state.bounds.bottom() - state.offset.borrow().y;
5421
5422 match state.child_bounds.binary_search_by(|bounds| {
5423 if bottom < bounds.top() {
5424 Ordering::Greater
5425 } else if bottom > bounds.bottom() {
5426 Ordering::Less
5427 } else {
5428 Ordering::Equal
5429 }
5430 }) {
5431 Ok(ix) => ix,
5432 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
5433 }
5434 }
5435
5436 pub fn bounds(&self) -> Bounds<Pixels> {
5438 self.0.borrow().bounds
5439 }
5440
5441 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
5443 self.0.borrow().child_bounds.get(ix).cloned()
5444 }
5445
5446 pub fn scroll_to_item(&self, ix: usize) {
5450 let mut state = self.0.borrow_mut();
5451 state.revision.bump();
5452 state.active_item = Some(ScrollActiveItem {
5453 index: ix,
5454 strategy: ScrollStrategy::default(),
5455 });
5456 }
5457
5458 pub fn scroll_to_top_of_item(&self, ix: usize) {
5462 let mut state = self.0.borrow_mut();
5463 state.revision.bump();
5464 state.active_item = Some(ScrollActiveItem {
5465 index: ix,
5466 strategy: ScrollStrategy::Top,
5467 });
5468 }
5469
5470 #[cfg(test)]
5474 fn scroll_to_active_item(&self) {
5475 let mut state = self.0.borrow_mut();
5476 let viewport_bounds = state.bounds;
5477 let overflow = state.overflow;
5478 Self::scroll_to_active_item_in_state(&mut state, viewport_bounds, overflow);
5479 }
5480
5481 fn scroll_to_active_item_in_state(
5482 state: &mut ScrollHandleState,
5483 viewport_bounds: Bounds<Pixels>,
5484 overflow: Point<Overflow>,
5485 ) {
5486 let Some(active_item) = state.active_item else {
5487 return;
5488 };
5489
5490 let active_item = match state.child_bounds.get(active_item.index) {
5491 Some(bounds) => {
5492 let mut scroll_offset = state.offset.borrow_mut();
5493
5494 match active_item.strategy {
5495 ScrollStrategy::FirstVisible => {
5496 if overflow.y == Overflow::Scroll {
5497 let child_height = bounds.size.height;
5498 let viewport_height = viewport_bounds.size.height;
5499 if child_height > viewport_height {
5500 scroll_offset.y = viewport_bounds.top() - bounds.top();
5501 } else if bounds.top() + scroll_offset.y < viewport_bounds.top() {
5502 scroll_offset.y = viewport_bounds.top() - bounds.top();
5503 } else if bounds.bottom() + scroll_offset.y > viewport_bounds.bottom() {
5504 scroll_offset.y = viewport_bounds.bottom() - bounds.bottom();
5505 }
5506 }
5507 }
5508 ScrollStrategy::Top => {
5509 scroll_offset.y = viewport_bounds.top() - bounds.top();
5510 }
5511 }
5512
5513 if overflow.x == Overflow::Scroll {
5514 let child_width = bounds.size.width;
5515 let viewport_width = viewport_bounds.size.width;
5516 if child_width > viewport_width {
5517 scroll_offset.x = viewport_bounds.left() - bounds.left();
5518 } else if bounds.left() + scroll_offset.x < viewport_bounds.left() {
5519 scroll_offset.x = viewport_bounds.left() - bounds.left();
5520 } else if bounds.right() + scroll_offset.x > viewport_bounds.right() {
5521 scroll_offset.x = viewport_bounds.right() - bounds.right();
5522 }
5523 }
5524 None
5525 }
5526 None => Some(active_item),
5527 };
5528 state.active_item = active_item;
5529 }
5530
5531 pub fn scroll_to_bottom(&self) {
5533 let mut state = self.0.borrow_mut();
5534 state.revision.bump();
5535 state.scroll_to_bottom = true;
5536 }
5537
5538 pub fn set_offset(&self, mut position: Point<Pixels>) {
5543 let state = self.0.borrow();
5544 state.revision.bump();
5545 *state.offset.borrow_mut() = position;
5546 }
5547
5548 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
5553 let ix = self.top_item();
5554 let state = self.0.borrow();
5555
5556 if let Some(child_bounds) = state.child_bounds.get(ix) {
5557 (
5558 ix,
5559 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
5560 )
5561 } else {
5562 (ix, px(0.))
5563 }
5564 }
5565
5566 pub fn logical_scroll_bottom(&self) -> (usize, Pixels) {
5568 let ix = self.bottom_item();
5569 let state = self.0.borrow();
5570
5571 if let Some(child_bounds) = state.child_bounds.get(ix) {
5572 (
5573 ix,
5574 child_bounds.bottom() + state.offset.borrow().y - state.bounds.bottom(),
5575 )
5576 } else {
5577 (ix, px(0.))
5578 }
5579 }
5580
5581 pub fn children_count(&self) -> usize {
5583 self.0.borrow().child_bounds.len()
5584 }
5585}
5586
5587#[cfg(test)]
5588mod tests {
5589 use super::*;
5590 use crate::{
5591 AnyWindowHandle, Context, GestureTuning, InputEvent, Keystroke, MouseMoveEvent,
5592 TestAppContext, TouchEvent, TouchId, canvas, util::FluentBuilder as _,
5593 };
5594 use std::{cell::Cell, rc::Weak};
5595
5596 #[gpui::test]
5597 fn tracked_scroll_reports_children_to_prepaint_listener(cx: &mut TestAppContext) {
5598 struct ScrollView {
5599 bounds: Rc<RefCell<Vec<Bounds<Pixels>>>>,
5600 handle: ScrollHandle,
5601 }
5602
5603 impl Render for ScrollView {
5604 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5605 let bounds = self.bounds.clone();
5606 div()
5607 .on_children_prepainted(move |children, _, _| {
5608 *bounds.borrow_mut() = children;
5609 })
5610 .id("scrolling-container")
5611 .flex()
5612 .flex_col()
5613 .size_full()
5614 .overflow_y_scroll()
5615 .track_scroll(&self.handle)
5616 .child(div().h(px(20.)).flex_shrink_0())
5617 .child(div().h(px(30.)).flex_shrink_0())
5618 }
5619 }
5620
5621 let captured = Rc::new(RefCell::new(Vec::new()));
5622 let scroll_handle = ScrollHandle::new();
5623 let window = cx.open_window(size(px(100.), px(100.)), |_, _| ScrollView {
5624 bounds: captured.clone(),
5625 handle: scroll_handle.clone(),
5626 });
5627 cx.update_window(window.into(), |_, window, cx| window.draw(cx).clear(cx))
5628 .unwrap();
5629
5630 let bounds = captured.borrow();
5631 assert_eq!(bounds.len(), 2);
5632 assert_eq!(bounds[0].size.height, px(20.));
5633 assert_eq!(bounds[1].size.height, px(30.));
5634 assert_eq!(bounds[1].top(), bounds[0].bottom());
5635 assert_eq!(scroll_handle.bounds_for_item(0), Some(bounds[0]));
5636 assert_eq!(scroll_handle.bounds_for_item(1), Some(bounds[1]));
5637 }
5638
5639 #[gpui::test]
5640 fn tracked_scroll_item_uses_current_viewport_when_it_shrinks(cx: &mut TestAppContext) {
5641 struct ResizableScrollView {
5642 height: Pixels,
5643 handle: ScrollHandle,
5644 }
5645
5646 impl Render for ResizableScrollView {
5647 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5648 div()
5649 .id("resizable-scroll")
5650 .w(px(100.))
5651 .h(self.height)
5652 .flex()
5653 .flex_col()
5654 .overflow_y_scroll()
5655 .track_scroll(&self.handle)
5656 .children((0..4).map(|_| div().h(px(30.)).flex_shrink_0()))
5657 }
5658 }
5659
5660 let handle = ScrollHandle::new();
5661 let window = cx.open_window(size(px(100.), px(100.)), |_, _| ResizableScrollView {
5662 height: px(100.),
5663 handle: handle.clone(),
5664 });
5665 cx.update_window(window.into(), |_, window, cx| window.draw(cx).clear(cx))
5666 .unwrap();
5667
5668 let old_viewport = handle.bounds();
5669 let target = handle.bounds_for_item(2).unwrap();
5670 assert_eq!(old_viewport.size.height, px(100.));
5671 assert!(target.top() >= old_viewport.top());
5672 assert!(target.bottom() <= old_viewport.bottom());
5673
5674 window
5675 .update(cx, |view, window, _cx| {
5676 view.height = px(40.);
5677 window.refresh();
5678 })
5679 .unwrap();
5680 handle.scroll_to_item(2);
5683 cx.update_window(window.into(), |_, window, cx| window.draw(cx).clear(cx))
5684 .unwrap();
5685
5686 let viewport = handle.bounds();
5687 let target = handle.bounds_for_item(2).unwrap();
5688 let offset = handle.offset();
5689 assert_eq!(viewport.size.height, px(40.));
5690 assert!(target.top() + offset.y >= viewport.top());
5691 assert!(target.bottom() + offset.y <= viewport.bottom());
5692 }
5693
5694 #[gpui::test]
5695 fn inline_div_places_element_children_in_text_flow(cx: &mut TestAppContext) {
5696 let placed_children = Rc::new(RefCell::new(Vec::new()));
5697 let captured_bounds = placed_children.clone();
5698 let window = cx.add_empty_window();
5699 let highlight_color = hsla(0.4, 0.6, 0.5, 1.0);
5700
5701 window.draw(
5702 point(px(10.), px(20.)),
5703 size(px(160.), px(80.)),
5704 move |_, _| {
5705 div()
5706 .block()
5707 .w(px(160.))
5708 .child(StyledText::new("prefix ").with_highlights([(
5709 0.."prefix ".len(),
5710 HighlightStyle {
5711 background_color: Some(highlight_color),
5712 ..Default::default()
5713 },
5714 )]))
5715 .child(div().inline_flex().size(px(18.)).align_baseline())
5716 .child(div().inline_flex().size(px(18.)).align_middle())
5717 .child(" suffix")
5718 .on_children_prepainted(move |bounds, _, _| {
5719 *captured_bounds.borrow_mut() = bounds;
5720 })
5721 .into_any_element()
5722 },
5723 );
5724
5725 let placed_children = placed_children.borrow();
5726 assert_eq!(placed_children.len(), 4);
5727 let baseline_box = placed_children[1];
5728 let middle_box = placed_children[2];
5729 assert_eq!(baseline_box.size, size(px(18.), px(18.)));
5730 assert_eq!(middle_box.size, baseline_box.size);
5731 assert!(baseline_box.origin.x > px(10.));
5732 assert!(middle_box.origin.x >= baseline_box.right());
5733 assert!(middle_box.right() <= px(170.));
5734 assert!(baseline_box.origin.y >= px(20.));
5735 assert!(middle_box.origin.y > baseline_box.origin.y);
5736 assert!(middle_box.bottom() > baseline_box.bottom());
5737 assert!(
5738 window.update(|window, _| {
5739 window
5740 .rendered_frame
5741 .scene
5742 .quads
5743 .iter()
5744 .any(|quad| quad.background.solid == highlight_color.into())
5745 }),
5746 "the highlighted inline text did not paint its background"
5747 );
5748 }
5749
5750 #[gpui::test]
5751 fn default_block_preserves_grid_flow_and_hides_none_descendants(cx: &mut TestAppContext) {
5752 let hidden_prepainted = Rc::new(Cell::new(false));
5753 let hidden_prepaint = hidden_prepainted.clone();
5754 let window = cx.add_empty_window();
5755
5756 window.draw(
5757 point(px(10.), px(20.)),
5758 size(px(200.), px(100.)),
5759 move |_, _| {
5760 div()
5761 .w(px(200.))
5762 .child(
5763 div()
5764 .grid()
5765 .w_full()
5766 .grid_cols(2)
5767 .debug_selector(|| "grid".into())
5768 .child(div().h(px(20.)).debug_selector(|| "grid-cell-1".into()))
5769 .child(div().h(px(30.)).debug_selector(|| "grid-cell-2".into())),
5770 )
5771 .child(div().hidden().h(px(300.)).child(canvas(
5772 move |_, _, _| hidden_prepaint.set(true),
5773 |_, _, _, _| {},
5774 )))
5775 .child(div().h(px(10.)).debug_selector(|| "following-block".into()))
5776 .into_any_element()
5777 },
5778 );
5779
5780 let [grid, first_cell, second_cell, following] =
5781 ["grid", "grid-cell-1", "grid-cell-2", "following-block"].map(|selector| {
5782 window
5783 .update(|window, _| window.rendered_frame.debug_bounds.get(selector).copied())
5784 .unwrap_or_else(|| panic!("{selector} was not rendered"))
5785 });
5786
5787 assert_eq!(first_cell.origin.y, grid.origin.y);
5788 assert_eq!(second_cell.origin.y, grid.origin.y);
5789 assert!(second_cell.origin.x > first_cell.origin.x);
5790 assert_eq!(following.origin.y, grid.bottom());
5791 assert!(!hidden_prepainted.get());
5792 }
5793
5794 struct ChildPrepaintTestView {
5795 custom_order: bool,
5796 visits: Rc<RefCell<Vec<(usize, Point<Pixels>)>>>,
5797 callback_offsets: Rc<RefCell<Vec<Point<Pixels>>>>,
5798 scroll_handle: ScrollHandle,
5799 }
5800
5801 impl Render for ChildPrepaintTestView {
5802 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
5803 let children = (0..3).map(|index| {
5804 let visits = self.visits.clone();
5805
5806 canvas(
5807 move |_bounds, window, _cx| {
5808 visits.borrow_mut().push((index, window.element_offset()));
5809 },
5810 |_bounds, _state, _window, _cx| {},
5811 )
5812 .w_full()
5813 .h(px(40.))
5814 .flex_shrink_0()
5815 });
5816 let mut element = div().size_full().flex().flex_col().children(children);
5817
5818 if self.custom_order {
5819 let visits = self.visits.clone();
5820 let callback_offsets = self.callback_offsets.clone();
5821 element = element.with_dynamic_prepaint_order(move |window, _cx| {
5822 assert!(visits.borrow().is_empty());
5823 callback_offsets.borrow_mut().push(window.element_offset());
5824
5825 SmallVec::from_slice(&[2, 9, 0, 1])
5826 });
5827 }
5828
5829 element
5830 .id("prepaint-order")
5831 .overflow_y_scroll()
5832 .track_scroll(&self.scroll_handle)
5833 }
5834 }
5835
5836 #[gpui::test]
5837 fn child_prepaint_preserves_order_and_scroll_offset(cx: &mut TestAppContext) {
5838 let cx = cx.add_empty_window();
5839 let origin = point(px(20.), px(30.));
5840 let scroll_offset = point(px(0.), px(-15.));
5841 let expected_offset = origin + scroll_offset;
5842
5843 for custom_order in [false, true] {
5844 let visits = Rc::new(RefCell::new(Vec::new()));
5845 let callback_offsets = Rc::new(RefCell::new(Vec::new()));
5846 let scroll_handle = ScrollHandle::new();
5847 scroll_handle.set_offset(scroll_offset);
5848
5849 cx.draw(origin, size(px(50.), px(50.)), |_window, cx| {
5850 cx.new(|_cx| ChildPrepaintTestView {
5851 custom_order,
5852 visits: visits.clone(),
5853 callback_offsets: callback_offsets.clone(),
5854 scroll_handle,
5855 })
5856 .into_any_element()
5857 });
5858
5859 let expected_order = if custom_order { [2, 0, 1] } else { [0, 1, 2] };
5860 assert_eq!(
5861 *visits.borrow(),
5862 expected_order.map(|index| (index, expected_offset)),
5863 );
5864
5865 if custom_order {
5866 assert_eq!(*callback_offsets.borrow(), [expected_offset]);
5867 } else {
5868 assert!(callback_offsets.borrow().is_empty());
5869 }
5870 }
5871 }
5872
5873 struct GroupHoverTestView {
5874 render_count: Rc<Cell<usize>>,
5875 anonymous_paint_count: Rc<Cell<usize>>,
5876 stateful_width: Rc<Cell<Pixels>>,
5877 }
5878
5879 impl Render for GroupHoverTestView {
5880 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
5881 self.render_count.set(self.render_count.get() + 1);
5882 let anonymous_paint_count = self.anonymous_paint_count.clone();
5883 let stateful_width = self.stateful_width.clone();
5884 div().size_full().child(
5885 div()
5886 .ml(px(20.))
5887 .mt(px(20.))
5888 .size(px(50.))
5889 .relative()
5890 .group("hover-group")
5891 .child(
5892 div()
5893 .absolute()
5894 .size_full()
5895 .invisible()
5896 .group_hover("hover-group", |style| style.visible())
5897 .child(canvas(
5898 |_, _, _| {},
5899 move |_, _, _, _| {
5900 anonymous_paint_count.set(anonymous_paint_count.get() + 1)
5901 },
5902 )),
5903 )
5904 .child(
5905 div()
5906 .id("stateful-group-hover-target")
5907 .absolute()
5908 .top_0()
5909 .left_0()
5910 .size(px(10.))
5911 .group_hover("hover-group", |style| style.size(px(20.)))
5912 .child(
5913 canvas(
5914 move |bounds, _, _| stateful_width.set(bounds.size.width),
5915 |_, _, _, _| {},
5916 )
5917 .size_full(),
5918 ),
5919 ),
5920 )
5921 }
5922 }
5923
5924 #[gpui::test]
5925 fn group_hover_styles_update_only_on_transitions(cx: &mut TestAppContext) {
5926 let render_count = Rc::new(Cell::new(0));
5927 let anonymous_paint_count = Rc::new(Cell::new(0));
5928 let stateful_width = Rc::new(Cell::new(px(0.)));
5929 let window = cx.add_window({
5930 let render_count = render_count.clone();
5931 let anonymous_paint_count = anonymous_paint_count.clone();
5932 let stateful_width = stateful_width.clone();
5933 move |_, _| GroupHoverTestView {
5934 render_count,
5935 anonymous_paint_count,
5936 stateful_width,
5937 }
5938 });
5939 let window = AnyWindowHandle::from(window);
5940
5941 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
5942 .unwrap();
5943 assert_eq!(anonymous_paint_count.get(), 0);
5944 assert_eq!(stateful_width.get(), px(10.));
5945
5946 let move_mouse = |cx: &mut TestAppContext, position| {
5947 cx.update_window(window, |_, window, cx| {
5948 window.simulate_mouse_move(position, cx)
5949 })
5950 .unwrap();
5951 };
5952
5953 let initial_render_count = render_count.get();
5954 move_mouse(cx, point(px(25.), px(25.)));
5955 assert_eq!(render_count.get(), initial_render_count + 1);
5956 assert_eq!(anonymous_paint_count.get(), 1);
5957 assert_eq!(stateful_width.get(), px(20.));
5958
5959 move_mouse(cx, point(px(30.), px(30.)));
5960 assert_eq!(render_count.get(), initial_render_count + 1);
5961 assert_eq!(anonymous_paint_count.get(), 1);
5962 assert_eq!(stateful_width.get(), px(20.));
5963
5964 move_mouse(cx, point(px(5.), px(5.)));
5965 assert_eq!(render_count.get(), initial_render_count + 2);
5966 assert_eq!(anonymous_paint_count.get(), 1);
5967 assert_eq!(stateful_width.get(), px(10.));
5968
5969 move_mouse(cx, point(px(10.), px(10.)));
5970 assert_eq!(render_count.get(), initial_render_count + 2);
5971 assert_eq!(anonymous_paint_count.get(), 1);
5972 assert_eq!(stateful_width.get(), px(10.));
5973 }
5974
5975 struct HoverListenerLayoutTestView {
5976 target_left: Pixels,
5977 hover_transitions: Rc<RefCell<Vec<bool>>>,
5978 }
5979
5980 impl Render for HoverListenerLayoutTestView {
5981 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
5982 let hover_transitions = self.hover_transitions.clone();
5983 div().relative().size_full().child(
5984 div()
5985 .id("hover-target")
5986 .absolute()
5987 .left(self.target_left)
5988 .top_0()
5989 .size(px(20.))
5990 .on_click(|_, _, _| {})
5991 .on_hover(move |is_hovered, _, _| {
5992 hover_transitions.borrow_mut().push(*is_hovered);
5993 }),
5994 )
5995 }
5996 }
5997
5998 #[gpui::test]
5999 fn default_hover_listener_updates_when_layout_changes_under_stationary_mouse(
6000 cx: &mut TestAppContext,
6001 ) {
6002 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
6003 let window = cx.add_window({
6004 let hover_transitions = hover_transitions.clone();
6005 move |_, _| HoverListenerLayoutTestView {
6006 target_left: px(40.),
6007 hover_transitions,
6008 }
6009 });
6010 let any_window = AnyWindowHandle::from(window);
6011
6012 cx.update_window(any_window, |_, window, cx| {
6013 window.draw(cx).clear(cx);
6014 window.simulate_mouse_move(point(px(10.), px(10.)), cx);
6015 })
6016 .unwrap();
6017 assert!(hover_transitions.borrow().is_empty());
6018
6019 window
6020 .update(cx, |view, _, cx| {
6021 view.target_left = px(0.);
6022 cx.notify();
6023 })
6024 .unwrap();
6025 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
6026 .unwrap();
6027 assert_eq!(*hover_transitions.borrow(), [true]);
6028
6029 window
6030 .update(cx, |view, _, cx| {
6031 view.target_left = px(40.);
6032 cx.notify();
6033 })
6034 .unwrap();
6035 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
6036 .unwrap();
6037 assert_eq!(*hover_transitions.borrow(), [true, false]);
6038 }
6039
6040 #[gpui::test]
6041 fn default_hover_listener_ends_after_key_press(cx: &mut TestAppContext) {
6042 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
6043 let window = cx.add_window({
6044 let hover_transitions = hover_transitions.clone();
6045 move |_, _| HoverListenerLayoutTestView {
6046 target_left: px(0.),
6047 hover_transitions,
6048 }
6049 });
6050 let any_window = AnyWindowHandle::from(window);
6051
6052 cx.update_window(any_window, |_, window, cx| {
6053 window.draw(cx).clear(cx);
6054 window.simulate_mouse_move(point(px(10.), px(10.)), cx);
6055 })
6056 .unwrap();
6057 assert_eq!(*hover_transitions.borrow(), [true]);
6058
6059 key_down(cx, any_window, "a");
6060 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
6061 .unwrap();
6062 assert_eq!(*hover_transitions.borrow(), [true, false]);
6063 }
6064
6065 struct HoverListenerModeLayoutTestView {
6066 target_left: Pixels,
6067 hover_transitions: Rc<RefCell<Vec<bool>>>,
6068 }
6069
6070 impl Render for HoverListenerModeLayoutTestView {
6071 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
6072 let hover_transitions = self.hover_transitions.clone();
6073 div().relative().size_full().child(
6074 div()
6075 .id("hover-target")
6076 .absolute()
6077 .left(self.target_left)
6078 .top_0()
6079 .size(px(20.))
6080 .hover_listener_mode(HoverListenerMode::InputModalityIndependent)
6081 .on_hover(move |is_hovered, _, _| {
6082 hover_transitions.borrow_mut().push(*is_hovered);
6083 }),
6084 )
6085 }
6086 }
6087
6088 #[gpui::test]
6089 fn input_modality_independent_hover_listener_updates_after_key_press(cx: &mut TestAppContext) {
6090 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
6091 let window = cx.add_window({
6092 let hover_transitions = hover_transitions.clone();
6093 move |_, _| HoverListenerModeLayoutTestView {
6094 target_left: px(40.),
6095 hover_transitions,
6096 }
6097 });
6098 let any_window = AnyWindowHandle::from(window);
6099 let pointer_position = point(px(10.), px(10.));
6100
6101 cx.update_window(any_window, |_, window, cx| {
6102 window.draw(cx).clear(cx);
6103 window.simulate_mouse_move(pointer_position, cx);
6104 })
6105 .unwrap();
6106 assert!(hover_transitions.borrow().is_empty());
6107
6108 key_down(cx, any_window, "a");
6109 window
6110 .update(cx, |view, _, cx| {
6111 view.target_left = px(0.);
6112 cx.notify();
6113 })
6114 .unwrap();
6115 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
6116 .unwrap();
6117 assert_eq!(*hover_transitions.borrow(), [true]);
6118
6119 key_down(cx, any_window, "b");
6120 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
6121 .unwrap();
6122 assert_eq!(*hover_transitions.borrow(), [true]);
6123
6124 cx.update_window(any_window, |_, window, cx| {
6125 window.simulate_mouse_move(point(px(30.), px(10.)), cx);
6126 })
6127 .unwrap();
6128 assert_eq!(*hover_transitions.borrow(), [true, false]);
6129
6130 cx.update_window(any_window, |_, window, cx| {
6131 window.simulate_mouse_move(pointer_position, cx);
6132 window.dispatch_event(
6133 MouseExitEvent {
6134 position: pointer_position,
6135 ..Default::default()
6136 }
6137 .to_platform_input(),
6138 cx,
6139 );
6140 })
6141 .unwrap();
6142 assert_eq!(*hover_transitions.borrow(), [true, false, true, false]);
6143 }
6144
6145 #[gpui::test]
6146 fn default_hover_listener_remains_hovered_during_stationary_mouse_press(
6147 cx: &mut TestAppContext,
6148 ) {
6149 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
6150 let window = cx.add_window({
6151 let hover_transitions = hover_transitions.clone();
6152 move |_, _| HoverListenerLayoutTestView {
6153 target_left: px(0.),
6154 hover_transitions,
6155 }
6156 });
6157 let any_window = AnyWindowHandle::from(window);
6158 let mouse_position = point(px(10.), px(10.));
6159
6160 cx.update_window(any_window, |_, window, cx| {
6161 window.draw(cx).clear(cx);
6162 window.simulate_mouse_move(mouse_position, cx);
6163 })
6164 .unwrap();
6165 assert_eq!(*hover_transitions.borrow(), [true]);
6166
6167 cx.update_window(any_window, |_, window, cx| {
6168 window.dispatch_event(
6169 MouseDownEvent {
6170 position: mouse_position,
6171 button: MouseButton::Left,
6172 modifiers: Default::default(),
6173 click_count: 1,
6174 first_mouse: false,
6175 }
6176 .to_platform_input(),
6177 cx,
6178 );
6179 window.draw(cx).clear(cx);
6180 })
6181 .unwrap();
6182 assert_eq!(*hover_transitions.borrow(), [true]);
6183
6184 cx.update_window(any_window, |_, window, cx| {
6185 window.dispatch_event(
6186 MouseUpEvent {
6187 position: mouse_position,
6188 button: MouseButton::Left,
6189 modifiers: Default::default(),
6190 click_count: 1,
6191 }
6192 .to_platform_input(),
6193 cx,
6194 );
6195 window.draw(cx).clear(cx);
6196 })
6197 .unwrap();
6198 assert_eq!(*hover_transitions.borrow(), [true]);
6199 }
6200
6201 struct HoverRegionView {
6202 events: Rc<RefCell<Vec<Option<&'static str>>>>,
6203 projections: Rc<Cell<usize>>,
6204 }
6205
6206 impl Render for HoverRegionView {
6207 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
6208 let events = self.events.clone();
6209 let projections = self.projections.clone();
6210 div().size_full().child(
6211 div()
6212 .id("hover-region-target")
6213 .ml(px(20.))
6214 .mt(px(20.))
6215 .size(px(40.))
6216 .on_hover_region(
6217 move |position, bounds| {
6218 projections.set(projections.get() + 1);
6219 let x = position.x - bounds.left();
6220 (x < px(15.))
6221 .then_some("left")
6222 .or_else(|| (x >= px(25.)).then_some("right"))
6223 },
6224 move |region, _, _| events.borrow_mut().push(*region),
6225 ),
6226 )
6227 }
6228 }
6229
6230 #[gpui::test]
6231 fn hover_region_transitions_once_and_stays_none_after_exit(cx: &mut TestAppContext) {
6232 let events = Rc::new(RefCell::new(Vec::new()));
6233 let projections = Rc::new(Cell::new(0));
6234 let window = cx.add_window({
6235 let events = events.clone();
6236 let projections = projections.clone();
6237 move |_, _| HoverRegionView {
6238 events,
6239 projections,
6240 }
6241 });
6242 let any_window = AnyWindowHandle::from(window);
6243
6244 let mut expected = Vec::new();
6245 for (x, transition) in [
6246 (25., Some(Some("left"))),
6247 (30., None),
6248 (40., Some(None)), (42., None),
6250 (50., Some(Some("right"))),
6251 (50., None),
6252 ] {
6253 cx.update_window(any_window, |_, window, cx| {
6254 window.simulate_mouse_move(point(px(x), px(25.)), cx);
6255 window.draw(cx).clear(cx);
6256 })
6257 .unwrap();
6258 if let Some(transition) = transition {
6259 expected.push(transition);
6260 }
6261 assert_eq!(*events.borrow(), expected, "x={x}");
6262 }
6263 let count = projections.get();
6264 cx.update_window(any_window, |_, window, cx| {
6265 window.dispatch_event(
6266 MouseExitEvent::default().to_platform_input(),
6268 cx,
6269 );
6270 window.draw(cx).clear(cx);
6271 window.dispatch_event(MouseExitEvent::default().to_platform_input(), cx);
6272 window.draw(cx).clear(cx);
6273 })
6274 .unwrap();
6275
6276 expected.push(None);
6277 assert_eq!(*events.borrow(), expected);
6278 assert_eq!(
6279 projections.get(),
6280 count,
6281 "exit and subsequent draws must not run the projection"
6282 );
6283 cx.update_window(any_window, |_, window, cx| {
6284 window.simulate_mouse_move(point(px(50.), px(25.)), cx);
6285 window.simulate_mouse_move(point(px(60.), px(25.)), cx);
6286 window.simulate_mouse_move(point(px(65.), px(25.)), cx);
6287 })
6288 .unwrap();
6289 expected.extend([Some("right"), None]);
6290 assert_eq!(
6291 *events.borrow(),
6292 expected,
6293 "reentry and half-open element bounds"
6294 );
6295 }
6296
6297 #[derive(Debug, PartialEq)]
6298 enum HoverRegionEvent {
6299 Number(Option<u8>),
6300 Flag(Option<bool>),
6301 }
6302
6303 struct MultipleHoverRegionsView {
6304 events: Rc<RefCell<Vec<HoverRegionEvent>>>,
6305 }
6306
6307 impl Render for MultipleHoverRegionsView {
6308 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
6309 let events = self.events.clone();
6310 let events_second = events.clone();
6311 div()
6312 .id("multiple-hover-regions")
6313 .size(px(40.))
6314 .on_hover_region(
6315 |position, _| (position.x < px(20.)).then_some(1u8),
6316 move |value, _, _| events.borrow_mut().push(HoverRegionEvent::Number(*value)),
6317 )
6318 .on_hover_region(
6319 |position, _| (position.x >= px(20.)).then_some(true),
6320 move |value, _, _| {
6321 events_second
6323 .borrow_mut()
6324 .push(HoverRegionEvent::Flag(*value));
6325 },
6326 )
6327 }
6328 }
6329
6330 #[gpui::test]
6331 fn hover_regions_keep_independent_state_when_rebuilt(cx: &mut TestAppContext) {
6332 let events = Rc::new(RefCell::new(Vec::new()));
6333 let view = cx.add_window({
6334 let events = events.clone();
6335 move |_, _| MultipleHoverRegionsView { events }
6336 });
6337 let any_window = AnyWindowHandle::from(view);
6338 cx.update_window(any_window, |_, window, cx| {
6339 window.draw(cx).clear(cx);
6340 window.simulate_mouse_move(point(px(5.), px(5.)), cx);
6341 window.simulate_mouse_move(point(px(25.), px(5.)), cx);
6342 })
6343 .unwrap();
6344 use HoverRegionEvent::{Flag, Number};
6345 assert_eq!(
6346 *events.borrow(),
6347 [Number(Some(1)), Number(None), Flag(Some(true))]
6348 );
6349 for _ in 0..3 {
6350 view.update(cx, |_, _, cx| cx.notify()).unwrap();
6351 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
6352 .unwrap();
6353 assert_eq!(
6354 *events.borrow(),
6355 [Number(Some(1)), Number(None), Flag(Some(true))]
6356 );
6357 }
6358 cx.update_window(any_window, |_, window, cx| {
6359 window.simulate_mouse_move(point(px(5.), px(5.)), cx);
6360 window.simulate_mouse_move(point(px(50.), px(5.)), cx);
6361 })
6362 .unwrap();
6363 assert_eq!(
6364 *events.borrow(),
6365 [
6366 Number(Some(1)),
6367 Number(None),
6368 Flag(Some(true)),
6369 Number(Some(1)),
6370 Flag(None),
6371 Number(None)
6372 ]
6373 );
6374 }
6375
6376 struct ChangingHoverRegionView {
6377 enabled: bool,
6378 alternate_type: bool,
6379 events: Rc<RefCell<Vec<HoverRegionEvent>>>,
6380 }
6381
6382 impl Render for ChangingHoverRegionView {
6383 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
6384 let events = self.events.clone();
6385 let target = div().id("changing-regions").size(px(40.));
6386 if !self.enabled {
6387 target
6388 } else if self.alternate_type {
6389 target.on_hover_region(
6390 |_, _| Some(true),
6391 move |value, _, _| events.borrow_mut().push(HoverRegionEvent::Flag(*value)),
6392 )
6393 } else {
6394 target.on_hover_region(
6395 |_, _| Some(1u8),
6396 move |value, _, _| events.borrow_mut().push(HoverRegionEvent::Number(*value)),
6397 )
6398 }
6399 }
6400 }
6401
6402 #[gpui::test]
6403 fn hover_region_registration_replaces_types_and_discards_removed_state(
6404 cx: &mut TestAppContext,
6405 ) {
6406 let events = Rc::new(RefCell::new(Vec::new()));
6407 let window = cx.add_window({
6408 let events = events.clone();
6409 move |_, _| ChangingHoverRegionView {
6410 enabled: true,
6411 alternate_type: false,
6412 events,
6413 }
6414 });
6415 let any_window = AnyWindowHandle::from(window);
6416 cx.update_window(any_window, |_, window, cx| {
6417 window.simulate_mouse_move(point(px(5.), px(5.)), cx);
6418 })
6419 .unwrap();
6420 assert_eq!(*events.borrow(), [HoverRegionEvent::Number(Some(1))]);
6421 window
6422 .update(cx, |view, _, cx| {
6423 view.alternate_type = true;
6424 cx.notify();
6425 })
6426 .unwrap();
6427 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
6428 .unwrap();
6429 assert_eq!(
6430 *events.borrow(),
6431 [
6432 HoverRegionEvent::Number(Some(1)),
6433 HoverRegionEvent::Flag(Some(true))
6434 ]
6435 );
6436 window
6437 .update(cx, |view, _, cx| {
6438 view.enabled = false;
6439 cx.notify();
6440 })
6441 .unwrap();
6442 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
6443 .unwrap();
6444 window
6445 .update(cx, |view, _, cx| {
6446 view.enabled = true;
6447 cx.notify();
6448 })
6449 .unwrap();
6450 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
6451 .unwrap();
6452 assert_eq!(
6453 *events.borrow(),
6454 [
6455 HoverRegionEvent::Number(Some(1)),
6456 HoverRegionEvent::Flag(Some(true)),
6457 HoverRegionEvent::Flag(Some(true))
6458 ]
6459 );
6460 }
6461
6462 struct TestTooltipView;
6463
6464 impl Render for TestTooltipView {
6465 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
6466 div().w(px(20.)).h(px(20.)).child("tooltip")
6467 }
6468 }
6469
6470 type CapturedActiveTooltip = Rc<RefCell<Option<Weak<RefCell<Option<ActiveTooltip>>>>>>;
6471
6472 struct TooltipCaptureElement {
6473 child: AnyElement,
6474 captured_active_tooltip: CapturedActiveTooltip,
6475 }
6476
6477 impl IntoElement for TooltipCaptureElement {
6478 type Element = Self;
6479
6480 fn into_element(self) -> Self::Element {
6481 self
6482 }
6483 }
6484
6485 impl Element for TooltipCaptureElement {
6486 type RequestLayoutState = ();
6487 type PrepaintState = ();
6488
6489 fn id(&self) -> Option<ElementId> {
6490 None
6491 }
6492
6493 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
6494 None
6495 }
6496
6497 fn request_layout(
6498 &mut self,
6499 _id: Option<&GlobalElementId>,
6500 _inspector_id: Option<&InspectorElementId>,
6501 window: &mut Window,
6502 cx: &mut App,
6503 ) -> (LayoutId, Self::RequestLayoutState) {
6504 (self.child.request_layout(window, cx), ())
6505 }
6506
6507 fn prepaint(
6508 &mut self,
6509 _id: Option<&GlobalElementId>,
6510 _inspector_id: Option<&InspectorElementId>,
6511 _bounds: Bounds<Pixels>,
6512 _request_layout: &mut Self::RequestLayoutState,
6513 window: &mut Window,
6514 cx: &mut App,
6515 ) -> Self::PrepaintState {
6516 self.child.prepaint(window, cx);
6517 }
6518
6519 fn paint(
6520 &mut self,
6521 _id: Option<&GlobalElementId>,
6522 _inspector_id: Option<&InspectorElementId>,
6523 _bounds: Bounds<Pixels>,
6524 _request_layout: &mut Self::RequestLayoutState,
6525 _prepaint: &mut Self::PrepaintState,
6526 window: &mut Window,
6527 cx: &mut App,
6528 ) {
6529 self.child.paint(window, cx);
6530 window.with_global_id("target".into(), |global_id, window| {
6531 window.with_element_state::<InteractiveElementState, _>(
6532 global_id,
6533 |state, _window| {
6534 let state = state.unwrap();
6535 *self.captured_active_tooltip.borrow_mut() =
6536 state.active_tooltip.as_ref().map(Rc::downgrade);
6537 ((), state)
6538 },
6539 )
6540 });
6541 }
6542 }
6543
6544 struct TooltipOwner {
6545 captured_active_tooltip: CapturedActiveTooltip,
6546 show_delay_override: Option<Duration>,
6547 }
6548
6549 impl Render for TooltipOwner {
6550 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
6551 TooltipCaptureElement {
6552 child: div()
6553 .size_full()
6554 .child(
6555 div()
6556 .id("target")
6557 .w(px(50.))
6558 .h(px(50.))
6559 .tooltip(|_, cx| cx.new(|_| TestTooltipView).into())
6560 .when_some(self.show_delay_override, |this, delay| {
6561 this.tooltip_show_delay(delay)
6562 }),
6563 )
6564 .into_any_element(),
6565 captured_active_tooltip: self.captured_active_tooltip.clone(),
6566 }
6567 }
6568 }
6569
6570 #[test]
6571 fn scroll_handle_aligns_wide_children_to_left_edge() {
6572 let handle = ScrollHandle::new();
6573 {
6574 let mut state = handle.0.borrow_mut();
6575 state.bounds = Bounds::new(point(px(0.), px(0.)), size(px(80.), px(20.)));
6576 state.child_bounds = vec![Bounds::new(point(px(25.), px(0.)), size(px(200.), px(20.)))];
6577 state.overflow.x = Overflow::Scroll;
6578 state.active_item = Some(ScrollActiveItem {
6579 index: 0,
6580 strategy: ScrollStrategy::default(),
6581 });
6582 }
6583
6584 handle.scroll_to_active_item();
6585
6586 assert_eq!(handle.offset().x, px(-25.));
6587 }
6588
6589 #[test]
6590 fn scroll_handle_aligns_tall_children_to_top_edge() {
6591 let handle = ScrollHandle::new();
6592 {
6593 let mut state = handle.0.borrow_mut();
6594 state.bounds = Bounds::new(point(px(0.), px(0.)), size(px(20.), px(80.)));
6595 state.child_bounds = vec![Bounds::new(point(px(0.), px(25.)), size(px(20.), px(200.)))];
6596 state.overflow.y = Overflow::Scroll;
6597 state.active_item = Some(ScrollActiveItem {
6598 index: 0,
6599 strategy: ScrollStrategy::default(),
6600 });
6601 }
6602
6603 handle.scroll_to_active_item();
6604
6605 assert_eq!(handle.offset().y, px(-25.));
6606 }
6607
6608 #[test]
6609 fn long_press_shows_tooltip_until_touch_ends() {
6610 let (mut test_app, any_window, captured_active_tooltip) = create_tooltip_owner_test(None);
6611 let active_tooltip = captured_active_tooltip
6612 .borrow()
6613 .clone()
6614 .unwrap()
6615 .upgrade()
6616 .unwrap();
6617 let touch_id = TouchId(1);
6618 let touch_position = point(px(49.), px(10.));
6619
6620 test_app
6621 .update_window(any_window, |_, window, cx| {
6622 window.dispatch_event(
6623 TouchEvent {
6624 id: touch_id,
6625 phase: TouchPhase::Started,
6626 position: touch_position,
6627 predicted_position: None,
6628 force: None,
6629 }
6630 .to_platform_input(),
6631 cx,
6632 );
6633 })
6634 .unwrap();
6635 test_app
6636 .dispatcher
6637 .advance_clock(GestureTuning::default().long_press_duration + Duration::from_millis(1));
6638 test_app.run_until_parked();
6639
6640 assert!(matches!(
6641 active_tooltip.borrow().as_ref(),
6642 Some(ActiveTooltip::Visible { .. })
6643 ));
6644
6645 let moved_position = point(px(55.), px(10.));
6646 test_app
6647 .update_window(any_window, |_, window, cx| {
6648 window.dispatch_event(
6649 TouchEvent {
6650 id: touch_id,
6651 phase: TouchPhase::Moved,
6652 position: moved_position,
6653 predicted_position: None,
6654 force: None,
6655 }
6656 .to_platform_input(),
6657 cx,
6658 );
6659 window.draw(cx).clear(cx);
6660 })
6661 .unwrap();
6662 assert!(matches!(
6663 active_tooltip.borrow().as_ref(),
6664 Some(ActiveTooltip::Visible { .. })
6665 ));
6666
6667 test_app
6668 .update_window(any_window, |_, window, cx| {
6669 window.dispatch_event(
6670 TouchEvent {
6671 id: touch_id,
6672 phase: TouchPhase::Ended,
6673 position: moved_position,
6674 predicted_position: None,
6675 force: None,
6676 }
6677 .to_platform_input(),
6678 cx,
6679 );
6680 })
6681 .unwrap();
6682
6683 assert!(active_tooltip.borrow().is_none());
6684 }
6685
6686 fn setup_tooltip_owner_test(
6687 show_delay_override: Option<Duration>,
6688 ) -> (
6689 TestAppContext,
6690 crate::AnyWindowHandle,
6691 CapturedActiveTooltip,
6692 ) {
6693 let (mut test_app, any_window, captured_active_tooltip) =
6694 create_tooltip_owner_test(show_delay_override);
6695
6696 test_app
6697 .update_window(any_window, |_, window, cx| {
6698 window.dispatch_event(
6699 MouseMoveEvent {
6700 position: point(px(10.), px(10.)),
6701 modifiers: Default::default(),
6702 pressed_button: None,
6703 }
6704 .to_platform_input(),
6705 cx,
6706 );
6707 })
6708 .unwrap();
6709
6710 test_app
6711 .update_window(any_window, |_, window, cx| {
6712 window.draw(cx).clear(cx);
6713 })
6714 .unwrap();
6715
6716 (test_app, any_window, captured_active_tooltip)
6717 }
6718
6719 fn create_tooltip_owner_test(
6720 show_delay_override: Option<Duration>,
6721 ) -> (
6722 TestAppContext,
6723 crate::AnyWindowHandle,
6724 CapturedActiveTooltip,
6725 ) {
6726 let mut test_app = TestAppContext::single();
6727 let captured_active_tooltip: CapturedActiveTooltip = Rc::new(RefCell::new(None));
6728 let window = test_app.add_window({
6729 let captured_active_tooltip = captured_active_tooltip.clone();
6730 move |_, _| TooltipOwner {
6731 captured_active_tooltip,
6732 show_delay_override,
6733 }
6734 });
6735 let any_window = window.into();
6736
6737 test_app
6738 .update_window(any_window, |_, window, cx| {
6739 window.draw(cx).clear(cx);
6740 })
6741 .unwrap();
6742
6743 (test_app, any_window, captured_active_tooltip)
6744 }
6745
6746 #[test]
6747 fn tooltip_waiting_for_show_is_released_when_its_owner_disappears() {
6748 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
6749
6750 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
6751 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
6752 assert!(matches!(
6753 active_tooltip.borrow().as_ref(),
6754 Some(ActiveTooltip::WaitingForShow { .. })
6755 ));
6756
6757 test_app
6758 .update_window(any_window, |_, window, _| {
6759 window.remove_window();
6760 })
6761 .unwrap();
6762 test_app.run_until_parked();
6763 drop(active_tooltip);
6764
6765 assert!(weak_active_tooltip.upgrade().is_none());
6766 }
6767
6768 #[test]
6769 fn tooltip_respects_custom_show_delay() {
6770 let extra_delay = Duration::from_secs(1);
6771 let show_delay_override = DEFAULT_TOOLTIP_SHOW_DELAY + extra_delay;
6772 let (mut test_app, _any_window, captured_active_tooltip) =
6773 setup_tooltip_owner_test(Some(show_delay_override));
6774
6775 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
6776 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
6777
6778 test_app
6779 .dispatcher
6780 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
6781 test_app.run_until_parked();
6782
6783 assert!(matches!(
6784 active_tooltip.borrow().as_ref(),
6785 Some(ActiveTooltip::WaitingForShow { .. })
6786 ));
6787
6788 test_app.dispatcher.advance_clock(extra_delay);
6789 test_app.run_until_parked();
6790
6791 assert!(matches!(
6792 active_tooltip.borrow().as_ref(),
6793 Some(ActiveTooltip::Visible { .. })
6794 ));
6795 }
6796
6797 #[test]
6798 fn tooltip_is_released_when_its_owner_disappears() {
6799 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
6800
6801 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
6802 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
6803
6804 test_app
6805 .dispatcher
6806 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
6807 test_app.run_until_parked();
6808
6809 assert!(matches!(
6810 active_tooltip.borrow().as_ref(),
6811 Some(ActiveTooltip::Visible { .. })
6812 ));
6813
6814 test_app
6815 .update_window(any_window, |_, window, _| {
6816 window.remove_window();
6817 })
6818 .unwrap();
6819 test_app.run_until_parked();
6820 drop(active_tooltip);
6821
6822 assert!(weak_active_tooltip.upgrade().is_none());
6823 }
6824
6825 #[test]
6826 fn tooltip_hides_after_mouse_leaves_origin() {
6827 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
6828
6829 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
6830 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
6831
6832 test_app
6833 .dispatcher
6834 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
6835 test_app.run_until_parked();
6836
6837 assert!(matches!(
6838 active_tooltip.borrow().as_ref(),
6839 Some(ActiveTooltip::Visible { .. })
6840 ));
6841
6842 test_app
6843 .update_window(any_window, |_, window, cx| {
6844 window.dispatch_event(
6845 MouseMoveEvent {
6846 position: point(px(75.), px(75.)),
6847 modifiers: Default::default(),
6848 pressed_button: None,
6849 }
6850 .to_platform_input(),
6851 cx,
6852 );
6853 })
6854 .unwrap();
6855
6856 assert!(active_tooltip.borrow().is_none());
6857 }
6858
6859 struct MouseDownOutOwner {
6860 mouse_down_out_count: Rc<RefCell<usize>>,
6861 }
6862
6863 impl Render for MouseDownOutOwner {
6864 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
6865 let mouse_down_out_count = self.mouse_down_out_count.clone();
6866 div()
6867 .size_full()
6868 .child(div().id("target").w(px(50.)).h(px(50.)).on_mouse_down_out(
6869 move |_, _, _| {
6870 *mouse_down_out_count.borrow_mut() += 1;
6871 },
6872 ))
6873 }
6874 }
6875
6876 #[test]
6877 fn mouse_down_out_is_suppressed_while_window_prompt_is_active() {
6878 let mut test_app = TestAppContext::single();
6879 let mouse_down_out_count = Rc::new(RefCell::new(0));
6880 let window = test_app.add_window({
6881 let mouse_down_out_count = mouse_down_out_count.clone();
6882 move |_, _| MouseDownOutOwner {
6883 mouse_down_out_count,
6884 }
6885 });
6886 let any_window: AnyWindowHandle = window.into();
6887
6888 fn dispatch_mouse_down_outside_target(
6889 test_app: &mut TestAppContext,
6890 any_window: AnyWindowHandle,
6891 ) {
6892 test_app
6893 .update_window(any_window, |_, window, cx| {
6894 window.dispatch_event(
6895 MouseDownEvent {
6896 position: point(px(75.), px(75.)),
6897 button: MouseButton::Left,
6898 modifiers: Default::default(),
6899 click_count: 1,
6900 first_mouse: false,
6901 }
6902 .to_platform_input(),
6903 cx,
6904 );
6905 })
6906 .unwrap();
6907 }
6908
6909 test_app
6910 .update_window(any_window, |_, window, cx| {
6911 window.draw(cx).clear(cx);
6912 })
6913 .unwrap();
6914
6915 dispatch_mouse_down_outside_target(&mut test_app, any_window);
6916 assert_eq!(
6917 *mouse_down_out_count.borrow(),
6918 1,
6919 "mouse down outside the element should fire mouse-down-out listeners"
6920 );
6921
6922 test_app
6923 .update_window(any_window, |_, window, cx| {
6924 cx.set_prompt_builder(crate::fallback_prompt_renderer);
6925 let _receiver =
6926 window.prompt(crate::PromptLevel::Warning, "message", None, &["Ok"], cx);
6927 assert!(window.has_active_prompt());
6928 window.draw(cx).clear(cx);
6929 })
6930 .unwrap();
6931
6932 dispatch_mouse_down_outside_target(&mut test_app, any_window);
6933 assert_eq!(
6934 *mouse_down_out_count.borrow(),
6935 1,
6936 "mouse down over an active prompt should not fire mouse-down-out listeners"
6937 );
6938 }
6939
6940 #[test]
6941 fn touch_tap_out_fires_once_after_release() {
6942 let mut test_app = TestAppContext::single();
6943 let mouse_down_out_count = Rc::new(RefCell::new(0));
6944 let window = test_app.add_window({
6945 let mouse_down_out_count = mouse_down_out_count.clone();
6946 move |_, _| MouseDownOutOwner {
6947 mouse_down_out_count,
6948 }
6949 });
6950 let any_window: AnyWindowHandle = window.into();
6951
6952 test_app
6953 .update_window(any_window, |_, window, cx| {
6954 window.draw(cx).clear(cx);
6955 let started = TouchEvent {
6956 id: TouchId(1),
6957 phase: TouchPhase::Started,
6958 position: point(px(75.), px(75.)),
6959 force: None,
6960 predicted_position: None,
6961 };
6962 window.dispatch_event(started.clone().to_platform_input(), cx);
6963 assert_eq!(*mouse_down_out_count.borrow(), 0);
6964 window.dispatch_event(
6965 TouchEvent {
6966 phase: TouchPhase::Ended,
6967 ..started
6968 }
6969 .to_platform_input(),
6970 cx,
6971 );
6972 })
6973 .unwrap();
6974
6975 assert_eq!(
6976 *mouse_down_out_count.borrow(),
6977 1,
6978 "a touch tap outside the element should use mouse-down-out listeners"
6979 );
6980 }
6981
6982 #[crate::test]
6983 fn touch_pan_and_cancel_do_not_fire_mouse_down_out(cx: &mut TestAppContext) {
6984 let mouse_down_out_count = Rc::new(RefCell::new(0));
6985 let window = cx.add_window({
6986 let mouse_down_out_count = mouse_down_out_count.clone();
6987 move |_, _| MouseDownOutOwner {
6988 mouse_down_out_count,
6989 }
6990 });
6991 cx.update_window(window.into(), |_, window, cx| {
6992 window.draw(cx).clear(cx);
6993 for (id, samples) in [
6994 (
6995 TouchId(1),
6996 [
6997 (TouchPhase::Started, 100.),
6998 (TouchPhase::Moved, 75.),
6999 (TouchPhase::Ended, 75.),
7000 ],
7001 ),
7002 (
7003 TouchId(2),
7004 [
7005 (TouchPhase::Started, 100.),
7006 (TouchPhase::Moved, 100.),
7007 (TouchPhase::Cancelled, 100.),
7008 ],
7009 ),
7010 ] {
7011 for (phase, vertical_position) in samples {
7012 window.dispatch_event(
7013 TouchEvent {
7014 id,
7015 phase,
7016 position: point(px(75.), px(vertical_position)),
7017 predicted_position: None,
7018 force: None,
7019 }
7020 .to_platform_input(),
7021 cx,
7022 );
7023 }
7024 }
7025 })
7026 .unwrap();
7027 assert_eq!(*mouse_down_out_count.borrow(), 0);
7028 }
7029
7030 #[test]
7031 fn test_accessibility_id_builder_writes_author_id() {
7032 let mut element = div()
7033 .id("buffer-font-size")
7034 .accessibility_id("settings.buffer-font-size");
7035 let mut node = accesskit::Node::new(accesskit::Role::SpinButton);
7036
7037 element.interactivity().write_a11y_info(&mut node);
7038
7039 assert_eq!(node.author_id(), Some("settings.buffer-font-size"));
7040 }
7041
7042 #[test]
7043 fn test_write_a11y_info_string_and_numeric_properties() {
7044 let mut interactivity = Interactivity::default();
7045 interactivity.aria.author_id = Some("settings.buffer-font-size".into());
7046 interactivity.aria.label = Some("Buffer Font Size".into());
7047 interactivity.aria.value = Some("15".into());
7048 interactivity.aria.placeholder = Some("Search".into());
7049 interactivity.aria.numeric_value = Some(15.0);
7050 interactivity.aria.min_numeric_value = Some(6.0);
7051 interactivity.aria.max_numeric_value = Some(72.0);
7052 interactivity.aria.numeric_value_step = Some(1.0);
7053
7054 let mut node = accesskit::Node::new(accesskit::Role::SpinButton);
7055 interactivity.write_a11y_info(&mut node);
7056
7057 assert_eq!(node.author_id(), Some("settings.buffer-font-size"));
7058 assert_eq!(node.label(), Some("Buffer Font Size"));
7059 assert_eq!(node.value(), Some("15"));
7060 assert_eq!(node.placeholder(), Some("Search"));
7061 assert_eq!(node.numeric_value(), Some(15.0));
7062 assert_eq!(node.min_numeric_value(), Some(6.0));
7063 assert_eq!(node.max_numeric_value(), Some(72.0));
7064 assert_eq!(node.numeric_value_step(), Some(1.0));
7065 }
7066
7067 #[test]
7068 fn test_write_a11y_info_disabled_modal_and_live_region() {
7069 let mut interactivity = Interactivity::default();
7070 interactivity.aria.disabled = Some(true);
7071 interactivity.aria.modal = Some(true);
7072 interactivity.aria.live = Some(accesskit::Live::Assertive);
7073 interactivity.aria.live_atomic = Some(true);
7074
7075 let mut node = accesskit::Node::new(accesskit::Role::Dialog);
7076 interactivity.write_a11y_info(&mut node);
7077
7078 assert!(node.is_disabled());
7079 assert!(node.is_modal());
7080 assert_eq!(node.live(), Some(accesskit::Live::Assertive));
7081 assert!(node.is_live_atomic());
7082 }
7083
7084 #[test]
7085 fn test_write_a11y_info_disabled_and_modal_false_clear_the_flags() {
7086 let mut interactivity = Interactivity::default();
7091 interactivity.aria.disabled = Some(false);
7092 interactivity.aria.modal = Some(false);
7093 interactivity.aria.live_atomic = Some(false);
7094
7095 let mut node = accesskit::Node::new(accesskit::Role::Dialog);
7096 node.set_disabled();
7097 node.set_modal();
7098 node.set_live_atomic();
7099
7100 interactivity.write_a11y_info(&mut node);
7101
7102 assert!(!node.is_disabled());
7103 assert!(!node.is_modal());
7104 assert!(!node.is_live_atomic());
7105 }
7106
7107 struct KeyboardActivationTest {
7110 focus_a: FocusHandle,
7111 focus_b: FocusHandle,
7112 clicks: Rc<RefCell<Vec<&'static str>>>,
7113 }
7114
7115 impl Render for KeyboardActivationTest {
7116 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
7117 let clicks_a = self.clicks.clone();
7118 let clicks_b = self.clicks.clone();
7119 div()
7120 .size_full()
7121 .child(
7122 div()
7123 .id("a")
7124 .w(px(50.))
7125 .h(px(50.))
7126 .track_focus(&self.focus_a)
7127 .on_click(move |_, _, _| clicks_a.borrow_mut().push("a")),
7128 )
7129 .child(
7130 div()
7131 .id("b")
7132 .w(px(50.))
7133 .h(px(50.))
7134 .track_focus(&self.focus_b)
7135 .on_click(move |_, _, _| clicks_b.borrow_mut().push("b")),
7136 )
7137 }
7138 }
7139
7140 fn setup_keyboard_activation_test() -> (
7141 TestAppContext,
7142 AnyWindowHandle,
7143 Rc<RefCell<Vec<&'static str>>>,
7144 FocusHandle,
7145 FocusHandle,
7146 ) {
7147 let mut cx = TestAppContext::single();
7148 let (focus_a, focus_b) = cx.update(|cx| (cx.focus_handle(), cx.focus_handle()));
7149 let clicks: Rc<RefCell<Vec<&'static str>>> = Rc::new(RefCell::new(Vec::new()));
7150 let window = cx.add_window({
7151 let focus_a = focus_a.clone();
7152 let focus_b = focus_b.clone();
7153 let clicks = clicks.clone();
7154 move |_, _| KeyboardActivationTest {
7155 focus_a,
7156 focus_b,
7157 clicks,
7158 }
7159 });
7160 (cx, window.into(), clicks, focus_a, focus_b)
7161 }
7162
7163 fn focus_and_draw(cx: &mut TestAppContext, window: AnyWindowHandle, handle: &FocusHandle) {
7166 cx.update_window(window, |_, window, cx| window.focus(handle, cx))
7167 .unwrap();
7168 cx.run_until_parked();
7169 cx.update_window(window, |_, window, cx| {
7170 window.draw(cx).clear(cx);
7171 })
7172 .unwrap();
7173 }
7174
7175 fn key_down(cx: &mut TestAppContext, window: AnyWindowHandle, key: &str) {
7176 let keystroke = Keystroke::parse(key).unwrap();
7177 cx.update_window(window, |_, window, cx| {
7178 window.dispatch_event(
7179 KeyDownEvent {
7180 keystroke,
7181 is_held: false,
7182 prefer_character_input: false,
7183 }
7184 .to_platform_input(),
7185 cx,
7186 );
7187 })
7188 .unwrap();
7189 }
7190
7191 fn key_up(cx: &mut TestAppContext, window: AnyWindowHandle, key: &str) {
7192 let keystroke = Keystroke::parse(key).unwrap();
7193 cx.update_window(window, |_, window, cx| {
7194 window.dispatch_event(KeyUpEvent { keystroke }.to_platform_input(), cx);
7195 })
7196 .unwrap();
7197 }
7198
7199 #[test]
7201 fn keyboard_activation_fires_click_on_same_element() {
7202 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
7203
7204 focus_and_draw(&mut cx, window, &focus_a);
7205 key_down(&mut cx, window, "enter");
7206 key_up(&mut cx, window, "enter");
7207
7208 assert_eq!(*clicks.borrow(), vec!["a"]);
7209 }
7210
7211 #[test]
7216 fn keyboard_activation_does_not_leak_across_focus_change() {
7217 let (mut cx, window, clicks, focus_a, focus_b) = setup_keyboard_activation_test();
7218
7219 focus_and_draw(&mut cx, window, &focus_a);
7221 key_down(&mut cx, window, "enter");
7222
7223 focus_and_draw(&mut cx, window, &focus_b);
7225 key_up(&mut cx, window, "enter");
7226
7227 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
7230 }
7231
7232 #[test]
7237 fn keyboard_activation_does_not_leak_when_focus_returns() {
7238 let (mut cx, window, clicks, focus_a, focus_b) = setup_keyboard_activation_test();
7239
7240 focus_and_draw(&mut cx, window, &focus_a);
7242 key_down(&mut cx, window, "enter");
7243
7244 focus_and_draw(&mut cx, window, &focus_b);
7247 focus_and_draw(&mut cx, window, &focus_a);
7248 key_up(&mut cx, window, "enter");
7249
7250 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
7252 }
7253
7254 #[test]
7259 fn keyboard_activation_cleared_by_intervening_key_release() {
7260 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
7261
7262 focus_and_draw(&mut cx, window, &focus_a);
7263 key_down(&mut cx, window, "escape");
7264 key_down(&mut cx, window, "space");
7265 key_up(&mut cx, window, "escape");
7266 key_up(&mut cx, window, "space");
7267
7268 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
7269 }
7270
7271 #[test]
7275 fn keyboard_activation_does_not_distinguish_space_and_enter() {
7276 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
7277
7278 focus_and_draw(&mut cx, window, &focus_a);
7279 key_down(&mut cx, window, "space");
7280 key_up(&mut cx, window, "enter");
7281
7282 assert_eq!(*clicks.borrow(), vec!["a"]);
7283 }
7284
7285 #[test]
7288 fn keyboard_activation_cleared_by_intervening_keydown() {
7289 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
7290
7291 focus_and_draw(&mut cx, window, &focus_a);
7292 key_down(&mut cx, window, "enter");
7293 key_down(&mut cx, window, "a");
7294 key_up(&mut cx, window, "enter");
7295
7296 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
7297 }
7298
7299 #[test]
7302 fn keyboard_activation_ignores_modified_keys() {
7303 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
7304
7305 focus_and_draw(&mut cx, window, &focus_a);
7306 key_down(&mut cx, window, "cmd-enter");
7307 key_up(&mut cx, window, "cmd-enter");
7308
7309 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
7310 }
7311
7312 struct TabGroupFocus {
7316 group_a: FocusHandle,
7317 item_a: FocusHandle,
7318 group_b: FocusHandle,
7319 item_b: FocusHandle,
7320 }
7321
7322 impl Render for TabGroupFocus {
7323 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
7324 fn group(container: &FocusHandle, item: &FocusHandle) -> Div {
7325 div()
7326 .track_focus(container)
7327 .tab_group()
7328 .child(div().track_focus(item))
7329 }
7330 div()
7331 .child(group(&self.group_a, &self.item_a))
7332 .child(group(&self.group_b, &self.item_b))
7333 }
7334 }
7335
7336 #[test]
7340 fn focus_next_from_tab_group_container_enters_that_group() {
7341 let mut cx = TestAppContext::single();
7342 let (group_a, item_a, group_b, item_b) = cx.update(|cx| {
7343 (
7344 cx.focus_handle(),
7345 cx.focus_handle().tab_stop(true),
7346 cx.focus_handle(),
7347 cx.focus_handle().tab_stop(true),
7348 )
7349 });
7350 let window: AnyWindowHandle = cx
7351 .add_window({
7352 let (group_a, item_a, group_b, item_b) =
7353 (group_a, item_a, group_b.clone(), item_b.clone());
7354 move |_, _| TabGroupFocus {
7355 group_a,
7356 item_a,
7357 group_b,
7358 item_b,
7359 }
7360 })
7361 .into();
7362 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
7363 .unwrap();
7364
7365 let focused = cx
7367 .update_window(window, |_, window, cx| {
7368 window.focus(&group_b, cx);
7369 window.focus_next(cx);
7370 window.focused(cx).map(|handle| handle.id)
7371 })
7372 .unwrap();
7373
7374 assert_eq!(focused, Some(item_b.id));
7375 }
7376
7377 #[gpui::test]
7378 fn test_fractional_padding_does_not_make_a_fitting_container_scrollable(
7379 cx: &mut TestAppContext,
7380 ) {
7381 struct PaddedContainer {
7382 scroll_handle: ScrollHandle,
7383 }
7384
7385 impl Render for PaddedContainer {
7386 fn render(
7387 &mut self,
7388 _window: &mut Window,
7389 _cx: &mut Context<Self>,
7390 ) -> impl IntoElement {
7391 div().size_full().child(
7394 div()
7395 .id("container")
7396 .h(px(50.))
7397 .w(px(100.))
7398 .py(px(4.25))
7399 .overflow_y_scroll()
7400 .track_scroll(&self.scroll_handle)
7401 .child(div().w_full().h(px(42.))),
7402 )
7403 }
7404 }
7405
7406 let scroll_handle = ScrollHandle::new();
7407 let window: AnyWindowHandle = cx
7408 .add_window({
7409 let scroll_handle = scroll_handle.clone();
7410 move |_, _| PaddedContainer { scroll_handle }
7411 })
7412 .into();
7413 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
7414 .unwrap();
7415
7416 assert_eq!(scroll_handle.max_offset().y, px(0.));
7417 }
7418
7419 struct ContentSizedGrid;
7420
7421 impl Render for ContentSizedGrid {
7422 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
7423 let widths = [px(100.), px(200.), px(50.)];
7424 div().size_full().child(
7425 div()
7426 .w_full()
7427 .grid()
7428 .grid_cols_max_content(widths.len() as u16)
7429 .children(widths.into_iter().enumerate().map(|(index, width)| {
7430 div()
7431 .debug_selector(move || format!("cell-{index}"))
7432 .w(width)
7433 .h(px(10.))
7434 })),
7435 )
7436 }
7437 }
7438
7439 #[gpui::test]
7440 fn grid_cols_max_content_sizes_columns_to_their_content(cx: &mut TestAppContext) {
7441 let window = cx.add_window(|_, _| ContentSizedGrid);
7442 cx.update_window(window.into(), |_, window, cx| window.draw(cx).clear(cx))
7443 .unwrap();
7444
7445 let mut bounds = |selector: &'static str| {
7446 cx.update_window(window.into(), |_, window, _| {
7447 window.rendered_frame.debug_bounds.get(selector).copied()
7448 })
7449 .unwrap()
7450 .unwrap_or_else(|| panic!("{selector} was not rendered"))
7451 };
7452
7453 assert_eq!(bounds("cell-0").origin.x, px(0.));
7454 assert_eq!(bounds("cell-1").origin.x, px(100.));
7455 assert_eq!(bounds("cell-2").origin.x, px(300.));
7456 }
7457}