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