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