Skip to repository content4903 lines · 196.2 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T07:44:19.863Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
div.rs
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::PinchEvent;
19use crate::{
20 Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent, DispatchPhase,
21 Display, Element, ElementId, Entity, EntityId, FocusHandle, Global, GlobalElementId, Hitbox,
22 HitboxBehavior, HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent,
23 KeyUpEvent, KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent, MouseButton,
24 MouseClickEvent, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MousePressureEvent,
25 MouseUpEvent, Overflow, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString,
26 Size, Style, StyleRefinement, Styled, Task, TooltipId, Visibility, Window, WindowControlArea,
27 point, px, size,
28};
29use collections::HashMap;
30use gpui_util::ResultExt;
31use refineable::Refineable;
32use smallvec::SmallVec;
33use stacksafe::{StackSafe, stacksafe};
34use std::{
35 any::{Any, TypeId},
36 cell::RefCell,
37 cmp::Ordering,
38 fmt::Debug,
39 marker::PhantomData,
40 mem,
41 rc::Rc,
42 sync::Arc,
43 time::Duration,
44};
45
46use super::ImageCacheProvider;
47
48const DRAG_THRESHOLD: f64 = 2.;
49const DEFAULT_TOOLTIP_SHOW_DELAY: Duration = Duration::from_millis(500);
50const HOVERABLE_TOOLTIP_HIDE_DELAY: Duration = Duration::from_millis(500);
51
52/// The styling information for a given group.
53pub struct GroupStyle {
54 /// The identifier for this group.
55 pub group: SharedString,
56
57 /// The specific style refinement that this group would apply
58 /// to its children.
59 pub style: Box<StyleRefinement>,
60}
61
62/// An event for when a drag is moving over this element, with the given state type.
63pub struct DragMoveEvent<T> {
64 /// The mouse move event that triggered this drag move event.
65 pub event: MouseMoveEvent,
66
67 /// The bounds of this element.
68 pub bounds: Bounds<Pixels>,
69 drag: PhantomData<T>,
70 dragged_item: Arc<dyn Any>,
71}
72
73impl<T: 'static> DragMoveEvent<T> {
74 /// Returns the drag state for this event.
75 pub fn drag<'b>(&self, cx: &'b App) -> &'b T {
76 cx.active_drag
77 .as_ref()
78 .and_then(|drag| drag.value.downcast_ref::<T>())
79 .expect("DragMoveEvent is only valid when the stored active drag is of the same type.")
80 }
81
82 /// An item that is about to be dropped.
83 pub fn dragged_item(&self) -> &dyn Any {
84 self.dragged_item.as_ref()
85 }
86}
87
88impl Interactivity {
89 /// Create an `Interactivity`, capturing the caller location in debug mode.
90 #[cfg(any(feature = "inspector", debug_assertions))]
91 #[track_caller]
92 pub fn new() -> Interactivity {
93 Interactivity {
94 source_location: Some(core::panic::Location::caller()),
95 ..Default::default()
96 }
97 }
98
99 /// Create an `Interactivity`, capturing the caller location in debug mode.
100 #[cfg(not(any(feature = "inspector", debug_assertions)))]
101 pub fn new() -> Interactivity {
102 Interactivity::default()
103 }
104
105 /// Gets the source location of construction. Returns `None` when not in debug mode.
106 pub fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
107 #[cfg(any(feature = "inspector", debug_assertions))]
108 {
109 self.source_location
110 }
111
112 #[cfg(not(any(feature = "inspector", debug_assertions)))]
113 {
114 None
115 }
116 }
117
118 /// Bind the given callback to the mouse down event for the given mouse button, during the bubble phase.
119 /// The imperative API equivalent of [`InteractiveElement::on_mouse_down`].
120 ///
121 /// See [`Context::listener`](crate::Context::listener) to get access to the view state from this callback.
122 pub fn on_mouse_down(
123 &mut self,
124 button: MouseButton,
125 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
126 ) {
127 self.mouse_down_listeners
128 .push(Box::new(move |event, phase, hitbox, window, cx| {
129 if phase == DispatchPhase::Bubble
130 && event.button == button
131 && hitbox.is_hovered(window)
132 {
133 (listener)(event, window, cx)
134 }
135 }));
136 }
137
138 /// Bind the given callback to the mouse down event for any button, during the capture phase.
139 /// The imperative API equivalent of [`InteractiveElement::capture_any_mouse_down`].
140 ///
141 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
142 pub fn capture_any_mouse_down(
143 &mut self,
144 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
145 ) {
146 self.mouse_down_listeners
147 .push(Box::new(move |event, phase, hitbox, window, cx| {
148 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
149 (listener)(event, window, cx)
150 }
151 }));
152 }
153
154 /// Bind the given callback to the mouse down event for any button, during the bubble phase.
155 /// The imperative API equivalent to [`InteractiveElement::on_any_mouse_down`].
156 ///
157 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
158 pub fn on_any_mouse_down(
159 &mut self,
160 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
161 ) {
162 self.mouse_down_listeners
163 .push(Box::new(move |event, phase, hitbox, window, cx| {
164 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
165 (listener)(event, window, cx)
166 }
167 }));
168 }
169
170 /// Bind the given callback to the mouse pressure event, during the bubble phase
171 /// the imperative API equivalent to [`InteractiveElement::on_mouse_pressure`].
172 ///
173 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
174 pub fn on_mouse_pressure(
175 &mut self,
176 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
177 ) {
178 self.mouse_pressure_listeners
179 .push(Box::new(move |event, phase, hitbox, window, cx| {
180 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
181 (listener)(event, window, cx)
182 }
183 }));
184 }
185
186 /// Bind the given callback to the mouse pressure event, during the capture phase
187 /// the imperative API equivalent to [`InteractiveElement::on_mouse_pressure`].
188 ///
189 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
190 pub fn capture_mouse_pressure(
191 &mut self,
192 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
193 ) {
194 self.mouse_pressure_listeners
195 .push(Box::new(move |event, phase, hitbox, window, cx| {
196 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
197 (listener)(event, window, cx)
198 }
199 }));
200 }
201
202 /// Bind the given callback to the mouse up event for the given button, during the bubble phase.
203 /// The imperative API equivalent to [`InteractiveElement::on_mouse_up`].
204 ///
205 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
206 pub fn on_mouse_up(
207 &mut self,
208 button: MouseButton,
209 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
210 ) {
211 self.mouse_up_listeners
212 .push(Box::new(move |event, phase, hitbox, window, cx| {
213 if phase == DispatchPhase::Bubble
214 && event.button == button
215 && hitbox.is_hovered(window)
216 {
217 (listener)(event, window, cx)
218 }
219 }));
220 }
221
222 /// Bind the given callback to the mouse up event for any button, during the capture phase.
223 /// The imperative API equivalent to [`InteractiveElement::capture_any_mouse_up`].
224 ///
225 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
226 pub fn capture_any_mouse_up(
227 &mut self,
228 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
229 ) {
230 self.mouse_up_listeners
231 .push(Box::new(move |event, phase, hitbox, window, cx| {
232 if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
233 (listener)(event, window, cx)
234 }
235 }));
236 }
237
238 /// Bind the given callback to the mouse up event for any button, during the bubble phase.
239 /// The imperative API equivalent to [`Interactivity::on_any_mouse_up`].
240 ///
241 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
242 pub fn on_any_mouse_up(
243 &mut self,
244 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
245 ) {
246 self.mouse_up_listeners
247 .push(Box::new(move |event, phase, hitbox, window, cx| {
248 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
249 (listener)(event, window, cx)
250 }
251 }));
252 }
253
254 /// Bind the given callback to the mouse down event, on any button, during the capture phase,
255 /// when the mouse is outside of the bounds of this element.
256 /// The imperative API equivalent to [`InteractiveElement::on_mouse_down_out`].
257 ///
258 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
259 pub fn on_mouse_down_out(
260 &mut self,
261 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
262 ) {
263 self.mouse_down_listeners
264 .push(Box::new(move |event, phase, hitbox, window, cx| {
265 if phase == DispatchPhase::Capture
266 && !window.has_active_prompt()
267 && !hitbox.contains(&window.mouse_position())
268 {
269 (listener)(event, window, cx)
270 }
271 }));
272 }
273
274 /// Bind the given callback to the mouse up event, for the given button, during the capture phase,
275 /// when the mouse is outside of the bounds of this element.
276 /// The imperative API equivalent to [`InteractiveElement::on_mouse_up_out`].
277 ///
278 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
279 pub fn on_mouse_up_out(
280 &mut self,
281 button: MouseButton,
282 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
283 ) {
284 self.mouse_up_listeners
285 .push(Box::new(move |event, phase, hitbox, window, cx| {
286 if phase == DispatchPhase::Capture
287 && event.button == button
288 && !hitbox.is_hovered(window)
289 {
290 (listener)(event, window, cx);
291 }
292 }));
293 }
294
295 /// Bind the given callback to the mouse move event, during the bubble phase.
296 /// The imperative API equivalent to [`InteractiveElement::on_mouse_move`].
297 ///
298 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
299 pub fn on_mouse_move(
300 &mut self,
301 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
302 ) {
303 self.mouse_move_listeners
304 .push(Box::new(move |event, phase, hitbox, window, cx| {
305 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
306 (listener)(event, window, cx);
307 }
308 }));
309 }
310
311 /// Bind the given callback to the mouse exit event, during the bubble phase.
312 /// The imperative API equivalent to [`InteractiveElement::on_mouse_exit`].
313 ///
314 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
315 pub fn on_mouse_exit(
316 &mut self,
317 listener: impl Fn(&MouseExitEvent, &mut Window, &mut App) + 'static,
318 ) {
319 self.mouse_exit_listeners
320 .push(Box::new(move |event, phase, hitbox, window, cx| {
321 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
322 (listener)(event, window, cx);
323 }
324 }));
325 }
326
327 /// Bind the given callback to the mouse drag event of the given type. Note that this
328 /// will be called for all move events, inside or outside of this element, as long as the
329 /// drag was started with this element under the mouse. Useful for implementing draggable
330 /// UIs that don't conform to a drag and drop style interaction, like resizing.
331 /// The imperative API equivalent to [`InteractiveElement::on_drag_move`].
332 ///
333 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
334 pub fn on_drag_move<T>(
335 &mut self,
336 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
337 ) where
338 T: 'static,
339 {
340 self.mouse_move_listeners
341 .push(Box::new(move |event, phase, hitbox, window, cx| {
342 if phase == DispatchPhase::Capture
343 && let Some(drag) = &cx.active_drag
344 && drag.value.as_ref().type_id() == TypeId::of::<T>()
345 {
346 (listener)(
347 &DragMoveEvent {
348 event: event.clone(),
349 bounds: hitbox.bounds,
350 drag: PhantomData,
351 dragged_item: Arc::clone(&drag.value),
352 },
353 window,
354 cx,
355 );
356 }
357 }));
358 }
359
360 /// Bind the given callback to scroll wheel events during the bubble phase.
361 /// The imperative API equivalent to [`InteractiveElement::on_scroll_wheel`].
362 ///
363 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
364 pub fn on_scroll_wheel(
365 &mut self,
366 listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
367 ) {
368 self.scroll_wheel_listeners
369 .push(Box::new(move |event, phase, hitbox, window, cx| {
370 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
371 (listener)(event, window, cx);
372 }
373 }));
374 }
375
376 /// Bind the given callback to pinch gesture events during the bubble phase.
377 ///
378 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
379 pub fn on_pinch(&mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) {
380 self.pinch_listeners
381 .push(Box::new(move |event, phase, hitbox, window, cx| {
382 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
383 (listener)(event, window, cx);
384 }
385 }));
386 }
387
388 /// Bind the given callback to pinch gesture events during the capture phase.
389 ///
390 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
391 pub fn capture_pinch(
392 &mut self,
393 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
394 ) {
395 self.pinch_listeners
396 .push(Box::new(move |event, phase, _hitbox, window, cx| {
397 if phase == DispatchPhase::Capture {
398 (listener)(event, window, cx);
399 } else {
400 cx.propagate();
401 }
402 }));
403 }
404
405 /// Bind the given callback to an action dispatch during the capture phase.
406 /// The imperative API equivalent to [`InteractiveElement::capture_action`].
407 ///
408 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
409 pub fn capture_action<A: Action>(
410 &mut self,
411 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
412 ) {
413 self.action_listeners.push((
414 TypeId::of::<A>(),
415 Box::new(move |action, phase, window, cx| {
416 let action = action.downcast_ref().unwrap();
417 if phase == DispatchPhase::Capture {
418 (listener)(action, window, cx)
419 } else {
420 cx.propagate();
421 }
422 }),
423 ));
424 }
425
426 /// Bind the given callback to an action dispatch during the bubble phase.
427 /// The imperative API equivalent to [`InteractiveElement::on_action`].
428 ///
429 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
430 #[track_caller]
431 pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Window, &mut App) + 'static) {
432 self.action_listeners.push((
433 TypeId::of::<A>(),
434 Box::new(move |action, phase, window, cx| {
435 let action = action.downcast_ref().unwrap();
436 if phase == DispatchPhase::Bubble {
437 (listener)(action, window, cx)
438 }
439 }),
440 ));
441 }
442
443 /// Bind the given callback to an action dispatch, based on a dynamic action parameter
444 /// instead of a type parameter. Useful for component libraries that want to expose
445 /// action bindings to their users.
446 /// The imperative API equivalent to [`InteractiveElement::on_boxed_action`].
447 ///
448 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
449 pub fn on_boxed_action(
450 &mut self,
451 action: &dyn Action,
452 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
453 ) {
454 let action = action.boxed_clone();
455 self.action_listeners.push((
456 (*action).type_id(),
457 Box::new(move |_, phase, window, cx| {
458 if phase == DispatchPhase::Bubble {
459 (listener)(&*action, window, cx)
460 }
461 }),
462 ));
463 }
464
465 /// Bind the given callback to key down events during the bubble phase.
466 /// The imperative API equivalent to [`InteractiveElement::on_key_down`].
467 ///
468 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
469 pub fn on_key_down(
470 &mut self,
471 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
472 ) {
473 self.key_down_listeners
474 .push(Box::new(move |event, phase, window, cx| {
475 if phase == DispatchPhase::Bubble {
476 (listener)(event, window, cx)
477 }
478 }));
479 }
480
481 /// Bind the given callback to key down events during the capture phase.
482 /// The imperative API equivalent to [`InteractiveElement::capture_key_down`].
483 ///
484 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
485 pub fn capture_key_down(
486 &mut self,
487 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
488 ) {
489 self.key_down_listeners
490 .push(Box::new(move |event, phase, window, cx| {
491 if phase == DispatchPhase::Capture {
492 listener(event, window, cx)
493 }
494 }));
495 }
496
497 /// Bind the given callback to key up events during the bubble phase.
498 /// The imperative API equivalent to [`InteractiveElement::on_key_up`].
499 ///
500 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
501 pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static) {
502 self.key_up_listeners
503 .push(Box::new(move |event, phase, window, cx| {
504 if phase == DispatchPhase::Bubble {
505 listener(event, window, cx)
506 }
507 }));
508 }
509
510 /// Bind the given callback to key up events during the capture phase.
511 /// The imperative API equivalent to [`InteractiveElement::on_key_up`].
512 ///
513 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
514 pub fn capture_key_up(
515 &mut self,
516 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
517 ) {
518 self.key_up_listeners
519 .push(Box::new(move |event, phase, window, cx| {
520 if phase == DispatchPhase::Capture {
521 listener(event, window, cx)
522 }
523 }));
524 }
525
526 /// Bind the given callback to modifiers changing events.
527 /// The imperative API equivalent to [`InteractiveElement::on_modifiers_changed`].
528 ///
529 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
530 pub fn on_modifiers_changed(
531 &mut self,
532 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
533 ) {
534 self.modifiers_changed_listeners
535 .push(Box::new(move |event, window, cx| {
536 listener(event, window, cx)
537 }));
538 }
539
540 /// Bind the given callback to drop events of the given type, whether or not the drag started on this element.
541 /// The imperative API equivalent to [`InteractiveElement::on_drop`].
542 ///
543 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
544 pub fn on_drop<T: 'static>(&mut self, listener: impl Fn(&T, &mut Window, &mut App) + 'static) {
545 self.drop_listeners.push((
546 TypeId::of::<T>(),
547 Box::new(move |dragged_value, window, cx| {
548 listener(dragged_value.downcast_ref().unwrap(), window, cx);
549 }),
550 ));
551 }
552
553 /// Use the given predicate to determine whether or not a drop event should be dispatched to this element.
554 /// The imperative API equivalent to [`InteractiveElement::can_drop`].
555 pub fn can_drop(
556 &mut self,
557 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
558 ) {
559 self.can_drop_predicate = Some(Box::new(predicate));
560 }
561
562 /// Bind the given callback to click events of this element.
563 /// The imperative API equivalent to [`StatefulInteractiveElement::on_click`].
564 ///
565 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
566 pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
567 where
568 Self: Sized,
569 {
570 self.click_listeners.push(Rc::new(move |event, window, cx| {
571 listener(event, window, cx)
572 }));
573 }
574
575 /// Bind the given callback to non-primary click events of this element.
576 /// The imperative API equivalent to [`StatefulInteractiveElement::on_aux_click`].
577 ///
578 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
579 pub fn on_aux_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
580 where
581 Self: Sized,
582 {
583 self.aux_click_listeners
584 .push(Rc::new(move |event, window, cx| {
585 listener(event, window, cx)
586 }));
587 }
588
589 /// On drag initiation, this callback will be used to create a new view to render the dragged value for a
590 /// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with
591 /// the [`Self::on_drag_move`] API.
592 /// The imperative API equivalent to [`StatefulInteractiveElement::on_drag`].
593 ///
594 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
595 pub fn on_drag<T, W>(
596 &mut self,
597 value: T,
598 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
599 ) where
600 Self: Sized,
601 T: 'static,
602 W: 'static + Render,
603 {
604 debug_assert!(
605 self.drag_listener.is_none(),
606 "calling on_drag more than once on the same element is not supported"
607 );
608 self.drag_listener = Some((
609 Arc::new(value),
610 Box::new(move |value, offset, window, cx| {
611 constructor(value.downcast_ref().unwrap(), offset, window, cx).into()
612 }),
613 ));
614 }
615
616 /// Bind the given callback on the hover start and end events of this element. Note that the boolean
617 /// passed to the callback is true when the hover starts and false when it ends.
618 /// The imperative API equivalent to [`StatefulInteractiveElement::on_hover`].
619 ///
620 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
621 pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static)
622 where
623 Self: Sized,
624 {
625 debug_assert!(
626 self.hover_listener.is_none(),
627 "calling on_hover more than once on the same element is not supported"
628 );
629 self.hover_listener = Some(Box::new(listener));
630 }
631
632 /// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
633 /// The imperative API equivalent to [`StatefulInteractiveElement::tooltip`].
634 pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static)
635 where
636 Self: Sized,
637 {
638 debug_assert!(
639 self.tooltip_builder.is_none(),
640 "calling tooltip more than once on the same element is not supported"
641 );
642 self.tooltip_builder = Some(TooltipBuilder {
643 build: Rc::new(build_tooltip),
644 hoverable: false,
645 });
646 }
647
648 /// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
649 /// The tooltip itself is also hoverable and won't disappear when the user moves the mouse into
650 /// the tooltip. The imperative API equivalent to [`StatefulInteractiveElement::hoverable_tooltip`].
651 pub fn hoverable_tooltip(
652 &mut self,
653 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
654 ) where
655 Self: Sized,
656 {
657 debug_assert!(
658 self.tooltip_builder.is_none(),
659 "calling tooltip more than once on the same element is not supported"
660 );
661 self.tooltip_builder = Some(TooltipBuilder {
662 build: Rc::new(build_tooltip),
663 hoverable: true,
664 });
665 }
666
667 /// Set the delay before this element's tooltip is shown.
668 /// The imperative API equivalent to [`StatefulInteractiveElement::tooltip_show_delay`].
669 pub fn tooltip_show_delay(&mut self, delay: Duration) {
670 self.tooltip_show_delay = Some(delay);
671 }
672
673 /// Block the mouse from all interactions with elements behind this element's hitbox. Typically
674 /// `block_mouse_except_scroll` should be preferred.
675 ///
676 /// The imperative API equivalent to [`InteractiveElement::occlude`]
677 pub fn occlude_mouse(&mut self) {
678 self.hitbox_behavior = HitboxBehavior::BlockMouse;
679 }
680
681 /// Set the bounds of this element as a window control area for the platform window.
682 /// The imperative API equivalent to [`InteractiveElement::window_control_area`]
683 pub fn window_control_area(&mut self, area: WindowControlArea) {
684 self.window_control = Some(area);
685 }
686
687 /// Block non-scroll mouse interactions with elements behind this element's hitbox.
688 /// The imperative API equivalent to [`InteractiveElement::block_mouse_except_scroll`].
689 ///
690 /// See [`Hitbox::is_hovered`] for details.
691 pub fn block_mouse_except_scroll(&mut self) {
692 self.hitbox_behavior = HitboxBehavior::BlockMouseExceptScroll;
693 }
694
695 fn has_pinch_listeners(&self) -> bool {
696 !self.pinch_listeners.is_empty()
697 }
698}
699
700/// A trait for elements that want to use the standard GPUI event handlers that don't
701/// require any state.
702pub trait InteractiveElement: Sized {
703 /// Retrieve the interactivity state associated with this element
704 fn interactivity(&mut self) -> &mut Interactivity;
705
706 /// Assign this element to a group of elements that can be styled together
707 fn group(mut self, group: impl Into<SharedString>) -> Self {
708 self.interactivity().group = Some(group.into());
709 self
710 }
711
712 /// Assign this element an ID, so that it can be used with interactivity
713 fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
714 self.interactivity().element_id = Some(id.into());
715
716 Stateful { element: self }
717 }
718
719 /// Track the focus state of the given focus handle on this element.
720 /// If the focus handle is focused by the application, this element will
721 /// apply its focused styles.
722 fn track_focus(mut self, focus_handle: &FocusHandle) -> Self {
723 self.interactivity().focusable = true;
724 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
725 self
726 }
727
728 /// Set whether this element is a tab stop.
729 ///
730 /// When false, the element remains in tab-index order but cannot be reached via keyboard navigation.
731 /// Useful for container elements: focus the container, then call `window.focus_next(cx)` to focus
732 /// the first tab stop inside it while having the container element itself be unreachable via the keyboard.
733 /// Should only be used with `tab_index`.
734 fn tab_stop(mut self, tab_stop: bool) -> Self {
735 self.interactivity().tab_stop = tab_stop;
736 self
737 }
738
739 /// Set index of the tab stop order, and set this node as a tab stop.
740 /// This will default the element to being a tab stop. See [`Self::tab_stop`] for more information.
741 /// This should only be used in conjunction with `tab_group`
742 /// in order to not interfere with the tab index of other elements.
743 fn tab_index(mut self, index: isize) -> Self {
744 self.interactivity().focusable = true;
745 self.interactivity().tab_index = Some(index);
746 self.interactivity().tab_stop = true;
747 self
748 }
749
750 /// Designate this div as a "tab group". Tab groups have their own location in the tab-index order,
751 /// but for children of the tab group, the tab index is reset to 0. This can be useful for swapping
752 /// the order of tab stops within the group, without having to renumber all the tab stops in the whole
753 /// application.
754 fn tab_group(mut self) -> Self {
755 self.interactivity().tab_group = true;
756 if self.interactivity().tab_index.is_none() {
757 self.interactivity().tab_index = Some(0);
758 }
759 self
760 }
761
762 /// Set the keymap context for this element. This will be used to determine
763 /// which action to dispatch from the keymap.
764 fn key_context<C, E>(mut self, key_context: C) -> Self
765 where
766 C: TryInto<KeyContext, Error = E>,
767 E: std::fmt::Display,
768 {
769 if let Some(key_context) = key_context.try_into().log_err() {
770 self.interactivity().key_context = Some(key_context);
771 }
772 self
773 }
774
775 /// Apply the given style to this element when the mouse hovers over it
776 fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
777 debug_assert!(
778 self.interactivity().hover_style.is_none(),
779 "hover style already set"
780 );
781 self.interactivity().hover_style = Some(Box::new(f(StyleRefinement::default())));
782 self
783 }
784
785 /// Apply the given style to this element when the mouse hovers over a group member
786 fn group_hover(
787 mut self,
788 group_name: impl Into<SharedString>,
789 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
790 ) -> Self {
791 self.interactivity().group_hover_style = Some(GroupStyle {
792 group: group_name.into(),
793 style: Box::new(f(StyleRefinement::default())),
794 });
795 self
796 }
797
798 /// Bind the given callback to the mouse down event for the given mouse button.
799 /// The fluent API equivalent to [`Interactivity::on_mouse_down`].
800 ///
801 /// See [`Context::listener`](crate::Context::listener) to get access to the view state from this callback.
802 fn on_mouse_down(
803 mut self,
804 button: MouseButton,
805 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
806 ) -> Self {
807 self.interactivity().on_mouse_down(button, listener);
808 self
809 }
810
811 #[cfg(any(test, feature = "test-support"))]
812 /// Set a key that can be used to look up this element's bounds
813 /// in the [`crate::VisualTestContext::debug_bounds`] map
814 /// This is a noop in release builds
815 fn debug_selector(mut self, f: impl FnOnce() -> String) -> Self {
816 self.interactivity().debug_selector = Some(f());
817 self
818 }
819
820 #[cfg(not(any(test, feature = "test-support")))]
821 /// Set a key that can be used to look up this element's bounds
822 /// in the [`crate::VisualTestContext::debug_bounds`] map
823 /// This is a noop in release builds
824 #[inline]
825 fn debug_selector(self, _: impl FnOnce() -> String) -> Self {
826 self
827 }
828
829 /// Bind the given callback to the mouse down event for any button, during the capture phase.
830 /// The fluent API equivalent to [`Interactivity::capture_any_mouse_down`].
831 ///
832 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
833 fn capture_any_mouse_down(
834 mut self,
835 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
836 ) -> Self {
837 self.interactivity().capture_any_mouse_down(listener);
838 self
839 }
840
841 /// Bind the given callback to the mouse down event for any button, during the capture phase.
842 /// The fluent API equivalent to [`Interactivity::on_any_mouse_down`].
843 ///
844 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
845 fn on_any_mouse_down(
846 mut self,
847 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
848 ) -> Self {
849 self.interactivity().on_any_mouse_down(listener);
850 self
851 }
852
853 /// Bind the given callback to the mouse up event for the given button, during the bubble phase.
854 /// The fluent API equivalent to [`Interactivity::on_mouse_up`].
855 ///
856 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
857 fn on_mouse_up(
858 mut self,
859 button: MouseButton,
860 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
861 ) -> Self {
862 self.interactivity().on_mouse_up(button, listener);
863 self
864 }
865
866 /// Bind the given callback to the mouse up event for any button, during the capture phase.
867 /// The fluent API equivalent to [`Interactivity::capture_any_mouse_up`].
868 ///
869 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
870 fn capture_any_mouse_up(
871 mut self,
872 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
873 ) -> Self {
874 self.interactivity().capture_any_mouse_up(listener);
875 self
876 }
877
878 /// Bind the given callback to the mouse pressure event, during the bubble phase
879 /// the fluent API equivalent to [`Interactivity::on_mouse_pressure`]
880 ///
881 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
882 fn on_mouse_pressure(
883 mut self,
884 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
885 ) -> Self {
886 self.interactivity().on_mouse_pressure(listener);
887 self
888 }
889
890 /// Bind the given callback to the mouse pressure event, during the capture phase
891 /// the fluent API equivalent to [`Interactivity::on_mouse_pressure`]
892 ///
893 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
894 fn capture_mouse_pressure(
895 mut self,
896 listener: impl Fn(&MousePressureEvent, &mut Window, &mut App) + 'static,
897 ) -> Self {
898 self.interactivity().capture_mouse_pressure(listener);
899 self
900 }
901
902 /// Bind the given callback to the mouse down event, on any button, during the capture phase,
903 /// when the mouse is outside of the bounds of this element.
904 /// The fluent API equivalent to [`Interactivity::on_mouse_down_out`].
905 ///
906 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
907 fn on_mouse_down_out(
908 mut self,
909 listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
910 ) -> Self {
911 self.interactivity().on_mouse_down_out(listener);
912 self
913 }
914
915 /// Bind the given callback to the mouse up event, for the given button, during the capture phase,
916 /// when the mouse is outside of the bounds of this element.
917 /// The fluent API equivalent to [`Interactivity::on_mouse_up_out`].
918 ///
919 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
920 fn on_mouse_up_out(
921 mut self,
922 button: MouseButton,
923 listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static,
924 ) -> Self {
925 self.interactivity().on_mouse_up_out(button, listener);
926 self
927 }
928
929 /// Bind the given callback to the mouse move event, during the bubble phase.
930 /// The fluent API equivalent to [`Interactivity::on_mouse_move`].
931 ///
932 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
933 fn on_mouse_move(
934 mut self,
935 listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static,
936 ) -> Self {
937 self.interactivity().on_mouse_move(listener);
938 self
939 }
940
941 /// Bind the given callback to the mouse exit event, during the bubble phase.
942 /// The fluent API equivalent to [`Interactivity::on_mouse_exit`].
943 ///
944 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
945 fn on_mouse_exit(
946 mut self,
947 listener: impl Fn(&MouseExitEvent, &mut Window, &mut App) + 'static,
948 ) -> Self {
949 self.interactivity().on_mouse_exit(listener);
950 self
951 }
952
953 /// Bind the given callback to the mouse drag event of the given type. Note that this
954 /// will be called for all move events, inside or outside of this element, as long as the
955 /// drag was started with this element under the mouse. Useful for implementing draggable
956 /// UIs that don't conform to a drag and drop style interaction, like resizing.
957 /// The fluent API equivalent to [`Interactivity::on_drag_move`].
958 ///
959 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
960 fn on_drag_move<T: 'static>(
961 mut self,
962 listener: impl Fn(&DragMoveEvent<T>, &mut Window, &mut App) + 'static,
963 ) -> Self {
964 self.interactivity().on_drag_move(listener);
965 self
966 }
967
968 /// Bind the given callback to scroll wheel events during the bubble phase.
969 /// The fluent API equivalent to [`Interactivity::on_scroll_wheel`].
970 ///
971 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
972 fn on_scroll_wheel(
973 mut self,
974 listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
975 ) -> Self {
976 self.interactivity().on_scroll_wheel(listener);
977 self
978 }
979
980 /// Bind the given callback to pinch gesture events during the bubble phase.
981 /// The fluent API equivalent to [`Interactivity::on_pinch`].
982 ///
983 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
984 fn on_pinch(mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) -> Self {
985 self.interactivity().on_pinch(listener);
986 self
987 }
988
989 /// Bind the given callback to pinch gesture events during the capture phase.
990 /// The fluent API equivalent to [`Interactivity::capture_pinch`].
991 ///
992 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
993 fn capture_pinch(
994 mut self,
995 listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
996 ) -> Self {
997 self.interactivity().capture_pinch(listener);
998 self
999 }
1000 /// Capture the given action, before normal action dispatch can fire.
1001 /// The fluent API equivalent to [`Interactivity::capture_action`].
1002 ///
1003 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1004 fn capture_action<A: Action>(
1005 mut self,
1006 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
1007 ) -> Self {
1008 self.interactivity().capture_action(listener);
1009 self
1010 }
1011
1012 /// Bind the given callback to an action dispatch during the bubble phase.
1013 /// The fluent API equivalent to [`Interactivity::on_action`].
1014 ///
1015 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1016 #[track_caller]
1017 fn on_action<A: Action>(
1018 mut self,
1019 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
1020 ) -> Self {
1021 self.interactivity().on_action(listener);
1022 self
1023 }
1024
1025 /// Bind the given callback to an action dispatch, based on a dynamic action parameter
1026 /// instead of a type parameter. Useful for component libraries that want to expose
1027 /// action bindings to their users.
1028 /// The fluent API equivalent to [`Interactivity::on_boxed_action`].
1029 ///
1030 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1031 fn on_boxed_action(
1032 mut self,
1033 action: &dyn Action,
1034 listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static,
1035 ) -> Self {
1036 self.interactivity().on_boxed_action(action, listener);
1037 self
1038 }
1039
1040 /// Bind the given callback to key down events during the bubble phase.
1041 /// The fluent API equivalent to [`Interactivity::on_key_down`].
1042 ///
1043 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1044 fn on_key_down(
1045 mut self,
1046 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1047 ) -> Self {
1048 self.interactivity().on_key_down(listener);
1049 self
1050 }
1051
1052 /// Bind the given callback to key down events during the capture phase.
1053 /// The fluent API equivalent to [`Interactivity::capture_key_down`].
1054 ///
1055 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1056 fn capture_key_down(
1057 mut self,
1058 listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static,
1059 ) -> Self {
1060 self.interactivity().capture_key_down(listener);
1061 self
1062 }
1063
1064 /// Bind the given callback to key up events during the bubble phase.
1065 /// The fluent API equivalent to [`Interactivity::on_key_up`].
1066 ///
1067 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1068 fn on_key_up(
1069 mut self,
1070 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1071 ) -> Self {
1072 self.interactivity().on_key_up(listener);
1073 self
1074 }
1075
1076 /// Bind the given callback to key up events during the capture phase.
1077 /// The fluent API equivalent to [`Interactivity::capture_key_up`].
1078 ///
1079 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1080 fn capture_key_up(
1081 mut self,
1082 listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static,
1083 ) -> Self {
1084 self.interactivity().capture_key_up(listener);
1085 self
1086 }
1087
1088 /// Bind the given callback to modifiers changing events.
1089 /// The fluent API equivalent to [`Interactivity::on_modifiers_changed`].
1090 ///
1091 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1092 fn on_modifiers_changed(
1093 mut self,
1094 listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static,
1095 ) -> Self {
1096 self.interactivity().on_modifiers_changed(listener);
1097 self
1098 }
1099
1100 /// Apply the given style when the given data type is dragged over this element
1101 fn drag_over<S: 'static>(
1102 mut self,
1103 f: impl 'static + Fn(StyleRefinement, &S, &mut Window, &mut App) -> StyleRefinement,
1104 ) -> Self {
1105 self.interactivity().drag_over_styles.push((
1106 TypeId::of::<S>(),
1107 Box::new(move |currently_dragged: &dyn Any, window, cx| {
1108 f(
1109 StyleRefinement::default(),
1110 currently_dragged.downcast_ref::<S>().unwrap(),
1111 window,
1112 cx,
1113 )
1114 }),
1115 ));
1116 self
1117 }
1118
1119 /// Apply the given style when the given data type is dragged over this element's group
1120 fn group_drag_over<S: 'static>(
1121 mut self,
1122 group_name: impl Into<SharedString>,
1123 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1124 ) -> Self {
1125 self.interactivity().group_drag_over_styles.push((
1126 TypeId::of::<S>(),
1127 GroupStyle {
1128 group: group_name.into(),
1129 style: Box::new(f(StyleRefinement::default())),
1130 },
1131 ));
1132 self
1133 }
1134
1135 /// Bind the given callback to drop events of the given type, whether or not the drag started on this element.
1136 /// The fluent API equivalent to [`Interactivity::on_drop`].
1137 ///
1138 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1139 fn on_drop<T: 'static>(
1140 mut self,
1141 listener: impl Fn(&T, &mut Window, &mut App) + 'static,
1142 ) -> Self {
1143 self.interactivity().on_drop(listener);
1144 self
1145 }
1146
1147 /// Use the given predicate to determine whether or not a drop event should be dispatched to this element.
1148 /// The fluent API equivalent to [`Interactivity::can_drop`].
1149 fn can_drop(
1150 mut self,
1151 predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static,
1152 ) -> Self {
1153 self.interactivity().can_drop(predicate);
1154 self
1155 }
1156
1157 /// Block the mouse from all interactions with elements behind this element's hitbox. Typically
1158 /// `block_mouse_except_scroll` should be preferred.
1159 /// The fluent API equivalent to [`Interactivity::occlude_mouse`].
1160 fn occlude(mut self) -> Self {
1161 self.interactivity().occlude_mouse();
1162 self
1163 }
1164
1165 /// Set the bounds of this element as a window control area for the platform window.
1166 /// The fluent API equivalent to [`Interactivity::window_control_area`].
1167 fn window_control_area(mut self, area: WindowControlArea) -> Self {
1168 self.interactivity().window_control_area(area);
1169 self
1170 }
1171
1172 /// Block non-scroll mouse interactions with elements behind this element's hitbox.
1173 /// The fluent API equivalent to [`Interactivity::block_mouse_except_scroll`].
1174 ///
1175 /// See [`Hitbox::is_hovered`] for details.
1176 fn block_mouse_except_scroll(mut self) -> Self {
1177 self.interactivity().block_mouse_except_scroll();
1178 self
1179 }
1180
1181 /// Set the given styles to be applied when this element, specifically, is focused.
1182 /// Requires that the element is focusable. Elements can be made focusable using [`InteractiveElement::track_focus`].
1183 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1184 where
1185 Self: Sized,
1186 {
1187 self.interactivity().focus_style = Some(Box::new(f(StyleRefinement::default())));
1188 self
1189 }
1190
1191 /// Set the given styles to be applied when this element is inside another element that is focused.
1192 /// Requires that the element is focusable. Elements can be made focusable using [`InteractiveElement::track_focus`].
1193 fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1194 where
1195 Self: Sized,
1196 {
1197 self.interactivity().in_focus_style = Some(Box::new(f(StyleRefinement::default())));
1198 self
1199 }
1200
1201 /// Set the given styles to be applied when this element is focused via keyboard navigation.
1202 /// This is similar to CSS's `:focus-visible` pseudo-class - it only applies when the element
1203 /// is focused AND the user is navigating via keyboard (not mouse clicks).
1204 /// Requires that the element is focusable. Elements can be made focusable using [`InteractiveElement::track_focus`].
1205 fn focus_visible(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1206 where
1207 Self: Sized,
1208 {
1209 self.interactivity().focus_visible_style = Some(Box::new(f(StyleRefinement::default())));
1210 self
1211 }
1212}
1213
1214/// A trait for elements that want to use the standard GPUI interactivity features
1215/// that require state.
1216pub trait StatefulInteractiveElement: InteractiveElement {
1217 /// Set the accessible role for this element.
1218 ///
1219 /// See the [accessibility guide](crate::_accessibility) for an overview.
1220 fn role(mut self, role: accesskit::Role) -> Self {
1221 debug_assert!(
1222 role != accesskit::Role::GenericContainer,
1223 "GenericContainer is filtered out of the a11y tree and has no effect"
1224 );
1225 self.interactivity().override_role = Some(role);
1226 self
1227 }
1228
1229 /// Set the accessible label for this element.
1230 fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
1231 self.interactivity().aria.label = Some(label.into());
1232 self
1233 }
1234
1235 /// Set the accessible description for this element. Unlike the label (which
1236 /// names the element), the description provides supplementary information
1237 /// that assistive technology announces after the name, role, and value -
1238 /// for example a settings subtitle or a hint.
1239 fn aria_description(mut self, description: impl Into<SharedString>) -> Self {
1240 self.interactivity().aria.description = Some(description.into());
1241 self
1242 }
1243
1244 /// Set the keyboard shortcut(s) that activate this element, announced by
1245 /// assistive technology (maps to AccessKit's `keyboard_shortcut`).
1246 ///
1247 /// Note that this does not create a keymap. It simply instructs assistive
1248 /// technology what the keymap is.
1249 fn aria_keyshortcuts(mut self, keyshortcuts: impl Into<SharedString>) -> Self {
1250 self.interactivity().aria.keyshortcuts = Some(keyshortcuts.into());
1251 self
1252 }
1253
1254 /// Report this element as the focused node in the accessibility tree,
1255 /// overriding the element that holds real keyboard focus — but only while
1256 /// one of its ancestors actually holds focus.
1257 ///
1258 /// This implements the `aria-activedescendant` pattern for composite
1259 /// widgets that keep keyboard focus on a container (e.g. a menu or
1260 /// listbox) while a child is "selected": set this on the selected child so
1261 /// assistive technology announces and highlights it as focused.
1262 ///
1263 /// The element must also have a [`role`][Self::role] (and an id) so it
1264 /// produces an accessibility node. Unlike the web's container-side
1265 /// `aria-activedescendant`, this is set on the descendant; GPUI honors it
1266 /// only when a focused ancestor is present in the tree, so it is safe to
1267 /// set unconditionally on the selected child — if the container isn't
1268 /// focused, the claim is ignored.
1269 fn aria_active_descendant(mut self) -> Self {
1270 self.interactivity().report_active_descendant_focus = true;
1271 self
1272 }
1273
1274 /// Contribute synthetic accessibility nodes — nodes that don't correspond
1275 /// to any element — as children of this element's a11y node. For example,
1276 /// text runs describing an editor's text content.
1277 ///
1278 /// The closure is called after this element is prepainted, and only if it
1279 /// contributed a node to the accessibility tree (i.e. it has an id and a
1280 /// [`role`][StatefulInteractiveElement::role]).
1281 ///
1282 /// See [`Element::a11y_synthetic_children`] for details.
1283 fn a11y_synthetic_children(
1284 mut self,
1285 f: impl FnOnce(&mut crate::A11ySubtreeBuilder) + 'static,
1286 ) -> Self {
1287 self.interactivity().a11y_synthetic_children = Some(Box::new(f));
1288 self
1289 }
1290
1291 /// Set the selected state for this element.
1292 fn aria_selected(mut self, selected: bool) -> Self {
1293 self.interactivity().aria.selected = Some(selected);
1294 self
1295 }
1296
1297 /// Set the expanded state for this element.
1298 fn aria_expanded(mut self, expanded: bool) -> Self {
1299 self.interactivity().aria.expanded = Some(expanded);
1300 self
1301 }
1302
1303 /// Set the toggled state for this element.
1304 fn aria_toggled(mut self, toggled: accesskit::Toggled) -> Self {
1305 self.interactivity().aria.toggled = Some(toggled);
1306 self
1307 }
1308
1309 /// Set the numeric value for this element.
1310 fn aria_numeric_value(mut self, value: f64) -> Self {
1311 self.interactivity().aria.numeric_value = Some(value);
1312 self
1313 }
1314
1315 /// Set the step by which assistive technology should expect the numeric
1316 /// value of this element to change (e.g. when incrementing a spin button).
1317 fn aria_numeric_value_step(mut self, step: f64) -> Self {
1318 self.interactivity().aria.numeric_value_step = Some(step);
1319 self
1320 }
1321
1322 /// Set the string value of this element, e.g. the text content of a simple
1323 /// text input.
1324 fn aria_value(mut self, value: impl Into<SharedString>) -> Self {
1325 self.interactivity().aria.value = Some(value.into());
1326 self
1327 }
1328
1329 /// Set the placeholder text reported to assistive technology for this
1330 /// element, shown when a text input is empty.
1331 fn aria_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
1332 self.interactivity().aria.placeholder = Some(placeholder.into());
1333 self
1334 }
1335
1336 /// Set the minimum numeric value for this element.
1337 fn aria_min_numeric_value(mut self, value: f64) -> Self {
1338 self.interactivity().aria.min_numeric_value = Some(value);
1339 self
1340 }
1341
1342 /// Set the maximum numeric value for this element.
1343 fn aria_max_numeric_value(mut self, value: f64) -> Self {
1344 self.interactivity().aria.max_numeric_value = Some(value);
1345 self
1346 }
1347
1348 /// Set the orientation of this element.
1349 fn aria_orientation(mut self, orientation: accesskit::Orientation) -> Self {
1350 self.interactivity().aria.orientation = Some(orientation);
1351 self
1352 }
1353
1354 /// Set the heading level of this element.
1355 fn aria_level(mut self, level: usize) -> Self {
1356 self.interactivity().aria.level = Some(level);
1357 self
1358 }
1359
1360 /// Set the position in set of this element.
1361 fn aria_position_in_set(mut self, position: usize) -> Self {
1362 self.interactivity().aria.position_in_set = Some(position);
1363 self
1364 }
1365
1366 /// Set the size of set for this element.
1367 fn aria_size_of_set(mut self, size: usize) -> Self {
1368 self.interactivity().aria.size_of_set = Some(size);
1369 self
1370 }
1371
1372 /// Set the row index for this element.
1373 fn aria_row_index(mut self, index: usize) -> Self {
1374 self.interactivity().aria.row_index = Some(index);
1375 self
1376 }
1377
1378 /// Set the column index for this element.
1379 fn aria_column_index(mut self, index: usize) -> Self {
1380 self.interactivity().aria.column_index = Some(index);
1381 self
1382 }
1383
1384 /// Set the row count for this element.
1385 fn aria_row_count(mut self, count: usize) -> Self {
1386 self.interactivity().aria.row_count = Some(count);
1387 self
1388 }
1389
1390 /// Set the column count for this element.
1391 fn aria_column_count(mut self, count: usize) -> Self {
1392 self.interactivity().aria.column_count = Some(count);
1393 self
1394 }
1395
1396 /// Register a handler for an accessibility action on this element.
1397 /// The handler is called when a screen reader requests the given action.
1398 ///
1399 /// See the [accessibility guide](crate::_accessibility) for an overview.
1400 fn on_a11y_action(
1401 mut self,
1402 action: accesskit::Action,
1403 listener: impl FnMut(Option<&accesskit::ActionData>, &mut crate::Window, &mut crate::App)
1404 + 'static,
1405 ) -> Self {
1406 self.interactivity()
1407 .a11y_action_listeners
1408 .push((action, Box::new(listener)));
1409 self
1410 }
1411
1412 /// Set this element to focusable.
1413 fn focusable(mut self) -> Self {
1414 self.interactivity().focusable = true;
1415 self
1416 }
1417
1418 /// Set the overflow x and y to scroll.
1419 fn overflow_scroll(mut self) -> Self {
1420 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1421 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1422 self
1423 }
1424
1425 /// Set the overflow x to scroll.
1426 fn overflow_x_scroll(mut self) -> Self {
1427 self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
1428 self
1429 }
1430
1431 /// Set the overflow y to scroll.
1432 fn overflow_y_scroll(mut self) -> Self {
1433 self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
1434 self
1435 }
1436
1437 /// Track the scroll state of this element with the given handle.
1438 fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
1439 self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone());
1440 self
1441 }
1442
1443 /// Track the scroll state of this element with the given handle.
1444 fn anchor_scroll(mut self, scroll_anchor: Option<ScrollAnchor>) -> Self {
1445 self.interactivity().scroll_anchor = scroll_anchor;
1446 self
1447 }
1448
1449 /// Set the given styles to be applied when this element is active.
1450 fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
1451 where
1452 Self: Sized,
1453 {
1454 self.interactivity().active_style = Some(Box::new(f(StyleRefinement::default())));
1455 self
1456 }
1457
1458 /// Set the given styles to be applied when this element's group is active.
1459 fn group_active(
1460 mut self,
1461 group_name: impl Into<SharedString>,
1462 f: impl FnOnce(StyleRefinement) -> StyleRefinement,
1463 ) -> Self
1464 where
1465 Self: Sized,
1466 {
1467 self.interactivity().group_active_style = Some(GroupStyle {
1468 group: group_name.into(),
1469 style: Box::new(f(StyleRefinement::default())),
1470 });
1471 self
1472 }
1473
1474 /// Bind the given callback to click events of this element.
1475 /// The fluent API equivalent to [`Interactivity::on_click`].
1476 ///
1477 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1478 fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self
1479 where
1480 Self: Sized,
1481 {
1482 self.interactivity().on_click(listener);
1483 self
1484 }
1485
1486 /// Bind the given callback to non-primary click events of this element.
1487 /// The fluent API equivalent to [`Interactivity::on_aux_click`].
1488 ///
1489 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1490 fn on_aux_click(
1491 mut self,
1492 listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
1493 ) -> Self
1494 where
1495 Self: Sized,
1496 {
1497 self.interactivity().on_aux_click(listener);
1498 self
1499 }
1500
1501 /// On drag initiation, this callback will be used to create a new view to render the dragged value for a
1502 /// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with
1503 /// the [`InteractiveElement::on_drag_move`] API.
1504 /// The callback also has access to the offset of triggering click from the origin of parent element.
1505 /// The fluent API equivalent to [`Interactivity::on_drag`].
1506 ///
1507 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1508 fn on_drag<T, W>(
1509 mut self,
1510 value: T,
1511 constructor: impl Fn(&T, Point<Pixels>, &mut Window, &mut App) -> Entity<W> + 'static,
1512 ) -> Self
1513 where
1514 Self: Sized,
1515 T: 'static,
1516 W: 'static + Render,
1517 {
1518 self.interactivity().on_drag(value, constructor);
1519 self
1520 }
1521
1522 /// Bind the given callback on the hover start and end events of this element. Note that the boolean
1523 /// passed to the callback is true when the hover starts and false when it ends.
1524 /// The fluent API equivalent to [`Interactivity::on_hover`].
1525 ///
1526 /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
1527 fn on_hover(mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self
1528 where
1529 Self: Sized,
1530 {
1531 self.interactivity().on_hover(listener);
1532 self
1533 }
1534
1535 /// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
1536 /// The fluent API equivalent to [`Interactivity::tooltip`].
1537 fn tooltip(mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self
1538 where
1539 Self: Sized,
1540 {
1541 self.interactivity().tooltip(build_tooltip);
1542 self
1543 }
1544
1545 /// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
1546 /// The tooltip itself is also hoverable and won't disappear when the user moves the mouse into
1547 /// the tooltip. The fluent API equivalent to [`Interactivity::hoverable_tooltip`].
1548 fn hoverable_tooltip(
1549 mut self,
1550 build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
1551 ) -> Self
1552 where
1553 Self: Sized,
1554 {
1555 self.interactivity().hoverable_tooltip(build_tooltip);
1556 self
1557 }
1558
1559 /// Set the delay before this element's tooltip is shown.
1560 /// The fluent API equivalent to [`Interactivity::tooltip_show_delay`].
1561 fn tooltip_show_delay(mut self, delay: Duration) -> Self
1562 where
1563 Self: Sized,
1564 {
1565 self.interactivity().tooltip_show_delay(delay);
1566 self
1567 }
1568}
1569
1570pub(crate) type MouseDownListener =
1571 Box<dyn Fn(&MouseDownEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1572pub(crate) type MouseUpListener =
1573 Box<dyn Fn(&MouseUpEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1574pub(crate) type MousePressureListener =
1575 Box<dyn Fn(&MousePressureEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1576pub(crate) type MouseMoveListener =
1577 Box<dyn Fn(&MouseMoveEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1578pub(crate) type MouseExitListener =
1579 Box<dyn Fn(&MouseExitEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1580
1581pub(crate) type ScrollWheelListener =
1582 Box<dyn Fn(&ScrollWheelEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1583
1584pub(crate) type PinchListener =
1585 Box<dyn Fn(&PinchEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
1586
1587pub(crate) type ClickListener = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>;
1588
1589pub(crate) type DragListener =
1590 Box<dyn Fn(&dyn Any, Point<Pixels>, &mut Window, &mut App) -> AnyView + 'static>;
1591
1592type DropListener = Box<dyn Fn(&dyn Any, &mut Window, &mut App) + 'static>;
1593
1594type CanDropPredicate = Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static>;
1595
1596pub(crate) struct TooltipBuilder {
1597 build: Rc<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>,
1598 hoverable: bool,
1599}
1600
1601pub(crate) type KeyDownListener =
1602 Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1603
1604pub(crate) type KeyUpListener =
1605 Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut Window, &mut App) + 'static>;
1606
1607pub(crate) type ModifiersChangedListener =
1608 Box<dyn Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static>;
1609
1610pub(crate) type ActionListener =
1611 Box<dyn Fn(&dyn Any, DispatchPhase, &mut Window, &mut App) + 'static>;
1612
1613/// Construct a new [`Div`] element
1614#[track_caller]
1615pub fn div() -> Div {
1616 Div {
1617 interactivity: Interactivity::new(),
1618 children: SmallVec::default(),
1619 prepaint_listener: None,
1620 image_cache: None,
1621 prepaint_order_fn: None,
1622 }
1623}
1624
1625/// A [`Div`] element, the all-in-one element for building complex UIs in GPUI
1626pub struct Div {
1627 interactivity: Interactivity,
1628 children: SmallVec<[StackSafe<AnyElement>; 2]>,
1629 prepaint_listener: Option<Box<dyn Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static>>,
1630 image_cache: Option<Box<dyn ImageCacheProvider>>,
1631 prepaint_order_fn: Option<Box<dyn Fn(&mut Window, &mut App) -> SmallVec<[usize; 8]>>>,
1632}
1633
1634impl Div {
1635 /// Add a listener to be called when the children of this `Div` are prepainted.
1636 /// This allows you to store the [`Bounds`] of the children for later use.
1637 pub fn on_children_prepainted(
1638 mut self,
1639 listener: impl Fn(Vec<Bounds<Pixels>>, &mut Window, &mut App) + 'static,
1640 ) -> Self {
1641 self.prepaint_listener = Some(Box::new(listener));
1642 self
1643 }
1644
1645 /// Add an image cache at the location of this div in the element tree.
1646 pub fn image_cache(mut self, cache: impl ImageCacheProvider) -> Self {
1647 self.image_cache = Some(Box::new(cache));
1648 self
1649 }
1650
1651 /// Specify a function that determines the order in which children are prepainted.
1652 ///
1653 /// The function is called at prepaint time and should return a vector of child indices
1654 /// in the desired prepaint order. Each index should appear exactly once.
1655 ///
1656 /// This is useful when the prepaint of one child affects state that another child reads.
1657 /// For example, in split editor views, the editor with an autoscroll request should
1658 /// be prepainted first so its scroll position update is visible to the other editor.
1659 pub fn with_dynamic_prepaint_order(
1660 mut self,
1661 order_fn: impl Fn(&mut Window, &mut App) -> SmallVec<[usize; 8]> + 'static,
1662 ) -> Self {
1663 self.prepaint_order_fn = Some(Box::new(order_fn));
1664 self
1665 }
1666}
1667
1668/// A frame state for a `Div` element, which contains layout IDs for its children.
1669///
1670/// This struct is used internally by the `Div` element to manage the layout state of its children
1671/// during the UI update cycle. It holds a small vector of `LayoutId` values, each corresponding to
1672/// a child element of the `Div`. These IDs are used to query the layout engine for the computed
1673/// bounds of the children after the layout phase is complete.
1674pub struct DivFrameState {
1675 child_layout_ids: SmallVec<[LayoutId; 2]>,
1676}
1677
1678/// Interactivity state displayed an manipulated in the inspector.
1679#[derive(Clone)]
1680pub struct DivInspectorState {
1681 /// The inspected element's base style. This is used for both inspecting and modifying the
1682 /// state. In the future it will make sense to separate the read and write, possibly tracking
1683 /// the modifications.
1684 #[cfg(any(feature = "inspector", debug_assertions))]
1685 pub base_style: Box<StyleRefinement>,
1686 /// Inspects the bounds of the element.
1687 pub bounds: Bounds<Pixels>,
1688 /// Size of the children of the element, or `bounds.size` if it has no children.
1689 pub content_size: Size<Pixels>,
1690}
1691
1692impl Styled for Div {
1693 fn style(&mut self) -> &mut StyleRefinement {
1694 &mut self.interactivity.base_style
1695 }
1696}
1697
1698impl InteractiveElement for Div {
1699 fn interactivity(&mut self) -> &mut Interactivity {
1700 &mut self.interactivity
1701 }
1702}
1703
1704impl ParentElement for Div {
1705 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1706 self.children
1707 .extend(elements.into_iter().map(StackSafe::new))
1708 }
1709}
1710
1711impl Element for Div {
1712 type RequestLayoutState = DivFrameState;
1713 type PrepaintState = Option<Hitbox>;
1714
1715 fn id(&self) -> Option<ElementId> {
1716 self.interactivity.element_id.clone()
1717 }
1718
1719 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
1720 self.interactivity.source_location()
1721 }
1722
1723 fn a11y_role(&self) -> Option<accesskit::Role> {
1724 // Nodes with `GenericContainer` should never be reported to accesskit.
1725 // Equivalent to an HTML div with no role.
1726 self.interactivity
1727 .override_role
1728 .filter(|role| *role != accesskit::Role::GenericContainer)
1729 }
1730
1731 fn write_a11y_info(&self, node: &mut accesskit::Node) {
1732 self.interactivity.write_a11y_info(node);
1733 }
1734
1735 fn a11y_synthetic_children(
1736 &mut self,
1737 _prepaint: &mut Self::PrepaintState,
1738 builder: &mut crate::A11ySubtreeBuilder,
1739 ) {
1740 if let Some(f) = self.interactivity.a11y_synthetic_children.take() {
1741 f(builder);
1742 }
1743 }
1744
1745 #[stacksafe]
1746 fn request_layout(
1747 &mut self,
1748 global_id: Option<&GlobalElementId>,
1749 inspector_id: Option<&InspectorElementId>,
1750 window: &mut Window,
1751 cx: &mut App,
1752 ) -> (LayoutId, Self::RequestLayoutState) {
1753 let mut child_layout_ids = SmallVec::new();
1754 let image_cache = self
1755 .image_cache
1756 .as_mut()
1757 .map(|provider| provider.provide(window, cx));
1758
1759 let layout_id = window.with_image_cache(image_cache, |window| {
1760 self.interactivity.request_layout(
1761 global_id,
1762 inspector_id,
1763 window,
1764 cx,
1765 |style, window, cx| {
1766 window.with_text_style(style.text_style().cloned(), |window| {
1767 child_layout_ids = self
1768 .children
1769 .iter_mut()
1770 .map(|child| child.request_layout(window, cx))
1771 .collect::<SmallVec<_>>();
1772 window.request_layout(style, child_layout_ids.iter().copied(), cx)
1773 })
1774 },
1775 )
1776 });
1777
1778 (layout_id, DivFrameState { child_layout_ids })
1779 }
1780
1781 #[stacksafe]
1782 fn prepaint(
1783 &mut self,
1784 global_id: Option<&GlobalElementId>,
1785 inspector_id: Option<&InspectorElementId>,
1786 bounds: Bounds<Pixels>,
1787 request_layout: &mut Self::RequestLayoutState,
1788 window: &mut Window,
1789 cx: &mut App,
1790 ) -> Option<Hitbox> {
1791 let image_cache = self
1792 .image_cache
1793 .as_mut()
1794 .map(|provider| provider.provide(window, cx));
1795
1796 let has_prepaint_listener = self.prepaint_listener.is_some();
1797 let mut children_bounds = Vec::with_capacity(if has_prepaint_listener {
1798 request_layout.child_layout_ids.len()
1799 } else {
1800 0
1801 });
1802
1803 let mut child_min = point(Pixels::MAX, Pixels::MAX);
1804 let mut child_max = Point::default();
1805 if let Some(handle) = self.interactivity.scroll_anchor.as_ref() {
1806 *handle.last_origin.borrow_mut() = bounds.origin - window.element_offset();
1807 }
1808 let content_size = if request_layout.child_layout_ids.is_empty() {
1809 bounds.size
1810 } else if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
1811 let mut state = scroll_handle.0.borrow_mut();
1812 state.child_bounds = Vec::with_capacity(request_layout.child_layout_ids.len());
1813 for child_layout_id in &request_layout.child_layout_ids {
1814 let child_bounds = window.layout_bounds(*child_layout_id);
1815 child_min = child_min.min(&child_bounds.origin);
1816 child_max = child_max.max(&child_bounds.bottom_right());
1817 state.child_bounds.push(child_bounds);
1818 }
1819 (child_max - child_min).into()
1820 } else {
1821 for child_layout_id in &request_layout.child_layout_ids {
1822 let child_bounds = window.layout_bounds(*child_layout_id);
1823 child_min = child_min.min(&child_bounds.origin);
1824 child_max = child_max.max(&child_bounds.bottom_right());
1825
1826 if has_prepaint_listener {
1827 children_bounds.push(child_bounds);
1828 }
1829 }
1830 (child_max - child_min).into()
1831 };
1832
1833 if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
1834 scroll_handle.scroll_to_active_item();
1835 }
1836
1837 self.interactivity.prepaint(
1838 global_id,
1839 inspector_id,
1840 bounds,
1841 content_size,
1842 window,
1843 cx,
1844 |style, scroll_offset, hitbox, window, cx| {
1845 // skip children
1846 if style.display == Display::None {
1847 return hitbox;
1848 }
1849
1850 window.with_image_cache(image_cache, |window| {
1851 window.with_element_offset(scroll_offset, |window| {
1852 if let Some(order_fn) = &self.prepaint_order_fn {
1853 let order = order_fn(window, cx);
1854 for idx in order {
1855 if let Some(child) = self.children.get_mut(idx) {
1856 child.prepaint(window, cx);
1857 }
1858 }
1859 } else {
1860 for child in &mut self.children {
1861 child.prepaint(window, cx);
1862 }
1863 }
1864 });
1865
1866 if let Some(listener) = self.prepaint_listener.as_ref() {
1867 listener(children_bounds, window, cx);
1868 }
1869 });
1870
1871 hitbox
1872 },
1873 )
1874 }
1875
1876 #[stacksafe]
1877 fn paint(
1878 &mut self,
1879 global_id: Option<&GlobalElementId>,
1880 inspector_id: Option<&InspectorElementId>,
1881 bounds: Bounds<Pixels>,
1882 _request_layout: &mut Self::RequestLayoutState,
1883 hitbox: &mut Option<Hitbox>,
1884 window: &mut Window,
1885 cx: &mut App,
1886 ) {
1887 let image_cache = self
1888 .image_cache
1889 .as_mut()
1890 .map(|provider| provider.provide(window, cx));
1891
1892 window.with_image_cache(image_cache, |window| {
1893 self.interactivity.paint(
1894 global_id,
1895 inspector_id,
1896 bounds,
1897 hitbox.as_ref(),
1898 window,
1899 cx,
1900 |style, window, cx| {
1901 // skip children
1902 if style.display == Display::None {
1903 return;
1904 }
1905
1906 for child in &mut self.children {
1907 child.paint(window, cx);
1908 }
1909 },
1910 )
1911 });
1912 }
1913}
1914
1915impl IntoElement for Div {
1916 type Element = Self;
1917
1918 fn into_element(self) -> Self::Element {
1919 self
1920 }
1921}
1922
1923#[derive(Default)]
1924pub(crate) struct AriaProperties {
1925 pub(crate) label: Option<SharedString>,
1926 pub(crate) description: Option<SharedString>,
1927 pub(crate) keyshortcuts: Option<SharedString>,
1928 pub(crate) selected: Option<bool>,
1929 pub(crate) expanded: Option<bool>,
1930 pub(crate) toggled: Option<accesskit::Toggled>,
1931 pub(crate) numeric_value: Option<f64>,
1932 pub(crate) min_numeric_value: Option<f64>,
1933 pub(crate) max_numeric_value: Option<f64>,
1934 pub(crate) numeric_value_step: Option<f64>,
1935 pub(crate) value: Option<SharedString>,
1936 pub(crate) placeholder: Option<SharedString>,
1937 pub(crate) orientation: Option<accesskit::Orientation>,
1938 pub(crate) level: Option<usize>,
1939 pub(crate) position_in_set: Option<usize>,
1940 pub(crate) size_of_set: Option<usize>,
1941 pub(crate) row_index: Option<usize>,
1942 pub(crate) column_index: Option<usize>,
1943 pub(crate) row_count: Option<usize>,
1944 pub(crate) column_count: Option<usize>,
1945}
1946
1947/// The interactivity struct. Powers all of the general-purpose
1948/// interactivity in the `Div` element.
1949#[derive(Default)]
1950pub struct Interactivity {
1951 /// The element ID of the element. In id is required to support a stateful subset of the interactivity such as on_click.
1952 pub element_id: Option<ElementId>,
1953 /// Whether the element was clicked. This will only be present after layout.
1954 pub active: Option<bool>,
1955 /// Whether the element was hovered. This will only be present after paint if an hitbox
1956 /// was created for the interactive element.
1957 pub hovered: Option<bool>,
1958 pub(crate) tooltip_id: Option<TooltipId>,
1959 pub(crate) content_size: Size<Pixels>,
1960 pub(crate) key_context: Option<KeyContext>,
1961 pub(crate) focusable: bool,
1962 pub(crate) tracked_focus_handle: Option<FocusHandle>,
1963 pub(crate) tracked_scroll_handle: Option<ScrollHandle>,
1964 pub(crate) scroll_anchor: Option<ScrollAnchor>,
1965 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
1966 pub(crate) group: Option<SharedString>,
1967 /// The base style of the element, before any modifications are applied
1968 /// by focus, active, etc.
1969 pub base_style: Box<StyleRefinement>,
1970 pub(crate) focus_style: Option<Box<StyleRefinement>>,
1971 pub(crate) in_focus_style: Option<Box<StyleRefinement>>,
1972 pub(crate) focus_visible_style: Option<Box<StyleRefinement>>,
1973 pub(crate) hover_style: Option<Box<StyleRefinement>>,
1974 pub(crate) group_hover_style: Option<GroupStyle>,
1975 pub(crate) active_style: Option<Box<StyleRefinement>>,
1976 pub(crate) group_active_style: Option<GroupStyle>,
1977 pub(crate) drag_over_styles: Vec<(
1978 TypeId,
1979 Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> StyleRefinement>,
1980 )>,
1981 pub(crate) group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
1982 pub(crate) mouse_down_listeners: Vec<MouseDownListener>,
1983 pub(crate) mouse_up_listeners: Vec<MouseUpListener>,
1984 pub(crate) mouse_pressure_listeners: Vec<MousePressureListener>,
1985 pub(crate) mouse_move_listeners: Vec<MouseMoveListener>,
1986 pub(crate) mouse_exit_listeners: Vec<MouseExitListener>,
1987 pub(crate) scroll_wheel_listeners: Vec<ScrollWheelListener>,
1988 pub(crate) pinch_listeners: Vec<PinchListener>,
1989 pub(crate) key_down_listeners: Vec<KeyDownListener>,
1990 pub(crate) key_up_listeners: Vec<KeyUpListener>,
1991 pub(crate) modifiers_changed_listeners: Vec<ModifiersChangedListener>,
1992 pub(crate) action_listeners: Vec<(TypeId, ActionListener)>,
1993 pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
1994 pub(crate) can_drop_predicate: Option<CanDropPredicate>,
1995 pub(crate) click_listeners: Vec<ClickListener>,
1996 pub(crate) aux_click_listeners: Vec<ClickListener>,
1997 pub(crate) drag_listener: Option<(Arc<dyn Any>, DragListener)>,
1998 pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut Window, &mut App)>>,
1999 pub(crate) tooltip_builder: Option<TooltipBuilder>,
2000 pub(crate) tooltip_show_delay: Option<Duration>,
2001 pub(crate) window_control: Option<WindowControlArea>,
2002 pub(crate) hitbox_behavior: HitboxBehavior,
2003 pub(crate) tab_index: Option<isize>,
2004 pub(crate) tab_group: bool,
2005 pub(crate) tab_stop: bool,
2006
2007 pub(crate) a11y_action_listeners:
2008 Vec<(accesskit::Action, crate::window::a11y::A11yActionListener)>,
2009 pub(crate) a11y_synthetic_children: Option<Box<dyn FnOnce(&mut crate::A11ySubtreeBuilder)>>,
2010 pub(crate) report_active_descendant_focus: bool,
2011 pub(crate) override_role: Option<accesskit::Role>,
2012 pub(crate) aria: AriaProperties,
2013
2014 #[cfg(any(feature = "inspector", debug_assertions))]
2015 pub(crate) source_location: Option<&'static core::panic::Location<'static>>,
2016
2017 #[cfg(any(test, feature = "test-support"))]
2018 pub(crate) debug_selector: Option<String>,
2019}
2020
2021impl Interactivity {
2022 /// Layout this element according to this interactivity state's configured styles
2023 pub fn request_layout(
2024 &mut self,
2025 global_id: Option<&GlobalElementId>,
2026 _inspector_id: Option<&InspectorElementId>,
2027 window: &mut Window,
2028 cx: &mut App,
2029 f: impl FnOnce(Style, &mut Window, &mut App) -> LayoutId,
2030 ) -> LayoutId {
2031 #[cfg(any(feature = "inspector", debug_assertions))]
2032 window.with_inspector_state(
2033 _inspector_id,
2034 cx,
2035 |inspector_state: &mut Option<DivInspectorState>, _window| {
2036 if let Some(inspector_state) = inspector_state {
2037 self.base_style = inspector_state.base_style.clone();
2038 } else {
2039 *inspector_state = Some(DivInspectorState {
2040 base_style: self.base_style.clone(),
2041 bounds: Default::default(),
2042 content_size: Default::default(),
2043 })
2044 }
2045 },
2046 );
2047
2048 window.with_optional_element_state::<InteractiveElementState, _>(
2049 global_id,
2050 |element_state, window| {
2051 let mut element_state =
2052 element_state.map(|element_state| element_state.unwrap_or_default());
2053
2054 if let Some(element_state) = element_state.as_ref()
2055 && cx.has_active_drag()
2056 {
2057 if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() {
2058 *pending_mouse_down.borrow_mut() = None;
2059 }
2060 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
2061 *clicked_state.borrow_mut() = ElementClickedState::default();
2062 }
2063 }
2064
2065 // Ensure we store a focus handle in our element state if we're focusable.
2066 // If there's an explicit focus handle we're tracking, use that. Otherwise
2067 // create a new handle and store it in the element state, which lives for as
2068 // as frames contain an element with this id.
2069 if self.focusable
2070 && self.tracked_focus_handle.is_none()
2071 && let Some(element_state) = element_state.as_mut()
2072 {
2073 let mut handle = element_state
2074 .focus_handle
2075 .get_or_insert_with(|| cx.focus_handle())
2076 .clone()
2077 .tab_stop(self.tab_stop);
2078
2079 if let Some(index) = self.tab_index {
2080 handle = handle.tab_index(index);
2081 }
2082
2083 self.tracked_focus_handle = Some(handle);
2084 }
2085
2086 if let Some(scroll_handle) = self.tracked_scroll_handle.as_ref() {
2087 self.scroll_offset = Some(scroll_handle.0.borrow().offset.clone());
2088 } else if (self.base_style.overflow.x == Some(Overflow::Scroll)
2089 || self.base_style.overflow.y == Some(Overflow::Scroll))
2090 && let Some(element_state) = element_state.as_mut()
2091 {
2092 self.scroll_offset = Some(
2093 element_state
2094 .scroll_offset
2095 .get_or_insert_with(Rc::default)
2096 .clone(),
2097 );
2098 }
2099
2100 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
2101 let layout_id = f(style, window, cx);
2102 (layout_id, element_state)
2103 },
2104 )
2105 }
2106
2107 /// Commit the bounds of this element according to this interactivity state's configured styles.
2108 pub fn prepaint<R>(
2109 &mut self,
2110 global_id: Option<&GlobalElementId>,
2111 _inspector_id: Option<&InspectorElementId>,
2112 bounds: Bounds<Pixels>,
2113 content_size: Size<Pixels>,
2114 window: &mut Window,
2115 cx: &mut App,
2116 f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut Window, &mut App) -> R,
2117 ) -> R {
2118 self.content_size = content_size;
2119
2120 #[cfg(any(feature = "inspector", debug_assertions))]
2121 window.with_inspector_state(
2122 _inspector_id,
2123 cx,
2124 |inspector_state: &mut Option<DivInspectorState>, _window| {
2125 if let Some(inspector_state) = inspector_state {
2126 inspector_state.bounds = bounds;
2127 inspector_state.content_size = content_size;
2128 }
2129 },
2130 );
2131
2132 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
2133 window.set_focus_handle(focus_handle, cx);
2134
2135 if window.a11y.is_active() {
2136 if let Some(global_id) = global_id {
2137 let node_id = global_id.accesskit_node_id();
2138 window.a11y.set_focusable(node_id, focus_handle.id);
2139 if focus_handle.is_focused(window) {
2140 window.a11y.set_focus(node_id);
2141 }
2142 } else if focus_handle.is_focused(window) {
2143 // Focusable, but with no element id it can't have an
2144 // accessibility node, so screen readers fall back to the
2145 // whole window.
2146 window
2147 .a11y
2148 .note_focus_without_node(focus_handle.id, "it has no element id");
2149 }
2150 }
2151 }
2152
2153 if self.report_active_descendant_focus && window.a11y.is_active() {
2154 if let Some(global_id) = global_id {
2155 window
2156 .a11y
2157 .set_active_descendant(global_id.accesskit_node_id());
2158 }
2159 }
2160 window.with_optional_element_state::<InteractiveElementState, _>(
2161 global_id,
2162 |element_state, window| {
2163 let mut element_state =
2164 element_state.map(|element_state| element_state.unwrap_or_default());
2165 let style = self.compute_style_internal(None, element_state.as_mut(), window, cx);
2166
2167 if let Some(element_state) = element_state.as_mut() {
2168 if let Some(clicked_state) = element_state.clicked_state.as_ref() {
2169 let clicked_state = clicked_state.borrow();
2170 self.active = Some(clicked_state.element);
2171 }
2172 if self.hover_style.is_some() || self.group_hover_style.is_some() {
2173 element_state
2174 .hover_state
2175 .get_or_insert_with(Default::default);
2176 }
2177 if let Some(active_tooltip) = element_state.active_tooltip.as_ref() {
2178 if self.tooltip_builder.is_some() {
2179 self.tooltip_id = set_tooltip_on_window(active_tooltip, window);
2180 } else {
2181 // If there is no longer a tooltip builder, remove the active tooltip.
2182 element_state.active_tooltip.take();
2183 }
2184 }
2185 }
2186
2187 window.with_text_style(style.text_style().cloned(), |window| {
2188 window.with_content_mask(
2189 style.overflow_mask(bounds, window.rem_size()),
2190 |window| {
2191 let hitbox = if self.should_insert_hitbox(&style, window, cx) {
2192 Some(window.insert_hitbox(bounds, self.hitbox_behavior))
2193 } else {
2194 None
2195 };
2196
2197 let scroll_offset =
2198 self.clamp_scroll_position(bounds, &style, window, cx);
2199 let result = f(&style, scroll_offset, hitbox, window, cx);
2200 (result, element_state)
2201 },
2202 )
2203 })
2204 },
2205 )
2206 }
2207
2208 fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
2209 self.hitbox_behavior != HitboxBehavior::Normal
2210 || self.window_control.is_some()
2211 || style.mouse_cursor.is_some()
2212 || self.group.is_some()
2213 || self.scroll_offset.is_some()
2214 || self.tracked_focus_handle.is_some()
2215 || self.hover_style.is_some()
2216 || self.group_hover_style.is_some()
2217 || self.hover_listener.is_some()
2218 || !self.mouse_up_listeners.is_empty()
2219 || !self.mouse_pressure_listeners.is_empty()
2220 || !self.mouse_down_listeners.is_empty()
2221 || !self.mouse_move_listeners.is_empty()
2222 || !self.mouse_exit_listeners.is_empty()
2223 || !self.click_listeners.is_empty()
2224 || !self.aux_click_listeners.is_empty()
2225 || !self.scroll_wheel_listeners.is_empty()
2226 || self.has_pinch_listeners()
2227 || self.drag_listener.is_some()
2228 || !self.drop_listeners.is_empty()
2229 || !self.drag_over_styles.is_empty()
2230 || self.tooltip_builder.is_some()
2231 || window.is_inspector_picking(cx)
2232 }
2233
2234 fn clamp_scroll_position(
2235 &self,
2236 bounds: Bounds<Pixels>,
2237 style: &Style,
2238 window: &mut Window,
2239 _cx: &mut App,
2240 ) -> Point<Pixels> {
2241 fn round_to_two_decimals(pixels: Pixels) -> Pixels {
2242 const ROUNDING_FACTOR: f32 = 100.0;
2243 (pixels * ROUNDING_FACTOR).round() / ROUNDING_FACTOR
2244 }
2245
2246 if let Some(scroll_offset) = self.scroll_offset.as_ref() {
2247 let mut scroll_to_bottom = false;
2248 let mut tracked_scroll_handle = self
2249 .tracked_scroll_handle
2250 .as_ref()
2251 .map(|handle| handle.0.borrow_mut());
2252 if let Some(mut scroll_handle_state) = tracked_scroll_handle.as_deref_mut() {
2253 scroll_handle_state.overflow = style.overflow;
2254 scroll_to_bottom = mem::take(&mut scroll_handle_state.scroll_to_bottom);
2255 }
2256
2257 let rem_size = window.rem_size();
2258 let padding = style.padding.to_pixels(bounds.size.into(), rem_size);
2259 let padding_size = size(padding.left + padding.right, padding.top + padding.bottom);
2260 // The floating point values produced by Taffy and ours often vary
2261 // slightly after ~5 decimal places. This can lead to cases where after
2262 // subtracting these, the container becomes scrollable for less than
2263 // 0.00000x pixels. As we generally don't benefit from a precision that
2264 // high for the maximum scroll, we round the scroll max to 2 decimal
2265 // places here.
2266 let padded_content_size = self.content_size + padding_size;
2267 let scroll_max = Point::from(padded_content_size - bounds.size)
2268 .map(round_to_two_decimals)
2269 .max(&Default::default());
2270 // Clamp scroll offset in case scroll max is smaller now (e.g., if children
2271 // were removed or the bounds became larger).
2272 let mut scroll_offset = scroll_offset.borrow_mut();
2273
2274 scroll_offset.x = scroll_offset.x.clamp(-scroll_max.x, px(0.));
2275 if scroll_to_bottom {
2276 scroll_offset.y = -scroll_max.y;
2277 } else {
2278 scroll_offset.y = scroll_offset.y.clamp(-scroll_max.y, px(0.));
2279 }
2280
2281 if let Some(mut scroll_handle_state) = tracked_scroll_handle {
2282 scroll_handle_state.max_offset = scroll_max;
2283 scroll_handle_state.bounds = bounds;
2284 }
2285
2286 *scroll_offset
2287 } else {
2288 Point::default()
2289 }
2290 }
2291
2292 /// Paint this element according to this interactivity state's configured styles
2293 /// and bind the element's mouse and keyboard events.
2294 ///
2295 /// content_size is the size of the content of the element, which may be larger than the
2296 /// element's bounds if the element is scrollable.
2297 ///
2298 /// the final computed style will be passed to the provided function, along
2299 /// with the current scroll offset
2300 pub fn paint(
2301 &mut self,
2302 global_id: Option<&GlobalElementId>,
2303 _inspector_id: Option<&InspectorElementId>,
2304 bounds: Bounds<Pixels>,
2305 hitbox: Option<&Hitbox>,
2306 window: &mut Window,
2307 cx: &mut App,
2308 f: impl FnOnce(&Style, &mut Window, &mut App),
2309 ) {
2310 self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(window));
2311 window.with_optional_element_state::<InteractiveElementState, _>(
2312 global_id,
2313 |element_state, window| {
2314 let mut element_state =
2315 element_state.map(|element_state| element_state.unwrap_or_default());
2316
2317 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
2318
2319 #[cfg(any(feature = "test-support", test))]
2320 if let Some(debug_selector) = &self.debug_selector {
2321 window
2322 .next_frame
2323 .debug_bounds
2324 .insert(debug_selector.clone(), bounds);
2325 }
2326
2327 self.paint_hover_group_handler(window, cx);
2328
2329 if style.visibility == Visibility::Hidden {
2330 return ((), element_state);
2331 }
2332
2333 let mut tab_group = None;
2334 if self.tab_group {
2335 tab_group = self.tab_index;
2336 }
2337
2338 window.with_element_opacity(style.opacity, |window| {
2339 style.paint(bounds, window, cx, |window: &mut Window, cx: &mut App| {
2340 window.with_text_style(style.text_style().cloned(), |window| {
2341 window.with_content_mask(
2342 style.overflow_mask(bounds, window.rem_size()),
2343 |window| {
2344 window.with_tab_group(tab_group, |window| {
2345 // Register the container's own focus handle *inside* its
2346 // tab group, so that focusing the container and then
2347 // calling `focus_next` descends into this group's first
2348 // item. Inserting it before `with_tab_group` would give the
2349 // container a shallower tab path than its children; with
2350 // sibling groups every container would then sort ahead of
2351 // every item, and `focus_next` from a container would jump
2352 // to the first item in the whole window instead of its own.
2353 if let Some(focus_handle) = &self.tracked_focus_handle {
2354 window.next_frame.tab_stops.insert(focus_handle);
2355 }
2356 if let Some(hitbox) = hitbox {
2357 #[cfg(debug_assertions)]
2358 self.paint_debug_info(
2359 global_id, hitbox, &style, window, cx,
2360 );
2361
2362 if let Some(drag) = cx.active_drag.as_ref() {
2363 if let Some(mouse_cursor) = drag.cursor_style {
2364 window.set_window_cursor_style(mouse_cursor);
2365 }
2366 } else {
2367 if let Some(mouse_cursor) = style.mouse_cursor {
2368 window.set_cursor_style(mouse_cursor, hitbox);
2369 }
2370 }
2371
2372 if let Some(group) = self.group.clone() {
2373 GroupHitboxes::push(group, hitbox.id, cx);
2374 }
2375
2376 if let Some(area) = self.window_control {
2377 window.insert_window_control_hitbox(
2378 area,
2379 hitbox.clone(),
2380 );
2381 }
2382
2383 self.paint_mouse_listeners(
2384 hitbox,
2385 element_state.as_mut(),
2386 window,
2387 cx,
2388 );
2389 self.paint_scroll_listener(hitbox, &style, window, cx);
2390 }
2391
2392 self.paint_keyboard_listeners(window, cx);
2393
2394 if window.a11y.is_active() {
2395 if let Some(global_id) = global_id {
2396 if !self.a11y_action_listeners.is_empty() {
2397 let node_id = global_id.accesskit_node_id();
2398 for (action, listener) in
2399 self.a11y_action_listeners.drain(..)
2400 {
2401 window.on_a11y_action(
2402 node_id, action, listener,
2403 );
2404 }
2405 }
2406 }
2407 }
2408
2409 f(&style, window, cx);
2410
2411 if let Some(_hitbox) = hitbox {
2412 #[cfg(any(feature = "inspector", debug_assertions))]
2413 window.insert_inspector_hitbox(
2414 _hitbox.id,
2415 _inspector_id,
2416 cx,
2417 );
2418
2419 if let Some(group) = self.group.as_ref() {
2420 GroupHitboxes::pop(group, cx);
2421 }
2422 }
2423 })
2424 },
2425 );
2426 });
2427 });
2428 });
2429
2430 ((), element_state)
2431 },
2432 );
2433 }
2434
2435 #[cfg(debug_assertions)]
2436 fn paint_debug_info(
2437 &self,
2438 global_id: Option<&GlobalElementId>,
2439 hitbox: &Hitbox,
2440 style: &Style,
2441 window: &mut Window,
2442 cx: &mut App,
2443 ) {
2444 use crate::{BorderStyle, TextAlign};
2445
2446 if let Some(global_id) = global_id
2447 && (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
2448 && hitbox.is_hovered(window)
2449 {
2450 const FONT_SIZE: crate::Pixels = crate::Pixels(10.);
2451 let element_id = format!("{global_id:?}");
2452 let str_len = element_id.len();
2453
2454 let render_debug_text = |window: &mut Window| {
2455 if let Some(text) = window
2456 .text_system()
2457 .shape_text(
2458 element_id.into(),
2459 FONT_SIZE,
2460 &[window.text_style().to_run(str_len)],
2461 None,
2462 None,
2463 )
2464 .ok()
2465 .and_then(|mut text| text.pop())
2466 {
2467 text.paint(hitbox.origin, FONT_SIZE, TextAlign::Left, None, window, cx)
2468 .ok();
2469
2470 let text_bounds = crate::Bounds {
2471 origin: hitbox.origin,
2472 size: text.size(FONT_SIZE),
2473 };
2474 if let Some(source_location) = self.source_location
2475 && text_bounds.contains(&window.mouse_position())
2476 && window.modifiers().secondary()
2477 {
2478 let secondary_held = window.modifiers().secondary();
2479 window.on_key_event({
2480 move |e: &crate::ModifiersChangedEvent, _phase, window, _cx| {
2481 if e.modifiers.secondary() != secondary_held
2482 && text_bounds.contains(&window.mouse_position())
2483 {
2484 window.refresh();
2485 }
2486 }
2487 });
2488
2489 let was_hovered = hitbox.is_hovered(window);
2490 let current_view = window.current_view();
2491 window.on_mouse_event({
2492 let hitbox = hitbox.clone();
2493 move |_: &MouseMoveEvent, phase, window, cx| {
2494 if phase == DispatchPhase::Capture {
2495 let hovered = hitbox.is_hovered(window);
2496 if hovered != was_hovered {
2497 cx.notify(current_view)
2498 }
2499 }
2500 }
2501 });
2502
2503 window.on_mouse_event({
2504 let hitbox = hitbox.clone();
2505 move |e: &crate::MouseDownEvent, phase, window, cx| {
2506 if text_bounds.contains(&e.position)
2507 && phase.capture()
2508 && hitbox.is_hovered(window)
2509 {
2510 cx.stop_propagation();
2511 let Ok(dir) = std::env::current_dir() else {
2512 return;
2513 };
2514
2515 eprintln!(
2516 "This element was created at:\n{}:{}:{}",
2517 dir.join(source_location.file()).to_string_lossy(),
2518 source_location.line(),
2519 source_location.column()
2520 );
2521 }
2522 }
2523 });
2524 window.paint_quad(crate::outline(
2525 crate::Bounds {
2526 origin: hitbox.origin
2527 + crate::point(crate::px(0.), FONT_SIZE - px(2.)),
2528 size: crate::Size {
2529 width: text_bounds.size.width,
2530 height: crate::px(1.),
2531 },
2532 },
2533 crate::red(),
2534 BorderStyle::default(),
2535 ))
2536 }
2537 }
2538 };
2539
2540 window.with_text_style(
2541 Some(crate::TextStyleRefinement {
2542 color: Some(crate::red()),
2543 line_height: Some(FONT_SIZE.into()),
2544 background_color: Some(crate::white()),
2545 ..Default::default()
2546 }),
2547 render_debug_text,
2548 )
2549 }
2550 }
2551
2552 fn paint_mouse_listeners(
2553 &mut self,
2554 hitbox: &Hitbox,
2555 element_state: Option<&mut InteractiveElementState>,
2556 window: &mut Window,
2557 cx: &mut App,
2558 ) {
2559 let is_focused = self
2560 .tracked_focus_handle
2561 .as_ref()
2562 .map(|handle| handle.is_focused(window))
2563 .unwrap_or(false);
2564
2565 // If this element can be focused, register a mouse down listener
2566 // that will automatically transfer focus when hitting the element.
2567 // This behavior can be suppressed by using `cx.prevent_default()`.
2568 if let Some(focus_handle) = self.tracked_focus_handle.clone() {
2569 let hitbox = hitbox.clone();
2570 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, cx| {
2571 if phase == DispatchPhase::Bubble
2572 && hitbox.is_hovered(window)
2573 && !window.default_prevented()
2574 {
2575 window.focus(&focus_handle, cx);
2576 // If there is a parent that is also focusable, prevent it
2577 // from transferring focus because we already did so.
2578 window.prevent_default();
2579 }
2580 });
2581 }
2582
2583 for listener in self.mouse_down_listeners.drain(..) {
2584 let hitbox = hitbox.clone();
2585 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
2586 listener(event, phase, &hitbox, window, cx);
2587 })
2588 }
2589
2590 for listener in self.mouse_up_listeners.drain(..) {
2591 let hitbox = hitbox.clone();
2592 window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
2593 listener(event, phase, &hitbox, window, cx);
2594 })
2595 }
2596
2597 for listener in self.mouse_pressure_listeners.drain(..) {
2598 let hitbox = hitbox.clone();
2599 window.on_mouse_event(move |event: &MousePressureEvent, phase, window, cx| {
2600 listener(event, phase, &hitbox, window, cx);
2601 })
2602 }
2603
2604 for listener in self.mouse_move_listeners.drain(..) {
2605 let hitbox = hitbox.clone();
2606 window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
2607 listener(event, phase, &hitbox, window, cx);
2608 })
2609 }
2610
2611 for listener in self.mouse_exit_listeners.drain(..) {
2612 let hitbox = hitbox.clone();
2613 window.on_mouse_event(move |event: &MouseExitEvent, phase, window, cx| {
2614 listener(event, phase, &hitbox, window, cx);
2615 })
2616 }
2617
2618 for listener in self.scroll_wheel_listeners.drain(..) {
2619 let hitbox = hitbox.clone();
2620 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
2621 listener(event, phase, &hitbox, window, cx);
2622 })
2623 }
2624
2625 for listener in self.pinch_listeners.drain(..) {
2626 let hitbox = hitbox.clone();
2627 window.on_mouse_event(move |event: &PinchEvent, phase, window, cx| {
2628 listener(event, phase, &hitbox, window, cx);
2629 })
2630 }
2631
2632 if self.hover_style.is_some()
2633 || self.base_style.mouse_cursor.is_some()
2634 || cx.active_drag.is_some() && !self.drag_over_styles.is_empty()
2635 {
2636 let hitbox = hitbox.clone();
2637 let hover_state = self.hover_style.as_ref().and_then(|_| {
2638 element_state
2639 .as_ref()
2640 .and_then(|state| state.hover_state.as_ref())
2641 .cloned()
2642 });
2643 let current_view = window.current_view();
2644
2645 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2646 let hovered = hitbox.is_hovered(window);
2647 let was_hovered = hover_state
2648 .as_ref()
2649 .is_some_and(|state| state.borrow().element);
2650 if phase == DispatchPhase::Capture && hovered != was_hovered {
2651 if let Some(hover_state) = &hover_state {
2652 hover_state.borrow_mut().element = hovered;
2653 cx.notify(current_view);
2654 }
2655 }
2656 });
2657 }
2658
2659 if let Some(group_hover) = self.group_hover_style.as_ref() {
2660 if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
2661 let hover_state = element_state
2662 .as_ref()
2663 .and_then(|element| element.hover_state.as_ref())
2664 .cloned();
2665 let current_view = window.current_view();
2666
2667 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
2668 let group_hovered = group_hitbox_id.is_hovered(window);
2669 let was_group_hovered = hover_state
2670 .as_ref()
2671 .is_some_and(|state| state.borrow().group);
2672 if phase == DispatchPhase::Capture && group_hovered != was_group_hovered {
2673 if let Some(hover_state) = &hover_state {
2674 hover_state.borrow_mut().group = group_hovered;
2675 cx.notify(current_view);
2676 }
2677 }
2678 });
2679 }
2680 }
2681
2682 let drag_cursor_style = self.base_style.as_ref().mouse_cursor;
2683
2684 let mut drag_listener = mem::take(&mut self.drag_listener);
2685 let drop_listeners = mem::take(&mut self.drop_listeners);
2686 let click_listeners = mem::take(&mut self.click_listeners);
2687 let aux_click_listeners = mem::take(&mut self.aux_click_listeners);
2688 let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
2689
2690 if !drop_listeners.is_empty() {
2691 let hitbox = hitbox.clone();
2692 window.on_mouse_event({
2693 move |_: &MouseUpEvent, phase, window, cx| {
2694 if let Some(drag) = &cx.active_drag
2695 && phase == DispatchPhase::Bubble
2696 && hitbox.is_hovered(window)
2697 {
2698 let drag_state_type = drag.value.as_ref().type_id();
2699 for (drop_state_type, listener) in &drop_listeners {
2700 if *drop_state_type == drag_state_type {
2701 let drag = cx
2702 .active_drag
2703 .take()
2704 .expect("checked for type drag state type above");
2705
2706 let mut can_drop = true;
2707 if let Some(predicate) = &can_drop_predicate {
2708 can_drop = predicate(drag.value.as_ref(), window, cx);
2709 }
2710
2711 if can_drop {
2712 listener(drag.value.as_ref(), window, cx);
2713 window.refresh();
2714 cx.stop_propagation();
2715 }
2716 }
2717 }
2718 }
2719 }
2720 });
2721 }
2722
2723 if let Some(element_state) = element_state {
2724 if !click_listeners.is_empty()
2725 || !aux_click_listeners.is_empty()
2726 || drag_listener.is_some()
2727 {
2728 let pending_mouse_down = element_state
2729 .pending_mouse_down
2730 .get_or_insert_with(Default::default)
2731 .clone();
2732
2733 let pending_keyboard_down = element_state
2734 .pending_keyboard_down
2735 .get_or_insert_with(Default::default)
2736 .clone();
2737
2738 let clicked_state = element_state
2739 .clicked_state
2740 .get_or_insert_with(Default::default)
2741 .clone();
2742
2743 window.on_mouse_event({
2744 let pending_mouse_down = pending_mouse_down.clone();
2745 let hitbox = hitbox.clone();
2746 let has_aux_click_listeners = !aux_click_listeners.is_empty();
2747 move |event: &MouseDownEvent, phase, window, _cx| {
2748 if phase == DispatchPhase::Bubble
2749 && (event.button == MouseButton::Left || has_aux_click_listeners)
2750 && hitbox.is_hovered(window)
2751 {
2752 *pending_mouse_down.borrow_mut() = Some(event.clone());
2753 window.refresh();
2754 }
2755 }
2756 });
2757
2758 window.on_mouse_event({
2759 let pending_mouse_down = pending_mouse_down.clone();
2760 let hitbox = hitbox.clone();
2761 move |event: &MouseMoveEvent, phase, window, cx| {
2762 if phase == DispatchPhase::Capture {
2763 return;
2764 }
2765
2766 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2767 if let Some(mouse_down) = pending_mouse_down.clone()
2768 && !cx.has_active_drag()
2769 && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
2770 && let Some((drag_value, drag_listener)) = drag_listener.take()
2771 && mouse_down.button == MouseButton::Left
2772 {
2773 *clicked_state.borrow_mut() = ElementClickedState::default();
2774 let cursor_offset = event.position - hitbox.origin;
2775 let drag =
2776 (drag_listener)(drag_value.as_ref(), cursor_offset, window, cx);
2777 cx.active_drag = Some(AnyDrag {
2778 view: drag,
2779 value: drag_value,
2780 cursor_offset,
2781 cursor_style: drag_cursor_style,
2782 });
2783 pending_mouse_down.take();
2784 window.refresh();
2785 cx.stop_propagation();
2786 }
2787 }
2788 });
2789
2790 if is_focused {
2791 // Record the focus generation at which an enter/space key
2792 // down event happened on this element. The next key up
2793 // event will be mapped to a click event if both of the
2794 // following are true:
2795 // - no other key events happen in between
2796 // - the focus generation is the same (implying focus did not move)
2797 //
2798 // This design avoids an ABA problem that happens if you
2799 // store the focus handle that registered the keypress.
2800 window.on_key_event({
2801 let pending_keyboard_down = pending_keyboard_down.clone();
2802 move |event: &KeyDownEvent, phase, window, _cx| {
2803 if phase.bubble() && !window.default_prevented() {
2804 let stroke = &event.keystroke;
2805 let is_activation_key = (stroke.key.eq("enter")
2806 || stroke.key.eq("space"))
2807 && !stroke.modifiers.modified();
2808 *pending_keyboard_down.borrow_mut() =
2809 is_activation_key.then_some(window.focus_generation);
2810 }
2811 }
2812 });
2813
2814 // Press enter, space to trigger click, when the element is focused.
2815 window.on_key_event({
2816 let click_listeners = click_listeners.clone();
2817 let hitbox = hitbox.clone();
2818 move |event: &KeyUpEvent, phase, window, cx| {
2819 if phase.bubble() && !window.default_prevented() {
2820 let stroke = &event.keystroke;
2821 let keyboard_button = if stroke.key.eq("enter") {
2822 Some(KeyboardButton::Enter)
2823 } else if stroke.key.eq("space") {
2824 Some(KeyboardButton::Space)
2825 } else {
2826 None
2827 };
2828
2829 if let Some(button) = keyboard_button
2830 && !stroke.modifiers.modified()
2831 {
2832 let pending =
2833 std::mem::take(&mut *pending_keyboard_down.borrow_mut());
2834 if pending != Some(window.focus_generation) {
2835 return;
2836 }
2837
2838 let click_event = ClickEvent::Keyboard(KeyboardClickEvent {
2839 button,
2840 bounds: hitbox.bounds,
2841 });
2842
2843 for listener in &click_listeners {
2844 listener(&click_event, window, cx);
2845 }
2846 } else {
2847 // Releasing any other key mid-press means
2848 // this isn't a clean activation, so cancel
2849 // the pending keydown.
2850 *pending_keyboard_down.borrow_mut() = None;
2851 }
2852 }
2853 }
2854 });
2855 }
2856
2857 window.on_mouse_event({
2858 let mut captured_mouse_down = None;
2859 let hitbox = hitbox.clone();
2860 move |event: &MouseUpEvent, phase, window, cx| match phase {
2861 // Clear the pending mouse down during the capture phase,
2862 // so that it happens even if another event handler stops
2863 // propagation.
2864 DispatchPhase::Capture => {
2865 let mut pending_mouse_down = pending_mouse_down.borrow_mut();
2866 if pending_mouse_down.is_some() && hitbox.is_hovered(window) {
2867 captured_mouse_down = pending_mouse_down.take();
2868 window.refresh();
2869 } else if pending_mouse_down.is_some() {
2870 // Clear the pending mouse down event (without firing click handlers)
2871 // if the hitbox is not being hovered.
2872 // This avoids dragging elements that changed their position
2873 // immediately after being clicked.
2874 // See https://github.com/zed-industries/zed/issues/24600 for more details
2875 pending_mouse_down.take();
2876 window.refresh();
2877 }
2878 }
2879 // Fire click handlers during the bubble phase.
2880 DispatchPhase::Bubble => {
2881 if let Some(mouse_down) = captured_mouse_down.take() {
2882 let btn = mouse_down.button;
2883
2884 let mouse_click = ClickEvent::Mouse(MouseClickEvent {
2885 down: mouse_down,
2886 up: event.clone(),
2887 });
2888
2889 match btn {
2890 MouseButton::Left => {
2891 for listener in &click_listeners {
2892 listener(&mouse_click, window, cx);
2893 }
2894 }
2895 _ => {
2896 for listener in &aux_click_listeners {
2897 listener(&mouse_click, window, cx);
2898 }
2899 }
2900 }
2901 }
2902 }
2903 }
2904 });
2905 }
2906
2907 if let Some(hover_listener) = self.hover_listener.take() {
2908 let was_hovered = element_state
2909 .hover_listener_state
2910 .get_or_insert_with(Default::default)
2911 .clone();
2912 let has_mouse_down = element_state
2913 .pending_mouse_down
2914 .get_or_insert_with(Default::default)
2915 .clone();
2916 let hover_listener = Rc::new(hover_listener);
2917 let update_hover = move |is_hovered: bool, window: &mut Window, cx: &mut App| {
2918 let mut was_hovered = was_hovered.borrow_mut();
2919 if is_hovered != *was_hovered {
2920 *was_hovered = is_hovered;
2921 drop(was_hovered);
2922 hover_listener(&is_hovered, window, cx);
2923 }
2924 };
2925
2926 window.on_mouse_event({
2927 let update_hover = update_hover.clone();
2928 let hitbox = hitbox.clone();
2929 move |_: &MouseMoveEvent, phase, window, cx| {
2930 if phase == DispatchPhase::Bubble {
2931 let is_hovered = has_mouse_down.borrow().is_none()
2932 && !cx.has_active_drag()
2933 && hitbox.is_hovered(window);
2934 update_hover(is_hovered, window, cx);
2935 }
2936 }
2937 });
2938
2939 // The pointer can leave the window without a final MouseMove, so also
2940 // clear hover on MouseExited.
2941 window.on_mouse_event(move |_: &MouseExitEvent, phase, window, cx| {
2942 if phase == DispatchPhase::Bubble {
2943 update_hover(false, window, cx);
2944 }
2945 });
2946 }
2947
2948 if let Some(tooltip_builder) = self.tooltip_builder.take() {
2949 let active_tooltip = element_state
2950 .active_tooltip
2951 .get_or_insert_with(Default::default)
2952 .clone();
2953 let pending_mouse_down = element_state
2954 .pending_mouse_down
2955 .get_or_insert_with(Default::default)
2956 .clone();
2957
2958 let tooltip_is_hoverable = tooltip_builder.hoverable;
2959 let build_tooltip = Rc::new(move |window: &mut Window, cx: &mut App| {
2960 Some(((tooltip_builder.build)(window, cx), tooltip_is_hoverable))
2961 });
2962 // Use bounds instead of testing hitbox since this is called during prepaint.
2963 let check_is_hovered_during_prepaint = Rc::new({
2964 let pending_mouse_down = pending_mouse_down.clone();
2965 let source_bounds = hitbox.bounds;
2966 move |window: &Window| {
2967 !window.last_input_was_keyboard()
2968 && pending_mouse_down.borrow().is_none()
2969 && source_bounds.contains(&window.mouse_position())
2970 }
2971 });
2972 let check_is_hovered = Rc::new({
2973 let hitbox = hitbox.clone();
2974 move |window: &Window| {
2975 pending_mouse_down.borrow().is_none() && hitbox.is_hovered(window)
2976 }
2977 });
2978 register_tooltip_mouse_handlers(
2979 &active_tooltip,
2980 self.tooltip_id,
2981 build_tooltip,
2982 check_is_hovered,
2983 check_is_hovered_during_prepaint,
2984 self.tooltip_show_delay,
2985 window,
2986 );
2987 }
2988
2989 // We unconditionally bind both the mouse up and mouse down active state handlers
2990 // Because we might not get a chance to render a frame before the mouse up event arrives.
2991 let active_state = element_state
2992 .clicked_state
2993 .get_or_insert_with(Default::default)
2994 .clone();
2995
2996 {
2997 let active_state = active_state.clone();
2998 window.on_mouse_event(move |_: &MouseUpEvent, phase, window, _cx| {
2999 if phase == DispatchPhase::Capture && active_state.borrow().is_clicked() {
3000 *active_state.borrow_mut() = ElementClickedState::default();
3001 window.refresh();
3002 }
3003 });
3004 }
3005
3006 {
3007 let active_group_hitbox = self
3008 .group_active_style
3009 .as_ref()
3010 .and_then(|group_active| GroupHitboxes::get(&group_active.group, cx));
3011 let hitbox = hitbox.clone();
3012 window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _cx| {
3013 if phase == DispatchPhase::Bubble && !window.default_prevented() {
3014 let group_hovered = active_group_hitbox
3015 .is_some_and(|group_hitbox_id| group_hitbox_id.is_hovered(window));
3016 let element_hovered = hitbox.is_hovered(window);
3017 if group_hovered || element_hovered {
3018 *active_state.borrow_mut() = ElementClickedState {
3019 group: group_hovered,
3020 element: element_hovered,
3021 };
3022 window.refresh();
3023 }
3024 }
3025 });
3026 }
3027 }
3028 }
3029
3030 fn paint_keyboard_listeners(&mut self, window: &mut Window, _cx: &mut App) {
3031 let key_down_listeners = mem::take(&mut self.key_down_listeners);
3032 let key_up_listeners = mem::take(&mut self.key_up_listeners);
3033 let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners);
3034 let action_listeners = mem::take(&mut self.action_listeners);
3035 if let Some(context) = self.key_context.clone() {
3036 window.set_key_context(context);
3037 }
3038
3039 for listener in key_down_listeners {
3040 window.on_key_event(move |event: &KeyDownEvent, phase, window, cx| {
3041 listener(event, phase, window, cx);
3042 })
3043 }
3044
3045 for listener in key_up_listeners {
3046 window.on_key_event(move |event: &KeyUpEvent, phase, window, cx| {
3047 listener(event, phase, window, cx);
3048 })
3049 }
3050
3051 for listener in modifiers_changed_listeners {
3052 window.on_modifiers_changed(move |event: &ModifiersChangedEvent, window, cx| {
3053 listener(event, window, cx);
3054 })
3055 }
3056
3057 for (action_type, listener) in action_listeners {
3058 window.on_action(action_type, listener)
3059 }
3060 }
3061
3062 fn paint_hover_group_handler(&self, window: &mut Window, cx: &mut App) {
3063 let group_hitbox = self
3064 .group_hover_style
3065 .as_ref()
3066 .and_then(|group_hover| GroupHitboxes::get(&group_hover.group, cx));
3067
3068 if let Some(group_hitbox) = group_hitbox {
3069 let was_hovered = group_hitbox.is_hovered(window);
3070 let current_view = window.current_view();
3071 window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| {
3072 let hovered = group_hitbox.is_hovered(window);
3073 if phase == DispatchPhase::Capture && hovered != was_hovered {
3074 cx.notify(current_view);
3075 }
3076 });
3077 }
3078 }
3079
3080 fn paint_scroll_listener(
3081 &self,
3082 hitbox: &Hitbox,
3083 style: &Style,
3084 window: &mut Window,
3085 _cx: &mut App,
3086 ) {
3087 if let Some(scroll_offset) = self.scroll_offset.clone() {
3088 let overflow = style.overflow;
3089 let allow_concurrent_scroll = style.allow_concurrent_scroll;
3090 let restrict_scroll_to_axis = style.restrict_scroll_to_axis;
3091 let line_height = window.line_height();
3092 let hitbox = hitbox.clone();
3093 let current_view = window.current_view();
3094 window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
3095 if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) {
3096 let mut scroll_offset = scroll_offset.borrow_mut();
3097 let old_scroll_offset = *scroll_offset;
3098 let delta = event.delta.pixel_delta(line_height);
3099
3100 let mut delta_x = Pixels::ZERO;
3101 if overflow.x == Overflow::Scroll {
3102 if !delta.x.is_zero() {
3103 delta_x = delta.x;
3104 } else if !restrict_scroll_to_axis && overflow.y != Overflow::Scroll {
3105 delta_x = delta.y;
3106 }
3107 }
3108 let mut delta_y = Pixels::ZERO;
3109 if overflow.y == Overflow::Scroll {
3110 if !delta.y.is_zero() {
3111 delta_y = delta.y;
3112 } else if !restrict_scroll_to_axis && overflow.x != Overflow::Scroll {
3113 delta_y = delta.x;
3114 }
3115 }
3116 if !allow_concurrent_scroll && !delta_x.is_zero() && !delta_y.is_zero() {
3117 if delta_x.abs() > delta_y.abs() {
3118 delta_y = Pixels::ZERO;
3119 } else {
3120 delta_x = Pixels::ZERO;
3121 }
3122 }
3123 scroll_offset.y += delta_y;
3124 scroll_offset.x += delta_x;
3125 if *scroll_offset != old_scroll_offset {
3126 cx.notify(current_view);
3127 }
3128 }
3129 });
3130 }
3131 }
3132
3133 /// Compute the visual style for this element, based on the current bounds and the element's state.
3134 pub fn compute_style(
3135 &self,
3136 global_id: Option<&GlobalElementId>,
3137 hitbox: Option<&Hitbox>,
3138 window: &mut Window,
3139 cx: &mut App,
3140 ) -> Style {
3141 window.with_optional_element_state(global_id, |element_state, window| {
3142 let mut element_state =
3143 element_state.map(|element_state| element_state.unwrap_or_default());
3144 let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx);
3145 (style, element_state)
3146 })
3147 }
3148
3149 /// Called from internal methods that have already called with_element_state.
3150 fn compute_style_internal(
3151 &self,
3152 hitbox: Option<&Hitbox>,
3153 element_state: Option<&mut InteractiveElementState>,
3154 window: &mut Window,
3155 cx: &mut App,
3156 ) -> Style {
3157 let mut style = Style::default();
3158 style.refine(&self.base_style);
3159
3160 if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
3161 if let Some(in_focus_style) = self.in_focus_style.as_ref()
3162 && focus_handle.within_focused(window, cx)
3163 {
3164 style.refine(in_focus_style);
3165 }
3166
3167 if let Some(focus_style) = self.focus_style.as_ref()
3168 && focus_handle.is_focused(window)
3169 {
3170 style.refine(focus_style);
3171 }
3172
3173 if let Some(focus_visible_style) = self.focus_visible_style.as_ref()
3174 && focus_handle.is_focused(window)
3175 && window.last_input_was_keyboard()
3176 {
3177 style.refine(focus_visible_style);
3178 }
3179 }
3180
3181 if !cx.has_active_drag() {
3182 if let Some(group_hover) = self.group_hover_style.as_ref() {
3183 let is_group_hovered =
3184 if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) {
3185 group_hitbox_id.is_hovered(window)
3186 } else if let Some(element_state) = element_state.as_ref() {
3187 element_state
3188 .hover_state
3189 .as_ref()
3190 .map(|state| state.borrow().group)
3191 .unwrap_or(false)
3192 } else {
3193 false
3194 };
3195
3196 if is_group_hovered {
3197 style.refine(&group_hover.style);
3198 }
3199 }
3200
3201 if let Some(hover_style) = self.hover_style.as_ref() {
3202 let is_hovered = if let Some(hitbox) = hitbox {
3203 hitbox.is_hovered(window)
3204 } else if let Some(element_state) = element_state.as_ref() {
3205 element_state
3206 .hover_state
3207 .as_ref()
3208 .map(|state| state.borrow().element)
3209 .unwrap_or(false)
3210 } else {
3211 false
3212 };
3213
3214 if is_hovered {
3215 style.refine(hover_style);
3216 }
3217 }
3218 }
3219
3220 if let Some(hitbox) = hitbox {
3221 if let Some(drag) = cx.active_drag.take() {
3222 let mut can_drop = true;
3223 if let Some(can_drop_predicate) = &self.can_drop_predicate {
3224 can_drop = can_drop_predicate(drag.value.as_ref(), window, cx);
3225 }
3226
3227 if can_drop {
3228 for (state_type, group_drag_style) in &self.group_drag_over_styles {
3229 if let Some(group_hitbox_id) =
3230 GroupHitboxes::get(&group_drag_style.group, cx)
3231 && *state_type == drag.value.as_ref().type_id()
3232 && group_hitbox_id.is_hovered(window)
3233 {
3234 style.refine(&group_drag_style.style);
3235 }
3236 }
3237
3238 for (state_type, build_drag_over_style) in &self.drag_over_styles {
3239 if *state_type == drag.value.as_ref().type_id() && hitbox.is_hovered(window)
3240 {
3241 style.refine(&build_drag_over_style(drag.value.as_ref(), window, cx));
3242 }
3243 }
3244 }
3245
3246 style.mouse_cursor = drag.cursor_style;
3247 cx.active_drag = Some(drag);
3248 }
3249 }
3250
3251 if let Some(element_state) = element_state {
3252 let clicked_state = element_state
3253 .clicked_state
3254 .get_or_insert_with(Default::default)
3255 .borrow();
3256 if clicked_state.group
3257 && let Some(group) = self.group_active_style.as_ref()
3258 {
3259 style.refine(&group.style)
3260 }
3261
3262 if let Some(active_style) = self.active_style.as_ref()
3263 && clicked_state.element
3264 {
3265 style.refine(active_style)
3266 }
3267 }
3268
3269 style
3270 }
3271
3272 pub(crate) fn write_a11y_info(&self, node: &mut accesskit::Node) {
3273 if let Some(label) = &self.aria.label {
3274 node.set_label(label.to_string());
3275 }
3276 if let Some(description) = &self.aria.description {
3277 node.set_description(description.to_string());
3278 }
3279 if let Some(keyshortcuts) = &self.aria.keyshortcuts {
3280 node.set_keyboard_shortcut(keyshortcuts.to_string());
3281 }
3282 if let Some(selected) = self.aria.selected {
3283 node.set_selected(selected);
3284 }
3285 if let Some(expanded) = self.aria.expanded {
3286 node.set_expanded(expanded);
3287 }
3288 if let Some(toggled) = self.aria.toggled {
3289 node.set_toggled(toggled);
3290 }
3291 if let Some(value) = self.aria.numeric_value {
3292 node.set_numeric_value(value);
3293 }
3294 if let Some(value) = self.aria.min_numeric_value {
3295 node.set_min_numeric_value(value);
3296 }
3297 if let Some(value) = self.aria.max_numeric_value {
3298 node.set_max_numeric_value(value);
3299 }
3300 if let Some(step) = self.aria.numeric_value_step {
3301 node.set_numeric_value_step(step);
3302 }
3303 if let Some(value) = &self.aria.value {
3304 node.set_value(value.to_string());
3305 }
3306 if let Some(placeholder) = &self.aria.placeholder {
3307 node.set_placeholder(placeholder.to_string());
3308 }
3309 if let Some(orientation) = self.aria.orientation {
3310 node.set_orientation(orientation);
3311 }
3312 if let Some(level) = self.aria.level {
3313 node.set_level(level);
3314 }
3315 if let Some(position) = self.aria.position_in_set {
3316 node.set_position_in_set(position);
3317 }
3318 if let Some(size) = self.aria.size_of_set {
3319 node.set_size_of_set(size);
3320 }
3321 if let Some(index) = self.aria.row_index {
3322 node.set_row_index(index);
3323 }
3324 if let Some(index) = self.aria.column_index {
3325 node.set_column_index(index);
3326 }
3327 if let Some(count) = self.aria.row_count {
3328 node.set_row_count(count);
3329 }
3330 if let Some(count) = self.aria.column_count {
3331 node.set_column_count(count);
3332 }
3333 if !self.click_listeners.is_empty() {
3334 node.add_action(accesskit::Action::Click);
3335 }
3336 if self.tracked_focus_handle.is_some() || self.focusable {
3337 node.add_action(accesskit::Action::Focus);
3338 }
3339 for (action, _) in &self.a11y_action_listeners {
3340 node.add_action(*action);
3341 }
3342 }
3343}
3344
3345/// The per-frame state of an interactive element. Used for tracking stateful interactions like clicks
3346/// and scroll offsets.
3347#[derive(Default)]
3348pub struct InteractiveElementState {
3349 pub(crate) focus_handle: Option<FocusHandle>,
3350 pub(crate) clicked_state: Option<Rc<RefCell<ElementClickedState>>>,
3351 pub(crate) hover_state: Option<Rc<RefCell<ElementHoverState>>>,
3352 pub(crate) hover_listener_state: Option<Rc<RefCell<bool>>>,
3353 pub(crate) pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
3354 /// Set to the window's [`focus_generation`](crate::Window::focus_generation)
3355 /// when an Enter/Space keydown is received while this element is focused,
3356 /// recording that we are waiting for the matching keyup to fire a keyboard
3357 /// click. On keyup the click only fires if the stored generation still
3358 /// matches the window's current one, i.e. focus never moved during the
3359 /// press (mirroring the browser clearing a control's pressed state on
3360 /// blur). `None` means no activation key is pending.
3361 pub(crate) pending_keyboard_down: Option<Rc<RefCell<Option<u64>>>>,
3362 pub(crate) scroll_offset: Option<Rc<RefCell<Point<Pixels>>>>,
3363 pub(crate) active_tooltip: Option<Rc<RefCell<Option<ActiveTooltip>>>>,
3364}
3365
3366/// Whether or not the element or a group that contains it is clicked by the mouse.
3367#[derive(Copy, Clone, Default, Eq, PartialEq)]
3368pub struct ElementClickedState {
3369 /// True if this element's group has been clicked, false otherwise
3370 pub group: bool,
3371
3372 /// True if this element has been clicked, false otherwise
3373 pub element: bool,
3374}
3375
3376impl ElementClickedState {
3377 fn is_clicked(&self) -> bool {
3378 self.group || self.element
3379 }
3380}
3381
3382/// Whether or not the element or a group that contains it is hovered.
3383#[derive(Copy, Clone, Default, Eq, PartialEq)]
3384pub struct ElementHoverState {
3385 /// True if this element's group is hovered, false otherwise
3386 pub group: bool,
3387
3388 /// True if this element is hovered, false otherwise
3389 pub element: bool,
3390}
3391
3392pub(crate) enum ActiveTooltip {
3393 /// Currently delaying before showing the tooltip.
3394 WaitingForShow { _task: Task<()> },
3395 /// Tooltip is visible, element was hovered or for hoverable tooltips, the tooltip was hovered.
3396 Visible {
3397 tooltip: AnyTooltip,
3398 is_hoverable: bool,
3399 },
3400 /// Tooltip is visible and hoverable, but the mouse is no longer hovering. Currently delaying
3401 /// before hiding it.
3402 WaitingForHide {
3403 tooltip: AnyTooltip,
3404 _task: Task<()>,
3405 },
3406}
3407
3408pub(crate) fn clear_active_tooltip(
3409 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3410 window: &mut Window,
3411) {
3412 match active_tooltip.borrow_mut().take() {
3413 None => {}
3414 Some(ActiveTooltip::WaitingForShow { .. }) => {}
3415 Some(ActiveTooltip::Visible { .. }) => window.refresh(),
3416 Some(ActiveTooltip::WaitingForHide { .. }) => window.refresh(),
3417 }
3418}
3419
3420pub(crate) fn clear_active_tooltip_if_not_hoverable(
3421 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3422 window: &mut Window,
3423) {
3424 let should_clear = match active_tooltip.borrow().as_ref() {
3425 None => false,
3426 Some(ActiveTooltip::WaitingForShow { .. }) => false,
3427 Some(ActiveTooltip::Visible { is_hoverable, .. }) => !is_hoverable,
3428 Some(ActiveTooltip::WaitingForHide { .. }) => false,
3429 };
3430 if should_clear {
3431 active_tooltip.borrow_mut().take();
3432 window.refresh();
3433 }
3434}
3435
3436pub(crate) fn set_tooltip_on_window(
3437 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3438 window: &mut Window,
3439) -> Option<TooltipId> {
3440 let tooltip = match active_tooltip.borrow().as_ref() {
3441 None => return None,
3442 Some(ActiveTooltip::WaitingForShow { .. }) => return None,
3443 Some(ActiveTooltip::Visible { tooltip, .. }) => tooltip.clone(),
3444 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => tooltip.clone(),
3445 };
3446 Some(window.set_tooltip(tooltip))
3447}
3448
3449pub(crate) fn register_tooltip_mouse_handlers(
3450 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3451 tooltip_id: Option<TooltipId>,
3452 build_tooltip: Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3453 check_is_hovered: Rc<dyn Fn(&Window) -> bool>,
3454 check_is_hovered_during_prepaint: Rc<dyn Fn(&Window) -> bool>,
3455 show_delay: Option<Duration>,
3456 window: &mut Window,
3457) {
3458 let current_view = window.current_view();
3459 let show_delay = show_delay.unwrap_or(DEFAULT_TOOLTIP_SHOW_DELAY);
3460
3461 window.on_mouse_event({
3462 let active_tooltip = active_tooltip.clone();
3463 let build_tooltip = build_tooltip.clone();
3464 let check_is_hovered = check_is_hovered.clone();
3465 move |_: &MouseMoveEvent, phase, window, cx| {
3466 handle_tooltip_mouse_move(
3467 &active_tooltip,
3468 &build_tooltip,
3469 &check_is_hovered,
3470 &check_is_hovered_during_prepaint,
3471 tooltip_id,
3472 current_view,
3473 phase,
3474 show_delay,
3475 window,
3476 cx,
3477 )
3478 }
3479 });
3480
3481 window.on_mouse_event({
3482 let active_tooltip = active_tooltip.clone();
3483 move |_: &MouseDownEvent, _phase, window: &mut Window, _cx| {
3484 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3485 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3486 }
3487 }
3488 });
3489
3490 window.on_mouse_event({
3491 let active_tooltip = active_tooltip.clone();
3492 move |_: &ScrollWheelEvent, _phase, window: &mut Window, _cx| {
3493 if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) {
3494 clear_active_tooltip_if_not_hoverable(&active_tooltip, window);
3495 }
3496 }
3497 });
3498}
3499
3500/// Handles displaying tooltips when an element is hovered.
3501///
3502/// The mouse hovering logic also relies on being called from window prepaint in order to handle the
3503/// case where the element the tooltip is on is not rendered - in that case its mouse listeners are
3504/// also not registered. During window prepaint, the hitbox information is not available, so
3505/// `check_is_hovered_during_prepaint` is used which bases the check off of the absolute bounds of
3506/// the element.
3507///
3508/// TODO: There's a minor bug due to the use of absolute bounds while checking during prepaint - it
3509/// does not know if the hitbox is occluded. In the case where a tooltip gets displayed and then
3510/// gets occluded after display, it will stick around until the mouse exits the hover bounds.
3511fn handle_tooltip_mouse_move(
3512 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3513 build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
3514 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3515 check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
3516 tooltip_id: Option<TooltipId>,
3517 current_view: EntityId,
3518 phase: DispatchPhase,
3519 show_delay: Duration,
3520 window: &mut Window,
3521 cx: &mut App,
3522) {
3523 // Separates logic for what mutation should occur from applying it, to avoid overlapping
3524 // RefCell borrows.
3525 enum Action {
3526 None,
3527 CancelShow,
3528 ScheduleShow,
3529 CheckVisible,
3530 }
3531
3532 let action = match active_tooltip.borrow().as_ref() {
3533 None => {
3534 let is_hovered = check_is_hovered(window);
3535 if is_hovered && phase.bubble() {
3536 Action::ScheduleShow
3537 } else {
3538 Action::None
3539 }
3540 }
3541 Some(ActiveTooltip::WaitingForShow { .. }) => {
3542 let is_hovered = check_is_hovered(window);
3543 if is_hovered {
3544 Action::None
3545 } else {
3546 Action::CancelShow
3547 }
3548 }
3549 Some(ActiveTooltip::Visible { is_hoverable, .. }) => {
3550 if phase.capture()
3551 && !check_is_hovered(window)
3552 && (!*is_hoverable
3553 || !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3554 {
3555 Action::CheckVisible
3556 } else {
3557 Action::None
3558 }
3559 }
3560 Some(ActiveTooltip::WaitingForHide { .. }) => {
3561 if phase.capture()
3562 && (check_is_hovered(window)
3563 || tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
3564 {
3565 Action::CheckVisible
3566 } else {
3567 Action::None
3568 }
3569 }
3570 };
3571
3572 match action {
3573 Action::None => {}
3574 Action::CancelShow => {
3575 // Cancel waiting to show tooltip when it is no longer hovered.
3576 active_tooltip.borrow_mut().take();
3577 }
3578 Action::ScheduleShow => {
3579 let delayed_show_task = window.spawn(cx, {
3580 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3581 let build_tooltip = build_tooltip.clone();
3582 let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
3583 async move |cx| {
3584 cx.background_executor().timer(show_delay).await;
3585 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3586 return;
3587 };
3588 cx.update(|window, cx| {
3589 let new_tooltip =
3590 build_tooltip(window, cx).map(|(view, tooltip_is_hoverable)| {
3591 let weak_active_tooltip = Rc::downgrade(&active_tooltip);
3592 ActiveTooltip::Visible {
3593 tooltip: AnyTooltip {
3594 view,
3595 mouse_position: window.mouse_position(),
3596 check_visible_and_update: Rc::new(
3597 move |tooltip_bounds, window, cx| {
3598 let Some(active_tooltip) =
3599 weak_active_tooltip.upgrade()
3600 else {
3601 return false;
3602 };
3603 handle_tooltip_check_visible_and_update(
3604 &active_tooltip,
3605 tooltip_is_hoverable,
3606 &check_is_hovered_during_prepaint,
3607 tooltip_bounds,
3608 window,
3609 cx,
3610 )
3611 },
3612 ),
3613 },
3614 is_hoverable: tooltip_is_hoverable,
3615 }
3616 });
3617 *active_tooltip.borrow_mut() = new_tooltip;
3618 window.refresh();
3619 })
3620 .ok();
3621 }
3622 });
3623 active_tooltip
3624 .borrow_mut()
3625 .replace(ActiveTooltip::WaitingForShow {
3626 _task: delayed_show_task,
3627 });
3628 }
3629 Action::CheckVisible => cx.notify(current_view),
3630 }
3631}
3632
3633/// Returns a callback which will be called by window prepaint to update tooltip visibility. The
3634/// purpose of doing this logic here instead of the mouse move handler is that the mouse move
3635/// handler won't get called when the element is not painted (e.g. via use of `visible_on_hover`).
3636fn handle_tooltip_check_visible_and_update(
3637 active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
3638 tooltip_is_hoverable: bool,
3639 check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
3640 tooltip_bounds: Bounds<Pixels>,
3641 window: &mut Window,
3642 cx: &mut App,
3643) -> bool {
3644 // Separates logic for what mutation should occur from applying it, to avoid overlapping RefCell
3645 // borrows.
3646 enum Action {
3647 None,
3648 Hide,
3649 ScheduleHide(AnyTooltip),
3650 CancelHide(AnyTooltip),
3651 }
3652
3653 let is_hovered = check_is_hovered(window)
3654 || (tooltip_is_hoverable && tooltip_bounds.contains(&window.mouse_position()));
3655 let action = match active_tooltip.borrow().as_ref() {
3656 Some(ActiveTooltip::Visible { tooltip, .. }) => {
3657 if is_hovered {
3658 Action::None
3659 } else {
3660 if tooltip_is_hoverable {
3661 Action::ScheduleHide(tooltip.clone())
3662 } else {
3663 Action::Hide
3664 }
3665 }
3666 }
3667 Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => {
3668 if is_hovered {
3669 Action::CancelHide(tooltip.clone())
3670 } else {
3671 Action::None
3672 }
3673 }
3674 None | Some(ActiveTooltip::WaitingForShow { .. }) => Action::None,
3675 };
3676
3677 match action {
3678 Action::None => {}
3679 Action::Hide => clear_active_tooltip(active_tooltip, window),
3680 Action::ScheduleHide(tooltip) => {
3681 let delayed_hide_task = window.spawn(cx, {
3682 let weak_active_tooltip = Rc::downgrade(active_tooltip);
3683 async move |cx| {
3684 cx.background_executor()
3685 .timer(HOVERABLE_TOOLTIP_HIDE_DELAY)
3686 .await;
3687 let Some(active_tooltip) = weak_active_tooltip.upgrade() else {
3688 return;
3689 };
3690 if active_tooltip.borrow_mut().take().is_some() {
3691 cx.update(|window, _cx| window.refresh()).ok();
3692 }
3693 }
3694 });
3695 active_tooltip
3696 .borrow_mut()
3697 .replace(ActiveTooltip::WaitingForHide {
3698 tooltip,
3699 _task: delayed_hide_task,
3700 });
3701 }
3702 Action::CancelHide(tooltip) => {
3703 // Cancel waiting to hide tooltip when it becomes hovered.
3704 active_tooltip.borrow_mut().replace(ActiveTooltip::Visible {
3705 tooltip,
3706 is_hoverable: true,
3707 });
3708 }
3709 }
3710
3711 active_tooltip.borrow().is_some()
3712}
3713
3714#[derive(Default)]
3715pub(crate) struct GroupHitboxes(HashMap<SharedString, SmallVec<[HitboxId; 1]>>);
3716
3717impl Global for GroupHitboxes {}
3718
3719impl GroupHitboxes {
3720 pub fn get(name: &SharedString, cx: &mut App) -> Option<HitboxId> {
3721 cx.default_global::<Self>()
3722 .0
3723 .get(name)
3724 .and_then(|bounds_stack| bounds_stack.last())
3725 .cloned()
3726 }
3727
3728 pub fn push(name: SharedString, hitbox_id: HitboxId, cx: &mut App) {
3729 cx.default_global::<Self>()
3730 .0
3731 .entry(name)
3732 .or_default()
3733 .push(hitbox_id);
3734 }
3735
3736 pub fn pop(name: &SharedString, cx: &mut App) {
3737 cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
3738 }
3739}
3740
3741/// A wrapper around an element that can store state, produced after assigning an ElementId.
3742pub struct Stateful<E> {
3743 pub(crate) element: E,
3744}
3745
3746impl<E> Styled for Stateful<E>
3747where
3748 E: Styled,
3749{
3750 fn style(&mut self) -> &mut StyleRefinement {
3751 self.element.style()
3752 }
3753}
3754
3755impl<E> StatefulInteractiveElement for Stateful<E>
3756where
3757 E: Element,
3758 Self: InteractiveElement,
3759{
3760}
3761
3762impl<E> InteractiveElement for Stateful<E>
3763where
3764 E: InteractiveElement,
3765{
3766 fn interactivity(&mut self) -> &mut Interactivity {
3767 self.element.interactivity()
3768 }
3769}
3770
3771impl<E> Element for Stateful<E>
3772where
3773 E: Element,
3774{
3775 type RequestLayoutState = E::RequestLayoutState;
3776 type PrepaintState = E::PrepaintState;
3777
3778 fn id(&self) -> Option<ElementId> {
3779 self.element.id()
3780 }
3781
3782 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
3783 self.element.source_location()
3784 }
3785
3786 fn a11y_role(&self) -> Option<accesskit::Role> {
3787 self.element.a11y_role()
3788 }
3789
3790 fn write_a11y_info(&self, node: &mut accesskit::Node) {
3791 self.element.write_a11y_info(node);
3792 }
3793
3794 fn a11y_synthetic_children(
3795 &mut self,
3796 prepaint: &mut Self::PrepaintState,
3797 builder: &mut crate::A11ySubtreeBuilder,
3798 ) {
3799 self.element.a11y_synthetic_children(prepaint, builder);
3800 }
3801
3802 fn request_layout(
3803 &mut self,
3804 id: Option<&GlobalElementId>,
3805 inspector_id: Option<&InspectorElementId>,
3806 window: &mut Window,
3807 cx: &mut App,
3808 ) -> (LayoutId, Self::RequestLayoutState) {
3809 self.element.request_layout(id, inspector_id, window, cx)
3810 }
3811
3812 fn prepaint(
3813 &mut self,
3814 id: Option<&GlobalElementId>,
3815 inspector_id: Option<&InspectorElementId>,
3816 bounds: Bounds<Pixels>,
3817 state: &mut Self::RequestLayoutState,
3818 window: &mut Window,
3819 cx: &mut App,
3820 ) -> E::PrepaintState {
3821 self.element
3822 .prepaint(id, inspector_id, bounds, state, window, cx)
3823 }
3824
3825 fn paint(
3826 &mut self,
3827 id: Option<&GlobalElementId>,
3828 inspector_id: Option<&InspectorElementId>,
3829 bounds: Bounds<Pixels>,
3830 request_layout: &mut Self::RequestLayoutState,
3831 prepaint: &mut Self::PrepaintState,
3832 window: &mut Window,
3833 cx: &mut App,
3834 ) {
3835 self.element.paint(
3836 id,
3837 inspector_id,
3838 bounds,
3839 request_layout,
3840 prepaint,
3841 window,
3842 cx,
3843 );
3844 }
3845}
3846
3847impl<E> IntoElement for Stateful<E>
3848where
3849 E: Element,
3850{
3851 type Element = Self;
3852
3853 fn into_element(self) -> Self::Element {
3854 self
3855 }
3856}
3857
3858impl<E> ParentElement for Stateful<E>
3859where
3860 E: ParentElement,
3861{
3862 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
3863 self.element.extend(elements)
3864 }
3865}
3866
3867/// Represents an element that can be scrolled *to* in its parent element.
3868/// Contrary to [ScrollHandle::scroll_to_active_item], an anchored element does not have to be an immediate child of the parent.
3869#[derive(Clone)]
3870pub struct ScrollAnchor {
3871 handle: ScrollHandle,
3872 last_origin: Rc<RefCell<Point<Pixels>>>,
3873}
3874
3875impl ScrollAnchor {
3876 /// Creates a [ScrollAnchor] associated with a given [ScrollHandle].
3877 pub fn for_handle(handle: ScrollHandle) -> Self {
3878 Self {
3879 handle,
3880 last_origin: Default::default(),
3881 }
3882 }
3883 /// Request scroll to this item on the next frame.
3884 pub fn scroll_to(&self, window: &mut Window, _cx: &mut App) {
3885 let this = self.clone();
3886
3887 window.on_next_frame(move |_, _| {
3888 let viewport_bounds = this.handle.bounds();
3889 let self_bounds = *this.last_origin.borrow();
3890 this.handle.set_offset(viewport_bounds.origin - self_bounds);
3891 });
3892 }
3893}
3894
3895#[derive(Default, Debug)]
3896struct ScrollHandleState {
3897 offset: Rc<RefCell<Point<Pixels>>>,
3898 bounds: Bounds<Pixels>,
3899 max_offset: Point<Pixels>,
3900 child_bounds: Vec<Bounds<Pixels>>,
3901 scroll_to_bottom: bool,
3902 overflow: Point<Overflow>,
3903 active_item: Option<ScrollActiveItem>,
3904}
3905
3906#[derive(Default, Debug, Clone, Copy)]
3907struct ScrollActiveItem {
3908 index: usize,
3909 strategy: ScrollStrategy,
3910}
3911
3912#[derive(Default, Debug, Clone, Copy)]
3913enum ScrollStrategy {
3914 #[default]
3915 FirstVisible,
3916 Top,
3917}
3918
3919/// A handle to the scrollable aspects of an element.
3920/// Used for accessing scroll state, like the current scroll offset,
3921/// and for mutating the scroll state, like scrolling to a specific child.
3922#[derive(Clone, Debug)]
3923pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
3924
3925impl Default for ScrollHandle {
3926 fn default() -> Self {
3927 Self::new()
3928 }
3929}
3930
3931impl ScrollHandle {
3932 /// Construct a new scroll handle.
3933 pub fn new() -> Self {
3934 Self(Rc::default())
3935 }
3936
3937 /// Get the current scroll offset.
3938 pub fn offset(&self) -> Point<Pixels> {
3939 *self.0.borrow().offset.borrow()
3940 }
3941
3942 /// Get the maximum scroll offset.
3943 pub fn max_offset(&self) -> Point<Pixels> {
3944 self.0.borrow().max_offset
3945 }
3946
3947 /// Get the top child that's scrolled into view.
3948 pub fn top_item(&self) -> usize {
3949 let state = self.0.borrow();
3950 let top = state.bounds.top() - state.offset.borrow().y;
3951
3952 match state.child_bounds.binary_search_by(|bounds| {
3953 if top < bounds.top() {
3954 Ordering::Greater
3955 } else if top > bounds.bottom() {
3956 Ordering::Less
3957 } else {
3958 Ordering::Equal
3959 }
3960 }) {
3961 Ok(ix) => ix,
3962 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
3963 }
3964 }
3965
3966 /// Get the bottom child that's scrolled into view.
3967 pub fn bottom_item(&self) -> usize {
3968 let state = self.0.borrow();
3969 let bottom = state.bounds.bottom() - state.offset.borrow().y;
3970
3971 match state.child_bounds.binary_search_by(|bounds| {
3972 if bottom < bounds.top() {
3973 Ordering::Greater
3974 } else if bottom > bounds.bottom() {
3975 Ordering::Less
3976 } else {
3977 Ordering::Equal
3978 }
3979 }) {
3980 Ok(ix) => ix,
3981 Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
3982 }
3983 }
3984
3985 /// Return the bounds into which this child is painted
3986 pub fn bounds(&self) -> Bounds<Pixels> {
3987 self.0.borrow().bounds
3988 }
3989
3990 /// Get the bounds for a specific child.
3991 pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
3992 self.0.borrow().child_bounds.get(ix).cloned()
3993 }
3994
3995 /// Update [ScrollHandleState]'s active item for scrolling to in prepaint
3996 pub fn scroll_to_item(&self, ix: usize) {
3997 let mut state = self.0.borrow_mut();
3998 state.active_item = Some(ScrollActiveItem {
3999 index: ix,
4000 strategy: ScrollStrategy::default(),
4001 });
4002 }
4003
4004 /// Update [ScrollHandleState]'s active item for scrolling to in prepaint
4005 /// This scrolls the minimal amount to ensure that the child is the first visible element
4006 pub fn scroll_to_top_of_item(&self, ix: usize) {
4007 let mut state = self.0.borrow_mut();
4008 state.active_item = Some(ScrollActiveItem {
4009 index: ix,
4010 strategy: ScrollStrategy::Top,
4011 });
4012 }
4013
4014 /// Scrolls the minimal amount to either ensure that the child is
4015 /// fully visible or the top element of the view depends on the
4016 /// scroll strategy
4017 fn scroll_to_active_item(&self) {
4018 let mut state = self.0.borrow_mut();
4019
4020 let Some(active_item) = state.active_item else {
4021 return;
4022 };
4023
4024 let active_item = match state.child_bounds.get(active_item.index) {
4025 Some(bounds) => {
4026 let mut scroll_offset = state.offset.borrow_mut();
4027
4028 match active_item.strategy {
4029 ScrollStrategy::FirstVisible => {
4030 if state.overflow.y == Overflow::Scroll {
4031 let child_height = bounds.size.height;
4032 let viewport_height = state.bounds.size.height;
4033 if child_height > viewport_height {
4034 scroll_offset.y = state.bounds.top() - bounds.top();
4035 } else if bounds.top() + scroll_offset.y < state.bounds.top() {
4036 scroll_offset.y = state.bounds.top() - bounds.top();
4037 } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
4038 scroll_offset.y = state.bounds.bottom() - bounds.bottom();
4039 }
4040 }
4041 }
4042 ScrollStrategy::Top => {
4043 scroll_offset.y = state.bounds.top() - bounds.top();
4044 }
4045 }
4046
4047 if state.overflow.x == Overflow::Scroll {
4048 let child_width = bounds.size.width;
4049 let viewport_width = state.bounds.size.width;
4050 if child_width > viewport_width {
4051 scroll_offset.x = state.bounds.left() - bounds.left();
4052 } else if bounds.left() + scroll_offset.x < state.bounds.left() {
4053 scroll_offset.x = state.bounds.left() - bounds.left();
4054 } else if bounds.right() + scroll_offset.x > state.bounds.right() {
4055 scroll_offset.x = state.bounds.right() - bounds.right();
4056 }
4057 }
4058 None
4059 }
4060 None => Some(active_item),
4061 };
4062 state.active_item = active_item;
4063 }
4064
4065 /// Scrolls to the bottom.
4066 pub fn scroll_to_bottom(&self) {
4067 let mut state = self.0.borrow_mut();
4068 state.scroll_to_bottom = true;
4069 }
4070
4071 /// Set the offset explicitly. The offset is the distance from the top left of the
4072 /// parent container to the top left of the first child.
4073 /// As you scroll further down the offset becomes more negative.
4074 pub fn set_offset(&self, mut position: Point<Pixels>) {
4075 let state = self.0.borrow();
4076 *state.offset.borrow_mut() = position;
4077 }
4078
4079 /// Get the logical scroll top, based on a child index and a pixel offset.
4080 pub fn logical_scroll_top(&self) -> (usize, Pixels) {
4081 let ix = self.top_item();
4082 let state = self.0.borrow();
4083
4084 if let Some(child_bounds) = state.child_bounds.get(ix) {
4085 (
4086 ix,
4087 child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
4088 )
4089 } else {
4090 (ix, px(0.))
4091 }
4092 }
4093
4094 /// Get the logical scroll bottom, based on a child index and a pixel offset.
4095 pub fn logical_scroll_bottom(&self) -> (usize, Pixels) {
4096 let ix = self.bottom_item();
4097 let state = self.0.borrow();
4098
4099 if let Some(child_bounds) = state.child_bounds.get(ix) {
4100 (
4101 ix,
4102 child_bounds.bottom() + state.offset.borrow().y - state.bounds.bottom(),
4103 )
4104 } else {
4105 (ix, px(0.))
4106 }
4107 }
4108
4109 /// Get the count of children for scrollable item.
4110 pub fn children_count(&self) -> usize {
4111 self.0.borrow().child_bounds.len()
4112 }
4113}
4114
4115#[cfg(test)]
4116mod tests {
4117 use super::*;
4118 use crate::{
4119 AnyWindowHandle, AppContext as _, Context, InputEvent, Keystroke, MouseMoveEvent,
4120 TestAppContext, canvas, util::FluentBuilder as _,
4121 };
4122 use std::{cell::Cell, rc::Weak};
4123
4124 struct GroupHoverTestView {
4125 render_count: Rc<Cell<usize>>,
4126 anonymous_paint_count: Rc<Cell<usize>>,
4127 stateful_width: Rc<Cell<Pixels>>,
4128 }
4129
4130 impl Render for GroupHoverTestView {
4131 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4132 self.render_count.set(self.render_count.get() + 1);
4133 let anonymous_paint_count = self.anonymous_paint_count.clone();
4134 let stateful_width = self.stateful_width.clone();
4135 div().size_full().child(
4136 div()
4137 .ml(px(20.))
4138 .mt(px(20.))
4139 .size(px(50.))
4140 .relative()
4141 .group("hover-group")
4142 .child(
4143 div()
4144 .absolute()
4145 .size_full()
4146 .invisible()
4147 .group_hover("hover-group", |style| style.visible())
4148 .child(canvas(
4149 |_, _, _| {},
4150 move |_, _, _, _| {
4151 anonymous_paint_count.set(anonymous_paint_count.get() + 1)
4152 },
4153 )),
4154 )
4155 .child(
4156 div()
4157 .id("stateful-group-hover-target")
4158 .absolute()
4159 .top_0()
4160 .left_0()
4161 .size(px(10.))
4162 .group_hover("hover-group", |style| style.size(px(20.)))
4163 .child(canvas(
4164 move |bounds, _, _| stateful_width.set(bounds.size.width),
4165 |_, _, _, _| {},
4166 )),
4167 ),
4168 )
4169 }
4170 }
4171
4172 #[gpui::test]
4173 fn group_hover_styles_update_only_on_transitions(cx: &mut TestAppContext) {
4174 let render_count = Rc::new(Cell::new(0));
4175 let anonymous_paint_count = Rc::new(Cell::new(0));
4176 let stateful_width = Rc::new(Cell::new(px(0.)));
4177 let window = cx.add_window({
4178 let render_count = render_count.clone();
4179 let anonymous_paint_count = anonymous_paint_count.clone();
4180 let stateful_width = stateful_width.clone();
4181 move |_, _| GroupHoverTestView {
4182 render_count,
4183 anonymous_paint_count,
4184 stateful_width,
4185 }
4186 });
4187 let window = AnyWindowHandle::from(window);
4188
4189 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
4190 .unwrap();
4191 assert_eq!(anonymous_paint_count.get(), 0);
4192 assert_eq!(stateful_width.get(), px(10.));
4193
4194 let move_mouse = |cx: &mut TestAppContext, position| {
4195 cx.update_window(window, |_, window, cx| {
4196 window.simulate_mouse_move(position, cx)
4197 })
4198 .unwrap();
4199 };
4200
4201 let initial_render_count = render_count.get();
4202 move_mouse(cx, point(px(25.), px(25.)));
4203 assert_eq!(render_count.get(), initial_render_count + 1);
4204 assert_eq!(anonymous_paint_count.get(), 1);
4205 assert_eq!(stateful_width.get(), px(20.));
4206
4207 move_mouse(cx, point(px(30.), px(30.)));
4208 assert_eq!(render_count.get(), initial_render_count + 1);
4209 assert_eq!(anonymous_paint_count.get(), 1);
4210 assert_eq!(stateful_width.get(), px(20.));
4211
4212 move_mouse(cx, point(px(5.), px(5.)));
4213 assert_eq!(render_count.get(), initial_render_count + 2);
4214 assert_eq!(anonymous_paint_count.get(), 1);
4215 assert_eq!(stateful_width.get(), px(10.));
4216
4217 move_mouse(cx, point(px(10.), px(10.)));
4218 assert_eq!(render_count.get(), initial_render_count + 2);
4219 assert_eq!(anonymous_paint_count.get(), 1);
4220 assert_eq!(stateful_width.get(), px(10.));
4221 }
4222
4223 struct TestTooltipView;
4224
4225 impl Render for TestTooltipView {
4226 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4227 div().w(px(20.)).h(px(20.)).child("tooltip")
4228 }
4229 }
4230
4231 type CapturedActiveTooltip = Rc<RefCell<Option<Weak<RefCell<Option<ActiveTooltip>>>>>>;
4232
4233 struct TooltipCaptureElement {
4234 child: AnyElement,
4235 captured_active_tooltip: CapturedActiveTooltip,
4236 }
4237
4238 impl IntoElement for TooltipCaptureElement {
4239 type Element = Self;
4240
4241 fn into_element(self) -> Self::Element {
4242 self
4243 }
4244 }
4245
4246 impl Element for TooltipCaptureElement {
4247 type RequestLayoutState = ();
4248 type PrepaintState = ();
4249
4250 fn id(&self) -> Option<ElementId> {
4251 None
4252 }
4253
4254 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
4255 None
4256 }
4257
4258 fn request_layout(
4259 &mut self,
4260 _id: Option<&GlobalElementId>,
4261 _inspector_id: Option<&InspectorElementId>,
4262 window: &mut Window,
4263 cx: &mut App,
4264 ) -> (LayoutId, Self::RequestLayoutState) {
4265 (self.child.request_layout(window, cx), ())
4266 }
4267
4268 fn prepaint(
4269 &mut self,
4270 _id: Option<&GlobalElementId>,
4271 _inspector_id: Option<&InspectorElementId>,
4272 _bounds: Bounds<Pixels>,
4273 _request_layout: &mut Self::RequestLayoutState,
4274 window: &mut Window,
4275 cx: &mut App,
4276 ) -> Self::PrepaintState {
4277 self.child.prepaint(window, cx);
4278 }
4279
4280 fn paint(
4281 &mut self,
4282 _id: Option<&GlobalElementId>,
4283 _inspector_id: Option<&InspectorElementId>,
4284 _bounds: Bounds<Pixels>,
4285 _request_layout: &mut Self::RequestLayoutState,
4286 _prepaint: &mut Self::PrepaintState,
4287 window: &mut Window,
4288 cx: &mut App,
4289 ) {
4290 self.child.paint(window, cx);
4291 window.with_global_id("target".into(), |global_id, window| {
4292 window.with_element_state::<InteractiveElementState, _>(
4293 global_id,
4294 |state, _window| {
4295 let state = state.unwrap();
4296 *self.captured_active_tooltip.borrow_mut() =
4297 state.active_tooltip.as_ref().map(Rc::downgrade);
4298 ((), state)
4299 },
4300 )
4301 });
4302 }
4303 }
4304
4305 struct TooltipOwner {
4306 captured_active_tooltip: CapturedActiveTooltip,
4307 show_delay_override: Option<Duration>,
4308 }
4309
4310 impl Render for TooltipOwner {
4311 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4312 TooltipCaptureElement {
4313 child: div()
4314 .size_full()
4315 .child(
4316 div()
4317 .id("target")
4318 .w(px(50.))
4319 .h(px(50.))
4320 .tooltip(|_, cx| cx.new(|_| TestTooltipView).into())
4321 .when_some(self.show_delay_override, |this, delay| {
4322 this.tooltip_show_delay(delay)
4323 }),
4324 )
4325 .into_any_element(),
4326 captured_active_tooltip: self.captured_active_tooltip.clone(),
4327 }
4328 }
4329 }
4330
4331 #[test]
4332 fn scroll_handle_aligns_wide_children_to_left_edge() {
4333 let handle = ScrollHandle::new();
4334 {
4335 let mut state = handle.0.borrow_mut();
4336 state.bounds = Bounds::new(point(px(0.), px(0.)), size(px(80.), px(20.)));
4337 state.child_bounds = vec![Bounds::new(point(px(25.), px(0.)), size(px(200.), px(20.)))];
4338 state.overflow.x = Overflow::Scroll;
4339 state.active_item = Some(ScrollActiveItem {
4340 index: 0,
4341 strategy: ScrollStrategy::default(),
4342 });
4343 }
4344
4345 handle.scroll_to_active_item();
4346
4347 assert_eq!(handle.offset().x, px(-25.));
4348 }
4349
4350 #[test]
4351 fn scroll_handle_aligns_tall_children_to_top_edge() {
4352 let handle = ScrollHandle::new();
4353 {
4354 let mut state = handle.0.borrow_mut();
4355 state.bounds = Bounds::new(point(px(0.), px(0.)), size(px(20.), px(80.)));
4356 state.child_bounds = vec![Bounds::new(point(px(0.), px(25.)), size(px(20.), px(200.)))];
4357 state.overflow.y = Overflow::Scroll;
4358 state.active_item = Some(ScrollActiveItem {
4359 index: 0,
4360 strategy: ScrollStrategy::default(),
4361 });
4362 }
4363
4364 handle.scroll_to_active_item();
4365
4366 assert_eq!(handle.offset().y, px(-25.));
4367 }
4368
4369 fn setup_tooltip_owner_test(
4370 show_delay_override: Option<Duration>,
4371 ) -> (
4372 TestAppContext,
4373 crate::AnyWindowHandle,
4374 CapturedActiveTooltip,
4375 ) {
4376 let mut test_app = TestAppContext::single();
4377 let captured_active_tooltip: CapturedActiveTooltip = Rc::new(RefCell::new(None));
4378 let window = test_app.add_window({
4379 let captured_active_tooltip = captured_active_tooltip.clone();
4380 move |_, _| TooltipOwner {
4381 captured_active_tooltip,
4382 show_delay_override,
4383 }
4384 });
4385 let any_window = window.into();
4386
4387 test_app
4388 .update_window(any_window, |_, window, cx| {
4389 window.draw(cx).clear(cx);
4390 })
4391 .unwrap();
4392
4393 test_app
4394 .update_window(any_window, |_, window, cx| {
4395 window.dispatch_event(
4396 MouseMoveEvent {
4397 position: point(px(10.), px(10.)),
4398 modifiers: Default::default(),
4399 pressed_button: None,
4400 }
4401 .to_platform_input(),
4402 cx,
4403 );
4404 })
4405 .unwrap();
4406
4407 test_app
4408 .update_window(any_window, |_, window, cx| {
4409 window.draw(cx).clear(cx);
4410 })
4411 .unwrap();
4412
4413 (test_app, any_window, captured_active_tooltip)
4414 }
4415
4416 #[test]
4417 fn tooltip_waiting_for_show_is_released_when_its_owner_disappears() {
4418 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
4419
4420 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4421 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4422 assert!(matches!(
4423 active_tooltip.borrow().as_ref(),
4424 Some(ActiveTooltip::WaitingForShow { .. })
4425 ));
4426
4427 test_app
4428 .update_window(any_window, |_, window, _| {
4429 window.remove_window();
4430 })
4431 .unwrap();
4432 test_app.run_until_parked();
4433 drop(active_tooltip);
4434
4435 assert!(weak_active_tooltip.upgrade().is_none());
4436 }
4437
4438 #[test]
4439 fn tooltip_respects_custom_show_delay() {
4440 let extra_delay = Duration::from_secs(1);
4441 let show_delay_override = DEFAULT_TOOLTIP_SHOW_DELAY + extra_delay;
4442 let (mut test_app, _any_window, captured_active_tooltip) =
4443 setup_tooltip_owner_test(Some(show_delay_override));
4444
4445 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4446 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4447
4448 test_app
4449 .dispatcher
4450 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
4451 test_app.run_until_parked();
4452
4453 assert!(matches!(
4454 active_tooltip.borrow().as_ref(),
4455 Some(ActiveTooltip::WaitingForShow { .. })
4456 ));
4457
4458 test_app.dispatcher.advance_clock(extra_delay);
4459 test_app.run_until_parked();
4460
4461 assert!(matches!(
4462 active_tooltip.borrow().as_ref(),
4463 Some(ActiveTooltip::Visible { .. })
4464 ));
4465 }
4466
4467 #[test]
4468 fn tooltip_is_released_when_its_owner_disappears() {
4469 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
4470
4471 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4472 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4473
4474 test_app
4475 .dispatcher
4476 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
4477 test_app.run_until_parked();
4478
4479 assert!(matches!(
4480 active_tooltip.borrow().as_ref(),
4481 Some(ActiveTooltip::Visible { .. })
4482 ));
4483
4484 test_app
4485 .update_window(any_window, |_, window, _| {
4486 window.remove_window();
4487 })
4488 .unwrap();
4489 test_app.run_until_parked();
4490 drop(active_tooltip);
4491
4492 assert!(weak_active_tooltip.upgrade().is_none());
4493 }
4494
4495 #[test]
4496 fn tooltip_hides_after_mouse_leaves_origin() {
4497 let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test(None);
4498
4499 let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
4500 let active_tooltip = weak_active_tooltip.upgrade().unwrap();
4501
4502 test_app
4503 .dispatcher
4504 .advance_clock(DEFAULT_TOOLTIP_SHOW_DELAY);
4505 test_app.run_until_parked();
4506
4507 assert!(matches!(
4508 active_tooltip.borrow().as_ref(),
4509 Some(ActiveTooltip::Visible { .. })
4510 ));
4511
4512 test_app
4513 .update_window(any_window, |_, window, cx| {
4514 window.dispatch_event(
4515 MouseMoveEvent {
4516 position: point(px(75.), px(75.)),
4517 modifiers: Default::default(),
4518 pressed_button: None,
4519 }
4520 .to_platform_input(),
4521 cx,
4522 );
4523 })
4524 .unwrap();
4525
4526 assert!(active_tooltip.borrow().is_none());
4527 }
4528
4529 struct MouseDownOutOwner {
4530 mouse_down_out_count: Rc<RefCell<usize>>,
4531 }
4532
4533 impl Render for MouseDownOutOwner {
4534 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4535 let mouse_down_out_count = self.mouse_down_out_count.clone();
4536 div()
4537 .size_full()
4538 .child(div().id("target").w(px(50.)).h(px(50.)).on_mouse_down_out(
4539 move |_, _, _| {
4540 *mouse_down_out_count.borrow_mut() += 1;
4541 },
4542 ))
4543 }
4544 }
4545
4546 #[test]
4547 fn mouse_down_out_is_suppressed_while_window_prompt_is_active() {
4548 let mut test_app = TestAppContext::single();
4549 let mouse_down_out_count = Rc::new(RefCell::new(0));
4550 let window = test_app.add_window({
4551 let mouse_down_out_count = mouse_down_out_count.clone();
4552 move |_, _| MouseDownOutOwner {
4553 mouse_down_out_count,
4554 }
4555 });
4556 let any_window: AnyWindowHandle = window.into();
4557
4558 fn dispatch_mouse_down_outside_target(
4559 test_app: &mut TestAppContext,
4560 any_window: AnyWindowHandle,
4561 ) {
4562 test_app
4563 .update_window(any_window, |_, window, cx| {
4564 window.dispatch_event(
4565 MouseDownEvent {
4566 position: point(px(75.), px(75.)),
4567 button: MouseButton::Left,
4568 modifiers: Default::default(),
4569 click_count: 1,
4570 first_mouse: false,
4571 }
4572 .to_platform_input(),
4573 cx,
4574 );
4575 })
4576 .unwrap();
4577 }
4578
4579 test_app
4580 .update_window(any_window, |_, window, cx| {
4581 window.draw(cx).clear(cx);
4582 })
4583 .unwrap();
4584
4585 dispatch_mouse_down_outside_target(&mut test_app, any_window);
4586 assert_eq!(
4587 *mouse_down_out_count.borrow(),
4588 1,
4589 "mouse down outside the element should fire mouse-down-out listeners"
4590 );
4591
4592 test_app
4593 .update_window(any_window, |_, window, cx| {
4594 cx.set_prompt_builder(crate::fallback_prompt_renderer);
4595 let _receiver =
4596 window.prompt(crate::PromptLevel::Warning, "message", None, &["Ok"], cx);
4597 assert!(window.has_active_prompt());
4598 window.draw(cx).clear(cx);
4599 })
4600 .unwrap();
4601
4602 dispatch_mouse_down_outside_target(&mut test_app, any_window);
4603 assert_eq!(
4604 *mouse_down_out_count.borrow(),
4605 1,
4606 "mouse down over an active prompt should not fire mouse-down-out listeners"
4607 );
4608 }
4609
4610 #[test]
4611 fn test_write_a11y_info_string_and_numeric_properties() {
4612 let mut interactivity = Interactivity::default();
4613 interactivity.aria.label = Some("Buffer Font Size".into());
4614 interactivity.aria.value = Some("15".into());
4615 interactivity.aria.placeholder = Some("Search".into());
4616 interactivity.aria.numeric_value = Some(15.0);
4617 interactivity.aria.min_numeric_value = Some(6.0);
4618 interactivity.aria.max_numeric_value = Some(72.0);
4619 interactivity.aria.numeric_value_step = Some(1.0);
4620
4621 let mut node = accesskit::Node::new(accesskit::Role::SpinButton);
4622 interactivity.write_a11y_info(&mut node);
4623
4624 assert_eq!(node.label(), Some("Buffer Font Size"));
4625 assert_eq!(node.value(), Some("15"));
4626 assert_eq!(node.placeholder(), Some("Search"));
4627 assert_eq!(node.numeric_value(), Some(15.0));
4628 assert_eq!(node.min_numeric_value(), Some(6.0));
4629 assert_eq!(node.max_numeric_value(), Some(72.0));
4630 assert_eq!(node.numeric_value_step(), Some(1.0));
4631 }
4632
4633 /// Two focusable, clickable elements ("a" and "b") used to exercise the
4634 /// Enter/Space -> synthesized click press/release pairing.
4635 struct KeyboardActivationTest {
4636 focus_a: FocusHandle,
4637 focus_b: FocusHandle,
4638 clicks: Rc<RefCell<Vec<&'static str>>>,
4639 }
4640
4641 impl Render for KeyboardActivationTest {
4642 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
4643 let clicks_a = self.clicks.clone();
4644 let clicks_b = self.clicks.clone();
4645 div()
4646 .size_full()
4647 .child(
4648 div()
4649 .id("a")
4650 .w(px(50.))
4651 .h(px(50.))
4652 .track_focus(&self.focus_a)
4653 .on_click(move |_, _, _| clicks_a.borrow_mut().push("a")),
4654 )
4655 .child(
4656 div()
4657 .id("b")
4658 .w(px(50.))
4659 .h(px(50.))
4660 .track_focus(&self.focus_b)
4661 .on_click(move |_, _, _| clicks_b.borrow_mut().push("b")),
4662 )
4663 }
4664 }
4665
4666 fn setup_keyboard_activation_test() -> (
4667 TestAppContext,
4668 AnyWindowHandle,
4669 Rc<RefCell<Vec<&'static str>>>,
4670 FocusHandle,
4671 FocusHandle,
4672 ) {
4673 let mut cx = TestAppContext::single();
4674 let (focus_a, focus_b) = cx.update(|cx| (cx.focus_handle(), cx.focus_handle()));
4675 let clicks: Rc<RefCell<Vec<&'static str>>> = Rc::new(RefCell::new(Vec::new()));
4676 let window = cx.add_window({
4677 let focus_a = focus_a.clone();
4678 let focus_b = focus_b.clone();
4679 let clicks = clicks.clone();
4680 move |_, _| KeyboardActivationTest {
4681 focus_a,
4682 focus_b,
4683 clicks,
4684 }
4685 });
4686 (cx, window.into(), clicks, focus_a, focus_b)
4687 }
4688
4689 /// Move focus to `handle`, flush effects, then paint so the newly focused
4690 /// element registers its key handlers for the next dispatched event.
4691 fn focus_and_draw(cx: &mut TestAppContext, window: AnyWindowHandle, handle: &FocusHandle) {
4692 cx.update_window(window, |_, window, cx| window.focus(handle, cx))
4693 .unwrap();
4694 cx.run_until_parked();
4695 cx.update_window(window, |_, window, cx| {
4696 window.draw(cx).clear(cx);
4697 })
4698 .unwrap();
4699 }
4700
4701 fn key_down(cx: &mut TestAppContext, window: AnyWindowHandle, key: &str) {
4702 let keystroke = Keystroke::parse(key).unwrap();
4703 cx.update_window(window, |_, window, cx| {
4704 window.dispatch_event(
4705 KeyDownEvent {
4706 keystroke,
4707 is_held: false,
4708 prefer_character_input: false,
4709 }
4710 .to_platform_input(),
4711 cx,
4712 );
4713 })
4714 .unwrap();
4715 }
4716
4717 fn key_up(cx: &mut TestAppContext, window: AnyWindowHandle, key: &str) {
4718 let keystroke = Keystroke::parse(key).unwrap();
4719 cx.update_window(window, |_, window, cx| {
4720 window.dispatch_event(KeyUpEvent { keystroke }.to_platform_input(), cx);
4721 })
4722 .unwrap();
4723 }
4724
4725 /// Pressing and releasing Enter on the same focused element fires a click.
4726 #[test]
4727 fn keyboard_activation_fires_click_on_same_element() {
4728 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
4729
4730 focus_and_draw(&mut cx, window, &focus_a);
4731 key_down(&mut cx, window, "enter");
4732 key_up(&mut cx, window, "enter");
4733
4734 assert_eq!(*clicks.borrow(), vec!["a"]);
4735 }
4736
4737 /// A key-down whose key-up lands on a *different* element (because focus
4738 /// moved in between) must not leak a synthesized click onto the newly
4739 /// focused element. This is the core regression: previously the key-up
4740 /// handler fired unconditionally on whatever was focused at key-up time.
4741 #[test]
4742 fn keyboard_activation_does_not_leak_across_focus_change() {
4743 let (mut cx, window, clicks, focus_a, focus_b) = setup_keyboard_activation_test();
4744
4745 // Enter pressed while "a" is focused...
4746 focus_and_draw(&mut cx, window, &focus_a);
4747 key_down(&mut cx, window, "enter");
4748
4749 // ...focus moves to "b" before the release (as a confirm action would)...
4750 focus_and_draw(&mut cx, window, &focus_b);
4751 key_up(&mut cx, window, "enter");
4752
4753 // ...so neither element is clicked: "a" never saw the up, and "b"
4754 // never saw the down.
4755 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
4756 }
4757
4758 /// A keydown whose flag is left pending because focus moved away before
4759 /// the keyup must not fire a click when focus later *returns* to the same
4760 /// element (the menu trigger reopening case). The stamped focus generation
4761 /// no longer matches, so the stale pending state is ignored.
4762 #[test]
4763 fn keyboard_activation_does_not_leak_when_focus_returns() {
4764 let (mut cx, window, clicks, focus_a, focus_b) = setup_keyboard_activation_test();
4765
4766 // Enter pressed on "a"...
4767 focus_and_draw(&mut cx, window, &focus_a);
4768 key_down(&mut cx, window, "enter");
4769
4770 // ...focus leaves "a" before its keyup (so the pending state is never
4771 // consumed), then comes back to "a"...
4772 focus_and_draw(&mut cx, window, &focus_b);
4773 focus_and_draw(&mut cx, window, &focus_a);
4774 key_up(&mut cx, window, "enter");
4775
4776 // ...and the now-stale pending keydown must not fire a click.
4777 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
4778 }
4779
4780 /// A non-activation key *released* during the press must cancel the pending
4781 /// activation. For the sequence escape-down, space-down, escape-up,
4782 /// space-up the space forms a clean down/up pair, but the intervening
4783 /// escape-up means this isn't a plain space activation, so no click fires.
4784 #[test]
4785 fn keyboard_activation_cleared_by_intervening_key_release() {
4786 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
4787
4788 focus_and_draw(&mut cx, window, &focus_a);
4789 key_down(&mut cx, window, "escape");
4790 key_down(&mut cx, window, "space");
4791 key_up(&mut cx, window, "escape");
4792 key_up(&mut cx, window, "space");
4793
4794 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
4795 }
4796
4797 /// The flag is a single activation marker, not keyed by which activation
4798 /// key was used, so a Space down paired with an Enter up on the same
4799 /// element still fires a click.
4800 #[test]
4801 fn keyboard_activation_does_not_distinguish_space_and_enter() {
4802 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
4803
4804 focus_and_draw(&mut cx, window, &focus_a);
4805 key_down(&mut cx, window, "space");
4806 key_up(&mut cx, window, "enter");
4807
4808 assert_eq!(*clicks.borrow(), vec!["a"]);
4809 }
4810
4811 /// A non-activation key pressed between the activation down and up clears
4812 /// the pending flag, suppressing the click.
4813 #[test]
4814 fn keyboard_activation_cleared_by_intervening_keydown() {
4815 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
4816
4817 focus_and_draw(&mut cx, window, &focus_a);
4818 key_down(&mut cx, window, "enter");
4819 key_down(&mut cx, window, "a");
4820 key_up(&mut cx, window, "enter");
4821
4822 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
4823 }
4824
4825 /// A modified Enter (e.g. cmd-enter) is not treated as an activation key,
4826 /// so it neither sets the pending flag nor fires a click on release.
4827 #[test]
4828 fn keyboard_activation_ignores_modified_keys() {
4829 let (mut cx, window, clicks, focus_a, _focus_b) = setup_keyboard_activation_test();
4830
4831 focus_and_draw(&mut cx, window, &focus_a);
4832 key_down(&mut cx, window, "cmd-enter");
4833 key_up(&mut cx, window, "cmd-enter");
4834
4835 assert!(clicks.borrow().is_empty(), "clicks: {:?}", clicks.borrow());
4836 }
4837
4838 /// Two sibling tab groups, each a focusable container that is *not* itself a
4839 /// tab stop and holds a single tab stop. Mirrors how the title bar and
4840 /// status bar expose their controls as ARIA toolbars.
4841 struct TabGroupFocus {
4842 group_a: FocusHandle,
4843 item_a: FocusHandle,
4844 group_b: FocusHandle,
4845 item_b: FocusHandle,
4846 }
4847
4848 impl Render for TabGroupFocus {
4849 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
4850 fn group(container: &FocusHandle, item: &FocusHandle) -> Div {
4851 div()
4852 .track_focus(container)
4853 .tab_group()
4854 .child(div().track_focus(item))
4855 }
4856 div()
4857 .child(group(&self.group_a, &self.item_a))
4858 .child(group(&self.group_b, &self.item_b))
4859 }
4860 }
4861
4862 /// Focusing a tab-group container and pressing Tab (`focus_next`) must move
4863 /// focus to the first tab stop *inside that container*, as documented on
4864 /// [`InteractiveElement::tab_stop`].
4865 #[test]
4866 fn focus_next_from_tab_group_container_enters_that_group() {
4867 let mut cx = TestAppContext::single();
4868 let (group_a, item_a, group_b, item_b) = cx.update(|cx| {
4869 (
4870 cx.focus_handle(),
4871 cx.focus_handle().tab_stop(true),
4872 cx.focus_handle(),
4873 cx.focus_handle().tab_stop(true),
4874 )
4875 });
4876 let window: AnyWindowHandle = cx
4877 .add_window({
4878 let (group_a, item_a, group_b, item_b) =
4879 (group_a, item_a, group_b.clone(), item_b.clone());
4880 move |_, _| TabGroupFocus {
4881 group_a,
4882 item_a,
4883 group_b,
4884 item_b,
4885 }
4886 })
4887 .into();
4888 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
4889 .unwrap();
4890
4891 // Focus the *second* group's container, then advance like Tab would.
4892 let focused = cx
4893 .update_window(window, |_, window, cx| {
4894 window.focus(&group_b, cx);
4895 window.focus_next(cx);
4896 window.focused(cx).map(|handle| handle.id)
4897 })
4898 .unwrap();
4899
4900 assert_eq!(focused, Some(item_b.id));
4901 }
4902}
4903