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