1//! Div is the central, reusable element that most GPUI trees will be built from.
2//! It functions as a container for other elements, and provides a number of
3//! useful features for laying out and styling its children as well as binding
4//! mouse events and action handlers. It is meant to be similar to the HTML `<div>`
5//! element, but for GPUI.
6//!
7//! # Build your own div
8//!
9//! GPUI does not directly provide APIs for stateful, multi step events like `click`
10//! and `drag`. We want GPUI users to be able to build their own abstractions for
11//! their own needs. However, as a UI framework, we're also obliged to provide some
12//! building blocks to make the process of building your own elements easier.
13//! For this we have the [`Interactivity`] and the [`StyleRefinement`] structs, as well
14//! as several associated traits. Together, these provide the full suite of Dom-like events
15//! and Tailwind-like styling that you can use to build your own custom elements. Div is
16//! constructed by combining these two systems into an all-in-one element.
17
18use crate::{
19 point, px, size, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, Bounds,
20 ClickEvent, DispatchPhase, Element, ElementContext, ElementId, FocusHandle, Global,
21 IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton,
22 MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render,
23 ScrollWheelEvent, SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task,
24 View, Visibility, WindowContext,
25};
26
27use collections::HashMap;
28use refineable::Refineable;
29use smallvec::SmallVec;
30use std::{
31 any::{Any, TypeId},
32 cell::RefCell,
33 cmp::Ordering,
34 fmt::Debug,
35 marker::PhantomData,
36 mem,
37 ops::DerefMut,
38 rc::Rc,
39 time::Duration,
40};
41use taffy::style::Overflow;
42use util::ResultExt;
43
44const DRAG_THRESHOLD: f64 = 2.;
45pub(crate) const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
46
47/// The styling information for a given group.
48pub struct GroupStyle {
49 /// The identifier for this group.
50 pub group: SharedString,
51
52 /// The specific style refinement that this group would apply
53 /// to its children.
54 pub style: Box<StyleRefinement>,
55}
56
57/// An event for when a drag is moving over this element, with the given state type.
58pub struct DragMoveEvent<T> {
59 /// The mouse move event that triggered this drag move event.
60 pub event: MouseMoveEvent,
61
62 /// The bounds of this element.
63 pub bounds: Bounds<Pixels>,
64 drag: PhantomData<T>,
65}
66
67impl<T: 'static> DragMoveEvent<T> {
68 /// Returns the drag state for this event.
69 pub fn drag<'b>(&self, cx: &'b AppContext) -> &'b T {
70 cx.active_drag
71 .as_ref()
72 .and_then(|drag| drag.value.downcast_ref::<T>())
73 .expect("DragMoveEvent is only valid when the stored active drag is of the same type.")
74 }
75}
76
77impl Interactivity {
78 /// Bind the given callback to the mouse down event for the given mouse button, during the bubble phase
79 /// The imperative API equivalent of [`InteractiveElement::on_mouse_down`]
80 ///
81 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to the view state from this callback.
82 pub fn on_mouse_down(
83 &mut self,
84 button: MouseButton,
85 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
86 ) {
87 self.mouse_down_listeners
88 .push(Box::new(move |event, bounds, phase, cx| {
89 if phase == DispatchPhase::Bubble
90 && event.button == button
91 && bounds.visibly_contains(&event.position, cx)
92 {
93 (listener)(event, cx)
94 }
95 }));
96 }
97
98 /// Bind the given callback to the mouse down event for any button, during the capture phase
99 /// The imperative API equivalent of [`InteractiveElement::capture_any_mouse_down`]
100 ///
101 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
102 pub fn capture_any_mouse_down(
103 &mut self,
104 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
105 ) {
106 self.mouse_down_listeners
107 .push(Box::new(move |event, bounds, phase, cx| {
108 if phase == DispatchPhase::Capture && bounds.visibly_contains(&event.position, cx) {
109 (listener)(event, cx)
110 }
111 }));
112 }
113
114 /// Bind the given callback to the mouse down event for any button, during the bubble phase
115 /// the imperative API equivalent to [`InteractiveElement::on_any_mouse_down`]
116 ///
117 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
118 pub fn on_any_mouse_down(
119 &mut self,
120 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
121 ) {
122 self.mouse_down_listeners
123 .push(Box::new(move |event, bounds, phase, cx| {
124 if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
125 (listener)(event, cx)
126 }
127 }));
128 }
129
130 /// Bind the given callback to the mouse up event for the given button, during the bubble phase
131 /// the imperative API equivalent to [`InteractiveElement::on_mouse_up`]
132 ///
133 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
134 pub fn on_mouse_up(
135 &mut self,
136 button: MouseButton,
137 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
138 ) {
139 self.mouse_up_listeners
140 .push(Box::new(move |event, bounds, phase, cx| {
141 if phase == DispatchPhase::Bubble
142 && event.button == button
143 && bounds.visibly_contains(&event.position, cx)
144 {
145 (listener)(event, cx)
146 }
147 }));
148 }
149
150 /// Bind the given callback to the mouse up event for any button, during the capture phase
151 /// the imperative API equivalent to [`InteractiveElement::capture_any_mouse_up`]
152 ///
153 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
154 pub fn capture_any_mouse_up(
155 &mut self,
156 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
157 ) {
158 self.mouse_up_listeners
159 .push(Box::new(move |event, bounds, phase, cx| {
160 if phase == DispatchPhase::Capture && bounds.visibly_contains(&event.position, cx) {
161 (listener)(event, cx)
162 }
163 }));
164 }
165
166 /// Bind the given callback to the mouse up event for any button, during the bubble phase
167 /// the imperative API equivalent to [`Interactivity::on_any_mouse_up`]
168 ///
169 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
170 pub fn on_any_mouse_up(
171 &mut self,
172 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
173 ) {
174 self.mouse_up_listeners
175 .push(Box::new(move |event, bounds, phase, cx| {
176 if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
177 (listener)(event, cx)
178 }
179 }));
180 }
181
182 /// Bind the given callback to the mouse down event, on any button, during the capture phase,
183 /// when the mouse is outside of the bounds of this element.
184 /// The imperative API equivalent to [`InteractiveElement::on_mouse_down_out`]
185 ///
186 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
187 pub fn on_mouse_down_out(
188 &mut self,
189 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
190 ) {
191 self.mouse_down_listeners
192 .push(Box::new(move |event, bounds, phase, cx| {
193 if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx)
194 {
195 (listener)(event, cx)
196 }
197 }));
198 }
199
200 /// Bind the given callback to the mouse up event, for the given button, during the capture phase,
201 /// when the mouse is outside of the bounds of this element.
202 /// The imperative API equivalent to [`InteractiveElement::on_mouse_up_out`]
203 ///
204 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
205 pub fn on_mouse_up_out(
206 &mut self,
207 button: MouseButton,
208 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
209 ) {
210 self.mouse_up_listeners
211 .push(Box::new(move |event, bounds, phase, cx| {
212 if phase == DispatchPhase::Capture
213 && event.button == button
214 && !bounds.visibly_contains(&event.position, cx)
215 {
216 (listener)(event, cx);
217 }
218 }));
219 }
220
221 /// Bind the given callback to the mouse move event, during the bubble phase
222 /// The imperative API equivalent to [`InteractiveElement::on_mouse_move`]
223 ///
224 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
225 pub fn on_mouse_move(
226 &mut self,
227 listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static,
228 ) {
229 self.mouse_move_listeners
230 .push(Box::new(move |event, bounds, phase, cx| {
231 if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
232 (listener)(event, cx);
233 }
234 }));
235 }
236
237 /// Bind the given callback to the mouse drag event of the given type. Note that this
238 /// will be called for all move events, inside or outside of this element, as long as the
239 /// drag was started with this element under the mouse. Useful for implementing draggable
240 /// UIs that don't conform to a drag and drop style interaction, like resizing.
241 /// The imperative API equivalent to [`InteractiveElement::on_drag_move`]
242 ///
243 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
244 pub fn on_drag_move<T>(
245 &mut self,
246 listener: impl Fn(&DragMoveEvent<T>, &mut WindowContext) + 'static,
247 ) where
248 T: 'static,
249 {
250 self.mouse_move_listeners
251 .push(Box::new(move |event, bounds, phase, cx| {
252 if phase == DispatchPhase::Capture
253 && cx
254 .active_drag
255 .as_ref()
256 .is_some_and(|drag| drag.value.as_ref().type_id() == TypeId::of::<T>())
257 {
258 (listener)(
259 &DragMoveEvent {
260 event: event.clone(),
261 bounds: bounds.bounds,
262 drag: PhantomData,
263 },
264 cx,
265 );
266 }
267 }));
268 }
269
270 /// Bind the given callback to scroll wheel events during the bubble phase
271 /// The imperative API equivalent to [`InteractiveElement::on_scroll_wheel`]
272 ///
273 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
274 pub fn on_scroll_wheel(
275 &mut self,
276 listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static,
277 ) {
278 self.scroll_wheel_listeners
279 .push(Box::new(move |event, bounds, phase, cx| {
280 if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
281 (listener)(event, cx);
282 }
283 }));
284 }
285
286 /// Bind the given callback to an action dispatch during the capture phase
287 /// The imperative API equivalent to [`InteractiveElement::capture_action`]
288 ///
289 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
290 pub fn capture_action<A: Action>(
291 &mut self,
292 listener: impl Fn(&A, &mut WindowContext) + 'static,
293 ) {
294 self.action_listeners.push((
295 TypeId::of::<A>(),
296 Box::new(move |action, phase, cx| {
297 let action = action.downcast_ref().unwrap();
298 if phase == DispatchPhase::Capture {
299 (listener)(action, cx)
300 }
301 }),
302 ));
303 }
304
305 /// Bind the given callback to an action dispatch during the bubble phase
306 /// The imperative API equivalent to [`InteractiveElement::on_action`]
307 ///
308 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
309 pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) {
310 self.action_listeners.push((
311 TypeId::of::<A>(),
312 Box::new(move |action, phase, cx| {
313 let action = action.downcast_ref().unwrap();
314 if phase == DispatchPhase::Bubble {
315 (listener)(action, cx)
316 }
317 }),
318 ));
319 }
320
321 /// Bind the given callback to an action dispatch, based on a dynamic action parameter
322 /// instead of a type parameter. Useful for component libraries that want to expose
323 /// action bindings to their users.
324 /// The imperative API equivalent to [`InteractiveElement::on_boxed_action`]
325 ///
326 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
327 pub fn on_boxed_action(
328 &mut self,
329 action: &dyn Action,
330 listener: impl Fn(&Box<dyn Action>, &mut WindowContext) + 'static,
331 ) {
332 let action = action.boxed_clone();
333 self.action_listeners.push((
334 (*action).type_id(),
335 Box::new(move |_, phase, cx| {
336 if phase == DispatchPhase::Bubble {
337 (listener)(&action, cx)
338 }
339 }),
340 ));
341 }
342
343 /// Bind the given callback to key down events during the bubble phase
344 /// The imperative API equivalent to [`InteractiveElement::on_key_down`]
345 ///
346 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
347 pub fn on_key_down(&mut self, listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static) {
348 self.key_down_listeners
349 .push(Box::new(move |event, phase, cx| {
350 if phase == DispatchPhase::Bubble {
351 (listener)(event, cx)
352 }
353 }));
354 }
355
356 /// Bind the given callback to key down events during the capture phase
357 /// The imperative API equivalent to [`InteractiveElement::capture_key_down`]
358 ///
359 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
360 pub fn capture_key_down(
361 &mut self,
362 listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
363 ) {
364 self.key_down_listeners
365 .push(Box::new(move |event, phase, cx| {
366 if phase == DispatchPhase::Capture {
367 listener(event, cx)
368 }
369 }));
370 }
371
372 /// Bind the given callback to key up events during the bubble phase
373 /// The imperative API equivalent to [`InteractiveElement::on_key_up`]
374 ///
375 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
376 pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) {
377 self.key_up_listeners
378 .push(Box::new(move |event, phase, cx| {
379 if phase == DispatchPhase::Bubble {
380 listener(event, cx)
381 }
382 }));
383 }
384
385 /// Bind the given callback to key up events during the capture phase
386 /// The imperative API equivalent to [`InteractiveElement::on_key_up`]
387 ///
388 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
389 pub fn capture_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) {
390 self.key_up_listeners
391 .push(Box::new(move |event, phase, cx| {
392 if phase == DispatchPhase::Capture {
393 listener(event, cx)
394 }
395 }));
396 }
397
398 /// Bind the given callback to drop events of the given type, whether or not the drag started on this element
399 /// The imperative API equivalent to [`InteractiveElement::on_drop`]
400 ///
401 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
402 pub fn on_drop<T: 'static>(&mut self, listener: impl Fn(&T, &mut WindowContext) + 'static) {
403 self.drop_listeners.push((
404 TypeId::of::<T>(),
405 Box::new(move |dragged_value, cx| {
406 listener(dragged_value.downcast_ref().unwrap(), cx);
407 }),
408 ));
409 }
410
411 /// Use the given predicate to determine whether or not a drop event should be dispatched to this element
412 /// The imperative API equivalent to [`InteractiveElement::can_drop`]
413 pub fn can_drop(&mut self, predicate: impl Fn(&dyn Any, &mut WindowContext) -> bool + 'static) {
414 self.can_drop_predicate = Some(Box::new(predicate));
415 }
416
417 /// Bind the given callback to click events of this element
418 /// The imperative API equivalent to [`StatefulInteractiveElement::on_click`]
419 ///
420 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
421 pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static)
422 where
423 Self: Sized,
424 {
425 self.click_listeners
426 .push(Box::new(move |event, cx| listener(event, cx)));
427 }
428
429 /// On drag initiation, this callback will be used to create a new view to render the dragged value for a
430 /// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with
431 /// the [`Self::on_drag_move`] API
432 /// The imperative API equivalent to [`StatefulInteractiveElement::on_drag`]
433 ///
434 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
435 pub fn on_drag<T, W>(
436 &mut self,
437 value: T,
438 constructor: impl Fn(&T, &mut WindowContext) -> View<W> + 'static,
439 ) where
440 Self: Sized,
441 T: 'static,
442 W: 'static + Render,
443 {
444 debug_assert!(
445 self.drag_listener.is_none(),
446 "calling on_drag more than once on the same element is not supported"
447 );
448 self.drag_listener = Some((
449 Box::new(value),
450 Box::new(move |value, cx| constructor(value.downcast_ref().unwrap(), cx).into()),
451 ));
452 }
453
454 /// Bind the given callback on the hover start and end events of this element. Note that the boolean
455 /// passed to the callback is true when the hover starts and false when it ends.
456 /// The imperative API equivalent to [`StatefulInteractiveElement::on_drag`]
457 ///
458 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
459 pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static)
460 where
461 Self: Sized,
462 {
463 debug_assert!(
464 self.hover_listener.is_none(),
465 "calling on_hover more than once on the same element is not supported"
466 );
467 self.hover_listener = Some(Box::new(listener));
468 }
469
470 /// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
471 /// The imperative API equivalent to [`InteractiveElement::tooltip`]
472 pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static)
473 where
474 Self: Sized,
475 {
476 debug_assert!(
477 self.tooltip_builder.is_none(),
478 "calling tooltip more than once on the same element is not supported"
479 );
480 self.tooltip_builder = Some(Rc::new(build_tooltip));
481 }
482
483 /// Block the mouse from interacting with this element or any of it's children
484 /// The imperative API equivalent to [`InteractiveElement::block_mouse`]
485 pub fn block_mouse(&mut self) {
486 self.block_mouse = true;
487 }
488}
489
490/// A trait for elements that want to use the standard GPUI event handlers that don't
491/// require any state.
492pub trait InteractiveElement: Sized {
493 /// Retrieve the interactivity state associated with this element
494 fn interactivity(&mut self) -> &mut Interactivity;
495
496 /// Assign this element to a group of elements that can be styled together
497 fn group(mut self, group: impl Into<SharedString>) -> Self {
498 self.interactivity().group = Some(group.into());
499 self
500 }
501
502 /// Assign this elements
503 fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
504 self.interactivity().element_id = Some(id.into());
505
506 Stateful { element: self }
507 }
508
509 /// Track the focus state of the given focus handle on this element.
510 /// If the focus handle is focused by the application, this element will
511 /// apply it's focused styles.
512 fn track_focus(mut self, focus_handle: &FocusHandle) -> Focusable<Self> {
513 self.interactivity().focusable = true;
514 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
515 Focusable { element: self }
516 }
517
518 /// Set the keymap context for this element. This will be used to determine
519 /// which action to dispatch from the keymap.
520 fn key_context<C, E>(mut self, key_context: C) -> Self
521 where
522 C: TryInto<KeyContext, Error = E>,
523 E: Debug,
524 {
525 if let Some(key_context) = key_context.try_into().log_err() {
526 self.interactivity().key_context = Some(key_context);
527 }
528 self
529 }
530
531 /// Apply the given style to this element when the mouse hovers over it
532 fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
533 debug_assert!(
534 self.interactivity().hover_style.is_none(),
535 "hover style already set"
536 );
537 self.interactivity().hover_style = Some(Box::new(f(StyleRefinement::default())));
538 self
539 }
540
541 /// Apply the given style to this element when the mouse hovers over a group member
542 fn group_hover(
543 mut self,
544 group_name: impl Into<SharedString>,
545 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
546 ) -> Self {
547 self.interactivity().group_hover_style = Some(GroupStyle {
548 group: group_name.into(),
549 style: Box::new(f(StyleRefinement::default())),
550 });
551 self
552 }
553
554 /// Bind the given callback to the mouse down event for the given mouse button,
555 /// the fluent API equivalent to [`Interactivity::on_mouse_down`]
556 ///
557 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to the view state from this callback.
558 fn on_mouse_down(
559 mut self,
560 button: MouseButton,
561 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
562 ) -> Self {
563 self.interactivity().on_mouse_down(button, listener);
564 self
565 }
566
567 #[cfg(any(test, feature = "test-support"))]
568 /// Set a key that can be used to look up this element's bounds
569 /// in the [`VisualTestContext::debug_bounds`] map
570 /// This is a noop in release builds
571 fn debug_selector(mut self, f: impl FnOnce() -> String) -> Self {
572 self.interactivity().debug_selector = Some(f());
573 self
574 }
575
576 #[cfg(not(any(test, feature = "test-support")))]
577 /// Set a key that can be used to look up this element's bounds
578 /// in the [`VisualTestContext::debug_bounds`] map
579 /// This is a noop in release builds
580 #[inline]
581 fn debug_selector(self, _: impl FnOnce() -> String) -> Self {
582 self
583 }
584
585 /// Bind the given callback to the mouse down event for any button, during the capture phase
586 /// the fluent API equivalent to [`Interactivity::capture_any_mouse_down`]
587 ///
588 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
589 fn capture_any_mouse_down(
590 mut self,
591 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
592 ) -> Self {
593 self.interactivity().capture_any_mouse_down(listener);
594 self
595 }
596
597 /// Bind the given callback to the mouse down event for any button, during the capture phase
598 /// the fluent API equivalent to [`Interactivity::on_any_mouse_down`]
599 ///
600 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
601 fn on_any_mouse_down(
602 mut self,
603 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
604 ) -> Self {
605 self.interactivity().on_any_mouse_down(listener);
606 self
607 }
608
609 /// Bind the given callback to the mouse up event for the given button, during the bubble phase
610 /// the fluent API equivalent to [`Interactivity::on_mouse_up`]
611 ///
612 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
613 fn on_mouse_up(
614 mut self,
615 button: MouseButton,
616 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
617 ) -> Self {
618 self.interactivity().on_mouse_up(button, listener);
619 self
620 }
621
622 /// Bind the given callback to the mouse up event for any button, during the capture phase
623 /// the fluent API equivalent to [`Interactivity::capture_any_mouse_up`]
624 ///
625 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
626 fn capture_any_mouse_up(
627 mut self,
628 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
629 ) -> Self {
630 self.interactivity().capture_any_mouse_up(listener);
631 self
632 }
633
634 /// Bind the given callback to the mouse down event, on any button, during the capture phase,
635 /// when the mouse is outside of the bounds of this element.
636 /// The fluent API equivalent to [`Interactivity::on_mouse_down_out`]
637 ///
638 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
639 fn on_mouse_down_out(
640 mut self,
641 listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
642 ) -> Self {
643 self.interactivity().on_mouse_down_out(listener);
644 self
645 }
646
647 /// Bind the given callback to the mouse up event, for the given button, during the capture phase,
648 /// when the mouse is outside of the bounds of this element.
649 /// The fluent API equivalent to [`Interactivity::on_mouse_up_out`]
650 ///
651 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
652 fn on_mouse_up_out(
653 mut self,
654 button: MouseButton,
655 listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
656 ) -> Self {
657 self.interactivity().on_mouse_up_out(button, listener);
658 self
659 }
660
661 /// Bind the given callback to the mouse move event, during the bubble phase
662 /// The fluent API equivalent to [`Interactivity::on_mouse_move`]
663 ///
664 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
665 fn on_mouse_move(
666 mut self,
667 listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static,
668 ) -> Self {
669 self.interactivity().on_mouse_move(listener);
670 self
671 }
672
673 /// Bind the given callback to the mouse drag event of the given type. Note that this
674 /// will be called for all move events, inside or outside of this element, as long as the
675 /// drag was started with this element under the mouse. Useful for implementing draggable
676 /// UIs that don't conform to a drag and drop style interaction, like resizing.
677 /// The fluent API equivalent to [`Interactivity::on_drag_move`]
678 ///
679 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
680 fn on_drag_move<T: 'static>(
681 mut self,
682 listener: impl Fn(&DragMoveEvent<T>, &mut WindowContext) + 'static,
683 ) -> Self
684 where
685 T: 'static,
686 {
687 self.interactivity().on_drag_move(listener);
688 self
689 }
690
691 /// Bind the given callback to scroll wheel events during the bubble phase
692 /// The fluent API equivalent to [`Interactivity::on_scroll_wheel`]
693 ///
694 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
695 fn on_scroll_wheel(
696 mut self,
697 listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static,
698 ) -> Self {
699 self.interactivity().on_scroll_wheel(listener);
700 self
701 }
702
703 /// Capture the given action, before normal action dispatch can fire
704 /// The fluent API equivalent to [`Interactivity::on_scroll_wheel`]
705 ///
706 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
707 fn capture_action<A: Action>(
708 mut self,
709 listener: impl Fn(&A, &mut WindowContext) + 'static,
710 ) -> Self {
711 self.interactivity().capture_action(listener);
712 self
713 }
714
715 /// Bind the given callback to an action dispatch during the bubble phase
716 /// The fluent API equivalent to [`Interactivity::on_action`]
717 ///
718 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
719 fn on_action<A: Action>(mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) -> Self {
720 self.interactivity().on_action(listener);
721 self
722 }
723
724 /// Bind the given callback to an action dispatch, based on a dynamic action parameter
725 /// instead of a type parameter. Useful for component libraries that want to expose
726 /// action bindings to their users.
727 /// The fluent API equivalent to [`Interactivity::on_boxed_action`]
728 ///
729 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
730 fn on_boxed_action(
731 mut self,
732 action: &dyn Action,
733 listener: impl Fn(&Box<dyn Action>, &mut WindowContext) + 'static,
734 ) -> Self {
735 self.interactivity().on_boxed_action(action, listener);
736 self
737 }
738
739 /// Bind the given callback to key down events during the bubble phase
740 /// The fluent API equivalent to [`Interactivity::on_key_down`]
741 ///
742 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
743 fn on_key_down(
744 mut self,
745 listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
746 ) -> Self {
747 self.interactivity().on_key_down(listener);
748 self
749 }
750
751 /// Bind the given callback to key down events during the capture phase
752 /// The fluent API equivalent to [`Interactivity::capture_key_down`]
753 ///
754 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
755 fn capture_key_down(
756 mut self,
757 listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
758 ) -> Self {
759 self.interactivity().capture_key_down(listener);
760 self
761 }
762
763 /// Bind the given callback to key up events during the bubble phase
764 /// The fluent API equivalent to [`Interactivity::on_key_up`]
765 ///
766 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
767 fn on_key_up(mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) -> Self {
768 self.interactivity().on_key_up(listener);
769 self
770 }
771
772 /// Bind the given callback to key up events during the capture phase
773 /// The fluent API equivalent to [`Interactivity::capture_key_up`]
774 ///
775 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
776 fn capture_key_up(
777 mut self,
778 listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static,
779 ) -> Self {
780 self.interactivity().capture_key_up(listener);
781 self
782 }
783
784 /// Apply the given style when the given data type is dragged over this element
785 fn drag_over<S: 'static>(
786 mut self,
787 f: impl 'static + Fn(StyleRefinement, &S, &WindowContext) -> StyleRefinement,
788 ) -> Self {
789 self.interactivity().drag_over_styles.push((
790 TypeId::of::<S>(),
791 Box::new(move |currently_dragged: &dyn Any, cx| {
792 f(
793 StyleRefinement::default(),
794 currently_dragged.downcast_ref::<S>().unwrap(),
795 cx,
796 )
797 }),
798 ));
799 self
800 }
801
802 /// Apply the given style when the given data type is dragged over this element's group
803 fn group_drag_over<S: 'static>(
804 mut self,
805 group_name: impl Into<SharedString>,
806 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
807 ) -> Self {
808 self.interactivity().group_drag_over_styles.push((
809 TypeId::of::<S>(),
810 GroupStyle {
811 group: group_name.into(),
812 style: Box::new(f(StyleRefinement::default())),
813 },
814 ));
815 self
816 }
817
818 /// Bind the given callback to drop events of the given type, whether or not the drag started on this element
819 /// The fluent API equivalent to [`Interactivity::on_drop`]
820 ///
821 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
822 fn on_drop<T: 'static>(mut self, listener: impl Fn(&T, &mut WindowContext) + 'static) -> Self {
823 self.interactivity().on_drop(listener);
824 self
825 }
826
827 /// Use the given predicate to determine whether or not a drop event should be dispatched to this element
828 /// The fluent API equivalent to [`Interactivity::can_drop`]
829 fn can_drop(
830 mut self,
831 predicate: impl Fn(&dyn Any, &mut WindowContext) -> bool + 'static,
832 ) -> Self {
833 self.interactivity().can_drop(predicate);
834 self
835 }
836
837 /// Block the mouse from interacting with this element or any of it's children
838 /// The fluent API equivalent to [`Interactivity::block_mouse`]
839 fn block_mouse(mut self) -> Self {
840 self.interactivity().block_mouse();
841 self
842 }
843}
844
845/// A trait for elements that want to use the standard GPUI interactivity features
846/// that require state.
847pub trait StatefulInteractiveElement: InteractiveElement {
848 /// Set this element to focusable.
849 fn focusable(mut self) -> Focusable<Self> {
850 self.interactivity().focusable = true;
851 Focusable { element: self }
852 }
853
854 /// Set the overflow x and y to scroll.
855 fn overflow_scroll(mut self) -> Self {
856 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
857 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
858 self
859 }
860
861 /// Set the overflow x to scroll.
862 fn overflow_x_scroll(mut self) -> Self {
863 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
864 self
865 }
866
867 /// Set the overflow y to scroll.
868 fn overflow_y_scroll(mut self) -> Self {
869 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
870 self
871 }
872
873 /// Track the scroll state of this element with the given handle.
874 fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
875 self.interactivity().scroll_handle = Some(scroll_handle.clone());
876 self
877 }
878
879 /// Set the given styles to be applied when this element is active.
880 fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
881 where
882 Self: Sized,
883 {
884 self.interactivity().active_style = Some(Box::new(f(StyleRefinement::default())));
885 self
886 }
887
888 /// Set the given styles to be applied when this element's group is active.
889 fn group_active(
890 mut self,
891 group_name: impl Into<SharedString>,
892 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
893 ) -> Self
894 where
895 Self: Sized,
896 {
897 self.interactivity().group_active_style = Some(GroupStyle {
898 group: group_name.into(),
899 style: Box::new(f(StyleRefinement::default())),
900 });
901 self
902 }
903
904 /// Bind the given callback to click events of this element
905 /// The fluent API equivalent to [`Interactivity::on_click`]
906 ///
907 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
908 fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self
909 where
910 Self: Sized,
911 {
912 self.interactivity().on_click(listener);
913 self
914 }
915
916 /// On drag initiation, this callback will be used to create a new view to render the dragged value for a
917 /// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with
918 /// the [`Self::on_drag_move`] API
919 /// The fluent API equivalent to [`Interactivity::on_drag`]
920 ///
921 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
922 fn on_drag<T, W>(
923 mut self,
924 value: T,
925 constructor: impl Fn(&T, &mut WindowContext) -> View<W> + 'static,
926 ) -> Self
927 where
928 Self: Sized,
929 T: 'static,
930 W: 'static + Render,
931 {
932 self.interactivity().on_drag(value, constructor);
933 self
934 }
935
936 /// Bind the given callback on the hover start and end events of this element. Note that the boolean
937 /// passed to the callback is true when the hover starts and false when it ends.
938 /// The fluent API equivalent to [`Interactivity::on_hover`]
939 ///
940 /// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
941 fn on_hover(mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static) -> Self
942 where
943 Self: Sized,
944 {
945 self.interactivity().on_hover(listener);
946 self
947 }
948
949 /// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
950 /// The fluent API equivalent to [`Interactivity::tooltip`]
951 fn tooltip(mut self, build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self
952 where
953 Self: Sized,
954 {
955 self.interactivity().tooltip(build_tooltip);
956 self
957 }
958}
959
960/// A trait for providing focus related APIs to interactive elements
961pub trait FocusableElement: InteractiveElement {
962 /// Set the given styles to be applied when this element, specifically, is focused.
963 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
964 where
965 Self: Sized,
966 {
967 self.interactivity().focus_style = Some(Box::new(f(StyleRefinement::default())));
968 self
969 }
970
971 /// Set the given styles to be applied when this element is inside another element that is focused.
972 fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
973 where
974 Self: Sized,
975 {
976 self.interactivity().in_focus_style = Some(Box::new(f(StyleRefinement::default())));
977 self
978 }
979}
980
981pub(crate) type MouseDownListener =
982 Box<dyn Fn(&MouseDownEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
983pub(crate) type MouseUpListener =
984 Box<dyn Fn(&MouseUpEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
985
986pub(crate) type MouseMoveListener =
987 Box<dyn Fn(&MouseMoveEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
988
989pub(crate) type ScrollWheelListener =
990 Box<dyn Fn(&ScrollWheelEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
991
992pub(crate) type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
993
994pub(crate) type DragListener = Box<dyn Fn(&dyn Any, &mut WindowContext) -> AnyView + 'static>;
995
996type DropListener = Box<dyn Fn(&dyn Any, &mut WindowContext) + 'static>;
997
998type CanDropPredicate = Box<dyn Fn(&dyn Any, &mut WindowContext) -> bool + 'static>;
999
1000pub(crate) type TooltipBuilder = Rc<dyn Fn(&mut WindowContext) -> AnyView + 'static>;
1001
1002pub(crate) type KeyDownListener =
1003 Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + 'static>;
1004
1005pub(crate) type KeyUpListener =
1006 Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut WindowContext) + 'static>;
1007
1008pub(crate) type ActionListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
1009
1010/// Construct a new [`Div`] element
1011#[track_caller]
1012pub fn div() -> Div {
1013 #[cfg(debug_assertions)]
1014 let interactivity = Interactivity {
1015 location: Some(*core::panic::Location::caller()),
1016 ..Default::default()
1017 };
1018
1019 #[cfg(not(debug_assertions))]
1020 let interactivity = Interactivity::default();
1021
1022 Div {
1023 interactivity,
1024 children: SmallVec::default(),
1025 }
1026}
1027
1028/// A [`Div`] element, the all-in-one element for building complex UIs in GPUI
1029pub struct Div {
1030 interactivity: Interactivity,
1031 children: SmallVec<[AnyElement; 2]>,
1032}
1033
1034impl Styled for Div {
1035 fn style(&mut self) -> &mut StyleRefinement {
1036 &mut self.interactivity.base_style
1037 }
1038}
1039
1040impl InteractiveElement for Div {
1041 fn interactivity(&mut self) -> &mut Interactivity {
1042 &mut self.interactivity
1043 }
1044}
1045
1046impl ParentElement for Div {
1047 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1048 self.children.extend(elements)
1049 }
1050}
1051
1052impl Element for Div {
1053 type State = DivState;
1054
1055 fn request_layout(
1056 &mut self,
1057 element_state: Option<Self::State>,
1058 cx: &mut ElementContext,
1059 ) -> (LayoutId, Self::State) {
1060 let mut child_layout_ids = SmallVec::new();
1061 let (layout_id, interactive_state) = self.interactivity.layout(
1062 element_state.map(|s| s.interactive_state),
1063 cx,
1064 |style, cx| {
1065 cx.with_text_style(style.text_style().cloned(), |cx| {
1066 child_layout_ids = self
1067 .children
1068 .iter_mut()
1069 .map(|child| child.request_layout(cx))
1070 .collect::<SmallVec<_>>();
1071 cx.request_layout(&style, child_layout_ids.iter().copied())
1072 })
1073 },
1074 );
1075 (
1076 layout_id,
1077 DivState {
1078 interactive_state,
1079 child_layout_ids,
1080 },
1081 )
1082 }
1083
1084 fn paint(
1085 &mut self,
1086 bounds: Bounds<Pixels>,
1087 element_state: &mut Self::State,
1088 cx: &mut ElementContext,
1089 ) {
1090 let mut child_min = point(Pixels::MAX, Pixels::MAX);
1091 let mut child_max = Point::default();
1092 let content_size = if element_state.child_layout_ids.is_empty() {
1093 bounds.size
1094 } else if let Some(scroll_handle) = self.interactivity.scroll_handle.as_ref() {
1095 let mut state = scroll_handle.0.borrow_mut();
1096 state.child_bounds = Vec::with_capacity(element_state.child_layout_ids.len());
1097 state.bounds = bounds;
1098 let requested = state.requested_scroll_top.take();
1099
1100 for (ix, child_layout_id) in element_state.child_layout_ids.iter().enumerate() {
1101 let child_bounds = cx.layout_bounds(*child_layout_id);
1102 child_min = child_min.min(&child_bounds.origin);
1103 child_max = child_max.max(&child_bounds.lower_right());
1104 state.child_bounds.push(child_bounds);
1105
1106 if let Some(requested) = requested.as_ref() {
1107 if requested.0 == ix {
1108 *state.offset.borrow_mut() =
1109 bounds.origin - (child_bounds.origin - point(px(0.), requested.1));
1110 }
1111 }
1112 }
1113 (child_max - child_min).into()
1114 } else {
1115 for child_layout_id in &element_state.child_layout_ids {
1116 let child_bounds = cx.layout_bounds(*child_layout_id);
1117 child_min = child_min.min(&child_bounds.origin);
1118 child_max = child_max.max(&child_bounds.lower_right());
1119 }
1120 (child_max - child_min).into()
1121 };
1122
1123 self.interactivity.paint(
1124 bounds,
1125 content_size,
1126 &mut element_state.interactive_state,
1127 cx,
1128 |_style, scroll_offset, cx| {
1129 cx.with_element_offset(scroll_offset, |cx| {
1130 for child in &mut self.children {
1131 child.paint(cx);
1132 }
1133 })
1134 },
1135 );
1136 }
1137}
1138
1139impl IntoElement for Div {
1140 type Element = Self;
1141
1142 fn element_id(&self) -> Option<ElementId> {
1143 self.interactivity.element_id.clone()
1144 }
1145
1146 fn into_element(self) -> Self::Element {
1147 self
1148 }
1149}
1150
1151/// The state a div needs to keep track of between frames.
1152pub struct DivState {
1153 child_layout_ids: SmallVec<[LayoutId; 2]>,
1154 interactive_state: InteractiveElementState,
1155}
1156
1157impl DivState {
1158 /// Is the div currently being clicked on?
1159 pub fn is_active(&self) -> bool {
1160 self.interactive_state
1161 .pending_mouse_down
1162 .as_ref()
1163 .map_or(false, |pending| pending.borrow().is_some())
1164 }
1165}
1166
1167/// The interactivity struct. Powers all of the general-purpose
1168/// interactivity in the `Div` element.
1169#[derive(Default)]
1170pub struct Interactivity {
1171 /// The element ID of the element
1172 pub element_id: Option<ElementId>,
1173 pub(crate) key_context: Option<KeyContext>,
1174 pub(crate) focusable: bool,
1175 pub(crate) tracked_focus_handle: Option<FocusHandle>,
1176 pub(crate) scroll_handle: Option<ScrollHandle>,
1177 pub(crate) group: Option<SharedString>,
1178 /// The base style of the element, before any modifications are applied
1179 /// by focus, active, etc.
1180 pub base_style: Box<StyleRefinement>,
1181 pub(crate) focus_style: Option<Box<StyleRefinement>>,
1182 pub(crate) in_focus_style: Option<Box<StyleRefinement>>,
1183 pub(crate) hover_style: Option<Box<StyleRefinement>>,
1184 pub(crate) group_hover_style: Option<GroupStyle>,
1185 pub(crate) active_style: Option<Box<StyleRefinement>>,
1186 pub(crate) group_active_style: Option<GroupStyle>,
1187 pub(crate) drag_over_styles: Vec<(
1188 TypeId,
1189 Box<dyn Fn(&dyn Any, &mut WindowContext) -> StyleRefinement>,
1190 )>,
1191 pub(crate) group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
1192 pub(crate) mouse_down_listeners: Vec<MouseDownListener>,
1193 pub(crate) mouse_up_listeners: Vec<MouseUpListener>,
1194 pub(crate) mouse_move_listeners: Vec<MouseMoveListener>,
1195 pub(crate) scroll_wheel_listeners: Vec<ScrollWheelListener>,
1196 pub(crate) key_down_listeners: Vec<KeyDownListener>,
1197 pub(crate) key_up_listeners: Vec<KeyUpListener>,
1198 pub(crate) action_listeners: Vec<(TypeId, ActionListener)>,
1199 pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
1200 pub(crate) can_drop_predicate: Option<CanDropPredicate>,
1201 pub(crate) click_listeners: Vec<ClickListener>,
1202 pub(crate) drag_listener: Option<(Box<dyn Any>, DragListener)>,
1203 pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>,
1204 pub(crate) tooltip_builder: Option<TooltipBuilder>,
1205 pub(crate) block_mouse: bool,
1206
1207 #[cfg(debug_assertions)]
1208 pub(crate) location: Option<core::panic::Location<'static>>,
1209
1210 #[cfg(any(test, feature = "test-support"))]
1211 pub(crate) debug_selector: Option<String>,
1212}
1213
1214/// The bounds and depth of an element in the computed element tree.
1215#[derive(Clone, Debug)]
1216pub struct InteractiveBounds {
1217 /// The 2D bounds of the element
1218 pub bounds: Bounds<Pixels>,
1219 /// The 'stacking order', or depth, for this element
1220 pub stacking_order: StackingOrder,
1221}
1222
1223impl InteractiveBounds {
1224 /// Checks whether this point was inside these bounds, and that these bounds where the topmost layer
1225 pub fn visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
1226 self.bounds.contains(point) && cx.was_top_layer(point, &self.stacking_order)
1227 }
1228
1229 /// Checks whether this point was inside these bounds, and that these bounds where the topmost layer
1230 /// under an active drag
1231 pub fn drag_target_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
1232 self.bounds.contains(point)
1233 && cx.was_top_layer_under_active_drag(point, &self.stacking_order)
1234 }
1235}
1236
1237impl Interactivity {
1238 /// Layout this element according to this interactivity state's configured styles
1239 pub fn layout(
1240 &mut self,
1241 element_state: Option<InteractiveElementState>,
1242 cx: &mut ElementContext,
1243 f: impl FnOnce(Style, &mut ElementContext) -> LayoutId,
1244 ) -> (LayoutId, InteractiveElementState) {
1245 let mut element_state = element_state.unwrap_or_default();
1246
1247 if cx.has_active_drag() {
1248 if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() {
1249 *pending_mouse_down.borrow_mut() = None;
1250 }
1251 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
1252 *clicked_state.borrow_mut() = ElementClickedState::default();
1253 }
1254 }
1255
1256 // Ensure we store a focus handle in our element state if we're focusable.
1257 // If there's an explicit focus handle we're tracking, use that. Otherwise
1258 // create a new handle and store it in the element state, which lives for as
1259 // as frames contain an element with this id.
1260 if self.focusable {
1261 element_state.focus_handle.get_or_insert_with(|| {
1262 self.tracked_focus_handle
1263 .clone()
1264 .unwrap_or_else(|| cx.focus_handle())
1265 });
1266 }
1267
1268 if let Some(scroll_handle) = self.scroll_handle.as_ref() {
1269 element_state.scroll_offset = Some(scroll_handle.0.borrow().offset.clone());
1270 }
1271
1272 let style = self.compute_style(None, &mut element_state, cx);
1273 let layout_id = f(style, cx);
1274 (layout_id, element_state)
1275 }
1276
1277 /// Paint this element according to this interactivity state's configured styles
1278 /// and bind the element's mouse and keyboard events.
1279 ///
1280 /// content_size is the size of the content of the element, which may be larger than the
1281 /// element's bounds if the element is scrollable.
1282 ///
1283 /// the final computed style will be passed to the provided function, along
1284 /// with the current scroll offset
1285 pub fn paint(
1286 &mut self,
1287 bounds: Bounds<Pixels>,
1288 content_size: Size<Pixels>,
1289 element_state: &mut InteractiveElementState,
1290 cx: &mut ElementContext,
1291 f: impl FnOnce(&Style, Point<Pixels>, &mut ElementContext),
1292 ) {
1293 let style = self.compute_style(Some(bounds), element_state, cx);
1294 let z_index = style.z_index.unwrap_or(0);
1295
1296 #[cfg(any(feature = "test-support", test))]
1297 if let Some(debug_selector) = &self.debug_selector {
1298 cx.window
1299 .next_frame
1300 .debug_bounds
1301 .insert(debug_selector.clone(), bounds);
1302 }
1303
1304 let paint_hover_group_handler = |cx: &mut ElementContext| {
1305 let hover_group_bounds = self
1306 .group_hover_style
1307 .as_ref()
1308 .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
1309
1310 if let Some(group_bounds) = hover_group_bounds {
1311 let hovered = group_bounds.contains(&cx.mouse_position());
1312 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
1313 if phase == DispatchPhase::Capture
1314 && group_bounds.contains(&event.position) != hovered
1315 {
1316 cx.refresh();
1317 }
1318 });
1319 }
1320 };
1321
1322 if style.visibility == Visibility::Hidden {
1323 cx.with_z_index(z_index, |cx| paint_hover_group_handler(cx));
1324 return;
1325 }
1326
1327 cx.with_z_index(z_index, |cx| {
1328 style.paint(bounds, cx, |cx: &mut ElementContext| {
1329 cx.with_text_style(style.text_style().cloned(), |cx| {
1330 cx.with_content_mask(style.overflow_mask(bounds, cx.rem_size()), |cx| {
1331 #[cfg(debug_assertions)]
1332 if self.element_id.is_some()
1333 && (style.debug
1334 || style.debug_below
1335 || cx.has_global::<crate::DebugBelow>())
1336 && bounds.contains(&cx.mouse_position())
1337 {
1338 const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
1339 let element_id = format!("{:?}", self.element_id.as_ref().unwrap());
1340 let str_len = element_id.len();
1341
1342 let render_debug_text = |cx: &mut ElementContext| {
1343 if let Some(text) = cx
1344 .text_system()
1345 .shape_text(
1346 element_id.into(),
1347 FONT_SIZE,
1348 &[cx.text_style().to_run(str_len)],
1349 None,
1350 )
1351 .ok()
1352 .and_then(|mut text| text.pop())
1353 {
1354 text.paint(bounds.origin, FONT_SIZE, cx).ok();
1355
1356 let text_bounds = crate::Bounds {
1357 origin: bounds.origin,
1358 size: text.size(FONT_SIZE),
1359 };
1360 if self.location.is_some()
1361 && text_bounds.contains(&cx.mouse_position())
1362 && cx.modifiers().command
1363 {
1364 let command_held = cx.modifiers().command;
1365 cx.on_key_event({
1366 move |e: &crate::ModifiersChangedEvent, _phase, cx| {
1367 if e.modifiers.command != command_held
1368 && text_bounds.contains(&cx.mouse_position())
1369 {
1370 cx.refresh();
1371 }
1372 }
1373 });
1374
1375 let hovered = bounds.contains(&cx.mouse_position());
1376 cx.on_mouse_event(
1377 move |event: &MouseMoveEvent, phase, cx| {
1378 if phase == DispatchPhase::Capture
1379 && bounds.contains(&event.position) != hovered
1380 {
1381 cx.refresh();
1382 }
1383 },
1384 );
1385
1386 cx.on_mouse_event({
1387 let location = self.location.unwrap();
1388 move |e: &crate::MouseDownEvent, phase, cx| {
1389 if text_bounds.contains(&e.position)
1390 && phase.capture()
1391 {
1392 cx.stop_propagation();
1393 let Ok(dir) = std::env::current_dir() else {
1394 return;
1395 };
1396
1397 eprintln!(
1398 "This element was created at:\n{}:{}:{}",
1399 dir.join(location.file()).to_string_lossy(),
1400 location.line(),
1401 location.column()
1402 );
1403 }
1404 }
1405 });
1406 cx.paint_quad(crate::outline(
1407 crate::Bounds {
1408 origin: bounds.origin
1409 + crate::point(
1410 crate::px(0.),
1411 FONT_SIZE - px(2.),
1412 ),
1413 size: crate::Size {
1414 width: text_bounds.size.width,
1415 height: crate::px(1.),
1416 },
1417 },
1418 crate::red(),
1419 ))
1420 }
1421 }
1422 };
1423
1424 cx.with_z_index(1, |cx| {
1425 cx.with_text_style(
1426 Some(crate::TextStyleRefinement {
1427 color: Some(crate::red()),
1428 line_height: Some(FONT_SIZE.into()),
1429 background_color: Some(crate::white()),
1430 ..Default::default()
1431 }),
1432 render_debug_text,
1433 )
1434 });
1435 }
1436
1437 let interactive_bounds = InteractiveBounds {
1438 bounds: bounds.intersect(&cx.content_mask().bounds),
1439 stacking_order: cx.stacking_order().clone(),
1440 };
1441
1442 if self.block_mouse
1443 || style.background.as_ref().is_some_and(|fill| {
1444 fill.color().is_some_and(|color| !color.is_transparent())
1445 })
1446 {
1447 cx.add_opaque_layer(interactive_bounds.bounds);
1448 }
1449
1450 if !cx.has_active_drag() {
1451 if let Some(mouse_cursor) = style.mouse_cursor {
1452 let mouse_position = &cx.mouse_position();
1453 let hovered =
1454 interactive_bounds.visibly_contains(mouse_position, cx);
1455 if hovered {
1456 cx.set_cursor_style(mouse_cursor);
1457 }
1458 }
1459 }
1460
1461 // If this element can be focused, register a mouse down listener
1462 // that will automatically transfer focus when hitting the element.
1463 // This behavior can be suppressed by using `cx.prevent_default()`.
1464 if let Some(focus_handle) = element_state.focus_handle.clone() {
1465 cx.on_mouse_event({
1466 let interactive_bounds = interactive_bounds.clone();
1467 move |event: &MouseDownEvent, phase, cx| {
1468 if phase == DispatchPhase::Bubble
1469 && !cx.default_prevented()
1470 && interactive_bounds.visibly_contains(&event.position, cx)
1471 {
1472 cx.focus(&focus_handle);
1473 // If there is a parent that is also focusable, prevent it
1474 // from transferring focus because we already did so.
1475 cx.prevent_default();
1476 }
1477 }
1478 });
1479 }
1480
1481 for listener in self.mouse_down_listeners.drain(..) {
1482 let interactive_bounds = interactive_bounds.clone();
1483 cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
1484 listener(event, &interactive_bounds, phase, cx);
1485 })
1486 }
1487
1488 for listener in self.mouse_up_listeners.drain(..) {
1489 let interactive_bounds = interactive_bounds.clone();
1490 cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
1491 listener(event, &interactive_bounds, phase, cx);
1492 })
1493 }
1494
1495 for listener in self.mouse_move_listeners.drain(..) {
1496 let interactive_bounds = interactive_bounds.clone();
1497 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
1498 listener(event, &interactive_bounds, phase, cx);
1499 })
1500 }
1501
1502 for listener in self.scroll_wheel_listeners.drain(..) {
1503 let interactive_bounds = interactive_bounds.clone();
1504 cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
1505 listener(event, &interactive_bounds, phase, cx);
1506 })
1507 }
1508
1509 paint_hover_group_handler(cx);
1510
1511 if self.hover_style.is_some()
1512 || self.base_style.mouse_cursor.is_some()
1513 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
1514 {
1515 let bounds = bounds.intersect(&cx.content_mask().bounds);
1516 let hovered = bounds.contains(&cx.mouse_position());
1517 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
1518 if phase == DispatchPhase::Capture
1519 && bounds.contains(&event.position) != hovered
1520 {
1521 cx.refresh();
1522 }
1523 });
1524 }
1525
1526 let mut drag_listener = mem::take(&mut self.drag_listener);
1527 let drop_listeners = mem::take(&mut self.drop_listeners);
1528 let click_listeners = mem::take(&mut self.click_listeners);
1529 let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
1530
1531 if !drop_listeners.is_empty() {
1532 cx.on_mouse_event({
1533 let interactive_bounds = interactive_bounds.clone();
1534 move |event: &MouseUpEvent, phase, cx| {
1535 if let Some(drag) = &cx.active_drag {
1536 if phase == DispatchPhase::Bubble
1537 && interactive_bounds
1538 .drag_target_contains(&event.position, cx)
1539 {
1540 let drag_state_type = drag.value.as_ref().type_id();
1541 for (drop_state_type, listener) in &drop_listeners {
1542 if *drop_state_type == drag_state_type {
1543 let drag = cx.active_drag.take().expect(
1544 "checked for type drag state type above",
1545 );
1546
1547 let mut can_drop = true;
1548 if let Some(predicate) = &can_drop_predicate {
1549 can_drop = predicate(
1550 drag.value.as_ref(),
1551 cx.deref_mut(),
1552 );
1553 }
1554
1555 if can_drop {
1556 listener(
1557 drag.value.as_ref(),
1558 cx.deref_mut(),
1559 );
1560 cx.refresh();
1561 cx.stop_propagation();
1562 }
1563 }
1564 }
1565 }
1566 }
1567 }
1568 });
1569 }
1570
1571 if !click_listeners.is_empty() || drag_listener.is_some() {
1572 let pending_mouse_down = element_state
1573 .pending_mouse_down
1574 .get_or_insert_with(Default::default)
1575 .clone();
1576
1577 let clicked_state = element_state
1578 .clicked_state
1579 .get_or_insert_with(Default::default)
1580 .clone();
1581
1582 cx.on_mouse_event({
1583 let interactive_bounds = interactive_bounds.clone();
1584 let pending_mouse_down = pending_mouse_down.clone();
1585 move |event: &MouseDownEvent, phase, cx| {
1586 if phase == DispatchPhase::Bubble
1587 && event.button == MouseButton::Left
1588 && interactive_bounds.visibly_contains(&event.position, cx)
1589 {
1590 *pending_mouse_down.borrow_mut() = Some(event.clone());
1591 cx.refresh();
1592 }
1593 }
1594 });
1595
1596 cx.on_mouse_event({
1597 let pending_mouse_down = pending_mouse_down.clone();
1598 move |event: &MouseMoveEvent, phase, cx| {
1599 if phase == DispatchPhase::Capture {
1600 return;
1601 }
1602
1603 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
1604 if let Some(mouse_down) = pending_mouse_down.clone() {
1605 if !cx.has_active_drag()
1606 && (event.position - mouse_down.position).magnitude()
1607 > DRAG_THRESHOLD
1608 {
1609 if let Some((drag_value, drag_listener)) =
1610 drag_listener.take()
1611 {
1612 *clicked_state.borrow_mut() =
1613 ElementClickedState::default();
1614 let cursor_offset = event.position - bounds.origin;
1615 let drag = (drag_listener)(drag_value.as_ref(), cx);
1616 cx.active_drag = Some(AnyDrag {
1617 view: drag,
1618 value: drag_value,
1619 cursor_offset,
1620 });
1621 pending_mouse_down.take();
1622 cx.refresh();
1623 cx.stop_propagation();
1624 }
1625 }
1626 }
1627 }
1628 });
1629
1630 cx.on_mouse_event({
1631 let interactive_bounds = interactive_bounds.clone();
1632 let mut captured_mouse_down = None;
1633 move |event: &MouseUpEvent, phase, cx| match phase {
1634 // Clear the pending mouse down during the capture phase,
1635 // so that it happens even if another event handler stops
1636 // propagation.
1637 DispatchPhase::Capture => {
1638 let mut pending_mouse_down =
1639 pending_mouse_down.borrow_mut();
1640 if pending_mouse_down.is_some() {
1641 captured_mouse_down = pending_mouse_down.take();
1642 cx.refresh();
1643 }
1644 }
1645 // Fire click handlers during the bubble phase.
1646 DispatchPhase::Bubble => {
1647 if let Some(mouse_down) = captured_mouse_down.take() {
1648 if interactive_bounds
1649 .visibly_contains(&event.position, cx)
1650 {
1651 let mouse_click = ClickEvent {
1652 down: mouse_down,
1653 up: event.clone(),
1654 };
1655 for listener in &click_listeners {
1656 listener(&mouse_click, cx);
1657 }
1658 }
1659 }
1660 }
1661 }
1662 });
1663 }
1664
1665 if let Some(hover_listener) = self.hover_listener.take() {
1666 let was_hovered = element_state
1667 .hover_state
1668 .get_or_insert_with(Default::default)
1669 .clone();
1670 let has_mouse_down = element_state
1671 .pending_mouse_down
1672 .get_or_insert_with(Default::default)
1673 .clone();
1674 let interactive_bounds = interactive_bounds.clone();
1675
1676 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
1677 if phase != DispatchPhase::Bubble {
1678 return;
1679 }
1680 let is_hovered = interactive_bounds
1681 .visibly_contains(&event.position, cx)
1682 && has_mouse_down.borrow().is_none()
1683 && !cx.has_active_drag();
1684 let mut was_hovered = was_hovered.borrow_mut();
1685
1686 if is_hovered != *was_hovered {
1687 *was_hovered = is_hovered;
1688 drop(was_hovered);
1689
1690 hover_listener(&is_hovered, cx.deref_mut());
1691 }
1692 });
1693 }
1694
1695 if let Some(tooltip_builder) = self.tooltip_builder.take() {
1696 let active_tooltip = element_state
1697 .active_tooltip
1698 .get_or_insert_with(Default::default)
1699 .clone();
1700 let pending_mouse_down = element_state
1701 .pending_mouse_down
1702 .get_or_insert_with(Default::default)
1703 .clone();
1704 let interactive_bounds = interactive_bounds.clone();
1705
1706 cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
1707 let is_hovered = interactive_bounds
1708 .visibly_contains(&event.position, cx)
1709 && pending_mouse_down.borrow().is_none();
1710 if !is_hovered {
1711 active_tooltip.borrow_mut().take();
1712 return;
1713 }
1714
1715 if phase != DispatchPhase::Bubble {
1716 return;
1717 }
1718
1719 if active_tooltip.borrow().is_none() {
1720 let task = cx.spawn({
1721 let active_tooltip = active_tooltip.clone();
1722 let tooltip_builder = tooltip_builder.clone();
1723
1724 move |mut cx| async move {
1725 cx.background_executor().timer(TOOLTIP_DELAY).await;
1726 cx.update(|cx| {
1727 active_tooltip.borrow_mut().replace(
1728 ActiveTooltip {
1729 tooltip: Some(AnyTooltip {
1730 view: tooltip_builder(cx),
1731 cursor_offset: cx.mouse_position(),
1732 }),
1733 _task: None,
1734 },
1735 );
1736 cx.refresh();
1737 })
1738 .ok();
1739 }
1740 });
1741 active_tooltip.borrow_mut().replace(ActiveTooltip {
1742 tooltip: None,
1743 _task: Some(task),
1744 });
1745 }
1746 });
1747
1748 let active_tooltip = element_state
1749 .active_tooltip
1750 .get_or_insert_with(Default::default)
1751 .clone();
1752 cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
1753 active_tooltip.borrow_mut().take();
1754 });
1755
1756 if let Some(active_tooltip) = element_state
1757 .active_tooltip
1758 .get_or_insert_with(Default::default)
1759 .borrow()
1760 .as_ref()
1761 {
1762 if let Some(tooltip) = active_tooltip.tooltip.clone() {
1763 cx.set_tooltip(tooltip);
1764 }
1765 }
1766 }
1767
1768 let active_state = element_state
1769 .clicked_state
1770 .get_or_insert_with(Default::default)
1771 .clone();
1772 if active_state.borrow().is_clicked() {
1773 cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| {
1774 if phase == DispatchPhase::Capture {
1775 *active_state.borrow_mut() = ElementClickedState::default();
1776 cx.refresh();
1777 }
1778 });
1779 } else {
1780 let active_group_bounds = self
1781 .group_active_style
1782 .as_ref()
1783 .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
1784 let interactive_bounds = interactive_bounds.clone();
1785 cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| {
1786 if phase == DispatchPhase::Bubble && !cx.default_prevented() {
1787 let group = active_group_bounds
1788 .map_or(false, |bounds| bounds.contains(&down.position));
1789 let element =
1790 interactive_bounds.visibly_contains(&down.position, cx);
1791 if group || element {
1792 *active_state.borrow_mut() =
1793 ElementClickedState { group, element };
1794 cx.refresh();
1795 }
1796 }
1797 });
1798 }
1799
1800 let overflow = style.overflow;
1801 if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll {
1802 if let Some(scroll_handle) = &self.scroll_handle {
1803 scroll_handle.0.borrow_mut().overflow = overflow;
1804 }
1805
1806 let scroll_offset = element_state
1807 .scroll_offset
1808 .get_or_insert_with(Rc::default)
1809 .clone();
1810 let line_height = cx.line_height();
1811 let rem_size = cx.rem_size();
1812 let padding_size = size(
1813 style
1814 .padding
1815 .left
1816 .to_pixels(bounds.size.width.into(), rem_size)
1817 + style
1818 .padding
1819 .right
1820 .to_pixels(bounds.size.width.into(), rem_size),
1821 style
1822 .padding
1823 .top
1824 .to_pixels(bounds.size.height.into(), rem_size)
1825 + style
1826 .padding
1827 .bottom
1828 .to_pixels(bounds.size.height.into(), rem_size),
1829 );
1830 let scroll_max =
1831 (content_size + padding_size - bounds.size).max(&Size::default());
1832 // Clamp scroll offset in case scroll max is smaller now (e.g., if children
1833 // were removed or the bounds became larger).
1834 {
1835 let mut scroll_offset = scroll_offset.borrow_mut();
1836 scroll_offset.x = scroll_offset.x.clamp(-scroll_max.width, px(0.));
1837 scroll_offset.y = scroll_offset.y.clamp(-scroll_max.height, px(0.));
1838 }
1839
1840 let interactive_bounds = interactive_bounds.clone();
1841 cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
1842 if phase == DispatchPhase::Bubble
1843 && interactive_bounds.visibly_contains(&event.position, cx)
1844 {
1845 let mut scroll_offset = scroll_offset.borrow_mut();
1846 let old_scroll_offset = *scroll_offset;
1847 let delta = event.delta.pixel_delta(line_height);
1848
1849 if overflow.x == Overflow::Scroll {
1850 let mut delta_x = Pixels::ZERO;
1851 if !delta.x.is_zero() {
1852 delta_x = delta.x;
1853 } else if overflow.y != Overflow::Scroll {
1854 delta_x = delta.y;
1855 }
1856
1857 scroll_offset.x = (scroll_offset.x + delta_x)
1858 .clamp(-scroll_max.width, px(0.));
1859 }
1860
1861 if overflow.y == Overflow::Scroll {
1862 let mut delta_y = Pixels::ZERO;
1863 if !delta.y.is_zero() {
1864 delta_y = delta.y;
1865 } else if overflow.x != Overflow::Scroll {
1866 delta_y = delta.x;
1867 }
1868
1869 scroll_offset.y = (scroll_offset.y + delta_y)
1870 .clamp(-scroll_max.height, px(0.));
1871 }
1872
1873 if *scroll_offset != old_scroll_offset {
1874 cx.refresh();
1875 cx.stop_propagation();
1876 }
1877 }
1878 });
1879 }
1880
1881 if let Some(group) = self.group.clone() {
1882 GroupBounds::push(group, bounds, cx);
1883 }
1884
1885 let scroll_offset = element_state
1886 .scroll_offset
1887 .as_ref()
1888 .map(|scroll_offset| *scroll_offset.borrow());
1889
1890 let key_down_listeners = mem::take(&mut self.key_down_listeners);
1891 let key_up_listeners = mem::take(&mut self.key_up_listeners);
1892 let action_listeners = mem::take(&mut self.action_listeners);
1893 cx.with_key_dispatch(
1894 self.key_context.clone(),
1895 element_state.focus_handle.clone(),
1896 |_, cx| {
1897 for listener in key_down_listeners {
1898 cx.on_key_event(move |event: &KeyDownEvent, phase, cx| {
1899 listener(event, phase, cx);
1900 })
1901 }
1902
1903 for listener in key_up_listeners {
1904 cx.on_key_event(move |event: &KeyUpEvent, phase, cx| {
1905 listener(event, phase, cx);
1906 })
1907 }
1908
1909 for (action_type, listener) in action_listeners {
1910 cx.on_action(action_type, listener)
1911 }
1912
1913 f(&style, scroll_offset.unwrap_or_default(), cx)
1914 },
1915 );
1916
1917 if let Some(group) = self.group.as_ref() {
1918 GroupBounds::pop(group, cx);
1919 }
1920 });
1921 });
1922 });
1923 });
1924 }
1925
1926 /// Compute the visual style for this element, based on the current bounds and the element's state.
1927 pub fn compute_style(
1928 &self,
1929 bounds: Option<Bounds<Pixels>>,
1930 element_state: &mut InteractiveElementState,
1931 cx: &mut ElementContext,
1932 ) -> Style {
1933 let mut style = Style::default();
1934 style.refine(&self.base_style);
1935
1936 cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
1937 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
1938 if let Some(in_focus_style) = self.in_focus_style.as_ref() {
1939 if focus_handle.within_focused(cx) {
1940 style.refine(in_focus_style);
1941 }
1942 }
1943
1944 if let Some(focus_style) = self.focus_style.as_ref() {
1945 if focus_handle.is_focused(cx) {
1946 style.refine(focus_style);
1947 }
1948 }
1949 }
1950
1951 if let Some(bounds) = bounds {
1952 let mouse_position = cx.mouse_position();
1953 if !cx.has_active_drag() {
1954 if let Some(group_hover) = self.group_hover_style.as_ref() {
1955 if let Some(group_bounds) =
1956 GroupBounds::get(&group_hover.group, cx.deref_mut())
1957 {
1958 if group_bounds.contains(&mouse_position)
1959 && cx.was_top_layer(&mouse_position, cx.stacking_order())
1960 {
1961 style.refine(&group_hover.style);
1962 }
1963 }
1964 }
1965
1966 if let Some(hover_style) = self.hover_style.as_ref() {
1967 if bounds
1968 .intersect(&cx.content_mask().bounds)
1969 .contains(&mouse_position)
1970 && cx.was_top_layer(&mouse_position, cx.stacking_order())
1971 {
1972 style.refine(hover_style);
1973 }
1974 }
1975 }
1976
1977 if let Some(drag) = cx.active_drag.take() {
1978 let mut can_drop = true;
1979 if let Some(can_drop_predicate) = &self.can_drop_predicate {
1980 can_drop = can_drop_predicate(drag.value.as_ref(), cx.deref_mut());
1981 }
1982
1983 if can_drop {
1984 for (state_type, group_drag_style) in &self.group_drag_over_styles {
1985 if let Some(group_bounds) =
1986 GroupBounds::get(&group_drag_style.group, cx.deref_mut())
1987 {
1988 if *state_type == drag.value.as_ref().type_id()
1989 && group_bounds.contains(&mouse_position)
1990 {
1991 style.refine(&group_drag_style.style);
1992 }
1993 }
1994 }
1995
1996 for (state_type, build_drag_over_style) in &self.drag_over_styles {
1997 if *state_type == drag.value.as_ref().type_id()
1998 && bounds
1999 .intersect(&cx.content_mask().bounds)
2000 .contains(&mouse_position)
2001 && cx.was_top_layer_under_active_drag(
2002 &mouse_position,
2003 cx.stacking_order(),
2004 )
2005 {
2006 style.refine(&build_drag_over_style(drag.value.as_ref(), cx));
2007 }
2008 }
2009 }
2010
2011 cx.active_drag = Some(drag);
2012 }
2013 }
2014
2015 let clicked_state = element_state
2016 .clicked_state
2017 .get_or_insert_with(Default::default)
2018 .borrow();
2019 if clicked_state.group {
2020 if let Some(group) = self.group_active_style.as_ref() {
2021 style.refine(&group.style)
2022 }
2023 }
2024
2025 if let Some(active_style) = self.active_style.as_ref() {
2026 if clicked_state.element {
2027 style.refine(active_style)
2028 }
2029 }
2030 });
2031
2032 style
2033 }
2034}
2035
2036/// The per-frame state of an interactive element. Used for tracking stateful interactions like clicks
2037/// and scroll offsets.
2038#[derive(Default)]
2039pub struct InteractiveElementState {
2040 pub(crate) focus_handle: Option<FocusHandle>,
2041 pub(crate) clicked_state: Option<Rc<RefCell<ElementClickedState>>>,
2042 pub(crate) hover_state: Option<Rc<RefCell<bool>>>,
2043 pub(crate) pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
2044 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
2045 pub(crate) active_tooltip: Option<Rc<RefCell<Option<ActiveTooltip>>>>,
2046}
2047
2048/// The current active tooltip
2049pub struct ActiveTooltip {
2050 pub(crate) tooltip: Option<AnyTooltip>,
2051 pub(crate) _task: Option<Task<()>>,
2052}
2053
2054/// Whether or not the element or a group that contains it is clicked by the mouse.
2055#[derive(Copy, Clone, Default, Eq, PartialEq)]
2056pub struct ElementClickedState {
2057 /// True if this element's group has been clicked, false otherwise
2058 pub group: bool,
2059
2060 /// True if this element has been clicked, false otherwise
2061 pub element: bool,
2062}
2063
2064impl ElementClickedState {
2065 fn is_clicked(&self) -> bool {
2066 self.group || self.element
2067 }
2068}
2069
2070#[derive(Default)]
2071pub(crate) struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
2072
2073impl Global for GroupBounds {}
2074
2075impl GroupBounds {
2076 pub fn get(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
2077 cx.default_global::<Self>()
2078 .0
2079 .get(name)
2080 .and_then(|bounds_stack| bounds_stack.last())
2081 .cloned()
2082 }
2083
2084 pub fn push(name: SharedString, bounds: Bounds<Pixels>, cx: &mut AppContext) {
2085 cx.default_global::<Self>()
2086 .0
2087 .entry(name)
2088 .or_default()
2089 .push(bounds);
2090 }
2091
2092 pub fn pop(name: &SharedString, cx: &mut AppContext) {
2093 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
2094 }
2095}
2096
2097/// A wrapper around an element that can be focused.
2098pub struct Focusable<E> {
2099 /// The element that is focusable
2100 pub element: E,
2101}
2102
2103impl<E: InteractiveElement> FocusableElement for Focusable<E> {}
2104
2105impl<E> InteractiveElement for Focusable<E>
2106where
2107 E: InteractiveElement,
2108{
2109 fn interactivity(&mut self) -> &mut Interactivity {
2110 self.element.interactivity()
2111 }
2112}
2113
2114impl<E: StatefulInteractiveElement> StatefulInteractiveElement for Focusable<E> {}
2115
2116impl<E> Styled for Focusable<E>
2117where
2118 E: Styled,
2119{
2120 fn style(&mut self) -> &mut StyleRefinement {
2121 self.element.style()
2122 }
2123}
2124
2125impl<E> Element for Focusable<E>
2126where
2127 E: Element,
2128{
2129 type State = E::State;
2130
2131 fn request_layout(
2132 &mut self,
2133 state: Option<Self::State>,
2134 cx: &mut ElementContext,
2135 ) -> (LayoutId, Self::State) {
2136 self.element.request_layout(state, cx)
2137 }
2138
2139 fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
2140 self.element.paint(bounds, state, cx)
2141 }
2142}
2143
2144impl<E> IntoElement for Focusable<E>
2145where
2146 E: IntoElement,
2147{
2148 type Element = E::Element;
2149
2150 fn element_id(&self) -> Option<ElementId> {
2151 self.element.element_id()
2152 }
2153
2154 fn into_element(self) -> Self::Element {
2155 self.element.into_element()
2156 }
2157}
2158
2159impl<E> ParentElement for Focusable<E>
2160where
2161 E: ParentElement,
2162{
2163 fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
2164 self.element.extend(elements)
2165 }
2166}
2167
2168/// A wrapper around an element that can store state, produced after assigning an ElementId.
2169pub struct Stateful<E> {
2170 element: E,
2171}
2172
2173impl<E> Styled for Stateful<E>
2174where
2175 E: Styled,
2176{
2177 fn style(&mut self) -> &mut StyleRefinement {
2178 self.element.style()
2179 }
2180}
2181
2182impl<E> StatefulInteractiveElement for Stateful<E>
2183where
2184 E: Element,
2185 Self: InteractiveElement,
2186{
2187}
2188
2189impl<E> InteractiveElement for Stateful<E>
2190where
2191 E: InteractiveElement,
2192{
2193 fn interactivity(&mut self) -> &mut Interactivity {
2194 self.element.interactivity()
2195 }
2196}
2197
2198impl<E: FocusableElement> FocusableElement for Stateful<E> {}
2199
2200impl<E> Element for Stateful<E>
2201where
2202 E: Element,
2203{
2204 type State = E::State;
2205
2206 fn request_layout(
2207 &mut self,
2208 state: Option<Self::State>,
2209 cx: &mut ElementContext,
2210 ) -> (LayoutId, Self::State) {
2211 self.element.request_layout(state, cx)
2212 }
2213
2214 fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
2215 self.element.paint(bounds, state, cx)
2216 }
2217}
2218
2219impl<E> IntoElement for Stateful<E>
2220where
2221 E: Element,
2222{
2223 type Element = Self;
2224
2225 fn element_id(&self) -> Option<ElementId> {
2226 self.element.element_id()
2227 }
2228
2229 fn into_element(self) -> Self::Element {
2230 self
2231 }
2232}
2233
2234impl<E> ParentElement for Stateful<E>
2235where
2236 E: ParentElement,
2237{
2238 fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
2239 self.element.extend(elements)
2240 }
2241}
2242
2243#[derive(Default)]
2244struct ScrollHandleState {
2245 offset: Rc<RefCell<Point<Pixels>>>,
2246 bounds: Bounds<Pixels>,
2247 child_bounds: Vec<Bounds<Pixels>>,
2248 requested_scroll_top: Option<(usize, Pixels)>,
2249 overflow: Point<Overflow>,
2250}
2251
2252/// A handle to the scrollable aspects of an element.
2253/// Used for accessing scroll state, like the current scroll offset,
2254/// and for mutating the scroll state, like scrolling to a specific child.
2255#[derive(Clone)]
2256pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
2257
2258impl Default for ScrollHandle {
2259 fn default() -> Self {
2260 Self::new()
2261 }
2262}
2263
2264impl ScrollHandle {
2265 /// Construct a new scroll handle.
2266 pub fn new() -> Self {
2267 Self(Rc::default())
2268 }
2269
2270 /// Get the current scroll offset.
2271 pub fn offset(&self) -> Point<Pixels> {
2272 *self.0.borrow().offset.borrow()
2273 }
2274
2275 /// Get the top child that's scrolled into view.
2276 pub fn top_item(&self) -> usize {
2277 let state = self.0.borrow();
2278 let top = state.bounds.top() - state.offset.borrow().y;
2279
2280 match state.child_bounds.binary_search_by(|bounds| {
2281 if top < bounds.top() {
2282 Ordering::Greater
2283 } else if top > bounds.bottom() {
2284 Ordering::Less
2285 } else {
2286 Ordering::Equal
2287 }
2288 }) {
2289 Ok(ix) => ix,
2290 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
2291 }
2292 }
2293
2294 /// Get the bounds for a specific child.
2295 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
2296 self.0.borrow().child_bounds.get(ix).cloned()
2297 }
2298
2299 /// scroll_to_item scrolls the minimal amount to ensure that the child is
2300 /// fully visible
2301 pub fn scroll_to_item(&self, ix: usize) {
2302 let state = self.0.borrow();
2303
2304 let Some(bounds) = state.child_bounds.get(ix) else {
2305 return;
2306 };
2307
2308 let mut scroll_offset = state.offset.borrow_mut();
2309
2310 if state.overflow.y == Overflow::Scroll {
2311 if bounds.top() + scroll_offset.y < state.bounds.top() {
2312 scroll_offset.y = state.bounds.top() - bounds.top();
2313 } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
2314 scroll_offset.y = state.bounds.bottom() - bounds.bottom();
2315 }
2316 }
2317
2318 if state.overflow.x == Overflow::Scroll {
2319 if bounds.left() + scroll_offset.x < state.bounds.left() {
2320 scroll_offset.x = state.bounds.left() - bounds.left();
2321 } else if bounds.right() + scroll_offset.x > state.bounds.right() {
2322 scroll_offset.x = state.bounds.right() - bounds.right();
2323 }
2324 }
2325 }
2326
2327 /// Get the logical scroll top, based on a child index and a pixel offset.
2328 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
2329 let ix = self.top_item();
2330 let state = self.0.borrow();
2331
2332 if let Some(child_bounds) = state.child_bounds.get(ix) {
2333 (
2334 ix,
2335 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
2336 )
2337 } else {
2338 (ix, px(0.))
2339 }
2340 }
2341
2342 /// Set the logical scroll top, based on a child index and a pixel offset.
2343 pub fn set_logical_scroll_top(&self, ix: usize, px: Pixels) {
2344 self.0.borrow_mut().requested_scroll_top = Some((ix, px));
2345 }
2346}