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