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