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