1use crate::PinchEvent;
16use crate::collections::HashMap;
17use crate::refineable::Refineable;
18use crate::rgpui_util::ResultExt;
19use crate::{
20 Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent, DispatchPhase,
21 Display, Element, ElementId, Entity, EntityId, FocusHandle, Global, GlobalElementId, Hitbox,
22 HitboxBehavior, HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent,
23 KeyUpEvent, KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent, MouseButton,
24 MouseClickEvent, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MousePressureEvent,
25 MouseUpEvent, Overflow, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString,
26 Size, Style, StyleRefinement, Styled, Task, TooltipId, Visibility, Window, WindowControlArea,
27 point, px, size,
28};
29use smallvec::SmallVec;
30use stacksafe::{StackSafe, stacksafe};
31use std::{
32 any::{Any, TypeId},
33 cell::RefCell,
34 cmp::Ordering,
35 fmt::Debug,
36 marker::PhantomData,
37 mem,
38 rc::Rc,
39 sync::Arc,
40 time::Duration,
41};
42
43use super::ImageCacheProvider;
44
45const DRAG_THRESHOLD: f64 = 2.;
46const DEFAULT_TOOLTIP_SHOW_DELAY: Duration = Duration::from_millis(500);
47const HOVERABLE_TOOLTIP_HIDE_DELAY: Duration = Duration::from_millis(500);
48
49pub struct GroupStyle {
51 pub group: SharedString,
53
54 pub style: Box<StyleRefinement>,
56}
57
58pub struct DragMoveEvent<T> {
60 pub event: MouseMoveEvent,
62
63 pub bounds: Bounds<Pixels>,
65 drag: PhantomData<T>,
66 dragged_item: Arc<dyn Any>,
67}
68
69impl<T: 'static> DragMoveEvent<T> {
70 pub fn drag<'b>(&self, cx: &'b App) -> &'b T {
72 cx.active_drag
73 .as_ref()
74 .and_then(|drag| drag.value.downcast_ref::<T>())
75 .expect("DragMoveEvent is only valid when the stored active drag is of the same type.")
76 }
77
78 pub fn dragged_item(&self) -> &dyn Any {
80 self.dragged_item.as_ref()
81 }
82}
83
84impl Interactivity {
85 #[cfg(any(feature = "inspector", debug_assertions))]
87 #[track_caller]
88 pub fn new() -> Interactivity {
89 Interactivity {
90 source_location: Some(core::panic::Location::caller()),
91 ..Default::default()
92 }
93 }
94
95 #[cfg(not(any(feature = "inspector", debug_assertions)))]
97 pub fn new() -> Interactivity {
98 Interactivity::default()
99 }
100
101 pub fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
103 #[cfg(any(feature = "inspector", debug_assertions))]
104 {
105 self.source_location
106 }
107
108 #[cfg(not(any(feature = "inspector", debug_assertions)))]
109 {
110 None
111 }
112 }
113
114 pub fn on_mouse_down(
119 &mut self,
120 button: MouseButton,
121 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
122 ) {
123 self.mouse_down_listeners
124 .push(Box::new(move |event, phase, hitbox, window, cx| {
125 if phase == DispatchPhase::Bubble
126 && event.button == button
127 && hitbox.is_hovered(window)
128 {
129 (listener)(event, window, cx)
130 }
131 }));
132 }
133
134 pub fn capture_any_mouse_down(
139 &mut self,
140 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
141 ) {
142 self.mouse_down_listeners
143 .push(Box::new(move |event, phase, hitbox, window, cx| {
144 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
145 (listener)(event, window, cx)
146 }
147 }));
148 }
149
150 pub fn on_any_mouse_down(
155 &mut self,
156 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
157 ) {
158 self.mouse_down_listeners
159 .push(Box::new(move |event, phase, hitbox, window, cx| {
160 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
161 (listener)(event, window, cx)
162 }
163 }));
164 }
165
166 pub fn on_mouse_pressure(
171 &mut self,
172 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
173 ) {
174 self.mouse_pressure_listeners
175 .push(Box::new(move |event, phase, hitbox, window, cx| {
176 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
177 (listener)(event, window, cx)
178 }
179 }));
180 }
181
182 pub fn capture_mouse_pressure(
187 &mut self,
188 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
189 ) {
190 self.mouse_pressure_listeners
191 .push(Box::new(move |event, phase, hitbox, window, cx| {
192 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
193 (listener)(event, window, cx)
194 }
195 }));
196 }
197
198 pub fn on_mouse_up(
203 &mut self,
204 button: MouseButton,
205 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
206 ) {
207 self.mouse_up_listeners
208 .push(Box::new(move |event, phase, hitbox, window, cx| {
209 if phase == DispatchPhase::Bubble
210 && event.button == button
211 && hitbox.is_hovered(window)
212 {
213 (listener)(event, window, cx)
214 }
215 }));
216 }
217
218 pub fn capture_any_mouse_up(
223 &mut self,
224 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
225 ) {
226 self.mouse_up_listeners
227 .push(Box::new(move |event, phase, hitbox, window, cx| {
228 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
229 (listener)(event, window, cx)
230 }
231 }));
232 }
233
234 pub fn on_any_mouse_up(
239 &mut self,
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 && hitbox.is_hovered(window) {
245 (listener)(event, window, cx)
246 }
247 }));
248 }
249
250 pub fn on_mouse_down_out(
255 &mut self,
256 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
257 ) {
258 self.mouse_down_listeners
259 .push(Box::new(move |event, phase, hitbox, window, cx| {
260 if phase == DispatchPhase::Capture && !hitbox.contains(&window.mouse_position()) {
261 (listener)(event, window, cx)
262 }
263 }));
264 }
265
266 pub fn on_mouse_up_out(
271 &mut self,
272 button: MouseButton,
273 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
274 ) {
275 self.mouse_up_listeners
276 .push(Box::new(move |event, phase, hitbox, window, cx| {
277 if phase == DispatchPhase::Capture
278 && event.button == button
279 && !hitbox.is_hovered(window)
280 {
281 (listener)(event, window, cx);
282 }
283 }));
284 }
285
286 pub fn on_mouse_move(
291 &mut self,
292 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
293 ) {
294 self.mouse_move_listeners
295 .push(Box::new(move |event, phase, hitbox, window, cx| {
296 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
297 (listener)(event, window, cx);
298 }
299 }));
300 }
301
302 pub fn on_mouse_exit(
307 &mut self,
308 listener: impl Fn(&MouseExitEvent, &mut Window, &mut App) + 'static,
309 ) {
310 self.mouse_exit_listeners
311 .push(Box::new(move |event, phase, hitbox, window, cx| {
312 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
313 (listener)(event, window, cx);
314 }
315 }));
316 }
317
318 pub fn on_drag_move<T>(
326 &mut self,
327 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
328 ) where
329 T: 'static,
330 {
331 self.mouse_move_listeners
332 .push(Box::new(move |event, phase, hitbox, window, cx| {
333 if phase == DispatchPhase::Capture
334 && let Some(drag) = &cx.active_drag
335 && drag.value.as_ref().type_id() == TypeId::of::<T>()
336 {
337 (listener)(
338 &DragMoveEvent {
339 event: event.clone(),
340 bounds: hitbox.bounds,
341 drag: PhantomData,
342 dragged_item: Arc::clone(&drag.value),
343 },
344 window,
345 cx,
346 );
347 }
348 }));
349 }
350
351 pub fn on_scroll_wheel(
356 &mut self,
357 listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
358 ) {
359 self.scroll_wheel_listeners
360 .push(Box::new(move |event, phase, hitbox, window, cx| {
361 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
362 (listener)(event, window, cx);
363 }
364 }));
365 }
366
367 pub fn on_pinch(&mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) {
371 self.pinch_listeners
372 .push(Box::new(move |event, phase, hitbox, window, cx| {
373 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
374 (listener)(event, window, cx);
375 }
376 }));
377 }
378
379 pub fn capture_pinch(
383 &mut self,
384 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
385 ) {
386 self.pinch_listeners
387 .push(Box::new(move |event, phase, _hitbox, window, cx| {
388 if phase == DispatchPhase::Capture {
389 (listener)(event, window, cx);
390 } else {
391 cx.propagate();
392 }
393 }));
394 }
395
396 pub fn capture_action<A: Action>(
401 &mut self,
402 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
403 ) {
404 self.action_listeners.push((
405 TypeId::of::<A>(),
406 Box::new(move |action, phase, window, cx| {
407 let action = action.downcast_ref().unwrap();
408 if phase == DispatchPhase::Capture {
409 (listener)(action, window, cx)
410 } else {
411 cx.propagate();
412 }
413 }),
414 ));
415 }
416
417 #[track_caller]
422 pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Window, &mut App) + 'static) {
423 self.action_listeners.push((
424 TypeId::of::<A>(),
425 Box::new(move |action, phase, window, cx| {
426 let action = action.downcast_ref().unwrap();
427 if phase == DispatchPhase::Bubble {
428 (listener)(action, window, cx)
429 }
430 }),
431 ));
432 }
433
434 pub fn on_boxed_action(
440 &mut self,
441 action: &dyn Action,
442 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
443 ) {
444 let action = action.boxed_clone();
445 self.action_listeners.push((
446 (*action).type_id(),
447 Box::new(move |_, phase, window, cx| {
448 if phase == DispatchPhase::Bubble {
449 (listener)(&*action, window, cx)
450 }
451 }),
452 ));
453 }
454
455 pub fn on_key_down(
460 &mut self,
461 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
462 ) {
463 self.key_down_listeners
464 .push(Box::new(move |event, phase, window, cx| {
465 if phase == DispatchPhase::Bubble {
466 (listener)(event, window, cx)
467 }
468 }));
469 }
470
471 pub fn capture_key_down(
476 &mut self,
477 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
478 ) {
479 self.key_down_listeners
480 .push(Box::new(move |event, phase, window, cx| {
481 if phase == DispatchPhase::Capture {
482 listener(event, window, cx)
483 }
484 }));
485 }
486
487 pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static) {
492 self.key_up_listeners
493 .push(Box::new(move |event, phase, window, cx| {
494 if phase == DispatchPhase::Bubble {
495 listener(event, window, cx)
496 }
497 }));
498 }
499
500 pub fn capture_key_up(
505 &mut self,
506 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
507 ) {
508 self.key_up_listeners
509 .push(Box::new(move |event, phase, window, cx| {
510 if phase == DispatchPhase::Capture {
511 listener(event, window, cx)
512 }
513 }));
514 }
515
516 pub fn on_modifiers_changed(
521 &mut self,
522 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
523 ) {
524 self.modifiers_changed_listeners
525 .push(Box::new(move |event, window, cx| {
526 listener(event, window, cx)
527 }));
528 }
529
530 pub fn on_drop<T: 'static>(&mut self, listener: impl Fn(&T, &mut Window, &mut App) + 'static) {
535 self.drop_listeners.push((
536 TypeId::of::<T>(),
537 Box::new(move |dragged_value, window, cx| {
538 listener(dragged_value.downcast_ref().unwrap(), window, cx);
539 }),
540 ));
541 }
542
543 pub fn can_drop(
546 &mut self,
547 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
548 ) {
549 self.can_drop_predicate = Some(Box::new(predicate));
550 }
551
552 pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
557 where
558 Self: Sized,
559 {
560 self.click_listeners.push(Rc::new(move |event, window, cx| {
561 listener(event, window, cx)
562 }));
563 }
564
565 pub fn on_aux_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
570 where
571 Self: Sized,
572 {
573 self.aux_click_listeners
574 .push(Rc::new(move |event, window, cx| {
575 listener(event, window, cx)
576 }));
577 }
578
579 pub fn on_drag<T, W>(
585 &mut self,
586 value: T,
587 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
588 ) where
589 Self: Sized,
590 T: 'static,
591 W: 'static + Render,
592 {
593 debug_assert!(
594 self.drag_listener.is_none(),
595 "calling on_drag more than once on the same element is not supported"
596 );
597 self.drag_listener = Some((
598 Arc::new(value),
599 Box::new(move |value, offset, window, cx| {
600 constructor(value.downcast_ref().unwrap(), offset, window, cx).into()
601 }),
602 ));
603 }
604
605 pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static)
612 where
613 Self: Sized,
614 {
615 debug_assert!(
616 self.hover_listener.is_none(),
617 "calling on_hover more than once on the same element is not supported"
618 );
619 self.hover_listener = Some(Box::new(listener));
620 }
621
622 pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static)
625 where
626 Self: Sized,
627 {
628 debug_assert!(
629 self.tooltip_builder.is_none(),
630 "calling tooltip more than once on the same element is not supported"
631 );
632 self.tooltip_builder = Some(TooltipBuilder {
633 build: Rc::new(build_tooltip),
634 hoverable: false,
635 });
636 }
637
638 pub fn hoverable_tooltip(
642 &mut self,
643 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
644 ) where
645 Self: Sized,
646 {
647 debug_assert!(
648 self.tooltip_builder.is_none(),
649 "calling tooltip more than once on the same element is not supported"
650 );
651 self.tooltip_builder = Some(TooltipBuilder {
652 build: Rc::new(build_tooltip),
653 hoverable: true,
654 });
655 }
656
657 pub fn tooltip_show_delay(&mut self, delay: Duration) {
660 self.tooltip_show_delay = Some(delay);
661 }
662
663 pub fn occlude_mouse(&mut self) {
668 self.hitbox_behavior = HitboxBehavior::BlockMouse;
669 }
670
671 pub fn window_control_area(&mut self, area: WindowControlArea) {
674 self.window_control = Some(area);
675 }
676
677 pub fn block_mouse_except_scroll(&mut self) {
682 self.hitbox_behavior = HitboxBehavior::BlockMouseExceptScroll;
683 }
684
685 fn has_pinch_listeners(&self) -> bool {
686 !self.pinch_listeners.is_empty()
687 }
688}
689
690pub trait InteractiveElement: Sized {
692 fn interactivity(&mut self) -> &mut Interactivity;
694
695 fn group(mut self, group: impl Into<SharedString>) -> Self {
697 self.interactivity().group = Some(group.into());
698 self
699 }
700
701 fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
703 self.interactivity().element_id = Some(id.into());
704
705 Stateful { element: self }
706 }
707
708 fn track_focus(mut self, focus_handle: &FocusHandle) -> Self {
711 self.interactivity().focusable = true;
712 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
713 self
714 }
715
716 fn tab_stop(mut self, tab_stop: bool) -> Self {
723 self.interactivity().tab_stop = tab_stop;
724 self
725 }
726
727 fn tab_index(mut self, index: isize) -> Self {
732 self.interactivity().focusable = true;
733 self.interactivity().tab_index = Some(index);
734 self.interactivity().tab_stop = true;
735 self
736 }
737
738 fn tab_group(mut self) -> Self {
742 self.interactivity().tab_group = true;
743 if self.interactivity().tab_index.is_none() {
744 self.interactivity().tab_index = Some(0);
745 }
746 self
747 }
748
749 fn key_context<C, E>(mut self, key_context: C) -> Self
751 where
752 C: TryInto<KeyContext, Error = E>,
753 E: std::fmt::Display,
754 {
755 if let Some(key_context) = key_context.try_into().log_err() {
756 self.interactivity().key_context = Some(key_context);
757 }
758 self
759 }
760
761 fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
763 debug_assert!(
764 self.interactivity().hover_style.is_none(),
765 "hover style already set"
766 );
767 self.interactivity().hover_style = Some(Box::new(f(StyleRefinement::default())));
768 self
769 }
770
771 fn group_hover(
773 mut self,
774 group_name: impl Into<SharedString>,
775 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
776 ) -> Self {
777 self.interactivity().group_hover_style = Some(GroupStyle {
778 group: group_name.into(),
779 style: Box::new(f(StyleRefinement::default())),
780 });
781 self
782 }
783
784 fn on_mouse_down(
789 mut self,
790 button: MouseButton,
791 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
792 ) -> Self {
793 self.interactivity().on_mouse_down(button, listener);
794 self
795 }
796
797 #[cfg(any(test, feature = "test-support"))]
798 fn debug_selector(mut self, f: impl FnOnce() -> String) -> Self {
802 self.interactivity().debug_selector = Some(f());
803 self
804 }
805
806 #[cfg(not(any(test, feature = "test-support")))]
807 #[inline]
811 fn debug_selector(self, _: impl FnOnce() -> String) -> Self {
812 self
813 }
814
815 fn capture_any_mouse_down(
820 mut self,
821 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
822 ) -> Self {
823 self.interactivity().capture_any_mouse_down(listener);
824 self
825 }
826
827 fn on_any_mouse_down(
832 mut self,
833 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
834 ) -> Self {
835 self.interactivity().on_any_mouse_down(listener);
836 self
837 }
838
839 fn on_mouse_up(
844 mut self,
845 button: MouseButton,
846 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
847 ) -> Self {
848 self.interactivity().on_mouse_up(button, listener);
849 self
850 }
851
852 fn capture_any_mouse_up(
857 mut self,
858 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
859 ) -> Self {
860 self.interactivity().capture_any_mouse_up(listener);
861 self
862 }
863
864 fn on_mouse_pressure(
869 mut self,
870 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
871 ) -> Self {
872 self.interactivity().on_mouse_pressure(listener);
873 self
874 }
875
876 fn capture_mouse_pressure(
881 mut self,
882 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
883 ) -> Self {
884 self.interactivity().capture_mouse_pressure(listener);
885 self
886 }
887
888 fn on_mouse_down_out(
893 mut self,
894 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
895 ) -> Self {
896 self.interactivity().on_mouse_down_out(listener);
897 self
898 }
899
900 fn on_mouse_up_out(
905 mut self,
906 button: MouseButton,
907 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
908 ) -> Self {
909 self.interactivity().on_mouse_up_out(button, listener);
910 self
911 }
912
913 fn on_mouse_move(
918 mut self,
919 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
920 ) -> Self {
921 self.interactivity().on_mouse_move(listener);
922 self
923 }
924
925 fn on_mouse_exit(
930 mut self,
931 listener: impl Fn(&MouseExitEvent, &mut Window, &mut App) + 'static,
932 ) -> Self {
933 self.interactivity().on_mouse_exit(listener);
934 self
935 }
936
937 fn on_drag_move<T: 'static>(
945 mut self,
946 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
947 ) -> Self {
948 self.interactivity().on_drag_move(listener);
949 self
950 }
951
952 fn on_scroll_wheel(
957 mut self,
958 listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
959 ) -> Self {
960 self.interactivity().on_scroll_wheel(listener);
961 self
962 }
963
964 fn on_pinch(mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) -> Self {
969 self.interactivity().on_pinch(listener);
970 self
971 }
972
973 fn capture_pinch(
978 mut self,
979 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
980 ) -> Self {
981 self.interactivity().capture_pinch(listener);
982 self
983 }
984 fn capture_action<A: Action>(
989 mut self,
990 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
991 ) -> Self {
992 self.interactivity().capture_action(listener);
993 self
994 }
995
996 #[track_caller]
1001 fn on_action<A: Action>(
1002 mut self,
1003 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
1004 ) -> Self {
1005 self.interactivity().on_action(listener);
1006 self
1007 }
1008
1009 fn on_boxed_action(
1015 mut self,
1016 action: &dyn Action,
1017 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
1018 ) -> Self {
1019 self.interactivity().on_boxed_action(action, listener);
1020 self
1021 }
1022
1023 fn on_key_down(
1028 mut self,
1029 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1030 ) -> Self {
1031 self.interactivity().on_key_down(listener);
1032 self
1033 }
1034
1035 fn capture_key_down(
1040 mut self,
1041 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1042 ) -> Self {
1043 self.interactivity().capture_key_down(listener);
1044 self
1045 }
1046
1047 fn on_key_up(
1052 mut self,
1053 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1054 ) -> Self {
1055 self.interactivity().on_key_up(listener);
1056 self
1057 }
1058
1059 fn capture_key_up(
1064 mut self,
1065 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1066 ) -> Self {
1067 self.interactivity().capture_key_up(listener);
1068 self
1069 }
1070
1071 fn on_modifiers_changed(
1076 mut self,
1077 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
1078 ) -> Self {
1079 self.interactivity().on_modifiers_changed(listener);
1080 self
1081 }
1082
1083 fn drag_over<S: 'static>(
1085 mut self,
1086 f: impl 'static + Fn(StyleRefinement, &S, &mut Window, &mut App) -> StyleRefinement,
1087 ) -> Self {
1088 self.interactivity().drag_over_styles.push((
1089 TypeId::of::<S>(),
1090 Box::new(move |currently_dragged: &dyn Any, window, cx| {
1091 f(
1092 StyleRefinement::default(),
1093 currently_dragged.downcast_ref::<S>().unwrap(),
1094 window,
1095 cx,
1096 )
1097 }),
1098 ));
1099 self
1100 }
1101
1102 fn group_drag_over<S: 'static>(
1104 mut self,
1105 group_name: impl Into<SharedString>,
1106 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1107 ) -> Self {
1108 self.interactivity().group_drag_over_styles.push((
1109 TypeId::of::<S>(),
1110 GroupStyle {
1111 group: group_name.into(),
1112 style: Box::new(f(StyleRefinement::default())),
1113 },
1114 ));
1115 self
1116 }
1117
1118 fn on_drop<T: 'static>(
1123 mut self,
1124 listener: impl Fn(&T, &mut Window, &mut App) + 'static,
1125 ) -> Self {
1126 self.interactivity().on_drop(listener);
1127 self
1128 }
1129
1130 fn can_drop(
1133 mut self,
1134 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
1135 ) -> Self {
1136 self.interactivity().can_drop(predicate);
1137 self
1138 }
1139
1140 fn occlude(mut self) -> Self {
1144 self.interactivity().occlude_mouse();
1145 self
1146 }
1147
1148 fn window_control_area(mut self, area: WindowControlArea) -> Self {
1151 self.interactivity().window_control_area(area);
1152 self
1153 }
1154
1155 fn block_mouse_except_scroll(mut self) -> Self {
1160 self.interactivity().block_mouse_except_scroll();
1161 self
1162 }
1163
1164 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1167 where
1168 Self: Sized,
1169 {
1170 self.interactivity().focus_style = Some(Box::new(f(StyleRefinement::default())));
1171 self
1172 }
1173
1174 fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1177 where
1178 Self: Sized,
1179 {
1180 self.interactivity().in_focus_style = Some(Box::new(f(StyleRefinement::default())));
1181 self
1182 }
1183
1184 fn focus_visible(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1189 where
1190 Self: Sized,
1191 {
1192 self.interactivity().focus_visible_style = Some(Box::new(f(StyleRefinement::default())));
1193 self
1194 }
1195}
1196
1197pub trait StatefulInteractiveElement: InteractiveElement {
1199 fn role(mut self, role: accesskit::Role) -> Self {
1203 debug_assert!(
1204 role != accesskit::Role::GenericContainer,
1205 "GenericContainer is filtered out of the a11y tree and has no effect"
1206 );
1207 self.interactivity().override_role = Some(role);
1208 self
1209 }
1210
1211 fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
1213 self.interactivity().aria.label = Some(label.into());
1214 self
1215 }
1216
1217 fn aria_description(mut self, description: impl Into<SharedString>) -> Self {
1220 self.interactivity().aria.description = Some(description.into());
1221 self
1222 }
1223
1224 fn aria_keyshortcuts(mut self, keyshortcuts: impl Into<SharedString>) -> Self {
1229 self.interactivity().aria.keyshortcuts = Some(keyshortcuts.into());
1230 self
1231 }
1232
1233 fn aria_active_descendant(mut self) -> Self {
1245 self.interactivity().report_active_descendant_focus = true;
1246 self
1247 }
1248
1249 fn a11y_synthetic_children(
1257 mut self,
1258 f: impl FnOnce(&mut crate::A11ySubtreeBuilder) + 'static,
1259 ) -> Self {
1260 self.interactivity().a11y_synthetic_children = Some(Box::new(f));
1261 self
1262 }
1263
1264 fn aria_selected(mut self, selected: bool) -> Self {
1266 self.interactivity().aria.selected = Some(selected);
1267 self
1268 }
1269
1270 fn aria_expanded(mut self, expanded: bool) -> Self {
1272 self.interactivity().aria.expanded = Some(expanded);
1273 self
1274 }
1275
1276 fn aria_toggled(mut self, toggled: accesskit::Toggled) -> Self {
1278 self.interactivity().aria.toggled = Some(toggled);
1279 self
1280 }
1281
1282 fn aria_numeric_value(mut self, value: f64) -> Self {
1284 self.interactivity().aria.numeric_value = Some(value);
1285 self
1286 }
1287
1288 fn aria_numeric_value_step(mut self, step: f64) -> Self {
1290 self.interactivity().aria.numeric_value_step = Some(step);
1291 self
1292 }
1293
1294 fn aria_value(mut self, value: impl Into<SharedString>) -> Self {
1296 self.interactivity().aria.value = Some(value.into());
1297 self
1298 }
1299
1300 fn aria_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
1302 self.interactivity().aria.placeholder = Some(placeholder.into());
1303 self
1304 }
1305
1306 fn aria_min_numeric_value(mut self, value: f64) -> Self {
1308 self.interactivity().aria.min_numeric_value = Some(value);
1309 self
1310 }
1311
1312 fn aria_max_numeric_value(mut self, value: f64) -> Self {
1314 self.interactivity().aria.max_numeric_value = Some(value);
1315 self
1316 }
1317
1318 fn aria_orientation(mut self, orientation: accesskit::Orientation) -> Self {
1320 self.interactivity().aria.orientation = Some(orientation);
1321 self
1322 }
1323
1324 fn aria_level(mut self, level: usize) -> Self {
1326 self.interactivity().aria.level = Some(level);
1327 self
1328 }
1329
1330 fn aria_position_in_set(mut self, position: usize) -> Self {
1332 self.interactivity().aria.position_in_set = Some(position);
1333 self
1334 }
1335
1336 fn aria_size_of_set(mut self, size: usize) -> Self {
1338 self.interactivity().aria.size_of_set = Some(size);
1339 self
1340 }
1341
1342 fn aria_row_index(mut self, index: usize) -> Self {
1344 self.interactivity().aria.row_index = Some(index);
1345 self
1346 }
1347
1348 fn aria_column_index(mut self, index: usize) -> Self {
1350 self.interactivity().aria.column_index = Some(index);
1351 self
1352 }
1353
1354 fn aria_row_count(mut self, count: usize) -> Self {
1356 self.interactivity().aria.row_count = Some(count);
1357 self
1358 }
1359
1360 fn aria_column_count(mut self, count: usize) -> Self {
1362 self.interactivity().aria.column_count = Some(count);
1363 self
1364 }
1365
1366 fn on_a11y_action(
1371 mut self,
1372 action: accesskit::Action,
1373 listener: impl FnMut(Option<&accesskit::ActionData>, &mut crate::Window, &mut crate::App)
1374 + 'static,
1375 ) -> Self {
1376 self.interactivity()
1377 .a11y_action_listeners
1378 .push((action, Box::new(listener)));
1379 self
1380 }
1381
1382 fn focusable(mut self) -> Self {
1384 self.interactivity().focusable = true;
1385 self
1386 }
1387
1388 fn overflow_scroll(mut self) -> Self {
1390 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1391 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1392 self
1393 }
1394
1395 fn overflow_x_scroll(mut self) -> Self {
1397 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1398 self
1399 }
1400
1401 fn overflow_y_scroll(mut self) -> Self {
1403 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1404 self
1405 }
1406
1407 fn restrict_scroll_to_axis(mut self) -> Self {
1411 self.interactivity().base_style.restrict_scroll_to_axis = Some(true);
1412 self
1413 }
1414
1415 fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
1417 self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone());
1418 self
1419 }
1420
1421 fn anchor_scroll(mut self, scroll_anchor: Option<ScrollAnchor>) -> Self {
1423 self.interactivity().scroll_anchor = scroll_anchor;
1424 self
1425 }
1426
1427 fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1429 where
1430 Self: Sized,
1431 {
1432 self.interactivity().active_style = Some(Box::new(f(StyleRefinement::default())));
1433 self
1434 }
1435
1436 fn group_active(
1438 mut self,
1439 group_name: impl Into<SharedString>,
1440 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1441 ) -> Self
1442 where
1443 Self: Sized,
1444 {
1445 self.interactivity().group_active_style = Some(GroupStyle {
1446 group: group_name.into(),
1447 style: Box::new(f(StyleRefinement::default())),
1448 });
1449 self
1450 }
1451
1452 fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self
1457 where
1458 Self: Sized,
1459 {
1460 self.interactivity().on_click(listener);
1461 self
1462 }
1463
1464 fn on_aux_click(
1469 mut self,
1470 listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
1471 ) -> Self
1472 where
1473 Self: Sized,
1474 {
1475 self.interactivity().on_aux_click(listener);
1476 self
1477 }
1478
1479 fn on_drag<T, W>(
1486 mut self,
1487 value: T,
1488 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
1489 ) -> Self
1490 where
1491 Self: Sized,
1492 T: 'static,
1493 W: 'static + Render,
1494 {
1495 self.interactivity().on_drag(value, constructor);
1496 self
1497 }
1498
1499 fn on_hover(mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self
1506 where
1507 Self: Sized,
1508 {
1509 self.interactivity().on_hover(listener);
1510 self
1511 }
1512
1513 fn tooltip(mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self
1516 where
1517 Self: Sized,
1518 {
1519 self.interactivity().tooltip(build_tooltip);
1520 self
1521 }
1522
1523 fn hoverable_tooltip(
1527 mut self,
1528 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
1529 ) -> Self
1530 where
1531 Self: Sized,
1532 {
1533 self.interactivity().hoverable_tooltip(build_tooltip);
1534 self
1535 }
1536
1537 fn tooltip_show_delay(mut self, delay: Duration) -> Self
1540 where
1541 Self: Sized,
1542 {
1543 self.interactivity().tooltip_show_delay(delay);
1544 self
1545 }
1546}
1547
1548pub(crate) type MouseDownListener =
1549 Box<dyn Fn(&MouseDownEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1550pub(crate) type MouseUpListener =
1551 Box<dyn Fn(&MouseUpEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1552pub(crate) type MousePressureListener =
1553 Box<dyn Fn(&MousePressureEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1554pub(crate) type MouseMoveListener =
1555 Box<dyn Fn(&MouseMoveEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1556pub(crate) type MouseExitListener =
1557 Box<dyn Fn(&MouseExitEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1558
1559pub(crate) type ScrollWheelListener =
1560 Box<dyn Fn(&ScrollWheelEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1561
1562pub(crate) type PinchListener =
1563 Box<dyn Fn(&PinchEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1564
1565pub(crate) type ClickListener = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>;
1566
1567pub(crate) type DragListener =
1568 Box<dyn Fn(&dyn Any, Point<Pixels>, &mut Window, &mut App) -> AnyView + 'static>;
1569
1570type DropListener = Box<dyn Fn(&dyn Any, &mut Window, &mut App) + 'static>;
1571
1572type CanDropPredicate = Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static>;
1573
1574pub(crate) struct TooltipBuilder {
1575 build: Rc<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>,
1576 hoverable: bool,
1577}
1578
1579pub(crate) type KeyDownListener =
1580 Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1581
1582pub(crate) type KeyUpListener =
1583 Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1584
1585pub(crate) type ModifiersChangedListener =
1586 Box<dyn Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static>;
1587
1588pub(crate) type ActionListener =
1589 Box<dyn Fn(&dyn Any, DispatchPhase, &mut Window, &mut App) + 'static>;
1590
1591#[track_caller]
1593pub fn div() -> Div {
1594 Div {
1595 interactivity: Interactivity::new(),
1596 children: SmallVec::default(),
1597 prepaint_listener: None,
1598 image_cache: None,
1599 prepaint_order_fn: None,
1600 }
1601}
1602
1603pub struct Div {
1605 interactivity: Interactivity,
1606 children: SmallVec<[StackSafe<AnyElement>; 2]>,
1607 prepaint_listener: Option<Box<dyn Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static>>,
1608 image_cache: Option<Box<dyn ImageCacheProvider>>,
1609 prepaint_order_fn: Option<Box<dyn Fn(&mut Window, &mut App) -> SmallVec<[usize; 8]>>>,
1610}
1611
1612impl Div {
1613 pub fn on_children_prepainted(
1616 mut self,
1617 listener: impl Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static,
1618 ) -> Self {
1619 self.prepaint_listener = Some(Box::new(listener));
1620 self
1621 }
1622
1623 pub fn image_cache(mut self, cache: impl ImageCacheProvider) -> Self {
1625 self.image_cache = Some(Box::new(cache));
1626 self
1627 }
1628
1629 pub fn with_dynamic_prepaint_order(
1638 mut self,
1639 order_fn: impl Fn(&mut Window, &mut App) -> SmallVec<[usize; 8]> + 'static,
1640 ) -> Self {
1641 self.prepaint_order_fn = Some(Box::new(order_fn));
1642 self
1643 }
1644}
1645
1646pub struct DivFrameState {
1652 child_layout_ids: SmallVec<[LayoutId; 2]>,
1653}
1654
1655#[derive(Clone)]
1657pub struct DivInspectorState {
1658 #[cfg(any(feature = "inspector", debug_assertions))]
1661 pub base_style: Box<StyleRefinement>,
1662 pub bounds: Bounds<Pixels>,
1664 pub content_size: Size<Pixels>,
1666}
1667
1668impl Styled for Div {
1669 fn style(&mut self) -> &mut StyleRefinement {
1670 &mut self.interactivity.base_style
1671 }
1672}
1673
1674impl InteractiveElement for Div {
1675 fn interactivity(&mut self) -> &mut Interactivity {
1676 &mut self.interactivity
1677 }
1678}
1679
1680impl ParentElement for Div {
1681 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1682 self.children
1683 .extend(elements.into_iter().map(StackSafe::new))
1684 }
1685}
1686
1687impl Element for Div {
1688 type RequestLayoutState = DivFrameState;
1689 type PrepaintState = Option<Hitbox>;
1690
1691 fn id(&self) -> Option<ElementId> {
1692 self.interactivity.element_id.clone()
1693 }
1694
1695 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
1696 self.interactivity.source_location()
1697 }
1698
1699 fn a11y_role(&self) -> Option<accesskit::Role> {
1700 self.interactivity
1703 .override_role
1704 .filter(|role| *role != accesskit::Role::GenericContainer)
1705 }
1706
1707 fn write_a11y_info(&self, node: &mut accesskit::Node) {
1708 self.interactivity.write_a11y_info(node);
1709 }
1710
1711 fn a11y_synthetic_children(
1712 &mut self,
1713 _prepaint: &mut Self::PrepaintState,
1714 builder: &mut crate::A11ySubtreeBuilder,
1715 ) {
1716 if let Some(f) = self.interactivity.a11y_synthetic_children.take() {
1717 f(builder);
1718 }
1719 }
1720
1721 #[stacksafe]
1722 fn request_layout(
1723 &mut self,
1724 global_id: Option<&GlobalElementId>,
1725 inspector_id: Option<&InspectorElementId>,
1726 window: &mut Window,
1727 cx: &mut App,
1728 ) -> (LayoutId, Self::RequestLayoutState) {
1729 let mut child_layout_ids = SmallVec::new();
1730 let image_cache = self
1731 .image_cache
1732 .as_mut()
1733 .map(|provider| provider.provide(window, cx));
1734
1735 let layout_id = window.with_image_cache(image_cache, |window| {
1736 self.interactivity.request_layout(
1737 global_id,
1738 inspector_id,
1739 window,
1740 cx,
1741 |style, window, cx| {
1742 window.with_text_style(style.text_style().cloned(), |window| {
1743 child_layout_ids = self
1744 .children
1745 .iter_mut()
1746 .map(|child| child.request_layout(window, cx))
1747 .collect::<SmallVec<_>>();
1748 window.request_layout(style, child_layout_ids.iter().copied(), cx)
1749 })
1750 },
1751 )
1752 });
1753
1754 (layout_id, DivFrameState { child_layout_ids })
1755 }
1756
1757 #[stacksafe]
1758 fn prepaint(
1759 &mut self,
1760 global_id: Option<&GlobalElementId>,
1761 inspector_id: Option<&InspectorElementId>,
1762 bounds: Bounds<Pixels>,
1763 request_layout: &mut Self::RequestLayoutState,
1764 window: &mut Window,
1765 cx: &mut App,
1766 ) -> Option<Hitbox> {
1767 let image_cache = self
1768 .image_cache
1769 .as_mut()
1770 .map(|provider| provider.provide(window, cx));
1771
1772 let has_prepaint_listener = self.prepaint_listener.is_some();
1773 let mut children_bounds = Vec::with_capacity(if has_prepaint_listener {
1774 request_layout.child_layout_ids.len()
1775 } else {
1776 0
1777 });
1778
1779 let mut child_min = point(Pixels::MAX, Pixels::MAX);
1780 let mut child_max = Point::default();
1781 if let Some(handle) = self.interactivity.scroll_anchor.as_ref() {
1782 *handle.last_origin.borrow_mut() = bounds.origin - window.element_offset();
1783 }
1784 let content_size = if request_layout.child_layout_ids.is_empty() {
1785 bounds.size
1786 } else if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
1787 let mut state = scroll_handle.0.borrow_mut();
1788 state.child_bounds = Vec::with_capacity(request_layout.child_layout_ids.len());
1789 for child_layout_id in &request_layout.child_layout_ids {
1790 let child_bounds = window.layout_bounds(*child_layout_id);
1791 child_min = child_min.min(&child_bounds.origin);
1792 child_max = child_max.max(&child_bounds.bottom_right());
1793 state.child_bounds.push(child_bounds);
1794 }
1795 (child_max - child_min).into()
1796 } else {
1797 for child_layout_id in &request_layout.child_layout_ids {
1798 let child_bounds = window.layout_bounds(*child_layout_id);
1799 child_min = child_min.min(&child_bounds.origin);
1800 child_max = child_max.max(&child_bounds.bottom_right());
1801
1802 if has_prepaint_listener {
1803 children_bounds.push(child_bounds);
1804 }
1805 }
1806 (child_max - child_min).into()
1807 };
1808
1809 if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
1810 scroll_handle.scroll_to_active_item();
1811 }
1812
1813 self.interactivity.prepaint(
1814 global_id,
1815 inspector_id,
1816 bounds,
1817 content_size,
1818 window,
1819 cx,
1820 |style, scroll_offset, hitbox, window, cx| {
1821 if style.display == Display::None {
1823 return hitbox;
1824 }
1825
1826 window.with_image_cache(image_cache, |window| {
1827 #[cfg(feature = "dom-backend")]
1831 let paint_offset = if window.dom_builder_active() {
1832 Point::default()
1833 } else {
1834 scroll_offset
1835 };
1836 #[cfg(not(feature = "dom-backend"))]
1837 let paint_offset = scroll_offset;
1838 window.with_element_offset(paint_offset, |window| {
1839 if let Some(order_fn) = &self.prepaint_order_fn {
1840 let order = order_fn(window, cx);
1841 for idx in order {
1842 if let Some(child) = self.children.get_mut(idx) {
1843 child.prepaint(window, cx);
1844 }
1845 }
1846 } else {
1847 for child in &mut self.children {
1848 child.prepaint(window, cx);
1849 }
1850 }
1851 });
1852
1853 if let Some(listener) = self.prepaint_listener.as_ref() {
1854 listener(children_bounds, window, cx);
1855 }
1856 });
1857
1858 hitbox
1859 },
1860 )
1861 }
1862
1863 #[stacksafe]
1864 fn paint(
1865 &mut self,
1866 global_id: Option<&GlobalElementId>,
1867 inspector_id: Option<&InspectorElementId>,
1868 bounds: Bounds<Pixels>,
1869 _request_layout: &mut Self::RequestLayoutState,
1870 hitbox: &mut Option<Hitbox>,
1871 window: &mut Window,
1872 cx: &mut App,
1873 ) {
1874 let image_cache = self
1875 .image_cache
1876 .as_mut()
1877 .map(|provider| provider.provide(window, cx));
1878
1879 window.with_image_cache(image_cache, |window| {
1880 self.interactivity.paint(
1881 global_id,
1882 inspector_id,
1883 bounds,
1884 hitbox.as_ref(),
1885 window,
1886 cx,
1887 |style, window, cx| {
1888 if style.display == Display::None {
1890 return;
1891 }
1892
1893 for child in &mut self.children {
1894 child.paint(window, cx);
1895 }
1896 },
1897 )
1898 });
1899 }
1900
1901 #[cfg(feature = "dom-backend")]
1907 fn dom(
1908 &self,
1909 bounds: Bounds<Pixels>,
1910 window: &mut Window,
1911 cx: &mut App,
1912 ) -> Option<crate::DomNode> {
1913 use crate::{
1914 BackgroundTag, Corners, DomBoxShadow, DomDisplay, DomGradient, DomGradientKind,
1915 DomNode, DomNodeKind, DomOverflow, DomStyle, Overflow,
1916 };
1917
1918 let style = self.interactivity.compute_style(None, None, window, cx);
1919 if style.visibility == Visibility::Hidden {
1920 return None;
1921 }
1922
1923 let mut dom_style = DomStyle::from_bounds(bounds);
1924
1925 dom_style.display = match style.display {
1927 crate::Display::None => DomDisplay::None,
1928 _ => DomDisplay::Block,
1929 };
1930
1931 if let Some(fill) = style.background.as_ref()
1932 && let Some(background) = fill.color()
1933 {
1934 match background.tag {
1936 BackgroundTag::Solid => {
1937 if !background.solid.is_transparent() {
1938 dom_style.background_color = Some(background.solid);
1939 }
1940 }
1941 BackgroundTag::LinearGradient
1942 | BackgroundTag::RadialGradient
1943 | BackgroundTag::ConicGradient => {
1944 let count = (background.stop_count as usize).clamp(1, 4);
1945 let stops = background.colors[..count]
1946 .iter()
1947 .map(|stop| (stop.color, stop.percentage))
1948 .collect();
1949 let kind = match background.tag {
1950 BackgroundTag::LinearGradient => DomGradientKind::Linear,
1951 BackgroundTag::RadialGradient => DomGradientKind::Radial,
1952 _ => DomGradientKind::Conic,
1953 };
1954 dom_style.background_gradient = Some(DomGradient {
1955 kind,
1956 angle: background.gradient_angle_or_pattern_height,
1957 stops,
1958 });
1959 }
1960 BackgroundTag::PatternSlash | BackgroundTag::Checkerboard => {
1961 if !background.solid.is_transparent() {
1963 dom_style.background_color = Some(background.solid);
1964 }
1965 }
1966 }
1967 }
1968
1969 let radii: Corners<crate::Pixels> = style.corner_radii.to_pixels(window.rem_size());
1971 if radii.top_left == radii.top_right
1972 && radii.top_right == radii.bottom_right
1973 && radii.bottom_right == radii.bottom_left
1974 {
1975 dom_style.border_radius = Some(radii.top_left);
1976 }
1977
1978 if let Some(color) = style.border_color {
1980 let widths = style.border_widths.to_pixels(window.rem_size());
1981 let max_width = widths.max();
1982 if max_width > Pixels::ZERO && !color.is_transparent() {
1983 dom_style.border_color = Some(color);
1984 dom_style.border_width = Some(max_width);
1985 dom_style.border_style = Some(style.border_style);
1986 }
1987 }
1988
1989 if !style.box_shadow.is_empty() {
1991 dom_style.box_shadows = style
1992 .box_shadow
1993 .iter()
1994 .map(|shadow| DomBoxShadow {
1995 color: shadow.color,
1996 offset_x: shadow.offset.x,
1997 offset_y: shadow.offset.y,
1998 blur_radius: shadow.blur_radius,
1999 spread_radius: shadow.spread_radius,
2000 inset: shadow.inset,
2001 })
2002 .collect();
2003 }
2004
2005 dom_style.opacity = style.opacity;
2006 dom_style.cursor = style.mouse_cursor;
2007 dom_style.overflow = match (style.overflow.x, style.overflow.y) {
2011 (Overflow::Scroll, _) | (_, Overflow::Scroll) => DomOverflow::Scroll,
2012 (Overflow::Clip | Overflow::Hidden, _) | (_, Overflow::Clip | Overflow::Hidden) => {
2013 DomOverflow::Hidden
2014 }
2015 _ => DomOverflow::Visible,
2016 };
2017
2018 let scroll_handle = if dom_style.overflow == DomOverflow::Scroll {
2024 self.interactivity.tracked_scroll_handle.clone()
2025 } else {
2026 None
2027 };
2028
2029 Some(DomNode {
2030 kind: DomNodeKind::Element {
2031 tag: "div",
2032 attrs: Vec::new(),
2033 children: Vec::new(),
2034 },
2035 style: dom_style,
2036 scroll_handle,
2037 })
2038 }
2039}
2040
2041impl IntoElement for Div {
2042 type Element = Self;
2043
2044 fn into_element(self) -> Self::Element {
2045 self
2046 }
2047}
2048
2049#[derive(Default)]
2050pub(crate) struct AriaProperties {
2051 pub(crate) label: Option<SharedString>,
2052 pub(crate) description: Option<SharedString>,
2053 pub(crate) keyshortcuts: Option<SharedString>,
2054 pub(crate) selected: Option<bool>,
2055 pub(crate) expanded: Option<bool>,
2056 pub(crate) toggled: Option<accesskit::Toggled>,
2057 pub(crate) numeric_value: Option<f64>,
2058 pub(crate) min_numeric_value: Option<f64>,
2059 pub(crate) max_numeric_value: Option<f64>,
2060 pub(crate) numeric_value_step: Option<f64>,
2061 pub(crate) value: Option<SharedString>,
2062 pub(crate) placeholder: Option<SharedString>,
2063 pub(crate) orientation: Option<accesskit::Orientation>,
2064 pub(crate) level: Option<usize>,
2065 pub(crate) position_in_set: Option<usize>,
2066 pub(crate) size_of_set: Option<usize>,
2067 pub(crate) row_index: Option<usize>,
2068 pub(crate) column_index: Option<usize>,
2069 pub(crate) row_count: Option<usize>,
2070 pub(crate) column_count: Option<usize>,
2071}
2072
2073#[derive(Default)]
2075pub struct Interactivity {
2076 pub element_id: Option<ElementId>,
2078 pub active: Option<bool>,
2080 pub hovered: Option<bool>,
2082 pub(crate) tooltip_id: Option<TooltipId>,
2083 pub(crate) content_size: Size<Pixels>,
2084 pub(crate) key_context: Option<KeyContext>,
2085 pub(crate) focusable: bool,
2086 pub(crate) tracked_focus_handle: Option<FocusHandle>,
2087 pub(crate) tracked_scroll_handle: Option<ScrollHandle>,
2088 pub(crate) scroll_anchor: Option<ScrollAnchor>,
2089 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
2090 pub(crate) group: Option<SharedString>,
2091 pub base_style: Box<StyleRefinement>,
2093 pub(crate) focus_style: Option<Box<StyleRefinement>>,
2094 pub(crate) in_focus_style: Option<Box<StyleRefinement>>,
2095 pub(crate) focus_visible_style: Option<Box<StyleRefinement>>,
2096 pub(crate) hover_style: Option<Box<StyleRefinement>>,
2097 pub(crate) group_hover_style: Option<GroupStyle>,
2098 pub(crate) active_style: Option<Box<StyleRefinement>>,
2099 pub(crate) group_active_style: Option<GroupStyle>,
2100 pub(crate) drag_over_styles: Vec<(
2101 TypeId,
2102 Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> StyleRefinement>,
2103 )>,
2104 pub(crate) group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
2105 pub(crate) mouse_down_listeners: Vec<MouseDownListener>,
2106 pub(crate) mouse_up_listeners: Vec<MouseUpListener>,
2107 pub(crate) mouse_pressure_listeners: Vec<MousePressureListener>,
2108 pub(crate) mouse_move_listeners: Vec<MouseMoveListener>,
2109 pub(crate) mouse_exit_listeners: Vec<MouseExitListener>,
2110 pub(crate) scroll_wheel_listeners: Vec<ScrollWheelListener>,
2111 pub(crate) pinch_listeners: Vec<PinchListener>,
2112 pub(crate) key_down_listeners: Vec<KeyDownListener>,
2113 pub(crate) key_up_listeners: Vec<KeyUpListener>,
2114 pub(crate) modifiers_changed_listeners: Vec<ModifiersChangedListener>,
2115 pub(crate) action_listeners: Vec<(TypeId, ActionListener)>,
2116 pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
2117 pub(crate) can_drop_predicate: Option<CanDropPredicate>,
2118 pub(crate) click_listeners: Vec<ClickListener>,
2119 pub(crate) aux_click_listeners: Vec<ClickListener>,
2120 pub(crate) drag_listener: Option<(Arc<dyn Any>, DragListener)>,
2121 pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut Window, &mut App)>>,
2122 pub(crate) tooltip_builder: Option<TooltipBuilder>,
2123 pub(crate) tooltip_show_delay: Option<Duration>,
2124 pub(crate) window_control: Option<WindowControlArea>,
2125 pub(crate) hitbox_behavior: HitboxBehavior,
2126 pub(crate) tab_index: Option<isize>,
2127 pub(crate) tab_group: bool,
2128 pub(crate) tab_stop: bool,
2129
2130 pub(crate) a11y_action_listeners:
2131 Vec<(accesskit::Action, crate::window::a11y::A11yActionListener)>,
2132 pub(crate) a11y_synthetic_children: Option<Box<dyn FnOnce(&mut crate::A11ySubtreeBuilder)>>,
2133 pub(crate) report_active_descendant_focus: bool,
2134 pub(crate) override_role: Option<accesskit::Role>,
2135 pub(crate) aria: AriaProperties,
2136
2137 #[cfg(any(feature = "inspector", debug_assertions))]
2138 pub(crate) source_location: Option<&'static core::panic::Location<'static>>,
2139
2140 #[cfg(any(test, feature = "test-support"))]
2141 pub(crate) debug_selector: Option<String>,
2142}
2143
2144impl Interactivity {
2145 pub fn request_layout(
2147 &mut self,
2148 global_id: Option<&GlobalElementId>,
2149 _inspector_id: Option<&InspectorElementId>,
2150 window: &mut Window,
2151 cx: &mut App,
2152 f: impl FnOnce(Style, &mut Window, &mut App) -> LayoutId,
2153 ) -> LayoutId {
2154 #[cfg(any(feature = "inspector", debug_assertions))]
2155 window.with_inspector_state(
2156 _inspector_id,
2157 cx,
2158 |inspector_state: &mut Option<DivInspectorState>, _window| {
2159 if let Some(inspector_state) = inspector_state {
2160 self.base_style = inspector_state.base_style.clone();
2161 } else {
2162 *inspector_state = Some(DivInspectorState {
2163 base_style: self.base_style.clone(),
2164 bounds: Default::default(),
2165 content_size: Default::default(),
2166 })
2167 }
2168 },
2169 );
2170
2171 window.with_optional_element_state::<InteractiveElementState, _>(
2172 global_id,
2173 |element_state, window| {
2174 let mut element_state =
2175 element_state.map(|element_state| element_state.unwrap_or_default());
2176
2177 if let Some(element_state) = element_state.as_ref()
2178 && cx.has_active_drag()
2179 {
2180 if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() {
2181 *pending_mouse_down.borrow_mut() = None;
2182 }
2183 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
2184 *clicked_state.borrow_mut() = ElementClickedState::default();
2185 }
2186 }
2187
2188 if self.focusable
2193 && self.tracked_focus_handle.is_none()
2194 && let Some(element_state) = element_state.as_mut()
2195 {
2196 let mut handle = element_state
2197 .focus_handle
2198 .get_or_insert_with(|| cx.focus_handle())
2199 .clone()
2200 .tab_stop(self.tab_stop);
2201
2202 if let Some(index) = self.tab_index {
2203 handle = handle.tab_index(index);
2204 }
2205
2206 self.tracked_focus_handle = Some(handle);
2207 }
2208
2209 if let Some(scroll_handle) = self.tracked_scroll_handle.as_ref() {
2210 self.scroll_offset = Some(scroll_handle.0.borrow().offset.clone());
2211 } else if (self.base_style.overflow.x == Some(Overflow::Scroll)
2212 || self.base_style.overflow.y == Some(Overflow::Scroll))
2213 && let Some(element_state) = element_state.as_mut()
2214 {
2215 self.scroll_offset = Some(
2216 element_state
2217 .scroll_offset
2218 .get_or_insert_with(Rc::default)
2219 .clone(),
2220 );
2221 }
2222
2223 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
2224 let layout_id = f(style, window, cx);
2225 (layout_id, element_state)
2226 },
2227 )
2228 }
2229
2230 pub fn prepaint<R>(
2232 &mut self,
2233 global_id: Option<&GlobalElementId>,
2234 _inspector_id: Option<&InspectorElementId>,
2235 bounds: Bounds<Pixels>,
2236 content_size: Size<Pixels>,
2237 window: &mut Window,
2238 cx: &mut App,
2239 f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut Window, &mut App) -> R,
2240 ) -> R {
2241 self.content_size = content_size;
2242
2243 #[cfg(any(feature = "inspector", debug_assertions))]
2244 window.with_inspector_state(
2245 _inspector_id,
2246 cx,
2247 |inspector_state: &mut Option<DivInspectorState>, _window| {
2248 if let Some(inspector_state) = inspector_state {
2249 inspector_state.bounds = bounds;
2250 inspector_state.content_size = content_size;
2251 }
2252 },
2253 );
2254
2255 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
2256 window.set_focus_handle(focus_handle, cx);
2257
2258 if window.a11y.is_active() {
2259 if let Some(global_id) = global_id {
2260 let node_id = global_id.accesskit_node_id();
2261 window.a11y.set_focusable(node_id, focus_handle.id);
2262 if focus_handle.is_focused(window) {
2263 window.a11y.set_focus(node_id);
2264 }
2265 } else if focus_handle.is_focused(window) {
2266 window
2270 .a11y
2271 .note_focus_without_node(focus_handle.id, "it has no element id");
2272 }
2273 }
2274 }
2275
2276 if self.report_active_descendant_focus && window.a11y.is_active() {
2277 if let Some(global_id) = global_id {
2278 window
2279 .a11y
2280 .set_active_descendant(global_id.accesskit_node_id());
2281 }
2282 }
2283 window.with_optional_element_state::<InteractiveElementState, _>(
2284 global_id,
2285 |element_state, window| {
2286 let mut element_state =
2287 element_state.map(|element_state| element_state.unwrap_or_default());
2288 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
2289
2290 if let Some(element_state) = element_state.as_mut() {
2291 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
2292 let clicked_state = clicked_state.borrow();
2293 self.active = Some(clicked_state.element);
2294 }
2295 if self.hover_style.is_some() || self.group_hover_style.is_some() {
2296 element_state
2297 .hover_state
2298 .get_or_insert_with(Default::default);
2299 }
2300 if let Some(active_tooltip) = element_state.active_tooltip.as_ref() {
2301 if self.tooltip_builder.is_some() {
2302 self.tooltip_id = set_tooltip_on_window(active_tooltip, window);
2303 } else {
2304 element_state.active_tooltip.take();
2306 }
2307 }
2308 }
2309
2310 window.with_text_style(style.text_style().cloned(), |window| {
2311 window.with_content_mask(
2312 style.overflow_mask(bounds, window.rem_size()),
2313 |window| {
2314 let hitbox = if self.should_insert_hitbox(&style, window, cx) {
2315 Some(window.insert_hitbox(bounds, self.hitbox_behavior))
2316 } else {
2317 None
2318 };
2319
2320 let scroll_offset =
2321 self.clamp_scroll_position(bounds, &style, window, cx);
2322 let result = f(&style, scroll_offset, hitbox, window, cx);
2323 (result, element_state)
2324 },
2325 )
2326 })
2327 },
2328 )
2329 }
2330
2331 fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
2332 self.hitbox_behavior != HitboxBehavior::Normal
2333 || self.window_control.is_some()
2334 || style.mouse_cursor.is_some()
2335 || self.group.is_some()
2336 || self.scroll_offset.is_some()
2337 || self.tracked_focus_handle.is_some()
2338 || self.hover_style.is_some()
2339 || self.group_hover_style.is_some()
2340 || self.hover_listener.is_some()
2341 || !self.mouse_up_listeners.is_empty()
2342 || !self.mouse_pressure_listeners.is_empty()
2343 || !self.mouse_down_listeners.is_empty()
2344 || !self.mouse_move_listeners.is_empty()
2345 || !self.mouse_exit_listeners.is_empty()
2346 || !self.click_listeners.is_empty()
2347 || !self.aux_click_listeners.is_empty()
2348 || !self.scroll_wheel_listeners.is_empty()
2349 || self.has_pinch_listeners()
2350 || self.drag_listener.is_some()
2351 || !self.drop_listeners.is_empty()
2352 || self.tooltip_builder.is_some()
2353 || window.is_inspector_picking(cx)
2354 }
2355
2356 fn clamp_scroll_position(
2357 &self,
2358 bounds: Bounds<Pixels>,
2359 style: &Style,
2360 window: &mut Window,
2361 _cx: &mut App,
2362 ) -> Point<Pixels> {
2363 fn round_to_two_decimals(pixels: Pixels) -> Pixels {
2364 const ROUNDING_FACTOR: f32 = 100.0;
2365 (pixels * ROUNDING_FACTOR).round() / ROUNDING_FACTOR
2366 }
2367
2368 if let Some(scroll_offset) = self.scroll_offset.as_ref() {
2369 let mut scroll_to_bottom = false;
2370 let mut tracked_scroll_handle = self
2371 .tracked_scroll_handle
2372 .as_ref()
2373 .map(|handle| handle.0.borrow_mut());
2374 if let Some(mut scroll_handle_state) = tracked_scroll_handle.as_deref_mut() {
2375 scroll_handle_state.overflow = style.overflow;
2376 scroll_to_bottom = mem::take(&mut scroll_handle_state.scroll_to_bottom);
2377 }
2378
2379 let rem_size = window.rem_size();
2380 let padding = style.padding.to_pixels(bounds.size.into(), rem_size);
2381 let padding_size = size(padding.left + padding.right, padding.top + padding.bottom);
2382 let padded_content_size = self.content_size + padding_size;
2389 let scroll_max = Point::from(padded_content_size - bounds.size)
2390 .map(round_to_two_decimals)
2391 .max(&Default::default());
2392 let mut scroll_offset = scroll_offset.borrow_mut();
2395
2396 scroll_offset.x = scroll_offset.x.clamp(-scroll_max.x, px(0.));
2397 if scroll_to_bottom {
2398 scroll_offset.y = -scroll_max.y;
2399 } else {
2400 scroll_offset.y = scroll_offset.y.clamp(-scroll_max.y, px(0.));
2401 }
2402
2403 if let Some(mut scroll_handle_state) = tracked_scroll_handle {
2404 scroll_handle_state.max_offset = scroll_max;
2405 scroll_handle_state.bounds = bounds;
2406 }
2407
2408 *scroll_offset
2409 } else {
2410 Point::default()
2411 }
2412 }
2413
2414 pub fn paint(
2420 &mut self,
2421 global_id: Option<&GlobalElementId>,
2422 _inspector_id: Option<&InspectorElementId>,
2423 bounds: Bounds<Pixels>,
2424 hitbox: Option<&Hitbox>,
2425 window: &mut Window,
2426 cx: &mut App,
2427 f: impl FnOnce(&Style, &mut Window, &mut App),
2428 ) {
2429 self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(window));
2430 window.with_optional_element_state::<InteractiveElementState, _>(
2431 global_id,
2432 |element_state, window| {
2433 let mut element_state =
2434 element_state.map(|element_state| element_state.unwrap_or_default());
2435
2436 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
2437
2438 #[cfg(any(feature = "test-support", test))]
2439 if let Some(debug_selector) = &self.debug_selector {
2440 window
2441 .next_frame
2442 .debug_bounds
2443 .insert(debug_selector.clone(), bounds);
2444 }
2445
2446 self.paint_hover_group_handler(window, cx);
2447
2448 if style.visibility == Visibility::Hidden {
2449 return ((), element_state);
2450 }
2451
2452 let mut tab_group = None;
2453 if self.tab_group {
2454 tab_group = self.tab_index;
2455 }
2456
2457 window.with_element_opacity(style.opacity, |window| {
2458 style.paint(bounds, window, cx, |window: &mut Window, cx: &mut App| {
2459 window.with_text_style(style.text_style().cloned(), |window| {
2460 window.with_content_mask(
2461 style.overflow_mask(bounds, window.rem_size()),
2462 |window| {
2463 window.with_tab_group(tab_group, |window| {
2464 if let Some(focus_handle) = &self.tracked_focus_handle {
2473 window.next_frame.tab_stops.insert(focus_handle);
2474 }
2475 if let Some(hitbox) = hitbox {
2476 #[cfg(debug_assertions)]
2477 self.paint_debug_info(
2478 global_id, hitbox, &style, window, cx,
2479 );
2480
2481 if let Some(drag) = cx.active_drag.as_ref() {
2482 if let Some(mouse_cursor) = drag.cursor_style {
2483 window.set_window_cursor_style(mouse_cursor);
2484 }
2485 } else {
2486 if let Some(mouse_cursor) = style.mouse_cursor {
2487 window.set_cursor_style(mouse_cursor, hitbox);
2488 }
2489 }
2490
2491 if let Some(group) = self.group.clone() {
2492 GroupHitboxes::push(group, hitbox.id, cx);
2493 }
2494
2495 if let Some(area) = self.window_control {
2496 window.insert_window_control_hitbox(
2497 area,
2498 hitbox.clone(),
2499 );
2500 }
2501
2502 self.paint_mouse_listeners(
2503 hitbox,
2504 element_state.as_mut(),
2505 window,
2506 cx,
2507 );
2508 self.paint_scroll_listener(hitbox, &style, window, cx);
2509 }
2510
2511 self.paint_keyboard_listeners(window, cx);
2512
2513 if window.a11y.is_active() {
2514 if let Some(global_id) = global_id {
2515 if !self.a11y_action_listeners.is_empty() {
2516 let node_id = global_id.accesskit_node_id();
2517 for (action, listener) in
2518 self.a11y_action_listeners.drain(..)
2519 {
2520 window.on_a11y_action(
2521 node_id, action, listener,
2522 );
2523 }
2524 }
2525 }
2526 }
2527
2528 f(&style, window, cx);
2529
2530 if let Some(_hitbox) = hitbox {
2531 #[cfg(any(feature = "inspector", debug_assertions))]
2532 window.insert_inspector_hitbox(
2533 _hitbox.id,
2534 _inspector_id,
2535 cx,
2536 );
2537
2538 if let Some(group) = self.group.as_ref() {
2539 GroupHitboxes::pop(group, cx);
2540 }
2541 }
2542 })
2543 },
2544 );
2545 });
2546 });
2547 });
2548
2549 ((), element_state)
2550 },
2551 );
2552 }
2553
2554 #[cfg(debug_assertions)]
2555 fn paint_debug_info(
2556 &self,
2557 global_id: Option<&GlobalElementId>,
2558 hitbox: &Hitbox,
2559 style: &Style,
2560 window: &mut Window,
2561 cx: &mut App,
2562 ) {
2563 use crate::{BorderStyle, TextAlign};
2564
2565 if let Some(global_id) = global_id
2566 && (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
2567 && hitbox.is_hovered(window)
2568 {
2569 const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
2570 let element_id = format!("{global_id:?}");
2571 let str_len = element_id.len();
2572
2573 let render_debug_text = |window: &mut Window| {
2574 if let Some(text) = window
2575 .text_system()
2576 .shape_text(
2577 element_id.into(),
2578 FONT_SIZE,
2579 &[window.text_style().to_run(str_len)],
2580 None,
2581 None,
2582 )
2583 .ok()
2584 .and_then(|mut text| text.pop())
2585 {
2586 text.paint(hitbox.origin, FONT_SIZE, TextAlign::Left, None, window, cx)
2587 .ok();
2588
2589 let text_bounds = crate::Bounds {
2590 origin: hitbox.origin,
2591 size: text.size(FONT_SIZE),
2592 };
2593 if let Some(source_location) = self.source_location
2594 && text_bounds.contains(&window.mouse_position())
2595 && window.modifiers().secondary()
2596 {
2597 let secondary_held = window.modifiers().secondary();
2598 window.on_key_event({
2599 move |e: &crate::ModifiersChangedEvent, _phase, window, _cx| {
2600 if e.modifiers.secondary() != secondary_held
2601 && text_bounds.contains(&window.mouse_position())
2602 {
2603 window.refresh();
2604 }
2605 }
2606 });
2607
2608 let was_hovered = hitbox.is_hovered(window);
2609 let current_view = window.current_view();
2610 window.on_mouse_event({
2611 let hitbox = hitbox.clone();
2612 move |_: &MouseMoveEvent, phase, window, cx| {
2613 if phase == DispatchPhase::Capture {
2614 let hovered = hitbox.is_hovered(window);
2615 if hovered != was_hovered {
2616 cx.notify(current_view)
2617 }
2618 }
2619 }
2620 });
2621
2622 window.on_mouse_event({
2623 let hitbox = hitbox.clone();
2624 move |e: &crate::MouseDownEvent, phase, window, cx| {
2625 if text_bounds.contains(&e.position)
2626 && phase.capture()
2627 && hitbox.is_hovered(window)
2628 {
2629 cx.stop_propagation();
2630 let Ok(dir) = std::env::current_dir() else {
2631 return;
2632 };
2633
2634 eprintln!(
2635 "This element was created at:\n{}:{}:{}",
2636 dir.join(source_location.file()).to_string_lossy(),
2637 source_location.line(),
2638 source_location.column()
2639 );
2640 }
2641 }
2642 });
2643 window.paint_quad(crate::outline(
2644 crate::Bounds {
2645 origin: hitbox.origin
2646 + crate::point(crate::px(0.), FONT_SIZE - px(2.)),
2647 size: crate::Size {
2648 width: text_bounds.size.width,
2649 height: crate::px(1.),
2650 },
2651 },
2652 crate::red(),
2653 BorderStyle::default(),
2654 ))
2655 }
2656 }
2657 };
2658
2659 window.with_text_style(
2660 Some(crate::TextStyleRefinement {
2661 color: Some(crate::red()),
2662 line_height: Some(FONT_SIZE.into()),
2663 background_color: Some(crate::white()),
2664 ..Default::default()
2665 }),
2666 render_debug_text,
2667 )
2668 }
2669 }
2670
2671 fn paint_mouse_listeners(
2672 &mut self,
2673 hitbox: &Hitbox,
2674 element_state: Option<&mut InteractiveElementState>,
2675 window: &mut Window,
2676 cx: &mut App,
2677 ) {
2678 let is_focused = self
2679 .tracked_focus_handle
2680 .as_ref()
2681 .map(|handle| handle.is_focused(window))
2682 .unwrap_or(false);
2683
2684 if let Some(focus_handle) = self.tracked_focus_handle.clone() {
2688 let hitbox = hitbox.clone();
2689 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, cx| {
2690 if phase == DispatchPhase::Bubble
2691 && hitbox.is_hovered(window)
2692 && !window.default_prevented()
2693 {
2694 window.focus(&focus_handle, cx);
2695 window.prevent_default();
2698 }
2699 });
2700 }
2701
2702 for listener in self.mouse_down_listeners.drain(..) {
2703 let hitbox = hitbox.clone();
2704 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
2705 listener(event, phase, &hitbox, window, cx);
2706 })
2707 }
2708
2709 for listener in self.mouse_up_listeners.drain(..) {
2710 let hitbox = hitbox.clone();
2711 window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
2712 listener(event, phase, &hitbox, window, cx);
2713 })
2714 }
2715
2716 for listener in self.mouse_pressure_listeners.drain(..) {
2717 let hitbox = hitbox.clone();
2718 window.on_mouse_event(move |event: &MousePressureEvent, phase, window, cx| {
2719 listener(event, phase, &hitbox, window, cx);
2720 })
2721 }
2722
2723 for listener in self.mouse_move_listeners.drain(..) {
2724 let hitbox = hitbox.clone();
2725 window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
2726 listener(event, phase, &hitbox, window, cx);
2727 })
2728 }
2729
2730 for listener in self.mouse_exit_listeners.drain(..) {
2731 let hitbox = hitbox.clone();
2732 window.on_mouse_event(move |event: &MouseExitEvent, phase, window, cx| {
2733 listener(event, phase, &hitbox, window, cx);
2734 })
2735 }
2736
2737 for listener in self.scroll_wheel_listeners.drain(..) {
2738 let hitbox = hitbox.clone();
2739 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
2740 listener(event, phase, &hitbox, window, cx);
2741 })
2742 }
2743
2744 for listener in self.pinch_listeners.drain(..) {
2745 let hitbox = hitbox.clone();
2746 window.on_mouse_event(move |event: &PinchEvent, phase, window, cx| {
2747 listener(event, phase, &hitbox, window, cx);
2748 })
2749 }
2750
2751 if self.hover_style.is_some()
2752 || self.base_style.mouse_cursor.is_some()
2753 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
2754 {
2755 let hitbox = hitbox.clone();
2756 let hover_state = self.hover_style.as_ref().and_then(|_| {
2757 element_state
2758 .as_ref()
2759 .and_then(|state| state.hover_state.as_ref())
2760 .cloned()
2761 });
2762 let current_view = window.current_view();
2763
2764 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2765 let hovered = hitbox.is_hovered(window);
2766 let was_hovered = hover_state
2767 .as_ref()
2768 .is_some_and(|state| state.borrow().element);
2769 if phase == DispatchPhase::Capture && hovered != was_hovered {
2770 if let Some(hover_state) = &hover_state {
2771 hover_state.borrow_mut().element = hovered;
2772 cx.notify(current_view);
2773 }
2774 }
2775 });
2776 }
2777
2778 if let Some(group_hover) = self.group_hover_style.as_ref() {
2779 if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
2780 let hover_state = element_state
2781 .as_ref()
2782 .and_then(|element| element.hover_state.as_ref())
2783 .cloned();
2784 let current_view = window.current_view();
2785
2786 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2787 let group_hovered = group_hitbox_id.is_hovered(window);
2788 let was_group_hovered = hover_state
2789 .as_ref()
2790 .is_some_and(|state| state.borrow().group);
2791 if phase == DispatchPhase::Capture && group_hovered != was_group_hovered {
2792 if let Some(hover_state) = &hover_state {
2793 hover_state.borrow_mut().group = group_hovered;
2794 }
2795 cx.notify(current_view);
2796 }
2797 });
2798 }
2799 }
2800
2801 let drag_cursor_style = self.base_style.as_ref().mouse_cursor;
2802
2803 let mut drag_listener = mem::take(&mut self.drag_listener);
2804 let drop_listeners = mem::take(&mut self.drop_listeners);
2805 let click_listeners = mem::take(&mut self.click_listeners);
2806 let aux_click_listeners = mem::take(&mut self.aux_click_listeners);
2807 let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
2808
2809 if !drop_listeners.is_empty() {
2810 let hitbox = hitbox.clone();
2811 window.on_mouse_event({
2812 move |_: &MouseUpEvent, phase, window, cx| {
2813 if let Some(drag) = &cx.active_drag
2814 && phase == DispatchPhase::Bubble
2815 && hitbox.is_hovered(window)
2816 {
2817 let drag_state_type = drag.value.as_ref().type_id();
2818 for (drop_state_type, listener) in &drop_listeners {
2819 if *drop_state_type == drag_state_type {
2820 let drag = cx
2821 .active_drag
2822 .take()
2823 .expect("checked for type drag state type above");
2824
2825 let mut can_drop = true;
2826 if let Some(predicate) = &can_drop_predicate {
2827 can_drop = predicate(drag.value.as_ref(), window, cx);
2828 }
2829
2830 if can_drop {
2831 listener(drag.value.as_ref(), window, cx);
2832 window.refresh();
2833 cx.stop_propagation();
2834 }
2835 }
2836 }
2837 }
2838 }
2839 });
2840 }
2841
2842 if let Some(element_state) = element_state {
2843 if !click_listeners.is_empty()
2844 || !aux_click_listeners.is_empty()
2845 || drag_listener.is_some()
2846 {
2847 let pending_mouse_down = element_state
2848 .pending_mouse_down
2849 .get_or_insert_with(Default::default)
2850 .clone();
2851
2852 let pending_keyboard_down = element_state
2853 .pending_keyboard_down
2854 .get_or_insert_with(Default::default)
2855 .clone();
2856
2857 let clicked_state = element_state
2858 .clicked_state
2859 .get_or_insert_with(Default::default)
2860 .clone();
2861
2862 window.on_mouse_event({
2863 let pending_mouse_down = pending_mouse_down.clone();
2864 let hitbox = hitbox.clone();
2865 let has_aux_click_listeners = !aux_click_listeners.is_empty();
2866 move |event: &MouseDownEvent, phase, window, _cx| {
2867 if phase == DispatchPhase::Bubble
2868 && (event.button == MouseButton::Left || has_aux_click_listeners)
2869 && hitbox.is_hovered(window)
2870 {
2871 *pending_mouse_down.borrow_mut() = Some(event.clone());
2872 window.refresh();
2873 }
2874 }
2875 });
2876
2877 window.on_mouse_event({
2878 let pending_mouse_down = pending_mouse_down.clone();
2879 let hitbox = hitbox.clone();
2880 move |event: &MouseMoveEvent, phase, window, cx| {
2881 if phase == DispatchPhase::Capture {
2882 return;
2883 }
2884
2885 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2886 if let Some(mouse_down) = pending_mouse_down.clone()
2887 && !cx.has_active_drag()
2888 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
2889 && let Some((drag_value, drag_listener)) = drag_listener.take()
2890 && mouse_down.button == MouseButton::Left
2891 {
2892 *clicked_state.borrow_mut() = ElementClickedState::default();
2893 let cursor_offset = event.position - hitbox.origin;
2894 let drag =
2895 (drag_listener)(drag_value.as_ref(), cursor_offset, window, cx);
2896 cx.active_drag = Some(AnyDrag {
2897 view: drag,
2898 value: drag_value,
2899 cursor_offset,
2900 cursor_style: drag_cursor_style,
2901 });
2902 pending_mouse_down.take();
2903 window.refresh();
2904 cx.stop_propagation();
2905 }
2906 }
2907 });
2908
2909 if is_focused {
2910 window.on_key_event({
2920 let pending_keyboard_down = pending_keyboard_down.clone();
2921 move |event: &KeyDownEvent, phase, window, _cx| {
2922 if phase.bubble() && !window.default_prevented() {
2923 let stroke = &event.keystroke;
2924 let is_activation_key = (stroke.key.eq("enter")
2925 || stroke.key.eq("space"))
2926 && !stroke.modifiers.modified();
2927 *pending_keyboard_down.borrow_mut() =
2928 is_activation_key.then_some(window.focus_generation);
2929 }
2930 }
2931 });
2932
2933 window.on_key_event({
2935 let click_listeners = click_listeners.clone();
2936 let hitbox = hitbox.clone();
2937 move |event: &KeyUpEvent, phase, window, cx| {
2938 if phase.bubble() && !window.default_prevented() {
2939 let stroke = &event.keystroke;
2940 let keyboard_button = if stroke.key.eq("enter") {
2941 Some(KeyboardButton::Enter)
2942 } else if stroke.key.eq("space") {
2943 Some(KeyboardButton::Space)
2944 } else {
2945 None
2946 };
2947
2948 if let Some(button) = keyboard_button
2949 && !stroke.modifiers.modified()
2950 {
2951 let pending =
2952 std::mem::take(&mut *pending_keyboard_down.borrow_mut());
2953 if pending != Some(window.focus_generation) {
2954 return;
2955 }
2956
2957 let click_event = ClickEvent::Keyboard(KeyboardClickEvent {
2958 button,
2959 bounds: hitbox.bounds,
2960 });
2961
2962 for listener in &click_listeners {
2963 listener(&click_event, window, cx);
2964 }
2965 } else {
2966 *pending_keyboard_down.borrow_mut() = None;
2970 }
2971 }
2972 }
2973 });
2974 }
2975
2976 window.on_mouse_event({
2977 let mut captured_mouse_down = None;
2978 let hitbox = hitbox.clone();
2979 move |event: &MouseUpEvent, phase, window, cx| match phase {
2980 DispatchPhase::Capture => {
2984 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2985 if pending_mouse_down.is_some() && hitbox.is_hovered(window) {
2986 captured_mouse_down = pending_mouse_down.take();
2987 window.refresh();
2988 } else if pending_mouse_down.is_some() {
2989 pending_mouse_down.take();
2995 window.refresh();
2996 }
2997 }
2998 DispatchPhase::Bubble => {
3000 if let Some(mouse_down) = captured_mouse_down.take() {
3001 let btn = mouse_down.button;
3002
3003 let mouse_click = ClickEvent::Mouse(MouseClickEvent {
3004 down: mouse_down,
3005 up: event.clone(),
3006 });
3007
3008 match btn {
3009 MouseButton::Left => {
3010 for listener in &click_listeners {
3011 listener(&mouse_click, window, cx);
3012 }
3013 }
3014 _ => {
3015 for listener in &aux_click_listeners {
3016 listener(&mouse_click, window, cx);
3017 }
3018 }
3019 }
3020 }
3021 }
3022 }
3023 });
3024 }
3025
3026 if let Some(hover_listener) = self.hover_listener.take() {
3027 let was_hovered = element_state
3028 .hover_listener_state
3029 .get_or_insert_with(Default::default)
3030 .clone();
3031 let has_mouse_down = element_state
3032 .pending_mouse_down
3033 .get_or_insert_with(Default::default)
3034 .clone();
3035 let hover_listener = Rc::new(hover_listener);
3036 let hover_listener_state = was_hovered.clone();
3037 let update_hover = move |is_hovered: bool, window: &mut Window, cx: &mut App| {
3038 let mut was_hovered = hover_listener_state.borrow_mut();
3039 if is_hovered != *was_hovered {
3040 *was_hovered = is_hovered;
3041 drop(was_hovered);
3042 hover_listener(&is_hovered, window, cx);
3043 }
3044 };
3045
3046 if has_mouse_down.borrow().is_none() {
3047 let is_hovered = !cx.has_active_drag() && hitbox.is_hovered(window);
3048 if is_hovered != *was_hovered.borrow() {
3049 let update_hover = update_hover.clone();
3050 window.defer(cx, move |window, cx| {
3051 update_hover(is_hovered, window, cx);
3052 });
3053 }
3054 }
3055
3056 window.on_mouse_event({
3057 let update_hover = update_hover.clone();
3058 let hitbox = hitbox.clone();
3059 move |_: &MouseMoveEvent, phase, window, cx| {
3060 if phase == DispatchPhase::Bubble {
3061 let is_hovered = has_mouse_down.borrow().is_none()
3062 && !cx.has_active_drag()
3063 && hitbox.is_hovered(window);
3064 update_hover(is_hovered, window, cx);
3065 }
3066 }
3067 });
3068
3069 window.on_mouse_event(move |_: &MouseExitEvent, phase, window, cx| {
3072 if phase == DispatchPhase::Bubble {
3073 update_hover(false, window, cx);
3074 }
3075 });
3076 }
3077
3078 if let Some(tooltip_builder) = self.tooltip_builder.take() {
3079 let active_tooltip = element_state
3080 .active_tooltip
3081 .get_or_insert_with(Default::default)
3082 .clone();
3083 let pending_mouse_down = element_state
3084 .pending_mouse_down
3085 .get_or_insert_with(Default::default)
3086 .clone();
3087
3088 let tooltip_is_hoverable = tooltip_builder.hoverable;
3089 let build_tooltip = Rc::new(move |window: &mut Window, cx: &mut App| {
3090 Some(((tooltip_builder.build)(window, cx), tooltip_is_hoverable))
3091 });
3092 let check_is_hovered_during_prepaint = Rc::new({
3094 let pending_mouse_down = pending_mouse_down.clone();
3095 let source_bounds = hitbox.bounds;
3096 move |window: &Window| {
3097 !window.last_input_was_keyboard()
3098 && pending_mouse_down.borrow().is_none()
3099 && source_bounds.contains(&window.mouse_position())
3100 }
3101 });
3102 let check_is_hovered = Rc::new({
3103 let hitbox = hitbox.clone();
3104 move |window: &Window| {
3105 pending_mouse_down.borrow().is_none() && hitbox.is_hovered(window)
3106 }
3107 });
3108 register_tooltip_mouse_handlers(
3109 &active_tooltip,
3110 self.tooltip_id,
3111 build_tooltip,
3112 check_is_hovered,
3113 check_is_hovered_during_prepaint,
3114 self.tooltip_show_delay,
3115 window,
3116 );
3117 }
3118
3119 let active_state = element_state
3122 .clicked_state
3123 .get_or_insert_with(Default::default)
3124 .clone();
3125
3126 {
3127 let active_state = active_state.clone();
3128 window.on_mouse_event(move |_: &MouseUpEvent, phase, window, _cx| {
3129 if phase == DispatchPhase::Capture && active_state.borrow().is_clicked() {
3130 *active_state.borrow_mut() = ElementClickedState::default();
3131 window.refresh();
3132 }
3133 });
3134 }
3135
3136 {
3137 let active_group_hitbox = self
3138 .group_active_style
3139 .as_ref()
3140 .and_then(|group_active| GroupHitboxes::get(&group_active.group, cx));
3141 let hitbox = hitbox.clone();
3142 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _cx| {
3143 if phase == DispatchPhase::Bubble && !window.default_prevented() {
3144 let group_hovered = active_group_hitbox
3145 .is_some_and(|group_hitbox_id| group_hitbox_id.is_hovered(window));
3146 let element_hovered = hitbox.is_hovered(window);
3147 if group_hovered || element_hovered {
3148 *active_state.borrow_mut() = ElementClickedState {
3149 group: group_hovered,
3150 element: element_hovered,
3151 };
3152 window.refresh();
3153 }
3154 }
3155 });
3156 }
3157 }
3158 }
3159
3160 fn paint_keyboard_listeners(&mut self, window: &mut Window, _cx: &mut App) {
3161 let key_down_listeners = mem::take(&mut self.key_down_listeners);
3162 let key_up_listeners = mem::take(&mut self.key_up_listeners);
3163 let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners);
3164 let action_listeners = mem::take(&mut self.action_listeners);
3165 if let Some(context) = self.key_context.clone() {
3166 window.set_key_context(context);
3167 }
3168
3169 for listener in key_down_listeners {
3170 window.on_key_event(move |event: &KeyDownEvent, phase, window, cx| {
3171 listener(event, phase, window, cx);
3172 })
3173 }
3174
3175 for listener in key_up_listeners {
3176 window.on_key_event(move |event: &KeyUpEvent, phase, window, cx| {
3177 listener(event, phase, window, cx);
3178 })
3179 }
3180
3181 for listener in modifiers_changed_listeners {
3182 window.on_modifiers_changed(move |event: &ModifiersChangedEvent, window, cx| {
3183 listener(event, window, cx);
3184 })
3185 }
3186
3187 for (action_type, listener) in action_listeners {
3188 window.on_action(action_type, listener)
3189 }
3190 }
3191
3192 fn paint_hover_group_handler(&self, window: &mut Window, cx: &mut App) {
3193 let group_hitbox = self
3194 .group_hover_style
3195 .as_ref()
3196 .and_then(|group_hover| GroupHitboxes::get(&group_hover.group, cx));
3197
3198 if let Some(group_hitbox) = group_hitbox {
3199 let was_hovered = group_hitbox.is_hovered(window);
3200 let current_view = window.current_view();
3201 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
3202 let hovered = group_hitbox.is_hovered(window);
3203 if phase == DispatchPhase::Capture && hovered != was_hovered {
3204 cx.notify(current_view);
3205 }
3206 });
3207 }
3208 }
3209
3210 fn paint_scroll_listener(
3211 &self,
3212 hitbox: &Hitbox,
3213 style: &Style,
3214 window: &mut Window,
3215 _cx: &mut App,
3216 ) {
3217 if let Some(scroll_offset) = self.scroll_offset.clone() {
3218 let overflow = style.overflow;
3219 let allow_concurrent_scroll = style.allow_concurrent_scroll;
3220 let restrict_scroll_to_axis = style.restrict_scroll_to_axis;
3221 let line_height = window.line_height();
3222 let hitbox = hitbox.clone();
3223 let current_view = window.current_view();
3224 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
3225 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
3226 let mut scroll_offset = scroll_offset.borrow_mut();
3227 let old_scroll_offset = *scroll_offset;
3228 let delta = event.delta.pixel_delta(line_height);
3229
3230 let mut delta_x = Pixels::ZERO;
3231 if overflow.x == Overflow::Scroll {
3232 if !delta.x.is_zero() {
3233 delta_x = delta.x;
3234 } else if !restrict_scroll_to_axis && overflow.y != Overflow::Scroll {
3235 delta_x = delta.y;
3236 }
3237 }
3238 let mut delta_y = Pixels::ZERO;
3239 if overflow.y == Overflow::Scroll {
3240 if !delta.y.is_zero() {
3241 delta_y = delta.y;
3242 } else if !restrict_scroll_to_axis && overflow.x != Overflow::Scroll {
3243 delta_y = delta.x;
3244 }
3245 }
3246 if !allow_concurrent_scroll && !delta_x.is_zero() && !delta_y.is_zero() {
3247 if delta_x.abs() > delta_y.abs() {
3248 delta_y = Pixels::ZERO;
3249 } else {
3250 delta_x = Pixels::ZERO;
3251 }
3252 }
3253 scroll_offset.y += delta_y;
3254 scroll_offset.x += delta_x;
3255 if *scroll_offset != old_scroll_offset {
3256 cx.notify(current_view);
3257 }
3258 }
3259 });
3260 }
3261 }
3262
3263 pub fn compute_style(
3265 &self,
3266 global_id: Option<&GlobalElementId>,
3267 hitbox: Option<&Hitbox>,
3268 window: &mut Window,
3269 cx: &mut App,
3270 ) -> Style {
3271 window.with_optional_element_state(global_id, |element_state, window| {
3272 let mut element_state =
3273 element_state.map(|element_state| element_state.unwrap_or_default());
3274 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
3275 (style, element_state)
3276 })
3277 }
3278
3279 fn compute_style_internal(
3281 &self,
3282 hitbox: Option<&Hitbox>,
3283 element_state: Option<&mut InteractiveElementState>,
3284 window: &mut Window,
3285 cx: &mut App,
3286 ) -> Style {
3287 let mut style = Style::default();
3288 style.refine(&self.base_style);
3289
3290 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
3291 if let Some(in_focus_style) = self.in_focus_style.as_ref()
3292 && focus_handle.within_focused(window, cx)
3293 {
3294 style.refine(in_focus_style);
3295 }
3296
3297 if let Some(focus_style) = self.focus_style.as_ref()
3298 && focus_handle.is_focused(window)
3299 {
3300 style.refine(focus_style);
3301 }
3302
3303 if let Some(focus_visible_style) = self.focus_visible_style.as_ref()
3304 && focus_handle.is_focused(window)
3305 && window.last_input_was_keyboard()
3306 {
3307 style.refine(focus_visible_style);
3308 }
3309 }
3310
3311 if !cx.has_active_drag() {
3312 if let Some(group_hover) = self.group_hover_style.as_ref() {
3313 let is_group_hovered =
3314 if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
3315 group_hitbox_id.is_hovered(window)
3316 } else if let Some(element_state) = element_state.as_ref() {
3317 element_state
3318 .hover_state
3319 .as_ref()
3320 .map(|state| state.borrow().group)
3321 .unwrap_or(false)
3322 } else {
3323 false
3324 };
3325
3326 if is_group_hovered {
3327 style.refine(&group_hover.style);
3328 }
3329 }
3330
3331 if let Some(hover_style) = self.hover_style.as_ref() {
3332 let is_hovered = if let Some(hitbox) = hitbox {
3333 hitbox.is_hovered(window)
3334 } else if let Some(element_state) = element_state.as_ref() {
3335 element_state
3336 .hover_state
3337 .as_ref()
3338 .map(|state| state.borrow().element)
3339 .unwrap_or(false)
3340 } else {
3341 false
3342 };
3343
3344 if is_hovered {
3345 style.refine(hover_style);
3346 }
3347 }
3348 }
3349
3350 if let Some(hitbox) = hitbox {
3351 if let Some(drag) = cx.active_drag.take() {
3352 let mut can_drop = true;
3353 if let Some(can_drop_predicate) = &self.can_drop_predicate {
3354 can_drop = can_drop_predicate(drag.value.as_ref(), window, cx);
3355 }
3356
3357 if can_drop {
3358 for (state_type, group_drag_style) in &self.group_drag_over_styles {
3359 if let Some(group_hitbox_id) =
3360 GroupHitboxes::get(&group_drag_style.group, cx)
3361 && *state_type == drag.value.as_ref().type_id()
3362 && group_hitbox_id.is_hovered(window)
3363 {
3364 style.refine(&group_drag_style.style);
3365 }
3366 }
3367
3368 for (state_type, build_drag_over_style) in &self.drag_over_styles {
3369 if *state_type == drag.value.as_ref().type_id() && hitbox.is_hovered(window)
3370 {
3371 style.refine(&build_drag_over_style(drag.value.as_ref(), window, cx));
3372 }
3373 }
3374 }
3375
3376 style.mouse_cursor = drag.cursor_style;
3377 cx.active_drag = Some(drag);
3378 }
3379 }
3380
3381 if let Some(element_state) = element_state {
3382 let clicked_state = element_state
3383 .clicked_state
3384 .get_or_insert_with(Default::default)
3385 .borrow();
3386 if clicked_state.group
3387 && let Some(group) = self.group_active_style.as_ref()
3388 {
3389 style.refine(&group.style)
3390 }
3391
3392 if let Some(active_style) = self.active_style.as_ref()
3393 && clicked_state.element
3394 {
3395 style.refine(active_style)
3396 }
3397 }
3398
3399 style
3400 }
3401
3402 pub(crate) fn write_a11y_info(&self, node: &mut accesskit::Node) {
3403 if let Some(label) = &self.aria.label {
3404 node.set_label(label.to_string());
3405 }
3406 if let Some(description) = &self.aria.description {
3407 node.set_description(description.to_string());
3408 }
3409 if let Some(keyshortcuts) = &self.aria.keyshortcuts {
3410 node.set_keyboard_shortcut(keyshortcuts.to_string());
3411 }
3412 if let Some(selected) = self.aria.selected {
3413 node.set_selected(selected);
3414 }
3415 if let Some(expanded) = self.aria.expanded {
3416 node.set_expanded(expanded);
3417 }
3418 if let Some(toggled) = self.aria.toggled {
3419 node.set_toggled(toggled);
3420 }
3421 if let Some(value) = self.aria.numeric_value {
3422 node.set_numeric_value(value);
3423 }
3424 if let Some(value) = self.aria.min_numeric_value {
3425 node.set_min_numeric_value(value);
3426 }
3427 if let Some(value) = self.aria.max_numeric_value {
3428 node.set_max_numeric_value(value);
3429 }
3430 if let Some(step) = self.aria.numeric_value_step {
3431 node.set_numeric_value_step(step);
3432 }
3433 if let Some(value) = &self.aria.value {
3434 node.set_value(value.to_string());
3435 }
3436 if let Some(placeholder) = &self.aria.placeholder {
3437 node.set_placeholder(placeholder.to_string());
3438 }
3439 if let Some(orientation) = self.aria.orientation {
3440 node.set_orientation(orientation);
3441 }
3442 if let Some(level) = self.aria.level {
3443 node.set_level(level);
3444 }
3445 if let Some(position) = self.aria.position_in_set {
3446 node.set_position_in_set(position);
3447 }
3448 if let Some(size) = self.aria.size_of_set {
3449 node.set_size_of_set(size);
3450 }
3451 if let Some(index) = self.aria.row_index {
3452 node.set_row_index(index);
3453 }
3454 if let Some(index) = self.aria.column_index {
3455 node.set_column_index(index);
3456 }
3457 if let Some(count) = self.aria.row_count {
3458 node.set_row_count(count);
3459 }
3460 if let Some(count) = self.aria.column_count {
3461 node.set_column_count(count);
3462 }
3463 if !self.click_listeners.is_empty() {
3464 node.add_action(accesskit::Action::Click);
3465 }
3466 if self.tracked_focus_handle.is_some() || self.focusable {
3467 node.add_action(accesskit::Action::Focus);
3468 }
3469 for (action, _) in &self.a11y_action_listeners {
3470 node.add_action(*action);
3471 }
3472 }
3473}
3474
3475#[derive(Default)]
3477pub struct InteractiveElementState {
3478 pub(crate) focus_handle: Option<FocusHandle>,
3479 pub(crate) clicked_state: Option<Rc<RefCell<ElementClickedState>>>,
3480 pub(crate) hover_state: Option<Rc<RefCell<ElementHoverState>>>,
3481 pub(crate) hover_listener_state: Option<Rc<RefCell<bool>>>,
3482 pub(crate) pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
3483 pub(crate) pending_keyboard_down: Option<Rc<RefCell<Option<u64>>>>,
3489 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
3490 pub(crate) active_tooltip: Option<Rc<RefCell<Option<ActiveTooltip>>>>,
3491}
3492
3493#[derive(Copy, Clone, Default, Eq, PartialEq)]
3495pub struct ElementClickedState {
3496 pub group: bool,
3498
3499 pub element: bool,
3501}
3502
3503impl ElementClickedState {
3504 fn is_clicked(&self) -> bool {
3505 self.group || self.element
3506 }
3507}
3508
3509#[derive(Copy, Clone, Default, Eq, PartialEq)]
3511pub struct ElementHoverState {
3512 pub group: bool,
3514
3515 pub element: bool,
3517}
3518
3519pub(crate) enum ActiveTooltip {
3520 WaitingForShow { _task: Task<()> },
3522 Visible {
3524 tooltip: AnyTooltip,
3525 is_hoverable: bool,
3526 },
3527 WaitingForHide {
3529 tooltip: AnyTooltip,
3530 _task: Task<()>,
3531 },
3532}
3533
3534pub(crate) fn clear_active_tooltip(
3535 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3536 window: &mut Window,
3537) {
3538 match active_tooltip.borrow_mut().take() {
3539 None => {}
3540 Some(ActiveTooltip::WaitingForShow { .. }) => {}
3541 Some(ActiveTooltip::Visible { .. }) => window.refresh(),
3542 Some(ActiveTooltip::WaitingForHide { .. }) => window.refresh(),
3543 }
3544}
3545
3546pub(crate) fn clear_active_tooltip_if_not_hoverable(
3547 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3548 window: &mut Window,
3549) {
3550 let should_clear = match active_tooltip.borrow().as_ref() {
3551 None => false,
3552 Some(ActiveTooltip::WaitingForShow { .. }) => false,
3553 Some(ActiveTooltip::Visible { is_hoverable, .. }) => !is_hoverable,
3554 Some(ActiveTooltip::WaitingForHide { .. }) => false,
3555 };
3556 if should_clear {
3557 active_tooltip.borrow_mut().take();
3558 window.refresh();
3559 }
3560}
3561
3562pub(crate) fn set_tooltip_on_window(
3563 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3564 window: &mut Window,
3565) -> Option<TooltipId> {
3566 let tooltip = match active_tooltip.borrow().as_ref() {
3567 None => return None,
3568 Some(ActiveTooltip::WaitingForShow { .. }) => return None,
3569 Some(ActiveTooltip::Visible { tooltip, .. }) => tooltip.clone(),
3570 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => tooltip.clone(),
3571 };
3572 Some(window.set_tooltip(tooltip))
3573}
3574
3575pub(crate) fn register_tooltip_mouse_handlers(
3576 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3577 tooltip_id: Option<TooltipId>,
3578 build_tooltip: Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3579 check_is_hovered: Rc<dyn Fn(&Window) -> bool>,
3580 check_is_hovered_during_prepaint: Rc<dyn Fn(&Window) -> bool>,
3581 show_delay: Option<Duration>,
3582 window: &mut Window,
3583) {
3584 let current_view = window.current_view();
3585 let show_delay = show_delay.unwrap_or(DEFAULT_TOOLTIP_SHOW_DELAY);
3586
3587 window.on_mouse_event({
3588 let active_tooltip = active_tooltip.clone();
3589 let build_tooltip = build_tooltip.clone();
3590 let check_is_hovered = check_is_hovered.clone();
3591 move |_: &MouseMoveEvent, phase, window, cx| {
3592 handle_tooltip_mouse_move(
3593 &active_tooltip,
3594 &build_tooltip,
3595 &check_is_hovered,
3596 &check_is_hovered_during_prepaint,
3597 tooltip_id,
3598 current_view,
3599 phase,
3600 show_delay,
3601 window,
3602 cx,
3603 )
3604 }
3605 });
3606
3607 window.on_mouse_event({
3608 let active_tooltip = active_tooltip.clone();
3609 move |_: &MouseDownEvent, _phase, window: &mut Window, _cx| {
3610 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3611 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3612 }
3613 }
3614 });
3615
3616 window.on_mouse_event({
3617 let active_tooltip = active_tooltip.clone();
3618 move |_: &ScrollWheelEvent, _phase, window: &mut Window, _cx| {
3619 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3620 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3621 }
3622 }
3623 });
3624}
3625
3626fn handle_tooltip_mouse_move(
3635 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3636 build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3637 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3638 check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
3639 tooltip_id: Option<TooltipId>,
3640 current_view: EntityId,
3641 phase: DispatchPhase,
3642 show_delay: Duration,
3643 window: &mut Window,
3644 cx: &mut App,
3645) {
3646 enum Action {
3649 None,
3650 CancelShow,
3651 ScheduleShow,
3652 CheckVisible,
3653 }
3654
3655 let action = match active_tooltip.borrow().as_ref() {
3656 None => {
3657 let is_hovered = check_is_hovered(window);
3658 if is_hovered && phase.bubble() {
3659 Action::ScheduleShow
3660 } else {
3661 Action::None
3662 }
3663 }
3664 Some(ActiveTooltip::WaitingForShow { .. }) => {
3665 let is_hovered = check_is_hovered(window);
3666 if is_hovered {
3667 Action::None
3668 } else {
3669 Action::CancelShow
3670 }
3671 }
3672 Some(ActiveTooltip::Visible { is_hoverable, .. }) => {
3673 if phase.capture()
3674 && !check_is_hovered(window)
3675 && (!*is_hoverable
3676 || !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3677 {
3678 Action::CheckVisible
3679 } else {
3680 Action::None
3681 }
3682 }
3683 Some(ActiveTooltip::WaitingForHide { .. }) => {
3684 if phase.capture()
3685 && (check_is_hovered(window)
3686 || tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3687 {
3688 Action::CheckVisible
3689 } else {
3690 Action::None
3691 }
3692 }
3693 };
3694
3695 match action {
3696 Action::None => {}
3697 Action::CancelShow => {
3698 active_tooltip.borrow_mut().take();
3700 }
3701 Action::ScheduleShow => {
3702 let delayed_show_task = window.spawn(cx, {
3703 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3704 let build_tooltip = build_tooltip.clone();
3705 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3706 async move |cx| {
3707 cx.background_executor().timer(show_delay).await;
3708 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3709 return;
3710 };
3711 cx.update(|window, cx| {
3712 let new_tooltip =
3713 build_tooltip(window, cx).map(|(view, tooltip_is_hoverable)| {
3714 let weak_active_tooltip = Rc::downgrade(&active_tooltip);
3715 ActiveTooltip::Visible {
3716 tooltip: AnyTooltip {
3717 view,
3718 mouse_position: window.mouse_position(),
3719 check_visible_and_update: Rc::new(
3720 move |tooltip_bounds, window, cx| {
3721 let Some(active_tooltip) =
3722 weak_active_tooltip.upgrade()
3723 else {
3724 return false;
3725 };
3726 handle_tooltip_check_visible_and_update(
3727 &active_tooltip,
3728 tooltip_is_hoverable,
3729 &check_is_hovered_during_prepaint,
3730 tooltip_bounds,
3731 window,
3732 cx,
3733 )
3734 },
3735 ),
3736 },
3737 is_hoverable: tooltip_is_hoverable,
3738 }
3739 });
3740 *active_tooltip.borrow_mut() = new_tooltip;
3741 window.refresh();
3742 })
3743 .ok();
3744 }
3745 });
3746 active_tooltip
3747 .borrow_mut()
3748 .replace(ActiveTooltip::WaitingForShow {
3749 _task: delayed_show_task,
3750 });
3751 }
3752 Action::CheckVisible => cx.notify(current_view),
3753 }
3754}
3755
3756fn handle_tooltip_check_visible_and_update(
3760 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3761 tooltip_is_hoverable: bool,
3762 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3763 tooltip_bounds: Bounds<Pixels>,
3764 window: &mut Window,
3765 cx: &mut App,
3766) -> bool {
3767 enum Action {
3770 None,
3771 Hide,
3772 ScheduleHide(AnyTooltip),
3773 CancelHide(AnyTooltip),
3774 }
3775
3776 let is_hovered = check_is_hovered(window)
3777 || (tooltip_is_hoverable && tooltip_bounds.contains(&window.mouse_position()));
3778 let action = match active_tooltip.borrow().as_ref() {
3779 Some(ActiveTooltip::Visible { tooltip, .. }) => {
3780 if is_hovered {
3781 Action::None
3782 } else {
3783 if tooltip_is_hoverable {
3784 Action::ScheduleHide(tooltip.clone())
3785 } else {
3786 Action::Hide
3787 }
3788 }
3789 }
3790 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => {
3791 if is_hovered {
3792 Action::CancelHide(tooltip.clone())
3793 } else {
3794 Action::None
3795 }
3796 }
3797 None | Some(ActiveTooltip::WaitingForShow { .. }) => Action::None,
3798 };
3799
3800 match action {
3801 Action::None => {}
3802 Action::Hide => clear_active_tooltip(active_tooltip, window),
3803 Action::ScheduleHide(tooltip) => {
3804 let delayed_hide_task = window.spawn(cx, {
3805 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3806 async move |cx| {
3807 cx.background_executor()
3808 .timer(HOVERABLE_TOOLTIP_HIDE_DELAY)
3809 .await;
3810 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3811 return;
3812 };
3813 if active_tooltip.borrow_mut().take().is_some() {
3814 cx.update(|window, _cx| window.refresh()).ok();
3815 }
3816 }
3817 });
3818 active_tooltip
3819 .borrow_mut()
3820 .replace(ActiveTooltip::WaitingForHide {
3821 tooltip,
3822 _task: delayed_hide_task,
3823 });
3824 }
3825 Action::CancelHide(tooltip) => {
3826 active_tooltip.borrow_mut().replace(ActiveTooltip::Visible {
3828 tooltip,
3829 is_hoverable: true,
3830 });
3831 }
3832 }
3833
3834 active_tooltip.borrow().is_some()
3835}
3836
3837#[derive(Default)]
3838pub(crate) struct GroupHitboxes(HashMap<SharedString, SmallVec<[HitboxId; 1]>>);
3839
3840impl Global for GroupHitboxes {}
3841
3842impl GroupHitboxes {
3843 pub fn get(name: &SharedString, cx: &mut App) -> Option<HitboxId> {
3844 cx.default_global::<Self>()
3845 .0
3846 .get(name)
3847 .and_then(|bounds_stack| bounds_stack.last())
3848 .cloned()
3849 }
3850
3851 pub fn push(name: SharedString, hitbox_id: HitboxId, cx: &mut App) {
3852 cx.default_global::<Self>()
3853 .0
3854 .entry(name)
3855 .or_default()
3856 .push(hitbox_id);
3857 }
3858
3859 pub fn pop(name: &SharedString, cx: &mut App) {
3860 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
3861 }
3862}
3863
3864pub struct Stateful<E> {
3866 pub(crate) element: E,
3867}
3868
3869impl<E> Styled for Stateful<E>
3870where
3871 E: Styled,
3872{
3873 fn style(&mut self) -> &mut StyleRefinement {
3874 self.element.style()
3875 }
3876}
3877
3878impl<E> StatefulInteractiveElement for Stateful<E>
3879where
3880 E: Element,
3881 Self: InteractiveElement,
3882{
3883}
3884
3885impl<E> InteractiveElement for Stateful<E>
3886where
3887 E: InteractiveElement,
3888{
3889 fn interactivity(&mut self) -> &mut Interactivity {
3890 self.element.interactivity()
3891 }
3892}
3893
3894impl<E> Element for Stateful<E>
3895where
3896 E: Element,
3897{
3898 type RequestLayoutState = E::RequestLayoutState;
3899 type PrepaintState = E::PrepaintState;
3900
3901 fn id(&self) -> Option<ElementId> {
3902 self.element.id()
3903 }
3904
3905 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
3906 self.element.source_location()
3907 }
3908
3909 fn a11y_role(&self) -> Option<accesskit::Role> {
3910 self.element.a11y_role()
3911 }
3912
3913 fn write_a11y_info(&self, node: &mut accesskit::Node) {
3914 self.element.write_a11y_info(node);
3915 }
3916
3917 fn a11y_synthetic_children(
3918 &mut self,
3919 prepaint: &mut Self::PrepaintState,
3920 builder: &mut crate::A11ySubtreeBuilder,
3921 ) {
3922 self.element.a11y_synthetic_children(prepaint, builder);
3923 }
3924
3925 fn request_layout(
3926 &mut self,
3927 id: Option<&GlobalElementId>,
3928 inspector_id: Option<&InspectorElementId>,
3929 window: &mut Window,
3930 cx: &mut App,
3931 ) -> (LayoutId, Self::RequestLayoutState) {
3932 self.element.request_layout(id, inspector_id, window, cx)
3933 }
3934
3935 fn prepaint(
3936 &mut self,
3937 id: Option<&GlobalElementId>,
3938 inspector_id: Option<&InspectorElementId>,
3939 bounds: Bounds<Pixels>,
3940 state: &mut Self::RequestLayoutState,
3941 window: &mut Window,
3942 cx: &mut App,
3943 ) -> E::PrepaintState {
3944 self.element
3945 .prepaint(id, inspector_id, bounds, state, window, cx)
3946 }
3947
3948 fn paint(
3949 &mut self,
3950 id: Option<&GlobalElementId>,
3951 inspector_id: Option<&InspectorElementId>,
3952 bounds: Bounds<Pixels>,
3953 request_layout: &mut Self::RequestLayoutState,
3954 prepaint: &mut Self::PrepaintState,
3955 window: &mut Window,
3956 cx: &mut App,
3957 ) {
3958 self.element.paint(
3959 id,
3960 inspector_id,
3961 bounds,
3962 request_layout,
3963 prepaint,
3964 window,
3965 cx,
3966 );
3967 }
3968
3969 #[cfg(feature = "dom-backend")]
3976 fn dom(
3977 &self,
3978 bounds: Bounds<Pixels>,
3979 window: &mut Window,
3980 cx: &mut App,
3981 ) -> Option<crate::DomNode> {
3982 self.element.dom(bounds, window, cx)
3983 }
3984}
3985
3986impl<E> IntoElement for Stateful<E>
3987where
3988 E: Element,
3989{
3990 type Element = Self;
3991
3992 fn into_element(self) -> Self::Element {
3993 self
3994 }
3995}
3996
3997impl<E> ParentElement for Stateful<E>
3998where
3999 E: ParentElement,
4000{
4001 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
4002 self.element.extend(elements)
4003 }
4004}
4005
4006#[derive(Clone)]
4009pub struct ScrollAnchor {
4010 handle: ScrollHandle,
4011 last_origin: Rc<RefCell<Point<Pixels>>>,
4012}
4013
4014impl ScrollAnchor {
4015 pub fn for_handle(handle: ScrollHandle) -> Self {
4017 Self {
4018 handle,
4019 last_origin: Default::default(),
4020 }
4021 }
4022 pub fn scroll_to(&self, window: &mut Window, _cx: &mut App) {
4024 let this = self.clone();
4025
4026 window.on_next_frame(move |_, _| {
4027 let viewport_bounds = this.handle.bounds();
4028 let self_bounds = *this.last_origin.borrow();
4029 this.handle.set_offset(viewport_bounds.origin - self_bounds);
4030 });
4031 }
4032}
4033
4034#[derive(Default, Debug)]
4035struct ScrollHandleState {
4036 offset: Rc<RefCell<Point<Pixels>>>,
4037 bounds: Bounds<Pixels>,
4038 max_offset: Point<Pixels>,
4039 child_bounds: Vec<Bounds<Pixels>>,
4040 scroll_to_bottom: bool,
4041 overflow: Point<Overflow>,
4042 active_item: Option<ScrollActiveItem>,
4043}
4044
4045#[derive(Default, Debug, Clone, Copy)]
4046struct ScrollActiveItem {
4047 index: usize,
4048 strategy: ScrollStrategy,
4049}
4050
4051#[derive(Default, Debug, Clone, Copy)]
4052enum ScrollStrategy {
4053 #[default]
4054 FirstVisible,
4055 Top,
4056}
4057
4058#[derive(Clone, Debug)]
4061pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
4062
4063impl Default for ScrollHandle {
4064 fn default() -> Self {
4065 Self::new()
4066 }
4067}
4068
4069impl ScrollHandle {
4070 pub fn new() -> Self {
4072 Self(Rc::default())
4073 }
4074
4075 pub fn offset(&self) -> Point<Pixels> {
4077 *self.0.borrow().offset.borrow()
4078 }
4079
4080 pub fn max_offset(&self) -> Point<Pixels> {
4082 self.0.borrow().max_offset
4083 }
4084
4085 pub fn top_item(&self) -> usize {
4087 let state = self.0.borrow();
4088 let top = state.bounds.top() - state.offset.borrow().y;
4089
4090 match state.child_bounds.binary_search_by(|bounds| {
4091 if top < bounds.top() {
4092 Ordering::Greater
4093 } else if top > bounds.bottom() {
4094 Ordering::Less
4095 } else {
4096 Ordering::Equal
4097 }
4098 }) {
4099 Ok(ix) => ix,
4100 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
4101 }
4102 }
4103
4104 pub fn bottom_item(&self) -> usize {
4106 let state = self.0.borrow();
4107 let bottom = state.bounds.bottom() - state.offset.borrow().y;
4108
4109 match state.child_bounds.binary_search_by(|bounds| {
4110 if bottom < bounds.top() {
4111 Ordering::Greater
4112 } else if bottom > bounds.bottom() {
4113 Ordering::Less
4114 } else {
4115 Ordering::Equal
4116 }
4117 }) {
4118 Ok(ix) => ix,
4119 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
4120 }
4121 }
4122
4123 pub fn bounds(&self) -> Bounds<Pixels> {
4125 self.0.borrow().bounds
4126 }
4127
4128 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
4130 self.0.borrow().child_bounds.get(ix).cloned()
4131 }
4132
4133 pub fn scroll_to_item(&self, ix: usize) {
4135 let mut state = self.0.borrow_mut();
4136 state.active_item = Some(ScrollActiveItem {
4137 index: ix,
4138 strategy: ScrollStrategy::default(),
4139 });
4140 }
4141
4142 pub fn scroll_to_top_of_item(&self, ix: usize) {
4145 let mut state = self.0.borrow_mut();
4146 state.active_item = Some(ScrollActiveItem {
4147 index: ix,
4148 strategy: ScrollStrategy::Top,
4149 });
4150 }
4151
4152 fn scroll_to_active_item(&self) {
4154 let mut state = self.0.borrow_mut();
4155
4156 let Some(active_item) = state.active_item else {
4157 return;
4158 };
4159
4160 let active_item = match state.child_bounds.get(active_item.index) {
4161 Some(bounds) => {
4162 let mut scroll_offset = state.offset.borrow_mut();
4163
4164 match active_item.strategy {
4165 ScrollStrategy::FirstVisible => {
4166 if state.overflow.y == Overflow::Scroll {
4167 let child_height = bounds.size.height;
4168 let viewport_height = state.bounds.size.height;
4169 if child_height > viewport_height {
4170 scroll_offset.y = state.bounds.top() - bounds.top();
4171 } else if bounds.top() + scroll_offset.y < state.bounds.top() {
4172 scroll_offset.y = state.bounds.top() - bounds.top();
4173 } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
4174 scroll_offset.y = state.bounds.bottom() - bounds.bottom();
4175 }
4176 }
4177 }
4178 ScrollStrategy::Top => {
4179 scroll_offset.y = state.bounds.top() - bounds.top();
4180 }
4181 }
4182
4183 if state.overflow.x == Overflow::Scroll {
4184 let child_width = bounds.size.width;
4185 let viewport_width = state.bounds.size.width;
4186 if child_width > viewport_width {
4187 scroll_offset.x = state.bounds.left() - bounds.left();
4188 } else if bounds.left() + scroll_offset.x < state.bounds.left() {
4189 scroll_offset.x = state.bounds.left() - bounds.left();
4190 } else if bounds.right() + scroll_offset.x > state.bounds.right() {
4191 scroll_offset.x = state.bounds.right() - bounds.right();
4192 }
4193 }
4194 None
4195 }
4196 None => Some(active_item),
4197 };
4198 state.active_item = active_item;
4199 }
4200
4201 pub fn scroll_to_bottom(&self) {
4203 let mut state = self.0.borrow_mut();
4204 state.scroll_to_bottom = true;
4205 }
4206
4207 pub fn set_offset(&self, mut position: Point<Pixels>) {
4210 let state = self.0.borrow();
4211 *state.offset.borrow_mut() = position;
4212 }
4213
4214 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
4216 let ix = self.top_item();
4217 let state = self.0.borrow();
4218
4219 if let Some(child_bounds) = state.child_bounds.get(ix) {
4220 (
4221 ix,
4222 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
4223 )
4224 } else {
4225 (ix, px(0.))
4226 }
4227 }
4228
4229 pub fn logical_scroll_bottom(&self) -> (usize, Pixels) {
4231 let ix = self.bottom_item();
4232 let state = self.0.borrow();
4233
4234 if let Some(child_bounds) = state.child_bounds.get(ix) {
4235 (
4236 ix,
4237 child_bounds.bottom() + state.offset.borrow().y - state.bounds.bottom(),
4238 )
4239 } else {
4240 (ix, px(0.))
4241 }
4242 }
4243
4244 pub fn children_count(&self) -> usize {
4246 self.0.borrow().child_bounds.len()
4247 }
4248}
4249
4250#[cfg(test)]
4251mod tests {
4252 use super::*;
4253 use crate::{
4254 AnyWindowHandle, AppContext as _, Context, InputEvent, Keystroke, MouseMoveEvent,
4255 TestAppContext, util::FluentBuilder as _,
4256 };
4257 use std::rc::Weak;
4258
4259 struct TestTooltipView;
4260
4261 impl Render for TestTooltipView {
4262 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4263 div().w(px(20.)).h(px(20.)).child("tooltip")
4264 }
4265 }
4266
4267 type CapturedActiveTooltip = Rc<RefCell<Option<Weak<RefCell<Option<ActiveTooltip>>>>>>;
4268
4269 struct TooltipCaptureElement {
4270 child: AnyElement,
4271 captured_active_tooltip: CapturedActiveTooltip,
4272 }
4273
4274 impl IntoElement for TooltipCaptureElement {
4275 type Element = Self;
4276
4277 fn into_element(self) -> Self::Element {
4278 self
4279 }
4280 }
4281
4282 impl Element for TooltipCaptureElement {
4283 type RequestLayoutState = ();
4284 type PrepaintState = ();
4285
4286 fn id(&self) -> Option<ElementId> {
4287 None
4288 }
4289
4290 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
4291 None
4292 }
4293
4294 fn request_layout(
4295 &mut self,
4296 _id: Option<&GlobalElementId>,
4297 _inspector_id: Option<&InspectorElementId>,
4298 window: &mut Window,
4299 cx: &mut App,
4300 ) -> (LayoutId, Self::RequestLayoutState) {
4301 (self.child.request_layout(window, cx), ())
4302 }
4303
4304 fn prepaint(
4305 &mut self,
4306 _id: Option<&GlobalElementId>,
4307 _inspector_id: Option<&InspectorElementId>,
4308 _bounds: Bounds<Pixels>,
4309 _request_layout: &mut Self::RequestLayoutState,
4310 window: &mut Window,
4311 cx: &mut App,
4312 ) -> Self::PrepaintState {
4313 self.child.prepaint(window, cx);
4314 }
4315
4316 fn paint(
4317 &mut self,
4318 _id: Option<&GlobalElementId>,
4319 _inspector_id: Option<&InspectorElementId>,
4320 _bounds: Bounds<Pixels>,
4321 _request_layout: &mut Self::RequestLayoutState,
4322 _prepaint: &mut Self::PrepaintState,
4323 window: &mut Window,
4324 cx: &mut App,
4325 ) {
4326 self.child.paint(window, cx);
4327 window.with_global_id("target".into(), |global_id, window| {
4328 window.with_element_state::<InteractiveElementState, _>(
4329 global_id,
4330 |state, _window| {
4331 let state = state.unwrap();
4332 *self.captured_active_tooltip.borrow_mut() =
4333 state.active_tooltip.as_ref().map(Rc::downgrade);
4334 ((), state)
4335 },
4336 )
4337 });
4338 }
4339 }
4340
4341 struct TooltipOwner {
4342 captured_active_tooltip: CapturedActiveTooltip,
4343 show_delay_override: Option<Duration>,
4344 }
4345
4346 impl Render for TooltipOwner {
4347 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4348 TooltipCaptureElement {
4349 child: div()
4350 .size_full()
4351 .child(
4352 div()
4353 .id("target")
4354 .w(px(50.))
4355 .h(px(50.))
4356 .tooltip(|_, cx| cx.new(|_| TestTooltipView).into())
4357 .when_some(self.show_delay_override, |this, delay| {
4358 this.tooltip_show_delay(delay)
4359 }),
4360 )
4361 .into_any_element(),
4362 captured_active_tooltip: self.captured_active_tooltip.clone(),
4363 }
4364 }
4365 }
4366
4367 #[test]
4368 fn scroll_handle_aligns_wide_children_to_left_edge() {
4369 let handle = ScrollHandle::new();
4370 {
4371 let mut state = handle.0.borrow_mut();
4372 state.bounds = Bounds::new(point(px(0.), px(0.)), size(px(80.), px(20.)));
4373 state.child_bounds = vec![Bounds::new(point(px(25.), px(0.)), size(px(200.), px(20.)))];
4374 state.overflow.x = Overflow::Scroll;
4375 state.active_item = Some(ScrollActiveItem {
4376 index: 0,
4377 strategy: ScrollStrategy::default(),
4378 });
4379 }
4380
4381 handle.scroll_to_active_item();
4382
4383 assert_eq!(handle.offset().x, px(-25.));
4384 }
4385
4386 #[test]
4387 fn scroll_handle_aligns_tall_children_to_top_edge() {
4388 let handle = ScrollHandle::new();
4389 {
4390 let mut state = handle.0.borrow_mut();
4391 state.bounds = Bounds::new(point(px(0.), px(0.)), size(px(20.), px(80.)));
4392 state.child_bounds = vec![Bounds::new(point(px(0.), px(25.)), size(px(20.), px(200.)))];
4393 state.overflow.y = Overflow::Scroll;
4394 state.active_item = Some(ScrollActiveItem {
4395 index: 0,
4396 strategy: ScrollStrategy::default(),
4397 });
4398 }
4399
4400 handle.scroll_to_active_item();
4401
4402 assert_eq!(handle.offset().y, px(-25.));
4403 }
4404
4405 fn setup_tooltip_owner_test(
4406 show_delay_override: Option<Duration>,
4407 ) -> (
4408 TestAppContext,
4409 crate::AnyWindowHandle,
4410 CapturedActiveTooltip,
4411 ) {
4412 let mut test_app = TestAppContext::single();
4413 let captured_active_tooltip: CapturedActiveTooltip = Rc::new(RefCell::new(None));
4414 let window = test_app.add_window({
4415 let captured_active_tooltip = captured_active_tooltip.clone();
4416 move |_, _| TooltipOwner {
4417 captured_active_tooltip,
4418 show_delay_override,
4419 }
4420 });
4421 let any_window = window.into();
4422
4423 test_app
4424 .update_window(any_window, |_, window, cx| {
4425 window.draw(cx).clear(cx);
4426 })
4427 .unwrap();
4428
4429 test_app
4430 .update_window(any_window, |_, window, cx| {
4431 window.dispatch_event(
4432 MouseMoveEvent {
4433 position: point(px(10.), px(10.)),
4434 modifiers: Default::default(),
4435 pressed_button: None,
4436 }
4437 .to_platform_input(),
4438 cx,
4439 );
4440 })
4441 .unwrap();
4442
4443 test_app
4444 .update_window(any_window, |_, window, cx| {
4445 window.draw(cx).clear(cx);
4446 })
4447 .unwrap();
4448
4449 (test_app, any_window, captured_active_tooltip)
4450 }
4451
4452 #[test]
4453 fn tooltip_waiting_for_show_is_released_when_its_owner_disappears() {
4454 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
4455
4456 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4457 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4458 assert!(matches!(
4459 active_tooltip.borrow().as_ref(),
4460 Some(ActiveTooltip::WaitingForShow { .. })
4461 ));
4462
4463 test_app
4464 .update_window(any_window, |_, window, _| {
4465 window.remove_window();
4466 })
4467 .unwrap();
4468 test_app.run_until_parked();
4469 drop(active_tooltip);
4470
4471 assert!(weak_active_tooltip.upgrade().is_none());
4472 }
4473
4474 struct HoverListenerLayoutTestView {
4475 target_left: Pixels,
4476 hover_transitions: Rc<RefCell<Vec<bool>>>,
4477 }
4478
4479 impl Render for HoverListenerLayoutTestView {
4480 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4481 let hover_transitions = self.hover_transitions.clone();
4482 div().relative().size_full().child(
4483 div()
4484 .id("hover-target")
4485 .absolute()
4486 .left(self.target_left)
4487 .top_0()
4488 .size(px(20.))
4489 .on_click(|_, _, _| {})
4490 .on_hover(move |is_hovered, _, _| {
4491 hover_transitions.borrow_mut().push(*is_hovered);
4492 }),
4493 )
4494 }
4495 }
4496
4497 #[rgpui::test]
4498 fn hover_listeners_update_when_layout_changes_under_stationary_mouse(cx: &mut TestAppContext) {
4499 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4500 let window = cx.add_window({
4501 let hover_transitions = hover_transitions.clone();
4502 move |_, _| HoverListenerLayoutTestView {
4503 target_left: px(40.),
4504 hover_transitions,
4505 }
4506 });
4507 let any_window = AnyWindowHandle::from(window);
4508
4509 cx.update_window(any_window, |_, window, cx| {
4510 window.draw(cx).clear(cx);
4511 window.simulate_mouse_move(point(px(10.), px(10.)), cx);
4512 })
4513 .unwrap();
4514 assert!(hover_transitions.borrow().is_empty());
4515
4516 window
4517 .update(cx, |view, _, cx| {
4518 view.target_left = px(0.);
4519 cx.notify();
4520 })
4521 .unwrap();
4522 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
4523 .unwrap();
4524 assert_eq!(*hover_transitions.borrow(), [true]);
4525
4526 window
4527 .update(cx, |view, _, cx| {
4528 view.target_left = px(40.);
4529 cx.notify();
4530 })
4531 .unwrap();
4532 cx.update_window(any_window, |_, window, cx| window.draw(cx).clear(cx))
4533 .unwrap();
4534 assert_eq!(*hover_transitions.borrow(), [true, false]);
4535 }
4536
4537 #[rgpui::test]
4538 fn hover_listeners_remain_hovered_during_stationary_mouse_press(cx: &mut TestAppContext) {
4539 let hover_transitions = Rc::new(RefCell::new(Vec::new()));
4540 let window = cx.add_window({
4541 let hover_transitions = hover_transitions.clone();
4542 move |_, _| HoverListenerLayoutTestView {
4543 target_left: px(0.),
4544 hover_transitions,
4545 }
4546 });
4547 let any_window = AnyWindowHandle::from(window);
4548 let mouse_position = point(px(10.), px(10.));
4549
4550 cx.update_window(any_window, |_, window, cx| {
4551 window.draw(cx).clear(cx);
4552 window.simulate_mouse_move(mouse_position, cx);
4553 })
4554 .unwrap();
4555 assert_eq!(*hover_transitions.borrow(), [true]);
4556
4557 cx.update_window(any_window, |_, window, cx| {
4558 window.dispatch_event(
4559 MouseDownEvent {
4560 position: mouse_position,
4561 button: MouseButton::Left,
4562 modifiers: Default::default(),
4563 click_count: 1,
4564 first_mouse: false,
4565 }
4566 .to_platform_input(),
4567 cx,
4568 );
4569 window.draw(cx).clear(cx);
4570 })
4571 .unwrap();
4572 assert_eq!(*hover_transitions.borrow(), [true]);
4573
4574 cx.update_window(any_window, |_, window, cx| {
4575 window.dispatch_event(
4576 MouseUpEvent {
4577 position: mouse_position,
4578 button: MouseButton::Left,
4579 modifiers: Default::default(),
4580 click_count: 1,
4581 }
4582 .to_platform_input(),
4583 cx,
4584 );
4585 window.draw(cx).clear(cx);
4586 })
4587 .unwrap();
4588 assert_eq!(*hover_transitions.borrow(), [true]);
4589 }
4590
4591 #[test]
4592 fn tooltip_respects_custom_show_delay() {
4593 let extra_delay = Duration::from_secs(1);
4594 let show_delay_override = DEFAULT_TOOLTIP_SHOW_DELAY + extra_delay;
4595 let (mut test_app, _any_window, captured_active_tooltip) =
4596 setup_tooltip_owner_test(Some(show_delay_override));
4597
4598 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4599 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4600
4601 test_app
4602 .dispatcher
4603 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
4604 test_app.run_until_parked();
4605
4606 assert!(matches!(
4607 active_tooltip.borrow().as_ref(),
4608 Some(ActiveTooltip::WaitingForShow { .. })
4609 ));
4610
4611 test_app.dispatcher.advance_clock(extra_delay);
4612 test_app.run_until_parked();
4613
4614 assert!(matches!(
4615 active_tooltip.borrow().as_ref(),
4616 Some(ActiveTooltip::Visible { .. })
4617 ));
4618 }
4619
4620 #[test]
4621 fn tooltip_is_released_when_its_owner_disappears() {
4622 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
4623
4624 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4625 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4626
4627 test_app
4628 .dispatcher
4629 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
4630 test_app.run_until_parked();
4631
4632 assert!(matches!(
4633 active_tooltip.borrow().as_ref(),
4634 Some(ActiveTooltip::Visible { .. })
4635 ));
4636
4637 test_app
4638 .update_window(any_window, |_, window, _| {
4639 window.remove_window();
4640 })
4641 .unwrap();
4642 test_app.run_until_parked();
4643 drop(active_tooltip);
4644
4645 assert!(weak_active_tooltip.upgrade().is_none());
4646 }
4647
4648 #[test]
4649 fn tooltip_hides_after_mouse_leaves_origin() {
4650 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
4651
4652 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4653 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4654
4655 test_app
4656 .dispatcher
4657 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
4658 test_app.run_until_parked();
4659
4660 assert!(matches!(
4661 active_tooltip.borrow().as_ref(),
4662 Some(ActiveTooltip::Visible { .. })
4663 ));
4664
4665 test_app
4666 .update_window(any_window, |_, window, cx| {
4667 window.dispatch_event(
4668 MouseMoveEvent {
4669 position: point(px(75.), px(75.)),
4670 modifiers: Default::default(),
4671 pressed_button: None,
4672 }
4673 .to_platform_input(),
4674 cx,
4675 );
4676 })
4677 .unwrap();
4678
4679 assert!(active_tooltip.borrow().is_none());
4680 }
4681
4682 #[test]
4683 fn test_write_a11y_info_string_and_numeric_properties() {
4684 let mut interactivity = Interactivity::default();
4685 interactivity.aria.label = Some("Buffer Font Size".into());
4686 interactivity.aria.value = Some("15".into());
4687 interactivity.aria.placeholder = Some("Search".into());
4688 interactivity.aria.numeric_value = Some(15.0);
4689 interactivity.aria.min_numeric_value = Some(6.0);
4690 interactivity.aria.max_numeric_value = Some(72.0);
4691 interactivity.aria.numeric_value_step = Some(1.0);
4692
4693 let mut node = accesskit::Node::new(accesskit::Role::SpinButton);
4694 interactivity.write_a11y_info(&mut node);
4695
4696 assert_eq!(node.label(), Some("Buffer Font Size"));
4697 assert_eq!(node.value(), Some("15"));
4698 assert_eq!(node.placeholder(), Some("Search"));
4699 assert_eq!(node.numeric_value(), Some(15.0));
4700 assert_eq!(node.min_numeric_value(), Some(6.0));
4701 assert_eq!(node.max_numeric_value(), Some(72.0));
4702 assert_eq!(node.numeric_value_step(), Some(1.0));
4703 }
4704
4705 struct KeyboardActivationTest {
4708 focus_a: FocusHandle,
4709 focus_b: FocusHandle,
4710 clicks: Rc<RefCell<Vec<&'static str>>>,
4711 }
4712
4713 impl Render for KeyboardActivationTest {
4714 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4715 let clicks_a = self.clicks.clone();
4716 let clicks_b = self.clicks.clone();
4717 div()
4718 .size_full()
4719 .child(
4720 div()
4721 .id("a")
4722 .w(px(50.))
4723 .h(px(50.))
4724 .track_focus(&self.focus_a)
4725 .on_click(move |_, _, _| clicks_a.borrow_mut().push("a")),
4726 )
4727 .child(
4728 div()
4729 .id("b")
4730 .w(px(50.))
4731 .h(px(50.))
4732 .track_focus(&self.focus_b)
4733 .on_click(move |_, _, _| clicks_b.borrow_mut().push("b")),
4734 )
4735 }
4736 }
4737
4738 fn setup_keyboard_activation_test() -> (
4739 TestAppContext,
4740 AnyWindowHandle,
4741 Rc<RefCell<Vec<&'static str>>>,
4742 FocusHandle,
4743 FocusHandle,
4744 ) {
4745 let mut cx = TestAppContext::single();
4746 let (focus_a, focus_b) = cx.update(|cx| (cx.focus_handle(), cx.focus_handle()));
4747 let clicks: Rc<RefCell<Vec<&'static str>>> = Rc::new(RefCell::new(Vec::new()));
4748 let window = cx.add_window({
4749 let focus_a = focus_a.clone();
4750 let focus_b = focus_b.clone();
4751 let clicks = clicks.clone();
4752 move |_, _| KeyboardActivationTest {
4753 focus_a,
4754 focus_b,
4755 clicks,
4756 }
4757 });
4758 (cx, window.into(), clicks, focus_a, focus_b)
4759 }
4760
4761 fn focus_and_draw(cx: &mut TestAppContext, window: AnyWindowHandle, handle: &FocusHandle) {
4764 cx.update_window(window, |_, window, cx| window.focus(handle, cx))
4765 .unwrap();
4766 cx.run_until_parked();
4767 cx.update_window(window, |_, window, cx| {
4768 window.draw(cx).clear(cx);
4769 })
4770 .unwrap();
4771 }
4772
4773 fn key_down(cx: &mut TestAppContext, window: AnyWindowHandle, key: &str) {
4774 let keystroke = Keystroke::parse(key).unwrap();
4775 cx.update_window(window, |_, window, cx| {
4776 window.dispatch_event(
4777 KeyDownEvent {
4778 keystroke,
4779 is_held: false,
4780 prefer_character_input: false,
4781 }
4782 .to_platform_input(),
4783 cx,
4784 );
4785 })
4786 .unwrap();
4787 }
4788
4789 fn key_up(cx: &mut TestAppContext, window: AnyWindowHandle, key: &str) {
4790 let keystroke = Keystroke::parse(key).unwrap();
4791 cx.update_window(window, |_, window, cx| {
4792 window.dispatch_event(KeyUpEvent { keystroke }.to_platform_input(), cx);
4793 })
4794 .unwrap();
4795 }
4796
4797 #[test]
4799 fn keyboard_activation_fires_click_on_same_element() {
4800 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
4801
4802 focus_and_draw(&mut cx, window, &focus_a);
4803 key_down(&mut cx, window, "enter");
4804 key_up(&mut cx, window, "enter");
4805
4806 assert_eq!(*clicks.borrow(), vec!["a"]);
4807 }
4808
4809 #[test]
4813 fn keyboard_activation_does_not_leak_across_focus_change() {
4814 let (mut cx, window, clicks, focus_a, focus_b) = setup_keyboard_activation_test();
4815
4816 focus_and_draw(&mut cx, window, &focus_a);
4818 key_down(&mut cx, window, "enter");
4819
4820 focus_and_draw(&mut cx, window, &focus_b);
4822 key_up(&mut cx, window, "enter");
4823
4824 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
4827 }
4828
4829 #[test]
4833 fn keyboard_activation_does_not_leak_when_focus_returns() {
4834 let (mut cx, window, clicks, focus_a, focus_b) = setup_keyboard_activation_test();
4835
4836 focus_and_draw(&mut cx, window, &focus_a);
4838 key_down(&mut cx, window, "enter");
4839
4840 focus_and_draw(&mut cx, window, &focus_b);
4843 focus_and_draw(&mut cx, window, &focus_a);
4844 key_up(&mut cx, window, "enter");
4845
4846 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
4848 }
4849
4850 #[test]
4855 fn keyboard_activation_cleared_by_intervening_key_release() {
4856 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
4857
4858 focus_and_draw(&mut cx, window, &focus_a);
4859 key_down(&mut cx, window, "escape");
4860 key_down(&mut cx, window, "space");
4861 key_up(&mut cx, window, "escape");
4862 key_up(&mut cx, window, "space");
4863
4864 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
4865 }
4866
4867 #[test]
4870 fn keyboard_activation_does_not_distinguish_space_and_enter() {
4871 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
4872
4873 focus_and_draw(&mut cx, window, &focus_a);
4874 key_down(&mut cx, window, "space");
4875 key_up(&mut cx, window, "enter");
4876
4877 assert_eq!(*clicks.borrow(), vec!["a"]);
4878 }
4879
4880 #[test]
4883 fn keyboard_activation_cleared_by_intervening_keydown() {
4884 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
4885
4886 focus_and_draw(&mut cx, window, &focus_a);
4887 key_down(&mut cx, window, "enter");
4888 key_down(&mut cx, window, "a");
4889 key_up(&mut cx, window, "enter");
4890
4891 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
4892 }
4893
4894 #[test]
4897 fn keyboard_activation_ignores_modified_keys() {
4898 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
4899
4900 focus_and_draw(&mut cx, window, &focus_a);
4901 key_down(&mut cx, window, "cmd-enter");
4902 key_up(&mut cx, window, "cmd-enter");
4903
4904 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
4905 }
4906
4907 struct TabGroupFocus {
4911 group_a: FocusHandle,
4912 item_a: FocusHandle,
4913 group_b: FocusHandle,
4914 item_b: FocusHandle,
4915 }
4916
4917 impl Render for TabGroupFocus {
4918 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
4919 fn group(container: &FocusHandle, item: &FocusHandle) -> Div {
4920 div()
4921 .track_focus(container)
4922 .tab_group()
4923 .child(div().track_focus(item))
4924 }
4925 div()
4926 .child(group(&self.group_a, &self.item_a))
4927 .child(group(&self.group_b, &self.item_b))
4928 }
4929 }
4930
4931 #[test]
4935 fn focus_next_from_tab_group_container_enters_that_group() {
4936 let mut cx = TestAppContext::single();
4937 let (group_a, item_a, group_b, item_b) = cx.update(|cx| {
4938 (
4939 cx.focus_handle(),
4940 cx.focus_handle().tab_stop(true),
4941 cx.focus_handle(),
4942 cx.focus_handle().tab_stop(true),
4943 )
4944 });
4945 let window: AnyWindowHandle = cx
4946 .add_window({
4947 let (group_a, item_a, group_b, item_b) =
4948 (group_a, item_a, group_b.clone(), item_b.clone());
4949 move |_, _| TabGroupFocus {
4950 group_a,
4951 item_a,
4952 group_b,
4953 item_b,
4954 }
4955 })
4956 .into();
4957 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
4958 .unwrap();
4959
4960 let focused = cx
4962 .update_window(window, |_, window, cx| {
4963 window.focus(&group_b, cx);
4964 window.focus_next(cx);
4965 window.focused(cx).map(|handle| handle.id)
4966 })
4967 .unwrap();
4968
4969 assert_eq!(focused, Some(item_b.id));
4970 }
4971}