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