1use crate::{
2 point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext,
3 BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle,
4 IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent,
5 MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent,
6 SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility,
7 WindowContext,
8};
9use collections::HashMap;
10use refineable::Refineable;
11use smallvec::SmallVec;
12use std::{
13 any::{Any, TypeId},
14 cell::RefCell,
15 fmt::Debug,
16 mem,
17 rc::Rc,
18 time::Duration,
19};
20use taffy::style::Overflow;
21use util::ResultExt;
22
23const DRAG_THRESHOLD: f64 = 2.;
24const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
25
26pub struct GroupStyle {
27 pub group: SharedString,
28 pub style: StyleRefinement,
29}
30
31pub trait InteractiveElement: Sized + Element {
32 fn interactivity(&mut self) -> &mut Interactivity;
33
34 fn group(mut self, group: impl Into<SharedString>) -> Self {
35 self.interactivity().group = Some(group.into());
36 self
37 }
38
39 fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
40 self.interactivity().element_id = Some(id.into());
41
42 Stateful { element: self }
43 }
44
45 fn track_focus(mut self, focus_handle: &FocusHandle) -> Focusable<Self> {
46 self.interactivity().focusable = true;
47 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
48 Focusable { element: self }
49 }
50
51 fn key_context<C, E>(mut self, key_context: C) -> Self
52 where
53 C: TryInto<KeyContext, Error = E>,
54 E: Debug,
55 {
56 if let Some(key_context) = key_context.try_into().log_err() {
57 self.interactivity().key_context = key_context;
58 }
59 self
60 }
61
62 fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
63 self.interactivity().hover_style = f(StyleRefinement::default());
64 self
65 }
66
67 fn group_hover(
68 mut self,
69 group_name: impl Into<SharedString>,
70 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
71 ) -> Self {
72 self.interactivity().group_hover_style = Some(GroupStyle {
73 group: group_name.into(),
74 style: f(StyleRefinement::default()),
75 });
76 self
77 }
78
79 fn on_mouse_down(
80 mut self,
81 button: MouseButton,
82 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
83 ) -> Self {
84 self.interactivity().mouse_down_listeners.push(Box::new(
85 move |event, bounds, phase, cx| {
86 if phase == DispatchPhase::Bubble
87 && event.button == button
88 && bounds.visibly_contains(&event.position, cx)
89 {
90 (listener)(event, cx)
91 }
92 },
93 ));
94 self
95 }
96
97 fn on_any_mouse_down(
98 mut self,
99 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
100 ) -> Self {
101 self.interactivity().mouse_down_listeners.push(Box::new(
102 move |event, bounds, phase, cx| {
103 if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
104 (listener)(event, cx)
105 }
106 },
107 ));
108 self
109 }
110
111 fn on_mouse_up(
112 mut self,
113 button: MouseButton,
114 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
115 ) -> Self {
116 self.interactivity()
117 .mouse_up_listeners
118 .push(Box::new(move |event, bounds, phase, cx| {
119 if phase == DispatchPhase::Bubble
120 && event.button == button
121 && bounds.visibly_contains(&event.position, cx)
122 {
123 (listener)(event, cx)
124 }
125 }));
126 self
127 }
128
129 fn on_any_mouse_up(
130 mut self,
131 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
132 ) -> Self {
133 self.interactivity()
134 .mouse_up_listeners
135 .push(Box::new(move |event, bounds, phase, cx| {
136 if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
137 (listener)(event, cx)
138 }
139 }));
140 self
141 }
142
143 fn on_mouse_down_out(
144 mut self,
145 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
146 ) -> Self {
147 self.interactivity().mouse_down_listeners.push(Box::new(
148 move |event, bounds, phase, cx| {
149 if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx)
150 {
151 (listener)(event, cx)
152 }
153 },
154 ));
155 self
156 }
157
158 fn on_mouse_up_out(
159 mut self,
160 button: MouseButton,
161 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
162 ) -> Self {
163 self.interactivity()
164 .mouse_up_listeners
165 .push(Box::new(move |event, bounds, phase, cx| {
166 if phase == DispatchPhase::Capture
167 && event.button == button
168 && !bounds.visibly_contains(&event.position, cx)
169 {
170 (listener)(event, cx);
171 }
172 }));
173 self
174 }
175
176 fn on_mouse_move(
177 mut self,
178 listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static,
179 ) -> Self {
180 self.interactivity().mouse_move_listeners.push(Box::new(
181 move |event, bounds, phase, cx| {
182 if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
183 (listener)(event, cx);
184 }
185 },
186 ));
187 self
188 }
189
190 fn on_scroll_wheel(
191 mut self,
192 listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static,
193 ) -> Self {
194 self.interactivity().scroll_wheel_listeners.push(Box::new(
195 move |event, bounds, phase, cx| {
196 if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
197 (listener)(event, cx);
198 }
199 },
200 ));
201 self
202 }
203
204 /// Capture the given action, before normal action dispatch can fire
205 fn capture_action<A: Action>(
206 mut self,
207 listener: impl Fn(&A, &mut WindowContext) + 'static,
208 ) -> Self {
209 self.interactivity().action_listeners.push((
210 TypeId::of::<A>(),
211 Box::new(move |action, phase, cx| {
212 let action = action.downcast_ref().unwrap();
213 if phase == DispatchPhase::Capture {
214 (listener)(action, cx)
215 }
216 }),
217 ));
218 self
219 }
220
221 /// Add a listener for the given action, fires during the bubble event phase
222 fn on_action<A: Action>(mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) -> Self {
223 // NOTE: this debug assert has the side-effect of working around
224 // a bug where a crate consisting only of action definitions does
225 // not register the actions in debug builds:
226 //
227 // https://github.com/rust-lang/rust/issues/47384
228 // https://github.com/mmastrac/rust-ctor/issues/280
229 //
230 // if we are relying on this side-effect still, removing the debug_assert!
231 // likely breaks the command_palette tests.
232 // debug_assert!(
233 // A::is_registered(),
234 // "{:?} is not registered as an action",
235 // A::qualified_name()
236 // );
237 self.interactivity().action_listeners.push((
238 TypeId::of::<A>(),
239 Box::new(move |action, phase, cx| {
240 let action = action.downcast_ref().unwrap();
241 if phase == DispatchPhase::Bubble {
242 (listener)(action, cx)
243 }
244 }),
245 ));
246 self
247 }
248
249 fn on_key_down(
250 mut self,
251 listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
252 ) -> Self {
253 self.interactivity()
254 .key_down_listeners
255 .push(Box::new(move |event, phase, cx| {
256 if phase == DispatchPhase::Bubble {
257 (listener)(event, cx)
258 }
259 }));
260 self
261 }
262
263 fn capture_key_down(
264 mut self,
265 listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
266 ) -> Self {
267 self.interactivity()
268 .key_down_listeners
269 .push(Box::new(move |event, phase, cx| {
270 if phase == DispatchPhase::Capture {
271 listener(event, cx)
272 }
273 }));
274 self
275 }
276
277 fn on_key_up(mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) -> Self {
278 self.interactivity()
279 .key_up_listeners
280 .push(Box::new(move |event, phase, cx| {
281 if phase == DispatchPhase::Bubble {
282 listener(event, cx)
283 }
284 }));
285 self
286 }
287
288 fn capture_key_up(
289 mut self,
290 listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static,
291 ) -> Self {
292 self.interactivity()
293 .key_up_listeners
294 .push(Box::new(move |event, phase, cx| {
295 if phase == DispatchPhase::Capture {
296 listener(event, cx)
297 }
298 }));
299 self
300 }
301
302 fn drag_over<S: 'static>(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
303 self.interactivity()
304 .drag_over_styles
305 .push((TypeId::of::<S>(), f(StyleRefinement::default())));
306 self
307 }
308
309 fn group_drag_over<S: 'static>(
310 mut self,
311 group_name: impl Into<SharedString>,
312 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
313 ) -> Self {
314 self.interactivity().group_drag_over_styles.push((
315 TypeId::of::<S>(),
316 GroupStyle {
317 group: group_name.into(),
318 style: f(StyleRefinement::default()),
319 },
320 ));
321 self
322 }
323
324 fn on_drop<W: 'static>(
325 mut self,
326 listener: impl Fn(&View<W>, &mut WindowContext) + 'static,
327 ) -> Self {
328 self.interactivity().drop_listeners.push((
329 TypeId::of::<W>(),
330 Box::new(move |dragged_view, cx| {
331 listener(&dragged_view.downcast().unwrap(), cx);
332 }),
333 ));
334 self
335 }
336}
337
338pub trait StatefulInteractiveElement: InteractiveElement {
339 fn focusable(mut self) -> Focusable<Self> {
340 self.interactivity().focusable = true;
341 Focusable { element: self }
342 }
343
344 fn overflow_scroll(mut self) -> Self {
345 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
346 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
347 self
348 }
349
350 fn overflow_x_scroll(mut self) -> Self {
351 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
352 self
353 }
354
355 fn overflow_y_scroll(mut self) -> Self {
356 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
357 self
358 }
359
360 fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
361 where
362 Self: Sized,
363 {
364 self.interactivity().active_style = f(StyleRefinement::default());
365 self
366 }
367
368 fn group_active(
369 mut self,
370 group_name: impl Into<SharedString>,
371 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
372 ) -> Self
373 where
374 Self: Sized,
375 {
376 self.interactivity().group_active_style = Some(GroupStyle {
377 group: group_name.into(),
378 style: f(StyleRefinement::default()),
379 });
380 self
381 }
382
383 fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self
384 where
385 Self: Sized,
386 {
387 self.interactivity()
388 .click_listeners
389 .push(Box::new(move |event, cx| listener(event, cx)));
390 self
391 }
392
393 fn on_drag<W>(mut self, listener: impl Fn(&mut WindowContext) -> View<W> + 'static) -> Self
394 where
395 Self: Sized,
396 W: 'static + Render,
397 {
398 debug_assert!(
399 self.interactivity().drag_listener.is_none(),
400 "calling on_drag more than once on the same element is not supported"
401 );
402 self.interactivity().drag_listener = Some(Box::new(move |cursor_offset, cx| AnyDrag {
403 view: listener(cx).into(),
404 cursor_offset,
405 }));
406 self
407 }
408
409 fn on_hover(mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static) -> Self
410 where
411 Self: Sized,
412 {
413 debug_assert!(
414 self.interactivity().hover_listener.is_none(),
415 "calling on_hover more than once on the same element is not supported"
416 );
417 self.interactivity().hover_listener = Some(Box::new(listener));
418 self
419 }
420
421 fn tooltip(mut self, build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self
422 where
423 Self: Sized,
424 {
425 debug_assert!(
426 self.interactivity().tooltip_builder.is_none(),
427 "calling tooltip more than once on the same element is not supported"
428 );
429 self.interactivity().tooltip_builder = Some(Rc::new(build_tooltip));
430
431 self
432 }
433}
434
435pub trait FocusableElement: InteractiveElement {
436 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
437 where
438 Self: Sized,
439 {
440 self.interactivity().focus_style = f(StyleRefinement::default());
441 self
442 }
443
444 fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
445 where
446 Self: Sized,
447 {
448 self.interactivity().in_focus_style = f(StyleRefinement::default());
449 self
450 }
451
452 fn on_focus(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
453 where
454 Self: Sized,
455 {
456 self.interactivity()
457 .focus_listeners
458 .push(Box::new(move |focus_handle, event, cx| {
459 if event.focused.as_ref() == Some(focus_handle) {
460 listener(event, cx)
461 }
462 }));
463 self
464 }
465
466 fn on_blur(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
467 where
468 Self: Sized,
469 {
470 self.interactivity()
471 .focus_listeners
472 .push(Box::new(move |focus_handle, event, cx| {
473 if event.blurred.as_ref() == Some(focus_handle) {
474 listener(event, cx)
475 }
476 }));
477 self
478 }
479
480 fn on_focus_in(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
481 where
482 Self: Sized,
483 {
484 self.interactivity()
485 .focus_listeners
486 .push(Box::new(move |focus_handle, event, cx| {
487 let descendant_blurred = event
488 .blurred
489 .as_ref()
490 .map_or(false, |blurred| focus_handle.contains(blurred, cx));
491 let descendant_focused = event
492 .focused
493 .as_ref()
494 .map_or(false, |focused| focus_handle.contains(focused, cx));
495
496 if !descendant_blurred && descendant_focused {
497 listener(event, cx)
498 }
499 }));
500 self
501 }
502
503 fn on_focus_out(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
504 where
505 Self: Sized,
506 {
507 self.interactivity()
508 .focus_listeners
509 .push(Box::new(move |focus_handle, event, cx| {
510 let descendant_blurred = event
511 .blurred
512 .as_ref()
513 .map_or(false, |blurred| focus_handle.contains(blurred, cx));
514 let descendant_focused = event
515 .focused
516 .as_ref()
517 .map_or(false, |focused| focus_handle.contains(focused, cx));
518 if descendant_blurred && !descendant_focused {
519 listener(event, cx)
520 }
521 }));
522 self
523 }
524}
525
526pub type FocusListeners = SmallVec<[FocusListener; 2]>;
527
528pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>;
529
530pub type MouseDownListener =
531 Box<dyn Fn(&MouseDownEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
532pub type MouseUpListener =
533 Box<dyn Fn(&MouseUpEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
534
535pub type MouseMoveListener =
536 Box<dyn Fn(&MouseMoveEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
537
538pub type ScrollWheelListener =
539 Box<dyn Fn(&ScrollWheelEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
540
541pub type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
542
543pub type DragListener = Box<dyn Fn(Point<Pixels>, &mut WindowContext) -> AnyDrag + 'static>;
544
545type DropListener = dyn Fn(AnyView, &mut WindowContext) + 'static;
546
547pub type TooltipBuilder = Rc<dyn Fn(&mut WindowContext) -> AnyView + 'static>;
548
549pub type KeyDownListener = Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + 'static>;
550
551pub type KeyUpListener = Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut WindowContext) + 'static>;
552
553pub type ActionListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
554
555pub fn div() -> Div {
556 Div {
557 interactivity: Interactivity::default(),
558 children: SmallVec::default(),
559 }
560}
561
562pub struct Div {
563 interactivity: Interactivity,
564 children: SmallVec<[AnyElement; 2]>,
565}
566
567impl Styled for Div {
568 fn style(&mut self) -> &mut StyleRefinement {
569 &mut self.interactivity.base_style
570 }
571}
572
573impl InteractiveElement for Div {
574 fn interactivity(&mut self) -> &mut Interactivity {
575 &mut self.interactivity
576 }
577}
578
579impl ParentElement for Div {
580 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
581 &mut self.children
582 }
583}
584
585impl Element for Div {
586 type State = DivState;
587
588 fn layout(
589 &mut self,
590 element_state: Option<Self::State>,
591 cx: &mut WindowContext,
592 ) -> (LayoutId, Self::State) {
593 let mut child_layout_ids = SmallVec::new();
594 let mut interactivity = mem::take(&mut self.interactivity);
595 let (layout_id, interactive_state) = interactivity.layout(
596 element_state.map(|s| s.interactive_state),
597 cx,
598 |style, cx| {
599 cx.with_text_style(style.text_style().cloned(), |cx| {
600 child_layout_ids = self
601 .children
602 .iter_mut()
603 .map(|child| child.layout(cx))
604 .collect::<SmallVec<_>>();
605 cx.request_layout(&style, child_layout_ids.iter().copied())
606 })
607 },
608 );
609 self.interactivity = interactivity;
610 (
611 layout_id,
612 DivState {
613 interactive_state,
614 child_layout_ids,
615 },
616 )
617 }
618
619 fn paint(
620 self,
621 bounds: Bounds<Pixels>,
622 element_state: &mut Self::State,
623 cx: &mut WindowContext,
624 ) {
625 let mut child_min = point(Pixels::MAX, Pixels::MAX);
626 let mut child_max = Point::default();
627 let content_size = if element_state.child_layout_ids.is_empty() {
628 bounds.size
629 } else {
630 for child_layout_id in &element_state.child_layout_ids {
631 let child_bounds = cx.layout_bounds(*child_layout_id);
632 child_min = child_min.min(&child_bounds.origin);
633 child_max = child_max.max(&child_bounds.lower_right());
634 }
635 (child_max - child_min).into()
636 };
637
638 self.interactivity.paint(
639 bounds,
640 content_size,
641 &mut element_state.interactive_state,
642 cx,
643 |style, scroll_offset, cx| {
644 if style.visibility == Visibility::Hidden {
645 return;
646 }
647
648 let z_index = style.z_index.unwrap_or(0);
649
650 cx.with_z_index(z_index, |cx| {
651 cx.with_z_index(0, |cx| {
652 style.paint(bounds, cx);
653 });
654 cx.with_z_index(1, |cx| {
655 cx.with_text_style(style.text_style().cloned(), |cx| {
656 cx.with_content_mask(style.overflow_mask(bounds), |cx| {
657 cx.with_element_offset(scroll_offset, |cx| {
658 for child in self.children {
659 child.paint(cx);
660 }
661 })
662 })
663 })
664 })
665 })
666 },
667 );
668 }
669}
670
671impl IntoElement for Div {
672 type Element = Self;
673
674 fn element_id(&self) -> Option<ElementId> {
675 self.interactivity.element_id.clone()
676 }
677
678 fn into_element(self) -> Self::Element {
679 self
680 }
681}
682
683pub struct DivState {
684 child_layout_ids: SmallVec<[LayoutId; 4]>,
685 interactive_state: InteractiveElementState,
686}
687
688impl DivState {
689 pub fn is_active(&self) -> bool {
690 self.interactive_state.pending_mouse_down.borrow().is_some()
691 }
692}
693
694pub struct Interactivity {
695 pub element_id: Option<ElementId>,
696 pub key_context: KeyContext,
697 pub focusable: bool,
698 pub tracked_focus_handle: Option<FocusHandle>,
699 pub focus_listeners: FocusListeners,
700 pub group: Option<SharedString>,
701 pub base_style: StyleRefinement,
702 pub focus_style: StyleRefinement,
703 pub in_focus_style: StyleRefinement,
704 pub hover_style: StyleRefinement,
705 pub group_hover_style: Option<GroupStyle>,
706 pub active_style: StyleRefinement,
707 pub group_active_style: Option<GroupStyle>,
708 pub drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
709 pub group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>,
710 pub mouse_down_listeners: SmallVec<[MouseDownListener; 2]>,
711 pub mouse_up_listeners: SmallVec<[MouseUpListener; 2]>,
712 pub mouse_move_listeners: SmallVec<[MouseMoveListener; 2]>,
713 pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener; 2]>,
714 pub key_down_listeners: SmallVec<[KeyDownListener; 2]>,
715 pub key_up_listeners: SmallVec<[KeyUpListener; 2]>,
716 pub action_listeners: SmallVec<[(TypeId, ActionListener); 8]>,
717 pub drop_listeners: SmallVec<[(TypeId, Box<DropListener>); 2]>,
718 pub click_listeners: SmallVec<[ClickListener; 2]>,
719 pub drag_listener: Option<DragListener>,
720 pub hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>,
721 pub tooltip_builder: Option<TooltipBuilder>,
722}
723
724#[derive(Clone)]
725pub struct InteractiveBounds {
726 pub bounds: Bounds<Pixels>,
727 pub stacking_order: StackingOrder,
728}
729
730impl InteractiveBounds {
731 pub fn visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
732 self.bounds.contains_point(point) && cx.was_top_layer(&point, &self.stacking_order)
733 }
734}
735
736impl Interactivity {
737 pub fn layout(
738 &mut self,
739 element_state: Option<InteractiveElementState>,
740 cx: &mut WindowContext,
741 f: impl FnOnce(Style, &mut WindowContext) -> LayoutId,
742 ) -> (LayoutId, InteractiveElementState) {
743 let mut element_state = element_state.unwrap_or_default();
744
745 // Ensure we store a focus handle in our element state if we're focusable.
746 // If there's an explicit focus handle we're tracking, use that. Otherwise
747 // create a new handle and store it in the element state, which lives for as
748 // as frames contain an element with this id.
749 if self.focusable {
750 element_state.focus_handle.get_or_insert_with(|| {
751 self.tracked_focus_handle
752 .clone()
753 .unwrap_or_else(|| cx.focus_handle())
754 });
755 }
756
757 let style = self.compute_style(None, &mut element_state, cx);
758 let layout_id = f(style, cx);
759 (layout_id, element_state)
760 }
761
762 pub fn paint(
763 mut self,
764 bounds: Bounds<Pixels>,
765 content_size: Size<Pixels>,
766 element_state: &mut InteractiveElementState,
767 cx: &mut WindowContext,
768 f: impl FnOnce(Style, Point<Pixels>, &mut WindowContext),
769 ) {
770 let style = self.compute_style(Some(bounds), element_state, cx);
771
772 if style
773 .background
774 .as_ref()
775 .is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent()))
776 {
777 cx.with_z_index(style.z_index.unwrap_or(0), |cx| cx.add_opaque_layer(bounds))
778 }
779
780 let interactive_bounds = Rc::new(InteractiveBounds {
781 bounds: bounds.intersect(&cx.content_mask().bounds),
782 stacking_order: cx.stacking_order().clone(),
783 });
784
785 if let Some(mouse_cursor) = style.mouse_cursor {
786 let mouse_position = &cx.mouse_position();
787 let hovered = interactive_bounds.visibly_contains(mouse_position, cx);
788 if hovered {
789 cx.set_cursor_style(mouse_cursor);
790 }
791 }
792
793 for listener in self.mouse_down_listeners.drain(..) {
794 let interactive_bounds = interactive_bounds.clone();
795 cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
796 listener(event, &*interactive_bounds, phase, cx);
797 })
798 }
799
800 for listener in self.mouse_up_listeners.drain(..) {
801 let interactive_bounds = interactive_bounds.clone();
802 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
803 listener(event, &*interactive_bounds, phase, cx);
804 })
805 }
806
807 for listener in self.mouse_move_listeners.drain(..) {
808 let interactive_bounds = interactive_bounds.clone();
809 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
810 listener(event, &*interactive_bounds, phase, cx);
811 })
812 }
813
814 for listener in self.scroll_wheel_listeners.drain(..) {
815 let interactive_bounds = interactive_bounds.clone();
816 cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
817 listener(event, &*interactive_bounds, phase, cx);
818 })
819 }
820
821 let hover_group_bounds = self
822 .group_hover_style
823 .as_ref()
824 .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
825
826 if let Some(group_bounds) = hover_group_bounds {
827 let hovered = group_bounds.contains_point(&cx.mouse_position());
828 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
829 if phase == DispatchPhase::Capture {
830 if group_bounds.contains_point(&event.position) != hovered {
831 cx.notify();
832 }
833 }
834 });
835 }
836
837 if self.hover_style.is_some()
838 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
839 {
840 let bounds = bounds.intersect(&cx.content_mask().bounds);
841 let hovered = bounds.contains_point(&cx.mouse_position());
842 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
843 if phase == DispatchPhase::Capture {
844 if bounds.contains_point(&event.position) != hovered {
845 cx.notify();
846 }
847 }
848 });
849 }
850
851 if cx.active_drag.is_some() {
852 let drop_listeners = mem::take(&mut self.drop_listeners);
853 let interactive_bounds = interactive_bounds.clone();
854 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
855 if phase == DispatchPhase::Bubble
856 && interactive_bounds.visibly_contains(&event.position, &cx)
857 {
858 if let Some(drag_state_type) =
859 cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
860 {
861 for (drop_state_type, listener) in &drop_listeners {
862 if *drop_state_type == drag_state_type {
863 let drag = cx
864 .active_drag
865 .take()
866 .expect("checked for type drag state type above");
867 listener(drag.view.clone(), cx);
868 cx.notify();
869 cx.stop_propagation();
870 }
871 }
872 }
873 }
874 });
875 }
876
877 let click_listeners = mem::take(&mut self.click_listeners);
878 let drag_listener = mem::take(&mut self.drag_listener);
879
880 if !click_listeners.is_empty() || drag_listener.is_some() {
881 let pending_mouse_down = element_state.pending_mouse_down.clone();
882 let mouse_down = pending_mouse_down.borrow().clone();
883 if let Some(mouse_down) = mouse_down {
884 if let Some(drag_listener) = drag_listener {
885 let active_state = element_state.clicked_state.clone();
886 let interactive_bounds = interactive_bounds.clone();
887
888 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
889 if cx.active_drag.is_some() {
890 if phase == DispatchPhase::Capture {
891 cx.notify();
892 }
893 } else if phase == DispatchPhase::Bubble
894 && interactive_bounds.visibly_contains(&event.position, cx)
895 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
896 {
897 *active_state.borrow_mut() = ElementClickedState::default();
898 let cursor_offset = event.position - bounds.origin;
899 let drag = drag_listener(cursor_offset, cx);
900 cx.active_drag = Some(drag);
901 cx.notify();
902 cx.stop_propagation();
903 }
904 });
905 }
906
907 let interactive_bounds = interactive_bounds.clone();
908 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
909 if phase == DispatchPhase::Bubble
910 && interactive_bounds.visibly_contains(&event.position, cx)
911 {
912 let mouse_click = ClickEvent {
913 down: mouse_down.clone(),
914 up: event.clone(),
915 };
916 for listener in &click_listeners {
917 listener(&mouse_click, cx);
918 }
919 }
920 *pending_mouse_down.borrow_mut() = None;
921 cx.notify();
922 });
923 } else {
924 let interactive_bounds = interactive_bounds.clone();
925 cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
926 if phase == DispatchPhase::Bubble
927 && interactive_bounds.visibly_contains(&event.position, cx)
928 {
929 *pending_mouse_down.borrow_mut() = Some(event.clone());
930 cx.notify();
931 }
932 });
933 }
934 }
935
936 if let Some(hover_listener) = self.hover_listener.take() {
937 let was_hovered = element_state.hover_state.clone();
938 let has_mouse_down = element_state.pending_mouse_down.clone();
939 let interactive_bounds = interactive_bounds.clone();
940
941 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
942 if phase != DispatchPhase::Bubble {
943 return;
944 }
945 let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
946 && has_mouse_down.borrow().is_none();
947 let mut was_hovered = was_hovered.borrow_mut();
948
949 if is_hovered != was_hovered.clone() {
950 *was_hovered = is_hovered;
951 drop(was_hovered);
952
953 hover_listener(&is_hovered, cx);
954 }
955 });
956 }
957
958 if let Some(tooltip_builder) = self.tooltip_builder.take() {
959 let active_tooltip = element_state.active_tooltip.clone();
960 let pending_mouse_down = element_state.pending_mouse_down.clone();
961 let interactive_bounds = interactive_bounds.clone();
962
963 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
964 if phase != DispatchPhase::Bubble {
965 return;
966 }
967
968 let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
969 && pending_mouse_down.borrow().is_none();
970 if !is_hovered {
971 active_tooltip.borrow_mut().take();
972 return;
973 }
974
975 if active_tooltip.borrow().is_none() {
976 let task = cx.spawn({
977 let active_tooltip = active_tooltip.clone();
978 let tooltip_builder = tooltip_builder.clone();
979
980 move |mut cx| async move {
981 cx.background_executor().timer(TOOLTIP_DELAY).await;
982 cx.update(|_, cx| {
983 active_tooltip.borrow_mut().replace(ActiveTooltip {
984 tooltip: Some(AnyTooltip {
985 view: tooltip_builder(cx),
986 cursor_offset: cx.mouse_position(),
987 }),
988 _task: None,
989 });
990 cx.notify();
991 })
992 .ok();
993 }
994 });
995 active_tooltip.borrow_mut().replace(ActiveTooltip {
996 tooltip: None,
997 _task: Some(task),
998 });
999 }
1000 });
1001
1002 let active_tooltip = element_state.active_tooltip.clone();
1003 cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
1004 active_tooltip.borrow_mut().take();
1005 });
1006
1007 if let Some(active_tooltip) = element_state.active_tooltip.borrow().as_ref() {
1008 if active_tooltip.tooltip.is_some() {
1009 cx.active_tooltip = active_tooltip.tooltip.clone()
1010 }
1011 }
1012 }
1013
1014 let active_state = element_state.clicked_state.clone();
1015 if !active_state.borrow().is_clicked() {
1016 cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| {
1017 if phase == DispatchPhase::Capture {
1018 *active_state.borrow_mut() = ElementClickedState::default();
1019 cx.notify();
1020 }
1021 });
1022 } else {
1023 let active_group_bounds = self
1024 .group_active_style
1025 .as_ref()
1026 .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
1027 let interactive_bounds = interactive_bounds.clone();
1028 cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| {
1029 if phase == DispatchPhase::Bubble {
1030 let group = active_group_bounds
1031 .map_or(false, |bounds| bounds.contains_point(&down.position));
1032 let element = interactive_bounds.visibly_contains(&down.position, cx);
1033 if group || element {
1034 *active_state.borrow_mut() = ElementClickedState { group, element };
1035 cx.notify();
1036 }
1037 }
1038 });
1039 }
1040
1041 let overflow = style.overflow;
1042 if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll {
1043 let scroll_offset = element_state
1044 .scroll_offset
1045 .get_or_insert_with(Rc::default)
1046 .clone();
1047 let line_height = cx.line_height();
1048 let scroll_max = (content_size - bounds.size).max(&Size::default());
1049 let interactive_bounds = interactive_bounds.clone();
1050
1051 cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
1052 if phase == DispatchPhase::Bubble
1053 && interactive_bounds.visibly_contains(&event.position, cx)
1054 {
1055 let mut scroll_offset = scroll_offset.borrow_mut();
1056 let old_scroll_offset = *scroll_offset;
1057 let delta = event.delta.pixel_delta(line_height);
1058
1059 if overflow.x == Overflow::Scroll {
1060 scroll_offset.x =
1061 (scroll_offset.x + delta.x).clamp(-scroll_max.width, px(0.));
1062 }
1063
1064 if overflow.y == Overflow::Scroll {
1065 scroll_offset.y =
1066 (scroll_offset.y + delta.y).clamp(-scroll_max.height, px(0.));
1067 }
1068
1069 if *scroll_offset != old_scroll_offset {
1070 cx.notify();
1071 cx.stop_propagation();
1072 }
1073 }
1074 });
1075 }
1076
1077 if let Some(group) = self.group.clone() {
1078 GroupBounds::push(group, bounds, cx);
1079 }
1080
1081 let scroll_offset = element_state
1082 .scroll_offset
1083 .as_ref()
1084 .map(|scroll_offset| *scroll_offset.borrow());
1085
1086 cx.with_key_dispatch(
1087 self.key_context.clone(),
1088 element_state.focus_handle.clone(),
1089 |_, cx| {
1090 for listener in self.key_down_listeners.drain(..) {
1091 cx.on_key_event(move |event: &KeyDownEvent, phase, cx| {
1092 listener(event, phase, cx);
1093 })
1094 }
1095
1096 for listener in self.key_up_listeners.drain(..) {
1097 cx.on_key_event(move |event: &KeyUpEvent, phase, cx| {
1098 listener(event, phase, cx);
1099 })
1100 }
1101
1102 for (action_type, listener) in self.action_listeners {
1103 cx.on_action(action_type, listener)
1104 }
1105
1106 if let Some(focus_handle) = element_state.focus_handle.as_ref() {
1107 for listener in self.focus_listeners {
1108 let focus_handle = focus_handle.clone();
1109 cx.on_focus_changed(move |event, cx| listener(&focus_handle, event, cx));
1110 }
1111 }
1112
1113 f(style, scroll_offset.unwrap_or_default(), cx)
1114 },
1115 );
1116
1117 if let Some(group) = self.group.as_ref() {
1118 GroupBounds::pop(group, cx);
1119 }
1120 }
1121
1122 pub fn compute_style(
1123 &self,
1124 bounds: Option<Bounds<Pixels>>,
1125 element_state: &mut InteractiveElementState,
1126 cx: &mut WindowContext,
1127 ) -> Style {
1128 let mut style = Style::default();
1129 style.refine(&self.base_style);
1130
1131 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
1132 if focus_handle.within_focused(cx) {
1133 style.refine(&self.in_focus_style);
1134 }
1135
1136 if focus_handle.is_focused(cx) {
1137 style.refine(&self.focus_style);
1138 }
1139 }
1140
1141 if let Some(bounds) = bounds {
1142 let mouse_position = cx.mouse_position();
1143 if let Some(group_hover) = self.group_hover_style.as_ref() {
1144 if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) {
1145 if group_bounds.contains_point(&mouse_position)
1146 && cx.was_top_layer(&mouse_position, cx.stacking_order())
1147 {
1148 style.refine(&group_hover.style);
1149 }
1150 }
1151 }
1152 if self.hover_style.is_some() {
1153 if bounds
1154 .intersect(&cx.content_mask().bounds)
1155 .contains_point(&mouse_position)
1156 && cx.was_top_layer(&mouse_position, cx.stacking_order())
1157 {
1158 style.refine(&self.hover_style);
1159 }
1160 }
1161
1162 if let Some(drag) = cx.active_drag.take() {
1163 for (state_type, group_drag_style) in &self.group_drag_over_styles {
1164 if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
1165 if *state_type == drag.view.entity_type()
1166 && group_bounds.contains_point(&mouse_position)
1167 {
1168 style.refine(&group_drag_style.style);
1169 }
1170 }
1171 }
1172
1173 for (state_type, drag_over_style) in &self.drag_over_styles {
1174 if *state_type == drag.view.entity_type()
1175 && bounds
1176 .intersect(&cx.content_mask().bounds)
1177 .contains_point(&mouse_position)
1178 {
1179 style.refine(drag_over_style);
1180 }
1181 }
1182
1183 cx.active_drag = Some(drag);
1184 }
1185 }
1186
1187 let clicked_state = element_state.clicked_state.borrow();
1188 if clicked_state.group {
1189 if let Some(group) = self.group_active_style.as_ref() {
1190 style.refine(&group.style)
1191 }
1192 }
1193
1194 if clicked_state.element {
1195 style.refine(&self.active_style)
1196 }
1197
1198 style
1199 }
1200}
1201
1202impl Default for Interactivity {
1203 fn default() -> Self {
1204 Self {
1205 element_id: None,
1206 key_context: KeyContext::default(),
1207 focusable: false,
1208 tracked_focus_handle: None,
1209 focus_listeners: SmallVec::default(),
1210 // scroll_offset: Point::default(),
1211 group: None,
1212 base_style: StyleRefinement::default(),
1213 focus_style: StyleRefinement::default(),
1214 in_focus_style: StyleRefinement::default(),
1215 hover_style: StyleRefinement::default(),
1216 group_hover_style: None,
1217 active_style: StyleRefinement::default(),
1218 group_active_style: None,
1219 drag_over_styles: SmallVec::new(),
1220 group_drag_over_styles: SmallVec::new(),
1221 mouse_down_listeners: SmallVec::new(),
1222 mouse_up_listeners: SmallVec::new(),
1223 mouse_move_listeners: SmallVec::new(),
1224 scroll_wheel_listeners: SmallVec::new(),
1225 key_down_listeners: SmallVec::new(),
1226 key_up_listeners: SmallVec::new(),
1227 action_listeners: SmallVec::new(),
1228 drop_listeners: SmallVec::new(),
1229 click_listeners: SmallVec::new(),
1230 drag_listener: None,
1231 hover_listener: None,
1232 tooltip_builder: None,
1233 }
1234 }
1235}
1236
1237#[derive(Default)]
1238pub struct InteractiveElementState {
1239 pub focus_handle: Option<FocusHandle>,
1240 pub clicked_state: Rc<RefCell<ElementClickedState>>,
1241 pub hover_state: Rc<RefCell<bool>>,
1242 pub pending_mouse_down: Rc<RefCell<Option<MouseDownEvent>>>,
1243 pub scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
1244 pub active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
1245}
1246
1247pub struct ActiveTooltip {
1248 tooltip: Option<AnyTooltip>,
1249 _task: Option<Task<()>>,
1250}
1251
1252/// Whether or not the element or a group that contains it is clicked by the mouse.
1253#[derive(Copy, Clone, Default, Eq, PartialEq)]
1254pub struct ElementClickedState {
1255 pub group: bool,
1256 pub element: bool,
1257}
1258
1259impl ElementClickedState {
1260 fn is_clicked(&self) -> bool {
1261 self.group || self.element
1262 }
1263}
1264
1265#[derive(Default)]
1266pub struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
1267
1268impl GroupBounds {
1269 pub fn get(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
1270 cx.default_global::<Self>()
1271 .0
1272 .get(name)
1273 .and_then(|bounds_stack| bounds_stack.last())
1274 .cloned()
1275 }
1276
1277 pub fn push(name: SharedString, bounds: Bounds<Pixels>, cx: &mut AppContext) {
1278 cx.default_global::<Self>()
1279 .0
1280 .entry(name)
1281 .or_default()
1282 .push(bounds);
1283 }
1284
1285 pub fn pop(name: &SharedString, cx: &mut AppContext) {
1286 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
1287 }
1288}
1289
1290pub struct Focusable<E> {
1291 element: E,
1292}
1293
1294impl<E: InteractiveElement> FocusableElement for Focusable<E> {}
1295
1296impl<E> InteractiveElement for Focusable<E>
1297where
1298 E: InteractiveElement,
1299{
1300 fn interactivity(&mut self) -> &mut Interactivity {
1301 self.element.interactivity()
1302 }
1303}
1304
1305impl<E: StatefulInteractiveElement> StatefulInteractiveElement for Focusable<E> {}
1306
1307impl<E> Styled for Focusable<E>
1308where
1309 E: Styled,
1310{
1311 fn style(&mut self) -> &mut StyleRefinement {
1312 self.element.style()
1313 }
1314}
1315
1316impl<E> Element for Focusable<E>
1317where
1318 E: Element,
1319{
1320 type State = E::State;
1321
1322 fn layout(
1323 &mut self,
1324 state: Option<Self::State>,
1325 cx: &mut WindowContext,
1326 ) -> (LayoutId, Self::State) {
1327 self.element.layout(state, cx)
1328 }
1329
1330 fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
1331 self.element.paint(bounds, state, cx)
1332 }
1333}
1334
1335impl<E> IntoElement for Focusable<E>
1336where
1337 E: Element,
1338{
1339 type Element = E;
1340
1341 fn element_id(&self) -> Option<ElementId> {
1342 self.element.element_id()
1343 }
1344
1345 fn into_element(self) -> Self::Element {
1346 self.element
1347 }
1348}
1349
1350impl<E> ParentElement for Focusable<E>
1351where
1352 E: ParentElement,
1353{
1354 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
1355 self.element.children_mut()
1356 }
1357}
1358
1359pub struct Stateful<E> {
1360 element: E,
1361}
1362
1363impl<E> Styled for Stateful<E>
1364where
1365 E: Styled,
1366{
1367 fn style(&mut self) -> &mut StyleRefinement {
1368 self.element.style()
1369 }
1370}
1371
1372impl<E> StatefulInteractiveElement for Stateful<E>
1373where
1374 E: Element,
1375 Self: InteractiveElement,
1376{
1377}
1378
1379impl<E> InteractiveElement for Stateful<E>
1380where
1381 E: InteractiveElement,
1382{
1383 fn interactivity(&mut self) -> &mut Interactivity {
1384 self.element.interactivity()
1385 }
1386}
1387
1388impl<E: FocusableElement> FocusableElement for Stateful<E> {}
1389
1390impl<E> Element for Stateful<E>
1391where
1392 E: Element,
1393{
1394 type State = E::State;
1395
1396 fn layout(
1397 &mut self,
1398 state: Option<Self::State>,
1399 cx: &mut WindowContext,
1400 ) -> (LayoutId, Self::State) {
1401 self.element.layout(state, cx)
1402 }
1403
1404 fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
1405 self.element.paint(bounds, state, cx)
1406 }
1407}
1408
1409impl<E> IntoElement for Stateful<E>
1410where
1411 E: Element,
1412{
1413 type Element = Self;
1414
1415 fn element_id(&self) -> Option<ElementId> {
1416 self.element.element_id()
1417 }
1418
1419 fn into_element(self) -> Self::Element {
1420 self
1421 }
1422}
1423
1424impl<E> ParentElement for Stateful<E>
1425where
1426 E: ParentElement,
1427{
1428 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
1429 self.element.children_mut()
1430 }
1431}