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 Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent, DispatchPhase,
20 Element, ElementId, Entity, FocusHandle, Global, GlobalElementId, Hitbox, HitboxBehavior,
21 HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent,
22 KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent, MouseButton,
23 MouseClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Overflow, ParentElement, Pixels,
24 Point, Render, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task,
25 TooltipId, Visibility, Window, WindowControlArea, point, px, size,
26};
27use collections::HashMap;
28use refineable::Refineable;
29use smallvec::SmallVec;
30use stacksafe::{StackSafe, stacksafe};
31use std::{
32 any::{Any, TypeId},
33 cell::RefCell,
34 cmp::Ordering,
35 fmt::Debug,
36 marker::PhantomData,
37 mem,
38 rc::Rc,
39 sync::Arc,
40 time::Duration,
41};
42use util::ResultExt;
43
44use super::ImageCacheProvider;
45
46const DRAG_THRESHOLD: f64 = 2.;
47const TOOLTIP_SHOW_DELAY: Duration = Duration::from_millis(500);
48const HOVERABLE_TOOLTIP_HIDE_DELAY: Duration = Duration::from_millis(500);
49
50/// The styling information for a given group.
51pub struct GroupStyle {
52 /// The identifier for this group.
53 pub group: SharedString,
54
55 /// The specific style refinement that this group would apply
56 /// to its children.
57 pub style: Box<StyleRefinement>,
58}
59
60/// An event for when a drag is moving over this element, with the given state type.
61pub struct DragMoveEvent<T> {
62 /// The mouse move event that triggered this drag move event.
63 pub event: MouseMoveEvent,
64
65 /// The bounds of this element.
66 pub bounds: Bounds<Pixels>,
67 drag: PhantomData<T>,
68 dragged_item: Arc<dyn Any>,
69}
70
71impl<T: 'static> DragMoveEvent<T> {
72 /// Returns the drag state for this event.
73 pub fn drag<'b>(&self, cx: &'b App) -> &'b T {
74 cx.active_drag
75 .as_ref()
76 .and_then(|drag| drag.value.downcast_ref::<T>())
77 .expect("DragMoveEvent is only valid when the stored active drag is of the same type.")
78 }
79
80 /// An item that is about to be dropped.
81 pub fn dragged_item(&self) -> &dyn Any {
82 self.dragged_item.as_ref()
83 }
84}
85
86impl Interactivity {
87 /// Create an `Interactivity`, capturing the caller location in debug mode.
88 #[cfg(any(feature = "inspector", debug_assertions))]
89 #[track_caller]
90 pub fn new() -> Interactivity {
91 Interactivity {
92 source_location: Some(core::panic::Location::caller()),
93 ..Default::default()
94 }
95 }
96
97 /// Create an `Interactivity`, capturing the caller location in debug mode.
98 #[cfg(not(any(feature = "inspector", debug_assertions)))]
99 pub fn new() -> Interactivity {
100 Interactivity::default()
101 }
102
103 /// Gets the source location of construction. Returns `None` when not in debug mode.
104 pub fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
105 #[cfg(any(feature = "inspector", debug_assertions))]
106 {
107 self.source_location
108 }
109
110 #[cfg(not(any(feature = "inspector", debug_assertions)))]
111 {
112 None
113 }
114 }
115
116 /// Bind the given callback to the mouse down event for the given mouse button, during the bubble phase
117 /// The imperative API equivalent of [`InteractiveElement::on_mouse_down`]
118 ///
119 /// See [`Context::listener`](crate::Context::listener) to get access to the view state from this callback.
120 pub fn on_mouse_down(
121 &mut self,
122 button: MouseButton,
123 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
124 ) {
125 self.mouse_down_listeners
126 .push(Box::new(move |event, phase, hitbox, window, cx| {
127 if phase == DispatchPhase::Bubble
128 && event.button == button
129 && hitbox.is_hovered(window)
130 {
131 (listener)(event, window, cx)
132 }
133 }));
134 }
135
136 /// Bind the given callback to the mouse down event for any button, during the capture phase
137 /// The imperative API equivalent of [`InteractiveElement::capture_any_mouse_down`]
138 ///
139 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
140 pub fn capture_any_mouse_down(
141 &mut self,
142 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
143 ) {
144 self.mouse_down_listeners
145 .push(Box::new(move |event, phase, hitbox, window, cx| {
146 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
147 (listener)(event, window, cx)
148 }
149 }));
150 }
151
152 /// Bind the given callback to the mouse down event for any button, during the bubble phase
153 /// the imperative API equivalent to [`InteractiveElement::on_any_mouse_down`]
154 ///
155 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
156 pub fn on_any_mouse_down(
157 &mut self,
158 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
159 ) {
160 self.mouse_down_listeners
161 .push(Box::new(move |event, phase, hitbox, window, cx| {
162 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
163 (listener)(event, window, cx)
164 }
165 }));
166 }
167
168 /// Bind the given callback to the mouse up event for the given button, during the bubble phase
169 /// the imperative API equivalent to [`InteractiveElement::on_mouse_up`]
170 ///
171 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
172 pub fn on_mouse_up(
173 &mut self,
174 button: MouseButton,
175 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
176 ) {
177 self.mouse_up_listeners
178 .push(Box::new(move |event, phase, hitbox, window, cx| {
179 if phase == DispatchPhase::Bubble
180 && event.button == button
181 && hitbox.is_hovered(window)
182 {
183 (listener)(event, window, cx)
184 }
185 }));
186 }
187
188 /// Bind the given callback to the mouse up event for any button, during the capture phase
189 /// the imperative API equivalent to [`InteractiveElement::capture_any_mouse_up`]
190 ///
191 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
192 pub fn capture_any_mouse_up(
193 &mut self,
194 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
195 ) {
196 self.mouse_up_listeners
197 .push(Box::new(move |event, phase, hitbox, window, cx| {
198 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
199 (listener)(event, window, cx)
200 }
201 }));
202 }
203
204 /// Bind the given callback to the mouse up event for any button, during the bubble phase
205 /// the imperative API equivalent to [`Interactivity::on_any_mouse_up`]
206 ///
207 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
208 pub fn on_any_mouse_up(
209 &mut self,
210 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
211 ) {
212 self.mouse_up_listeners
213 .push(Box::new(move |event, phase, hitbox, window, cx| {
214 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
215 (listener)(event, window, cx)
216 }
217 }));
218 }
219
220 /// Bind the given callback to the mouse down event, on any button, during the capture phase,
221 /// when the mouse is outside of the bounds of this element.
222 /// The imperative API equivalent to [`InteractiveElement::on_mouse_down_out`]
223 ///
224 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
225 pub fn on_mouse_down_out(
226 &mut self,
227 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
228 ) {
229 self.mouse_down_listeners
230 .push(Box::new(move |event, phase, hitbox, window, cx| {
231 if phase == DispatchPhase::Capture && !hitbox.contains(&window.mouse_position()) {
232 (listener)(event, window, cx)
233 }
234 }));
235 }
236
237 /// Bind the given callback to the mouse up event, for the given button, during the capture phase,
238 /// when the mouse is outside of the bounds of this element.
239 /// The imperative API equivalent to [`InteractiveElement::on_mouse_up_out`]
240 ///
241 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
242 pub fn on_mouse_up_out(
243 &mut self,
244 button: MouseButton,
245 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
246 ) {
247 self.mouse_up_listeners
248 .push(Box::new(move |event, phase, hitbox, window, cx| {
249 if phase == DispatchPhase::Capture
250 && event.button == button
251 && !hitbox.is_hovered(window)
252 {
253 (listener)(event, window, cx);
254 }
255 }));
256 }
257
258 /// Bind the given callback to the mouse move event, during the bubble phase
259 /// The imperative API equivalent to [`InteractiveElement::on_mouse_move`]
260 ///
261 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
262 pub fn on_mouse_move(
263 &mut self,
264 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
265 ) {
266 self.mouse_move_listeners
267 .push(Box::new(move |event, phase, hitbox, window, cx| {
268 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
269 (listener)(event, window, cx);
270 }
271 }));
272 }
273
274 /// Bind the given callback to the mouse drag event of the given type. Note that this
275 /// will be called for all move events, inside or outside of this element, as long as the
276 /// drag was started with this element under the mouse. Useful for implementing draggable
277 /// UIs that don't conform to a drag and drop style interaction, like resizing.
278 /// The imperative API equivalent to [`InteractiveElement::on_drag_move`]
279 ///
280 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
281 pub fn on_drag_move<T>(
282 &mut self,
283 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
284 ) where
285 T: 'static,
286 {
287 self.mouse_move_listeners
288 .push(Box::new(move |event, phase, hitbox, window, cx| {
289 if phase == DispatchPhase::Capture {
290 if let Some(drag) = &cx.active_drag {
291 if drag.value.as_ref().type_id() == TypeId::of::<T>() {
292 (listener)(
293 &DragMoveEvent {
294 event: event.clone(),
295 bounds: hitbox.bounds,
296 drag: PhantomData,
297 dragged_item: Arc::clone(&drag.value),
298 },
299 window,
300 cx,
301 );
302 }
303 }
304 }
305 }));
306 }
307
308 /// Bind the given callback to scroll wheel events during the bubble phase
309 /// The imperative API equivalent to [`InteractiveElement::on_scroll_wheel`]
310 ///
311 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
312 pub fn on_scroll_wheel(
313 &mut self,
314 listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
315 ) {
316 self.scroll_wheel_listeners
317 .push(Box::new(move |event, phase, hitbox, window, cx| {
318 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
319 (listener)(event, window, cx);
320 }
321 }));
322 }
323
324 /// Bind the given callback to an action dispatch during the capture phase
325 /// The imperative API equivalent to [`InteractiveElement::capture_action`]
326 ///
327 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
328 pub fn capture_action<A: Action>(
329 &mut self,
330 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
331 ) {
332 self.action_listeners.push((
333 TypeId::of::<A>(),
334 Box::new(move |action, phase, window, cx| {
335 let action = action.downcast_ref().unwrap();
336 if phase == DispatchPhase::Capture {
337 (listener)(action, window, cx)
338 } else {
339 cx.propagate();
340 }
341 }),
342 ));
343 }
344
345 /// Bind the given callback to an action dispatch during the bubble phase
346 /// The imperative API equivalent to [`InteractiveElement::on_action`]
347 ///
348 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
349 pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Window, &mut App) + 'static) {
350 self.action_listeners.push((
351 TypeId::of::<A>(),
352 Box::new(move |action, phase, window, cx| {
353 let action = action.downcast_ref().unwrap();
354 if phase == DispatchPhase::Bubble {
355 (listener)(action, window, cx)
356 }
357 }),
358 ));
359 }
360
361 /// Bind the given callback to an action dispatch, based on a dynamic action parameter
362 /// instead of a type parameter. Useful for component libraries that want to expose
363 /// action bindings to their users.
364 /// The imperative API equivalent to [`InteractiveElement::on_boxed_action`]
365 ///
366 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
367 pub fn on_boxed_action(
368 &mut self,
369 action: &dyn Action,
370 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
371 ) {
372 let action = action.boxed_clone();
373 self.action_listeners.push((
374 (*action).type_id(),
375 Box::new(move |_, phase, window, cx| {
376 if phase == DispatchPhase::Bubble {
377 (listener)(&*action, window, cx)
378 }
379 }),
380 ));
381 }
382
383 /// Bind the given callback to key down events during the bubble phase
384 /// The imperative API equivalent to [`InteractiveElement::on_key_down`]
385 ///
386 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
387 pub fn on_key_down(
388 &mut self,
389 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
390 ) {
391 self.key_down_listeners
392 .push(Box::new(move |event, phase, window, cx| {
393 if phase == DispatchPhase::Bubble {
394 (listener)(event, window, cx)
395 }
396 }));
397 }
398
399 /// Bind the given callback to key down events during the capture phase
400 /// The imperative API equivalent to [`InteractiveElement::capture_key_down`]
401 ///
402 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
403 pub fn capture_key_down(
404 &mut self,
405 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
406 ) {
407 self.key_down_listeners
408 .push(Box::new(move |event, phase, window, cx| {
409 if phase == DispatchPhase::Capture {
410 listener(event, window, cx)
411 }
412 }));
413 }
414
415 /// Bind the given callback to key up events during the bubble phase
416 /// The imperative API equivalent to [`InteractiveElement::on_key_up`]
417 ///
418 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
419 pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static) {
420 self.key_up_listeners
421 .push(Box::new(move |event, phase, window, cx| {
422 if phase == DispatchPhase::Bubble {
423 listener(event, window, cx)
424 }
425 }));
426 }
427
428 /// Bind the given callback to key up events during the capture phase
429 /// The imperative API equivalent to [`InteractiveElement::on_key_up`]
430 ///
431 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
432 pub fn capture_key_up(
433 &mut self,
434 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
435 ) {
436 self.key_up_listeners
437 .push(Box::new(move |event, phase, window, cx| {
438 if phase == DispatchPhase::Capture {
439 listener(event, window, cx)
440 }
441 }));
442 }
443
444 /// Bind the given callback to modifiers changing events.
445 /// The imperative API equivalent to [`InteractiveElement::on_modifiers_changed`]
446 ///
447 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
448 pub fn on_modifiers_changed(
449 &mut self,
450 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
451 ) {
452 self.modifiers_changed_listeners
453 .push(Box::new(move |event, window, cx| {
454 listener(event, window, cx)
455 }));
456 }
457
458 /// Bind the given callback to drop events of the given type, whether or not the drag started on this element
459 /// The imperative API equivalent to [`InteractiveElement::on_drop`]
460 ///
461 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
462 pub fn on_drop<T: 'static>(&mut self, listener: impl Fn(&T, &mut Window, &mut App) + 'static) {
463 self.drop_listeners.push((
464 TypeId::of::<T>(),
465 Box::new(move |dragged_value, window, cx| {
466 listener(dragged_value.downcast_ref().unwrap(), window, cx);
467 }),
468 ));
469 }
470
471 /// Use the given predicate to determine whether or not a drop event should be dispatched to this element
472 /// The imperative API equivalent to [`InteractiveElement::can_drop`]
473 pub fn can_drop(
474 &mut self,
475 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
476 ) {
477 self.can_drop_predicate = Some(Box::new(predicate));
478 }
479
480 /// Bind the given callback to click events of this element
481 /// The imperative API equivalent to [`StatefulInteractiveElement::on_click`]
482 ///
483 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
484 pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
485 where
486 Self: Sized,
487 {
488 self.click_listeners.push(Rc::new(move |event, window, cx| {
489 listener(event, window, cx)
490 }));
491 }
492
493 /// On drag initiation, this callback will be used to create a new view to render the dragged value for a
494 /// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with
495 /// the [`Self::on_drag_move`] API
496 /// The imperative API equivalent to [`StatefulInteractiveElement::on_drag`]
497 ///
498 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
499 pub fn on_drag<T, W>(
500 &mut self,
501 value: T,
502 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
503 ) where
504 Self: Sized,
505 T: 'static,
506 W: 'static + Render,
507 {
508 debug_assert!(
509 self.drag_listener.is_none(),
510 "calling on_drag more than once on the same element is not supported"
511 );
512 self.drag_listener = Some((
513 Arc::new(value),
514 Box::new(move |value, offset, window, cx| {
515 constructor(value.downcast_ref().unwrap(), offset, window, cx).into()
516 }),
517 ));
518 }
519
520 /// Bind the given callback on the hover start and end events of this element. Note that the boolean
521 /// passed to the callback is true when the hover starts and false when it ends.
522 /// The imperative API equivalent to [`StatefulInteractiveElement::on_hover`]
523 ///
524 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
525 pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static)
526 where
527 Self: Sized,
528 {
529 debug_assert!(
530 self.hover_listener.is_none(),
531 "calling on_hover more than once on the same element is not supported"
532 );
533 self.hover_listener = Some(Box::new(listener));
534 }
535
536 /// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
537 /// The imperative API equivalent to [`InteractiveElement::tooltip`]
538 pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static)
539 where
540 Self: Sized,
541 {
542 debug_assert!(
543 self.tooltip_builder.is_none(),
544 "calling tooltip more than once on the same element is not supported"
545 );
546 self.tooltip_builder = Some(TooltipBuilder {
547 build: Rc::new(build_tooltip),
548 hoverable: false,
549 });
550 }
551
552 /// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
553 /// The tooltip itself is also hoverable and won't disappear when the user moves the mouse into
554 /// the tooltip. The imperative API equivalent to [`InteractiveElement::hoverable_tooltip`]
555 pub fn hoverable_tooltip(
556 &mut self,
557 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
558 ) where
559 Self: Sized,
560 {
561 debug_assert!(
562 self.tooltip_builder.is_none(),
563 "calling tooltip more than once on the same element is not supported"
564 );
565 self.tooltip_builder = Some(TooltipBuilder {
566 build: Rc::new(build_tooltip),
567 hoverable: true,
568 });
569 }
570
571 /// Block the mouse from all interactions with elements behind this element's hitbox. Typically
572 /// `block_mouse_except_scroll` should be preferred.
573 ///
574 /// The imperative API equivalent to [`InteractiveElement::occlude`]
575 pub fn occlude_mouse(&mut self) {
576 self.hitbox_behavior = HitboxBehavior::BlockMouse;
577 }
578
579 /// Set the bounds of this element as a window control area for the platform window.
580 /// The imperative API equivalent to [`InteractiveElement::window_control_area`]
581 pub fn window_control_area(&mut self, area: WindowControlArea) {
582 self.window_control = Some(area);
583 }
584
585 /// Block non-scroll mouse interactions with elements behind this element's hitbox. See
586 /// [`Hitbox::is_hovered`] for details.
587 ///
588 /// The imperative API equivalent to [`InteractiveElement::block_mouse_except_scroll`]
589 pub fn block_mouse_except_scroll(&mut self) {
590 self.hitbox_behavior = HitboxBehavior::BlockMouseExceptScroll;
591 }
592}
593
594/// A trait for elements that want to use the standard GPUI event handlers that don't
595/// require any state.
596pub trait InteractiveElement: Sized {
597 /// Retrieve the interactivity state associated with this element
598 fn interactivity(&mut self) -> &mut Interactivity;
599
600 /// Assign this element to a group of elements that can be styled together
601 fn group(mut self, group: impl Into<SharedString>) -> Self {
602 self.interactivity().group = Some(group.into());
603 self
604 }
605
606 /// Assign this element an ID, so that it can be used with interactivity
607 fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
608 self.interactivity().element_id = Some(id.into());
609
610 Stateful { element: self }
611 }
612
613 /// Track the focus state of the given focus handle on this element.
614 /// If the focus handle is focused by the application, this element will
615 /// apply its focused styles.
616 fn track_focus(mut self, focus_handle: &FocusHandle) -> Self {
617 self.interactivity().focusable = true;
618 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
619 self
620 }
621
622 /// Set index of the tab stop order.
623 fn tab_index(mut self, index: isize) -> Self {
624 self.interactivity().focusable = true;
625 self.interactivity().tab_index = Some(index);
626 self
627 }
628
629 /// Set the keymap context for this element. This will be used to determine
630 /// which action to dispatch from the keymap.
631 fn key_context<C, E>(mut self, key_context: C) -> Self
632 where
633 C: TryInto<KeyContext, Error = E>,
634 E: Debug,
635 {
636 if let Some(key_context) = key_context.try_into().log_err() {
637 self.interactivity().key_context = Some(key_context);
638 }
639 self
640 }
641
642 /// Apply the given style to this element when the mouse hovers over it
643 fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
644 debug_assert!(
645 self.interactivity().hover_style.is_none(),
646 "hover style already set"
647 );
648 self.interactivity().hover_style = Some(Box::new(f(StyleRefinement::default())));
649 self
650 }
651
652 /// Apply the given style to this element when the mouse hovers over a group member
653 fn group_hover(
654 mut self,
655 group_name: impl Into<SharedString>,
656 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
657 ) -> Self {
658 self.interactivity().group_hover_style = Some(GroupStyle {
659 group: group_name.into(),
660 style: Box::new(f(StyleRefinement::default())),
661 });
662 self
663 }
664
665 /// Bind the given callback to the mouse down event for the given mouse button,
666 /// the fluent API equivalent to [`Interactivity::on_mouse_down`]
667 ///
668 /// See [`Context::listener`](crate::Context::listener) to get access to the view state from this callback.
669 fn on_mouse_down(
670 mut self,
671 button: MouseButton,
672 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
673 ) -> Self {
674 self.interactivity().on_mouse_down(button, listener);
675 self
676 }
677
678 #[cfg(any(test, feature = "test-support"))]
679 /// Set a key that can be used to look up this element's bounds
680 /// in the [`VisualTestContext::debug_bounds`] map
681 /// This is a noop in release builds
682 fn debug_selector(mut self, f: impl FnOnce() -> String) -> Self {
683 self.interactivity().debug_selector = Some(f());
684 self
685 }
686
687 #[cfg(not(any(test, feature = "test-support")))]
688 /// Set a key that can be used to look up this element's bounds
689 /// in the [`VisualTestContext::debug_bounds`] map
690 /// This is a noop in release builds
691 #[inline]
692 fn debug_selector(self, _: impl FnOnce() -> String) -> Self {
693 self
694 }
695
696 /// Bind the given callback to the mouse down event for any button, during the capture phase
697 /// the fluent API equivalent to [`Interactivity::capture_any_mouse_down`]
698 ///
699 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
700 fn capture_any_mouse_down(
701 mut self,
702 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
703 ) -> Self {
704 self.interactivity().capture_any_mouse_down(listener);
705 self
706 }
707
708 /// Bind the given callback to the mouse down event for any button, during the capture phase
709 /// the fluent API equivalent to [`Interactivity::on_any_mouse_down`]
710 ///
711 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
712 fn on_any_mouse_down(
713 mut self,
714 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
715 ) -> Self {
716 self.interactivity().on_any_mouse_down(listener);
717 self
718 }
719
720 /// Bind the given callback to the mouse up event for the given button, during the bubble phase
721 /// the fluent API equivalent to [`Interactivity::on_mouse_up`]
722 ///
723 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
724 fn on_mouse_up(
725 mut self,
726 button: MouseButton,
727 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
728 ) -> Self {
729 self.interactivity().on_mouse_up(button, listener);
730 self
731 }
732
733 /// Bind the given callback to the mouse up event for any button, during the capture phase
734 /// the fluent API equivalent to [`Interactivity::capture_any_mouse_up`]
735 ///
736 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
737 fn capture_any_mouse_up(
738 mut self,
739 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
740 ) -> Self {
741 self.interactivity().capture_any_mouse_up(listener);
742 self
743 }
744
745 /// Bind the given callback to the mouse down event, on any button, during the capture phase,
746 /// when the mouse is outside of the bounds of this element.
747 /// The fluent API equivalent to [`Interactivity::on_mouse_down_out`]
748 ///
749 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
750 fn on_mouse_down_out(
751 mut self,
752 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
753 ) -> Self {
754 self.interactivity().on_mouse_down_out(listener);
755 self
756 }
757
758 /// Bind the given callback to the mouse up event, for the given button, during the capture phase,
759 /// when the mouse is outside of the bounds of this element.
760 /// The fluent API equivalent to [`Interactivity::on_mouse_up_out`]
761 ///
762 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
763 fn on_mouse_up_out(
764 mut self,
765 button: MouseButton,
766 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
767 ) -> Self {
768 self.interactivity().on_mouse_up_out(button, listener);
769 self
770 }
771
772 /// Bind the given callback to the mouse move event, during the bubble phase
773 /// The fluent API equivalent to [`Interactivity::on_mouse_move`]
774 ///
775 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
776 fn on_mouse_move(
777 mut self,
778 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
779 ) -> Self {
780 self.interactivity().on_mouse_move(listener);
781 self
782 }
783
784 /// Bind the given callback to the mouse drag event of the given type. Note that this
785 /// will be called for all move events, inside or outside of this element, as long as the
786 /// drag was started with this element under the mouse. Useful for implementing draggable
787 /// UIs that don't conform to a drag and drop style interaction, like resizing.
788 /// The fluent API equivalent to [`Interactivity::on_drag_move`]
789 ///
790 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
791 fn on_drag_move<T: 'static>(
792 mut self,
793 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
794 ) -> Self {
795 self.interactivity().on_drag_move(listener);
796 self
797 }
798
799 /// Bind the given callback to scroll wheel events during the bubble phase
800 /// The fluent API equivalent to [`Interactivity::on_scroll_wheel`]
801 ///
802 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
803 fn on_scroll_wheel(
804 mut self,
805 listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
806 ) -> Self {
807 self.interactivity().on_scroll_wheel(listener);
808 self
809 }
810
811 /// Capture the given action, before normal action dispatch can fire
812 /// The fluent API equivalent to [`Interactivity::on_scroll_wheel`]
813 ///
814 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
815 fn capture_action<A: Action>(
816 mut self,
817 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
818 ) -> Self {
819 self.interactivity().capture_action(listener);
820 self
821 }
822
823 /// Bind the given callback to an action dispatch during the bubble phase
824 /// The fluent API equivalent to [`Interactivity::on_action`]
825 ///
826 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
827 fn on_action<A: Action>(
828 mut self,
829 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
830 ) -> Self {
831 self.interactivity().on_action(listener);
832 self
833 }
834
835 /// Bind the given callback to an action dispatch, based on a dynamic action parameter
836 /// instead of a type parameter. Useful for component libraries that want to expose
837 /// action bindings to their users.
838 /// The fluent API equivalent to [`Interactivity::on_boxed_action`]
839 ///
840 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
841 fn on_boxed_action(
842 mut self,
843 action: &dyn Action,
844 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
845 ) -> Self {
846 self.interactivity().on_boxed_action(action, listener);
847 self
848 }
849
850 /// Bind the given callback to key down events during the bubble phase
851 /// The fluent API equivalent to [`Interactivity::on_key_down`]
852 ///
853 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
854 fn on_key_down(
855 mut self,
856 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
857 ) -> Self {
858 self.interactivity().on_key_down(listener);
859 self
860 }
861
862 /// Bind the given callback to key down events during the capture phase
863 /// The fluent API equivalent to [`Interactivity::capture_key_down`]
864 ///
865 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
866 fn capture_key_down(
867 mut self,
868 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
869 ) -> Self {
870 self.interactivity().capture_key_down(listener);
871 self
872 }
873
874 /// Bind the given callback to key up events during the bubble phase
875 /// The fluent API equivalent to [`Interactivity::on_key_up`]
876 ///
877 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
878 fn on_key_up(
879 mut self,
880 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
881 ) -> Self {
882 self.interactivity().on_key_up(listener);
883 self
884 }
885
886 /// Bind the given callback to key up events during the capture phase
887 /// The fluent API equivalent to [`Interactivity::capture_key_up`]
888 ///
889 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
890 fn capture_key_up(
891 mut self,
892 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
893 ) -> Self {
894 self.interactivity().capture_key_up(listener);
895 self
896 }
897
898 /// Bind the given callback to modifiers changing events.
899 /// The fluent API equivalent to [`Interactivity::on_modifiers_changed`]
900 ///
901 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
902 fn on_modifiers_changed(
903 mut self,
904 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
905 ) -> Self {
906 self.interactivity().on_modifiers_changed(listener);
907 self
908 }
909
910 /// Apply the given style when the given data type is dragged over this element
911 fn drag_over<S: 'static>(
912 mut self,
913 f: impl 'static + Fn(StyleRefinement, &S, &mut Window, &mut App) -> StyleRefinement,
914 ) -> Self {
915 self.interactivity().drag_over_styles.push((
916 TypeId::of::<S>(),
917 Box::new(move |currently_dragged: &dyn Any, window, cx| {
918 f(
919 StyleRefinement::default(),
920 currently_dragged.downcast_ref::<S>().unwrap(),
921 window,
922 cx,
923 )
924 }),
925 ));
926 self
927 }
928
929 /// Apply the given style when the given data type is dragged over this element's group
930 fn group_drag_over<S: 'static>(
931 mut self,
932 group_name: impl Into<SharedString>,
933 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
934 ) -> Self {
935 self.interactivity().group_drag_over_styles.push((
936 TypeId::of::<S>(),
937 GroupStyle {
938 group: group_name.into(),
939 style: Box::new(f(StyleRefinement::default())),
940 },
941 ));
942 self
943 }
944
945 /// Bind the given callback to drop events of the given type, whether or not the drag started on this element
946 /// The fluent API equivalent to [`Interactivity::on_drop`]
947 ///
948 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
949 fn on_drop<T: 'static>(
950 mut self,
951 listener: impl Fn(&T, &mut Window, &mut App) + 'static,
952 ) -> Self {
953 self.interactivity().on_drop(listener);
954 self
955 }
956
957 /// Use the given predicate to determine whether or not a drop event should be dispatched to this element
958 /// The fluent API equivalent to [`Interactivity::can_drop`]
959 fn can_drop(
960 mut self,
961 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
962 ) -> Self {
963 self.interactivity().can_drop(predicate);
964 self
965 }
966
967 /// Block the mouse from all interactions with elements behind this element's hitbox. Typically
968 /// `block_mouse_except_scroll` should be preferred.
969 /// The fluent API equivalent to [`Interactivity::occlude_mouse`]
970 fn occlude(mut self) -> Self {
971 self.interactivity().occlude_mouse();
972 self
973 }
974
975 /// Set the bounds of this element as a window control area for the platform window.
976 /// The fluent API equivalent to [`Interactivity::window_control_area`]
977 fn window_control_area(mut self, area: WindowControlArea) -> Self {
978 self.interactivity().window_control_area(area);
979 self
980 }
981
982 /// Block non-scroll mouse interactions with elements behind this element's hitbox. See
983 /// [`Hitbox::is_hovered`] for details.
984 ///
985 /// The fluent API equivalent to [`Interactivity::block_mouse_except_scroll`]
986 fn block_mouse_except_scroll(mut self) -> Self {
987 self.interactivity().block_mouse_except_scroll();
988 self
989 }
990
991 /// Set the given styles to be applied when this element, specifically, is focused.
992 /// Requires that the element is focusable. Elements can be made focusable using [`InteractiveElement::track_focus`].
993 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
994 where
995 Self: Sized,
996 {
997 self.interactivity().focus_style = Some(Box::new(f(StyleRefinement::default())));
998 self
999 }
1000
1001 /// Set the given styles to be applied when this element is inside another element that is focused.
1002 /// Requires that the element is focusable. Elements can be made focusable using [`InteractiveElement::track_focus`].
1003 fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1004 where
1005 Self: Sized,
1006 {
1007 self.interactivity().in_focus_style = Some(Box::new(f(StyleRefinement::default())));
1008 self
1009 }
1010}
1011
1012/// A trait for elements that want to use the standard GPUI interactivity features
1013/// that require state.
1014pub trait StatefulInteractiveElement: InteractiveElement {
1015 /// Set this element to focusable.
1016 fn focusable(mut self) -> Self {
1017 self.interactivity().focusable = true;
1018 self
1019 }
1020
1021 /// Set the overflow x and y to scroll.
1022 fn overflow_scroll(mut self) -> Self {
1023 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1024 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1025 self
1026 }
1027
1028 /// Set the overflow x to scroll.
1029 fn overflow_x_scroll(mut self) -> Self {
1030 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1031 self
1032 }
1033
1034 /// Set the overflow y to scroll.
1035 fn overflow_y_scroll(mut self) -> Self {
1036 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1037 self
1038 }
1039
1040 /// Track the scroll state of this element with the given handle.
1041 fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
1042 self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone());
1043 self
1044 }
1045
1046 /// Track the scroll state of this element with the given handle.
1047 fn anchor_scroll(mut self, scroll_anchor: Option<ScrollAnchor>) -> Self {
1048 self.interactivity().scroll_anchor = scroll_anchor;
1049 self
1050 }
1051
1052 /// Set the given styles to be applied when this element is active.
1053 fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1054 where
1055 Self: Sized,
1056 {
1057 self.interactivity().active_style = Some(Box::new(f(StyleRefinement::default())));
1058 self
1059 }
1060
1061 /// Set the given styles to be applied when this element's group is active.
1062 fn group_active(
1063 mut self,
1064 group_name: impl Into<SharedString>,
1065 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1066 ) -> Self
1067 where
1068 Self: Sized,
1069 {
1070 self.interactivity().group_active_style = Some(GroupStyle {
1071 group: group_name.into(),
1072 style: Box::new(f(StyleRefinement::default())),
1073 });
1074 self
1075 }
1076
1077 /// Bind the given callback to click events of this element
1078 /// The fluent API equivalent to [`Interactivity::on_click`]
1079 ///
1080 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1081 fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self
1082 where
1083 Self: Sized,
1084 {
1085 self.interactivity().on_click(listener);
1086 self
1087 }
1088
1089 /// On drag initiation, this callback will be used to create a new view to render the dragged value for a
1090 /// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with
1091 /// the [`Self::on_drag_move`] API.
1092 /// The callback also has access to the offset of triggering click from the origin of parent element.
1093 /// The fluent API equivalent to [`Interactivity::on_drag`]
1094 ///
1095 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1096 fn on_drag<T, W>(
1097 mut self,
1098 value: T,
1099 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
1100 ) -> Self
1101 where
1102 Self: Sized,
1103 T: 'static,
1104 W: 'static + Render,
1105 {
1106 self.interactivity().on_drag(value, constructor);
1107 self
1108 }
1109
1110 /// Bind the given callback on the hover start and end events of this element. Note that the boolean
1111 /// passed to the callback is true when the hover starts and false when it ends.
1112 /// The fluent API equivalent to [`Interactivity::on_hover`]
1113 ///
1114 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1115 fn on_hover(mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self
1116 where
1117 Self: Sized,
1118 {
1119 self.interactivity().on_hover(listener);
1120 self
1121 }
1122
1123 /// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
1124 /// The fluent API equivalent to [`Interactivity::tooltip`]
1125 fn tooltip(mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self
1126 where
1127 Self: Sized,
1128 {
1129 self.interactivity().tooltip(build_tooltip);
1130 self
1131 }
1132
1133 /// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
1134 /// The tooltip itself is also hoverable and won't disappear when the user moves the mouse into
1135 /// the tooltip. The fluent API equivalent to [`Interactivity::hoverable_tooltip`]
1136 fn hoverable_tooltip(
1137 mut self,
1138 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
1139 ) -> Self
1140 where
1141 Self: Sized,
1142 {
1143 self.interactivity().hoverable_tooltip(build_tooltip);
1144 self
1145 }
1146}
1147
1148pub(crate) type MouseDownListener =
1149 Box<dyn Fn(&MouseDownEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1150pub(crate) type MouseUpListener =
1151 Box<dyn Fn(&MouseUpEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1152
1153pub(crate) type MouseMoveListener =
1154 Box<dyn Fn(&MouseMoveEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1155
1156pub(crate) type ScrollWheelListener =
1157 Box<dyn Fn(&ScrollWheelEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1158
1159pub(crate) type ClickListener = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>;
1160
1161pub(crate) type DragListener =
1162 Box<dyn Fn(&dyn Any, Point<Pixels>, &mut Window, &mut App) -> AnyView + 'static>;
1163
1164type DropListener = Box<dyn Fn(&dyn Any, &mut Window, &mut App) + 'static>;
1165
1166type CanDropPredicate = Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static>;
1167
1168pub(crate) struct TooltipBuilder {
1169 build: Rc<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>,
1170 hoverable: bool,
1171}
1172
1173pub(crate) type KeyDownListener =
1174 Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1175
1176pub(crate) type KeyUpListener =
1177 Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1178
1179pub(crate) type ModifiersChangedListener =
1180 Box<dyn Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static>;
1181
1182pub(crate) type ActionListener =
1183 Box<dyn Fn(&dyn Any, DispatchPhase, &mut Window, &mut App) + 'static>;
1184
1185/// Construct a new [`Div`] element
1186#[track_caller]
1187pub fn div() -> Div {
1188 Div {
1189 interactivity: Interactivity::new(),
1190 children: SmallVec::default(),
1191 prepaint_listener: None,
1192 image_cache: None,
1193 }
1194}
1195
1196/// A [`Div`] element, the all-in-one element for building complex UIs in GPUI
1197pub struct Div {
1198 interactivity: Interactivity,
1199 children: SmallVec<[StackSafe<AnyElement>; 2]>,
1200 prepaint_listener: Option<Box<dyn Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static>>,
1201 image_cache: Option<Box<dyn ImageCacheProvider>>,
1202}
1203
1204impl Div {
1205 /// Add a listener to be called when the children of this `Div` are prepainted.
1206 /// This allows you to store the [`Bounds`] of the children for later use.
1207 pub fn on_children_prepainted(
1208 mut self,
1209 listener: impl Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static,
1210 ) -> Self {
1211 self.prepaint_listener = Some(Box::new(listener));
1212 self
1213 }
1214
1215 /// Add an image cache at the location of this div in the element tree.
1216 pub fn image_cache(mut self, cache: impl ImageCacheProvider) -> Self {
1217 self.image_cache = Some(Box::new(cache));
1218 self
1219 }
1220}
1221
1222/// A frame state for a `Div` element, which contains layout IDs for its children.
1223///
1224/// This struct is used internally by the `Div` element to manage the layout state of its children
1225/// during the UI update cycle. It holds a small vector of `LayoutId` values, each corresponding to
1226/// a child element of the `Div`. These IDs are used to query the layout engine for the computed
1227/// bounds of the children after the layout phase is complete.
1228pub struct DivFrameState {
1229 child_layout_ids: SmallVec<[LayoutId; 2]>,
1230}
1231
1232/// Interactivity state displayed an manipulated in the inspector.
1233#[derive(Clone)]
1234pub struct DivInspectorState {
1235 /// The inspected element's base style. This is used for both inspecting and modifying the
1236 /// state. In the future it will make sense to separate the read and write, possibly tracking
1237 /// the modifications.
1238 #[cfg(any(feature = "inspector", debug_assertions))]
1239 pub base_style: Box<StyleRefinement>,
1240 /// Inspects the bounds of the element.
1241 pub bounds: Bounds<Pixels>,
1242 /// Size of the children of the element, or `bounds.size` if it has no children.
1243 pub content_size: Size<Pixels>,
1244}
1245
1246impl Styled for Div {
1247 fn style(&mut self) -> &mut StyleRefinement {
1248 &mut self.interactivity.base_style
1249 }
1250}
1251
1252impl InteractiveElement for Div {
1253 fn interactivity(&mut self) -> &mut Interactivity {
1254 &mut self.interactivity
1255 }
1256}
1257
1258impl ParentElement for Div {
1259 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1260 self.children
1261 .extend(elements.into_iter().map(StackSafe::new))
1262 }
1263}
1264
1265impl Element for Div {
1266 type RequestLayoutState = DivFrameState;
1267 type PrepaintState = Option<Hitbox>;
1268
1269 fn id(&self) -> Option<ElementId> {
1270 self.interactivity.element_id.clone()
1271 }
1272
1273 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
1274 self.interactivity.source_location()
1275 }
1276
1277 #[stacksafe]
1278 fn request_layout(
1279 &mut self,
1280 global_id: Option<&GlobalElementId>,
1281 inspector_id: Option<&InspectorElementId>,
1282 window: &mut Window,
1283 cx: &mut App,
1284 ) -> (LayoutId, Self::RequestLayoutState) {
1285 let mut child_layout_ids = SmallVec::new();
1286 let image_cache = self
1287 .image_cache
1288 .as_mut()
1289 .map(|provider| provider.provide(window, cx));
1290
1291 let layout_id = window.with_image_cache(image_cache, |window| {
1292 self.interactivity.request_layout(
1293 global_id,
1294 inspector_id,
1295 window,
1296 cx,
1297 |style, window, cx| {
1298 window.with_text_style(style.text_style().cloned(), |window| {
1299 child_layout_ids = self
1300 .children
1301 .iter_mut()
1302 .map(|child| child.request_layout(window, cx))
1303 .collect::<SmallVec<_>>();
1304 window.request_layout(style, child_layout_ids.iter().copied(), cx)
1305 })
1306 },
1307 )
1308 });
1309
1310 (layout_id, DivFrameState { child_layout_ids })
1311 }
1312
1313 #[stacksafe]
1314 fn prepaint(
1315 &mut self,
1316 global_id: Option<&GlobalElementId>,
1317 inspector_id: Option<&InspectorElementId>,
1318 bounds: Bounds<Pixels>,
1319 request_layout: &mut Self::RequestLayoutState,
1320 window: &mut Window,
1321 cx: &mut App,
1322 ) -> Option<Hitbox> {
1323 let has_prepaint_listener = self.prepaint_listener.is_some();
1324 let mut children_bounds = Vec::with_capacity(if has_prepaint_listener {
1325 request_layout.child_layout_ids.len()
1326 } else {
1327 0
1328 });
1329
1330 let mut child_min = point(Pixels::MAX, Pixels::MAX);
1331 let mut child_max = Point::default();
1332 if let Some(handle) = self.interactivity.scroll_anchor.as_ref() {
1333 *handle.last_origin.borrow_mut() = bounds.origin - window.element_offset();
1334 }
1335 let content_size = if request_layout.child_layout_ids.is_empty() {
1336 bounds.size
1337 } else if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
1338 let mut state = scroll_handle.0.borrow_mut();
1339 state.child_bounds = Vec::with_capacity(request_layout.child_layout_ids.len());
1340 for child_layout_id in &request_layout.child_layout_ids {
1341 let child_bounds = window.layout_bounds(*child_layout_id);
1342 child_min = child_min.min(&child_bounds.origin);
1343 child_max = child_max.max(&child_bounds.bottom_right());
1344 state.child_bounds.push(child_bounds);
1345 }
1346 (child_max - child_min).into()
1347 } else {
1348 for child_layout_id in &request_layout.child_layout_ids {
1349 let child_bounds = window.layout_bounds(*child_layout_id);
1350 child_min = child_min.min(&child_bounds.origin);
1351 child_max = child_max.max(&child_bounds.bottom_right());
1352
1353 if has_prepaint_listener {
1354 children_bounds.push(child_bounds);
1355 }
1356 }
1357 (child_max - child_min).into()
1358 };
1359
1360 self.interactivity.prepaint(
1361 global_id,
1362 inspector_id,
1363 bounds,
1364 content_size,
1365 window,
1366 cx,
1367 |_style, scroll_offset, hitbox, window, cx| {
1368 window.with_element_offset(scroll_offset, |window| {
1369 for child in &mut self.children {
1370 child.prepaint(window, cx);
1371 }
1372 });
1373
1374 if let Some(listener) = self.prepaint_listener.as_ref() {
1375 listener(children_bounds, window, cx);
1376 }
1377
1378 hitbox
1379 },
1380 )
1381 }
1382
1383 #[stacksafe]
1384 fn paint(
1385 &mut self,
1386 global_id: Option<&GlobalElementId>,
1387 inspector_id: Option<&InspectorElementId>,
1388 bounds: Bounds<Pixels>,
1389 _request_layout: &mut Self::RequestLayoutState,
1390 hitbox: &mut Option<Hitbox>,
1391 window: &mut Window,
1392 cx: &mut App,
1393 ) {
1394 let image_cache = self
1395 .image_cache
1396 .as_mut()
1397 .map(|provider| provider.provide(window, cx));
1398
1399 window.with_image_cache(image_cache, |window| {
1400 self.interactivity.paint(
1401 global_id,
1402 inspector_id,
1403 bounds,
1404 hitbox.as_ref(),
1405 window,
1406 cx,
1407 |_style, window, cx| {
1408 for child in &mut self.children {
1409 child.paint(window, cx);
1410 }
1411 },
1412 )
1413 });
1414 }
1415}
1416
1417impl IntoElement for Div {
1418 type Element = Self;
1419
1420 fn into_element(self) -> Self::Element {
1421 self
1422 }
1423}
1424
1425/// The interactivity struct. Powers all of the general-purpose
1426/// interactivity in the `Div` element.
1427#[derive(Default)]
1428pub struct Interactivity {
1429 /// The element ID of the element. In id is required to support a stateful subset of the interactivity such as on_click.
1430 pub element_id: Option<ElementId>,
1431 /// Whether the element was clicked. This will only be present after layout.
1432 pub active: Option<bool>,
1433 /// Whether the element was hovered. This will only be present after paint if an hitbox
1434 /// was created for the interactive element.
1435 pub hovered: Option<bool>,
1436 pub(crate) tooltip_id: Option<TooltipId>,
1437 pub(crate) content_size: Size<Pixels>,
1438 pub(crate) key_context: Option<KeyContext>,
1439 pub(crate) focusable: bool,
1440 pub(crate) tracked_focus_handle: Option<FocusHandle>,
1441 pub(crate) tracked_scroll_handle: Option<ScrollHandle>,
1442 pub(crate) scroll_anchor: Option<ScrollAnchor>,
1443 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
1444 pub(crate) group: Option<SharedString>,
1445 /// The base style of the element, before any modifications are applied
1446 /// by focus, active, etc.
1447 pub base_style: Box<StyleRefinement>,
1448 pub(crate) focus_style: Option<Box<StyleRefinement>>,
1449 pub(crate) in_focus_style: Option<Box<StyleRefinement>>,
1450 pub(crate) hover_style: Option<Box<StyleRefinement>>,
1451 pub(crate) group_hover_style: Option<GroupStyle>,
1452 pub(crate) active_style: Option<Box<StyleRefinement>>,
1453 pub(crate) group_active_style: Option<GroupStyle>,
1454 pub(crate) drag_over_styles: Vec<(
1455 TypeId,
1456 Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> StyleRefinement>,
1457 )>,
1458 pub(crate) group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
1459 pub(crate) mouse_down_listeners: Vec<MouseDownListener>,
1460 pub(crate) mouse_up_listeners: Vec<MouseUpListener>,
1461 pub(crate) mouse_move_listeners: Vec<MouseMoveListener>,
1462 pub(crate) scroll_wheel_listeners: Vec<ScrollWheelListener>,
1463 pub(crate) key_down_listeners: Vec<KeyDownListener>,
1464 pub(crate) key_up_listeners: Vec<KeyUpListener>,
1465 pub(crate) modifiers_changed_listeners: Vec<ModifiersChangedListener>,
1466 pub(crate) action_listeners: Vec<(TypeId, ActionListener)>,
1467 pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
1468 pub(crate) can_drop_predicate: Option<CanDropPredicate>,
1469 pub(crate) click_listeners: Vec<ClickListener>,
1470 pub(crate) drag_listener: Option<(Arc<dyn Any>, DragListener)>,
1471 pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut Window, &mut App)>>,
1472 pub(crate) tooltip_builder: Option<TooltipBuilder>,
1473 pub(crate) window_control: Option<WindowControlArea>,
1474 pub(crate) hitbox_behavior: HitboxBehavior,
1475 pub(crate) tab_index: Option<isize>,
1476
1477 #[cfg(any(feature = "inspector", debug_assertions))]
1478 pub(crate) source_location: Option<&'static core::panic::Location<'static>>,
1479
1480 #[cfg(any(test, feature = "test-support"))]
1481 pub(crate) debug_selector: Option<String>,
1482}
1483
1484impl Interactivity {
1485 /// Layout this element according to this interactivity state's configured styles
1486 pub fn request_layout(
1487 &mut self,
1488 global_id: Option<&GlobalElementId>,
1489 _inspector_id: Option<&InspectorElementId>,
1490 window: &mut Window,
1491 cx: &mut App,
1492 f: impl FnOnce(Style, &mut Window, &mut App) -> LayoutId,
1493 ) -> LayoutId {
1494 #[cfg(any(feature = "inspector", debug_assertions))]
1495 window.with_inspector_state(
1496 _inspector_id,
1497 cx,
1498 |inspector_state: &mut Option<DivInspectorState>, _window| {
1499 if let Some(inspector_state) = inspector_state {
1500 self.base_style = inspector_state.base_style.clone();
1501 } else {
1502 *inspector_state = Some(DivInspectorState {
1503 base_style: self.base_style.clone(),
1504 bounds: Default::default(),
1505 content_size: Default::default(),
1506 })
1507 }
1508 },
1509 );
1510
1511 window.with_optional_element_state::<InteractiveElementState, _>(
1512 global_id,
1513 |element_state, window| {
1514 let mut element_state =
1515 element_state.map(|element_state| element_state.unwrap_or_default());
1516
1517 if let Some(element_state) = element_state.as_ref() {
1518 if cx.has_active_drag() {
1519 if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref()
1520 {
1521 *pending_mouse_down.borrow_mut() = None;
1522 }
1523 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
1524 *clicked_state.borrow_mut() = ElementClickedState::default();
1525 }
1526 }
1527 }
1528
1529 // Ensure we store a focus handle in our element state if we're focusable.
1530 // If there's an explicit focus handle we're tracking, use that. Otherwise
1531 // create a new handle and store it in the element state, which lives for as
1532 // as frames contain an element with this id.
1533 if self.focusable && self.tracked_focus_handle.is_none() {
1534 if let Some(element_state) = element_state.as_mut() {
1535 let mut handle = element_state
1536 .focus_handle
1537 .get_or_insert_with(|| cx.focus_handle())
1538 .clone()
1539 .tab_stop(false);
1540
1541 if let Some(index) = self.tab_index {
1542 handle = handle.tab_index(index).tab_stop(true);
1543 }
1544
1545 self.tracked_focus_handle = Some(handle);
1546 }
1547 }
1548
1549 if let Some(scroll_handle) = self.tracked_scroll_handle.as_ref() {
1550 self.scroll_offset = Some(scroll_handle.0.borrow().offset.clone());
1551 } else if self.base_style.overflow.x == Some(Overflow::Scroll)
1552 || self.base_style.overflow.y == Some(Overflow::Scroll)
1553 {
1554 if let Some(element_state) = element_state.as_mut() {
1555 self.scroll_offset = Some(
1556 element_state
1557 .scroll_offset
1558 .get_or_insert_with(Rc::default)
1559 .clone(),
1560 );
1561 }
1562 }
1563
1564 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
1565 let layout_id = f(style, window, cx);
1566 (layout_id, element_state)
1567 },
1568 )
1569 }
1570
1571 /// Commit the bounds of this element according to this interactivity state's configured styles.
1572 pub fn prepaint<R>(
1573 &mut self,
1574 global_id: Option<&GlobalElementId>,
1575 _inspector_id: Option<&InspectorElementId>,
1576 bounds: Bounds<Pixels>,
1577 content_size: Size<Pixels>,
1578 window: &mut Window,
1579 cx: &mut App,
1580 f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut Window, &mut App) -> R,
1581 ) -> R {
1582 self.content_size = content_size;
1583
1584 #[cfg(any(feature = "inspector", debug_assertions))]
1585 window.with_inspector_state(
1586 _inspector_id,
1587 cx,
1588 |inspector_state: &mut Option<DivInspectorState>, _window| {
1589 if let Some(inspector_state) = inspector_state {
1590 inspector_state.bounds = bounds;
1591 inspector_state.content_size = content_size;
1592 }
1593 },
1594 );
1595
1596 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
1597 window.set_focus_handle(focus_handle, cx);
1598 }
1599 window.with_optional_element_state::<InteractiveElementState, _>(
1600 global_id,
1601 |element_state, window| {
1602 let mut element_state =
1603 element_state.map(|element_state| element_state.unwrap_or_default());
1604 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
1605
1606 if let Some(element_state) = element_state.as_mut() {
1607 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
1608 let clicked_state = clicked_state.borrow();
1609 self.active = Some(clicked_state.element);
1610 }
1611 if let Some(active_tooltip) = element_state.active_tooltip.as_ref() {
1612 if self.tooltip_builder.is_some() {
1613 self.tooltip_id = set_tooltip_on_window(active_tooltip, window);
1614 } else {
1615 // If there is no longer a tooltip builder, remove the active tooltip.
1616 element_state.active_tooltip.take();
1617 }
1618 }
1619 }
1620
1621 window.with_text_style(style.text_style().cloned(), |window| {
1622 window.with_content_mask(
1623 style.overflow_mask(bounds, window.rem_size()),
1624 |window| {
1625 let hitbox = if self.should_insert_hitbox(&style, window, cx) {
1626 Some(window.insert_hitbox(bounds, self.hitbox_behavior))
1627 } else {
1628 None
1629 };
1630
1631 let scroll_offset =
1632 self.clamp_scroll_position(bounds, &style, window, cx);
1633 let result = f(&style, scroll_offset, hitbox, window, cx);
1634 (result, element_state)
1635 },
1636 )
1637 })
1638 },
1639 )
1640 }
1641
1642 fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
1643 self.hitbox_behavior != HitboxBehavior::Normal
1644 || self.window_control.is_some()
1645 || style.mouse_cursor.is_some()
1646 || self.group.is_some()
1647 || self.scroll_offset.is_some()
1648 || self.tracked_focus_handle.is_some()
1649 || self.hover_style.is_some()
1650 || self.group_hover_style.is_some()
1651 || self.hover_listener.is_some()
1652 || !self.mouse_up_listeners.is_empty()
1653 || !self.mouse_down_listeners.is_empty()
1654 || !self.mouse_move_listeners.is_empty()
1655 || !self.click_listeners.is_empty()
1656 || !self.scroll_wheel_listeners.is_empty()
1657 || self.drag_listener.is_some()
1658 || !self.drop_listeners.is_empty()
1659 || self.tooltip_builder.is_some()
1660 || window.is_inspector_picking(cx)
1661 }
1662
1663 fn clamp_scroll_position(
1664 &self,
1665 bounds: Bounds<Pixels>,
1666 style: &Style,
1667 window: &mut Window,
1668 _cx: &mut App,
1669 ) -> Point<Pixels> {
1670 fn round_to_two_decimals(pixels: Pixels) -> Pixels {
1671 const ROUNDING_FACTOR: f32 = 100.0;
1672 (pixels * ROUNDING_FACTOR).round() / ROUNDING_FACTOR
1673 }
1674
1675 if let Some(scroll_offset) = self.scroll_offset.as_ref() {
1676 let mut scroll_to_bottom = false;
1677 let mut tracked_scroll_handle = self
1678 .tracked_scroll_handle
1679 .as_ref()
1680 .map(|handle| handle.0.borrow_mut());
1681 if let Some(mut scroll_handle_state) = tracked_scroll_handle.as_deref_mut() {
1682 scroll_handle_state.overflow = style.overflow;
1683 scroll_to_bottom = mem::take(&mut scroll_handle_state.scroll_to_bottom);
1684 }
1685
1686 let rem_size = window.rem_size();
1687 let padding = style.padding.to_pixels(bounds.size.into(), rem_size);
1688 let padding_size = size(padding.left + padding.right, padding.top + padding.bottom);
1689 // The floating point values produced by Taffy and ours often vary
1690 // slightly after ~5 decimal places. This can lead to cases where after
1691 // subtracting these, the container becomes scrollable for less than
1692 // 0.00000x pixels. As we generally don't benefit from a precision that
1693 // high for the maximum scroll, we round the scroll max to 2 decimal
1694 // places here.
1695 let padded_content_size = self.content_size + padding_size;
1696 let scroll_max = (padded_content_size - bounds.size)
1697 .map(round_to_two_decimals)
1698 .max(&Default::default());
1699 // Clamp scroll offset in case scroll max is smaller now (e.g., if children
1700 // were removed or the bounds became larger).
1701 let mut scroll_offset = scroll_offset.borrow_mut();
1702
1703 scroll_offset.x = scroll_offset.x.clamp(-scroll_max.width, px(0.));
1704 if scroll_to_bottom {
1705 scroll_offset.y = -scroll_max.height;
1706 } else {
1707 scroll_offset.y = scroll_offset.y.clamp(-scroll_max.height, px(0.));
1708 }
1709
1710 if let Some(mut scroll_handle_state) = tracked_scroll_handle {
1711 scroll_handle_state.max_offset = scroll_max;
1712 scroll_handle_state.bounds = bounds;
1713 }
1714
1715 *scroll_offset
1716 } else {
1717 Point::default()
1718 }
1719 }
1720
1721 /// Paint this element according to this interactivity state's configured styles
1722 /// and bind the element's mouse and keyboard events.
1723 ///
1724 /// content_size is the size of the content of the element, which may be larger than the
1725 /// element's bounds if the element is scrollable.
1726 ///
1727 /// the final computed style will be passed to the provided function, along
1728 /// with the current scroll offset
1729 pub fn paint(
1730 &mut self,
1731 global_id: Option<&GlobalElementId>,
1732 _inspector_id: Option<&InspectorElementId>,
1733 bounds: Bounds<Pixels>,
1734 hitbox: Option<&Hitbox>,
1735 window: &mut Window,
1736 cx: &mut App,
1737 f: impl FnOnce(&Style, &mut Window, &mut App),
1738 ) {
1739 self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(window));
1740 window.with_optional_element_state::<InteractiveElementState, _>(
1741 global_id,
1742 |element_state, window| {
1743 let mut element_state =
1744 element_state.map(|element_state| element_state.unwrap_or_default());
1745
1746 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
1747
1748 #[cfg(any(feature = "test-support", test))]
1749 if let Some(debug_selector) = &self.debug_selector {
1750 window
1751 .next_frame
1752 .debug_bounds
1753 .insert(debug_selector.clone(), bounds);
1754 }
1755
1756 self.paint_hover_group_handler(window, cx);
1757
1758 if style.visibility == Visibility::Hidden {
1759 return ((), element_state);
1760 }
1761
1762 if let Some(focus_handle) = &self.tracked_focus_handle {
1763 window.next_frame.tab_handles.insert(focus_handle);
1764 }
1765
1766 window.with_element_opacity(style.opacity, |window| {
1767 style.paint(bounds, window, cx, |window: &mut Window, cx: &mut App| {
1768 window.with_text_style(style.text_style().cloned(), |window| {
1769 window.with_content_mask(
1770 style.overflow_mask(bounds, window.rem_size()),
1771 |window| {
1772 if let Some(hitbox) = hitbox {
1773 #[cfg(debug_assertions)]
1774 self.paint_debug_info(
1775 global_id, hitbox, &style, window, cx,
1776 );
1777
1778 if let Some(drag) = cx.active_drag.as_ref() {
1779 if let Some(mouse_cursor) = drag.cursor_style {
1780 window.set_window_cursor_style(mouse_cursor);
1781 }
1782 } else {
1783 if let Some(mouse_cursor) = style.mouse_cursor {
1784 window.set_cursor_style(mouse_cursor, hitbox);
1785 }
1786 }
1787
1788 if let Some(group) = self.group.clone() {
1789 GroupHitboxes::push(group, hitbox.id, cx);
1790 }
1791
1792 if let Some(area) = self.window_control {
1793 window
1794 .insert_window_control_hitbox(area, hitbox.clone());
1795 }
1796
1797 self.paint_mouse_listeners(
1798 hitbox,
1799 element_state.as_mut(),
1800 window,
1801 cx,
1802 );
1803 self.paint_scroll_listener(hitbox, &style, window, cx);
1804 }
1805
1806 self.paint_keyboard_listeners(window, cx);
1807 f(&style, window, cx);
1808
1809 if let Some(_hitbox) = hitbox {
1810 #[cfg(any(feature = "inspector", debug_assertions))]
1811 window.insert_inspector_hitbox(
1812 _hitbox.id,
1813 _inspector_id,
1814 cx,
1815 );
1816
1817 if let Some(group) = self.group.as_ref() {
1818 GroupHitboxes::pop(group, cx);
1819 }
1820 }
1821 },
1822 );
1823 });
1824 });
1825 });
1826
1827 ((), element_state)
1828 },
1829 );
1830 }
1831
1832 #[cfg(debug_assertions)]
1833 fn paint_debug_info(
1834 &self,
1835 global_id: Option<&GlobalElementId>,
1836 hitbox: &Hitbox,
1837 style: &Style,
1838 window: &mut Window,
1839 cx: &mut App,
1840 ) {
1841 use crate::{BorderStyle, TextAlign};
1842
1843 if global_id.is_some()
1844 && (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
1845 && hitbox.is_hovered(window)
1846 {
1847 const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
1848 let element_id = format!("{:?}", global_id.unwrap());
1849 let str_len = element_id.len();
1850
1851 let render_debug_text = |window: &mut Window| {
1852 if let Some(text) = window
1853 .text_system()
1854 .shape_text(
1855 element_id.into(),
1856 FONT_SIZE,
1857 &[window.text_style().to_run(str_len)],
1858 None,
1859 None,
1860 )
1861 .ok()
1862 .and_then(|mut text| text.pop())
1863 {
1864 text.paint(hitbox.origin, FONT_SIZE, TextAlign::Left, None, window, cx)
1865 .ok();
1866
1867 let text_bounds = crate::Bounds {
1868 origin: hitbox.origin,
1869 size: text.size(FONT_SIZE),
1870 };
1871 if self.source_location.is_some()
1872 && text_bounds.contains(&window.mouse_position())
1873 && window.modifiers().secondary()
1874 {
1875 let secondary_held = window.modifiers().secondary();
1876 window.on_key_event({
1877 move |e: &crate::ModifiersChangedEvent, _phase, window, _cx| {
1878 if e.modifiers.secondary() != secondary_held
1879 && text_bounds.contains(&window.mouse_position())
1880 {
1881 window.refresh();
1882 }
1883 }
1884 });
1885
1886 let was_hovered = hitbox.is_hovered(window);
1887 let current_view = window.current_view();
1888 window.on_mouse_event({
1889 let hitbox = hitbox.clone();
1890 move |_: &MouseMoveEvent, phase, window, cx| {
1891 if phase == DispatchPhase::Capture {
1892 let hovered = hitbox.is_hovered(window);
1893 if hovered != was_hovered {
1894 cx.notify(current_view)
1895 }
1896 }
1897 }
1898 });
1899
1900 window.on_mouse_event({
1901 let hitbox = hitbox.clone();
1902 let location = self.source_location.unwrap();
1903 move |e: &crate::MouseDownEvent, phase, window, cx| {
1904 if text_bounds.contains(&e.position)
1905 && phase.capture()
1906 && hitbox.is_hovered(window)
1907 {
1908 cx.stop_propagation();
1909 let Ok(dir) = std::env::current_dir() else {
1910 return;
1911 };
1912
1913 eprintln!(
1914 "This element was created at:\n{}:{}:{}",
1915 dir.join(location.file()).to_string_lossy(),
1916 location.line(),
1917 location.column()
1918 );
1919 }
1920 }
1921 });
1922 window.paint_quad(crate::outline(
1923 crate::Bounds {
1924 origin: hitbox.origin
1925 + crate::point(crate::px(0.), FONT_SIZE - px(2.)),
1926 size: crate::Size {
1927 width: text_bounds.size.width,
1928 height: crate::px(1.),
1929 },
1930 },
1931 crate::red(),
1932 BorderStyle::default(),
1933 ))
1934 }
1935 }
1936 };
1937
1938 window.with_text_style(
1939 Some(crate::TextStyleRefinement {
1940 color: Some(crate::red()),
1941 line_height: Some(FONT_SIZE.into()),
1942 background_color: Some(crate::white()),
1943 ..Default::default()
1944 }),
1945 render_debug_text,
1946 )
1947 }
1948 }
1949
1950 fn paint_mouse_listeners(
1951 &mut self,
1952 hitbox: &Hitbox,
1953 element_state: Option<&mut InteractiveElementState>,
1954 window: &mut Window,
1955 cx: &mut App,
1956 ) {
1957 let is_focused = self
1958 .tracked_focus_handle
1959 .as_ref()
1960 .map(|handle| handle.is_focused(window))
1961 .unwrap_or(false);
1962
1963 // If this element can be focused, register a mouse down listener
1964 // that will automatically transfer focus when hitting the element.
1965 // This behavior can be suppressed by using `cx.prevent_default()`.
1966 if let Some(focus_handle) = self.tracked_focus_handle.clone() {
1967 let hitbox = hitbox.clone();
1968 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _| {
1969 if phase == DispatchPhase::Bubble
1970 && hitbox.is_hovered(window)
1971 && !window.default_prevented()
1972 {
1973 window.focus(&focus_handle);
1974 // If there is a parent that is also focusable, prevent it
1975 // from transferring focus because we already did so.
1976 window.prevent_default();
1977 }
1978 });
1979 }
1980
1981 for listener in self.mouse_down_listeners.drain(..) {
1982 let hitbox = hitbox.clone();
1983 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
1984 listener(event, phase, &hitbox, window, cx);
1985 })
1986 }
1987
1988 for listener in self.mouse_up_listeners.drain(..) {
1989 let hitbox = hitbox.clone();
1990 window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
1991 listener(event, phase, &hitbox, window, cx);
1992 })
1993 }
1994
1995 for listener in self.mouse_move_listeners.drain(..) {
1996 let hitbox = hitbox.clone();
1997 window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
1998 listener(event, phase, &hitbox, window, cx);
1999 })
2000 }
2001
2002 for listener in self.scroll_wheel_listeners.drain(..) {
2003 let hitbox = hitbox.clone();
2004 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
2005 listener(event, phase, &hitbox, window, cx);
2006 })
2007 }
2008
2009 if self.hover_style.is_some()
2010 || self.base_style.mouse_cursor.is_some()
2011 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
2012 {
2013 let hitbox = hitbox.clone();
2014 let was_hovered = hitbox.is_hovered(window);
2015 let current_view = window.current_view();
2016 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2017 let hovered = hitbox.is_hovered(window);
2018 if phase == DispatchPhase::Capture && hovered != was_hovered {
2019 cx.notify(current_view);
2020 }
2021 });
2022 }
2023 let drag_cursor_style = self.base_style.as_ref().mouse_cursor;
2024
2025 let mut drag_listener = mem::take(&mut self.drag_listener);
2026 let drop_listeners = mem::take(&mut self.drop_listeners);
2027 let click_listeners = mem::take(&mut self.click_listeners);
2028 let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
2029
2030 if !drop_listeners.is_empty() {
2031 let hitbox = hitbox.clone();
2032 window.on_mouse_event({
2033 move |_: &MouseUpEvent, phase, window, cx| {
2034 if let Some(drag) = &cx.active_drag {
2035 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
2036 let drag_state_type = drag.value.as_ref().type_id();
2037 for (drop_state_type, listener) in &drop_listeners {
2038 if *drop_state_type == drag_state_type {
2039 let drag = cx
2040 .active_drag
2041 .take()
2042 .expect("checked for type drag state type above");
2043
2044 let mut can_drop = true;
2045 if let Some(predicate) = &can_drop_predicate {
2046 can_drop = predicate(drag.value.as_ref(), window, cx);
2047 }
2048
2049 if can_drop {
2050 listener(drag.value.as_ref(), window, cx);
2051 window.refresh();
2052 cx.stop_propagation();
2053 }
2054 }
2055 }
2056 }
2057 }
2058 }
2059 });
2060 }
2061
2062 if let Some(element_state) = element_state {
2063 if !click_listeners.is_empty() || drag_listener.is_some() {
2064 let pending_mouse_down = element_state
2065 .pending_mouse_down
2066 .get_or_insert_with(Default::default)
2067 .clone();
2068
2069 let clicked_state = element_state
2070 .clicked_state
2071 .get_or_insert_with(Default::default)
2072 .clone();
2073
2074 window.on_mouse_event({
2075 let pending_mouse_down = pending_mouse_down.clone();
2076 let hitbox = hitbox.clone();
2077 move |event: &MouseDownEvent, phase, window, _cx| {
2078 if phase == DispatchPhase::Bubble
2079 && event.button == MouseButton::Left
2080 && hitbox.is_hovered(window)
2081 {
2082 *pending_mouse_down.borrow_mut() = Some(event.clone());
2083 window.refresh();
2084 }
2085 }
2086 });
2087
2088 window.on_mouse_event({
2089 let pending_mouse_down = pending_mouse_down.clone();
2090 let hitbox = hitbox.clone();
2091 move |event: &MouseMoveEvent, phase, window, cx| {
2092 if phase == DispatchPhase::Capture {
2093 return;
2094 }
2095
2096 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2097 if let Some(mouse_down) = pending_mouse_down.clone() {
2098 if !cx.has_active_drag()
2099 && (event.position - mouse_down.position).magnitude()
2100 > DRAG_THRESHOLD
2101 {
2102 if let Some((drag_value, drag_listener)) = drag_listener.take() {
2103 *clicked_state.borrow_mut() = ElementClickedState::default();
2104 let cursor_offset = event.position - hitbox.origin;
2105 let drag = (drag_listener)(
2106 drag_value.as_ref(),
2107 cursor_offset,
2108 window,
2109 cx,
2110 );
2111 cx.active_drag = Some(AnyDrag {
2112 view: drag,
2113 value: drag_value,
2114 cursor_offset,
2115 cursor_style: drag_cursor_style,
2116 });
2117 pending_mouse_down.take();
2118 window.refresh();
2119 cx.stop_propagation();
2120 }
2121 }
2122 }
2123 }
2124 });
2125
2126 if is_focused {
2127 // Press enter, space to trigger click, when the element is focused.
2128 window.on_key_event({
2129 let click_listeners = click_listeners.clone();
2130 let hitbox = hitbox.clone();
2131 move |event: &KeyUpEvent, phase, window, cx| {
2132 if phase.bubble() && !window.default_prevented() {
2133 let stroke = &event.keystroke;
2134 let keyboard_button = if stroke.key.eq("enter") {
2135 Some(KeyboardButton::Enter)
2136 } else if stroke.key.eq("space") {
2137 Some(KeyboardButton::Space)
2138 } else {
2139 None
2140 };
2141
2142 if let Some(button) = keyboard_button
2143 && !stroke.modifiers.modified()
2144 {
2145 let click_event = ClickEvent::Keyboard(KeyboardClickEvent {
2146 button,
2147 bounds: hitbox.bounds,
2148 });
2149
2150 for listener in &click_listeners {
2151 listener(&click_event, window, cx);
2152 }
2153 }
2154 }
2155 }
2156 });
2157 }
2158
2159 window.on_mouse_event({
2160 let mut captured_mouse_down = None;
2161 let hitbox = hitbox.clone();
2162 move |event: &MouseUpEvent, phase, window, cx| match phase {
2163 // Clear the pending mouse down during the capture phase,
2164 // so that it happens even if another event handler stops
2165 // propagation.
2166 DispatchPhase::Capture => {
2167 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2168 if pending_mouse_down.is_some() && hitbox.is_hovered(window) {
2169 captured_mouse_down = pending_mouse_down.take();
2170 window.refresh();
2171 } else if pending_mouse_down.is_some() {
2172 // Clear the pending mouse down event (without firing click handlers)
2173 // if the hitbox is not being hovered.
2174 // This avoids dragging elements that changed their position
2175 // immediately after being clicked.
2176 // See https://github.com/zed-industries/zed/issues/24600 for more details
2177 pending_mouse_down.take();
2178 window.refresh();
2179 }
2180 }
2181 // Fire click handlers during the bubble phase.
2182 DispatchPhase::Bubble => {
2183 if let Some(mouse_down) = captured_mouse_down.take() {
2184 let mouse_click = ClickEvent::Mouse(MouseClickEvent {
2185 down: mouse_down,
2186 up: event.clone(),
2187 });
2188 for listener in &click_listeners {
2189 listener(&mouse_click, window, cx);
2190 }
2191 }
2192 }
2193 }
2194 });
2195 }
2196
2197 if let Some(hover_listener) = self.hover_listener.take() {
2198 let hitbox = hitbox.clone();
2199 let was_hovered = element_state
2200 .hover_state
2201 .get_or_insert_with(Default::default)
2202 .clone();
2203 let has_mouse_down = element_state
2204 .pending_mouse_down
2205 .get_or_insert_with(Default::default)
2206 .clone();
2207
2208 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2209 if phase != DispatchPhase::Bubble {
2210 return;
2211 }
2212 let is_hovered = has_mouse_down.borrow().is_none()
2213 && !cx.has_active_drag()
2214 && hitbox.is_hovered(window);
2215 let mut was_hovered = was_hovered.borrow_mut();
2216
2217 if is_hovered != *was_hovered {
2218 *was_hovered = is_hovered;
2219 drop(was_hovered);
2220
2221 hover_listener(&is_hovered, window, cx);
2222 }
2223 });
2224 }
2225
2226 if let Some(tooltip_builder) = self.tooltip_builder.take() {
2227 let active_tooltip = element_state
2228 .active_tooltip
2229 .get_or_insert_with(Default::default)
2230 .clone();
2231 let pending_mouse_down = element_state
2232 .pending_mouse_down
2233 .get_or_insert_with(Default::default)
2234 .clone();
2235
2236 let tooltip_is_hoverable = tooltip_builder.hoverable;
2237 let build_tooltip = Rc::new(move |window: &mut Window, cx: &mut App| {
2238 Some(((tooltip_builder.build)(window, cx), tooltip_is_hoverable))
2239 });
2240 // Use bounds instead of testing hitbox since this is called during prepaint.
2241 let check_is_hovered_during_prepaint = Rc::new({
2242 let pending_mouse_down = pending_mouse_down.clone();
2243 let source_bounds = hitbox.bounds;
2244 move |window: &Window| {
2245 pending_mouse_down.borrow().is_none()
2246 && source_bounds.contains(&window.mouse_position())
2247 }
2248 });
2249 let check_is_hovered = Rc::new({
2250 let hitbox = hitbox.clone();
2251 move |window: &Window| {
2252 pending_mouse_down.borrow().is_none() && hitbox.is_hovered(window)
2253 }
2254 });
2255 register_tooltip_mouse_handlers(
2256 &active_tooltip,
2257 self.tooltip_id,
2258 build_tooltip,
2259 check_is_hovered,
2260 check_is_hovered_during_prepaint,
2261 window,
2262 );
2263 }
2264
2265 let active_state = element_state
2266 .clicked_state
2267 .get_or_insert_with(Default::default)
2268 .clone();
2269 if active_state.borrow().is_clicked() {
2270 window.on_mouse_event(move |_: &MouseUpEvent, phase, window, _cx| {
2271 if phase == DispatchPhase::Capture {
2272 *active_state.borrow_mut() = ElementClickedState::default();
2273 window.refresh();
2274 }
2275 });
2276 } else {
2277 let active_group_hitbox = self
2278 .group_active_style
2279 .as_ref()
2280 .and_then(|group_active| GroupHitboxes::get(&group_active.group, cx));
2281 let hitbox = hitbox.clone();
2282 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _cx| {
2283 if phase == DispatchPhase::Bubble && !window.default_prevented() {
2284 let group_hovered = active_group_hitbox
2285 .map_or(false, |group_hitbox_id| group_hitbox_id.is_hovered(window));
2286 let element_hovered = hitbox.is_hovered(window);
2287 if group_hovered || element_hovered {
2288 *active_state.borrow_mut() = ElementClickedState {
2289 group: group_hovered,
2290 element: element_hovered,
2291 };
2292 window.refresh();
2293 }
2294 }
2295 });
2296 }
2297 }
2298 }
2299
2300 fn paint_keyboard_listeners(&mut self, window: &mut Window, _cx: &mut App) {
2301 let key_down_listeners = mem::take(&mut self.key_down_listeners);
2302 let key_up_listeners = mem::take(&mut self.key_up_listeners);
2303 let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners);
2304 let action_listeners = mem::take(&mut self.action_listeners);
2305 if let Some(context) = self.key_context.clone() {
2306 window.set_key_context(context);
2307 }
2308
2309 for listener in key_down_listeners {
2310 window.on_key_event(move |event: &KeyDownEvent, phase, window, cx| {
2311 listener(event, phase, window, cx);
2312 })
2313 }
2314
2315 for listener in key_up_listeners {
2316 window.on_key_event(move |event: &KeyUpEvent, phase, window, cx| {
2317 listener(event, phase, window, cx);
2318 })
2319 }
2320
2321 for listener in modifiers_changed_listeners {
2322 window.on_modifiers_changed(move |event: &ModifiersChangedEvent, window, cx| {
2323 listener(event, window, cx);
2324 })
2325 }
2326
2327 for (action_type, listener) in action_listeners {
2328 window.on_action(action_type, listener)
2329 }
2330 }
2331
2332 fn paint_hover_group_handler(&self, window: &mut Window, cx: &mut App) {
2333 let group_hitbox = self
2334 .group_hover_style
2335 .as_ref()
2336 .and_then(|group_hover| GroupHitboxes::get(&group_hover.group, cx));
2337
2338 if let Some(group_hitbox) = group_hitbox {
2339 let was_hovered = group_hitbox.is_hovered(window);
2340 let current_view = window.current_view();
2341 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2342 let hovered = group_hitbox.is_hovered(window);
2343 if phase == DispatchPhase::Capture && hovered != was_hovered {
2344 cx.notify(current_view);
2345 }
2346 });
2347 }
2348 }
2349
2350 fn paint_scroll_listener(
2351 &self,
2352 hitbox: &Hitbox,
2353 style: &Style,
2354 window: &mut Window,
2355 _cx: &mut App,
2356 ) {
2357 if let Some(scroll_offset) = self.scroll_offset.clone() {
2358 let overflow = style.overflow;
2359 let allow_concurrent_scroll = style.allow_concurrent_scroll;
2360 let restrict_scroll_to_axis = style.restrict_scroll_to_axis;
2361 let line_height = window.line_height();
2362 let hitbox = hitbox.clone();
2363 let current_view = window.current_view();
2364 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
2365 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
2366 let mut scroll_offset = scroll_offset.borrow_mut();
2367 let old_scroll_offset = *scroll_offset;
2368 let delta = event.delta.pixel_delta(line_height);
2369
2370 let mut delta_x = Pixels::ZERO;
2371 if overflow.x == Overflow::Scroll {
2372 if !delta.x.is_zero() {
2373 delta_x = delta.x;
2374 } else if !restrict_scroll_to_axis && overflow.y != Overflow::Scroll {
2375 delta_x = delta.y;
2376 }
2377 }
2378 let mut delta_y = Pixels::ZERO;
2379 if overflow.y == Overflow::Scroll {
2380 if !delta.y.is_zero() {
2381 delta_y = delta.y;
2382 } else if !restrict_scroll_to_axis && overflow.x != Overflow::Scroll {
2383 delta_y = delta.x;
2384 }
2385 }
2386 if !allow_concurrent_scroll && !delta_x.is_zero() && !delta_y.is_zero() {
2387 if delta_x.abs() > delta_y.abs() {
2388 delta_y = Pixels::ZERO;
2389 } else {
2390 delta_x = Pixels::ZERO;
2391 }
2392 }
2393 scroll_offset.y += delta_y;
2394 scroll_offset.x += delta_x;
2395 if *scroll_offset != old_scroll_offset {
2396 cx.notify(current_view);
2397 }
2398 }
2399 });
2400 }
2401 }
2402
2403 /// Compute the visual style for this element, based on the current bounds and the element's state.
2404 pub fn compute_style(
2405 &self,
2406 global_id: Option<&GlobalElementId>,
2407 hitbox: Option<&Hitbox>,
2408 window: &mut Window,
2409 cx: &mut App,
2410 ) -> Style {
2411 window.with_optional_element_state(global_id, |element_state, window| {
2412 let mut element_state =
2413 element_state.map(|element_state| element_state.unwrap_or_default());
2414 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
2415 (style, element_state)
2416 })
2417 }
2418
2419 /// Called from internal methods that have already called with_element_state.
2420 fn compute_style_internal(
2421 &self,
2422 hitbox: Option<&Hitbox>,
2423 element_state: Option<&mut InteractiveElementState>,
2424 window: &mut Window,
2425 cx: &mut App,
2426 ) -> Style {
2427 let mut style = Style::default();
2428 style.refine(&self.base_style);
2429
2430 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
2431 if let Some(in_focus_style) = self.in_focus_style.as_ref() {
2432 if focus_handle.within_focused(window, cx) {
2433 style.refine(in_focus_style);
2434 }
2435 }
2436
2437 if let Some(focus_style) = self.focus_style.as_ref() {
2438 if focus_handle.is_focused(window) {
2439 style.refine(focus_style);
2440 }
2441 }
2442 }
2443
2444 if let Some(hitbox) = hitbox {
2445 if !cx.has_active_drag() {
2446 if let Some(group_hover) = self.group_hover_style.as_ref() {
2447 if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
2448 if group_hitbox_id.is_hovered(window) {
2449 style.refine(&group_hover.style);
2450 }
2451 }
2452 }
2453
2454 if let Some(hover_style) = self.hover_style.as_ref() {
2455 if hitbox.is_hovered(window) {
2456 style.refine(hover_style);
2457 }
2458 }
2459 }
2460
2461 if let Some(drag) = cx.active_drag.take() {
2462 let mut can_drop = true;
2463 if let Some(can_drop_predicate) = &self.can_drop_predicate {
2464 can_drop = can_drop_predicate(drag.value.as_ref(), window, cx);
2465 }
2466
2467 if can_drop {
2468 for (state_type, group_drag_style) in &self.group_drag_over_styles {
2469 if let Some(group_hitbox_id) =
2470 GroupHitboxes::get(&group_drag_style.group, cx)
2471 {
2472 if *state_type == drag.value.as_ref().type_id()
2473 && group_hitbox_id.is_hovered(window)
2474 {
2475 style.refine(&group_drag_style.style);
2476 }
2477 }
2478 }
2479
2480 for (state_type, build_drag_over_style) in &self.drag_over_styles {
2481 if *state_type == drag.value.as_ref().type_id() && hitbox.is_hovered(window)
2482 {
2483 style.refine(&build_drag_over_style(drag.value.as_ref(), window, cx));
2484 }
2485 }
2486 }
2487
2488 style.mouse_cursor = drag.cursor_style;
2489 cx.active_drag = Some(drag);
2490 }
2491 }
2492
2493 if let Some(element_state) = element_state {
2494 let clicked_state = element_state
2495 .clicked_state
2496 .get_or_insert_with(Default::default)
2497 .borrow();
2498 if clicked_state.group {
2499 if let Some(group) = self.group_active_style.as_ref() {
2500 style.refine(&group.style)
2501 }
2502 }
2503
2504 if let Some(active_style) = self.active_style.as_ref() {
2505 if clicked_state.element {
2506 style.refine(active_style)
2507 }
2508 }
2509 }
2510
2511 style
2512 }
2513}
2514
2515/// The per-frame state of an interactive element. Used for tracking stateful interactions like clicks
2516/// and scroll offsets.
2517#[derive(Default)]
2518pub struct InteractiveElementState {
2519 pub(crate) focus_handle: Option<FocusHandle>,
2520 pub(crate) clicked_state: Option<Rc<RefCell<ElementClickedState>>>,
2521 pub(crate) hover_state: Option<Rc<RefCell<bool>>>,
2522 pub(crate) pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
2523 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
2524 pub(crate) active_tooltip: Option<Rc<RefCell<Option<ActiveTooltip>>>>,
2525}
2526
2527/// Whether or not the element or a group that contains it is clicked by the mouse.
2528#[derive(Copy, Clone, Default, Eq, PartialEq)]
2529pub struct ElementClickedState {
2530 /// True if this element's group has been clicked, false otherwise
2531 pub group: bool,
2532
2533 /// True if this element has been clicked, false otherwise
2534 pub element: bool,
2535}
2536
2537impl ElementClickedState {
2538 fn is_clicked(&self) -> bool {
2539 self.group || self.element
2540 }
2541}
2542
2543pub(crate) enum ActiveTooltip {
2544 /// Currently delaying before showing the tooltip.
2545 WaitingForShow { _task: Task<()> },
2546 /// Tooltip is visible, element was hovered or for hoverable tooltips, the tooltip was hovered.
2547 Visible {
2548 tooltip: AnyTooltip,
2549 is_hoverable: bool,
2550 },
2551 /// Tooltip is visible and hoverable, but the mouse is no longer hovering. Currently delaying
2552 /// before hiding it.
2553 WaitingForHide {
2554 tooltip: AnyTooltip,
2555 _task: Task<()>,
2556 },
2557}
2558
2559pub(crate) fn clear_active_tooltip(
2560 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
2561 window: &mut Window,
2562) {
2563 match active_tooltip.borrow_mut().take() {
2564 None => {}
2565 Some(ActiveTooltip::WaitingForShow { .. }) => {}
2566 Some(ActiveTooltip::Visible { .. }) => window.refresh(),
2567 Some(ActiveTooltip::WaitingForHide { .. }) => window.refresh(),
2568 }
2569}
2570
2571pub(crate) fn clear_active_tooltip_if_not_hoverable(
2572 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
2573 window: &mut Window,
2574) {
2575 let should_clear = match active_tooltip.borrow().as_ref() {
2576 None => false,
2577 Some(ActiveTooltip::WaitingForShow { .. }) => false,
2578 Some(ActiveTooltip::Visible { is_hoverable, .. }) => !is_hoverable,
2579 Some(ActiveTooltip::WaitingForHide { .. }) => false,
2580 };
2581 if should_clear {
2582 active_tooltip.borrow_mut().take();
2583 window.refresh();
2584 }
2585}
2586
2587pub(crate) fn set_tooltip_on_window(
2588 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
2589 window: &mut Window,
2590) -> Option<TooltipId> {
2591 let tooltip = match active_tooltip.borrow().as_ref() {
2592 None => return None,
2593 Some(ActiveTooltip::WaitingForShow { .. }) => return None,
2594 Some(ActiveTooltip::Visible { tooltip, .. }) => tooltip.clone(),
2595 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => tooltip.clone(),
2596 };
2597 Some(window.set_tooltip(tooltip))
2598}
2599
2600pub(crate) fn register_tooltip_mouse_handlers(
2601 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
2602 tooltip_id: Option<TooltipId>,
2603 build_tooltip: Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
2604 check_is_hovered: Rc<dyn Fn(&Window) -> bool>,
2605 check_is_hovered_during_prepaint: Rc<dyn Fn(&Window) -> bool>,
2606 window: &mut Window,
2607) {
2608 window.on_mouse_event({
2609 let active_tooltip = active_tooltip.clone();
2610 let build_tooltip = build_tooltip.clone();
2611 let check_is_hovered = check_is_hovered.clone();
2612 move |_: &MouseMoveEvent, phase, window, cx| {
2613 handle_tooltip_mouse_move(
2614 &active_tooltip,
2615 &build_tooltip,
2616 &check_is_hovered,
2617 &check_is_hovered_during_prepaint,
2618 phase,
2619 window,
2620 cx,
2621 )
2622 }
2623 });
2624
2625 window.on_mouse_event({
2626 let active_tooltip = active_tooltip.clone();
2627 move |_: &MouseDownEvent, _phase, window: &mut Window, _cx| {
2628 if !tooltip_id.map_or(false, |tooltip_id| tooltip_id.is_hovered(window)) {
2629 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
2630 }
2631 }
2632 });
2633
2634 window.on_mouse_event({
2635 let active_tooltip = active_tooltip.clone();
2636 move |_: &ScrollWheelEvent, _phase, window: &mut Window, _cx| {
2637 if !tooltip_id.map_or(false, |tooltip_id| tooltip_id.is_hovered(window)) {
2638 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
2639 }
2640 }
2641 });
2642}
2643
2644/// Handles displaying tooltips when an element is hovered.
2645///
2646/// The mouse hovering logic also relies on being called from window prepaint in order to handle the
2647/// case where the element the tooltip is on is not rendered - in that case its mouse listeners are
2648/// also not registered. During window prepaint, the hitbox information is not available, so
2649/// `check_is_hovered_during_prepaint` is used which bases the check off of the absolute bounds of
2650/// the element.
2651///
2652/// TODO: There's a minor bug due to the use of absolute bounds while checking during prepaint - it
2653/// does not know if the hitbox is occluded. In the case where a tooltip gets displayed and then
2654/// gets occluded after display, it will stick around until the mouse exits the hover bounds.
2655fn handle_tooltip_mouse_move(
2656 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
2657 build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
2658 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
2659 check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
2660 phase: DispatchPhase,
2661 window: &mut Window,
2662 cx: &mut App,
2663) {
2664 // Separates logic for what mutation should occur from applying it, to avoid overlapping
2665 // RefCell borrows.
2666 enum Action {
2667 None,
2668 CancelShow,
2669 ScheduleShow,
2670 }
2671
2672 let action = match active_tooltip.borrow().as_ref() {
2673 None => {
2674 let is_hovered = check_is_hovered(window);
2675 if is_hovered && phase.bubble() {
2676 Action::ScheduleShow
2677 } else {
2678 Action::None
2679 }
2680 }
2681 Some(ActiveTooltip::WaitingForShow { .. }) => {
2682 let is_hovered = check_is_hovered(window);
2683 if is_hovered {
2684 Action::None
2685 } else {
2686 Action::CancelShow
2687 }
2688 }
2689 // These are handled in check_visible_and_update.
2690 Some(ActiveTooltip::Visible { .. }) | Some(ActiveTooltip::WaitingForHide { .. }) => {
2691 Action::None
2692 }
2693 };
2694
2695 match action {
2696 Action::None => {}
2697 Action::CancelShow => {
2698 // Cancel waiting to show tooltip when it is no longer hovered.
2699 active_tooltip.borrow_mut().take();
2700 }
2701 Action::ScheduleShow => {
2702 let delayed_show_task = window.spawn(cx, {
2703 let active_tooltip = active_tooltip.clone();
2704 let build_tooltip = build_tooltip.clone();
2705 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
2706 async move |cx| {
2707 cx.background_executor().timer(TOOLTIP_SHOW_DELAY).await;
2708 cx.update(|window, cx| {
2709 let new_tooltip =
2710 build_tooltip(window, cx).map(|(view, tooltip_is_hoverable)| {
2711 let active_tooltip = active_tooltip.clone();
2712 ActiveTooltip::Visible {
2713 tooltip: AnyTooltip {
2714 view,
2715 mouse_position: window.mouse_position(),
2716 check_visible_and_update: Rc::new(
2717 move |tooltip_bounds, window, cx| {
2718 handle_tooltip_check_visible_and_update(
2719 &active_tooltip,
2720 tooltip_is_hoverable,
2721 &check_is_hovered_during_prepaint,
2722 tooltip_bounds,
2723 window,
2724 cx,
2725 )
2726 },
2727 ),
2728 },
2729 is_hoverable: tooltip_is_hoverable,
2730 }
2731 });
2732 *active_tooltip.borrow_mut() = new_tooltip;
2733 window.refresh();
2734 })
2735 .ok();
2736 }
2737 });
2738 active_tooltip
2739 .borrow_mut()
2740 .replace(ActiveTooltip::WaitingForShow {
2741 _task: delayed_show_task,
2742 });
2743 }
2744 }
2745}
2746
2747/// Returns a callback which will be called by window prepaint to update tooltip visibility. The
2748/// purpose of doing this logic here instead of the mouse move handler is that the mouse move
2749/// handler won't get called when the element is not painted (e.g. via use of `visible_on_hover`).
2750fn handle_tooltip_check_visible_and_update(
2751 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
2752 tooltip_is_hoverable: bool,
2753 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
2754 tooltip_bounds: Bounds<Pixels>,
2755 window: &mut Window,
2756 cx: &mut App,
2757) -> bool {
2758 // Separates logic for what mutation should occur from applying it, to avoid overlapping RefCell
2759 // borrows.
2760 enum Action {
2761 None,
2762 Hide,
2763 ScheduleHide(AnyTooltip),
2764 CancelHide(AnyTooltip),
2765 }
2766
2767 let is_hovered = check_is_hovered(window)
2768 || (tooltip_is_hoverable && tooltip_bounds.contains(&window.mouse_position()));
2769 let action = match active_tooltip.borrow().as_ref() {
2770 Some(ActiveTooltip::Visible { tooltip, .. }) => {
2771 if is_hovered {
2772 Action::None
2773 } else {
2774 if tooltip_is_hoverable {
2775 Action::ScheduleHide(tooltip.clone())
2776 } else {
2777 Action::Hide
2778 }
2779 }
2780 }
2781 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => {
2782 if is_hovered {
2783 Action::CancelHide(tooltip.clone())
2784 } else {
2785 Action::None
2786 }
2787 }
2788 None | Some(ActiveTooltip::WaitingForShow { .. }) => Action::None,
2789 };
2790
2791 match action {
2792 Action::None => {}
2793 Action::Hide => clear_active_tooltip(active_tooltip, window),
2794 Action::ScheduleHide(tooltip) => {
2795 let delayed_hide_task = window.spawn(cx, {
2796 let active_tooltip = active_tooltip.clone();
2797 async move |cx| {
2798 cx.background_executor()
2799 .timer(HOVERABLE_TOOLTIP_HIDE_DELAY)
2800 .await;
2801 if active_tooltip.borrow_mut().take().is_some() {
2802 cx.update(|window, _cx| window.refresh()).ok();
2803 }
2804 }
2805 });
2806 active_tooltip
2807 .borrow_mut()
2808 .replace(ActiveTooltip::WaitingForHide {
2809 tooltip,
2810 _task: delayed_hide_task,
2811 });
2812 }
2813 Action::CancelHide(tooltip) => {
2814 // Cancel waiting to hide tooltip when it becomes hovered.
2815 active_tooltip.borrow_mut().replace(ActiveTooltip::Visible {
2816 tooltip,
2817 is_hoverable: true,
2818 });
2819 }
2820 }
2821
2822 active_tooltip.borrow().is_some()
2823}
2824
2825#[derive(Default)]
2826pub(crate) struct GroupHitboxes(HashMap<SharedString, SmallVec<[HitboxId; 1]>>);
2827
2828impl Global for GroupHitboxes {}
2829
2830impl GroupHitboxes {
2831 pub fn get(name: &SharedString, cx: &mut App) -> Option<HitboxId> {
2832 cx.default_global::<Self>()
2833 .0
2834 .get(name)
2835 .and_then(|bounds_stack| bounds_stack.last())
2836 .cloned()
2837 }
2838
2839 pub fn push(name: SharedString, hitbox_id: HitboxId, cx: &mut App) {
2840 cx.default_global::<Self>()
2841 .0
2842 .entry(name)
2843 .or_default()
2844 .push(hitbox_id);
2845 }
2846
2847 pub fn pop(name: &SharedString, cx: &mut App) {
2848 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
2849 }
2850}
2851
2852/// A wrapper around an element that can store state, produced after assigning an ElementId.
2853pub struct Stateful<E> {
2854 pub(crate) element: E,
2855}
2856
2857impl<E> Styled for Stateful<E>
2858where
2859 E: Styled,
2860{
2861 fn style(&mut self) -> &mut StyleRefinement {
2862 self.element.style()
2863 }
2864}
2865
2866impl<E> StatefulInteractiveElement for Stateful<E>
2867where
2868 E: Element,
2869 Self: InteractiveElement,
2870{
2871}
2872
2873impl<E> InteractiveElement for Stateful<E>
2874where
2875 E: InteractiveElement,
2876{
2877 fn interactivity(&mut self) -> &mut Interactivity {
2878 self.element.interactivity()
2879 }
2880}
2881
2882impl<E> Element for Stateful<E>
2883where
2884 E: Element,
2885{
2886 type RequestLayoutState = E::RequestLayoutState;
2887 type PrepaintState = E::PrepaintState;
2888
2889 fn id(&self) -> Option<ElementId> {
2890 self.element.id()
2891 }
2892
2893 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
2894 self.element.source_location()
2895 }
2896
2897 fn request_layout(
2898 &mut self,
2899 id: Option<&GlobalElementId>,
2900 inspector_id: Option<&InspectorElementId>,
2901 window: &mut Window,
2902 cx: &mut App,
2903 ) -> (LayoutId, Self::RequestLayoutState) {
2904 self.element.request_layout(id, inspector_id, window, cx)
2905 }
2906
2907 fn prepaint(
2908 &mut self,
2909 id: Option<&GlobalElementId>,
2910 inspector_id: Option<&InspectorElementId>,
2911 bounds: Bounds<Pixels>,
2912 state: &mut Self::RequestLayoutState,
2913 window: &mut Window,
2914 cx: &mut App,
2915 ) -> E::PrepaintState {
2916 self.element
2917 .prepaint(id, inspector_id, bounds, state, window, cx)
2918 }
2919
2920 fn paint(
2921 &mut self,
2922 id: Option<&GlobalElementId>,
2923 inspector_id: Option<&InspectorElementId>,
2924 bounds: Bounds<Pixels>,
2925 request_layout: &mut Self::RequestLayoutState,
2926 prepaint: &mut Self::PrepaintState,
2927 window: &mut Window,
2928 cx: &mut App,
2929 ) {
2930 self.element.paint(
2931 id,
2932 inspector_id,
2933 bounds,
2934 request_layout,
2935 prepaint,
2936 window,
2937 cx,
2938 );
2939 }
2940}
2941
2942impl<E> IntoElement for Stateful<E>
2943where
2944 E: Element,
2945{
2946 type Element = Self;
2947
2948 fn into_element(self) -> Self::Element {
2949 self
2950 }
2951}
2952
2953impl<E> ParentElement for Stateful<E>
2954where
2955 E: ParentElement,
2956{
2957 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
2958 self.element.extend(elements)
2959 }
2960}
2961
2962/// Represents an element that can be scrolled *to* in its parent element.
2963///
2964/// Contrary to [ScrollHandle::scroll_to_item], an anchored element does not have to be an immediate child of the parent.
2965#[derive(Clone)]
2966pub struct ScrollAnchor {
2967 handle: ScrollHandle,
2968 last_origin: Rc<RefCell<Point<Pixels>>>,
2969}
2970
2971impl ScrollAnchor {
2972 /// Creates a [ScrollAnchor] associated with a given [ScrollHandle].
2973 pub fn for_handle(handle: ScrollHandle) -> Self {
2974 Self {
2975 handle,
2976 last_origin: Default::default(),
2977 }
2978 }
2979 /// Request scroll to this item on the next frame.
2980 pub fn scroll_to(&self, window: &mut Window, _cx: &mut App) {
2981 let this = self.clone();
2982
2983 window.on_next_frame(move |_, _| {
2984 let viewport_bounds = this.handle.bounds();
2985 let self_bounds = *this.last_origin.borrow();
2986 this.handle.set_offset(viewport_bounds.origin - self_bounds);
2987 });
2988 }
2989}
2990
2991#[derive(Default, Debug)]
2992struct ScrollHandleState {
2993 offset: Rc<RefCell<Point<Pixels>>>,
2994 bounds: Bounds<Pixels>,
2995 max_offset: Size<Pixels>,
2996 child_bounds: Vec<Bounds<Pixels>>,
2997 scroll_to_bottom: bool,
2998 overflow: Point<Overflow>,
2999}
3000
3001/// A handle to the scrollable aspects of an element.
3002/// Used for accessing scroll state, like the current scroll offset,
3003/// and for mutating the scroll state, like scrolling to a specific child.
3004#[derive(Clone, Debug)]
3005pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
3006
3007impl Default for ScrollHandle {
3008 fn default() -> Self {
3009 Self::new()
3010 }
3011}
3012
3013impl ScrollHandle {
3014 /// Construct a new scroll handle.
3015 pub fn new() -> Self {
3016 Self(Rc::default())
3017 }
3018
3019 /// Get the current scroll offset.
3020 pub fn offset(&self) -> Point<Pixels> {
3021 *self.0.borrow().offset.borrow()
3022 }
3023
3024 /// Get the maximum scroll offset.
3025 pub fn max_offset(&self) -> Size<Pixels> {
3026 self.0.borrow().max_offset
3027 }
3028
3029 /// Get the top child that's scrolled into view.
3030 pub fn top_item(&self) -> usize {
3031 let state = self.0.borrow();
3032 let top = state.bounds.top() - state.offset.borrow().y;
3033
3034 match state.child_bounds.binary_search_by(|bounds| {
3035 if top < bounds.top() {
3036 Ordering::Greater
3037 } else if top > bounds.bottom() {
3038 Ordering::Less
3039 } else {
3040 Ordering::Equal
3041 }
3042 }) {
3043 Ok(ix) => ix,
3044 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
3045 }
3046 }
3047
3048 /// Return the bounds into which this child is painted
3049 pub fn bounds(&self) -> Bounds<Pixels> {
3050 self.0.borrow().bounds
3051 }
3052
3053 /// Get the bounds for a specific child.
3054 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
3055 self.0.borrow().child_bounds.get(ix).cloned()
3056 }
3057
3058 /// scroll_to_item scrolls the minimal amount to ensure that the child is
3059 /// fully visible
3060 pub fn scroll_to_item(&self, ix: usize) {
3061 let state = self.0.borrow();
3062
3063 let Some(bounds) = state.child_bounds.get(ix) else {
3064 return;
3065 };
3066
3067 let mut scroll_offset = state.offset.borrow_mut();
3068
3069 if state.overflow.y == Overflow::Scroll {
3070 if bounds.top() + scroll_offset.y < state.bounds.top() {
3071 scroll_offset.y = state.bounds.top() - bounds.top();
3072 } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
3073 scroll_offset.y = state.bounds.bottom() - bounds.bottom();
3074 }
3075 }
3076
3077 if state.overflow.x == Overflow::Scroll {
3078 if bounds.left() + scroll_offset.x < state.bounds.left() {
3079 scroll_offset.x = state.bounds.left() - bounds.left();
3080 } else if bounds.right() + scroll_offset.x > state.bounds.right() {
3081 scroll_offset.x = state.bounds.right() - bounds.right();
3082 }
3083 }
3084 }
3085
3086 /// Scrolls to the bottom.
3087 pub fn scroll_to_bottom(&self) {
3088 let mut state = self.0.borrow_mut();
3089 state.scroll_to_bottom = true;
3090 }
3091
3092 /// Set the offset explicitly. The offset is the distance from the top left of the
3093 /// parent container to the top left of the first child.
3094 /// As you scroll further down the offset becomes more negative.
3095 pub fn set_offset(&self, mut position: Point<Pixels>) {
3096 let state = self.0.borrow();
3097 *state.offset.borrow_mut() = position;
3098 }
3099
3100 /// Get the logical scroll top, based on a child index and a pixel offset.
3101 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
3102 let ix = self.top_item();
3103 let state = self.0.borrow();
3104
3105 if let Some(child_bounds) = state.child_bounds.get(ix) {
3106 (
3107 ix,
3108 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
3109 )
3110 } else {
3111 (ix, px(0.))
3112 }
3113 }
3114
3115 /// Get the count of children for scrollable item.
3116 pub fn children_count(&self) -> usize {
3117 self.0.borrow().child_bounds.len()
3118 }
3119}