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 cx.with_z_index(0, |cx| {
644 style.paint(bounds, cx);
645 });
646 cx.with_z_index(1, |cx| {
647 cx.with_text_style(style.text_style().cloned(), |cx| {
648 cx.with_content_mask(style.overflow_mask(bounds), |cx| {
649 cx.with_element_offset(scroll_offset, |cx| {
650 for child in self.children {
651 child.paint(cx);
652 }
653 })
654 })
655 })
656 })
657 })
658 },
659 );
660 }
661}
662
663impl IntoElement for Div {
664 type Element = Self;
665
666 fn element_id(&self) -> Option<ElementId> {
667 self.interactivity.element_id.clone()
668 }
669
670 fn into_element(self) -> Self::Element {
671 self
672 }
673}
674
675pub struct DivState {
676 child_layout_ids: SmallVec<[LayoutId; 2]>,
677 interactive_state: InteractiveElementState,
678}
679
680impl DivState {
681 pub fn is_active(&self) -> bool {
682 self.interactive_state.pending_mouse_down.borrow().is_some()
683 }
684}
685
686pub struct Interactivity {
687 pub element_id: Option<ElementId>,
688 pub key_context: Option<KeyContext>,
689 pub focusable: bool,
690 pub tracked_focus_handle: Option<FocusHandle>,
691 pub scroll_handle: Option<ScrollHandle>,
692 pub group: Option<SharedString>,
693 pub base_style: Box<StyleRefinement>,
694 pub focus_style: Option<Box<StyleRefinement>>,
695 pub in_focus_style: Option<Box<StyleRefinement>>,
696 pub hover_style: Option<Box<StyleRefinement>>,
697 pub group_hover_style: Option<GroupStyle>,
698 pub active_style: Option<Box<StyleRefinement>>,
699 pub group_active_style: Option<GroupStyle>,
700 pub drag_over_styles: Vec<(TypeId, StyleRefinement)>,
701 pub group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
702 pub mouse_down_listeners: Vec<MouseDownListener>,
703 pub mouse_up_listeners: Vec<MouseUpListener>,
704 pub mouse_move_listeners: Vec<MouseMoveListener>,
705 pub scroll_wheel_listeners: Vec<ScrollWheelListener>,
706 pub key_down_listeners: Vec<KeyDownListener>,
707 pub key_up_listeners: Vec<KeyUpListener>,
708 pub action_listeners: Vec<(TypeId, ActionListener)>,
709 pub drop_listeners: Vec<(TypeId, Box<DropListener>)>,
710 pub click_listeners: Vec<ClickListener>,
711 pub drag_listener: Option<DragListener>,
712 pub hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>,
713 pub tooltip_builder: Option<TooltipBuilder>,
714}
715
716#[derive(Clone, Debug)]
717pub struct InteractiveBounds {
718 pub bounds: Bounds<Pixels>,
719 pub stacking_order: StackingOrder,
720}
721
722impl InteractiveBounds {
723 pub fn visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
724 self.bounds.contains(point) && cx.was_top_layer(&point, &self.stacking_order)
725 }
726
727 pub fn drag_target_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
728 self.bounds.contains(point)
729 && cx.was_top_layer_under_active_drag(&point, &self.stacking_order)
730 }
731}
732
733impl Interactivity {
734 pub fn layout(
735 &mut self,
736 element_state: Option<InteractiveElementState>,
737 cx: &mut WindowContext,
738 f: impl FnOnce(Style, &mut WindowContext) -> LayoutId,
739 ) -> (LayoutId, InteractiveElementState) {
740 let mut element_state = element_state.unwrap_or_default();
741
742 // Ensure we store a focus handle in our element state if we're focusable.
743 // If there's an explicit focus handle we're tracking, use that. Otherwise
744 // create a new handle and store it in the element state, which lives for as
745 // as frames contain an element with this id.
746 if self.focusable {
747 element_state.focus_handle.get_or_insert_with(|| {
748 self.tracked_focus_handle
749 .clone()
750 .unwrap_or_else(|| cx.focus_handle())
751 });
752 }
753
754 if let Some(scroll_handle) = self.scroll_handle.as_ref() {
755 element_state.scroll_offset = Some(scroll_handle.0.borrow().offset.clone());
756 }
757
758 let style = self.compute_style(None, &mut element_state, cx);
759 let layout_id = f(style, cx);
760 (layout_id, element_state)
761 }
762
763 pub fn paint(
764 mut self,
765 bounds: Bounds<Pixels>,
766 content_size: Size<Pixels>,
767 element_state: &mut InteractiveElementState,
768 cx: &mut WindowContext,
769 f: impl FnOnce(Style, Point<Pixels>, &mut WindowContext),
770 ) {
771 let style = self.compute_style(Some(bounds), element_state, cx);
772
773 if style
774 .background
775 .as_ref()
776 .is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent()))
777 {
778 cx.with_z_index(style.z_index.unwrap_or(0), |cx| cx.add_opaque_layer(bounds))
779 }
780
781 let interactive_bounds = Rc::new(InteractiveBounds {
782 bounds: bounds.intersect(&cx.content_mask().bounds),
783 stacking_order: cx.stacking_order().clone(),
784 });
785
786 if let Some(mouse_cursor) = style.mouse_cursor {
787 let mouse_position = &cx.mouse_position();
788 let hovered = interactive_bounds.visibly_contains(mouse_position, cx);
789 if hovered {
790 cx.set_cursor_style(mouse_cursor);
791 }
792 }
793
794 // If this element can be focused, register a mouse down listener
795 // that will automatically transfer focus when hitting the element.
796 // This behavior can be suppressed by using `cx.prevent_default()`.
797 if let Some(focus_handle) = element_state.focus_handle.clone() {
798 cx.on_mouse_event({
799 let interactive_bounds = interactive_bounds.clone();
800 move |event: &MouseDownEvent, phase, cx| {
801 if phase == DispatchPhase::Bubble
802 && !cx.default_prevented()
803 && interactive_bounds.visibly_contains(&event.position, cx)
804 {
805 cx.focus(&focus_handle);
806 // If there is a parent that is also focusable, prevent it
807 // from trasferring focus because we already did so.
808 cx.prevent_default();
809 }
810 }
811 });
812 }
813
814 for listener in self.mouse_down_listeners.drain(..) {
815 let interactive_bounds = interactive_bounds.clone();
816 cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
817 listener(event, &*interactive_bounds, phase, cx);
818 })
819 }
820
821 for listener in self.mouse_up_listeners.drain(..) {
822 let interactive_bounds = interactive_bounds.clone();
823 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
824 listener(event, &*interactive_bounds, phase, cx);
825 })
826 }
827
828 for listener in self.mouse_move_listeners.drain(..) {
829 let interactive_bounds = interactive_bounds.clone();
830 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
831 listener(event, &*interactive_bounds, phase, cx);
832 })
833 }
834
835 for listener in self.scroll_wheel_listeners.drain(..) {
836 let interactive_bounds = interactive_bounds.clone();
837 cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
838 listener(event, &*interactive_bounds, phase, cx);
839 })
840 }
841
842 let hover_group_bounds = self
843 .group_hover_style
844 .as_ref()
845 .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
846
847 if let Some(group_bounds) = hover_group_bounds {
848 let hovered = group_bounds.contains(&cx.mouse_position());
849 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
850 if phase == DispatchPhase::Capture {
851 if group_bounds.contains(&event.position) != hovered {
852 cx.notify();
853 }
854 }
855 });
856 }
857
858 if self.hover_style.is_some()
859 || self.base_style.mouse_cursor.is_some()
860 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
861 {
862 let bounds = bounds.intersect(&cx.content_mask().bounds);
863 let hovered = bounds.contains(&cx.mouse_position());
864 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
865 if phase == DispatchPhase::Capture {
866 if bounds.contains(&event.position) != hovered {
867 cx.notify();
868 }
869 }
870 });
871 }
872
873 if cx.active_drag.is_some() {
874 let drop_listeners = mem::take(&mut self.drop_listeners);
875 let interactive_bounds = interactive_bounds.clone();
876 if !drop_listeners.is_empty() {
877 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
878 if phase == DispatchPhase::Bubble
879 && interactive_bounds.drag_target_contains(&event.position, cx)
880 {
881 if let Some(drag_state_type) =
882 cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
883 {
884 for (drop_state_type, listener) in &drop_listeners {
885 if *drop_state_type == drag_state_type {
886 let drag = cx
887 .active_drag
888 .take()
889 .expect("checked for type drag state type above");
890
891 listener(drag.view.clone(), cx);
892 cx.notify();
893 cx.stop_propagation();
894 }
895 }
896 } else {
897 cx.active_drag = None;
898 }
899 }
900 });
901 }
902 }
903
904 let click_listeners = mem::take(&mut self.click_listeners);
905 let drag_listener = mem::take(&mut self.drag_listener);
906
907 if !click_listeners.is_empty() || drag_listener.is_some() {
908 let pending_mouse_down = element_state.pending_mouse_down.clone();
909 let mouse_down = pending_mouse_down.borrow().clone();
910 if let Some(mouse_down) = mouse_down {
911 if let Some(drag_listener) = drag_listener {
912 let active_state = element_state.clicked_state.clone();
913 let interactive_bounds = interactive_bounds.clone();
914
915 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
916 if cx.active_drag.is_some() {
917 if phase == DispatchPhase::Capture {
918 cx.notify();
919 }
920 } else if phase == DispatchPhase::Bubble
921 && interactive_bounds.visibly_contains(&event.position, cx)
922 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
923 {
924 *active_state.borrow_mut() = ElementClickedState::default();
925 let cursor_offset = event.position - bounds.origin;
926 let drag = drag_listener(cursor_offset, cx);
927 cx.active_drag = Some(drag);
928 cx.notify();
929 cx.stop_propagation();
930 }
931 });
932 }
933
934 let interactive_bounds = interactive_bounds.clone();
935 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
936 if phase == DispatchPhase::Bubble
937 && interactive_bounds.visibly_contains(&event.position, cx)
938 {
939 let mouse_click = ClickEvent {
940 down: mouse_down.clone(),
941 up: event.clone(),
942 };
943 for listener in &click_listeners {
944 listener(&mouse_click, cx);
945 }
946 }
947 *pending_mouse_down.borrow_mut() = None;
948 cx.notify();
949 });
950 } else {
951 let interactive_bounds = interactive_bounds.clone();
952 cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
953 if phase == DispatchPhase::Bubble
954 && event.button == MouseButton::Left
955 && interactive_bounds.visibly_contains(&event.position, cx)
956 {
957 *pending_mouse_down.borrow_mut() = Some(event.clone());
958 cx.notify();
959 }
960 });
961 }
962 }
963
964 if let Some(hover_listener) = self.hover_listener.take() {
965 let was_hovered = element_state.hover_state.clone();
966 let has_mouse_down = element_state.pending_mouse_down.clone();
967 let interactive_bounds = interactive_bounds.clone();
968
969 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
970 if phase != DispatchPhase::Bubble {
971 return;
972 }
973 let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
974 && has_mouse_down.borrow().is_none();
975 let mut was_hovered = was_hovered.borrow_mut();
976
977 if is_hovered != was_hovered.clone() {
978 *was_hovered = is_hovered;
979 drop(was_hovered);
980
981 hover_listener(&is_hovered, cx);
982 }
983 });
984 }
985
986 if let Some(tooltip_builder) = self.tooltip_builder.take() {
987 let active_tooltip = element_state.active_tooltip.clone();
988 let pending_mouse_down = element_state.pending_mouse_down.clone();
989 let interactive_bounds = interactive_bounds.clone();
990
991 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
992 let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
993 && pending_mouse_down.borrow().is_none();
994 if !is_hovered {
995 active_tooltip.borrow_mut().take();
996 return;
997 }
998
999 if phase != DispatchPhase::Bubble {
1000 return;
1001 }
1002
1003 if active_tooltip.borrow().is_none() {
1004 let task = cx.spawn({
1005 let active_tooltip = active_tooltip.clone();
1006 let tooltip_builder = tooltip_builder.clone();
1007
1008 move |mut cx| async move {
1009 cx.background_executor().timer(TOOLTIP_DELAY).await;
1010 cx.update(|_, cx| {
1011 active_tooltip.borrow_mut().replace(ActiveTooltip {
1012 tooltip: Some(AnyTooltip {
1013 view: tooltip_builder(cx),
1014 cursor_offset: cx.mouse_position(),
1015 }),
1016 _task: None,
1017 });
1018 cx.notify();
1019 })
1020 .ok();
1021 }
1022 });
1023 active_tooltip.borrow_mut().replace(ActiveTooltip {
1024 tooltip: None,
1025 _task: Some(task),
1026 });
1027 }
1028 });
1029
1030 let active_tooltip = element_state.active_tooltip.clone();
1031 cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
1032 active_tooltip.borrow_mut().take();
1033 });
1034
1035 if let Some(active_tooltip) = element_state.active_tooltip.borrow().as_ref() {
1036 if active_tooltip.tooltip.is_some() {
1037 cx.active_tooltip = active_tooltip.tooltip.clone()
1038 }
1039 }
1040 }
1041
1042 let active_state = element_state.clicked_state.clone();
1043 if active_state.borrow().is_clicked() {
1044 cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| {
1045 if phase == DispatchPhase::Capture {
1046 *active_state.borrow_mut() = ElementClickedState::default();
1047 cx.notify();
1048 }
1049 });
1050 } else {
1051 let active_group_bounds = self
1052 .group_active_style
1053 .as_ref()
1054 .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
1055 let interactive_bounds = interactive_bounds.clone();
1056 cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| {
1057 if phase == DispatchPhase::Bubble && !cx.default_prevented() {
1058 let group =
1059 active_group_bounds.map_or(false, |bounds| bounds.contains(&down.position));
1060 let element = interactive_bounds.visibly_contains(&down.position, cx);
1061 if group || element {
1062 *active_state.borrow_mut() = ElementClickedState { group, element };
1063 cx.notify();
1064 }
1065 }
1066 });
1067 }
1068
1069 let overflow = style.overflow;
1070 if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll {
1071 if let Some(scroll_handle) = &self.scroll_handle {
1072 scroll_handle.0.borrow_mut().overflow = overflow;
1073 }
1074
1075 let scroll_offset = element_state
1076 .scroll_offset
1077 .get_or_insert_with(Rc::default)
1078 .clone();
1079 let line_height = cx.line_height();
1080 let scroll_max = (content_size - bounds.size).max(&Size::default());
1081 let interactive_bounds = interactive_bounds.clone();
1082
1083 cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
1084 if phase == DispatchPhase::Bubble
1085 && interactive_bounds.visibly_contains(&event.position, cx)
1086 {
1087 let mut scroll_offset = scroll_offset.borrow_mut();
1088 let old_scroll_offset = *scroll_offset;
1089 let delta = event.delta.pixel_delta(line_height);
1090
1091 if overflow.x == Overflow::Scroll {
1092 scroll_offset.x =
1093 (scroll_offset.x + delta.x).clamp(-scroll_max.width, px(0.));
1094 }
1095
1096 if overflow.y == Overflow::Scroll {
1097 scroll_offset.y =
1098 (scroll_offset.y + delta.y).clamp(-scroll_max.height, px(0.));
1099 }
1100
1101 if *scroll_offset != old_scroll_offset {
1102 cx.notify();
1103 cx.stop_propagation();
1104 }
1105 }
1106 });
1107 }
1108
1109 if let Some(group) = self.group.clone() {
1110 GroupBounds::push(group, bounds, cx);
1111 }
1112
1113 let scroll_offset = element_state
1114 .scroll_offset
1115 .as_ref()
1116 .map(|scroll_offset| *scroll_offset.borrow());
1117
1118 cx.with_key_dispatch(
1119 self.key_context.clone(),
1120 element_state.focus_handle.clone(),
1121 |_, cx| {
1122 for listener in self.key_down_listeners.drain(..) {
1123 cx.on_key_event(move |event: &KeyDownEvent, phase, cx| {
1124 listener(event, phase, cx);
1125 })
1126 }
1127
1128 for listener in self.key_up_listeners.drain(..) {
1129 cx.on_key_event(move |event: &KeyUpEvent, phase, cx| {
1130 listener(event, phase, cx);
1131 })
1132 }
1133
1134 for (action_type, listener) in self.action_listeners {
1135 cx.on_action(action_type, listener)
1136 }
1137
1138 f(style, scroll_offset.unwrap_or_default(), cx)
1139 },
1140 );
1141
1142 if let Some(group) = self.group.as_ref() {
1143 GroupBounds::pop(group, cx);
1144 }
1145 }
1146
1147 pub fn compute_style(
1148 &self,
1149 bounds: Option<Bounds<Pixels>>,
1150 element_state: &mut InteractiveElementState,
1151 cx: &mut WindowContext,
1152 ) -> Style {
1153 let mut style = Style::default();
1154 style.refine(&self.base_style);
1155
1156 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
1157 if let Some(in_focus_style) = self.in_focus_style.as_ref() {
1158 if focus_handle.within_focused(cx) {
1159 style.refine(in_focus_style);
1160 }
1161 }
1162
1163 if let Some(focus_style) = self.focus_style.as_ref() {
1164 if focus_handle.is_focused(cx) {
1165 style.refine(focus_style);
1166 }
1167 }
1168 }
1169
1170 if let Some(bounds) = bounds {
1171 let mouse_position = cx.mouse_position();
1172 if let Some(group_hover) = self.group_hover_style.as_ref() {
1173 if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) {
1174 if group_bounds.contains(&mouse_position)
1175 && cx.was_top_layer(&mouse_position, cx.stacking_order())
1176 {
1177 style.refine(&group_hover.style);
1178 }
1179 }
1180 }
1181 if let Some(hover_style) = self.hover_style.as_ref() {
1182 if bounds
1183 .intersect(&cx.content_mask().bounds)
1184 .contains(&mouse_position)
1185 && cx.was_top_layer(&mouse_position, cx.stacking_order())
1186 {
1187 style.refine(hover_style);
1188 }
1189 }
1190
1191 if let Some(drag) = cx.active_drag.take() {
1192 for (state_type, group_drag_style) in &self.group_drag_over_styles {
1193 if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
1194 if *state_type == drag.view.entity_type()
1195 && group_bounds.contains(&mouse_position)
1196 {
1197 style.refine(&group_drag_style.style);
1198 }
1199 }
1200 }
1201
1202 for (state_type, drag_over_style) in &self.drag_over_styles {
1203 if *state_type == drag.view.entity_type()
1204 && bounds
1205 .intersect(&cx.content_mask().bounds)
1206 .contains(&mouse_position)
1207 {
1208 style.refine(drag_over_style);
1209 }
1210 }
1211
1212 cx.active_drag = Some(drag);
1213 }
1214 }
1215
1216 let clicked_state = element_state.clicked_state.borrow();
1217 if clicked_state.group {
1218 if let Some(group) = self.group_active_style.as_ref() {
1219 style.refine(&group.style)
1220 }
1221 }
1222
1223 if let Some(active_style) = self.active_style.as_ref() {
1224 if clicked_state.element {
1225 style.refine(active_style)
1226 }
1227 }
1228
1229 style
1230 }
1231}
1232
1233impl Default for Interactivity {
1234 fn default() -> Self {
1235 Self {
1236 element_id: None,
1237 key_context: None,
1238 focusable: false,
1239 tracked_focus_handle: None,
1240 scroll_handle: None,
1241 // scroll_offset: Point::default(),
1242 group: None,
1243 base_style: Box::new(StyleRefinement::default()),
1244 focus_style: None,
1245 in_focus_style: None,
1246 hover_style: None,
1247 group_hover_style: None,
1248 active_style: None,
1249 group_active_style: None,
1250 drag_over_styles: Vec::new(),
1251 group_drag_over_styles: Vec::new(),
1252 mouse_down_listeners: Vec::new(),
1253 mouse_up_listeners: Vec::new(),
1254 mouse_move_listeners: Vec::new(),
1255 scroll_wheel_listeners: Vec::new(),
1256 key_down_listeners: Vec::new(),
1257 key_up_listeners: Vec::new(),
1258 action_listeners: Vec::new(),
1259 drop_listeners: Vec::new(),
1260 click_listeners: Vec::new(),
1261 drag_listener: None,
1262 hover_listener: None,
1263 tooltip_builder: None,
1264 }
1265 }
1266}
1267
1268#[derive(Default)]
1269pub struct InteractiveElementState {
1270 pub focus_handle: Option<FocusHandle>,
1271 pub clicked_state: Rc<RefCell<ElementClickedState>>,
1272 pub hover_state: Rc<RefCell<bool>>,
1273 pub pending_mouse_down: Rc<RefCell<Option<MouseDownEvent>>>,
1274 pub scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
1275 pub active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
1276}
1277
1278pub struct ActiveTooltip {
1279 tooltip: Option<AnyTooltip>,
1280 _task: Option<Task<()>>,
1281}
1282
1283/// Whether or not the element or a group that contains it is clicked by the mouse.
1284#[derive(Copy, Clone, Default, Eq, PartialEq)]
1285pub struct ElementClickedState {
1286 pub group: bool,
1287 pub element: bool,
1288}
1289
1290impl ElementClickedState {
1291 fn is_clicked(&self) -> bool {
1292 self.group || self.element
1293 }
1294}
1295
1296#[derive(Default)]
1297pub struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
1298
1299impl GroupBounds {
1300 pub fn get(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
1301 cx.default_global::<Self>()
1302 .0
1303 .get(name)
1304 .and_then(|bounds_stack| bounds_stack.last())
1305 .cloned()
1306 }
1307
1308 pub fn push(name: SharedString, bounds: Bounds<Pixels>, cx: &mut AppContext) {
1309 cx.default_global::<Self>()
1310 .0
1311 .entry(name)
1312 .or_default()
1313 .push(bounds);
1314 }
1315
1316 pub fn pop(name: &SharedString, cx: &mut AppContext) {
1317 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
1318 }
1319}
1320
1321pub struct Focusable<E> {
1322 pub element: E,
1323}
1324
1325impl<E: InteractiveElement> FocusableElement for Focusable<E> {}
1326
1327impl<E> InteractiveElement for Focusable<E>
1328where
1329 E: InteractiveElement,
1330{
1331 fn interactivity(&mut self) -> &mut Interactivity {
1332 self.element.interactivity()
1333 }
1334}
1335
1336impl<E: StatefulInteractiveElement> StatefulInteractiveElement for Focusable<E> {}
1337
1338impl<E> Styled for Focusable<E>
1339where
1340 E: Styled,
1341{
1342 fn style(&mut self) -> &mut StyleRefinement {
1343 self.element.style()
1344 }
1345}
1346
1347impl<E> Element for Focusable<E>
1348where
1349 E: Element,
1350{
1351 type State = E::State;
1352
1353 fn layout(
1354 &mut self,
1355 state: Option<Self::State>,
1356 cx: &mut WindowContext,
1357 ) -> (LayoutId, Self::State) {
1358 self.element.layout(state, cx)
1359 }
1360
1361 fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
1362 self.element.paint(bounds, state, cx)
1363 }
1364}
1365
1366impl<E> IntoElement for Focusable<E>
1367where
1368 E: IntoElement,
1369{
1370 type Element = E::Element;
1371
1372 fn element_id(&self) -> Option<ElementId> {
1373 self.element.element_id()
1374 }
1375
1376 fn into_element(self) -> Self::Element {
1377 self.element.into_element()
1378 }
1379}
1380
1381impl<E> ParentElement for Focusable<E>
1382where
1383 E: ParentElement,
1384{
1385 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
1386 self.element.children_mut()
1387 }
1388}
1389
1390pub struct Stateful<E> {
1391 element: E,
1392}
1393
1394impl<E> Styled for Stateful<E>
1395where
1396 E: Styled,
1397{
1398 fn style(&mut self) -> &mut StyleRefinement {
1399 self.element.style()
1400 }
1401}
1402
1403impl<E> StatefulInteractiveElement for Stateful<E>
1404where
1405 E: Element,
1406 Self: InteractiveElement,
1407{
1408}
1409
1410impl<E> InteractiveElement for Stateful<E>
1411where
1412 E: InteractiveElement,
1413{
1414 fn interactivity(&mut self) -> &mut Interactivity {
1415 self.element.interactivity()
1416 }
1417}
1418
1419impl<E: FocusableElement> FocusableElement for Stateful<E> {}
1420
1421impl<E> Element for Stateful<E>
1422where
1423 E: Element,
1424{
1425 type State = E::State;
1426
1427 fn layout(
1428 &mut self,
1429 state: Option<Self::State>,
1430 cx: &mut WindowContext,
1431 ) -> (LayoutId, Self::State) {
1432 self.element.layout(state, cx)
1433 }
1434
1435 fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
1436 self.element.paint(bounds, state, cx)
1437 }
1438}
1439
1440impl<E> IntoElement for Stateful<E>
1441where
1442 E: Element,
1443{
1444 type Element = Self;
1445
1446 fn element_id(&self) -> Option<ElementId> {
1447 self.element.element_id()
1448 }
1449
1450 fn into_element(self) -> Self::Element {
1451 self
1452 }
1453}
1454
1455impl<E> ParentElement for Stateful<E>
1456where
1457 E: ParentElement,
1458{
1459 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
1460 self.element.children_mut()
1461 }
1462}
1463
1464#[derive(Default)]
1465struct ScrollHandleState {
1466 // not great to have the nested rc's...
1467 offset: Rc<RefCell<Point<Pixels>>>,
1468 bounds: Bounds<Pixels>,
1469 child_bounds: Vec<Bounds<Pixels>>,
1470 requested_scroll_top: Option<(usize, Pixels)>,
1471 overflow: Point<Overflow>,
1472}
1473
1474#[derive(Clone)]
1475pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
1476
1477impl ScrollHandle {
1478 pub fn new() -> Self {
1479 Self(Rc::default())
1480 }
1481
1482 pub fn offset(&self) -> Point<Pixels> {
1483 self.0.borrow().offset.borrow().clone()
1484 }
1485
1486 pub fn top_item(&self) -> usize {
1487 let state = self.0.borrow();
1488 let top = state.bounds.top() - state.offset.borrow().y;
1489
1490 match state.child_bounds.binary_search_by(|bounds| {
1491 if top < bounds.top() {
1492 Ordering::Greater
1493 } else if top > bounds.bottom() {
1494 Ordering::Less
1495 } else {
1496 Ordering::Equal
1497 }
1498 }) {
1499 Ok(ix) => ix,
1500 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
1501 }
1502 }
1503
1504 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
1505 self.0.borrow().child_bounds.get(ix).cloned()
1506 }
1507
1508 /// scroll_to_item scrolls the minimal amount to ensure that the item is
1509 /// fully visible
1510 pub fn scroll_to_item(&self, ix: usize) {
1511 let state = self.0.borrow();
1512
1513 let Some(bounds) = state.child_bounds.get(ix) else {
1514 return;
1515 };
1516
1517 let mut scroll_offset = state.offset.borrow_mut();
1518
1519 if state.overflow.y == Overflow::Scroll {
1520 if bounds.top() + scroll_offset.y < state.bounds.top() {
1521 scroll_offset.y = state.bounds.top() - bounds.top();
1522 } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
1523 scroll_offset.y = state.bounds.bottom() - bounds.bottom();
1524 }
1525 }
1526
1527 if state.overflow.x == Overflow::Scroll {
1528 if bounds.left() + scroll_offset.x < state.bounds.left() {
1529 scroll_offset.x = state.bounds.left() - bounds.left();
1530 } else if bounds.right() + scroll_offset.x > state.bounds.right() {
1531 scroll_offset.x = state.bounds.right() - bounds.right();
1532 }
1533 }
1534 }
1535
1536 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
1537 let ix = self.top_item();
1538 let state = self.0.borrow();
1539
1540 if let Some(child_bounds) = state.child_bounds.get(ix) {
1541 (
1542 ix,
1543 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
1544 )
1545 } else {
1546 (ix, px(0.))
1547 }
1548 }
1549
1550 pub fn set_logical_scroll_top(&self, ix: usize, px: Pixels) {
1551 self.0.borrow_mut().requested_scroll_top = Some((ix, px));
1552 }
1553}