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