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