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