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