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 let z_index = style.z_index.unwrap_or(0);
646
647 cx.with_z_index(z_index, |cx| {
648 style.paint(bounds, cx, |cx| {
649 cx.with_text_style(style.text_style().cloned(), |cx| {
650 cx.with_content_mask(style.overflow_mask(bounds), |cx| {
651 cx.with_element_offset(scroll_offset, |cx| {
652 for child in self.children {
653 child.paint(cx);
654 }
655 })
656 })
657 })
658 });
659 })
660 },
661 );
662 }
663}
664
665impl IntoElement for Div {
666 type Element = Self;
667
668 fn element_id(&self) -> Option<ElementId> {
669 self.interactivity.element_id.clone()
670 }
671
672 fn into_element(self) -> Self::Element {
673 self
674 }
675}
676
677pub struct DivState {
678 child_layout_ids: SmallVec<[LayoutId; 2]>,
679 interactive_state: InteractiveElementState,
680}
681
682impl DivState {
683 pub fn is_active(&self) -> bool {
684 self.interactive_state.pending_mouse_down.borrow().is_some()
685 }
686}
687
688pub struct Interactivity {
689 pub element_id: Option<ElementId>,
690 pub key_context: Option<KeyContext>,
691 pub focusable: bool,
692 pub tracked_focus_handle: Option<FocusHandle>,
693 pub scroll_handle: Option<ScrollHandle>,
694 pub group: Option<SharedString>,
695 pub base_style: Box<StyleRefinement>,
696 pub focus_style: Option<Box<StyleRefinement>>,
697 pub in_focus_style: Option<Box<StyleRefinement>>,
698 pub hover_style: Option<Box<StyleRefinement>>,
699 pub group_hover_style: Option<GroupStyle>,
700 pub active_style: Option<Box<StyleRefinement>>,
701 pub group_active_style: Option<GroupStyle>,
702 pub drag_over_styles: Vec<(TypeId, StyleRefinement)>,
703 pub group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
704 pub mouse_down_listeners: Vec<MouseDownListener>,
705 pub mouse_up_listeners: Vec<MouseUpListener>,
706 pub mouse_move_listeners: Vec<MouseMoveListener>,
707 pub scroll_wheel_listeners: Vec<ScrollWheelListener>,
708 pub key_down_listeners: Vec<KeyDownListener>,
709 pub key_up_listeners: Vec<KeyUpListener>,
710 pub action_listeners: Vec<(TypeId, ActionListener)>,
711 pub drop_listeners: Vec<(TypeId, Box<DropListener>)>,
712 pub click_listeners: Vec<ClickListener>,
713 pub drag_listener: Option<DragListener>,
714 pub hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>,
715 pub tooltip_builder: Option<TooltipBuilder>,
716
717 #[cfg(debug_assertions)]
718 pub location: Option<core::panic::Location<'static>>,
719}
720
721#[derive(Clone, Debug)]
722pub struct InteractiveBounds {
723 pub bounds: Bounds<Pixels>,
724 pub stacking_order: StackingOrder,
725}
726
727impl InteractiveBounds {
728 pub fn visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
729 self.bounds.contains(point) && cx.was_top_layer(&point, &self.stacking_order)
730 }
731
732 pub fn drag_target_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
733 self.bounds.contains(point)
734 && cx.was_top_layer_under_active_drag(&point, &self.stacking_order)
735 }
736}
737
738impl Interactivity {
739 pub fn layout(
740 &mut self,
741 element_state: Option<InteractiveElementState>,
742 cx: &mut WindowContext,
743 f: impl FnOnce(Style, &mut WindowContext) -> LayoutId,
744 ) -> (LayoutId, InteractiveElementState) {
745 let mut element_state = element_state.unwrap_or_default();
746
747 // Ensure we store a focus handle in our element state if we're focusable.
748 // If there's an explicit focus handle we're tracking, use that. Otherwise
749 // create a new handle and store it in the element state, which lives for as
750 // as frames contain an element with this id.
751 if self.focusable {
752 element_state.focus_handle.get_or_insert_with(|| {
753 self.tracked_focus_handle
754 .clone()
755 .unwrap_or_else(|| cx.focus_handle())
756 });
757 }
758
759 if let Some(scroll_handle) = self.scroll_handle.as_ref() {
760 element_state.scroll_offset = Some(scroll_handle.0.borrow().offset.clone());
761 }
762
763 let style = self.compute_style(None, &mut element_state, cx);
764 let layout_id = f(style, cx);
765 (layout_id, element_state)
766 }
767
768 pub fn paint(
769 mut self,
770 bounds: Bounds<Pixels>,
771 content_size: Size<Pixels>,
772 element_state: &mut InteractiveElementState,
773 cx: &mut WindowContext,
774 f: impl FnOnce(Style, Point<Pixels>, &mut WindowContext),
775 ) {
776 let style = self.compute_style(Some(bounds), element_state, cx);
777
778 if style.visibility == Visibility::Hidden {
779 return;
780 }
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
894 .background
895 .as_ref()
896 .is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent()))
897 {
898 cx.with_z_index(style.z_index.unwrap_or(0), |cx| cx.add_opaque_layer(bounds))
899 }
900
901 let interactive_bounds = Rc::new(InteractiveBounds {
902 bounds: bounds.intersect(&cx.content_mask().bounds),
903 stacking_order: cx.stacking_order().clone(),
904 });
905
906 if let Some(mouse_cursor) = style.mouse_cursor {
907 let mouse_position = &cx.mouse_position();
908 let hovered = interactive_bounds.visibly_contains(mouse_position, cx);
909 if hovered {
910 cx.set_cursor_style(mouse_cursor);
911 }
912 }
913
914 // If this element can be focused, register a mouse down listener
915 // that will automatically transfer focus when hitting the element.
916 // This behavior can be suppressed by using `cx.prevent_default()`.
917 if let Some(focus_handle) = element_state.focus_handle.clone() {
918 cx.on_mouse_event({
919 let interactive_bounds = interactive_bounds.clone();
920 move |event: &MouseDownEvent, phase, cx| {
921 if phase == DispatchPhase::Bubble
922 && !cx.default_prevented()
923 && interactive_bounds.visibly_contains(&event.position, cx)
924 {
925 cx.focus(&focus_handle);
926 // If there is a parent that is also focusable, prevent it
927 // from trasferring focus because we already did so.
928 cx.prevent_default();
929 }
930 }
931 });
932 }
933
934 for listener in self.mouse_down_listeners.drain(..) {
935 let interactive_bounds = interactive_bounds.clone();
936 cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
937 listener(event, &*interactive_bounds, phase, cx);
938 })
939 }
940
941 for listener in self.mouse_up_listeners.drain(..) {
942 let interactive_bounds = interactive_bounds.clone();
943 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
944 listener(event, &*interactive_bounds, phase, cx);
945 })
946 }
947
948 for listener in self.mouse_move_listeners.drain(..) {
949 let interactive_bounds = interactive_bounds.clone();
950 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
951 listener(event, &*interactive_bounds, phase, cx);
952 })
953 }
954
955 for listener in self.scroll_wheel_listeners.drain(..) {
956 let interactive_bounds = interactive_bounds.clone();
957 cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
958 listener(event, &*interactive_bounds, phase, cx);
959 })
960 }
961
962 let hover_group_bounds = self
963 .group_hover_style
964 .as_ref()
965 .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
966
967 if let Some(group_bounds) = hover_group_bounds {
968 let hovered = group_bounds.contains(&cx.mouse_position());
969 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
970 if phase == DispatchPhase::Capture {
971 if group_bounds.contains(&event.position) != hovered {
972 cx.notify();
973 }
974 }
975 });
976 }
977
978 if self.hover_style.is_some()
979 || self.base_style.mouse_cursor.is_some()
980 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
981 {
982 let bounds = bounds.intersect(&cx.content_mask().bounds);
983 let hovered = bounds.contains(&cx.mouse_position());
984 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
985 if phase == DispatchPhase::Capture {
986 if bounds.contains(&event.position) != hovered {
987 cx.notify();
988 }
989 }
990 });
991 }
992
993 if cx.active_drag.is_some() {
994 let drop_listeners = mem::take(&mut self.drop_listeners);
995 let interactive_bounds = interactive_bounds.clone();
996 if !drop_listeners.is_empty() {
997 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
998 if phase == DispatchPhase::Bubble
999 && interactive_bounds.drag_target_contains(&event.position, cx)
1000 {
1001 if let Some(drag_state_type) =
1002 cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
1003 {
1004 for (drop_state_type, listener) in &drop_listeners {
1005 if *drop_state_type == drag_state_type {
1006 let drag = cx
1007 .active_drag
1008 .take()
1009 .expect("checked for type drag state type above");
1010
1011 listener(drag.view.clone(), cx);
1012 cx.notify();
1013 cx.stop_propagation();
1014 }
1015 }
1016 } else {
1017 cx.active_drag = None;
1018 }
1019 }
1020 });
1021 }
1022 }
1023
1024 let click_listeners = mem::take(&mut self.click_listeners);
1025 let drag_listener = mem::take(&mut self.drag_listener);
1026
1027 if !click_listeners.is_empty() || drag_listener.is_some() {
1028 let pending_mouse_down = element_state.pending_mouse_down.clone();
1029 let mouse_down = pending_mouse_down.borrow().clone();
1030 if let Some(mouse_down) = mouse_down {
1031 if let Some(drag_listener) = drag_listener {
1032 let active_state = element_state.clicked_state.clone();
1033 let interactive_bounds = interactive_bounds.clone();
1034
1035 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
1036 if cx.active_drag.is_some() {
1037 if phase == DispatchPhase::Capture {
1038 cx.notify();
1039 }
1040 } else if phase == DispatchPhase::Bubble
1041 && interactive_bounds.visibly_contains(&event.position, cx)
1042 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
1043 {
1044 *active_state.borrow_mut() = ElementClickedState::default();
1045 let cursor_offset = event.position - bounds.origin;
1046 let drag = drag_listener(cursor_offset, cx);
1047 cx.active_drag = Some(drag);
1048 cx.notify();
1049 cx.stop_propagation();
1050 }
1051 });
1052 }
1053
1054 let interactive_bounds = interactive_bounds.clone();
1055 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
1056 if phase == DispatchPhase::Bubble
1057 && interactive_bounds.visibly_contains(&event.position, cx)
1058 {
1059 let mouse_click = ClickEvent {
1060 down: mouse_down.clone(),
1061 up: event.clone(),
1062 };
1063 for listener in &click_listeners {
1064 listener(&mouse_click, cx);
1065 }
1066 }
1067 *pending_mouse_down.borrow_mut() = None;
1068 cx.notify();
1069 });
1070 } else {
1071 let interactive_bounds = interactive_bounds.clone();
1072 cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
1073 if phase == DispatchPhase::Bubble
1074 && event.button == MouseButton::Left
1075 && interactive_bounds.visibly_contains(&event.position, cx)
1076 {
1077 *pending_mouse_down.borrow_mut() = Some(event.clone());
1078 cx.notify();
1079 }
1080 });
1081 }
1082 }
1083
1084 if let Some(hover_listener) = self.hover_listener.take() {
1085 let was_hovered = element_state.hover_state.clone();
1086 let has_mouse_down = element_state.pending_mouse_down.clone();
1087 let interactive_bounds = interactive_bounds.clone();
1088
1089 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
1090 if phase != DispatchPhase::Bubble {
1091 return;
1092 }
1093 let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
1094 && has_mouse_down.borrow().is_none();
1095 let mut was_hovered = was_hovered.borrow_mut();
1096
1097 if is_hovered != was_hovered.clone() {
1098 *was_hovered = is_hovered;
1099 drop(was_hovered);
1100
1101 hover_listener(&is_hovered, cx);
1102 }
1103 });
1104 }
1105
1106 if let Some(tooltip_builder) = self.tooltip_builder.take() {
1107 let active_tooltip = element_state.active_tooltip.clone();
1108 let pending_mouse_down = element_state.pending_mouse_down.clone();
1109 let interactive_bounds = interactive_bounds.clone();
1110
1111 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
1112 let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
1113 && pending_mouse_down.borrow().is_none();
1114 if !is_hovered {
1115 active_tooltip.borrow_mut().take();
1116 return;
1117 }
1118
1119 if phase != DispatchPhase::Bubble {
1120 return;
1121 }
1122
1123 if active_tooltip.borrow().is_none() {
1124 let task = cx.spawn({
1125 let active_tooltip = active_tooltip.clone();
1126 let tooltip_builder = tooltip_builder.clone();
1127
1128 move |mut cx| async move {
1129 cx.background_executor().timer(TOOLTIP_DELAY).await;
1130 cx.update(|_, cx| {
1131 active_tooltip.borrow_mut().replace(ActiveTooltip {
1132 tooltip: Some(AnyTooltip {
1133 view: tooltip_builder(cx),
1134 cursor_offset: cx.mouse_position(),
1135 }),
1136 _task: None,
1137 });
1138 cx.notify();
1139 })
1140 .ok();
1141 }
1142 });
1143 active_tooltip.borrow_mut().replace(ActiveTooltip {
1144 tooltip: None,
1145 _task: Some(task),
1146 });
1147 }
1148 });
1149
1150 let active_tooltip = element_state.active_tooltip.clone();
1151 cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
1152 active_tooltip.borrow_mut().take();
1153 });
1154
1155 if let Some(active_tooltip) = element_state.active_tooltip.borrow().as_ref() {
1156 if active_tooltip.tooltip.is_some() {
1157 cx.active_tooltip = active_tooltip.tooltip.clone()
1158 }
1159 }
1160 }
1161
1162 let active_state = element_state.clicked_state.clone();
1163 if active_state.borrow().is_clicked() {
1164 cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| {
1165 if phase == DispatchPhase::Capture {
1166 *active_state.borrow_mut() = ElementClickedState::default();
1167 cx.notify();
1168 }
1169 });
1170 } else {
1171 let active_group_bounds = self
1172 .group_active_style
1173 .as_ref()
1174 .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
1175 let interactive_bounds = interactive_bounds.clone();
1176 cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| {
1177 if phase == DispatchPhase::Bubble && !cx.default_prevented() {
1178 let group =
1179 active_group_bounds.map_or(false, |bounds| bounds.contains(&down.position));
1180 let element = interactive_bounds.visibly_contains(&down.position, cx);
1181 if group || element {
1182 *active_state.borrow_mut() = ElementClickedState { group, element };
1183 cx.notify();
1184 }
1185 }
1186 });
1187 }
1188
1189 let overflow = style.overflow;
1190 if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll {
1191 if let Some(scroll_handle) = &self.scroll_handle {
1192 scroll_handle.0.borrow_mut().overflow = overflow;
1193 }
1194
1195 let scroll_offset = element_state
1196 .scroll_offset
1197 .get_or_insert_with(Rc::default)
1198 .clone();
1199 let line_height = cx.line_height();
1200 let scroll_max = (content_size - bounds.size).max(&Size::default());
1201 let interactive_bounds = interactive_bounds.clone();
1202
1203 cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
1204 if phase == DispatchPhase::Bubble
1205 && interactive_bounds.visibly_contains(&event.position, cx)
1206 {
1207 let mut scroll_offset = scroll_offset.borrow_mut();
1208 let old_scroll_offset = *scroll_offset;
1209 let delta = event.delta.pixel_delta(line_height);
1210
1211 if overflow.x == Overflow::Scroll {
1212 scroll_offset.x =
1213 (scroll_offset.x + delta.x).clamp(-scroll_max.width, px(0.));
1214 }
1215
1216 if overflow.y == Overflow::Scroll {
1217 scroll_offset.y =
1218 (scroll_offset.y + delta.y).clamp(-scroll_max.height, px(0.));
1219 }
1220
1221 if *scroll_offset != old_scroll_offset {
1222 cx.notify();
1223 cx.stop_propagation();
1224 }
1225 }
1226 });
1227 }
1228
1229 if let Some(group) = self.group.clone() {
1230 GroupBounds::push(group, bounds, cx);
1231 }
1232
1233 let scroll_offset = element_state
1234 .scroll_offset
1235 .as_ref()
1236 .map(|scroll_offset| *scroll_offset.borrow());
1237
1238 cx.with_key_dispatch(
1239 self.key_context.clone(),
1240 element_state.focus_handle.clone(),
1241 |_, cx| {
1242 for listener in self.key_down_listeners.drain(..) {
1243 cx.on_key_event(move |event: &KeyDownEvent, phase, cx| {
1244 listener(event, phase, cx);
1245 })
1246 }
1247
1248 for listener in self.key_up_listeners.drain(..) {
1249 cx.on_key_event(move |event: &KeyUpEvent, phase, cx| {
1250 listener(event, phase, cx);
1251 })
1252 }
1253
1254 for (action_type, listener) in self.action_listeners {
1255 cx.on_action(action_type, listener)
1256 }
1257
1258 f(style, scroll_offset.unwrap_or_default(), cx)
1259 },
1260 );
1261
1262 if let Some(group) = self.group.as_ref() {
1263 GroupBounds::pop(group, cx);
1264 }
1265 }
1266
1267 pub fn compute_style(
1268 &self,
1269 bounds: Option<Bounds<Pixels>>,
1270 element_state: &mut InteractiveElementState,
1271 cx: &mut WindowContext,
1272 ) -> Style {
1273 let mut style = Style::default();
1274 style.refine(&self.base_style);
1275
1276 cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
1277 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
1278 if let Some(in_focus_style) = self.in_focus_style.as_ref() {
1279 if focus_handle.within_focused(cx) {
1280 style.refine(in_focus_style);
1281 }
1282 }
1283
1284 if let Some(focus_style) = self.focus_style.as_ref() {
1285 if focus_handle.is_focused(cx) {
1286 style.refine(focus_style);
1287 }
1288 }
1289 }
1290
1291 if let Some(bounds) = bounds {
1292 let mouse_position = cx.mouse_position();
1293 if let Some(group_hover) = self.group_hover_style.as_ref() {
1294 if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) {
1295 if group_bounds.contains(&mouse_position)
1296 && cx.was_top_layer(&mouse_position, cx.stacking_order())
1297 {
1298 style.refine(&group_hover.style);
1299 }
1300 }
1301 }
1302 if let Some(hover_style) = self.hover_style.as_ref() {
1303 if bounds
1304 .intersect(&cx.content_mask().bounds)
1305 .contains(&mouse_position)
1306 && cx.was_top_layer(&mouse_position, cx.stacking_order())
1307 {
1308 style.refine(hover_style);
1309 }
1310 }
1311
1312 if let Some(drag) = cx.active_drag.take() {
1313 for (state_type, group_drag_style) in &self.group_drag_over_styles {
1314 if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
1315 if *state_type == drag.view.entity_type()
1316 && group_bounds.contains(&mouse_position)
1317 {
1318 style.refine(&group_drag_style.style);
1319 }
1320 }
1321 }
1322
1323 for (state_type, drag_over_style) in &self.drag_over_styles {
1324 if *state_type == drag.view.entity_type()
1325 && bounds
1326 .intersect(&cx.content_mask().bounds)
1327 .contains(&mouse_position)
1328 && cx.was_top_layer_under_active_drag(
1329 &mouse_position,
1330 cx.stacking_order(),
1331 )
1332 {
1333 style.refine(drag_over_style);
1334 }
1335 }
1336
1337 cx.active_drag = Some(drag);
1338 }
1339 }
1340
1341 let clicked_state = element_state.clicked_state.borrow();
1342 if clicked_state.group {
1343 if let Some(group) = self.group_active_style.as_ref() {
1344 style.refine(&group.style)
1345 }
1346 }
1347
1348 if let Some(active_style) = self.active_style.as_ref() {
1349 if clicked_state.element {
1350 style.refine(active_style)
1351 }
1352 }
1353 });
1354
1355 style
1356 }
1357}
1358
1359impl Default for Interactivity {
1360 fn default() -> Self {
1361 Self {
1362 element_id: None,
1363 key_context: None,
1364 focusable: false,
1365 tracked_focus_handle: None,
1366 scroll_handle: None,
1367 // scroll_offset: Point::default(),
1368 group: None,
1369 base_style: Box::new(StyleRefinement::default()),
1370 focus_style: None,
1371 in_focus_style: None,
1372 hover_style: None,
1373 group_hover_style: None,
1374 active_style: None,
1375 group_active_style: None,
1376 drag_over_styles: Vec::new(),
1377 group_drag_over_styles: Vec::new(),
1378 mouse_down_listeners: Vec::new(),
1379 mouse_up_listeners: Vec::new(),
1380 mouse_move_listeners: Vec::new(),
1381 scroll_wheel_listeners: Vec::new(),
1382 key_down_listeners: Vec::new(),
1383 key_up_listeners: Vec::new(),
1384 action_listeners: Vec::new(),
1385 drop_listeners: Vec::new(),
1386 click_listeners: Vec::new(),
1387 drag_listener: None,
1388 hover_listener: None,
1389 tooltip_builder: None,
1390
1391 #[cfg(debug_assertions)]
1392 location: None,
1393 }
1394 }
1395}
1396
1397#[derive(Default)]
1398pub struct InteractiveElementState {
1399 pub focus_handle: Option<FocusHandle>,
1400 pub clicked_state: Rc<RefCell<ElementClickedState>>,
1401 pub hover_state: Rc<RefCell<bool>>,
1402 pub pending_mouse_down: Rc<RefCell<Option<MouseDownEvent>>>,
1403 pub scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
1404 pub active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
1405}
1406
1407pub struct ActiveTooltip {
1408 tooltip: Option<AnyTooltip>,
1409 _task: Option<Task<()>>,
1410}
1411
1412/// Whether or not the element or a group that contains it is clicked by the mouse.
1413#[derive(Copy, Clone, Default, Eq, PartialEq)]
1414pub struct ElementClickedState {
1415 pub group: bool,
1416 pub element: bool,
1417}
1418
1419impl ElementClickedState {
1420 fn is_clicked(&self) -> bool {
1421 self.group || self.element
1422 }
1423}
1424
1425#[derive(Default)]
1426pub struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
1427
1428impl GroupBounds {
1429 pub fn get(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
1430 cx.default_global::<Self>()
1431 .0
1432 .get(name)
1433 .and_then(|bounds_stack| bounds_stack.last())
1434 .cloned()
1435 }
1436
1437 pub fn push(name: SharedString, bounds: Bounds<Pixels>, cx: &mut AppContext) {
1438 cx.default_global::<Self>()
1439 .0
1440 .entry(name)
1441 .or_default()
1442 .push(bounds);
1443 }
1444
1445 pub fn pop(name: &SharedString, cx: &mut AppContext) {
1446 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
1447 }
1448}
1449
1450pub struct Focusable<E> {
1451 pub element: E,
1452}
1453
1454impl<E: InteractiveElement> FocusableElement for Focusable<E> {}
1455
1456impl<E> InteractiveElement for Focusable<E>
1457where
1458 E: InteractiveElement,
1459{
1460 fn interactivity(&mut self) -> &mut Interactivity {
1461 self.element.interactivity()
1462 }
1463}
1464
1465impl<E: StatefulInteractiveElement> StatefulInteractiveElement for Focusable<E> {}
1466
1467impl<E> Styled for Focusable<E>
1468where
1469 E: Styled,
1470{
1471 fn style(&mut self) -> &mut StyleRefinement {
1472 self.element.style()
1473 }
1474}
1475
1476impl<E> Element for Focusable<E>
1477where
1478 E: Element,
1479{
1480 type State = E::State;
1481
1482 fn layout(
1483 &mut self,
1484 state: Option<Self::State>,
1485 cx: &mut WindowContext,
1486 ) -> (LayoutId, Self::State) {
1487 self.element.layout(state, cx)
1488 }
1489
1490 fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
1491 self.element.paint(bounds, state, cx)
1492 }
1493}
1494
1495impl<E> IntoElement for Focusable<E>
1496where
1497 E: IntoElement,
1498{
1499 type Element = E::Element;
1500
1501 fn element_id(&self) -> Option<ElementId> {
1502 self.element.element_id()
1503 }
1504
1505 fn into_element(self) -> Self::Element {
1506 self.element.into_element()
1507 }
1508}
1509
1510impl<E> ParentElement for Focusable<E>
1511where
1512 E: ParentElement,
1513{
1514 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
1515 self.element.children_mut()
1516 }
1517}
1518
1519pub struct Stateful<E> {
1520 element: E,
1521}
1522
1523impl<E> Styled for Stateful<E>
1524where
1525 E: Styled,
1526{
1527 fn style(&mut self) -> &mut StyleRefinement {
1528 self.element.style()
1529 }
1530}
1531
1532impl<E> StatefulInteractiveElement for Stateful<E>
1533where
1534 E: Element,
1535 Self: InteractiveElement,
1536{
1537}
1538
1539impl<E> InteractiveElement for Stateful<E>
1540where
1541 E: InteractiveElement,
1542{
1543 fn interactivity(&mut self) -> &mut Interactivity {
1544 self.element.interactivity()
1545 }
1546}
1547
1548impl<E: FocusableElement> FocusableElement for Stateful<E> {}
1549
1550impl<E> Element for Stateful<E>
1551where
1552 E: Element,
1553{
1554 type State = E::State;
1555
1556 fn layout(
1557 &mut self,
1558 state: Option<Self::State>,
1559 cx: &mut WindowContext,
1560 ) -> (LayoutId, Self::State) {
1561 self.element.layout(state, cx)
1562 }
1563
1564 fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
1565 self.element.paint(bounds, state, cx)
1566 }
1567}
1568
1569impl<E> IntoElement for Stateful<E>
1570where
1571 E: Element,
1572{
1573 type Element = Self;
1574
1575 fn element_id(&self) -> Option<ElementId> {
1576 self.element.element_id()
1577 }
1578
1579 fn into_element(self) -> Self::Element {
1580 self
1581 }
1582}
1583
1584impl<E> ParentElement for Stateful<E>
1585where
1586 E: ParentElement,
1587{
1588 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
1589 self.element.children_mut()
1590 }
1591}
1592
1593#[derive(Default)]
1594struct ScrollHandleState {
1595 // not great to have the nested rc's...
1596 offset: Rc<RefCell<Point<Pixels>>>,
1597 bounds: Bounds<Pixels>,
1598 child_bounds: Vec<Bounds<Pixels>>,
1599 requested_scroll_top: Option<(usize, Pixels)>,
1600 overflow: Point<Overflow>,
1601}
1602
1603#[derive(Clone)]
1604pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
1605
1606impl ScrollHandle {
1607 pub fn new() -> Self {
1608 Self(Rc::default())
1609 }
1610
1611 pub fn offset(&self) -> Point<Pixels> {
1612 self.0.borrow().offset.borrow().clone()
1613 }
1614
1615 pub fn top_item(&self) -> usize {
1616 let state = self.0.borrow();
1617 let top = state.bounds.top() - state.offset.borrow().y;
1618
1619 match state.child_bounds.binary_search_by(|bounds| {
1620 if top < bounds.top() {
1621 Ordering::Greater
1622 } else if top > bounds.bottom() {
1623 Ordering::Less
1624 } else {
1625 Ordering::Equal
1626 }
1627 }) {
1628 Ok(ix) => ix,
1629 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
1630 }
1631 }
1632
1633 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
1634 self.0.borrow().child_bounds.get(ix).cloned()
1635 }
1636
1637 /// scroll_to_item scrolls the minimal amount to ensure that the item is
1638 /// fully visible
1639 pub fn scroll_to_item(&self, ix: usize) {
1640 let state = self.0.borrow();
1641
1642 let Some(bounds) = state.child_bounds.get(ix) else {
1643 return;
1644 };
1645
1646 let mut scroll_offset = state.offset.borrow_mut();
1647
1648 if state.overflow.y == Overflow::Scroll {
1649 if bounds.top() + scroll_offset.y < state.bounds.top() {
1650 scroll_offset.y = state.bounds.top() - bounds.top();
1651 } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
1652 scroll_offset.y = state.bounds.bottom() - bounds.bottom();
1653 }
1654 }
1655
1656 if state.overflow.x == Overflow::Scroll {
1657 if bounds.left() + scroll_offset.x < state.bounds.left() {
1658 scroll_offset.x = state.bounds.left() - bounds.left();
1659 } else if bounds.right() + scroll_offset.x > state.bounds.right() {
1660 scroll_offset.x = state.bounds.right() - bounds.right();
1661 }
1662 }
1663 }
1664
1665 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
1666 let ix = self.top_item();
1667 let state = self.0.borrow();
1668
1669 if let Some(child_bounds) = state.child_bounds.get(ix) {
1670 (
1671 ix,
1672 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
1673 )
1674 } else {
1675 (ix, px(0.))
1676 }
1677 }
1678
1679 pub fn set_logical_scroll_top(&self, ix: usize, px: Pixels) {
1680 self.0.borrow_mut().requested_scroll_top = Some((ix, px));
1681 }
1682}