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