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