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