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 ElementState = 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::ElementState>,
613 cx: &mut ViewContext<V>,
614 ) -> (LayoutId, Self::ElementState) {
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 &mut self,
643 bounds: Bounds<Pixels>,
644 view_state: &mut V,
645 element_state: &mut Self::ElementState,
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 let mut interactivity = mem::take(&mut self.interactivity);
662 interactivity.paint(
663 bounds,
664 content_size,
665 &mut element_state.interactive_state,
666 cx,
667 |style, scroll_offset, cx| {
668 if style.visibility == Visibility::Hidden {
669 return;
670 }
671
672 let z_index = style.z_index.unwrap_or(0);
673
674 cx.with_z_index(z_index, |cx| {
675 cx.with_z_index(0, |cx| {
676 style.paint(bounds, cx);
677 });
678 cx.with_z_index(1, |cx| {
679 cx.with_text_style(style.text_style().cloned(), |cx| {
680 cx.with_content_mask(style.overflow_mask(bounds), |cx| {
681 cx.with_element_offset(scroll_offset, |cx| {
682 for child in &mut self.children {
683 child.paint(view_state, cx);
684 }
685 })
686 })
687 })
688 })
689 })
690 },
691 );
692 self.interactivity = interactivity;
693 }
694}
695
696impl<V: 'static> Component<V> for Div<V> {
697 fn render(self) -> AnyElement<V> {
698 AnyElement::new(self)
699 }
700}
701
702pub struct DivState {
703 child_layout_ids: SmallVec<[LayoutId; 4]>,
704 interactive_state: InteractiveElementState,
705}
706
707impl DivState {
708 pub fn is_active(&self) -> bool {
709 self.interactive_state.pending_mouse_down.borrow().is_some()
710 }
711}
712
713pub struct Interactivity<V> {
714 pub element_id: Option<ElementId>,
715 pub key_context: KeyContext,
716 pub focusable: bool,
717 pub tracked_focus_handle: Option<FocusHandle>,
718 pub focus_listeners: FocusListeners<V>,
719 pub group: Option<SharedString>,
720 pub base_style: StyleRefinement,
721 pub focus_style: StyleRefinement,
722 pub in_focus_style: StyleRefinement,
723 pub hover_style: StyleRefinement,
724 pub group_hover_style: Option<GroupStyle>,
725 pub active_style: StyleRefinement,
726 pub group_active_style: Option<GroupStyle>,
727 pub drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
728 pub group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>,
729 pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
730 pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
731 pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
732 pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
733 pub key_down_listeners: SmallVec<[KeyDownListener<V>; 2]>,
734 pub key_up_listeners: SmallVec<[KeyUpListener<V>; 2]>,
735 pub action_listeners: SmallVec<[(TypeId, ActionListener<V>); 8]>,
736 pub drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>,
737 pub click_listeners: SmallVec<[ClickListener<V>; 2]>,
738 pub drag_listener: Option<DragListener<V>>,
739 pub hover_listener: Option<HoverListener<V>>,
740 pub tooltip_builder: Option<TooltipBuilder<V>>,
741}
742
743impl<V> Interactivity<V>
744where
745 V: 'static,
746{
747 pub fn layout(
748 &mut self,
749 element_state: Option<InteractiveElementState>,
750 cx: &mut ViewContext<V>,
751 f: impl FnOnce(Style, &mut ViewContext<V>) -> LayoutId,
752 ) -> (LayoutId, InteractiveElementState) {
753 let mut element_state = element_state.unwrap_or_default();
754
755 // Ensure we store a focus handle in our element state if we're focusable.
756 // If there's an explicit focus handle we're tracking, use that. Otherwise
757 // create a new handle and store it in the element state, which lives for as
758 // as frames contain an element with this id.
759 if self.focusable {
760 element_state.focus_handle.get_or_insert_with(|| {
761 self.tracked_focus_handle
762 .clone()
763 .unwrap_or_else(|| cx.focus_handle())
764 });
765 }
766
767 let style = self.compute_style(None, &mut element_state, cx);
768 let layout_id = f(style, cx);
769 (layout_id, element_state)
770 }
771
772 pub fn paint(
773 &mut self,
774 bounds: Bounds<Pixels>,
775 content_size: Size<Pixels>,
776 element_state: &mut InteractiveElementState,
777 cx: &mut ViewContext<V>,
778 f: impl FnOnce(Style, Point<Pixels>, &mut ViewContext<V>),
779 ) {
780 let style = self.compute_style(Some(bounds), element_state, cx);
781
782 if let Some(mouse_cursor) = style.mouse_cursor {
783 let hovered = bounds.contains_point(&cx.mouse_position());
784 if hovered {
785 cx.set_cursor_style(mouse_cursor);
786 }
787 }
788
789 for listener in self.mouse_down_listeners.drain(..) {
790 cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| {
791 listener(state, event, &bounds, phase, cx);
792 })
793 }
794
795 for listener in self.mouse_up_listeners.drain(..) {
796 cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
797 listener(state, event, &bounds, phase, cx);
798 })
799 }
800
801 for listener in self.mouse_move_listeners.drain(..) {
802 cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| {
803 listener(state, event, &bounds, phase, cx);
804 })
805 }
806
807 for listener in self.scroll_wheel_listeners.drain(..) {
808 cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| {
809 listener(state, event, &bounds, phase, cx);
810 })
811 }
812
813 let hover_group_bounds = self
814 .group_hover_style
815 .as_ref()
816 .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
817
818 if let Some(group_bounds) = hover_group_bounds {
819 let hovered = group_bounds.contains_point(&cx.mouse_position());
820 cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
821 if phase == DispatchPhase::Capture {
822 if group_bounds.contains_point(&event.position) != hovered {
823 cx.notify();
824 }
825 }
826 });
827 }
828
829 if self.hover_style.is_some()
830 || (cx.active_drag.is_some() && !self.drag_over_styles.is_empty())
831 {
832 let hovered = bounds.contains_point(&cx.mouse_position());
833 cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
834 if phase == DispatchPhase::Capture {
835 if bounds.contains_point(&event.position) != hovered {
836 cx.notify();
837 }
838 }
839 });
840 }
841
842 if cx.active_drag.is_some() {
843 let drop_listeners = mem::take(&mut self.drop_listeners);
844 cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| {
845 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
846 if let Some(drag_state_type) =
847 cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
848 {
849 for (drop_state_type, listener) in &drop_listeners {
850 if *drop_state_type == drag_state_type {
851 let drag = cx
852 .active_drag
853 .take()
854 .expect("checked for type drag state type above");
855 listener(view, drag.view.clone(), cx);
856 cx.notify();
857 cx.stop_propagation();
858 }
859 }
860 }
861 }
862 });
863 }
864
865 let click_listeners = mem::take(&mut self.click_listeners);
866 let drag_listener = mem::take(&mut self.drag_listener);
867
868 if !click_listeners.is_empty() || drag_listener.is_some() {
869 let pending_mouse_down = element_state.pending_mouse_down.clone();
870 let mouse_down = pending_mouse_down.borrow().clone();
871 if let Some(mouse_down) = mouse_down {
872 if let Some(drag_listener) = drag_listener {
873 let active_state = element_state.clicked_state.clone();
874
875 cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
876 if cx.active_drag.is_some() {
877 if phase == DispatchPhase::Capture {
878 cx.notify();
879 }
880 } else if phase == DispatchPhase::Bubble
881 && bounds.contains_point(&event.position)
882 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
883 {
884 *active_state.borrow_mut() = ElementClickedState::default();
885 let cursor_offset = event.position - bounds.origin;
886 let drag = drag_listener(view_state, cursor_offset, cx);
887 cx.active_drag = Some(drag);
888 cx.notify();
889 cx.stop_propagation();
890 }
891 });
892 }
893
894 cx.on_mouse_event(move |view_state, event: &MouseUpEvent, phase, cx| {
895 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
896 let mouse_click = ClickEvent {
897 down: mouse_down.clone(),
898 up: event.clone(),
899 };
900 for listener in &click_listeners {
901 listener(view_state, &mouse_click, cx);
902 }
903 }
904 *pending_mouse_down.borrow_mut() = None;
905 cx.notify();
906 });
907 } else {
908 cx.on_mouse_event(move |_view_state, event: &MouseDownEvent, phase, cx| {
909 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
910 *pending_mouse_down.borrow_mut() = Some(event.clone());
911 cx.notify();
912 }
913 });
914 }
915 }
916
917 if let Some(hover_listener) = self.hover_listener.take() {
918 let was_hovered = element_state.hover_state.clone();
919 let has_mouse_down = element_state.pending_mouse_down.clone();
920
921 cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
922 if phase != DispatchPhase::Bubble {
923 return;
924 }
925 let is_hovered =
926 bounds.contains_point(&event.position) && has_mouse_down.borrow().is_none();
927 let mut was_hovered = was_hovered.borrow_mut();
928
929 if is_hovered != was_hovered.clone() {
930 *was_hovered = is_hovered;
931 drop(was_hovered);
932
933 hover_listener(view_state, is_hovered, cx);
934 }
935 });
936 }
937
938 if let Some(tooltip_builder) = self.tooltip_builder.take() {
939 let active_tooltip = element_state.active_tooltip.clone();
940 let pending_mouse_down = element_state.pending_mouse_down.clone();
941
942 cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
943 if phase != DispatchPhase::Bubble {
944 return;
945 }
946
947 let is_hovered =
948 bounds.contains_point(&event.position) && pending_mouse_down.borrow().is_none();
949 if !is_hovered {
950 active_tooltip.borrow_mut().take();
951 return;
952 }
953
954 if active_tooltip.borrow().is_none() {
955 let task = cx.spawn({
956 let active_tooltip = active_tooltip.clone();
957 let tooltip_builder = tooltip_builder.clone();
958
959 move |view, mut cx| async move {
960 cx.background_executor().timer(TOOLTIP_DELAY).await;
961 view.update(&mut cx, move |view_state, cx| {
962 active_tooltip.borrow_mut().replace(ActiveTooltip {
963 tooltip: Some(AnyTooltip {
964 view: tooltip_builder(view_state, cx),
965 cursor_offset: cx.mouse_position(),
966 }),
967 _task: None,
968 });
969 cx.notify();
970 })
971 .ok();
972 }
973 });
974 active_tooltip.borrow_mut().replace(ActiveTooltip {
975 tooltip: None,
976 _task: Some(task),
977 });
978 }
979 });
980
981 let active_tooltip = element_state.active_tooltip.clone();
982 cx.on_mouse_event(move |_, _: &MouseDownEvent, _, _| {
983 active_tooltip.borrow_mut().take();
984 });
985
986 if let Some(active_tooltip) = element_state.active_tooltip.borrow().as_ref() {
987 if active_tooltip.tooltip.is_some() {
988 cx.active_tooltip = active_tooltip.tooltip.clone()
989 }
990 }
991 }
992
993 let active_state = element_state.clicked_state.clone();
994 if !active_state.borrow().is_clicked() {
995 cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| {
996 if phase == DispatchPhase::Capture {
997 *active_state.borrow_mut() = ElementClickedState::default();
998 cx.notify();
999 }
1000 });
1001 } else {
1002 let active_group_bounds = self
1003 .group_active_style
1004 .as_ref()
1005 .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
1006 cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| {
1007 if phase == DispatchPhase::Bubble {
1008 let group = active_group_bounds
1009 .map_or(false, |bounds| bounds.contains_point(&down.position));
1010 let element = bounds.contains_point(&down.position);
1011 if group || element {
1012 *active_state.borrow_mut() = ElementClickedState { group, element };
1013 cx.notify();
1014 }
1015 }
1016 });
1017 }
1018
1019 let overflow = style.overflow;
1020 if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll {
1021 let scroll_offset = element_state
1022 .scroll_offset
1023 .get_or_insert_with(Rc::default)
1024 .clone();
1025 let line_height = cx.line_height();
1026 let scroll_max = (content_size - bounds.size).max(&Size::default());
1027
1028 cx.on_mouse_event(move |_, event: &ScrollWheelEvent, phase, cx| {
1029 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
1030 let mut scroll_offset = scroll_offset.borrow_mut();
1031 let old_scroll_offset = *scroll_offset;
1032 let delta = event.delta.pixel_delta(line_height);
1033
1034 if overflow.x == Overflow::Scroll {
1035 scroll_offset.x =
1036 (scroll_offset.x + delta.x).clamp(-scroll_max.width, px(0.));
1037 }
1038
1039 if overflow.y == Overflow::Scroll {
1040 scroll_offset.y =
1041 (scroll_offset.y + delta.y).clamp(-scroll_max.height, px(0.));
1042 }
1043
1044 if *scroll_offset != old_scroll_offset {
1045 cx.notify();
1046 cx.stop_propagation();
1047 }
1048 }
1049 });
1050 }
1051
1052 if let Some(group) = self.group.clone() {
1053 GroupBounds::push(group, bounds, cx);
1054 }
1055
1056 let scroll_offset = element_state
1057 .scroll_offset
1058 .as_ref()
1059 .map(|scroll_offset| *scroll_offset.borrow());
1060
1061 cx.with_key_dispatch(
1062 self.key_context.clone(),
1063 element_state.focus_handle.clone(),
1064 |_, cx| {
1065 for listener in self.key_down_listeners.drain(..) {
1066 cx.on_key_event(move |state, event: &KeyDownEvent, phase, cx| {
1067 listener(state, event, phase, cx);
1068 })
1069 }
1070
1071 for listener in self.key_up_listeners.drain(..) {
1072 cx.on_key_event(move |state, event: &KeyUpEvent, phase, cx| {
1073 listener(state, event, phase, cx);
1074 })
1075 }
1076
1077 for (action_type, listener) in self.action_listeners.drain(..) {
1078 cx.on_action(action_type, listener)
1079 }
1080
1081 if let Some(focus_handle) = element_state.focus_handle.as_ref() {
1082 for listener in self.focus_listeners.drain(..) {
1083 let focus_handle = focus_handle.clone();
1084 cx.on_focus_changed(move |view, event, cx| {
1085 listener(view, &focus_handle, event, cx)
1086 });
1087 }
1088 }
1089
1090 f(style, scroll_offset.unwrap_or_default(), cx)
1091 },
1092 );
1093
1094 if let Some(group) = self.group.as_ref() {
1095 GroupBounds::pop(group, cx);
1096 }
1097 }
1098
1099 pub fn compute_style(
1100 &self,
1101 bounds: Option<Bounds<Pixels>>,
1102 element_state: &mut InteractiveElementState,
1103 cx: &mut ViewContext<V>,
1104 ) -> Style {
1105 let mut style = Style::default();
1106 style.refine(&self.base_style);
1107
1108 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
1109 if focus_handle.within_focused(cx) {
1110 style.refine(&self.in_focus_style);
1111 }
1112
1113 if focus_handle.is_focused(cx) {
1114 style.refine(&self.focus_style);
1115 }
1116 }
1117
1118 if let Some(bounds) = bounds {
1119 let mouse_position = cx.mouse_position();
1120 if let Some(group_hover) = self.group_hover_style.as_ref() {
1121 if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) {
1122 if group_bounds.contains_point(&mouse_position) {
1123 style.refine(&group_hover.style);
1124 }
1125 }
1126 }
1127 // if self.hover_style.is_some() {
1128 if bounds.contains_point(&mouse_position) {
1129 // eprintln!("div hovered {bounds:?} {mouse_position:?}");
1130 style.refine(&self.hover_style);
1131 } else {
1132 // eprintln!("div NOT hovered {bounds:?} {mouse_position:?}");
1133 }
1134 // }
1135
1136 if let Some(drag) = cx.active_drag.take() {
1137 for (state_type, group_drag_style) in &self.group_drag_over_styles {
1138 if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
1139 if *state_type == drag.view.entity_type()
1140 && group_bounds.contains_point(&mouse_position)
1141 {
1142 style.refine(&group_drag_style.style);
1143 }
1144 }
1145 }
1146
1147 for (state_type, drag_over_style) in &self.drag_over_styles {
1148 if *state_type == drag.view.entity_type()
1149 && bounds.contains_point(&mouse_position)
1150 {
1151 style.refine(drag_over_style);
1152 }
1153 }
1154
1155 cx.active_drag = Some(drag);
1156 }
1157 }
1158
1159 let clicked_state = element_state.clicked_state.borrow();
1160 if clicked_state.group {
1161 if let Some(group) = self.group_active_style.as_ref() {
1162 style.refine(&group.style)
1163 }
1164 }
1165
1166 if clicked_state.element {
1167 style.refine(&self.active_style)
1168 }
1169
1170 style
1171 }
1172}
1173
1174impl<V: 'static> Default for Interactivity<V> {
1175 fn default() -> Self {
1176 Self {
1177 element_id: None,
1178 key_context: KeyContext::default(),
1179 focusable: false,
1180 tracked_focus_handle: None,
1181 focus_listeners: SmallVec::default(),
1182 // scroll_offset: Point::default(),
1183 group: None,
1184 base_style: StyleRefinement::default(),
1185 focus_style: StyleRefinement::default(),
1186 in_focus_style: StyleRefinement::default(),
1187 hover_style: StyleRefinement::default(),
1188 group_hover_style: None,
1189 active_style: StyleRefinement::default(),
1190 group_active_style: None,
1191 drag_over_styles: SmallVec::new(),
1192 group_drag_over_styles: SmallVec::new(),
1193 mouse_down_listeners: SmallVec::new(),
1194 mouse_up_listeners: SmallVec::new(),
1195 mouse_move_listeners: SmallVec::new(),
1196 scroll_wheel_listeners: SmallVec::new(),
1197 key_down_listeners: SmallVec::new(),
1198 key_up_listeners: SmallVec::new(),
1199 action_listeners: SmallVec::new(),
1200 drop_listeners: SmallVec::new(),
1201 click_listeners: SmallVec::new(),
1202 drag_listener: None,
1203 hover_listener: None,
1204 tooltip_builder: None,
1205 }
1206 }
1207}
1208
1209#[derive(Default)]
1210pub struct InteractiveElementState {
1211 pub focus_handle: Option<FocusHandle>,
1212 pub clicked_state: Rc<RefCell<ElementClickedState>>,
1213 pub hover_state: Rc<RefCell<bool>>,
1214 pub pending_mouse_down: Rc<RefCell<Option<MouseDownEvent>>>,
1215 pub scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
1216 pub active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
1217}
1218
1219pub struct ActiveTooltip {
1220 tooltip: Option<AnyTooltip>,
1221 _task: Option<Task<()>>,
1222}
1223
1224/// Whether or not the element or a group that contains it is clicked by the mouse.
1225#[derive(Copy, Clone, Default, Eq, PartialEq)]
1226pub struct ElementClickedState {
1227 pub group: bool,
1228 pub element: bool,
1229}
1230
1231impl ElementClickedState {
1232 fn is_clicked(&self) -> bool {
1233 self.group || self.element
1234 }
1235}
1236
1237#[derive(Default)]
1238pub struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
1239
1240impl GroupBounds {
1241 pub fn get(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
1242 cx.default_global::<Self>()
1243 .0
1244 .get(name)
1245 .and_then(|bounds_stack| bounds_stack.last())
1246 .cloned()
1247 }
1248
1249 pub fn push(name: SharedString, bounds: Bounds<Pixels>, cx: &mut AppContext) {
1250 cx.default_global::<Self>()
1251 .0
1252 .entry(name)
1253 .or_default()
1254 .push(bounds);
1255 }
1256
1257 pub fn pop(name: &SharedString, cx: &mut AppContext) {
1258 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
1259 }
1260}
1261
1262pub struct Focusable<V, E> {
1263 element: E,
1264 view_type: PhantomData<V>,
1265}
1266
1267impl<V: 'static, E: InteractiveComponent<V>> FocusableComponent<V> for Focusable<V, E> {}
1268
1269impl<V, E> InteractiveComponent<V> for Focusable<V, E>
1270where
1271 V: 'static,
1272 E: InteractiveComponent<V>,
1273{
1274 fn interactivity(&mut self) -> &mut Interactivity<V> {
1275 self.element.interactivity()
1276 }
1277}
1278
1279impl<V: 'static, E: StatefulInteractiveComponent<V, E>> StatefulInteractiveComponent<V, E>
1280 for Focusable<V, E>
1281{
1282}
1283
1284impl<V, E> Styled for Focusable<V, E>
1285where
1286 V: 'static,
1287 E: Styled,
1288{
1289 fn style(&mut self) -> &mut StyleRefinement {
1290 self.element.style()
1291 }
1292}
1293
1294impl<V, E> Element<V> for Focusable<V, E>
1295where
1296 V: 'static,
1297 E: Element<V>,
1298{
1299 type ElementState = E::ElementState;
1300
1301 fn element_id(&self) -> Option<ElementId> {
1302 self.element.element_id()
1303 }
1304
1305 fn layout(
1306 &mut self,
1307 view_state: &mut V,
1308 element_state: Option<Self::ElementState>,
1309 cx: &mut ViewContext<V>,
1310 ) -> (LayoutId, Self::ElementState) {
1311 self.element.layout(view_state, element_state, cx)
1312 }
1313
1314 fn paint(
1315 &mut self,
1316 bounds: Bounds<Pixels>,
1317 view_state: &mut V,
1318 element_state: &mut Self::ElementState,
1319 cx: &mut ViewContext<V>,
1320 ) {
1321 self.element.paint(bounds, view_state, element_state, cx);
1322 }
1323}
1324
1325impl<V, E> Component<V> for Focusable<V, E>
1326where
1327 V: 'static,
1328 E: 'static + Element<V>,
1329{
1330 fn render(self) -> AnyElement<V> {
1331 AnyElement::new(self)
1332 }
1333}
1334
1335impl<V, E> ParentComponent<V> for Focusable<V, E>
1336where
1337 V: 'static,
1338 E: ParentComponent<V>,
1339{
1340 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
1341 self.element.children_mut()
1342 }
1343}
1344
1345pub struct Stateful<V, E> {
1346 element: E,
1347 view_type: PhantomData<V>,
1348}
1349
1350impl<V, E> Styled for Stateful<V, E>
1351where
1352 V: 'static,
1353 E: Styled,
1354{
1355 fn style(&mut self) -> &mut StyleRefinement {
1356 self.element.style()
1357 }
1358}
1359
1360impl<V, E> StatefulInteractiveComponent<V, E> for Stateful<V, E>
1361where
1362 V: 'static,
1363 E: Element<V>,
1364 Self: InteractiveComponent<V>,
1365{
1366}
1367
1368impl<V, E> InteractiveComponent<V> for Stateful<V, E>
1369where
1370 V: 'static,
1371 E: InteractiveComponent<V>,
1372{
1373 fn interactivity(&mut self) -> &mut Interactivity<V> {
1374 self.element.interactivity()
1375 }
1376}
1377
1378impl<V: 'static, E: FocusableComponent<V>> FocusableComponent<V> for Stateful<V, E> {}
1379
1380impl<V, E> Element<V> for Stateful<V, E>
1381where
1382 V: 'static,
1383 E: Element<V>,
1384{
1385 type ElementState = E::ElementState;
1386
1387 fn element_id(&self) -> Option<ElementId> {
1388 self.element.element_id()
1389 }
1390
1391 fn layout(
1392 &mut self,
1393 view_state: &mut V,
1394 element_state: Option<Self::ElementState>,
1395 cx: &mut ViewContext<V>,
1396 ) -> (LayoutId, Self::ElementState) {
1397 self.element.layout(view_state, element_state, cx)
1398 }
1399
1400 fn paint(
1401 &mut self,
1402 bounds: Bounds<Pixels>,
1403 view_state: &mut V,
1404 element_state: &mut Self::ElementState,
1405 cx: &mut ViewContext<V>,
1406 ) {
1407 self.element.paint(bounds, view_state, element_state, cx)
1408 }
1409}
1410
1411impl<V, E> Component<V> for Stateful<V, E>
1412where
1413 V: 'static,
1414 E: 'static + Element<V>,
1415{
1416 fn render(self) -> AnyElement<V> {
1417 AnyElement::new(self)
1418 }
1419}
1420
1421impl<V, E> ParentComponent<V> for Stateful<V, E>
1422where
1423 V: 'static,
1424 E: ParentComponent<V>,
1425{
1426 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
1427 self.element.children_mut()
1428 }
1429}