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