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