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};
9
10use collections::HashMap;
11use refineable::Refineable;
12use smallvec::SmallVec;
13use std::{
14 any::{Any, TypeId},
15 cell::RefCell,
16 cmp::Ordering,
17 fmt::Debug,
18 marker::PhantomData,
19 mem,
20 rc::Rc,
21 time::Duration,
22};
23use taffy::style::Overflow;
24use util::ResultExt;
25
26const DRAG_THRESHOLD: f64 = 2.;
27const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
28
29pub struct GroupStyle {
30 pub group: SharedString,
31 pub style: Box<StyleRefinement>,
32}
33
34pub struct DragMoveEvent<T> {
35 pub event: MouseMoveEvent,
36 pub bounds: Bounds<Pixels>,
37 drag: PhantomData<T>,
38}
39
40impl<T: 'static> DragMoveEvent<T> {
41 pub fn drag<'b>(&self, cx: &'b AppContext) -> &'b T {
42 cx.active_drag
43 .as_ref()
44 .and_then(|drag| drag.value.downcast_ref::<T>())
45 .expect("DragMoveEvent is only valid when the stored active drag is of the same type.")
46 }
47}
48
49pub trait InteractiveElement: Sized {
50 fn interactivity(&mut self) -> &mut Interactivity;
51
52 fn group(mut self, group: impl Into<SharedString>) -> Self {
53 self.interactivity().group = Some(group.into());
54 self
55 }
56
57 fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
58 self.interactivity().element_id = Some(id.into());
59
60 Stateful { element: self }
61 }
62
63 fn track_focus(mut self, focus_handle: &FocusHandle) -> Focusable<Self> {
64 self.interactivity().focusable = true;
65 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
66 Focusable { element: self }
67 }
68
69 fn key_context<C, E>(mut self, key_context: C) -> Self
70 where
71 C: TryInto<KeyContext, Error = E>,
72 E: Debug,
73 {
74 if let Some(key_context) = key_context.try_into().log_err() {
75 self.interactivity().key_context = Some(key_context);
76 }
77 self
78 }
79
80 fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
81 debug_assert!(
82 self.interactivity().hover_style.is_none(),
83 "hover style already set"
84 );
85 self.interactivity().hover_style = Some(Box::new(f(StyleRefinement::default())));
86 self
87 }
88
89 fn group_hover(
90 mut self,
91 group_name: impl Into<SharedString>,
92 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
93 ) -> Self {
94 self.interactivity().group_hover_style = Some(GroupStyle {
95 group: group_name.into(),
96 style: Box::new(f(StyleRefinement::default())),
97 });
98 self
99 }
100
101 fn on_mouse_down(
102 mut self,
103 button: MouseButton,
104 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
105 ) -> Self {
106 self.interactivity().mouse_down_listeners.push(Box::new(
107 move |event, bounds, phase, cx| {
108 if phase == DispatchPhase::Bubble
109 && event.button == button
110 && bounds.visibly_contains(&event.position, cx)
111 {
112 (listener)(event, cx)
113 }
114 },
115 ));
116 self
117 }
118
119 fn on_any_mouse_down(
120 mut self,
121 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
122 ) -> Self {
123 self.interactivity().mouse_down_listeners.push(Box::new(
124 move |event, bounds, phase, cx| {
125 if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
126 (listener)(event, cx)
127 }
128 },
129 ));
130 self
131 }
132
133 fn on_mouse_up(
134 mut self,
135 button: MouseButton,
136 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
137 ) -> Self {
138 self.interactivity()
139 .mouse_up_listeners
140 .push(Box::new(move |event, bounds, phase, cx| {
141 if phase == DispatchPhase::Bubble
142 && event.button == button
143 && bounds.visibly_contains(&event.position, cx)
144 {
145 (listener)(event, cx)
146 }
147 }));
148 self
149 }
150
151 fn on_any_mouse_up(
152 mut self,
153 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
154 ) -> Self {
155 self.interactivity()
156 .mouse_up_listeners
157 .push(Box::new(move |event, bounds, phase, cx| {
158 if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
159 (listener)(event, cx)
160 }
161 }));
162 self
163 }
164
165 fn on_mouse_down_out(
166 mut self,
167 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
168 ) -> Self {
169 self.interactivity().mouse_down_listeners.push(Box::new(
170 move |event, bounds, phase, cx| {
171 if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx)
172 {
173 (listener)(event, cx)
174 }
175 },
176 ));
177 self
178 }
179
180 fn on_mouse_up_out(
181 mut self,
182 button: MouseButton,
183 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
184 ) -> Self {
185 self.interactivity()
186 .mouse_up_listeners
187 .push(Box::new(move |event, bounds, phase, cx| {
188 if phase == DispatchPhase::Capture
189 && event.button == button
190 && !bounds.visibly_contains(&event.position, cx)
191 {
192 (listener)(event, cx);
193 }
194 }));
195 self
196 }
197
198 fn on_mouse_move(
199 mut self,
200 listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static,
201 ) -> Self {
202 self.interactivity().mouse_move_listeners.push(Box::new(
203 move |event, bounds, phase, cx| {
204 if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
205 (listener)(event, cx);
206 }
207 },
208 ));
209 self
210 }
211
212 fn on_drag_move<T: 'static>(
213 mut self,
214 listener: impl Fn(&DragMoveEvent<T>, &mut WindowContext) + 'static,
215 ) -> Self
216 where
217 T: 'static,
218 {
219 self.interactivity().mouse_move_listeners.push(Box::new(
220 move |event, bounds, phase, cx| {
221 if phase == DispatchPhase::Capture
222 && bounds.drag_target_contains(&event.position, cx)
223 {
224 if cx
225 .active_drag
226 .as_ref()
227 .is_some_and(|drag| drag.value.as_ref().type_id() == TypeId::of::<T>())
228 {
229 (listener)(
230 &DragMoveEvent {
231 event: event.clone(),
232 bounds: bounds.bounds,
233 drag: PhantomData,
234 },
235 cx,
236 );
237 }
238 }
239 },
240 ));
241 self
242 }
243
244 fn on_scroll_wheel(
245 mut self,
246 listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static,
247 ) -> Self {
248 self.interactivity().scroll_wheel_listeners.push(Box::new(
249 move |event, bounds, phase, cx| {
250 if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
251 (listener)(event, cx);
252 }
253 },
254 ));
255 self
256 }
257
258 /// Capture the given action, before normal action dispatch can fire
259 fn capture_action<A: Action>(
260 mut self,
261 listener: impl Fn(&A, &mut WindowContext) + 'static,
262 ) -> Self {
263 self.interactivity().action_listeners.push((
264 TypeId::of::<A>(),
265 Box::new(move |action, phase, cx| {
266 let action = action.downcast_ref().unwrap();
267 if phase == DispatchPhase::Capture {
268 (listener)(action, cx)
269 }
270 }),
271 ));
272 self
273 }
274
275 /// Add a listener for the given action, fires during the bubble event phase
276 fn on_action<A: Action>(mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) -> Self {
277 self.interactivity().action_listeners.push((
278 TypeId::of::<A>(),
279 Box::new(move |action, phase, cx| {
280 let action = action.downcast_ref().unwrap();
281 if phase == DispatchPhase::Bubble {
282 (listener)(action, cx)
283 }
284 }),
285 ));
286 self
287 }
288
289 fn on_boxed_action(
290 mut self,
291 action: &Box<dyn Action>,
292 listener: impl Fn(&Box<dyn Action>, &mut WindowContext) + 'static,
293 ) -> Self {
294 let action = action.boxed_clone();
295 self.interactivity().action_listeners.push((
296 (*action).type_id(),
297 Box::new(move |_, phase, cx| {
298 if phase == DispatchPhase::Bubble {
299 (listener)(&action, cx)
300 }
301 }),
302 ));
303 self
304 }
305
306 fn on_key_down(
307 mut self,
308 listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
309 ) -> Self {
310 self.interactivity()
311 .key_down_listeners
312 .push(Box::new(move |event, phase, cx| {
313 if phase == DispatchPhase::Bubble {
314 (listener)(event, cx)
315 }
316 }));
317 self
318 }
319
320 fn capture_key_down(
321 mut self,
322 listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
323 ) -> Self {
324 self.interactivity()
325 .key_down_listeners
326 .push(Box::new(move |event, phase, cx| {
327 if phase == DispatchPhase::Capture {
328 listener(event, cx)
329 }
330 }));
331 self
332 }
333
334 fn on_key_up(mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) -> Self {
335 self.interactivity()
336 .key_up_listeners
337 .push(Box::new(move |event, phase, cx| {
338 if phase == DispatchPhase::Bubble {
339 listener(event, cx)
340 }
341 }));
342 self
343 }
344
345 fn capture_key_up(
346 mut self,
347 listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static,
348 ) -> Self {
349 self.interactivity()
350 .key_up_listeners
351 .push(Box::new(move |event, phase, cx| {
352 if phase == DispatchPhase::Capture {
353 listener(event, cx)
354 }
355 }));
356 self
357 }
358
359 fn drag_over<S: 'static>(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
360 self.interactivity()
361 .drag_over_styles
362 .push((TypeId::of::<S>(), f(StyleRefinement::default())));
363 self
364 }
365
366 fn group_drag_over<S: 'static>(
367 mut self,
368 group_name: impl Into<SharedString>,
369 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
370 ) -> Self {
371 self.interactivity().group_drag_over_styles.push((
372 TypeId::of::<S>(),
373 GroupStyle {
374 group: group_name.into(),
375 style: Box::new(f(StyleRefinement::default())),
376 },
377 ));
378 self
379 }
380
381 fn on_drop<T: 'static>(mut self, listener: impl Fn(&T, &mut WindowContext) + 'static) -> Self {
382 self.interactivity().drop_listeners.push((
383 TypeId::of::<T>(),
384 Box::new(move |dragged_value, cx| {
385 listener(dragged_value.downcast_ref().unwrap(), cx);
386 }),
387 ));
388 self
389 }
390}
391
392pub trait StatefulInteractiveElement: InteractiveElement {
393 fn focusable(mut self) -> Focusable<Self> {
394 self.interactivity().focusable = true;
395 Focusable { element: self }
396 }
397
398 fn overflow_scroll(mut self) -> Self {
399 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
400 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
401 self
402 }
403
404 fn overflow_x_scroll(mut self) -> Self {
405 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
406 self
407 }
408
409 fn overflow_y_scroll(mut self) -> Self {
410 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
411 self
412 }
413
414 fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
415 self.interactivity().scroll_handle = Some(scroll_handle.clone());
416 self
417 }
418
419 fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
420 where
421 Self: Sized,
422 {
423 self.interactivity().active_style = Some(Box::new(f(StyleRefinement::default())));
424 self
425 }
426
427 fn group_active(
428 mut self,
429 group_name: impl Into<SharedString>,
430 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
431 ) -> Self
432 where
433 Self: Sized,
434 {
435 self.interactivity().group_active_style = Some(GroupStyle {
436 group: group_name.into(),
437 style: Box::new(f(StyleRefinement::default())),
438 });
439 self
440 }
441
442 fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self
443 where
444 Self: Sized,
445 {
446 self.interactivity()
447 .click_listeners
448 .push(Box::new(move |event, cx| listener(event, cx)));
449 self
450 }
451
452 fn on_drag<T, W>(
453 mut self,
454 value: T,
455 constructor: impl Fn(&T, &mut WindowContext) -> View<W> + 'static,
456 ) -> Self
457 where
458 Self: Sized,
459 T: 'static,
460 W: 'static + Render,
461 {
462 debug_assert!(
463 self.interactivity().drag_listener.is_none(),
464 "calling on_drag more than once on the same element is not supported"
465 );
466 self.interactivity().drag_listener = Some((
467 Box::new(value),
468 Box::new(move |value, cx| constructor(value.downcast_ref().unwrap(), cx).into()),
469 ));
470 self
471 }
472
473 fn on_hover(mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static) -> Self
474 where
475 Self: Sized,
476 {
477 debug_assert!(
478 self.interactivity().hover_listener.is_none(),
479 "calling on_hover more than once on the same element is not supported"
480 );
481 self.interactivity().hover_listener = Some(Box::new(listener));
482 self
483 }
484
485 fn tooltip(mut self, build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self
486 where
487 Self: Sized,
488 {
489 debug_assert!(
490 self.interactivity().tooltip_builder.is_none(),
491 "calling tooltip more than once on the same element is not supported"
492 );
493 self.interactivity().tooltip_builder = Some(Rc::new(build_tooltip));
494 self
495 }
496}
497
498pub trait FocusableElement: InteractiveElement {
499 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
500 where
501 Self: Sized,
502 {
503 self.interactivity().focus_style = Some(Box::new(f(StyleRefinement::default())));
504 self
505 }
506
507 fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
508 where
509 Self: Sized,
510 {
511 self.interactivity().in_focus_style = Some(Box::new(f(StyleRefinement::default())));
512 self
513 }
514}
515
516pub type FocusListeners = Vec<FocusListener>;
517
518pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>;
519
520pub type MouseDownListener =
521 Box<dyn Fn(&MouseDownEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
522pub type MouseUpListener =
523 Box<dyn Fn(&MouseUpEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
524
525pub type MouseMoveListener =
526 Box<dyn Fn(&MouseMoveEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
527
528pub type ScrollWheelListener =
529 Box<dyn Fn(&ScrollWheelEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
530
531pub type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
532
533pub type DragListener = Box<dyn Fn(&dyn Any, &mut WindowContext) -> AnyView + 'static>;
534
535type DropListener = Box<dyn Fn(&dyn Any, &mut WindowContext) + 'static>;
536
537pub type TooltipBuilder = Rc<dyn Fn(&mut WindowContext) -> AnyView + 'static>;
538
539pub type KeyDownListener = Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + 'static>;
540
541pub type KeyUpListener = Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut WindowContext) + 'static>;
542
543pub type DragEventListener = Box<dyn Fn(&MouseMoveEvent, &mut WindowContext) + 'static>;
544
545pub type ActionListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
546
547#[track_caller]
548pub fn div() -> Div {
549 #[cfg(debug_assertions)]
550 let interactivity = {
551 let mut interactivity = Interactivity::default();
552 interactivity.location = Some(*core::panic::Location::caller());
553 interactivity
554 };
555
556 #[cfg(not(debug_assertions))]
557 let interactivity = Interactivity::default();
558
559 Div {
560 interactivity,
561 children: SmallVec::default(),
562 }
563}
564
565pub struct Div {
566 interactivity: Interactivity,
567 children: SmallVec<[AnyElement; 2]>,
568}
569
570impl Styled for Div {
571 fn style(&mut self) -> &mut StyleRefinement {
572 &mut self.interactivity.base_style
573 }
574}
575
576impl InteractiveElement for Div {
577 fn interactivity(&mut self) -> &mut Interactivity {
578 &mut self.interactivity
579 }
580}
581
582impl ParentElement for Div {
583 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
584 &mut self.children
585 }
586}
587
588impl Element for Div {
589 type State = DivState;
590
591 fn layout(
592 &mut self,
593 element_state: Option<Self::State>,
594 cx: &mut WindowContext,
595 ) -> (LayoutId, Self::State) {
596 let mut child_layout_ids = SmallVec::new();
597 let (layout_id, interactive_state) = self.interactivity.layout(
598 element_state.map(|s| s.interactive_state),
599 cx,
600 |style, cx| {
601 cx.with_text_style(style.text_style().cloned(), |cx| {
602 child_layout_ids = self
603 .children
604 .iter_mut()
605 .map(|child| child.layout(cx))
606 .collect::<SmallVec<_>>();
607 cx.request_layout(&style, child_layout_ids.iter().copied())
608 })
609 },
610 );
611 (
612 layout_id,
613 DivState {
614 interactive_state,
615 child_layout_ids,
616 },
617 )
618 }
619
620 fn paint(
621 self,
622 bounds: Bounds<Pixels>,
623 element_state: &mut Self::State,
624 cx: &mut WindowContext,
625 ) {
626 let mut child_min = point(Pixels::MAX, Pixels::MAX);
627 let mut child_max = Point::default();
628 let content_size = if element_state.child_layout_ids.is_empty() {
629 bounds.size
630 } else if let Some(scroll_handle) = self.interactivity.scroll_handle.as_ref() {
631 let mut state = scroll_handle.0.borrow_mut();
632 state.child_bounds = Vec::with_capacity(element_state.child_layout_ids.len());
633 state.bounds = bounds;
634 let requested = state.requested_scroll_top.take();
635
636 for (ix, child_layout_id) in element_state.child_layout_ids.iter().enumerate() {
637 let child_bounds = cx.layout_bounds(*child_layout_id);
638 child_min = child_min.min(&child_bounds.origin);
639 child_max = child_max.max(&child_bounds.lower_right());
640 state.child_bounds.push(child_bounds);
641
642 if let Some(requested) = requested.as_ref() {
643 if requested.0 == ix {
644 *state.offset.borrow_mut() =
645 bounds.origin - (child_bounds.origin - point(px(0.), requested.1));
646 }
647 }
648 }
649 (child_max - child_min).into()
650 } else {
651 for child_layout_id in &element_state.child_layout_ids {
652 let child_bounds = cx.layout_bounds(*child_layout_id);
653 child_min = child_min.min(&child_bounds.origin);
654 child_max = child_max.max(&child_bounds.lower_right());
655 }
656 (child_max - child_min).into()
657 };
658
659 self.interactivity.paint(
660 bounds,
661 content_size,
662 &mut element_state.interactive_state,
663 cx,
664 |style, scroll_offset, cx| {
665 let z_index = style.z_index.unwrap_or(0);
666
667 cx.with_z_index(z_index, |cx| {
668 style.paint(bounds, cx, |cx| {
669 cx.with_text_style(style.text_style().cloned(), |cx| {
670 cx.with_content_mask(style.overflow_mask(bounds), |cx| {
671 cx.with_element_offset(scroll_offset, |cx| {
672 for child in self.children {
673 child.paint(cx);
674 }
675 })
676 })
677 })
678 });
679 })
680 },
681 );
682 }
683}
684
685impl IntoElement for Div {
686 type Element = Self;
687
688 fn element_id(&self) -> Option<ElementId> {
689 self.interactivity.element_id.clone()
690 }
691
692 fn into_element(self) -> Self::Element {
693 self
694 }
695}
696
697pub struct DivState {
698 child_layout_ids: SmallVec<[LayoutId; 2]>,
699 interactive_state: InteractiveElementState,
700}
701
702impl DivState {
703 pub fn is_active(&self) -> bool {
704 self.interactive_state.pending_mouse_down.borrow().is_some()
705 }
706}
707
708pub struct Interactivity {
709 pub element_id: Option<ElementId>,
710 pub key_context: Option<KeyContext>,
711 pub focusable: bool,
712 pub tracked_focus_handle: Option<FocusHandle>,
713 pub scroll_handle: Option<ScrollHandle>,
714 pub group: Option<SharedString>,
715 pub base_style: Box<StyleRefinement>,
716 pub focus_style: Option<Box<StyleRefinement>>,
717 pub in_focus_style: Option<Box<StyleRefinement>>,
718 pub hover_style: Option<Box<StyleRefinement>>,
719 pub group_hover_style: Option<GroupStyle>,
720 pub active_style: Option<Box<StyleRefinement>>,
721 pub group_active_style: Option<GroupStyle>,
722 pub drag_over_styles: Vec<(TypeId, StyleRefinement)>,
723 pub group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
724 pub mouse_down_listeners: Vec<MouseDownListener>,
725 pub mouse_up_listeners: Vec<MouseUpListener>,
726 pub mouse_move_listeners: Vec<MouseMoveListener>,
727 pub scroll_wheel_listeners: Vec<ScrollWheelListener>,
728 pub key_down_listeners: Vec<KeyDownListener>,
729 pub key_up_listeners: Vec<KeyUpListener>,
730 pub action_listeners: Vec<(TypeId, ActionListener)>,
731 pub drop_listeners: Vec<(TypeId, DropListener)>,
732 pub click_listeners: Vec<ClickListener>,
733 pub drag_listener: Option<(Box<dyn Any>, DragListener)>,
734 pub hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>,
735 pub tooltip_builder: Option<TooltipBuilder>,
736
737 #[cfg(debug_assertions)]
738 pub location: Option<core::panic::Location<'static>>,
739}
740
741#[derive(Clone, Debug)]
742pub struct InteractiveBounds {
743 pub bounds: Bounds<Pixels>,
744 pub stacking_order: StackingOrder,
745}
746
747impl InteractiveBounds {
748 pub fn visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
749 self.bounds.contains(point) && cx.was_top_layer(&point, &self.stacking_order)
750 }
751
752 pub fn drag_target_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
753 self.bounds.contains(point)
754 && cx.was_top_layer_under_active_drag(&point, &self.stacking_order)
755 }
756}
757
758impl Interactivity {
759 pub fn layout(
760 &mut self,
761 element_state: Option<InteractiveElementState>,
762 cx: &mut WindowContext,
763 f: impl FnOnce(Style, &mut WindowContext) -> LayoutId,
764 ) -> (LayoutId, InteractiveElementState) {
765 let mut element_state = element_state.unwrap_or_default();
766
767 // Ensure we store a focus handle in our element state if we're focusable.
768 // If there's an explicit focus handle we're tracking, use that. Otherwise
769 // create a new handle and store it in the element state, which lives for as
770 // as frames contain an element with this id.
771 if self.focusable {
772 element_state.focus_handle.get_or_insert_with(|| {
773 self.tracked_focus_handle
774 .clone()
775 .unwrap_or_else(|| cx.focus_handle())
776 });
777 }
778
779 if let Some(scroll_handle) = self.scroll_handle.as_ref() {
780 element_state.scroll_offset = Some(scroll_handle.0.borrow().offset.clone());
781 }
782
783 let style = self.compute_style(None, &mut element_state, cx);
784 let layout_id = f(style, cx);
785 (layout_id, element_state)
786 }
787
788 pub fn paint(
789 mut self,
790 bounds: Bounds<Pixels>,
791 content_size: Size<Pixels>,
792 element_state: &mut InteractiveElementState,
793 cx: &mut WindowContext,
794 f: impl FnOnce(Style, Point<Pixels>, &mut WindowContext),
795 ) {
796 let style = self.compute_style(Some(bounds), element_state, cx);
797
798 if style.visibility == Visibility::Hidden {
799 return;
800 }
801
802 #[cfg(debug_assertions)]
803 if self.element_id.is_some()
804 && (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
805 && bounds.contains(&cx.mouse_position())
806 {
807 const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
808 let element_id = format!("{:?}", self.element_id.unwrap());
809 let str_len = element_id.len();
810
811 let render_debug_text = |cx: &mut WindowContext| {
812 if let Some(text) = cx
813 .text_system()
814 .shape_text(
815 &element_id,
816 FONT_SIZE,
817 &[cx.text_style().to_run(str_len)],
818 None,
819 )
820 .ok()
821 .map(|mut text| text.pop())
822 .flatten()
823 {
824 text.paint(bounds.origin, FONT_SIZE, cx).ok();
825
826 let text_bounds = crate::Bounds {
827 origin: bounds.origin,
828 size: text.size(FONT_SIZE),
829 };
830 if self.location.is_some()
831 && text_bounds.contains(&cx.mouse_position())
832 && cx.modifiers().command
833 {
834 let command_held = cx.modifiers().command;
835 cx.on_key_event({
836 let text_bounds = text_bounds.clone();
837 move |e: &crate::ModifiersChangedEvent, _phase, cx| {
838 if e.modifiers.command != command_held
839 && text_bounds.contains(&cx.mouse_position())
840 {
841 cx.notify();
842 }
843 }
844 });
845
846 let hovered = bounds.contains(&cx.mouse_position());
847 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
848 if phase == DispatchPhase::Capture {
849 if bounds.contains(&event.position) != hovered {
850 cx.notify();
851 }
852 }
853 });
854
855 cx.on_mouse_event({
856 let location = self.location.clone().unwrap();
857 let text_bounds = text_bounds.clone();
858 move |e: &crate::MouseDownEvent, phase, cx| {
859 if text_bounds.contains(&e.position) && phase.capture() {
860 cx.stop_propagation();
861 let Ok(dir) = std::env::current_dir() else {
862 return;
863 };
864
865 eprintln!(
866 "This element is created at:\n{}:{}:{}",
867 location.file(),
868 location.line(),
869 location.column()
870 );
871
872 std::process::Command::new("zed")
873 .arg(format!(
874 "{}/{}:{}:{}",
875 dir.to_string_lossy(),
876 location.file(),
877 location.line(),
878 location.column()
879 ))
880 .spawn()
881 .ok();
882 }
883 }
884 });
885 cx.paint_quad(crate::outline(
886 crate::Bounds {
887 origin: bounds.origin
888 + crate::point(crate::px(0.), FONT_SIZE - px(2.)),
889 size: crate::Size {
890 width: text_bounds.size.width,
891 height: crate::px(1.),
892 },
893 },
894 crate::red(),
895 ))
896 }
897 }
898 };
899
900 cx.with_z_index(1, |cx| {
901 cx.with_text_style(
902 Some(crate::TextStyleRefinement {
903 color: Some(crate::red()),
904 line_height: Some(FONT_SIZE.into()),
905 background_color: Some(crate::white()),
906 ..Default::default()
907 }),
908 render_debug_text,
909 )
910 });
911 }
912
913 if style
914 .background
915 .as_ref()
916 .is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent()))
917 {
918 cx.with_z_index(style.z_index.unwrap_or(0), |cx| cx.add_opaque_layer(bounds))
919 }
920
921 let interactive_bounds = Rc::new(InteractiveBounds {
922 bounds: bounds.intersect(&cx.content_mask().bounds),
923 stacking_order: cx.stacking_order().clone(),
924 });
925
926 if let Some(mouse_cursor) = style.mouse_cursor {
927 let mouse_position = &cx.mouse_position();
928 let hovered = interactive_bounds.visibly_contains(mouse_position, cx);
929 if hovered {
930 cx.set_cursor_style(mouse_cursor);
931 }
932 }
933
934 // If this element can be focused, register a mouse down listener
935 // that will automatically transfer focus when hitting the element.
936 // This behavior can be suppressed by using `cx.prevent_default()`.
937 if let Some(focus_handle) = element_state.focus_handle.clone() {
938 cx.on_mouse_event({
939 let interactive_bounds = interactive_bounds.clone();
940 move |event: &MouseDownEvent, phase, cx| {
941 if phase == DispatchPhase::Bubble
942 && !cx.default_prevented()
943 && interactive_bounds.visibly_contains(&event.position, cx)
944 {
945 cx.focus(&focus_handle);
946 // If there is a parent that is also focusable, prevent it
947 // from trasferring focus because we already did so.
948 cx.prevent_default();
949 }
950 }
951 });
952 }
953
954 for listener in self.mouse_down_listeners.drain(..) {
955 let interactive_bounds = interactive_bounds.clone();
956 cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
957 listener(event, &*interactive_bounds, phase, cx);
958 })
959 }
960
961 for listener in self.mouse_up_listeners.drain(..) {
962 let interactive_bounds = interactive_bounds.clone();
963 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
964 listener(event, &*interactive_bounds, phase, cx);
965 })
966 }
967
968 for listener in self.mouse_move_listeners.drain(..) {
969 let interactive_bounds = interactive_bounds.clone();
970 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
971 listener(event, &*interactive_bounds, phase, cx);
972 })
973 }
974
975 for listener in self.scroll_wheel_listeners.drain(..) {
976 let interactive_bounds = interactive_bounds.clone();
977 cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
978 listener(event, &*interactive_bounds, phase, cx);
979 })
980 }
981
982 let hover_group_bounds = self
983 .group_hover_style
984 .as_ref()
985 .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
986
987 if let Some(group_bounds) = hover_group_bounds {
988 let hovered = group_bounds.contains(&cx.mouse_position());
989 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
990 if phase == DispatchPhase::Capture {
991 if group_bounds.contains(&event.position) != hovered {
992 cx.notify();
993 }
994 }
995 });
996 }
997
998 if self.hover_style.is_some()
999 || self.base_style.mouse_cursor.is_some()
1000 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
1001 {
1002 let bounds = bounds.intersect(&cx.content_mask().bounds);
1003 let hovered = bounds.contains(&cx.mouse_position());
1004 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
1005 if phase == DispatchPhase::Capture {
1006 if bounds.contains(&event.position) != hovered {
1007 cx.notify();
1008 }
1009 }
1010 });
1011 }
1012
1013 if cx.active_drag.is_some() {
1014 let drop_listeners = mem::take(&mut self.drop_listeners);
1015 let interactive_bounds = interactive_bounds.clone();
1016 if !drop_listeners.is_empty() {
1017 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
1018 if phase == DispatchPhase::Bubble
1019 && interactive_bounds.drag_target_contains(&event.position, cx)
1020 {
1021 if let Some(drag_state_type) = cx
1022 .active_drag
1023 .as_ref()
1024 .map(|drag| drag.value.as_ref().type_id())
1025 {
1026 for (drop_state_type, listener) in &drop_listeners {
1027 if *drop_state_type == drag_state_type {
1028 let drag = cx
1029 .active_drag
1030 .take()
1031 .expect("checked for type drag state type above");
1032
1033 listener(drag.value.as_ref(), cx);
1034 cx.notify();
1035 cx.stop_propagation();
1036 }
1037 }
1038 } else {
1039 cx.active_drag = None;
1040 }
1041 }
1042 });
1043 }
1044 }
1045
1046 let click_listeners = mem::take(&mut self.click_listeners);
1047 let mut drag_listener = mem::take(&mut self.drag_listener);
1048
1049 if !click_listeners.is_empty() || drag_listener.is_some() {
1050 let pending_mouse_down = element_state.pending_mouse_down.clone();
1051 let mouse_down = pending_mouse_down.borrow().clone();
1052 if let Some(mouse_down) = mouse_down {
1053 if drag_listener.is_some() {
1054 let active_state = element_state.clicked_state.clone();
1055 let interactive_bounds = interactive_bounds.clone();
1056
1057 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
1058 if cx.active_drag.is_some() {
1059 if phase == DispatchPhase::Capture {
1060 cx.notify();
1061 }
1062 } else if phase == DispatchPhase::Bubble
1063 && interactive_bounds.visibly_contains(&event.position, cx)
1064 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
1065 {
1066 let (drag_value, drag_listener) = drag_listener
1067 .take()
1068 .expect("The notify below should invalidate this callback");
1069
1070 *active_state.borrow_mut() = ElementClickedState::default();
1071 let cursor_offset = event.position - bounds.origin;
1072 let drag = (drag_listener)(drag_value.as_ref(), cx);
1073 cx.active_drag = Some(AnyDrag {
1074 view: drag,
1075 value: drag_value,
1076 cursor_offset,
1077 });
1078 cx.notify();
1079 cx.stop_propagation();
1080 }
1081 });
1082 }
1083
1084 let interactive_bounds = interactive_bounds.clone();
1085 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
1086 if phase == DispatchPhase::Bubble
1087 && interactive_bounds.visibly_contains(&event.position, cx)
1088 {
1089 let mouse_click = ClickEvent {
1090 down: mouse_down.clone(),
1091 up: event.clone(),
1092 };
1093 for listener in &click_listeners {
1094 listener(&mouse_click, cx);
1095 }
1096 }
1097 *pending_mouse_down.borrow_mut() = None;
1098 cx.notify();
1099 });
1100 } else {
1101 let interactive_bounds = interactive_bounds.clone();
1102 cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
1103 if phase == DispatchPhase::Bubble
1104 && event.button == MouseButton::Left
1105 && interactive_bounds.visibly_contains(&event.position, cx)
1106 {
1107 *pending_mouse_down.borrow_mut() = Some(event.clone());
1108 cx.notify();
1109 }
1110 });
1111 }
1112 }
1113
1114 if let Some(hover_listener) = self.hover_listener.take() {
1115 let was_hovered = element_state.hover_state.clone();
1116 let has_mouse_down = element_state.pending_mouse_down.clone();
1117 let interactive_bounds = interactive_bounds.clone();
1118
1119 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
1120 if phase != DispatchPhase::Bubble {
1121 return;
1122 }
1123 let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
1124 && has_mouse_down.borrow().is_none();
1125 let mut was_hovered = was_hovered.borrow_mut();
1126
1127 if is_hovered != was_hovered.clone() {
1128 *was_hovered = is_hovered;
1129 drop(was_hovered);
1130
1131 hover_listener(&is_hovered, cx);
1132 }
1133 });
1134 }
1135
1136 if let Some(tooltip_builder) = self.tooltip_builder.take() {
1137 let active_tooltip = element_state.active_tooltip.clone();
1138 let pending_mouse_down = element_state.pending_mouse_down.clone();
1139 let interactive_bounds = interactive_bounds.clone();
1140
1141 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
1142 let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
1143 && pending_mouse_down.borrow().is_none();
1144 if !is_hovered {
1145 active_tooltip.borrow_mut().take();
1146 return;
1147 }
1148
1149 if phase != DispatchPhase::Bubble {
1150 return;
1151 }
1152
1153 if active_tooltip.borrow().is_none() {
1154 let task = cx.spawn({
1155 let active_tooltip = active_tooltip.clone();
1156 let tooltip_builder = tooltip_builder.clone();
1157
1158 move |mut cx| async move {
1159 cx.background_executor().timer(TOOLTIP_DELAY).await;
1160 cx.update(|_, cx| {
1161 active_tooltip.borrow_mut().replace(ActiveTooltip {
1162 tooltip: Some(AnyTooltip {
1163 view: tooltip_builder(cx),
1164 cursor_offset: cx.mouse_position(),
1165 }),
1166 _task: None,
1167 });
1168 cx.notify();
1169 })
1170 .ok();
1171 }
1172 });
1173 active_tooltip.borrow_mut().replace(ActiveTooltip {
1174 tooltip: None,
1175 _task: Some(task),
1176 });
1177 }
1178 });
1179
1180 let active_tooltip = element_state.active_tooltip.clone();
1181 cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
1182 active_tooltip.borrow_mut().take();
1183 });
1184
1185 if let Some(active_tooltip) = element_state.active_tooltip.borrow().as_ref() {
1186 if active_tooltip.tooltip.is_some() {
1187 cx.active_tooltip = active_tooltip.tooltip.clone()
1188 }
1189 }
1190 }
1191
1192 let active_state = element_state.clicked_state.clone();
1193 if active_state.borrow().is_clicked() {
1194 cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| {
1195 if phase == DispatchPhase::Capture {
1196 *active_state.borrow_mut() = ElementClickedState::default();
1197 cx.notify();
1198 }
1199 });
1200 } else {
1201 let active_group_bounds = self
1202 .group_active_style
1203 .as_ref()
1204 .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
1205 let interactive_bounds = interactive_bounds.clone();
1206 cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| {
1207 if phase == DispatchPhase::Bubble && !cx.default_prevented() {
1208 let group =
1209 active_group_bounds.map_or(false, |bounds| bounds.contains(&down.position));
1210 let element = interactive_bounds.visibly_contains(&down.position, cx);
1211 if group || element {
1212 *active_state.borrow_mut() = ElementClickedState { group, element };
1213 cx.notify();
1214 }
1215 }
1216 });
1217 }
1218
1219 let overflow = style.overflow;
1220 if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll {
1221 if let Some(scroll_handle) = &self.scroll_handle {
1222 scroll_handle.0.borrow_mut().overflow = overflow;
1223 }
1224
1225 let scroll_offset = element_state
1226 .scroll_offset
1227 .get_or_insert_with(Rc::default)
1228 .clone();
1229 let line_height = cx.line_height();
1230 let scroll_max = (content_size - bounds.size).max(&Size::default());
1231 let interactive_bounds = interactive_bounds.clone();
1232
1233 cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
1234 if phase == DispatchPhase::Bubble
1235 && interactive_bounds.visibly_contains(&event.position, cx)
1236 {
1237 let mut scroll_offset = scroll_offset.borrow_mut();
1238 let old_scroll_offset = *scroll_offset;
1239 let delta = event.delta.pixel_delta(line_height);
1240
1241 if overflow.x == Overflow::Scroll {
1242 scroll_offset.x =
1243 (scroll_offset.x + delta.x).clamp(-scroll_max.width, px(0.));
1244 }
1245
1246 if overflow.y == Overflow::Scroll {
1247 scroll_offset.y =
1248 (scroll_offset.y + delta.y).clamp(-scroll_max.height, px(0.));
1249 }
1250
1251 if *scroll_offset != old_scroll_offset {
1252 cx.notify();
1253 cx.stop_propagation();
1254 }
1255 }
1256 });
1257 }
1258
1259 if let Some(group) = self.group.clone() {
1260 GroupBounds::push(group, bounds, cx);
1261 }
1262
1263 let scroll_offset = element_state
1264 .scroll_offset
1265 .as_ref()
1266 .map(|scroll_offset| *scroll_offset.borrow());
1267
1268 cx.with_key_dispatch(
1269 self.key_context.clone(),
1270 element_state.focus_handle.clone(),
1271 |_, cx| {
1272 for listener in self.key_down_listeners.drain(..) {
1273 cx.on_key_event(move |event: &KeyDownEvent, phase, cx| {
1274 listener(event, phase, cx);
1275 })
1276 }
1277
1278 for listener in self.key_up_listeners.drain(..) {
1279 cx.on_key_event(move |event: &KeyUpEvent, phase, cx| {
1280 listener(event, phase, cx);
1281 })
1282 }
1283
1284 for (action_type, listener) in self.action_listeners {
1285 cx.on_action(action_type, listener)
1286 }
1287
1288 f(style, scroll_offset.unwrap_or_default(), cx)
1289 },
1290 );
1291
1292 if let Some(group) = self.group.as_ref() {
1293 GroupBounds::pop(group, cx);
1294 }
1295 }
1296
1297 pub fn compute_style(
1298 &self,
1299 bounds: Option<Bounds<Pixels>>,
1300 element_state: &mut InteractiveElementState,
1301 cx: &mut WindowContext,
1302 ) -> Style {
1303 let mut style = Style::default();
1304 style.refine(&self.base_style);
1305
1306 cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
1307 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
1308 if let Some(in_focus_style) = self.in_focus_style.as_ref() {
1309 if focus_handle.within_focused(cx) {
1310 style.refine(in_focus_style);
1311 }
1312 }
1313
1314 if let Some(focus_style) = self.focus_style.as_ref() {
1315 if focus_handle.is_focused(cx) {
1316 style.refine(focus_style);
1317 }
1318 }
1319 }
1320
1321 if let Some(bounds) = bounds {
1322 let mouse_position = cx.mouse_position();
1323 if let Some(group_hover) = self.group_hover_style.as_ref() {
1324 if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) {
1325 if group_bounds.contains(&mouse_position)
1326 && cx.was_top_layer(&mouse_position, cx.stacking_order())
1327 {
1328 style.refine(&group_hover.style);
1329 }
1330 }
1331 }
1332 if let Some(hover_style) = self.hover_style.as_ref() {
1333 if bounds
1334 .intersect(&cx.content_mask().bounds)
1335 .contains(&mouse_position)
1336 && cx.was_top_layer(&mouse_position, cx.stacking_order())
1337 {
1338 style.refine(hover_style);
1339 }
1340 }
1341
1342 if let Some(drag) = cx.active_drag.take() {
1343 for (state_type, group_drag_style) in &self.group_drag_over_styles {
1344 if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
1345 if *state_type == drag.value.as_ref().type_id()
1346 && group_bounds.contains(&mouse_position)
1347 {
1348 style.refine(&group_drag_style.style);
1349 }
1350 }
1351 }
1352
1353 for (state_type, drag_over_style) in &self.drag_over_styles {
1354 if *state_type == drag.value.as_ref().type_id()
1355 && bounds
1356 .intersect(&cx.content_mask().bounds)
1357 .contains(&mouse_position)
1358 && cx.was_top_layer_under_active_drag(
1359 &mouse_position,
1360 cx.stacking_order(),
1361 )
1362 {
1363 style.refine(drag_over_style);
1364 }
1365 }
1366
1367 cx.active_drag = Some(drag);
1368 }
1369 }
1370
1371 let clicked_state = element_state.clicked_state.borrow();
1372 if clicked_state.group {
1373 if let Some(group) = self.group_active_style.as_ref() {
1374 style.refine(&group.style)
1375 }
1376 }
1377
1378 if let Some(active_style) = self.active_style.as_ref() {
1379 if clicked_state.element {
1380 style.refine(active_style)
1381 }
1382 }
1383 });
1384
1385 style
1386 }
1387}
1388
1389impl Default for Interactivity {
1390 fn default() -> Self {
1391 Self {
1392 element_id: None,
1393 key_context: None,
1394 focusable: false,
1395 tracked_focus_handle: None,
1396 scroll_handle: None,
1397 // scroll_offset: Point::default(),
1398 group: None,
1399 base_style: Box::new(StyleRefinement::default()),
1400 focus_style: None,
1401 in_focus_style: None,
1402 hover_style: None,
1403 group_hover_style: None,
1404 active_style: None,
1405 group_active_style: None,
1406 drag_over_styles: Vec::new(),
1407 group_drag_over_styles: Vec::new(),
1408 mouse_down_listeners: Vec::new(),
1409 mouse_up_listeners: Vec::new(),
1410 mouse_move_listeners: Vec::new(),
1411 scroll_wheel_listeners: Vec::new(),
1412 key_down_listeners: Vec::new(),
1413 key_up_listeners: Vec::new(),
1414 action_listeners: Vec::new(),
1415 drop_listeners: Vec::new(),
1416 click_listeners: Vec::new(),
1417 drag_listener: None,
1418 hover_listener: None,
1419 tooltip_builder: None,
1420
1421 #[cfg(debug_assertions)]
1422 location: None,
1423 }
1424 }
1425}
1426
1427#[derive(Default)]
1428pub struct InteractiveElementState {
1429 pub focus_handle: Option<FocusHandle>,
1430 pub clicked_state: Rc<RefCell<ElementClickedState>>,
1431 pub hover_state: Rc<RefCell<bool>>,
1432 pub pending_mouse_down: Rc<RefCell<Option<MouseDownEvent>>>,
1433 pub scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
1434 pub active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
1435}
1436
1437pub struct ActiveTooltip {
1438 tooltip: Option<AnyTooltip>,
1439 _task: Option<Task<()>>,
1440}
1441
1442/// Whether or not the element or a group that contains it is clicked by the mouse.
1443#[derive(Copy, Clone, Default, Eq, PartialEq)]
1444pub struct ElementClickedState {
1445 pub group: bool,
1446 pub element: bool,
1447}
1448
1449impl ElementClickedState {
1450 fn is_clicked(&self) -> bool {
1451 self.group || self.element
1452 }
1453}
1454
1455#[derive(Default)]
1456pub struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
1457
1458impl GroupBounds {
1459 pub fn get(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
1460 cx.default_global::<Self>()
1461 .0
1462 .get(name)
1463 .and_then(|bounds_stack| bounds_stack.last())
1464 .cloned()
1465 }
1466
1467 pub fn push(name: SharedString, bounds: Bounds<Pixels>, cx: &mut AppContext) {
1468 cx.default_global::<Self>()
1469 .0
1470 .entry(name)
1471 .or_default()
1472 .push(bounds);
1473 }
1474
1475 pub fn pop(name: &SharedString, cx: &mut AppContext) {
1476 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
1477 }
1478}
1479
1480pub struct Focusable<E> {
1481 pub element: E,
1482}
1483
1484impl<E: InteractiveElement> FocusableElement for Focusable<E> {}
1485
1486impl<E> InteractiveElement for Focusable<E>
1487where
1488 E: InteractiveElement,
1489{
1490 fn interactivity(&mut self) -> &mut Interactivity {
1491 self.element.interactivity()
1492 }
1493}
1494
1495impl<E: StatefulInteractiveElement> StatefulInteractiveElement for Focusable<E> {}
1496
1497impl<E> Styled for Focusable<E>
1498where
1499 E: Styled,
1500{
1501 fn style(&mut self) -> &mut StyleRefinement {
1502 self.element.style()
1503 }
1504}
1505
1506impl<E> Element for Focusable<E>
1507where
1508 E: Element,
1509{
1510 type State = E::State;
1511
1512 fn layout(
1513 &mut self,
1514 state: Option<Self::State>,
1515 cx: &mut WindowContext,
1516 ) -> (LayoutId, Self::State) {
1517 self.element.layout(state, cx)
1518 }
1519
1520 fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
1521 self.element.paint(bounds, state, cx)
1522 }
1523}
1524
1525impl<E> IntoElement for Focusable<E>
1526where
1527 E: IntoElement,
1528{
1529 type Element = E::Element;
1530
1531 fn element_id(&self) -> Option<ElementId> {
1532 self.element.element_id()
1533 }
1534
1535 fn into_element(self) -> Self::Element {
1536 self.element.into_element()
1537 }
1538}
1539
1540impl<E> ParentElement for Focusable<E>
1541where
1542 E: ParentElement,
1543{
1544 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
1545 self.element.children_mut()
1546 }
1547}
1548
1549pub struct Stateful<E> {
1550 element: E,
1551}
1552
1553impl<E> Styled for Stateful<E>
1554where
1555 E: Styled,
1556{
1557 fn style(&mut self) -> &mut StyleRefinement {
1558 self.element.style()
1559 }
1560}
1561
1562impl<E> StatefulInteractiveElement for Stateful<E>
1563where
1564 E: Element,
1565 Self: InteractiveElement,
1566{
1567}
1568
1569impl<E> InteractiveElement for Stateful<E>
1570where
1571 E: InteractiveElement,
1572{
1573 fn interactivity(&mut self) -> &mut Interactivity {
1574 self.element.interactivity()
1575 }
1576}
1577
1578impl<E: FocusableElement> FocusableElement for Stateful<E> {}
1579
1580impl<E> Element for Stateful<E>
1581where
1582 E: Element,
1583{
1584 type State = E::State;
1585
1586 fn layout(
1587 &mut self,
1588 state: Option<Self::State>,
1589 cx: &mut WindowContext,
1590 ) -> (LayoutId, Self::State) {
1591 self.element.layout(state, cx)
1592 }
1593
1594 fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
1595 self.element.paint(bounds, state, cx)
1596 }
1597}
1598
1599impl<E> IntoElement for Stateful<E>
1600where
1601 E: Element,
1602{
1603 type Element = Self;
1604
1605 fn element_id(&self) -> Option<ElementId> {
1606 self.element.element_id()
1607 }
1608
1609 fn into_element(self) -> Self::Element {
1610 self
1611 }
1612}
1613
1614impl<E> ParentElement for Stateful<E>
1615where
1616 E: ParentElement,
1617{
1618 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
1619 self.element.children_mut()
1620 }
1621}
1622
1623#[derive(Default)]
1624struct ScrollHandleState {
1625 // not great to have the nested rc's...
1626 offset: Rc<RefCell<Point<Pixels>>>,
1627 bounds: Bounds<Pixels>,
1628 child_bounds: Vec<Bounds<Pixels>>,
1629 requested_scroll_top: Option<(usize, Pixels)>,
1630 overflow: Point<Overflow>,
1631}
1632
1633#[derive(Clone)]
1634pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
1635
1636impl ScrollHandle {
1637 pub fn new() -> Self {
1638 Self(Rc::default())
1639 }
1640
1641 pub fn offset(&self) -> Point<Pixels> {
1642 self.0.borrow().offset.borrow().clone()
1643 }
1644
1645 pub fn top_item(&self) -> usize {
1646 let state = self.0.borrow();
1647 let top = state.bounds.top() - state.offset.borrow().y;
1648
1649 match state.child_bounds.binary_search_by(|bounds| {
1650 if top < bounds.top() {
1651 Ordering::Greater
1652 } else if top > bounds.bottom() {
1653 Ordering::Less
1654 } else {
1655 Ordering::Equal
1656 }
1657 }) {
1658 Ok(ix) => ix,
1659 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
1660 }
1661 }
1662
1663 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
1664 self.0.borrow().child_bounds.get(ix).cloned()
1665 }
1666
1667 /// scroll_to_item scrolls the minimal amount to ensure that the item is
1668 /// fully visible
1669 pub fn scroll_to_item(&self, ix: usize) {
1670 let state = self.0.borrow();
1671
1672 let Some(bounds) = state.child_bounds.get(ix) else {
1673 return;
1674 };
1675
1676 let mut scroll_offset = state.offset.borrow_mut();
1677
1678 if state.overflow.y == Overflow::Scroll {
1679 if bounds.top() + scroll_offset.y < state.bounds.top() {
1680 scroll_offset.y = state.bounds.top() - bounds.top();
1681 } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
1682 scroll_offset.y = state.bounds.bottom() - bounds.bottom();
1683 }
1684 }
1685
1686 if state.overflow.x == Overflow::Scroll {
1687 if bounds.left() + scroll_offset.x < state.bounds.left() {
1688 scroll_offset.x = state.bounds.left() - bounds.left();
1689 } else if bounds.right() + scroll_offset.x > state.bounds.right() {
1690 scroll_offset.x = state.bounds.right() - bounds.right();
1691 }
1692 }
1693 }
1694
1695 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
1696 let ix = self.top_item();
1697 let state = self.0.borrow();
1698
1699 if let Some(child_bounds) = state.child_bounds.get(ix) {
1700 (
1701 ix,
1702 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
1703 )
1704 } else {
1705 (ix, px(0.))
1706 }
1707 }
1708
1709 pub fn set_logical_scroll_top(&self, ix: usize, px: Pixels) {
1710 self.0.borrow_mut().requested_scroll_top = Some((ix, px));
1711 }
1712}