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