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