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