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