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