1use crate::{
2 point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext,
3 BorrowWindow, Bounds, CallbackHandle, ClickEvent, Component, ConstructorHandle, DispatchPhase,
4 Element, ElementId, FocusEvent, FocusHandle, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId,
5 MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels, Point,
6 Render, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task, View,
7 Visibility, WindowContext,
8};
9use collections::HashMap;
10use refineable::Refineable;
11use smallvec::SmallVec;
12use std::{
13 any::{Any, TypeId},
14 cell::RefCell,
15 fmt::Debug,
16 mem,
17 rc::Rc,
18 time::Duration,
19};
20use taffy::style::Overflow;
21use util::ResultExt;
22
23const DRAG_THRESHOLD: f64 = 2.;
24const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
25
26pub struct GroupStyle {
27 pub group: SharedString,
28 pub style: StyleRefinement,
29}
30
31pub trait InteractiveComponent: Sized + Element {
32 fn interactivity(&mut self) -> &mut Interactivity;
33
34 fn group(mut self, group: impl Into<SharedString>) -> Self {
35 self.interactivity().group = Some(group.into());
36 self
37 }
38
39 fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
40 self.interactivity().element_id = Some(id.into());
41
42 Stateful { element: self }
43 }
44
45 fn track_focus(mut self, focus_handle: &FocusHandle) -> Focusable<Self> {
46 self.interactivity().focusable = true;
47 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
48 Focusable { element: self }
49 }
50
51 fn key_context<C, E>(mut self, key_context: C) -> Self
52 where
53 C: TryInto<KeyContext, Error = E>,
54 E: Debug,
55 {
56 if let Some(key_context) = key_context.try_into().log_err() {
57 self.interactivity().key_context = key_context;
58 }
59 self
60 }
61
62 fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
63 self.interactivity().hover_style = f(StyleRefinement::default());
64 self
65 }
66
67 fn group_hover(
68 mut self,
69 group_name: impl Into<SharedString>,
70 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
71 ) -> Self {
72 self.interactivity().group_hover_style = Some(GroupStyle {
73 group: group_name.into(),
74 style: f(StyleRefinement::default()),
75 });
76 self
77 }
78
79 fn on_mouse_down(
80 mut self,
81 button: MouseButton,
82 handler: impl Into<CallbackHandle<MouseDownEvent>>,
83 ) -> Self {
84 let handler = handler.into();
85 self.interactivity().mouse_down_listeners.push(Box::new(
86 move |event, bounds, phase, cx| {
87 if phase == DispatchPhase::Bubble
88 && event.button == button
89 && bounds.contains_point(&event.position)
90 {
91 (handler.callback)(event, cx)
92 }
93 },
94 ));
95 self
96 }
97
98 fn on_any_mouse_down(mut self, handler: impl Into<CallbackHandle<MouseDownEvent>>) -> Self {
99 let handler = handler.into();
100 self.interactivity().mouse_down_listeners.push(Box::new(
101 move |event, bounds, phase, cx| {
102 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
103 (handler.callback)(event, cx)
104 }
105 },
106 ));
107 self
108 }
109
110 fn on_mouse_up(
111 mut self,
112 button: MouseButton,
113 handler: impl Into<CallbackHandle<MouseUpEvent>>,
114 ) -> Self {
115 let handler = handler.into();
116 self.interactivity()
117 .mouse_up_listeners
118 .push(Box::new(move |event, bounds, phase, cx| {
119 if phase == DispatchPhase::Bubble
120 && event.button == button
121 && bounds.contains_point(&event.position)
122 {
123 (handler.callback)(event, cx)
124 }
125 }));
126 self
127 }
128
129 fn on_any_mouse_up(mut self, handler: impl Into<CallbackHandle<MouseUpEvent>>) -> Self {
130 let handler = handler.into();
131 self.interactivity()
132 .mouse_up_listeners
133 .push(Box::new(move |event, bounds, phase, cx| {
134 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
135 (handler.callback)(event, cx)
136 }
137 }));
138 self
139 }
140
141 fn on_mouse_down_out(mut self, handler: impl Into<CallbackHandle<MouseDownEvent>>) -> Self {
142 let handler = handler.into();
143 self.interactivity().mouse_down_listeners.push(Box::new(
144 move |event, bounds, phase, cx| {
145 if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) {
146 (handler.callback)(event, cx)
147 }
148 },
149 ));
150 self
151 }
152
153 fn on_mouse_up_out(
154 mut self,
155 button: MouseButton,
156 handler: impl Into<CallbackHandle<MouseUpEvent>>,
157 ) -> Self {
158 let handler = handler.into();
159 self.interactivity()
160 .mouse_up_listeners
161 .push(Box::new(move |event, bounds, phase, cx| {
162 if phase == DispatchPhase::Capture
163 && event.button == button
164 && !bounds.contains_point(&event.position)
165 {
166 (handler.callback)(event, cx);
167 }
168 }));
169 self
170 }
171
172 fn on_mouse_move(mut self, handler: impl Into<CallbackHandle<MouseMoveEvent>>) -> Self {
173 let handler = handler.into();
174 self.interactivity().mouse_move_listeners.push(Box::new(
175 move |event, bounds, phase, cx| {
176 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
177 (handler.callback)(event, cx);
178 }
179 },
180 ));
181 self
182 }
183
184 fn on_scroll_wheel(mut self, handler: impl Into<CallbackHandle<ScrollWheelEvent>>) -> Self {
185 let handler = handler.into();
186 self.interactivity().scroll_wheel_listeners.push(Box::new(
187 move |event, bounds, phase, cx| {
188 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
189 (handler.callback)(event, cx);
190 }
191 },
192 ));
193 self
194 }
195
196 /// Capture the given action, before normal action dispatch can fire
197 fn capture_action<A: Action>(mut self, listener: impl Into<CallbackHandle<A>>) -> Self {
198 let listener = listener.into();
199 self.interactivity().action_listeners.push((
200 TypeId::of::<A>(),
201 Box::new(move |action, phase, cx| {
202 let action = action.downcast_ref().unwrap();
203 if phase == DispatchPhase::Capture {
204 (listener.callback)(action, cx)
205 }
206 }),
207 ));
208 self
209 }
210
211 /// Add a listener for the given action, fires during the bubble event phase
212 fn on_action<A: Action>(mut self, listener: impl Into<CallbackHandle<A>> + 'static) -> Self {
213 let handle = listener.into();
214 // NOTE: this debug assert has the side-effect of working around
215 // a bug where a crate consisting only of action definitions does
216 // not register the actions in debug builds:
217 //
218 // https://github.com/rust-lang/rust/issues/47384
219 // https://github.com/mmastrac/rust-ctor/issues/280
220 //
221 // if we are relying on this side-effect still, removing the debug_assert!
222 // likely breaks the command_palette tests.
223 // debug_assert!(
224 // A::is_registered(),
225 // "{:?} is not registered as an action",
226 // A::qualified_name()
227 // );
228 self.interactivity().action_listeners.push((
229 TypeId::of::<A>(),
230 Box::new(move |action, phase, cx| {
231 let action = action.downcast_ref().unwrap();
232 if phase == DispatchPhase::Bubble {
233 (handle.callback)(action, cx)
234 }
235 }),
236 ));
237 self
238 }
239
240 fn on_key_down(mut self, listener: impl Into<CallbackHandle<KeyDownEvent>>) -> Self {
241 let listener = listener.into();
242 self.interactivity()
243 .key_down_listeners
244 .push(Box::new(move |event, phase, cx| {
245 if phase == DispatchPhase::Bubble {
246 (listener.callback)(event, cx)
247 }
248 }));
249 self
250 }
251
252 fn capture_key_down(mut self, listener: impl Into<CallbackHandle<KeyDownEvent>>) -> Self {
253 let listener = listener.into();
254 self.interactivity()
255 .key_down_listeners
256 .push(Box::new(move |event, phase, cx| {
257 if phase == DispatchPhase::Capture {
258 (listener.callback)(event, cx)
259 }
260 }));
261 self
262 }
263
264 fn on_key_up(mut self, listener: impl Into<CallbackHandle<KeyUpEvent>>) -> Self {
265 let listener = listener.into();
266 self.interactivity()
267 .key_up_listeners
268 .push(Box::new(move |event, phase, cx| {
269 if phase == DispatchPhase::Bubble {
270 (listener.callback)(event, cx)
271 }
272 }));
273 self
274 }
275
276 fn capture_key_up(mut self, listener: impl Into<CallbackHandle<KeyUpEvent>>) -> Self {
277 let listener = listener.into();
278 self.interactivity()
279 .key_up_listeners
280 .push(Box::new(move |event, phase, cx| {
281 if phase == DispatchPhase::Capture {
282 (listener.callback)(event, cx)
283 }
284 }));
285 self
286 }
287
288 fn drag_over<S: 'static>(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
289 self.interactivity()
290 .drag_over_styles
291 .push((TypeId::of::<S>(), f(StyleRefinement::default())));
292 self
293 }
294
295 fn group_drag_over<S: 'static>(
296 mut self,
297 group_name: impl Into<SharedString>,
298 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
299 ) -> Self {
300 self.interactivity().group_drag_over_styles.push((
301 TypeId::of::<S>(),
302 GroupStyle {
303 group: group_name.into(),
304 style: f(StyleRefinement::default()),
305 },
306 ));
307 self
308 }
309
310 fn on_drop<W: 'static>(mut self, listener: impl Into<CallbackHandle<View<W>>>) -> Self {
311 let listener = listener.into();
312 self.interactivity().drop_listeners.push((
313 TypeId::of::<W>(),
314 Box::new(move |dragged_view, cx| {
315 (listener.callback)(&dragged_view.downcast().unwrap(), cx);
316 }),
317 ));
318 self
319 }
320}
321
322pub trait StatefulInteractiveComponent: InteractiveComponent {
323 fn focusable(mut self) -> Focusable<Self> {
324 self.interactivity().focusable = true;
325 Focusable { element: self }
326 }
327
328 fn overflow_scroll(mut self) -> Self {
329 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
330 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
331 self
332 }
333
334 fn overflow_x_scroll(mut self) -> Self {
335 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
336 self
337 }
338
339 fn overflow_y_scroll(mut self) -> Self {
340 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
341 self
342 }
343
344 fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
345 where
346 Self: Sized,
347 {
348 self.interactivity().active_style = f(StyleRefinement::default());
349 self
350 }
351
352 fn group_active(
353 mut self,
354 group_name: impl Into<SharedString>,
355 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
356 ) -> Self
357 where
358 Self: Sized,
359 {
360 self.interactivity().group_active_style = Some(GroupStyle {
361 group: group_name.into(),
362 style: f(StyleRefinement::default()),
363 });
364 self
365 }
366
367 fn on_click(mut self, listener: impl Into<CallbackHandle<ClickEvent>>) -> Self
368 where
369 Self: Sized,
370 {
371 let listener = listener.into();
372 self.interactivity()
373 .click_listeners
374 .push(Box::new(move |event, cx| (listener.callback)(event, cx)));
375 self
376 }
377
378 fn on_drag<W>(mut self, listener: impl Into<ConstructorHandle<View<W>>>) -> Self
379 where
380 Self: Sized,
381 W: 'static + Render,
382 {
383 let listener = listener.into();
384 debug_assert!(
385 self.interactivity().drag_listener.is_none(),
386 "calling on_drag more than once on the same element is not supported"
387 );
388 self.interactivity().drag_listener = Some(Box::new(move |cursor_offset, cx| AnyDrag {
389 view: (listener.callback)(cx).into(),
390 cursor_offset,
391 }));
392 self
393 }
394
395 fn on_hover(mut self, listener: impl Into<CallbackHandle<bool>>) -> Self
396 where
397 Self: Sized,
398 {
399 let listener = listener.into();
400 debug_assert!(
401 self.interactivity().hover_listener.is_none(),
402 "calling on_hover more than once on the same element is not supported"
403 );
404 self.interactivity().hover_listener = Some(listener);
405 self
406 }
407
408 fn tooltip(mut self, build_tooltip: impl Into<ConstructorHandle<AnyView>>) -> Self
409 where
410 Self: Sized,
411 {
412 let build_tooltip = build_tooltip.into();
413 debug_assert!(
414 self.interactivity().tooltip_builder.is_none(),
415 "calling tooltip more than once on the same element is not supported"
416 );
417 self.interactivity().tooltip_builder =
418 Some(Rc::new(move |cx| (build_tooltip.callback)(cx)));
419
420 self
421 }
422}
423
424pub trait FocusableComponent: InteractiveComponent {
425 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
426 where
427 Self: Sized,
428 {
429 self.interactivity().focus_style = f(StyleRefinement::default());
430 self
431 }
432
433 fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
434 where
435 Self: Sized,
436 {
437 self.interactivity().in_focus_style = f(StyleRefinement::default());
438 self
439 }
440
441 fn on_focus(mut self, listener: impl Into<CallbackHandle<FocusEvent>>) -> Self
442 where
443 Self: Sized,
444 {
445 let listener = listener.into();
446 self.interactivity()
447 .focus_listeners
448 .push(Box::new(move |focus_handle, event, cx| {
449 if event.focused.as_ref() == Some(focus_handle) {
450 (listener.callback)(event, cx)
451 }
452 }));
453 self
454 }
455
456 fn on_blur(mut self, listener: impl Into<CallbackHandle<FocusEvent>>) -> Self
457 where
458 Self: Sized,
459 {
460 let listener = listener.into();
461 self.interactivity()
462 .focus_listeners
463 .push(Box::new(move |focus_handle, event, cx| {
464 if event.blurred.as_ref() == Some(focus_handle) {
465 (listener.callback)(event, cx)
466 }
467 }));
468 self
469 }
470
471 fn on_focus_in(mut self, listener: impl Into<CallbackHandle<FocusEvent>>) -> Self
472 where
473 Self: Sized,
474 {
475 let listener = listener.into();
476 self.interactivity()
477 .focus_listeners
478 .push(Box::new(move |focus_handle, event, cx| {
479 let descendant_blurred = event
480 .blurred
481 .as_ref()
482 .map_or(false, |blurred| focus_handle.contains(blurred, cx));
483 let descendant_focused = event
484 .focused
485 .as_ref()
486 .map_or(false, |focused| focus_handle.contains(focused, cx));
487
488 if !descendant_blurred && descendant_focused {
489 (listener.callback)(event, cx)
490 }
491 }));
492 self
493 }
494
495 fn on_focus_out(mut self, listener: impl Into<CallbackHandle<FocusEvent>>) -> Self
496 where
497 Self: Sized,
498 {
499 let listener = listener.into();
500 self.interactivity()
501 .focus_listeners
502 .push(Box::new(move |focus_handle, event, cx| {
503 let descendant_blurred = event
504 .blurred
505 .as_ref()
506 .map_or(false, |blurred| focus_handle.contains(blurred, cx));
507 let descendant_focused = event
508 .focused
509 .as_ref()
510 .map_or(false, |focused| focus_handle.contains(focused, cx));
511 if descendant_blurred && !descendant_focused {
512 (listener.callback)(event, cx)
513 }
514 }));
515 self
516 }
517}
518
519pub type FocusListeners = SmallVec<[FocusListener; 2]>;
520
521pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>;
522
523pub type MouseDownListener =
524 Box<dyn Fn(&MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
525pub type MouseUpListener =
526 Box<dyn Fn(&MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
527
528pub type MouseMoveListener =
529 Box<dyn Fn(&MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
530
531pub type ScrollWheelListener =
532 Box<dyn Fn(&ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
533
534pub type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
535
536pub type DragListener = Box<dyn Fn(Point<Pixels>, &mut WindowContext) -> AnyDrag + 'static>;
537
538type DropListener = dyn Fn(AnyView, &mut WindowContext) + 'static;
539
540pub type TooltipBuilder = Rc<dyn Fn(&mut WindowContext) -> AnyView + 'static>;
541
542pub type KeyDownListener = Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + 'static>;
543
544pub type KeyUpListener = Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut WindowContext) + 'static>;
545
546pub type ActionListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
547
548pub fn div() -> Div {
549 Div {
550 interactivity: Interactivity::default(),
551 children: SmallVec::default(),
552 }
553}
554
555pub struct Div {
556 interactivity: Interactivity,
557 children: SmallVec<[AnyElement; 2]>,
558}
559
560impl Styled for Div {
561 fn style(&mut self) -> &mut StyleRefinement {
562 &mut self.interactivity.base_style
563 }
564}
565
566impl InteractiveComponent for Div {
567 fn interactivity(&mut self) -> &mut Interactivity {
568 &mut self.interactivity
569 }
570}
571
572impl ParentComponent for Div {
573 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
574 &mut self.children
575 }
576}
577
578impl Element for Div {
579 type ElementState = DivState;
580
581 fn element_id(&self) -> Option<ElementId> {
582 self.interactivity.element_id.clone()
583 }
584
585 fn layout(
586 &mut self,
587 element_state: Option<Self::ElementState>,
588 cx: &mut WindowContext,
589 ) -> (LayoutId, Self::ElementState) {
590 let mut child_layout_ids = SmallVec::new();
591 let mut interactivity = mem::take(&mut self.interactivity);
592 let (layout_id, interactive_state) = interactivity.layout(
593 element_state.map(|s| s.interactive_state),
594 cx,
595 |style, cx| {
596 cx.with_text_style(style.text_style().cloned(), |cx| {
597 child_layout_ids = self
598 .children
599 .iter_mut()
600 .map(|child| child.layout(cx))
601 .collect::<SmallVec<_>>();
602 cx.request_layout(&style, child_layout_ids.iter().copied())
603 })
604 },
605 );
606 self.interactivity = interactivity;
607 (
608 layout_id,
609 DivState {
610 interactive_state,
611 child_layout_ids,
612 },
613 )
614 }
615
616 fn paint(
617 &mut self,
618 bounds: Bounds<Pixels>,
619 element_state: &mut Self::ElementState,
620 cx: &mut WindowContext,
621 ) {
622 let mut child_min = point(Pixels::MAX, Pixels::MAX);
623 let mut child_max = Point::default();
624 let content_size = if element_state.child_layout_ids.is_empty() {
625 bounds.size
626 } else {
627 for child_layout_id in &element_state.child_layout_ids {
628 let child_bounds = cx.layout_bounds(*child_layout_id);
629 child_min = child_min.min(&child_bounds.origin);
630 child_max = child_max.max(&child_bounds.lower_right());
631 }
632 (child_max - child_min).into()
633 };
634
635 let mut interactivity = mem::take(&mut self.interactivity);
636 interactivity.paint(
637 bounds,
638 content_size,
639 &mut element_state.interactive_state,
640 cx,
641 |style, scroll_offset, cx| {
642 if style.visibility == Visibility::Hidden {
643 return;
644 }
645
646 let z_index = style.z_index.unwrap_or(0);
647
648 cx.with_z_index(z_index, |cx| {
649 cx.with_z_index(0, |cx| {
650 style.paint(bounds, cx);
651 });
652 cx.with_z_index(1, |cx| {
653 cx.with_text_style(style.text_style().cloned(), |cx| {
654 cx.with_content_mask(style.overflow_mask(bounds), |cx| {
655 cx.with_element_offset(scroll_offset, |cx| {
656 for child in &mut self.children {
657 child.paint(cx);
658 }
659 })
660 })
661 })
662 })
663 })
664 },
665 );
666 self.interactivity = interactivity;
667 }
668}
669
670impl Component for Div {
671 fn render(self) -> AnyElement {
672 AnyElement::new(self)
673 }
674}
675
676pub struct DivState {
677 child_layout_ids: SmallVec<[LayoutId; 4]>,
678 interactive_state: InteractiveElementState,
679}
680
681impl DivState {
682 pub fn is_active(&self) -> bool {
683 self.interactive_state.pending_mouse_down.borrow().is_some()
684 }
685}
686
687pub struct Interactivity {
688 pub element_id: Option<ElementId>,
689 pub key_context: KeyContext,
690 pub focusable: bool,
691 pub tracked_focus_handle: Option<FocusHandle>,
692 pub focus_listeners: FocusListeners,
693 pub group: Option<SharedString>,
694 pub base_style: StyleRefinement,
695 pub focus_style: StyleRefinement,
696 pub in_focus_style: StyleRefinement,
697 pub hover_style: StyleRefinement,
698 pub group_hover_style: Option<GroupStyle>,
699 pub active_style: StyleRefinement,
700 pub group_active_style: Option<GroupStyle>,
701 pub drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
702 pub group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>,
703 pub mouse_down_listeners: SmallVec<[MouseDownListener; 2]>,
704 pub mouse_up_listeners: SmallVec<[MouseUpListener; 2]>,
705 pub mouse_move_listeners: SmallVec<[MouseMoveListener; 2]>,
706 pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener; 2]>,
707 pub key_down_listeners: SmallVec<[KeyDownListener; 2]>,
708 pub key_up_listeners: SmallVec<[KeyUpListener; 2]>,
709 pub action_listeners: SmallVec<[(TypeId, ActionListener); 8]>,
710 pub drop_listeners: SmallVec<[(TypeId, Box<DropListener>); 2]>,
711 pub click_listeners: SmallVec<[ClickListener; 2]>,
712 pub drag_listener: Option<DragListener>,
713 pub hover_listener: Option<CallbackHandle<bool>>,
714 pub tooltip_builder: Option<TooltipBuilder>,
715}
716
717impl Interactivity {
718 pub fn layout(
719 &mut self,
720 element_state: Option<InteractiveElementState>,
721 cx: &mut WindowContext,
722 f: impl FnOnce(Style, &mut WindowContext) -> LayoutId,
723 ) -> (LayoutId, InteractiveElementState) {
724 let mut element_state = element_state.unwrap_or_default();
725
726 // Ensure we store a focus handle in our element state if we're focusable.
727 // If there's an explicit focus handle we're tracking, use that. Otherwise
728 // create a new handle and store it in the element state, which lives for as
729 // as frames contain an element with this id.
730 if self.focusable {
731 element_state.focus_handle.get_or_insert_with(|| {
732 self.tracked_focus_handle
733 .clone()
734 .unwrap_or_else(|| cx.focus_handle())
735 });
736 }
737
738 let style = self.compute_style(None, &mut element_state, cx);
739 let layout_id = f(style, cx);
740 (layout_id, element_state)
741 }
742
743 pub fn paint(
744 &mut self,
745 bounds: Bounds<Pixels>,
746 content_size: Size<Pixels>,
747 element_state: &mut InteractiveElementState,
748 cx: &mut WindowContext,
749 f: impl FnOnce(Style, Point<Pixels>, &mut WindowContext),
750 ) {
751 let style = self.compute_style(Some(bounds), element_state, cx);
752
753 if let Some(mouse_cursor) = style.mouse_cursor {
754 let hovered = bounds.contains_point(&cx.mouse_position());
755 if hovered {
756 cx.set_cursor_style(mouse_cursor);
757 }
758 }
759
760 for listener in self.mouse_down_listeners.drain(..) {
761 cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
762 listener(event, &bounds, phase, cx);
763 })
764 }
765
766 for listener in self.mouse_up_listeners.drain(..) {
767 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
768 listener(event, &bounds, phase, cx);
769 })
770 }
771
772 for listener in self.mouse_move_listeners.drain(..) {
773 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
774 listener(event, &bounds, phase, cx);
775 })
776 }
777
778 for listener in self.scroll_wheel_listeners.drain(..) {
779 cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
780 listener(event, &bounds, phase, cx);
781 })
782 }
783
784 let hover_group_bounds = self
785 .group_hover_style
786 .as_ref()
787 .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
788
789 if let Some(group_bounds) = hover_group_bounds {
790 let hovered = group_bounds.contains_point(&cx.mouse_position());
791 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
792 if phase == DispatchPhase::Capture {
793 if group_bounds.contains_point(&event.position) != hovered {
794 cx.notify();
795 }
796 }
797 });
798 }
799
800 if self.hover_style.is_some()
801 || (cx.active_drag.is_some() && !self.drag_over_styles.is_empty())
802 {
803 let hovered = bounds.contains_point(&cx.mouse_position());
804 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
805 if phase == DispatchPhase::Capture {
806 if bounds.contains_point(&event.position) != hovered {
807 cx.notify();
808 }
809 }
810 });
811 }
812
813 if cx.active_drag.is_some() {
814 let drop_listeners = mem::take(&mut self.drop_listeners);
815 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
816 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
817 if let Some(drag_state_type) =
818 cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
819 {
820 for (drop_state_type, listener) in &drop_listeners {
821 if *drop_state_type == drag_state_type {
822 let drag = cx
823 .active_drag
824 .take()
825 .expect("checked for type drag state type above");
826 listener(drag.view.clone(), cx);
827 cx.notify();
828 cx.stop_propagation();
829 }
830 }
831 }
832 }
833 });
834 }
835
836 let click_listeners = mem::take(&mut self.click_listeners);
837 let drag_listener = mem::take(&mut self.drag_listener);
838
839 if !click_listeners.is_empty() || drag_listener.is_some() {
840 let pending_mouse_down = element_state.pending_mouse_down.clone();
841 let mouse_down = pending_mouse_down.borrow().clone();
842 if let Some(mouse_down) = mouse_down {
843 if let Some(drag_listener) = drag_listener {
844 let active_state = element_state.clicked_state.clone();
845
846 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
847 if cx.active_drag.is_some() {
848 if phase == DispatchPhase::Capture {
849 cx.notify();
850 }
851 } else if phase == DispatchPhase::Bubble
852 && bounds.contains_point(&event.position)
853 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
854 {
855 *active_state.borrow_mut() = ElementClickedState::default();
856 let cursor_offset = event.position - bounds.origin;
857 let drag = drag_listener(cursor_offset, cx);
858 cx.active_drag = Some(drag);
859 cx.notify();
860 cx.stop_propagation();
861 }
862 });
863 }
864
865 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
866 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
867 let mouse_click = ClickEvent {
868 down: mouse_down.clone(),
869 up: event.clone(),
870 };
871 for listener in &click_listeners {
872 listener(&mouse_click, cx);
873 }
874 }
875 *pending_mouse_down.borrow_mut() = None;
876 cx.notify();
877 });
878 } else {
879 cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
880 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
881 *pending_mouse_down.borrow_mut() = Some(event.clone());
882 cx.notify();
883 }
884 });
885 }
886 }
887
888 if let Some(hover_listener) = self.hover_listener.take() {
889 let was_hovered = element_state.hover_state.clone();
890 let has_mouse_down = element_state.pending_mouse_down.clone();
891
892 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
893 if phase != DispatchPhase::Bubble {
894 return;
895 }
896 let is_hovered =
897 bounds.contains_point(&event.position) && has_mouse_down.borrow().is_none();
898 let mut was_hovered = was_hovered.borrow_mut();
899
900 if is_hovered != was_hovered.clone() {
901 *was_hovered = is_hovered;
902 drop(was_hovered);
903
904 (hover_listener.callback)(&is_hovered, cx);
905 }
906 });
907 }
908
909 if let Some(tooltip_builder) = self.tooltip_builder.take() {
910 let active_tooltip = element_state.active_tooltip.clone();
911 let pending_mouse_down = element_state.pending_mouse_down.clone();
912
913 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
914 if phase != DispatchPhase::Bubble {
915 return;
916 }
917
918 let is_hovered =
919 bounds.contains_point(&event.position) && pending_mouse_down.borrow().is_none();
920 if !is_hovered {
921 active_tooltip.borrow_mut().take();
922 return;
923 }
924
925 if active_tooltip.borrow().is_none() {
926 let task = cx.spawn({
927 let active_tooltip = active_tooltip.clone();
928 let tooltip_builder = tooltip_builder.clone();
929
930 move |mut cx| async move {
931 cx.background_executor().timer(TOOLTIP_DELAY).await;
932 cx.update(|_, cx| {
933 active_tooltip.borrow_mut().replace(ActiveTooltip {
934 tooltip: Some(AnyTooltip {
935 view: tooltip_builder(cx),
936 cursor_offset: cx.mouse_position(),
937 }),
938 _task: None,
939 });
940 cx.notify();
941 })
942 .ok();
943 }
944 });
945 active_tooltip.borrow_mut().replace(ActiveTooltip {
946 tooltip: None,
947 _task: Some(task),
948 });
949 }
950 });
951
952 let active_tooltip = element_state.active_tooltip.clone();
953 cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
954 active_tooltip.borrow_mut().take();
955 });
956
957 if let Some(active_tooltip) = element_state.active_tooltip.borrow().as_ref() {
958 if active_tooltip.tooltip.is_some() {
959 cx.active_tooltip = active_tooltip.tooltip.clone()
960 }
961 }
962 }
963
964 let active_state = element_state.clicked_state.clone();
965 if !active_state.borrow().is_clicked() {
966 cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| {
967 if phase == DispatchPhase::Capture {
968 *active_state.borrow_mut() = ElementClickedState::default();
969 cx.notify();
970 }
971 });
972 } else {
973 let active_group_bounds = self
974 .group_active_style
975 .as_ref()
976 .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
977 cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| {
978 if phase == DispatchPhase::Bubble {
979 let group = active_group_bounds
980 .map_or(false, |bounds| bounds.contains_point(&down.position));
981 let element = bounds.contains_point(&down.position);
982 if group || element {
983 *active_state.borrow_mut() = ElementClickedState { group, element };
984 cx.notify();
985 }
986 }
987 });
988 }
989
990 let overflow = style.overflow;
991 if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll {
992 let scroll_offset = element_state
993 .scroll_offset
994 .get_or_insert_with(Rc::default)
995 .clone();
996 let line_height = cx.line_height();
997 let scroll_max = (content_size - bounds.size).max(&Size::default());
998
999 cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
1000 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
1001 let mut scroll_offset = scroll_offset.borrow_mut();
1002 let old_scroll_offset = *scroll_offset;
1003 let delta = event.delta.pixel_delta(line_height);
1004
1005 if overflow.x == Overflow::Scroll {
1006 scroll_offset.x =
1007 (scroll_offset.x + delta.x).clamp(-scroll_max.width, px(0.));
1008 }
1009
1010 if overflow.y == Overflow::Scroll {
1011 scroll_offset.y =
1012 (scroll_offset.y + delta.y).clamp(-scroll_max.height, px(0.));
1013 }
1014
1015 if *scroll_offset != old_scroll_offset {
1016 cx.notify();
1017 cx.stop_propagation();
1018 }
1019 }
1020 });
1021 }
1022
1023 if let Some(group) = self.group.clone() {
1024 GroupBounds::push(group, bounds, cx);
1025 }
1026
1027 let scroll_offset = element_state
1028 .scroll_offset
1029 .as_ref()
1030 .map(|scroll_offset| *scroll_offset.borrow());
1031
1032 cx.with_key_dispatch(
1033 self.key_context.clone(),
1034 element_state.focus_handle.clone(),
1035 |_, cx| {
1036 for listener in self.key_down_listeners.drain(..) {
1037 cx.on_key_event(move |event: &KeyDownEvent, phase, cx| {
1038 listener(event, phase, cx);
1039 })
1040 }
1041
1042 for listener in self.key_up_listeners.drain(..) {
1043 cx.on_key_event(move |event: &KeyUpEvent, phase, cx| {
1044 listener(event, phase, cx);
1045 })
1046 }
1047
1048 for (action_type, listener) in self.action_listeners.drain(..) {
1049 cx.on_action(action_type, listener)
1050 }
1051
1052 if let Some(focus_handle) = element_state.focus_handle.as_ref() {
1053 for listener in self.focus_listeners.drain(..) {
1054 let focus_handle = focus_handle.clone();
1055 cx.on_focus_changed(move |event, cx| listener(&focus_handle, event, cx));
1056 }
1057 }
1058
1059 f(style, scroll_offset.unwrap_or_default(), cx)
1060 },
1061 );
1062
1063 if let Some(group) = self.group.as_ref() {
1064 GroupBounds::pop(group, cx);
1065 }
1066 }
1067
1068 pub fn compute_style(
1069 &self,
1070 bounds: Option<Bounds<Pixels>>,
1071 element_state: &mut InteractiveElementState,
1072 cx: &mut WindowContext,
1073 ) -> Style {
1074 let mut style = Style::default();
1075 style.refine(&self.base_style);
1076
1077 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
1078 if focus_handle.within_focused(cx) {
1079 style.refine(&self.in_focus_style);
1080 }
1081
1082 if focus_handle.is_focused(cx) {
1083 style.refine(&self.focus_style);
1084 }
1085 }
1086
1087 if let Some(bounds) = bounds {
1088 let mouse_position = cx.mouse_position();
1089 if let Some(group_hover) = self.group_hover_style.as_ref() {
1090 if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) {
1091 if group_bounds.contains_point(&mouse_position) {
1092 style.refine(&group_hover.style);
1093 }
1094 }
1095 }
1096 // if self.hover_style.is_some() {
1097 if bounds.contains_point(&mouse_position) {
1098 // eprintln!("div hovered {bounds:?} {mouse_position:?}");
1099 style.refine(&self.hover_style);
1100 } else {
1101 // eprintln!("div NOT hovered {bounds:?} {mouse_position:?}");
1102 }
1103 // }
1104
1105 if let Some(drag) = cx.active_drag.take() {
1106 for (state_type, group_drag_style) in &self.group_drag_over_styles {
1107 if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
1108 if *state_type == drag.view.entity_type()
1109 && group_bounds.contains_point(&mouse_position)
1110 {
1111 style.refine(&group_drag_style.style);
1112 }
1113 }
1114 }
1115
1116 for (state_type, drag_over_style) in &self.drag_over_styles {
1117 if *state_type == drag.view.entity_type()
1118 && bounds.contains_point(&mouse_position)
1119 {
1120 style.refine(drag_over_style);
1121 }
1122 }
1123
1124 cx.active_drag = Some(drag);
1125 }
1126 }
1127
1128 let clicked_state = element_state.clicked_state.borrow();
1129 if clicked_state.group {
1130 if let Some(group) = self.group_active_style.as_ref() {
1131 style.refine(&group.style)
1132 }
1133 }
1134
1135 if clicked_state.element {
1136 style.refine(&self.active_style)
1137 }
1138
1139 style
1140 }
1141}
1142
1143impl Default for Interactivity {
1144 fn default() -> Self {
1145 Self {
1146 element_id: None,
1147 key_context: KeyContext::default(),
1148 focusable: false,
1149 tracked_focus_handle: None,
1150 focus_listeners: SmallVec::default(),
1151 // scroll_offset: Point::default(),
1152 group: None,
1153 base_style: StyleRefinement::default(),
1154 focus_style: StyleRefinement::default(),
1155 in_focus_style: StyleRefinement::default(),
1156 hover_style: StyleRefinement::default(),
1157 group_hover_style: None,
1158 active_style: StyleRefinement::default(),
1159 group_active_style: None,
1160 drag_over_styles: SmallVec::new(),
1161 group_drag_over_styles: SmallVec::new(),
1162 mouse_down_listeners: SmallVec::new(),
1163 mouse_up_listeners: SmallVec::new(),
1164 mouse_move_listeners: SmallVec::new(),
1165 scroll_wheel_listeners: SmallVec::new(),
1166 key_down_listeners: SmallVec::new(),
1167 key_up_listeners: SmallVec::new(),
1168 action_listeners: SmallVec::new(),
1169 drop_listeners: SmallVec::new(),
1170 click_listeners: SmallVec::new(),
1171 drag_listener: None,
1172 hover_listener: None,
1173 tooltip_builder: None,
1174 }
1175 }
1176}
1177
1178#[derive(Default)]
1179pub struct InteractiveElementState {
1180 pub focus_handle: Option<FocusHandle>,
1181 pub clicked_state: Rc<RefCell<ElementClickedState>>,
1182 pub hover_state: Rc<RefCell<bool>>,
1183 pub pending_mouse_down: Rc<RefCell<Option<MouseDownEvent>>>,
1184 pub scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
1185 pub active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
1186}
1187
1188pub struct ActiveTooltip {
1189 tooltip: Option<AnyTooltip>,
1190 _task: Option<Task<()>>,
1191}
1192
1193/// Whether or not the element or a group that contains it is clicked by the mouse.
1194#[derive(Copy, Clone, Default, Eq, PartialEq)]
1195pub struct ElementClickedState {
1196 pub group: bool,
1197 pub element: bool,
1198}
1199
1200impl ElementClickedState {
1201 fn is_clicked(&self) -> bool {
1202 self.group || self.element
1203 }
1204}
1205
1206#[derive(Default)]
1207pub struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
1208
1209impl GroupBounds {
1210 pub fn get(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
1211 cx.default_global::<Self>()
1212 .0
1213 .get(name)
1214 .and_then(|bounds_stack| bounds_stack.last())
1215 .cloned()
1216 }
1217
1218 pub fn push(name: SharedString, bounds: Bounds<Pixels>, cx: &mut AppContext) {
1219 cx.default_global::<Self>()
1220 .0
1221 .entry(name)
1222 .or_default()
1223 .push(bounds);
1224 }
1225
1226 pub fn pop(name: &SharedString, cx: &mut AppContext) {
1227 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
1228 }
1229}
1230
1231pub struct Focusable<E> {
1232 element: E,
1233}
1234
1235impl<E: InteractiveComponent> FocusableComponent for Focusable<E> {}
1236
1237impl<E> InteractiveComponent for Focusable<E>
1238where
1239 E: InteractiveComponent,
1240{
1241 fn interactivity(&mut self) -> &mut Interactivity {
1242 self.element.interactivity()
1243 }
1244}
1245
1246impl<E: StatefulInteractiveComponent> StatefulInteractiveComponent for Focusable<E> {}
1247
1248impl<E> Styled for Focusable<E>
1249where
1250 E: Styled,
1251{
1252 fn style(&mut self) -> &mut StyleRefinement {
1253 self.element.style()
1254 }
1255}
1256
1257impl<E> Element for Focusable<E>
1258where
1259 E: Element,
1260{
1261 type ElementState = E::ElementState;
1262
1263 fn element_id(&self) -> Option<ElementId> {
1264 self.element.element_id()
1265 }
1266
1267 fn layout(
1268 &mut self,
1269 element_state: Option<Self::ElementState>,
1270 cx: &mut WindowContext,
1271 ) -> (LayoutId, Self::ElementState) {
1272 self.element.layout(element_state, cx)
1273 }
1274
1275 fn paint(
1276 &mut self,
1277 bounds: Bounds<Pixels>,
1278 element_state: &mut Self::ElementState,
1279 cx: &mut WindowContext,
1280 ) {
1281 self.element.paint(bounds, element_state, cx);
1282 }
1283}
1284
1285impl<E> Component for Focusable<E>
1286where
1287 E: 'static + Element,
1288{
1289 fn render(self) -> AnyElement {
1290 AnyElement::new(self)
1291 }
1292}
1293
1294impl<E> ParentComponent for Focusable<E>
1295where
1296 E: ParentComponent,
1297{
1298 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
1299 self.element.children_mut()
1300 }
1301}
1302
1303pub struct Stateful<E> {
1304 element: E,
1305}
1306
1307impl<E> Styled for Stateful<E>
1308where
1309 E: Styled,
1310{
1311 fn style(&mut self) -> &mut StyleRefinement {
1312 self.element.style()
1313 }
1314}
1315
1316impl<E> StatefulInteractiveComponent for Stateful<E>
1317where
1318 E: Element,
1319 Self: InteractiveComponent,
1320{
1321}
1322
1323impl<E> InteractiveComponent for Stateful<E>
1324where
1325 E: InteractiveComponent,
1326{
1327 fn interactivity(&mut self) -> &mut Interactivity {
1328 self.element.interactivity()
1329 }
1330}
1331
1332impl<E: FocusableComponent> FocusableComponent for Stateful<E> {}
1333
1334impl<E> Element for Stateful<E>
1335where
1336 E: Element,
1337{
1338 type ElementState = E::ElementState;
1339
1340 fn element_id(&self) -> Option<ElementId> {
1341 self.element.element_id()
1342 }
1343
1344 fn layout(
1345 &mut self,
1346 element_state: Option<Self::ElementState>,
1347 cx: &mut WindowContext,
1348 ) -> (LayoutId, Self::ElementState) {
1349 self.element.layout(element_state, cx)
1350 }
1351
1352 fn paint(
1353 &mut self,
1354 bounds: Bounds<Pixels>,
1355 element_state: &mut Self::ElementState,
1356 cx: &mut WindowContext,
1357 ) {
1358 self.element.paint(bounds, element_state, cx)
1359 }
1360}
1361
1362impl<E> Component for Stateful<E>
1363where
1364 E: 'static + Element,
1365{
1366 fn render(self) -> AnyElement {
1367 AnyElement::new(self)
1368 }
1369}
1370
1371impl<E> ParentComponent for Stateful<E>
1372where
1373 E: ParentComponent,
1374{
1375 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
1376 self.element.children_mut()
1377 }
1378}