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