Skip to main content

rgpui/elements/
div.rs

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