Skip to main content

fret_ui/elements/
cx.rs

1use std::any::{Any, TypeId};
2use std::collections::HashMap;
3use std::hash::Hash;
4use std::panic::Location;
5use std::sync::Arc;
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::time::Duration;
8
9use smallvec::SmallVec;
10
11use fret_core::input::PointerType;
12use fret_core::window::{ColorScheme, ContrastPreference, ForcedColorsMode};
13use fret_core::{
14    AppWindowId, Color, Edges, EffectChain, EffectMode, EffectQuality, NodeId, Px, Rect,
15};
16use fret_runtime::{Effect, FrameId, Model, ModelId, ModelUpdateError, TimerToken};
17
18use crate::action::OnHoverChange;
19use crate::action::{
20    ActionRouteHooks, CommandActionHooks, CommandAvailabilityActionHooks, DismissibleActionHooks,
21    KeyActionHooks, OnActivate, OnCommand, OnCommandAvailability, OnDismissRequest,
22    OnDismissiblePointerMove, OnKeyDown, OnPinchGesture, OnPointerCancel, OnPointerDown,
23    OnPointerMove, OnPointerUp, OnPressablePointerDown, OnPressablePointerMove,
24    OnPressablePointerUp, OnRovingActiveChange, OnRovingNavigate, OnRovingTypeahead,
25    OnSelectableTextActivateSpan, OnTimer, OnWheel, PointerActionHooks, PressableActionHooks,
26    PressableHoverActionHooks, PressablePointerUpResult, RovingActionHooks,
27    SelectableTextActionHooks, TimerActionHooks,
28};
29use crate::canvas::{CanvasPaintHooks, CanvasPainter, OnCanvasPaint};
30use crate::element::{
31    AnyElement, BackdropSourceGroupProps, CanvasProps, ColumnProps, CompositeGroupProps,
32    ContainerProps, EffectLayerProps, ElementKind, FlexProps, FocusTraversalGateProps,
33    ForegroundScopeProps, GridProps, HitTestGateProps, HoverRegionProps, ImageProps,
34    InteractivityGateProps, LayoutQueryRegionProps, LayoutStyle, MaskLayerProps, OpacityProps,
35    PointerRegionProps, PressableProps, PressableState, ResizablePanelGroupProps, RowProps,
36    ScrollProps, ScrollbarProps, SelectableTextProps, SpacerProps, SpinnerProps, StackProps,
37    StyledTextProps, SvgIconProps, TextAreaProps, TextInputProps, TextProps, ViewportSurfaceProps,
38    VirtualListOptions, VirtualListProps, VirtualListState, VisualTransformProps,
39};
40use crate::overlay_placement::{AnchoredPanelLayoutTrace, Side};
41use crate::widget::Invalidation;
42use crate::{SvgSource, Theme, UiHost};
43use fret_core::window::WindowMetricsService;
44
45use super::hash::{callsite_hash, derive_child_id, stable_hash};
46use super::runtime::{EnvironmentQueryKey, LayoutQueryRegionMarker};
47use super::{ContinuousFrames, ElementRuntime, GlobalElementId, WindowElementState, global_root};
48
49/// Per-frame view construction context passed to declarative element constructors.
50///
51/// `ElementContext` exposes:
52/// - a mutable handle to the app/host (`cx.app`) implementing [`UiHost`],
53/// - the current window id and frame id,
54/// - helper constructors for common element kinds (containers, text, input, overlays, etc.),
55/// - access to theme and environment queries.
56///
57/// It is created and owned by [`UiTree`] and should be treated as an ephemeral, frame-scoped API.
58pub struct ElementContext<'a, H: UiHost> {
59    pub app: &'a mut H,
60    pub window: AppWindowId,
61    pub frame_id: FrameId,
62    pub bounds: Rect,
63    render_pass_id: u64,
64    window_state: &'a mut WindowElementState,
65    stack: Vec<GlobalElementId>,
66    callsite_counters: Vec<CallsiteCounters>,
67    view_cache_should_reuse: Option<&'a mut dyn FnMut(NodeId) -> bool>,
68}
69
70/// Explicit access contract for APIs that only need to land UI through an [`ElementContext`].
71///
72/// This keeps extracted helper/render-authoring surfaces free to accept a narrower context-access
73/// capability instead of hard-coding `&mut ElementContext<...>` or relying on implicit `Deref`
74/// bridges from facade types.
75pub trait ElementContextAccess<'a, H: UiHost> {
76    fn elements(&mut self) -> &mut ElementContext<'a, H>;
77}
78
79impl<'a, H: UiHost> ElementContextAccess<'a, H> for ElementContext<'a, H> {
80    fn elements(&mut self) -> &mut ElementContext<'a, H> {
81        self
82    }
83}
84
85type CallsiteCounters = SmallVec<[(u64, u32); 16]>;
86
87static NEXT_RENDER_PASS_ID: AtomicU64 = AtomicU64::new(1);
88
89fn next_render_pass_id() -> u64 {
90    NEXT_RENDER_PASS_ID.fetch_add(1, Ordering::Relaxed)
91}
92
93#[derive(Debug)]
94struct ProvidedState<T> {
95    value: Option<T>,
96}
97
98struct LocalModelSlot<T> {
99    model: Option<Model<T>>,
100}
101
102#[cfg(debug_assertions)]
103#[derive(Default)]
104struct RenderEvaluationCallsiteDiagnostics {
105    last_render_pass_id: u64,
106    calls_in_render_pass: u32,
107}
108
109#[cfg(debug_assertions)]
110fn note_call_in_render_evaluation(
111    diagnostics: &mut RenderEvaluationCallsiteDiagnostics,
112    render_pass_id: u64,
113) -> bool {
114    if diagnostics.last_render_pass_id != render_pass_id {
115        diagnostics.last_render_pass_id = render_pass_id;
116        diagnostics.calls_in_render_pass = 0;
117    }
118    diagnostics.calls_in_render_pass = diagnostics.calls_in_render_pass.saturating_add(1);
119    diagnostics.calls_in_render_pass == 2
120}
121
122impl<T> Default for LocalModelSlot<T> {
123    fn default() -> Self {
124        Self { model: None }
125    }
126}
127
128impl<T> Default for ProvidedState<T> {
129    fn default() -> Self {
130        Self { value: None }
131    }
132}
133
134fn bump_callsite_counter(counters: &mut CallsiteCounters, callsite: u64) -> u64 {
135    for (seen, next) in counters.iter_mut() {
136        if *seen != callsite {
137            continue;
138        }
139        let slot = (*next) as u64;
140        *next = next.saturating_add(1);
141        return slot;
142    }
143    counters.push((callsite, 1));
144    0
145}
146
147impl<'a, H: UiHost> ElementContext<'a, H> {
148    pub(crate) fn collect_children(
149        &mut self,
150        children: impl IntoIterator<Item = AnyElement>,
151    ) -> Vec<AnyElement> {
152        let iter = children.into_iter();
153        let (min, _) = iter.size_hint();
154        let mut out = self.window_state.take_scratch_element_children_vec(min);
155        out.extend(iter);
156        out
157    }
158
159    pub(crate) fn retained_virtual_list_row_any_element(
160        &mut self,
161        key: crate::ItemKey,
162        index: usize,
163        row: &crate::windowed_surface_host::RetainedVirtualListRowFn<H>,
164    ) -> AnyElement {
165        self.keyed(key, |cx| (row)(cx, index))
166    }
167
168    pub fn new(
169        app: &'a mut H,
170        runtime: &'a mut ElementRuntime,
171        window: AppWindowId,
172        bounds: Rect,
173        root: GlobalElementId,
174    ) -> Self {
175        let frame_id = app.frame_id();
176        runtime.prepare_window_for_frame(window, frame_id);
177
178        let window_state = runtime.for_window_mut(window);
179        window_state.mark_authoring_identity_seen(root);
180
181        Self {
182            app,
183            window,
184            frame_id,
185            bounds,
186            render_pass_id: next_render_pass_id(),
187            window_state,
188            stack: vec![root],
189            callsite_counters: vec![CallsiteCounters::new()],
190            view_cache_should_reuse: None,
191        }
192    }
193
194    pub(crate) fn set_view_cache_should_reuse(&mut self, f: &'a mut dyn FnMut(NodeId) -> bool) {
195        self.view_cache_should_reuse = Some(f);
196    }
197
198    pub fn new_for_root_name(
199        app: &'a mut H,
200        runtime: &'a mut ElementRuntime,
201        window: AppWindowId,
202        bounds: Rect,
203        root_name: &str,
204    ) -> Self {
205        let root = global_root(window, root_name);
206        #[cfg(feature = "diagnostics")]
207        {
208            let cx = Self::new(app, runtime, window, bounds, root);
209            cx.window_state
210                .record_debug_root(cx.frame_id, root, root_name);
211            cx
212        }
213        #[cfg(not(feature = "diagnostics"))]
214        {
215            Self::new(app, runtime, window, bounds, root)
216        }
217    }
218
219    pub(crate) fn new_for_existing_window_state(
220        app: &'a mut H,
221        window: AppWindowId,
222        bounds: Rect,
223        root: GlobalElementId,
224        window_state: &'a mut WindowElementState,
225    ) -> Self {
226        let frame_id = app.frame_id();
227        window_state.mark_authoring_identity_seen(root);
228        Self {
229            app,
230            window,
231            frame_id,
232            bounds,
233            render_pass_id: next_render_pass_id(),
234            window_state,
235            stack: vec![root],
236            callsite_counters: vec![CallsiteCounters::new()],
237            view_cache_should_reuse: None,
238        }
239    }
240
241    /// Framework-internal diagnostics hook for repeated same-callsite use within one render
242    /// evaluation.
243    ///
244    /// This is not a public authoring API; it exists so higher-level facade code can reuse the
245    /// kernel's identity/evaluation substrate instead of maintaining duplicate bookkeeping.
246    #[cfg(debug_assertions)]
247    #[doc(hidden)]
248    pub fn note_repeated_call_in_render_evaluation_at(
249        &mut self,
250        loc: &'static Location<'static>,
251    ) -> bool {
252        let key = (loc.file(), loc.line(), loc.column());
253        self.keyed_at(loc, key, |cx| {
254            let render_pass_id = cx.render_pass_id;
255            cx.root_state(
256                RenderEvaluationCallsiteDiagnostics::default,
257                |diagnostics| note_call_in_render_evaluation(diagnostics, render_pass_id),
258            )
259        })
260    }
261
262    #[cfg(not(debug_assertions))]
263    #[doc(hidden)]
264    pub fn note_repeated_call_in_render_evaluation_at(
265        &mut self,
266        _loc: &'static Location<'static>,
267    ) -> bool {
268        false
269    }
270
271    pub fn root_id(&self) -> GlobalElementId {
272        *self.stack.last().expect("root exists")
273    }
274
275    fn new_any_element(
276        &mut self,
277        id: GlobalElementId,
278        kind: ElementKind,
279        children: Vec<AnyElement>,
280    ) -> AnyElement {
281        AnyElement::new(id, kind, children)
282    }
283
284    /// Returns the nearest ancestor state value of type `S` in the current element scope stack.
285    ///
286    /// This is a lightweight building block for component-layer "provider" patterns (e.g. Radix
287    /// `DirectionProvider`) without requiring a dedicated runtime context mechanism.
288    pub fn inherited_state<S: Any>(&self) -> Option<&S> {
289        self.inherited_state_where(|_state: &S| true)
290    }
291
292    /// Like `inherited_state`, but allows skipping "inactive" states while continuing to search.
293    ///
294    /// This is useful when a state entry remains allocated but temporarily holds no active value
295    /// (e.g. an `Option<T>` that is `None` outside of a scope).
296    pub fn inherited_state_where<S: Any>(&self, predicate: impl Fn(&S) -> bool) -> Option<&S> {
297        let ty = TypeId::of::<S>();
298        for &id in self.stack.iter().rev() {
299            let key = (id, ty);
300            let Some(any) = self.window_state.state_any_ref(&key) else {
301                continue;
302            };
303            let Some(state) = any.downcast_ref::<S>() else {
304                continue;
305            };
306            if predicate(state) {
307                return Some(state);
308            }
309        }
310        None
311    }
312
313    /// Returns the nearest value installed via [`Self::provide`] for type `S`.
314    pub fn provided<S: Any>(&self) -> Option<&S> {
315        self.provided_where(|_state: &S| true)
316    }
317
318    /// Like [`Self::provided`], but allows filtering values while continuing the ancestor search.
319    pub fn provided_where<S: Any>(&self, predicate: impl Fn(&S) -> bool) -> Option<&S> {
320        self.inherited_state_where::<ProvidedState<S>>(|slot| {
321            slot.value.as_ref().is_some_and(&predicate)
322        })
323        .and_then(|slot| slot.value.as_ref())
324    }
325
326    /// Returns the last known `NodeId` for a declarative element, if available.
327    ///
328    /// This is intentionally a last-known query surface: authoritative render-time correctness
329    /// paths should prefer live window-frame or attached-tree resolution instead of treating this
330    /// as the current attached node.
331    ///
332    /// It remains safe to call during element rendering because it reads from the `ElementCx`'s
333    /// already borrowed window state, avoiding re-entrant `UiHost::with_global_mut` leases.
334    pub fn node_for_element(&self, element: GlobalElementId) -> Option<NodeId> {
335        self.window_state.node_entry(element).map(|e| e.node)
336    }
337
338    /// Returns the current-frame node mapping for `element`, if available.
339    ///
340    /// The authoritative liveness signal is the retained `node_entry` record being touched in the
341    /// current frame (`last_seen_frame == self.frame_id`).
342    pub fn live_node_for_element(&mut self, element: GlobalElementId) -> Option<NodeId> {
343        self.window_state
344            .node_entry(element)
345            .and_then(|entry| (entry.last_seen_frame == self.frame_id).then_some(entry.node))
346    }
347
348    pub fn focused_element(&self) -> Option<GlobalElementId> {
349        self.window_state.focused_element
350    }
351
352    pub fn is_focused_element(&self, element: GlobalElementId) -> bool {
353        self.window_state.focused_element == Some(element)
354    }
355
356    /// Returns `true` when the currently focused element is `element` itself or any descendant in
357    /// the declarative element tree for this window.
358    pub fn is_focus_within_element(&mut self, element: GlobalElementId) -> bool {
359        let Some(focused) = self.focused_element() else {
360            return false;
361        };
362        if focused == element {
363            return true;
364        }
365
366        let root_node = crate::declarative::node_for_element_in_window_frame(
367            &mut *self.app,
368            self.window,
369            element,
370        )
371        .or_else(|| self.window_state.node_entry(element).map(|e| e.node));
372        let Some(root_node) = root_node else {
373            return false;
374        };
375        let focused_node = crate::declarative::node_for_element_in_window_frame(
376            &mut *self.app,
377            self.window,
378            focused,
379        )
380        .or_else(|| self.window_state.node_entry(focused).map(|e| e.node));
381        let Some(focused_node) = focused_node else {
382            return false;
383        };
384
385        crate::declarative::node_contains_in_window_frame(
386            &mut *self.app,
387            self.window,
388            root_node,
389            focused_node,
390        )
391    }
392
393    pub fn has_active_text_selection(&self) -> bool {
394        self.window_state.active_text_selection().is_some()
395    }
396
397    pub fn has_active_text_selection_in_root(&self, root: GlobalElementId) -> bool {
398        self.window_state
399            .active_text_selection()
400            .is_some_and(|selection| selection.root == root)
401    }
402
403    pub(crate) fn sync_focused_element_from_focused_node(&mut self, focused: Option<NodeId>) {
404        self.window_state.focused_element = focused.and_then(|node| {
405            crate::declarative::frame::element_record_for_node(&mut *self.app, self.window, node)
406                .map(|record| record.element)
407                .or_else(|| self.window_state.element_for_node(node))
408        });
409    }
410
411    /// Returns the last frame's bounds for a declarative element, if available.
412    ///
413    /// This is safe to call during element rendering: it reads from the `ElementCx`'s already
414    /// borrowed window state, avoiding re-entrant `UiHost::with_global_mut` leases.
415    pub fn last_bounds_for_element(&self, element: GlobalElementId) -> Option<Rect> {
416        self.window_state.last_bounds(element)
417    }
418
419    /// Returns the last frame's **visual** bounds (post-`render_transform` AABB) for a declarative
420    /// element, if available.
421    ///
422    /// This is intended for anchored overlay policies that must track render transforms (ADR 0083)
423    /// without mixing layout transforms into the layout solver.
424    pub fn last_visual_bounds_for_element(&self, element: GlobalElementId) -> Option<Rect> {
425        self.window_state
426            .last_visual_bounds(element)
427            .or_else(|| self.window_state.last_bounds(element))
428    }
429
430    /// Returns the last recorded root bounds for the element's root, if available.
431    ///
432    /// This is safe to call during element rendering for the same reason as
433    /// `last_bounds_for_element`.
434    pub fn root_bounds_for_element(&self, element: GlobalElementId) -> Option<Rect> {
435        let root = self.window_state.node_entry(element).map(|e| e.root)?;
436        self.window_state.root_bounds(root)
437    }
438
439    /// Consume a transient event flag for the current element.
440    ///
441    /// This returns `true` at most once per recorded event (clear-on-read).
442    pub fn take_transient(&mut self, key: u64) -> bool {
443        let element = self.root_id();
444        self.take_transient_for(element, key)
445    }
446
447    /// Consume a transient event flag for the specified element.
448    ///
449    /// This returns `true` at most once per recorded event (clear-on-read).
450    pub fn take_transient_for(&mut self, element: GlobalElementId, key: u64) -> bool {
451        self.window_state.take_transient_event(element, key)
452    }
453
454    pub fn with_root_name<R>(&mut self, root_name: &str, f: impl FnOnce(&mut Self) -> R) -> R {
455        let root = global_root(self.window, root_name);
456
457        let prev_stack = std::mem::take(&mut self.stack);
458        let prev_counters = std::mem::take(&mut self.callsite_counters);
459
460        self.stack = vec![root];
461        self.callsite_counters = vec![CallsiteCounters::new()];
462        self.window_state.mark_authoring_identity_seen(root);
463
464        let out = f(self);
465
466        self.stack = prev_stack;
467        self.callsite_counters = prev_counters;
468
469        out
470    }
471
472    /// Runs `f` and then restores the current callsite counters to their previous state.
473    ///
474    /// This is useful when you need to "probe-render" a subtree (e.g. to compute derived
475    /// information like `focus_within`) without consuming callsite slots and disturbing
476    /// subsequent element identity in the real render pass.
477    pub fn with_callsite_counters_snapshot<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
478        let prev_stack = self.stack.clone();
479        let prev_counters = self.callsite_counters.clone();
480
481        let out = f(self);
482
483        debug_assert_eq!(
484            self.stack.len(),
485            prev_stack.len(),
486            "element stack depth must be balanced"
487        );
488        debug_assert_eq!(
489            self.callsite_counters.len(),
490            prev_counters.len(),
491            "callsite counters stack depth must be balanced"
492        );
493
494        self.stack = prev_stack;
495        self.callsite_counters = prev_counters;
496        debug_assert_eq!(self.callsite_counters.len(), self.stack.len());
497        out
498    }
499
500    /// Request a window redraw (one-shot).
501    ///
502    /// Use this after mutating state/models to schedule a repaint.
503    ///
504    /// Notes:
505    /// - This does not guarantee frame-driven progression. If you need to advance without input
506    ///   events (animations, progressive rendering), prefer `request_animation_frame()` or
507    ///   `begin_continuous_frames()`.
508    /// - This is not a timer: callers that need continuous progression must keep requesting frames.
509    pub fn request_frame(&mut self) {
510        self.app.request_redraw(self.window);
511    }
512
513    /// Mark the nearest cache root as needing a paint notification on the next mount.
514    ///
515    /// This is a lightweight way to force paint-cache roots to rerun paint (e.g. while animating
516    /// opacity/transform) without necessarily requesting a new animation frame from the runner.
517    pub fn notify_for_animation_frame(&mut self) {
518        // Drive invalidation from the current element, letting propagation and cache-root
519        // truncation pick the appropriate boundary (e.g. nearest view-cache root when enabled).
520        self.window_state
521            .request_notify_for_animation_frame(self.root_id());
522    }
523
524    /// Request the next animation frame for this window.
525    ///
526    /// Use this for frame-driven updates that must advance without input events.
527    ///
528    /// This is a one-shot request. Prefer `begin_continuous_frames()` when driving animations from
529    /// declarative UI code.
530    pub fn request_animation_frame(&mut self) {
531        self.notify_for_animation_frame();
532        self.app
533            .push_effect(Effect::RequestAnimationFrame(self.window));
534    }
535
536    /// Begin a "continuous frames" lease for this window.
537    ///
538    /// This is the preferred way for declarative UI to drive animations: while the returned
539    /// lease is held, the mount pass will continue requesting animation frames.
540    pub fn begin_continuous_frames(&mut self) -> ContinuousFrames {
541        let lease = self
542            .window_state
543            .begin_continuous_frames(Some(self.root_id()));
544        self.request_animation_frame();
545        lease
546    }
547
548    /// Request a one-shot timer event routed to the given element.
549    ///
550    /// This records the timer target in the current window element runtime, enabling
551    /// `Event::Timer { token }` to be dispatched to the element's `on_timer` hook.
552    pub fn set_timer_for(&mut self, element: GlobalElementId, token: TimerToken, after: Duration) {
553        self.window_state.timer_targets.insert(
554            token,
555            crate::elements::runtime::TimerTarget::Element(element),
556        );
557        self.app.push_effect(Effect::SetTimer {
558            window: Some(self.window),
559            token,
560            after,
561            repeat: None,
562        });
563    }
564
565    /// Request a one-shot timer event routed to the current element.
566    pub fn set_timer(&mut self, token: TimerToken, after: Duration) {
567        let element = self.root_id();
568        self.set_timer_for(element, token, after);
569    }
570
571    /// Cancel a previously requested timer and clear its routing entry for this window.
572    pub fn cancel_timer(&mut self, token: TimerToken) {
573        self.window_state.timer_targets.remove(&token);
574        self.app.push_effect(Effect::CancelTimer { token });
575    }
576
577    #[track_caller]
578    pub fn scope<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
579        let loc = Location::caller();
580        let callsite = callsite_hash(loc);
581        self.enter_with_callsite(loc, callsite, None, None, f)
582    }
583
584    #[doc(hidden)]
585    pub fn scope_at<R>(
586        &mut self,
587        loc: &'static Location<'static>,
588        f: impl FnOnce(&mut Self) -> R,
589    ) -> R {
590        let callsite = callsite_hash(loc);
591        self.enter_with_callsite(loc, callsite, None, None, f)
592    }
593
594    #[track_caller]
595    pub fn keyed<K: Hash, R>(&mut self, key: K, f: impl FnOnce(&mut Self) -> R) -> R {
596        let loc = Location::caller();
597        self.keyed_at(loc, key, f)
598    }
599
600    #[doc(hidden)]
601    pub fn keyed_at<K: Hash, R>(
602        &mut self,
603        loc: &'static Location<'static>,
604        key: K,
605        f: impl FnOnce(&mut Self) -> R,
606    ) -> R {
607        let caller = callsite_hash(loc);
608        let key_hash = stable_hash(&key);
609        self.enter_with_callsite(loc, caller, Some(key_hash), None, f)
610    }
611
612    #[track_caller]
613    pub fn named<R>(&mut self, name: &str, f: impl FnOnce(&mut Self) -> R) -> R {
614        let loc = Location::caller();
615        let caller = callsite_hash(loc);
616        let key_hash = stable_hash(&name);
617        self.enter_with_callsite(loc, caller, Some(key_hash), Some(name), f)
618    }
619
620    /// Component/internal identity lane: accesses root-scoped state stored under the current
621    /// `root_id()`.
622    ///
623    /// This is keyed by `(root_id, TypeId)`, so it is a shared slot per element root.
624    /// Repeated calls with the same state type under the same root intentionally see the same
625    /// state. If you need multiple independent slots of the same type, create a child scope first
626    /// via [`Self::scope`], [`Self::keyed`], or another helper that allocates a distinct root id.
627    pub fn root_state<S: Any, R>(
628        &mut self,
629        init: impl FnOnce() -> S,
630        f: impl FnOnce(&mut S) -> R,
631    ) -> R {
632        let id = self.root_id();
633        self.state_for(id, init, f)
634    }
635
636    /// Compatibility alias for [`Self::root_state`].
637    ///
638    /// Prefer [`Self::root_state`] in new first-party code. Keep this alias for migration and
639    /// downstream compatibility only.
640    #[deprecated(note = "use root_state(...) for root-scoped shared element state")]
641    pub fn with_state<S: Any, R>(
642        &mut self,
643        init: impl FnOnce() -> S,
644        f: impl FnOnce(&mut S) -> R,
645    ) -> R {
646        self.root_state(init, f)
647    }
648
649    /// Component/internal identity lane: accesses helper-local state stored under a synthetic
650    /// callsite-derived slot.
651    ///
652    /// Unlike [`Self::root_state`], this does not use the current `root_id()` directly. The slot is
653    /// derived from `(current_root, caller_location, call_index_at_that_location)` so distinct
654    /// callsites of the same state type do not collide.
655    ///
656    /// This is the correct primitive for helper-owned internals that want React hook-like slot
657    /// semantics without allocating a visible child scope in the element stack. The derived slot is
658    /// intentionally **not** pushed onto the inherited-state stack, so descendants cannot observe
659    /// it via [`Self::inherited_state`].
660    ///
661    /// If a helper needs to touch the same slot more than once during one render pass, either do
662    /// all work inside a single closure or allocate the slot once via [`Self::slot_id`] and then
663    /// use [`Self::state_for`] for repeated accesses.
664    ///
665    /// When the helper may be invoked from a reorderable collection, prefer wrapping the caller in
666    /// [`Self::keyed`] (or use [`Self::keyed_slot_state`]) so the surrounding identity stays stable
667    /// across insertions and reordering.
668    #[track_caller]
669    pub fn slot_state<S: Any, R>(
670        &mut self,
671        init: impl FnOnce() -> S,
672        f: impl FnOnce(&mut S) -> R,
673    ) -> R {
674        let loc = Location::caller();
675        let id = self.callsite_state_slot_id(loc, None);
676        self.state_for(id, init, f)
677    }
678
679    /// Like [`Self::slot_state`], but derives the synthetic slot from `(callsite, key)`.
680    ///
681    /// This mirrors [`Self::keyed`]: repeated renders with the same key reuse the same state slot
682    /// even if sibling order changes.
683    #[track_caller]
684    pub fn keyed_slot_state<K: Hash, S: Any, R>(
685        &mut self,
686        key: K,
687        init: impl FnOnce() -> S,
688        f: impl FnOnce(&mut S) -> R,
689    ) -> R {
690        let loc = Location::caller();
691        let id = self.callsite_state_slot_id(loc, Some(stable_hash(&key)));
692        self.state_for(id, init, f)
693    }
694
695    /// Allocates a synthetic callsite-derived state slot id without pushing a child scope.
696    ///
697    /// Use this when one helper invocation needs to read/write the same helper-local slot multiple
698    /// times during a single render pass.
699    #[track_caller]
700    pub fn slot_id(&mut self) -> GlobalElementId {
701        let loc = Location::caller();
702        self.slot_id_at(loc)
703    }
704
705    /// Like [`Self::slot_id`], but keys the synthetic slot by `(callsite, key)`.
706    ///
707    /// This is useful for helper-local state inside reorderable collections where sibling order is
708    /// not stable across renders.
709    #[track_caller]
710    pub fn keyed_slot_id<K: Hash>(&mut self, key: K) -> GlobalElementId {
711        let loc = Location::caller();
712        self.keyed_slot_id_at(loc, key)
713    }
714
715    /// Like [`Self::slot_id`], but anchors the synthetic slot at an explicit caller location.
716    #[doc(hidden)]
717    pub fn slot_id_at(&mut self, loc: &'static Location<'static>) -> GlobalElementId {
718        self.callsite_state_slot_id(loc, None)
719    }
720
721    /// Like [`Self::keyed_slot_id`], but anchors the synthetic slot at an explicit caller
722    /// location.
723    #[doc(hidden)]
724    pub fn keyed_slot_id_at<K: Hash>(
725        &mut self,
726        loc: &'static Location<'static>,
727        key: K,
728    ) -> GlobalElementId {
729        self.callsite_state_slot_id(loc, Some(stable_hash(&key)))
730    }
731
732    /// Component/internal identity lane: returns a helper-local model handle stored under a
733    /// synthetic callsite-derived slot.
734    ///
735    /// This is the model-handle counterpart to [`Self::slot_state`]: the slot identity is local to
736    /// this helper invocation and stable across frames, so callers can create copy-paste-friendly
737    /// local `Model<T>` state without abusing root-scoped [`Self::with_state`].
738    #[track_caller]
739    pub fn local_model<T: Any>(&mut self, init: impl FnOnce() -> T) -> Model<T> {
740        let slot = self.slot_id();
741        self.local_model_for(slot, init)
742    }
743
744    /// Like [`Self::local_model`], but anchors the synthetic slot at an explicit caller location.
745    #[doc(hidden)]
746    pub fn local_model_at<T: Any>(
747        &mut self,
748        loc: &'static Location<'static>,
749        init: impl FnOnce() -> T,
750    ) -> Model<T> {
751        let slot = self.slot_id_at(loc);
752        self.local_model_for(slot, init)
753    }
754
755    /// Like [`Self::local_model`], but keys the synthetic slot by `(callsite, key)`.
756    ///
757    /// Prefer this when one helper is invoked from a reorderable collection or when several local
758    /// models are authored through a shared helper and need explicit, stable names.
759    #[track_caller]
760    pub fn local_model_keyed<K: Hash, T: Any>(
761        &mut self,
762        key: K,
763        init: impl FnOnce() -> T,
764    ) -> Model<T> {
765        let slot = self.keyed_slot_id(key);
766        self.local_model_for(slot, init)
767    }
768
769    /// Like [`Self::local_model_keyed`], but anchors the synthetic slot at an explicit caller
770    /// location.
771    #[doc(hidden)]
772    pub fn local_model_keyed_at<K: Hash, T: Any>(
773        &mut self,
774        loc: &'static Location<'static>,
775        key: K,
776        init: impl FnOnce() -> T,
777    ) -> Model<T> {
778        let slot = self.keyed_slot_id_at(loc, key);
779        self.local_model_for(slot, init)
780    }
781
782    /// Accesses state stored under an explicit element identity.
783    pub fn state_for<S: Any, R>(
784        &mut self,
785        element: GlobalElementId,
786        init: impl FnOnce() -> S,
787        f: impl FnOnce(&mut S) -> R,
788    ) -> R {
789        self.window_state.with_state_mut(element, init, f)
790    }
791
792    /// Compatibility alias for [`Self::state_for`].
793    ///
794    /// Prefer [`Self::state_for`] in new first-party code. Keep this alias for migration and
795    /// downstream compatibility only.
796    #[deprecated(note = "use state_for(...) for explicit-identity element state")]
797    pub fn with_state_for<S: Any, R>(
798        &mut self,
799        element: GlobalElementId,
800        init: impl FnOnce() -> S,
801        f: impl FnOnce(&mut S) -> R,
802    ) -> R {
803        self.state_for(element, init, f)
804    }
805
806    /// Component/internal identity lane: returns a model handle stored under an explicit element
807    /// identity.
808    ///
809    /// This is the explicit-identity counterpart to [`Self::local_model`] and
810    /// [`Self::local_model_keyed`]. Prefer it when a helper must bind a `Model<T>` to a stable
811    /// overlay/portal/root id instead of a callsite-derived slot.
812    ///
813    /// Like [`Self::state_for`], storage is keyed by `(element, TypeId)`. Different model value
814    /// types may therefore share one explicit element id, while multiple models of the same type
815    /// still need distinct explicit ids.
816    pub fn model_for<T: Any>(
817        &mut self,
818        element: GlobalElementId,
819        init: impl FnOnce() -> T,
820    ) -> Model<T> {
821        self.local_model_for(element, init)
822    }
823
824    fn local_model_for<T: Any>(
825        &mut self,
826        slot: GlobalElementId,
827        init: impl FnOnce() -> T,
828    ) -> Model<T> {
829        let model = self.state_for(slot, LocalModelSlot::<T>::default, |st| st.model.clone());
830        match model {
831            Some(model) => model,
832            None => {
833                let model = self.app.models_mut().insert(init());
834                self.state_for(slot, LocalModelSlot::<T>::default, |st| {
835                    st.model = Some(model.clone());
836                });
837                model
838            }
839        }
840    }
841
842    /// Installs `value` as an inherited provider value for the current subtree.
843    ///
844    /// This is the preferred authoring surface for provider-style patterns. It keeps provider
845    /// semantics separate from raw root state while still using the same element-state substrate.
846    pub fn provide<S: Any, R>(&mut self, value: S, f: impl FnOnce(&mut Self) -> R) -> R {
847        let prev = self.root_state(ProvidedState::<S>::default, |slot| {
848            slot.value.replace(value)
849        });
850        let out = f(self);
851        self.root_state(ProvidedState::<S>::default, |slot| {
852            slot.value = prev;
853        });
854        out
855    }
856
857    pub fn observe_model<T>(&mut self, model: &Model<T>, invalidation: Invalidation) {
858        self.observe_model_id(model.id(), invalidation);
859    }
860
861    pub fn read_model<T: Any, R>(
862        &mut self,
863        model: &Model<T>,
864        invalidation: Invalidation,
865        f: impl FnOnce(&mut H, &T) -> R,
866    ) -> Result<R, ModelUpdateError> {
867        self.observe_model(model, invalidation);
868        model.read(&mut *self.app, f)
869    }
870
871    pub fn read_model_ref<T: Any, R>(
872        &mut self,
873        model: &Model<T>,
874        invalidation: Invalidation,
875        f: impl FnOnce(&T) -> R,
876    ) -> Result<R, ModelUpdateError> {
877        self.observe_model(model, invalidation);
878        self.app.models().read(model, f)
879    }
880
881    pub fn get_model_copied<T: Any + Copy>(
882        &mut self,
883        model: &Model<T>,
884        invalidation: Invalidation,
885    ) -> Option<T> {
886        self.read_model_ref(model, invalidation, |v| *v).ok()
887    }
888
889    pub fn get_model_cloned<T: Any + Clone>(
890        &mut self,
891        model: &Model<T>,
892        invalidation: Invalidation,
893    ) -> Option<T> {
894        self.read_model_ref(model, invalidation, Clone::clone).ok()
895    }
896
897    pub fn observe_model_id(&mut self, model: ModelId, invalidation: Invalidation) {
898        let id = self
899            .window_state
900            .current_view_cache_root()
901            .unwrap_or_else(|| self.root_id());
902        let list = self
903            .window_state
904            .observed_models_next
905            .entry(id)
906            .or_default();
907        if list
908            .iter()
909            .any(|(m, inv)| *m == model && *inv == invalidation)
910        {
911            return;
912        }
913        list.push((model, invalidation));
914    }
915
916    pub fn observe_global<T: Any>(&mut self, invalidation: Invalidation) {
917        self.observe_global_id(TypeId::of::<T>(), invalidation);
918    }
919
920    pub fn observe_global_id(&mut self, global: TypeId, invalidation: Invalidation) {
921        let id = self
922            .window_state
923            .current_view_cache_root()
924            .unwrap_or_else(|| self.root_id());
925        let list = self
926            .window_state
927            .observed_globals_next
928            .entry(id)
929            .or_default();
930        if list
931            .iter()
932            .any(|(g, inv)| *g == global && *inv == invalidation)
933        {
934            return;
935        }
936        list.push((global, invalidation));
937    }
938
939    fn observe_environment_query(&mut self, key: EnvironmentQueryKey, invalidation: Invalidation) {
940        let id = self
941            .window_state
942            .current_view_cache_root()
943            .unwrap_or_else(|| self.root_id());
944        let list = self
945            .window_state
946            .observed_environment_next
947            .entry(id)
948            .or_default();
949        if list
950            .iter()
951            .any(|(k, inv)| *k == key && *inv == invalidation)
952        {
953            return;
954        }
955        list.push((key, invalidation));
956    }
957
958    pub fn observe_layout_query_region(
959        &mut self,
960        region: GlobalElementId,
961        invalidation: Invalidation,
962    ) {
963        let id = self
964            .window_state
965            .current_view_cache_root()
966            .unwrap_or_else(|| self.root_id());
967        let list = self
968            .window_state
969            .observed_layout_queries_next
970            .entry(id)
971            .or_default();
972        if list
973            .iter()
974            .any(|(r, inv)| *r == region && *inv == invalidation)
975        {
976            return;
977        }
978        list.push((region, invalidation));
979    }
980
981    pub fn layout_query_bounds(
982        &mut self,
983        region: GlobalElementId,
984        invalidation: Invalidation,
985    ) -> Option<Rect> {
986        self.observe_layout_query_region(region, invalidation);
987        self.last_bounds_for_element(region)
988    }
989
990    pub fn environment_viewport_bounds(&mut self, invalidation: Invalidation) -> Rect {
991        self.observe_environment_query(EnvironmentQueryKey::ViewportSize, invalidation);
992        self.window_state.committed_viewport_bounds()
993    }
994
995    pub fn environment_viewport_width(&mut self, invalidation: Invalidation) -> Px {
996        self.environment_viewport_bounds(invalidation).size.width
997    }
998
999    pub fn environment_viewport_height(&mut self, invalidation: Invalidation) -> Px {
1000        self.environment_viewport_bounds(invalidation).size.height
1001    }
1002
1003    pub fn environment_scale_factor(&mut self, invalidation: Invalidation) -> f32 {
1004        self.observe_environment_query(EnvironmentQueryKey::ScaleFactor, invalidation);
1005        self.window_state.committed_scale_factor()
1006    }
1007
1008    pub fn environment_color_scheme(&mut self, invalidation: Invalidation) -> Option<ColorScheme> {
1009        self.observe_environment_query(EnvironmentQueryKey::ColorScheme, invalidation);
1010        self.window_state.committed_color_scheme()
1011    }
1012
1013    pub fn environment_prefers_reduced_motion(
1014        &mut self,
1015        invalidation: Invalidation,
1016    ) -> Option<bool> {
1017        self.observe_environment_query(EnvironmentQueryKey::PrefersReducedMotion, invalidation);
1018        self.window_state.committed_prefers_reduced_motion()
1019    }
1020
1021    pub fn environment_text_scale_factor(&mut self, invalidation: Invalidation) -> Option<f32> {
1022        self.observe_environment_query(EnvironmentQueryKey::TextScaleFactor, invalidation);
1023        self.window_state.committed_text_scale_factor()
1024    }
1025
1026    pub fn environment_prefers_reduced_transparency(
1027        &mut self,
1028        invalidation: Invalidation,
1029    ) -> Option<bool> {
1030        self.observe_environment_query(
1031            EnvironmentQueryKey::PrefersReducedTransparency,
1032            invalidation,
1033        );
1034        self.window_state.committed_prefers_reduced_transparency()
1035    }
1036
1037    pub fn environment_accent_color(&mut self, invalidation: Invalidation) -> Option<Color> {
1038        self.observe_environment_query(EnvironmentQueryKey::AccentColor, invalidation);
1039        self.window_state.committed_accent_color()
1040    }
1041
1042    pub fn environment_prefers_contrast(
1043        &mut self,
1044        invalidation: Invalidation,
1045    ) -> Option<ContrastPreference> {
1046        self.observe_environment_query(EnvironmentQueryKey::PrefersContrast, invalidation);
1047        self.window_state.committed_contrast_preference()
1048    }
1049
1050    pub fn environment_forced_colors_mode(
1051        &mut self,
1052        invalidation: Invalidation,
1053    ) -> Option<ForcedColorsMode> {
1054        self.observe_environment_query(EnvironmentQueryKey::ForcedColorsMode, invalidation);
1055        self.window_state.committed_forced_colors_mode()
1056    }
1057
1058    pub fn environment_safe_area_insets(&mut self, invalidation: Invalidation) -> Option<Edges> {
1059        self.observe_environment_query(EnvironmentQueryKey::SafeAreaInsets, invalidation);
1060        self.window_state.committed_safe_area_insets()
1061    }
1062
1063    pub fn environment_occlusion_insets(&mut self, invalidation: Invalidation) -> Option<Edges> {
1064        self.observe_environment_query(EnvironmentQueryKey::OcclusionInsets, invalidation);
1065        self.window_state.committed_occlusion_insets()
1066    }
1067
1068    pub fn environment_primary_pointer_type(&mut self, invalidation: Invalidation) -> PointerType {
1069        self.observe_environment_query(EnvironmentQueryKey::PrimaryPointerType, invalidation);
1070        self.window_state.committed_primary_pointer_type()
1071    }
1072
1073    pub fn environment_primary_pointer_can_hover(
1074        &mut self,
1075        invalidation: Invalidation,
1076        default_when_unknown: bool,
1077    ) -> bool {
1078        match self.environment_primary_pointer_type(invalidation) {
1079            PointerType::Touch => false,
1080            PointerType::Unknown => default_when_unknown,
1081            PointerType::Mouse | PointerType::Pen => true,
1082        }
1083    }
1084
1085    pub fn environment_primary_pointer_is_coarse(
1086        &mut self,
1087        invalidation: Invalidation,
1088        default_when_unknown: bool,
1089    ) -> bool {
1090        match self.environment_primary_pointer_type(invalidation) {
1091            PointerType::Touch => true,
1092            PointerType::Unknown => default_when_unknown,
1093            PointerType::Mouse | PointerType::Pen => false,
1094        }
1095    }
1096
1097    pub fn diagnostics_record_overlay_placement_anchored_panel(
1098        &mut self,
1099        overlay_root_name: Option<&str>,
1100        anchor_element: Option<GlobalElementId>,
1101        content_element: Option<GlobalElementId>,
1102        trace: AnchoredPanelLayoutTrace,
1103    ) {
1104        #[cfg(feature = "diagnostics")]
1105        self.window_state.record_overlay_placement_anchored_panel(
1106            self.frame_id,
1107            overlay_root_name.map(Arc::<str>::from),
1108            anchor_element,
1109            content_element,
1110            trace,
1111        );
1112        #[cfg(not(feature = "diagnostics"))]
1113        let _ = (overlay_root_name, anchor_element, content_element, trace);
1114    }
1115
1116    #[allow(clippy::too_many_arguments)]
1117    pub fn diagnostics_record_overlay_placement_placed_rect(
1118        &mut self,
1119        overlay_root_name: Option<&str>,
1120        anchor_element: Option<GlobalElementId>,
1121        content_element: Option<GlobalElementId>,
1122        outer: Rect,
1123        anchor: Rect,
1124        placed: Rect,
1125        side: Option<Side>,
1126    ) {
1127        #[cfg(feature = "diagnostics")]
1128        self.window_state.record_overlay_placement_placed_rect(
1129            self.frame_id,
1130            overlay_root_name.map(Arc::<str>::from),
1131            anchor_element,
1132            content_element,
1133            outer,
1134            anchor,
1135            placed,
1136            side,
1137        );
1138        #[cfg(not(feature = "diagnostics"))]
1139        let _ = (
1140            overlay_root_name,
1141            anchor_element,
1142            content_element,
1143            outer,
1144            anchor,
1145            placed,
1146            side,
1147        );
1148    }
1149
1150    pub fn theme(&mut self) -> &Theme {
1151        self.observe_global::<Theme>(Invalidation::Layout);
1152        Theme::global(&*self.app)
1153    }
1154
1155    #[track_caller]
1156    pub fn for_each_keyed<T, K: Hash>(
1157        &mut self,
1158        items: &[T],
1159        mut key: impl FnMut(&T) -> K,
1160        mut f: impl FnMut(&mut Self, usize, &T),
1161    ) {
1162        let loc = Location::caller();
1163        self.scope(|cx| {
1164            let mut first_dup: Option<(u64, usize, usize)> = None;
1165            let mut seen: HashMap<u64, usize> = HashMap::new();
1166            for (index, item) in items.iter().enumerate() {
1167                let k = key(item);
1168                let key_hash = stable_hash(&k);
1169                if first_dup.is_none()
1170                    && let Some(prev) = seen.insert(key_hash, index)
1171                {
1172                    first_dup = Some((key_hash, prev, index));
1173                }
1174                cx.keyed(k, |cx| f(cx, index, item));
1175            }
1176
1177            if let Some((key_hash, a, b)) = first_dup
1178                && cfg!(debug_assertions)
1179                && items.len() > 1
1180            {
1181                let element_path: Option<String> = {
1182                    #[cfg(feature = "diagnostics")]
1183                    {
1184                        cx.window_state.debug_path_for_element(cx.root_id())
1185                    }
1186                    #[cfg(not(feature = "diagnostics"))]
1187                    {
1188                        None
1189                    }
1190                };
1191
1192                tracing::warn!(
1193                    file = loc.file(),
1194                    line = loc.line(),
1195                    column = loc.column(),
1196                    key_hash = format_args!("{key_hash:#x}"),
1197                    first_index = a,
1198                    second_index = b,
1199                    element_path = element_path.as_deref().unwrap_or("<unknown>"),
1200                    "duplicate keyed list item key hash; element state may collide"
1201                );
1202            }
1203        });
1204    }
1205
1206    #[track_caller]
1207    pub fn for_each_unkeyed<T: Hash>(
1208        &mut self,
1209        items: &[T],
1210        mut f: impl FnMut(&mut Self, usize, &T),
1211    ) {
1212        let loc = Location::caller();
1213        let list_id = callsite_hash(loc);
1214        let fingerprints: Vec<u64> = items.iter().map(stable_hash).collect();
1215        self.window_state
1216            .cur_unkeyed_fingerprints
1217            .insert(list_id, fingerprints.clone());
1218
1219        if let Some(prev) = self.window_state.prev_unkeyed_fingerprints.get(&list_id)
1220            && prev != &fingerprints
1221            && items.len() > 1
1222            && cfg!(debug_assertions)
1223        {
1224            let element_path: Option<String> = {
1225                #[cfg(feature = "diagnostics")]
1226                {
1227                    self.window_state.debug_path_for_element(self.root_id())
1228                }
1229                #[cfg(not(feature = "diagnostics"))]
1230                {
1231                    None
1232                }
1233            };
1234            tracing::warn!(
1235                list_id = format_args!("{list_id:#x}"),
1236                file = loc.file(),
1237                line = loc.line(),
1238                column = loc.column(),
1239                element_path = element_path.as_deref().unwrap_or("<unknown>"),
1240                "unkeyed element list order changed; add explicit keys to preserve state"
1241            );
1242        }
1243
1244        self.scope(|cx| {
1245            for (index, item) in items.iter().enumerate() {
1246                let index_key = index as u64;
1247                cx.enter_with_callsite(loc, list_id, Some(index_key), None, |cx| {
1248                    f(cx, index, item)
1249                });
1250            }
1251        });
1252    }
1253
1254    fn enter_with_callsite<R>(
1255        &mut self,
1256        _loc: &'static Location<'static>,
1257        callsite: u64,
1258        key_hash: Option<u64>,
1259        _debug_name: Option<&str>,
1260        f: impl FnOnce(&mut Self) -> R,
1261    ) -> R {
1262        let parent = self.root_id();
1263        let counters = self
1264            .callsite_counters
1265            .last_mut()
1266            .expect("callsite counters exist");
1267        let slot = bump_callsite_counter(counters, callsite);
1268
1269        let child_salt = key_hash.unwrap_or(slot);
1270        let id = derive_child_id(parent, callsite, child_salt);
1271        self.window_state.mark_authoring_identity_seen(id);
1272
1273        #[cfg(feature = "diagnostics")]
1274        self.window_state.record_debug_child(
1275            self.frame_id,
1276            parent,
1277            id,
1278            _loc.file(),
1279            _loc.line(),
1280            _loc.column(),
1281            key_hash,
1282            _debug_name,
1283            slot,
1284        );
1285
1286        self.stack.push(id);
1287        self.callsite_counters.push(CallsiteCounters::new());
1288        let out = f(self);
1289        self.callsite_counters.pop();
1290        self.stack.pop();
1291        out
1292    }
1293
1294    fn callsite_state_slot_id(
1295        &mut self,
1296        loc: &'static Location<'static>,
1297        key_hash: Option<u64>,
1298    ) -> GlobalElementId {
1299        let parent = self.root_id();
1300        let callsite = callsite_hash(loc);
1301        let counters = self
1302            .callsite_counters
1303            .last_mut()
1304            .expect("callsite counters exist");
1305        let slot = bump_callsite_counter(counters, callsite);
1306        let child_salt = key_hash.unwrap_or(slot);
1307        derive_child_id(parent, callsite, child_salt)
1308    }
1309
1310    #[track_caller]
1311    pub fn container<I>(
1312        &mut self,
1313        props: ContainerProps,
1314        f: impl FnOnce(&mut Self) -> I,
1315    ) -> AnyElement
1316    where
1317        I: IntoIterator<Item = AnyElement>,
1318    {
1319        self.scope(|cx| {
1320            let id = cx.root_id();
1321            let built = f(cx);
1322            let children = cx.collect_children(built);
1323            cx.new_any_element(id, ElementKind::Container(props), children)
1324        })
1325    }
1326
1327    /// Creates a `Semantics` layout wrapper around the subtree.
1328    ///
1329    /// `Semantics` is intentionally input- and paint-transparent, but it **does** participate in
1330    /// layout via `SemanticsProps.layout`. Use it when you need a semantics node boundary (tree
1331    /// structure) or wrapper-only semantics features (e.g. a focusable semantics node).
1332    ///
1333    /// If you only need to stamp `test_id` / `label` / `role` / `value` for diagnostics or UI
1334    /// automation, prefer attaching `SemanticsDecoration` to an existing element via
1335    /// `AnyElement::attach_semantics(...)` to avoid introducing a layout node.
1336    #[track_caller]
1337    pub fn semantics<I>(
1338        &mut self,
1339        props: crate::element::SemanticsProps,
1340        f: impl FnOnce(&mut Self) -> I,
1341    ) -> AnyElement
1342    where
1343        I: IntoIterator<Item = AnyElement>,
1344    {
1345        self.scope(|cx| {
1346            let id = cx.root_id();
1347            let built = f(cx);
1348            let children = cx.collect_children(built);
1349            cx.new_any_element(id, ElementKind::Semantics(props), children)
1350        })
1351    }
1352
1353    #[track_caller]
1354    pub fn semantic_flex<I>(
1355        &mut self,
1356        props: crate::element::SemanticFlexProps,
1357        f: impl FnOnce(&mut Self) -> I,
1358    ) -> AnyElement
1359    where
1360        I: IntoIterator<Item = AnyElement>,
1361    {
1362        self.scope(|cx| {
1363            let id = cx.root_id();
1364            let built = f(cx);
1365            let children = cx.collect_children(built);
1366            cx.new_any_element(id, ElementKind::SemanticFlex(props), children)
1367        })
1368    }
1369
1370    #[track_caller]
1371    pub fn focus_scope<I>(
1372        &mut self,
1373        props: crate::element::FocusScopeProps,
1374        f: impl FnOnce(&mut Self) -> I,
1375    ) -> AnyElement
1376    where
1377        I: IntoIterator<Item = AnyElement>,
1378    {
1379        self.scope(|cx| {
1380            let id = cx.root_id();
1381            let built = f(cx);
1382            let children = cx.collect_children(built);
1383            cx.new_any_element(id, ElementKind::FocusScope(props), children)
1384        })
1385    }
1386
1387    #[track_caller]
1388    pub fn view_cache<I>(
1389        &mut self,
1390        props: crate::element::ViewCacheProps,
1391        f: impl FnOnce(&mut Self) -> I,
1392    ) -> AnyElement
1393    where
1394        I: IntoIterator<Item = AnyElement>,
1395    {
1396        self.scope(|cx| {
1397            let id = cx.root_id();
1398            let should_reuse = cx
1399                .window_state
1400                .node_entry(id)
1401                .map(|e| e.node)
1402                .and_then(|node| cx.view_cache_should_reuse.as_mut().map(|f| f(node)))
1403                .unwrap_or(false);
1404
1405            let theme_revision = Theme::global(&*cx.app).revision();
1406            let scale_factor = cx
1407                .app
1408                .global::<WindowMetricsService>()
1409                .and_then(|svc| svc.scale_factor(cx.window))
1410                .unwrap_or(1.0);
1411
1412            // View-cache keys must incorporate any "external" dependencies that can affect the
1413            // cached subtree without triggering a normal invalidation walk (e.g. environment
1414            // queries, layout queries).
1415            //
1416            // When reusing, depend on the previously-recorded deps for this cache root. When
1417            // re-rendering, the key is refreshed from `*_next` after the closure runs.
1418            let scale_bits = scale_factor.to_bits();
1419            let deps_key_rendered = if should_reuse {
1420                (
1421                    cx.window_state.environment_deps_fingerprint_rendered(id),
1422                    cx.window_state.layout_query_deps_fingerprint_rendered(id),
1423                )
1424            } else {
1425                (0, 0)
1426            };
1427            let key = stable_hash(&(
1428                theme_revision,
1429                scale_bits,
1430                props.cache_key,
1431                deps_key_rendered.0,
1432                deps_key_rendered.1,
1433            ));
1434
1435            let key_matches = if should_reuse {
1436                let matches = cx.window_state.view_cache_key_matches_and_touch(id, key);
1437                if !matches {
1438                    cx.window_state.record_view_cache_key_mismatch(id);
1439                }
1440                matches
1441            } else {
1442                false
1443            };
1444
1445            let reuse = should_reuse && key_matches;
1446
1447            let children: Vec<AnyElement> = if reuse {
1448                cx.window_state.mark_view_cache_reuse_root(id);
1449                cx.window_state.touch_view_cache_state_keys_if_recorded(id);
1450                cx.window_state
1451                    .touch_observed_models_for_element_if_recorded(id);
1452                cx.window_state
1453                    .touch_observed_globals_for_element_if_recorded(id);
1454                cx.window_state
1455                    .touch_observed_environment_for_element_if_recorded(id);
1456                cx.window_state
1457                    .touch_observed_layout_queries_for_element_if_recorded(id);
1458                Vec::new()
1459            } else {
1460                cx.window_state.begin_view_cache_scope(id);
1461                let built = f(cx);
1462                let children = cx.collect_children(built);
1463                cx.window_state.end_view_cache_scope(id);
1464
1465                let deps_key_next = (
1466                    cx.window_state.environment_deps_fingerprint_next(id),
1467                    cx.window_state.layout_query_deps_fingerprint_next(id),
1468                );
1469                let key = stable_hash(&(
1470                    theme_revision,
1471                    scale_bits,
1472                    props.cache_key,
1473                    deps_key_next.0,
1474                    deps_key_next.1,
1475                ));
1476                cx.window_state.set_view_cache_key(id, key);
1477                children
1478            };
1479            cx.new_any_element(id, ElementKind::ViewCache(props), children)
1480        })
1481    }
1482
1483    /// Explicit keep-alive cache root for cases where the element producer may be skipped by view caching.
1484    ///
1485    /// This mirrors the "reuse" side of `view_cache(...)`, but makes the decision explicit:
1486    /// - When `keep_alive=true`, the subtree is kept alive even if `f` is not executed.
1487    /// - When `keep_alive=false`, `f` is always executed (no implicit reuse).
1488    ///
1489    /// This is a mechanism-layer escape hatch intended for overlay synthesis and similar systems
1490    /// that must preserve subtree liveness when the normal authoring closure does not run.
1491    #[track_caller]
1492    pub fn view_cache_keep_alive<I>(
1493        &mut self,
1494        props: crate::element::ViewCacheProps,
1495        keep_alive: bool,
1496        f: impl FnOnce(&mut Self) -> I,
1497    ) -> AnyElement
1498    where
1499        I: IntoIterator<Item = AnyElement>,
1500    {
1501        self.scope(|cx| {
1502            let id = cx.root_id();
1503
1504            let theme_revision = Theme::global(&*cx.app).revision();
1505            let scale_factor = cx
1506                .app
1507                .global::<WindowMetricsService>()
1508                .and_then(|svc| svc.scale_factor(cx.window))
1509                .unwrap_or(1.0);
1510            let scale_bits = scale_factor.to_bits();
1511
1512            if keep_alive {
1513                cx.window_state.mark_view_cache_reuse_root(id);
1514                cx.window_state.touch_view_cache_state_keys_if_recorded(id);
1515                cx.window_state
1516                    .touch_observed_models_for_element_if_recorded(id);
1517                cx.window_state
1518                    .touch_observed_globals_for_element_if_recorded(id);
1519                cx.window_state
1520                    .touch_observed_environment_for_element_if_recorded(id);
1521                cx.window_state
1522                    .touch_observed_layout_queries_for_element_if_recorded(id);
1523
1524                // Preserve the previously-recorded key (if any); this root is being kept alive
1525                // explicitly rather than via normal key-matched reuse.
1526                let _ = (theme_revision, scale_bits);
1527                return cx.new_any_element(id, ElementKind::ViewCache(props), Vec::new());
1528            }
1529
1530            cx.window_state.begin_view_cache_scope(id);
1531            let built = f(cx);
1532            let children = cx.collect_children(built);
1533            cx.window_state.end_view_cache_scope(id);
1534
1535            let deps_key_next = (
1536                cx.window_state.environment_deps_fingerprint_next(id),
1537                cx.window_state.layout_query_deps_fingerprint_next(id),
1538            );
1539            let key = stable_hash(&(
1540                theme_revision,
1541                scale_bits,
1542                props.cache_key,
1543                deps_key_next.0,
1544                deps_key_next.1,
1545            ));
1546            cx.window_state.set_view_cache_key(id, key);
1547
1548            cx.new_any_element(id, ElementKind::ViewCache(props), children)
1549        })
1550    }
1551
1552    #[track_caller]
1553    pub fn focus_scope_with_id<I>(
1554        &mut self,
1555        props: crate::element::FocusScopeProps,
1556        f: impl FnOnce(&mut Self, GlobalElementId) -> I,
1557    ) -> AnyElement
1558    where
1559        I: IntoIterator<Item = AnyElement>,
1560    {
1561        self.scope(|cx| {
1562            let id = cx.root_id();
1563            let built = f(cx, id);
1564            let children = cx.collect_children(built);
1565            cx.new_any_element(id, ElementKind::FocusScope(props), children)
1566        })
1567    }
1568
1569    #[track_caller]
1570    pub fn layout_query_region_with_id<I>(
1571        &mut self,
1572        props: LayoutQueryRegionProps,
1573        f: impl FnOnce(&mut Self, GlobalElementId) -> I,
1574    ) -> AnyElement
1575    where
1576        I: IntoIterator<Item = AnyElement>,
1577    {
1578        self.scope(|cx| {
1579            let id = cx.root_id();
1580            let name = props.name.clone();
1581            cx.state_for(id, LayoutQueryRegionMarker::default, |marker| {
1582                marker.name = name.clone();
1583            });
1584            let built = f(cx, id);
1585            let children = cx.collect_children(built);
1586            cx.new_any_element(id, ElementKind::LayoutQueryRegion(props), children)
1587        })
1588    }
1589
1590    #[track_caller]
1591    pub fn layout_query_region<I>(
1592        &mut self,
1593        props: LayoutQueryRegionProps,
1594        f: impl FnOnce(&mut Self) -> I,
1595    ) -> AnyElement
1596    where
1597        I: IntoIterator<Item = AnyElement>,
1598    {
1599        self.layout_query_region_with_id(props, |cx, _id| f(cx))
1600    }
1601
1602    /// Creates a `Semantics` layout wrapper around the subtree and passes its element id.
1603    ///
1604    /// See [`Self::semantics`] for guidance on when to use `Semantics` vs `attach_semantics`
1605    /// (`SemanticsDecoration`).
1606    #[track_caller]
1607    pub fn semantics_with_id<I>(
1608        &mut self,
1609        props: crate::element::SemanticsProps,
1610        f: impl FnOnce(&mut Self, GlobalElementId) -> I,
1611    ) -> AnyElement
1612    where
1613        I: IntoIterator<Item = AnyElement>,
1614    {
1615        self.scope(|cx| {
1616            let id = cx.root_id();
1617            let built = f(cx, id);
1618            let children = cx.collect_children(built);
1619            cx.new_any_element(id, ElementKind::Semantics(props), children)
1620        })
1621    }
1622
1623    #[track_caller]
1624    pub fn opacity<I>(&mut self, opacity: f32, f: impl FnOnce(&mut Self) -> I) -> AnyElement
1625    where
1626        I: IntoIterator<Item = AnyElement>,
1627    {
1628        let props = OpacityProps {
1629            opacity: opacity.clamp(0.0, 1.0),
1630            ..Default::default()
1631        };
1632        self.opacity_props(props, f)
1633    }
1634
1635    #[track_caller]
1636    pub fn opacity_props<I>(
1637        &mut self,
1638        props: OpacityProps,
1639        f: impl FnOnce(&mut Self) -> I,
1640    ) -> AnyElement
1641    where
1642        I: IntoIterator<Item = AnyElement>,
1643    {
1644        self.scope(|cx| {
1645            let id = cx.root_id();
1646            let built = f(cx);
1647            let children = cx.collect_children(built);
1648            cx.new_any_element(id, ElementKind::Opacity(props), children)
1649        })
1650    }
1651
1652    #[track_caller]
1653    pub fn foreground_scope<I>(
1654        &mut self,
1655        foreground: fret_core::Color,
1656        f: impl FnOnce(&mut Self) -> I,
1657    ) -> AnyElement
1658    where
1659        I: IntoIterator<Item = AnyElement>,
1660    {
1661        self.foreground_scope_props(
1662            ForegroundScopeProps {
1663                foreground: Some(foreground),
1664                ..Default::default()
1665            },
1666            f,
1667        )
1668    }
1669
1670    #[track_caller]
1671    pub fn foreground_scope_props<I>(
1672        &mut self,
1673        props: ForegroundScopeProps,
1674        f: impl FnOnce(&mut Self) -> I,
1675    ) -> AnyElement
1676    where
1677        I: IntoIterator<Item = AnyElement>,
1678    {
1679        self.scope(|cx| {
1680            let id = cx.root_id();
1681            let built = f(cx);
1682            let children = cx.collect_children(built);
1683            cx.new_any_element(id, ElementKind::ForegroundScope(props), children)
1684        })
1685    }
1686
1687    #[track_caller]
1688    pub fn effect_layer<I>(
1689        &mut self,
1690        mode: EffectMode,
1691        chain: EffectChain,
1692        f: impl FnOnce(&mut Self) -> I,
1693    ) -> AnyElement
1694    where
1695        I: IntoIterator<Item = AnyElement>,
1696    {
1697        self.effect_layer_props(
1698            EffectLayerProps {
1699                mode,
1700                chain,
1701                ..Default::default()
1702            },
1703            f,
1704        )
1705    }
1706
1707    #[track_caller]
1708    pub fn effect_layer_props<I>(
1709        &mut self,
1710        props: EffectLayerProps,
1711        f: impl FnOnce(&mut Self) -> I,
1712    ) -> AnyElement
1713    where
1714        I: IntoIterator<Item = AnyElement>,
1715    {
1716        self.scope(|cx| {
1717            let id = cx.root_id();
1718            let built = f(cx);
1719            let children = cx.collect_children(built);
1720            cx.new_any_element(id, ElementKind::EffectLayer(props), children)
1721        })
1722    }
1723
1724    #[track_caller]
1725    pub fn backdrop_source_group_v1<I>(
1726        &mut self,
1727        pyramid: Option<fret_core::scene::CustomEffectPyramidRequestV1>,
1728        quality: EffectQuality,
1729        f: impl FnOnce(&mut Self) -> I,
1730    ) -> AnyElement
1731    where
1732        I: IntoIterator<Item = AnyElement>,
1733    {
1734        self.backdrop_source_group_v1_props(
1735            BackdropSourceGroupProps {
1736                pyramid,
1737                quality,
1738                ..Default::default()
1739            },
1740            f,
1741        )
1742    }
1743
1744    #[track_caller]
1745    pub fn backdrop_source_group_v1_props<I>(
1746        &mut self,
1747        props: BackdropSourceGroupProps,
1748        f: impl FnOnce(&mut Self) -> I,
1749    ) -> AnyElement
1750    where
1751        I: IntoIterator<Item = AnyElement>,
1752    {
1753        self.scope(|cx| {
1754            let id = cx.root_id();
1755            let built = f(cx);
1756            let children = cx.collect_children(built);
1757            cx.new_any_element(id, ElementKind::BackdropSourceGroup(props), children)
1758        })
1759    }
1760
1761    #[track_caller]
1762    pub fn mask_layer<I>(
1763        &mut self,
1764        mask: fret_core::scene::Mask,
1765        f: impl FnOnce(&mut Self) -> I,
1766    ) -> AnyElement
1767    where
1768        I: IntoIterator<Item = AnyElement>,
1769    {
1770        self.mask_layer_props(
1771            MaskLayerProps {
1772                layout: LayoutStyle::default(),
1773                mask,
1774            },
1775            f,
1776        )
1777    }
1778
1779    #[track_caller]
1780    pub fn mask_layer_props<I>(
1781        &mut self,
1782        props: MaskLayerProps,
1783        f: impl FnOnce(&mut Self) -> I,
1784    ) -> AnyElement
1785    where
1786        I: IntoIterator<Item = AnyElement>,
1787    {
1788        self.scope(|cx| {
1789            let id = cx.root_id();
1790            let built = f(cx);
1791            let children = cx.collect_children(built);
1792            cx.new_any_element(id, ElementKind::MaskLayer(props), children)
1793        })
1794    }
1795
1796    #[track_caller]
1797    pub fn composite_group<I>(
1798        &mut self,
1799        mode: fret_core::scene::BlendMode,
1800        f: impl FnOnce(&mut Self) -> I,
1801    ) -> AnyElement
1802    where
1803        I: IntoIterator<Item = AnyElement>,
1804    {
1805        self.composite_group_props(
1806            CompositeGroupProps {
1807                layout: LayoutStyle::default(),
1808                mode,
1809                quality: EffectQuality::Auto,
1810            },
1811            f,
1812        )
1813    }
1814
1815    #[track_caller]
1816    pub fn composite_group_props<I>(
1817        &mut self,
1818        props: CompositeGroupProps,
1819        f: impl FnOnce(&mut Self) -> I,
1820    ) -> AnyElement
1821    where
1822        I: IntoIterator<Item = AnyElement>,
1823    {
1824        self.scope(|cx| {
1825            let id = cx.root_id();
1826            let built = f(cx);
1827            let children = cx.collect_children(built);
1828            cx.new_any_element(id, ElementKind::CompositeGroup(props), children)
1829        })
1830    }
1831
1832    #[track_caller]
1833    pub fn visual_transform<I>(
1834        &mut self,
1835        transform: fret_core::Transform2D,
1836        f: impl FnOnce(&mut Self) -> I,
1837    ) -> AnyElement
1838    where
1839        I: IntoIterator<Item = AnyElement>,
1840    {
1841        self.visual_transform_props(
1842            VisualTransformProps {
1843                layout: LayoutStyle::default(),
1844                transform,
1845            },
1846            f,
1847        )
1848    }
1849
1850    #[track_caller]
1851    pub fn render_transform<I>(
1852        &mut self,
1853        transform: fret_core::Transform2D,
1854        f: impl FnOnce(&mut Self) -> I,
1855    ) -> AnyElement
1856    where
1857        I: IntoIterator<Item = AnyElement>,
1858    {
1859        self.render_transform_props(
1860            crate::element::RenderTransformProps {
1861                layout: LayoutStyle::default(),
1862                transform,
1863            },
1864            f,
1865        )
1866    }
1867
1868    #[track_caller]
1869    pub fn fractional_render_transform<I>(
1870        &mut self,
1871        translate_x_fraction: f32,
1872        translate_y_fraction: f32,
1873        f: impl FnOnce(&mut Self) -> I,
1874    ) -> AnyElement
1875    where
1876        I: IntoIterator<Item = AnyElement>,
1877    {
1878        self.fractional_render_transform_props(
1879            crate::element::FractionalRenderTransformProps {
1880                layout: LayoutStyle::default(),
1881                translate_x_fraction,
1882                translate_y_fraction,
1883            },
1884            f,
1885        )
1886    }
1887
1888    #[track_caller]
1889    pub fn visual_transform_props<I>(
1890        &mut self,
1891        props: VisualTransformProps,
1892        f: impl FnOnce(&mut Self) -> I,
1893    ) -> AnyElement
1894    where
1895        I: IntoIterator<Item = AnyElement>,
1896    {
1897        self.scope(|cx| {
1898            let id = cx.root_id();
1899            let built = f(cx);
1900            let children = cx.collect_children(built);
1901            cx.new_any_element(id, ElementKind::VisualTransform(props), children)
1902        })
1903    }
1904
1905    #[track_caller]
1906    pub fn render_transform_props<I>(
1907        &mut self,
1908        props: crate::element::RenderTransformProps,
1909        f: impl FnOnce(&mut Self) -> I,
1910    ) -> AnyElement
1911    where
1912        I: IntoIterator<Item = AnyElement>,
1913    {
1914        self.scope(|cx| {
1915            let id = cx.root_id();
1916            let built = f(cx);
1917            let children = cx.collect_children(built);
1918            cx.new_any_element(id, ElementKind::RenderTransform(props), children)
1919        })
1920    }
1921
1922    #[track_caller]
1923    pub fn fractional_render_transform_props<I>(
1924        &mut self,
1925        props: crate::element::FractionalRenderTransformProps,
1926        f: impl FnOnce(&mut Self) -> I,
1927    ) -> AnyElement
1928    where
1929        I: IntoIterator<Item = AnyElement>,
1930    {
1931        self.scope(|cx| {
1932            let id = cx.root_id();
1933            let built = f(cx);
1934            let children = cx.collect_children(built);
1935            cx.new_any_element(id, ElementKind::FractionalRenderTransform(props), children)
1936        })
1937    }
1938
1939    #[track_caller]
1940    pub fn anchored_props<I>(
1941        &mut self,
1942        props: crate::element::AnchoredProps,
1943        f: impl FnOnce(&mut Self) -> I,
1944    ) -> AnyElement
1945    where
1946        I: IntoIterator<Item = AnyElement>,
1947    {
1948        self.scope(|cx| {
1949            let id = cx.root_id();
1950            let built = f(cx);
1951            let children = cx.collect_children(built);
1952            cx.new_any_element(id, ElementKind::Anchored(props), children)
1953        })
1954    }
1955
1956    #[track_caller]
1957    pub fn interactivity_gate<I>(
1958        &mut self,
1959        present: bool,
1960        interactive: bool,
1961        f: impl FnOnce(&mut Self) -> I,
1962    ) -> AnyElement
1963    where
1964        I: IntoIterator<Item = AnyElement>,
1965    {
1966        self.interactivity_gate_props(
1967            InteractivityGateProps {
1968                present,
1969                interactive,
1970                ..Default::default()
1971            },
1972            f,
1973        )
1974    }
1975
1976    #[track_caller]
1977    pub fn interactivity_gate_props<I>(
1978        &mut self,
1979        props: InteractivityGateProps,
1980        f: impl FnOnce(&mut Self) -> I,
1981    ) -> AnyElement
1982    where
1983        I: IntoIterator<Item = AnyElement>,
1984    {
1985        self.scope(|cx| {
1986            let id = cx.root_id();
1987            let built = f(cx);
1988            let children = cx.collect_children(built);
1989            cx.new_any_element(id, ElementKind::InteractivityGate(props), children)
1990        })
1991    }
1992
1993    #[track_caller]
1994    pub fn hit_test_gate<I>(&mut self, hit_test: bool, f: impl FnOnce(&mut Self) -> I) -> AnyElement
1995    where
1996        I: IntoIterator<Item = AnyElement>,
1997    {
1998        self.hit_test_gate_props(
1999            HitTestGateProps {
2000                hit_test,
2001                ..Default::default()
2002            },
2003            f,
2004        )
2005    }
2006
2007    #[track_caller]
2008    pub fn hit_test_gate_props<I>(
2009        &mut self,
2010        props: HitTestGateProps,
2011        f: impl FnOnce(&mut Self) -> I,
2012    ) -> AnyElement
2013    where
2014        I: IntoIterator<Item = AnyElement>,
2015    {
2016        self.scope(|cx| {
2017            let id = cx.root_id();
2018            let built = f(cx);
2019            let children = cx.collect_children(built);
2020            cx.new_any_element(id, ElementKind::HitTestGate(props), children)
2021        })
2022    }
2023
2024    #[track_caller]
2025    pub fn focus_traversal_gate<I>(
2026        &mut self,
2027        traverse: bool,
2028        f: impl FnOnce(&mut Self) -> I,
2029    ) -> AnyElement
2030    where
2031        I: IntoIterator<Item = AnyElement>,
2032    {
2033        self.focus_traversal_gate_props(
2034            FocusTraversalGateProps {
2035                traverse,
2036                ..Default::default()
2037            },
2038            f,
2039        )
2040    }
2041
2042    #[track_caller]
2043    pub fn focus_traversal_gate_props<I>(
2044        &mut self,
2045        props: FocusTraversalGateProps,
2046        f: impl FnOnce(&mut Self) -> I,
2047    ) -> AnyElement
2048    where
2049        I: IntoIterator<Item = AnyElement>,
2050    {
2051        self.scope(|cx| {
2052            let id = cx.root_id();
2053            let built = f(cx);
2054            let children = cx.collect_children(built);
2055            cx.new_any_element(id, ElementKind::FocusTraversalGate(props), children)
2056        })
2057    }
2058
2059    #[track_caller]
2060    pub fn pressable<I>(
2061        &mut self,
2062        props: PressableProps,
2063        f: impl FnOnce(&mut Self, PressableState) -> I,
2064    ) -> AnyElement
2065    where
2066        I: IntoIterator<Item = AnyElement>,
2067    {
2068        self.scope(|cx| {
2069            let id = cx.root_id();
2070            let hovered = cx.window_state.hovered_pressable == Some(id);
2071            let hovered_raw = cx.window_state.hovered_pressable_raw == Some(id);
2072            let hovered_raw_below_barrier =
2073                cx.window_state.hovered_pressable_raw_below_barrier == Some(id);
2074            let pressed = cx.window_state.pressed_pressable == Some(id);
2075            let focused = cx.window_state.focused_element == Some(id);
2076            cx.pressable_clear_on_activate();
2077            cx.pressable_clear_on_clipboard_write_completed();
2078            cx.pressable_clear_on_hover_change();
2079            let built = f(
2080                cx,
2081                PressableState {
2082                    hovered,
2083                    hovered_raw,
2084                    hovered_raw_below_barrier,
2085                    pressed,
2086                    focused,
2087                },
2088            );
2089            let children = cx.collect_children(built);
2090            cx.new_any_element(id, ElementKind::Pressable(props), children)
2091        })
2092    }
2093
2094    #[track_caller]
2095    pub fn pressable_with_id<I>(
2096        &mut self,
2097        props: PressableProps,
2098        f: impl FnOnce(&mut Self, PressableState, GlobalElementId) -> I,
2099    ) -> AnyElement
2100    where
2101        I: IntoIterator<Item = AnyElement>,
2102    {
2103        self.scope(|cx| {
2104            let id = cx.root_id();
2105            let hovered = cx.window_state.hovered_pressable == Some(id);
2106            let hovered_raw = cx.window_state.hovered_pressable_raw == Some(id);
2107            let hovered_raw_below_barrier =
2108                cx.window_state.hovered_pressable_raw_below_barrier == Some(id);
2109            let pressed = cx.window_state.pressed_pressable == Some(id);
2110            let focused = cx.window_state.focused_element == Some(id);
2111            cx.pressable_clear_on_activate();
2112            cx.pressable_clear_on_clipboard_write_completed();
2113            cx.pressable_clear_on_hover_change();
2114            let built = f(
2115                cx,
2116                PressableState {
2117                    hovered,
2118                    hovered_raw,
2119                    hovered_raw_below_barrier,
2120                    pressed,
2121                    focused,
2122                },
2123                id,
2124            );
2125            let children = cx.collect_children(built);
2126            cx.new_any_element(id, ElementKind::Pressable(props), children)
2127        })
2128    }
2129
2130    #[track_caller]
2131    pub fn pressable_with_id_props<I>(
2132        &mut self,
2133        f: impl FnOnce(&mut Self, PressableState, GlobalElementId) -> (PressableProps, I),
2134    ) -> AnyElement
2135    where
2136        I: IntoIterator<Item = AnyElement>,
2137    {
2138        self.scope(|cx| {
2139            let id = cx.root_id();
2140            let hovered = cx.window_state.hovered_pressable == Some(id);
2141            let hovered_raw = cx.window_state.hovered_pressable_raw == Some(id);
2142            let hovered_raw_below_barrier =
2143                cx.window_state.hovered_pressable_raw_below_barrier == Some(id);
2144            let pressed = cx.window_state.pressed_pressable == Some(id);
2145            let focused = cx.window_state.focused_element == Some(id);
2146            cx.pressable_clear_on_activate();
2147            cx.pressable_clear_on_clipboard_write_completed();
2148            cx.pressable_clear_on_pointer_down();
2149            cx.pressable_clear_on_hover_change();
2150            let (props, children) = f(
2151                cx,
2152                PressableState {
2153                    hovered,
2154                    hovered_raw,
2155                    hovered_raw_below_barrier,
2156                    pressed,
2157                    focused,
2158                },
2159                id,
2160            );
2161            let children = cx.collect_children(children);
2162            cx.new_any_element(id, ElementKind::Pressable(props), children)
2163        })
2164    }
2165
2166    /// Register a component-owned activation handler for the current pressable element.
2167    ///
2168    /// This is a policy hook mechanism (ADR 0074): components decide what activation does (model
2169    /// writes, overlay requests, command dispatch), while the runtime remains mechanism-only.
2170    pub fn pressable_on_activate(&mut self, handler: OnActivate) {
2171        self.root_state(PressableActionHooks::default, |hooks| {
2172            hooks.on_activate = Some(handler);
2173        });
2174    }
2175
2176    pub fn pressable_on_activate_for(&mut self, element: GlobalElementId, handler: OnActivate) {
2177        self.state_for(element, PressableActionHooks::default, |hooks| {
2178            hooks.on_activate = Some(handler);
2179        });
2180    }
2181
2182    pub fn pressable_add_on_activate(&mut self, handler: OnActivate) {
2183        self.root_state(PressableActionHooks::default, |hooks| {
2184            hooks.on_activate = match hooks.on_activate.clone() {
2185                None => Some(handler),
2186                Some(prev) => {
2187                    let next = handler.clone();
2188                    Some(Arc::new(move |host, cx, reason| {
2189                        prev(host, cx, reason);
2190                        next(host, cx, reason);
2191                    }))
2192                }
2193            };
2194        });
2195    }
2196
2197    pub fn pressable_add_on_activate_for(&mut self, element: GlobalElementId, handler: OnActivate) {
2198        self.state_for(element, PressableActionHooks::default, |hooks| {
2199            hooks.on_activate = match hooks.on_activate.clone() {
2200                None => Some(handler),
2201                Some(prev) => {
2202                    let next = handler.clone();
2203                    Some(Arc::new(move |host, cx, reason| {
2204                        prev(host, cx, reason);
2205                        next(host, cx, reason);
2206                    }))
2207                }
2208            };
2209        });
2210    }
2211
2212    pub fn pressable_clear_on_activate(&mut self) {
2213        self.root_state(PressableActionHooks::default, |hooks| {
2214            hooks.on_activate = None;
2215        });
2216    }
2217
2218    /// Register a component-owned activation handler for interactive spans in the current
2219    /// selectable text element.
2220    ///
2221    /// This is a policy hook mechanism (ADR 0074): components decide what activation does
2222    /// (open URL, dispatch command, etc.), while the runtime remains mechanism-only.
2223    pub fn selectable_text_on_activate_span(&mut self, handler: OnSelectableTextActivateSpan) {
2224        self.root_state(SelectableTextActionHooks::default, |hooks| {
2225            hooks.on_activate_span = Some(handler);
2226        });
2227    }
2228
2229    pub fn selectable_text_on_activate_span_for(
2230        &mut self,
2231        element: GlobalElementId,
2232        handler: OnSelectableTextActivateSpan,
2233    ) {
2234        self.state_for(element, SelectableTextActionHooks::default, |hooks| {
2235            hooks.on_activate_span = Some(handler);
2236        });
2237    }
2238
2239    pub fn selectable_text_clear_on_activate_span(&mut self) {
2240        self.root_state(SelectableTextActionHooks::default, |hooks| {
2241            hooks.on_activate_span = None;
2242        });
2243    }
2244
2245    pub fn selectable_text_clear_on_activate_span_for(&mut self, element: GlobalElementId) {
2246        self.state_for(element, SelectableTextActionHooks::default, |hooks| {
2247            hooks.on_activate_span = None;
2248        });
2249    }
2250
2251    /// Register a component-owned pointer down handler for the current pressable element.
2252    ///
2253    /// This is a policy hook mechanism (ADR 0074): components can opt into Radix-style "select on
2254    /// mouse down" semantics without changing the default click-like activation behavior.
2255    pub fn pressable_on_pointer_down(&mut self, handler: OnPressablePointerDown) {
2256        self.root_state(PressableActionHooks::default, |hooks| {
2257            hooks.on_pointer_down = Some(handler);
2258        });
2259    }
2260
2261    pub fn pressable_on_pointer_move(&mut self, handler: OnPressablePointerMove) {
2262        self.root_state(PressableActionHooks::default, |hooks| {
2263            hooks.on_pointer_move = Some(handler);
2264        });
2265    }
2266
2267    pub fn pressable_on_pointer_up(&mut self, handler: OnPressablePointerUp) {
2268        self.root_state(PressableActionHooks::default, |hooks| {
2269            hooks.on_pointer_up = Some(handler);
2270        });
2271    }
2272
2273    pub fn pressable_on_clipboard_write_completed(
2274        &mut self,
2275        handler: crate::action::OnPressableClipboardWriteCompleted,
2276    ) {
2277        self.root_state(PressableActionHooks::default, |hooks| {
2278            hooks.on_clipboard_write_completed = Some(handler);
2279        });
2280    }
2281
2282    pub fn pressable_on_pointer_down_for(
2283        &mut self,
2284        element: GlobalElementId,
2285        handler: OnPressablePointerDown,
2286    ) {
2287        self.state_for(element, PressableActionHooks::default, |hooks| {
2288            hooks.on_pointer_down = Some(handler);
2289        });
2290    }
2291
2292    pub fn pressable_on_pointer_move_for(
2293        &mut self,
2294        element: GlobalElementId,
2295        handler: OnPressablePointerMove,
2296    ) {
2297        self.state_for(element, PressableActionHooks::default, |hooks| {
2298            hooks.on_pointer_move = Some(handler);
2299        });
2300    }
2301
2302    pub fn pressable_on_pointer_up_for(
2303        &mut self,
2304        element: GlobalElementId,
2305        handler: OnPressablePointerUp,
2306    ) {
2307        self.state_for(element, PressableActionHooks::default, |hooks| {
2308            hooks.on_pointer_up = Some(handler);
2309        });
2310    }
2311
2312    pub fn pressable_on_clipboard_write_completed_for(
2313        &mut self,
2314        element: GlobalElementId,
2315        handler: crate::action::OnPressableClipboardWriteCompleted,
2316    ) {
2317        self.state_for(element, PressableActionHooks::default, |hooks| {
2318            hooks.on_clipboard_write_completed = Some(handler);
2319        });
2320    }
2321
2322    pub fn pressable_add_on_pointer_down(&mut self, handler: OnPressablePointerDown) {
2323        self.root_state(PressableActionHooks::default, |hooks| {
2324            hooks.on_pointer_down = match hooks.on_pointer_down.clone() {
2325                None => Some(handler),
2326                Some(prev) => {
2327                    let next = handler.clone();
2328                    Some(Arc::new(move |host, cx, down| {
2329                        let prev_result = prev(host, cx, down);
2330                        let next_result = next(host, cx, down);
2331                        use crate::action::PressablePointerDownResult as R;
2332                        match (prev_result, next_result) {
2333                            (R::SkipDefaultAndStopPropagation, _)
2334                            | (_, R::SkipDefaultAndStopPropagation) => {
2335                                R::SkipDefaultAndStopPropagation
2336                            }
2337                            (R::SkipDefault, _) | (_, R::SkipDefault) => R::SkipDefault,
2338                            _ => R::Continue,
2339                        }
2340                    }))
2341                }
2342            };
2343        });
2344    }
2345
2346    pub fn pressable_add_on_pointer_move(&mut self, handler: OnPressablePointerMove) {
2347        self.root_state(PressableActionHooks::default, |hooks| {
2348            hooks.on_pointer_move = match hooks.on_pointer_move.clone() {
2349                None => Some(handler),
2350                Some(prev) => {
2351                    let next = handler.clone();
2352                    Some(Arc::new(move |host, cx, mv| {
2353                        let prev_handled = prev(host, cx, mv);
2354                        let next_handled = next(host, cx, mv);
2355                        prev_handled || next_handled
2356                    }))
2357                }
2358            };
2359        });
2360    }
2361
2362    pub fn pressable_add_on_pointer_up(&mut self, handler: OnPressablePointerUp) {
2363        self.root_state(PressableActionHooks::default, |hooks| {
2364            hooks.on_pointer_up = match hooks.on_pointer_up.clone() {
2365                None => Some(handler),
2366                Some(prev) => {
2367                    let next = handler.clone();
2368                    Some(Arc::new(move |host, cx, up| {
2369                        let prev_result = prev(host, cx, up);
2370                        let next_result = next(host, cx, up);
2371                        match (prev_result, next_result) {
2372                            (PressablePointerUpResult::SkipActivate, _)
2373                            | (_, PressablePointerUpResult::SkipActivate) => {
2374                                PressablePointerUpResult::SkipActivate
2375                            }
2376                            _ => PressablePointerUpResult::Continue,
2377                        }
2378                    }))
2379                }
2380            };
2381        });
2382    }
2383
2384    pub fn pressable_add_on_pointer_down_for(
2385        &mut self,
2386        element: GlobalElementId,
2387        handler: OnPressablePointerDown,
2388    ) {
2389        self.state_for(element, PressableActionHooks::default, |hooks| {
2390            hooks.on_pointer_down = match hooks.on_pointer_down.clone() {
2391                None => Some(handler),
2392                Some(prev) => {
2393                    let next = handler.clone();
2394                    Some(Arc::new(move |host, cx, down| {
2395                        let prev_result = prev(host, cx, down);
2396                        let next_result = next(host, cx, down);
2397                        use crate::action::PressablePointerDownResult as R;
2398                        match (prev_result, next_result) {
2399                            (R::SkipDefaultAndStopPropagation, _)
2400                            | (_, R::SkipDefaultAndStopPropagation) => {
2401                                R::SkipDefaultAndStopPropagation
2402                            }
2403                            (R::SkipDefault, _) | (_, R::SkipDefault) => R::SkipDefault,
2404                            _ => R::Continue,
2405                        }
2406                    }))
2407                }
2408            };
2409        });
2410    }
2411
2412    pub fn pressable_add_on_pointer_move_for(
2413        &mut self,
2414        element: GlobalElementId,
2415        handler: OnPressablePointerMove,
2416    ) {
2417        self.state_for(element, PressableActionHooks::default, |hooks| {
2418            hooks.on_pointer_move = match hooks.on_pointer_move.clone() {
2419                None => Some(handler),
2420                Some(prev) => {
2421                    let next = handler.clone();
2422                    Some(Arc::new(move |host, cx, mv| {
2423                        let prev_handled = prev(host, cx, mv);
2424                        let next_handled = next(host, cx, mv);
2425                        prev_handled || next_handled
2426                    }))
2427                }
2428            };
2429        });
2430    }
2431
2432    pub fn pressable_add_on_pointer_up_for(
2433        &mut self,
2434        element: GlobalElementId,
2435        handler: OnPressablePointerUp,
2436    ) {
2437        self.state_for(element, PressableActionHooks::default, |hooks| {
2438            hooks.on_pointer_up = match hooks.on_pointer_up.clone() {
2439                None => Some(handler),
2440                Some(prev) => {
2441                    let next = handler.clone();
2442                    Some(Arc::new(move |host, cx, up| {
2443                        let prev_result = prev(host, cx, up);
2444                        let next_result = next(host, cx, up);
2445                        match (prev_result, next_result) {
2446                            (PressablePointerUpResult::SkipActivate, _)
2447                            | (_, PressablePointerUpResult::SkipActivate) => {
2448                                PressablePointerUpResult::SkipActivate
2449                            }
2450                            _ => PressablePointerUpResult::Continue,
2451                        }
2452                    }))
2453                }
2454            };
2455        });
2456    }
2457
2458    pub fn pressable_clear_on_pointer_down(&mut self) {
2459        self.root_state(PressableActionHooks::default, |hooks| {
2460            hooks.on_pointer_down = None;
2461        });
2462    }
2463
2464    pub fn pressable_clear_on_pointer_move(&mut self) {
2465        self.root_state(PressableActionHooks::default, |hooks| {
2466            hooks.on_pointer_move = None;
2467        });
2468    }
2469
2470    pub fn pressable_clear_on_pointer_up(&mut self) {
2471        self.root_state(PressableActionHooks::default, |hooks| {
2472            hooks.on_pointer_up = None;
2473        });
2474    }
2475
2476    pub fn pressable_clear_on_clipboard_write_completed(&mut self) {
2477        self.root_state(PressableActionHooks::default, |hooks| {
2478            hooks.on_clipboard_write_completed = None;
2479        });
2480    }
2481
2482    /// Register a component-owned hover change handler for the current pressable element.
2483    ///
2484    /// This is a mechanism-only hook: the runtime tracks hover deterministically and invokes
2485    /// component code on hover transitions, without baking hover policy into `fret-ui`.
2486    pub fn pressable_on_hover_change(&mut self, handler: OnHoverChange) {
2487        self.root_state(PressableHoverActionHooks::default, |hooks| {
2488            hooks.on_hover_change = Some(handler);
2489        });
2490    }
2491
2492    pub fn pressable_add_on_hover_change(&mut self, handler: OnHoverChange) {
2493        self.root_state(PressableHoverActionHooks::default, |hooks| {
2494            hooks.on_hover_change = match hooks.on_hover_change.clone() {
2495                None => Some(handler),
2496                Some(prev) => {
2497                    let next = handler.clone();
2498                    Some(Arc::new(move |host, cx, hovered| {
2499                        prev(host, cx, hovered);
2500                        next(host, cx, hovered);
2501                    }))
2502                }
2503            };
2504        });
2505    }
2506
2507    pub fn pressable_clear_on_hover_change(&mut self) {
2508        self.root_state(PressableHoverActionHooks::default, |hooks| {
2509            hooks.on_hover_change = None;
2510        });
2511    }
2512
2513    #[track_caller]
2514    pub fn pointer_region<I>(
2515        &mut self,
2516        props: PointerRegionProps,
2517        f: impl FnOnce(&mut Self) -> I,
2518    ) -> AnyElement
2519    where
2520        I: IntoIterator<Item = AnyElement>,
2521    {
2522        self.scope(|cx| {
2523            let id = cx.root_id();
2524            cx.pointer_region_clear_on_pointer_down();
2525            cx.pointer_region_clear_on_pointer_move();
2526            cx.pointer_region_clear_on_pointer_up();
2527            cx.pointer_region_clear_on_wheel();
2528            cx.pointer_region_clear_on_pinch_gesture();
2529            let built = f(cx);
2530            let children = cx.collect_children(built);
2531            cx.new_any_element(id, ElementKind::PointerRegion(props), children)
2532        })
2533    }
2534
2535    #[track_caller]
2536    pub fn text_input_region<I>(
2537        &mut self,
2538        props: crate::element::TextInputRegionProps,
2539        f: impl FnOnce(&mut Self) -> I,
2540    ) -> AnyElement
2541    where
2542        I: IntoIterator<Item = AnyElement>,
2543    {
2544        self.scope(|cx| {
2545            let id = cx.root_id();
2546            cx.text_input_region_clear_on_text_input();
2547            cx.text_input_region_clear_on_ime();
2548            cx.text_input_region_clear_on_clipboard_read_text();
2549            cx.text_input_region_clear_on_clipboard_read_failed();
2550            cx.text_input_region_clear_on_set_selection();
2551            let built = f(cx);
2552            let children = cx.collect_children(built);
2553            cx.new_any_element(id, ElementKind::TextInputRegion(props), children)
2554        })
2555    }
2556
2557    pub fn internal_drag_region<I>(
2558        &mut self,
2559        props: crate::element::InternalDragRegionProps,
2560        f: impl FnOnce(&mut Self) -> I,
2561    ) -> AnyElement
2562    where
2563        I: IntoIterator<Item = AnyElement>,
2564    {
2565        self.scope(|cx| {
2566            let id = cx.root_id();
2567            cx.internal_drag_region_clear_on_internal_drag();
2568            let built = f(cx);
2569            let children = cx.collect_children(built);
2570            cx.new_any_element(id, ElementKind::InternalDragRegion(props), children)
2571        })
2572    }
2573
2574    pub fn internal_drag_region_on_internal_drag(
2575        &mut self,
2576        handler: crate::action::OnInternalDrag,
2577    ) {
2578        self.root_state(crate::action::InternalDragActionHooks::default, |hooks| {
2579            hooks.on_internal_drag = Some(handler);
2580        });
2581    }
2582
2583    pub fn internal_drag_region_clear_on_internal_drag(&mut self) {
2584        self.root_state(crate::action::InternalDragActionHooks::default, |hooks| {
2585            hooks.on_internal_drag = None;
2586        });
2587    }
2588
2589    pub fn external_drag_region<I>(
2590        &mut self,
2591        props: crate::element::ExternalDragRegionProps,
2592        f: impl FnOnce(&mut Self) -> I,
2593    ) -> AnyElement
2594    where
2595        I: IntoIterator<Item = AnyElement>,
2596    {
2597        self.scope(|cx| {
2598            let id = cx.root_id();
2599            cx.external_drag_region_clear_on_external_drag();
2600            let built = f(cx);
2601            let children = cx.collect_children(built);
2602            cx.new_any_element(id, ElementKind::ExternalDragRegion(props), children)
2603        })
2604    }
2605
2606    pub fn external_drag_region_on_external_drag(
2607        &mut self,
2608        handler: crate::action::OnExternalDrag,
2609    ) {
2610        self.root_state(crate::action::ExternalDragActionHooks::default, |hooks| {
2611            hooks.on_external_drag = Some(handler);
2612        });
2613    }
2614
2615    pub fn external_drag_region_clear_on_external_drag(&mut self) {
2616        self.root_state(crate::action::ExternalDragActionHooks::default, |hooks| {
2617            hooks.on_external_drag = None;
2618        });
2619    }
2620
2621    /// Register a component-owned pointer down handler for the current pointer region element.
2622    ///
2623    /// This is a mechanism-only hook point: components decide what a pointer down does (open a
2624    /// context menu, start a drag, request focus, etc.), while the runtime remains policy-free.
2625    pub fn pointer_region_on_pointer_down(&mut self, handler: OnPointerDown) {
2626        self.root_state(PointerActionHooks::default, |hooks| {
2627            hooks.on_pointer_down = Some(handler);
2628        });
2629    }
2630
2631    pub fn pointer_region_add_on_pointer_down(&mut self, handler: OnPointerDown) {
2632        self.root_state(PointerActionHooks::default, |hooks| {
2633            hooks.on_pointer_down = match hooks.on_pointer_down.clone() {
2634                None => Some(handler),
2635                Some(prev) => {
2636                    let next = handler.clone();
2637                    Some(Arc::new(move |host, cx, down| {
2638                        prev(host, cx, down) || next(host, cx, down)
2639                    }))
2640                }
2641            };
2642        });
2643    }
2644
2645    pub fn pointer_region_clear_on_pointer_down(&mut self) {
2646        self.root_state(PointerActionHooks::default, |hooks| {
2647            hooks.on_pointer_down = None;
2648        });
2649    }
2650
2651    /// Register a component-owned pointer move handler for the current pointer region element.
2652    ///
2653    /// This hook is invoked when the pointer region receives `PointerEvent::Move` events via
2654    /// normal hit-testing or pointer capture.
2655    pub fn pointer_region_on_pointer_move(&mut self, handler: OnPointerMove) {
2656        self.root_state(PointerActionHooks::default, |hooks| {
2657            hooks.on_pointer_move = Some(handler);
2658        });
2659    }
2660
2661    pub fn pointer_region_add_on_pointer_move(&mut self, handler: OnPointerMove) {
2662        self.root_state(PointerActionHooks::default, |hooks| {
2663            hooks.on_pointer_move = match hooks.on_pointer_move.clone() {
2664                None => Some(handler),
2665                Some(prev) => {
2666                    let next = handler.clone();
2667                    Some(Arc::new(move |host, cx, mv| {
2668                        prev(host, cx, mv) || next(host, cx, mv)
2669                    }))
2670                }
2671            };
2672        });
2673    }
2674
2675    pub fn pointer_region_clear_on_pointer_move(&mut self) {
2676        self.root_state(PointerActionHooks::default, |hooks| {
2677            hooks.on_pointer_move = None;
2678        });
2679    }
2680
2681    /// Register a component-owned pointer up handler for the current pointer region element.
2682    ///
2683    /// This hook is invoked when the pointer region receives `PointerEvent::Up` events via
2684    /// normal hit-testing or pointer capture.
2685    pub fn pointer_region_on_pointer_up(&mut self, handler: OnPointerUp) {
2686        self.root_state(PointerActionHooks::default, |hooks| {
2687            hooks.on_pointer_up = Some(handler);
2688        });
2689    }
2690
2691    /// Register a component-owned pointer cancel handler for the current pointer region element.
2692    ///
2693    /// This hook is invoked when the runtime receives `Event::PointerCancel` for a pointer stream
2694    /// that was previously interacting with this region (typically via pointer capture).
2695    pub fn pointer_region_on_pointer_cancel(&mut self, handler: OnPointerCancel) {
2696        self.root_state(PointerActionHooks::default, |hooks| {
2697            hooks.on_pointer_cancel = Some(handler);
2698        });
2699    }
2700
2701    pub fn pointer_region_on_wheel(&mut self, handler: OnWheel) {
2702        self.root_state(PointerActionHooks::default, |hooks| {
2703            hooks.on_wheel = Some(handler);
2704        });
2705    }
2706
2707    pub fn pointer_region_on_pinch_gesture(&mut self, handler: OnPinchGesture) {
2708        self.root_state(PointerActionHooks::default, |hooks| {
2709            hooks.on_pinch_gesture = Some(handler);
2710        });
2711    }
2712
2713    pub fn pointer_region_add_on_pointer_up(&mut self, handler: OnPointerUp) {
2714        self.root_state(PointerActionHooks::default, |hooks| {
2715            hooks.on_pointer_up = match hooks.on_pointer_up.clone() {
2716                None => Some(handler),
2717                Some(prev) => {
2718                    let next = handler.clone();
2719                    Some(Arc::new(move |host, cx, up| {
2720                        prev(host, cx, up) || next(host, cx, up)
2721                    }))
2722                }
2723            };
2724        });
2725    }
2726
2727    pub fn pointer_region_add_on_wheel(&mut self, handler: OnWheel) {
2728        self.root_state(PointerActionHooks::default, |hooks| {
2729            hooks.on_wheel = match hooks.on_wheel.clone() {
2730                None => Some(handler),
2731                Some(prev) => {
2732                    let next = handler.clone();
2733                    Some(Arc::new(move |host, cx, wheel| {
2734                        prev(host, cx, wheel) || next(host, cx, wheel)
2735                    }))
2736                }
2737            };
2738        });
2739    }
2740
2741    pub fn pointer_region_add_on_pinch_gesture(&mut self, handler: OnPinchGesture) {
2742        self.root_state(PointerActionHooks::default, |hooks| {
2743            hooks.on_pinch_gesture = match hooks.on_pinch_gesture.clone() {
2744                None => Some(handler),
2745                Some(prev) => {
2746                    let next = handler.clone();
2747                    Some(Arc::new(move |host, cx, pinch| {
2748                        prev(host, cx, pinch) || next(host, cx, pinch)
2749                    }))
2750                }
2751            };
2752        });
2753    }
2754
2755    pub fn pointer_region_clear_on_pointer_up(&mut self) {
2756        self.root_state(PointerActionHooks::default, |hooks| {
2757            hooks.on_pointer_up = None;
2758        });
2759    }
2760
2761    pub fn pointer_region_clear_on_wheel(&mut self) {
2762        self.root_state(PointerActionHooks::default, |hooks| {
2763            hooks.on_wheel = None;
2764        });
2765    }
2766
2767    pub fn pointer_region_clear_on_pinch_gesture(&mut self) {
2768        self.root_state(PointerActionHooks::default, |hooks| {
2769            hooks.on_pinch_gesture = None;
2770        });
2771    }
2772
2773    pub fn text_input_region_on_text_input(
2774        &mut self,
2775        handler: crate::action::OnTextInputRegionTextInput,
2776    ) {
2777        self.root_state(
2778            crate::action::TextInputRegionActionHooks::default,
2779            |hooks| {
2780                hooks.on_text_input = Some(handler);
2781            },
2782        );
2783    }
2784
2785    pub fn text_input_region_clear_on_text_input(&mut self) {
2786        self.root_state(
2787            crate::action::TextInputRegionActionHooks::default,
2788            |hooks| {
2789                hooks.on_text_input = None;
2790            },
2791        );
2792    }
2793
2794    pub fn text_input_region_on_ime(&mut self, handler: crate::action::OnTextInputRegionIme) {
2795        self.root_state(
2796            crate::action::TextInputRegionActionHooks::default,
2797            |hooks| {
2798                hooks.on_ime = Some(handler);
2799            },
2800        );
2801    }
2802
2803    pub fn text_input_region_clear_on_ime(&mut self) {
2804        self.root_state(
2805            crate::action::TextInputRegionActionHooks::default,
2806            |hooks| {
2807                hooks.on_ime = None;
2808            },
2809        );
2810    }
2811
2812    pub fn text_input_region_on_clipboard_read_text(
2813        &mut self,
2814        handler: crate::action::OnTextInputRegionClipboardReadText,
2815    ) {
2816        self.root_state(
2817            crate::action::TextInputRegionActionHooks::default,
2818            |hooks| {
2819                hooks.on_clipboard_read_text = Some(handler);
2820            },
2821        );
2822    }
2823
2824    pub fn text_input_region_clear_on_clipboard_read_text(&mut self) {
2825        self.root_state(
2826            crate::action::TextInputRegionActionHooks::default,
2827            |hooks| {
2828                hooks.on_clipboard_read_text = None;
2829            },
2830        );
2831    }
2832
2833    pub fn text_input_region_on_clipboard_read_failed(
2834        &mut self,
2835        handler: crate::action::OnTextInputRegionClipboardReadFailed,
2836    ) {
2837        self.root_state(
2838            crate::action::TextInputRegionActionHooks::default,
2839            |hooks| {
2840                hooks.on_clipboard_read_failed = Some(handler);
2841            },
2842        );
2843    }
2844
2845    pub fn text_input_region_clear_on_clipboard_read_failed(&mut self) {
2846        self.root_state(
2847            crate::action::TextInputRegionActionHooks::default,
2848            |hooks| {
2849                hooks.on_clipboard_read_failed = None;
2850            },
2851        );
2852    }
2853
2854    pub fn text_input_region_on_set_selection(
2855        &mut self,
2856        handler: crate::action::OnTextInputRegionSetSelection,
2857    ) {
2858        self.root_state(
2859            crate::action::TextInputRegionActionHooks::default,
2860            |hooks| {
2861                hooks.on_set_selection = Some(handler);
2862            },
2863        );
2864    }
2865
2866    pub fn text_input_region_clear_on_set_selection(&mut self) {
2867        self.root_state(
2868            crate::action::TextInputRegionActionHooks::default,
2869            |hooks| {
2870                hooks.on_set_selection = None;
2871            },
2872        );
2873    }
2874
2875    pub fn text_input_region_on_platform_text_input_query(
2876        &mut self,
2877        handler: crate::action::OnTextInputRegionPlatformTextInputQuery,
2878    ) {
2879        self.root_state(
2880            crate::action::TextInputRegionActionHooks::default,
2881            |hooks| {
2882                hooks.on_platform_text_input_query = Some(handler);
2883            },
2884        );
2885    }
2886
2887    pub fn text_input_region_clear_on_platform_text_input_query(&mut self) {
2888        self.root_state(
2889            crate::action::TextInputRegionActionHooks::default,
2890            |hooks| {
2891                hooks.on_platform_text_input_query = None;
2892            },
2893        );
2894    }
2895
2896    pub fn text_input_region_on_platform_text_input_replace_text_in_range_utf16(
2897        &mut self,
2898        handler: crate::action::OnTextInputRegionPlatformTextInputReplaceTextInRangeUtf16,
2899    ) {
2900        self.root_state(
2901            crate::action::TextInputRegionActionHooks::default,
2902            |hooks| {
2903                hooks.on_platform_text_input_replace_text_in_range_utf16 = Some(handler);
2904            },
2905        );
2906    }
2907
2908    pub fn text_input_region_clear_on_platform_text_input_replace_text_in_range_utf16(&mut self) {
2909        self.root_state(
2910            crate::action::TextInputRegionActionHooks::default,
2911            |hooks| {
2912                hooks.on_platform_text_input_replace_text_in_range_utf16 = None;
2913            },
2914        );
2915    }
2916
2917    pub fn text_input_region_on_platform_text_input_replace_and_mark_text_in_range_utf16(
2918        &mut self,
2919        handler: crate::action::OnTextInputRegionPlatformTextInputReplaceAndMarkTextInRangeUtf16,
2920    ) {
2921        self.root_state(
2922            crate::action::TextInputRegionActionHooks::default,
2923            |hooks| {
2924                hooks.on_platform_text_input_replace_and_mark_text_in_range_utf16 = Some(handler);
2925            },
2926        );
2927    }
2928
2929    pub fn text_input_region_clear_on_platform_text_input_replace_and_mark_text_in_range_utf16(
2930        &mut self,
2931    ) {
2932        self.root_state(
2933            crate::action::TextInputRegionActionHooks::default,
2934            |hooks| {
2935                hooks.on_platform_text_input_replace_and_mark_text_in_range_utf16 = None;
2936            },
2937        );
2938    }
2939
2940    pub fn key_on_key_down_for(&mut self, element: GlobalElementId, handler: OnKeyDown) {
2941        self.state_for(element, KeyActionHooks::default, |hooks| {
2942            hooks.on_key_down = Some(handler);
2943        });
2944    }
2945
2946    pub fn key_on_key_down_focused_for(&mut self, element: GlobalElementId, handler: OnKeyDown) {
2947        self.state_for(element, KeyActionHooks::default, |hooks| {
2948            hooks.on_key_down_focused = Some(handler);
2949        });
2950    }
2951
2952    pub fn key_add_on_key_down_for(&mut self, element: GlobalElementId, handler: OnKeyDown) {
2953        self.state_for(element, KeyActionHooks::default, |hooks| {
2954            hooks.on_key_down = match hooks.on_key_down.clone() {
2955                None => Some(handler),
2956                Some(prev) => {
2957                    let next = handler.clone();
2958                    Some(Arc::new(move |host, cx, down| {
2959                        prev(host, cx, down) || next(host, cx, down)
2960                    }))
2961                }
2962            };
2963        });
2964    }
2965
2966    pub fn key_prepend_on_key_down_for(&mut self, element: GlobalElementId, handler: OnKeyDown) {
2967        self.state_for(element, KeyActionHooks::default, |hooks| {
2968            hooks.on_key_down = match hooks.on_key_down.clone() {
2969                None => Some(handler),
2970                Some(prev) => {
2971                    let next = handler.clone();
2972                    Some(Arc::new(move |host, cx, down| {
2973                        next(host, cx, down) || prev(host, cx, down)
2974                    }))
2975                }
2976            };
2977        });
2978    }
2979
2980    pub fn key_clear_on_key_down_for(&mut self, element: GlobalElementId) {
2981        self.state_for(element, KeyActionHooks::default, |hooks| {
2982            hooks.on_key_down = None;
2983        });
2984    }
2985
2986    pub fn key_clear_on_key_down_focused_for(&mut self, element: GlobalElementId) {
2987        self.state_for(element, KeyActionHooks::default, |hooks| {
2988            hooks.on_key_down_focused = None;
2989        });
2990    }
2991
2992    pub fn key_on_key_down_capture_for(&mut self, element: GlobalElementId, handler: OnKeyDown) {
2993        self.state_for(element, KeyActionHooks::default, |hooks| {
2994            hooks.on_key_down_capture = Some(handler);
2995        });
2996    }
2997
2998    pub fn key_add_on_key_down_capture_for(
2999        &mut self,
3000        element: GlobalElementId,
3001        handler: OnKeyDown,
3002    ) {
3003        self.state_for(element, KeyActionHooks::default, |hooks| {
3004            hooks.on_key_down_capture = match hooks.on_key_down_capture.clone() {
3005                None => Some(handler),
3006                Some(prev) => {
3007                    let next = handler.clone();
3008                    Some(Arc::new(move |host, cx, down| {
3009                        prev(host, cx, down) || next(host, cx, down)
3010                    }))
3011                }
3012            };
3013        });
3014    }
3015
3016    pub fn key_prepend_on_key_down_capture_for(
3017        &mut self,
3018        element: GlobalElementId,
3019        handler: OnKeyDown,
3020    ) {
3021        self.state_for(element, KeyActionHooks::default, |hooks| {
3022            hooks.on_key_down_capture = match hooks.on_key_down_capture.clone() {
3023                None => Some(handler),
3024                Some(prev) => {
3025                    let next = handler.clone();
3026                    Some(Arc::new(move |host, cx, down| {
3027                        next(host, cx, down) || prev(host, cx, down)
3028                    }))
3029                }
3030            };
3031        });
3032    }
3033
3034    pub fn key_clear_on_key_down_capture_for(&mut self, element: GlobalElementId) {
3035        self.state_for(element, KeyActionHooks::default, |hooks| {
3036            hooks.on_key_down_capture = None;
3037        });
3038    }
3039
3040    pub fn command_on_command_for(&mut self, element: GlobalElementId, handler: OnCommand) {
3041        self.state_for(element, CommandActionHooks::default, |hooks| {
3042            hooks.on_command = Some(handler);
3043        });
3044    }
3045
3046    pub fn action_on_command_for_owner<Owner: Any>(
3047        &mut self,
3048        element: GlobalElementId,
3049        handler: OnCommand,
3050    ) {
3051        let owner = TypeId::of::<Owner>();
3052        self.state_for(element, ActionRouteHooks::default, |hooks| {
3053            hooks.set_on_command(owner, handler);
3054        });
3055    }
3056
3057    pub fn action_add_on_command_for_owner<Owner: Any>(
3058        &mut self,
3059        element: GlobalElementId,
3060        handler: OnCommand,
3061    ) {
3062        let owner = TypeId::of::<Owner>();
3063        self.state_for(element, ActionRouteHooks::default, |hooks| {
3064            hooks.add_on_command(owner, handler);
3065        });
3066    }
3067
3068    pub fn action_clear_on_command_for_owner<Owner: Any>(&mut self, element: GlobalElementId) {
3069        let owner = TypeId::of::<Owner>();
3070        self.state_for(element, ActionRouteHooks::default, |hooks| {
3071            hooks.clear_on_command(owner);
3072        });
3073    }
3074
3075    pub fn command_add_on_command_for(&mut self, element: GlobalElementId, handler: OnCommand) {
3076        self.state_for(element, CommandActionHooks::default, |hooks| {
3077            hooks.on_command = match hooks.on_command.clone() {
3078                None => Some(handler),
3079                Some(prev) => {
3080                    let next = handler.clone();
3081                    Some(Arc::new(move |host, cx, command| {
3082                        prev(host, cx, command.clone()) || next(host, cx, command)
3083                    }))
3084                }
3085            };
3086        });
3087    }
3088
3089    pub fn command_prepend_on_command_for(&mut self, element: GlobalElementId, handler: OnCommand) {
3090        self.state_for(element, CommandActionHooks::default, |hooks| {
3091            hooks.on_command = match hooks.on_command.clone() {
3092                None => Some(handler),
3093                Some(prev) => {
3094                    let next = handler.clone();
3095                    Some(Arc::new(move |host, cx, command| {
3096                        next(host, cx, command.clone()) || prev(host, cx, command)
3097                    }))
3098                }
3099            };
3100        });
3101    }
3102
3103    pub fn command_clear_on_command_for(&mut self, element: GlobalElementId) {
3104        self.state_for(element, CommandActionHooks::default, |hooks| {
3105            hooks.on_command = None;
3106        });
3107    }
3108
3109    pub fn command_on_command_availability_for(
3110        &mut self,
3111        element: GlobalElementId,
3112        handler: OnCommandAvailability,
3113    ) {
3114        self.state_for(element, CommandAvailabilityActionHooks::default, |hooks| {
3115            hooks.on_command_availability = Some(handler);
3116        });
3117    }
3118
3119    pub fn action_on_command_availability_for_owner<Owner: Any>(
3120        &mut self,
3121        element: GlobalElementId,
3122        handler: OnCommandAvailability,
3123    ) {
3124        let owner = TypeId::of::<Owner>();
3125        self.state_for(element, ActionRouteHooks::default, |hooks| {
3126            hooks.set_on_command_availability(owner, handler);
3127        });
3128    }
3129
3130    pub fn action_add_on_command_availability_for_owner<Owner: Any>(
3131        &mut self,
3132        element: GlobalElementId,
3133        handler: OnCommandAvailability,
3134    ) {
3135        let owner = TypeId::of::<Owner>();
3136        self.state_for(element, ActionRouteHooks::default, |hooks| {
3137            hooks.add_on_command_availability(owner, handler);
3138        });
3139    }
3140
3141    pub fn action_clear_on_command_availability_for_owner<Owner: Any>(
3142        &mut self,
3143        element: GlobalElementId,
3144    ) {
3145        let owner = TypeId::of::<Owner>();
3146        self.state_for(element, ActionRouteHooks::default, |hooks| {
3147            hooks.clear_on_command_availability(owner);
3148        });
3149    }
3150
3151    pub fn command_add_on_command_availability_for(
3152        &mut self,
3153        element: GlobalElementId,
3154        handler: OnCommandAvailability,
3155    ) {
3156        self.state_for(element, CommandAvailabilityActionHooks::default, |hooks| {
3157            hooks.on_command_availability = match hooks.on_command_availability.clone() {
3158                None => Some(handler),
3159                Some(prev) => {
3160                    let next = handler.clone();
3161                    Some(Arc::new(move |host, cx, command| {
3162                        let availability = prev(host, cx.clone(), command.clone());
3163                        if availability != crate::widget::CommandAvailability::NotHandled {
3164                            return availability;
3165                        }
3166                        next(host, cx, command)
3167                    }))
3168                }
3169            };
3170        });
3171    }
3172
3173    pub fn command_prepend_on_command_availability_for(
3174        &mut self,
3175        element: GlobalElementId,
3176        handler: OnCommandAvailability,
3177    ) {
3178        self.state_for(element, CommandAvailabilityActionHooks::default, |hooks| {
3179            hooks.on_command_availability = match hooks.on_command_availability.clone() {
3180                None => Some(handler),
3181                Some(prev) => {
3182                    let next = handler.clone();
3183                    Some(Arc::new(move |host, cx, command| {
3184                        let availability = next(host, cx.clone(), command.clone());
3185                        if availability != crate::widget::CommandAvailability::NotHandled {
3186                            return availability;
3187                        }
3188                        prev(host, cx, command)
3189                    }))
3190                }
3191            };
3192        });
3193    }
3194
3195    pub fn command_clear_on_command_availability_for(&mut self, element: GlobalElementId) {
3196        self.state_for(element, CommandAvailabilityActionHooks::default, |hooks| {
3197            hooks.on_command_availability = None;
3198        });
3199    }
3200
3201    pub fn timer_on_timer_for(&mut self, element: GlobalElementId, handler: OnTimer) {
3202        self.state_for(element, TimerActionHooks::default, |hooks| {
3203            hooks.on_timer = Some(handler);
3204        });
3205    }
3206
3207    pub fn timer_add_on_timer_for(&mut self, element: GlobalElementId, handler: OnTimer) {
3208        self.state_for(element, TimerActionHooks::default, |hooks| {
3209            hooks.on_timer = match hooks.on_timer.clone() {
3210                None => Some(handler),
3211                Some(prev) => {
3212                    let next = handler.clone();
3213                    Some(Arc::new(move |host, cx, token| {
3214                        prev(host, cx, token) || next(host, cx, token)
3215                    }))
3216                }
3217            };
3218        });
3219    }
3220
3221    pub fn timer_clear_on_timer_for(&mut self, element: GlobalElementId) {
3222        self.state_for(element, TimerActionHooks::default, |hooks| {
3223            hooks.on_timer = None;
3224        });
3225    }
3226
3227    /// Register a component-owned dismiss handler for the current dismissible root element.
3228    ///
3229    /// This is intended for overlay policy code that composes
3230    /// `render_dismissible_root_with_hooks(...)` and
3231    /// wants full control over dismissal semantics (ADR 0074).
3232    pub fn dismissible_on_dismiss_request(&mut self, handler: OnDismissRequest) {
3233        self.root_state(DismissibleActionHooks::default, |hooks| {
3234            hooks.on_dismiss_request = Some(handler);
3235        });
3236    }
3237
3238    /// Register a component-owned pointer-move observer for the current dismissible root element.
3239    ///
3240    /// This is used for overlay policies that need global pointer movement (e.g. submenu
3241    /// safe-hover corridors) without making the overlay hit-testable outside its content.
3242    pub fn dismissible_on_pointer_move(&mut self, handler: OnDismissiblePointerMove) {
3243        self.root_state(DismissibleActionHooks::default, |hooks| {
3244            hooks.on_pointer_move = Some(handler);
3245        });
3246    }
3247
3248    pub fn dismissible_add_on_dismiss_request(&mut self, handler: OnDismissRequest) {
3249        self.root_state(DismissibleActionHooks::default, |hooks| {
3250            hooks.on_dismiss_request = match hooks.on_dismiss_request.clone() {
3251                None => Some(handler),
3252                Some(prev) => {
3253                    let next = handler.clone();
3254                    Some(Arc::new(move |host, cx, req| {
3255                        prev(host, cx, req);
3256                        next(host, cx, req);
3257                    }))
3258                }
3259            };
3260        });
3261    }
3262
3263    pub fn dismissible_add_on_pointer_move(&mut self, handler: OnDismissiblePointerMove) {
3264        self.root_state(DismissibleActionHooks::default, |hooks| {
3265            hooks.on_pointer_move = match hooks.on_pointer_move.clone() {
3266                None => Some(handler),
3267                Some(prev) => {
3268                    let next = handler.clone();
3269                    Some(Arc::new(move |host, cx, mv| {
3270                        prev(host, cx, mv) || next(host, cx, mv)
3271                    }))
3272                }
3273            };
3274        });
3275    }
3276
3277    pub fn dismissible_clear_on_dismiss_request(&mut self) {
3278        self.root_state(DismissibleActionHooks::default, |hooks| {
3279            hooks.on_dismiss_request = None;
3280        });
3281    }
3282
3283    pub fn dismissible_clear_on_pointer_move(&mut self) {
3284        self.root_state(DismissibleActionHooks::default, |hooks| {
3285            hooks.on_pointer_move = None;
3286        });
3287    }
3288
3289    /// Register a component-owned roving active-change handler for the current roving element.
3290    ///
3291    /// This hook is invoked when the roving container changes focus among its children due to
3292    /// keyboard navigation (arrow keys, Home/End, or typeahead).
3293    ///
3294    /// Components can implement “automatic activation” (e.g. Tabs) by updating selection models
3295    /// here, keeping selection policy out of the runtime (ADR 0074).
3296    pub fn roving_on_active_change(&mut self, handler: OnRovingActiveChange) {
3297        self.root_state(RovingActionHooks::default, |hooks| {
3298            hooks.on_active_change = Some(handler);
3299        });
3300    }
3301
3302    pub fn roving_add_on_active_change(&mut self, handler: OnRovingActiveChange) {
3303        self.root_state(RovingActionHooks::default, |hooks| {
3304            hooks.on_active_change = match hooks.on_active_change.clone() {
3305                None => Some(handler),
3306                Some(prev) => {
3307                    let next = handler.clone();
3308                    Some(Arc::new(move |host, cx, idx| {
3309                        prev(host, cx, idx);
3310                        next(host, cx, idx);
3311                    }))
3312                }
3313            };
3314        });
3315    }
3316
3317    pub fn roving_clear_on_active_change(&mut self) {
3318        self.root_state(RovingActionHooks::default, |hooks| {
3319            hooks.on_active_change = None;
3320        });
3321    }
3322
3323    /// Register a component-owned roving typeahead handler for the current roving element.
3324    ///
3325    /// When set, the runtime forwards alphanumeric key presses to this handler so components can
3326    /// decide how typeahead should work (buffering, prefix matching, wrapping, etc.).
3327    pub fn roving_on_typeahead(&mut self, handler: OnRovingTypeahead) {
3328        self.root_state(RovingActionHooks::default, |hooks| {
3329            hooks.on_typeahead = Some(handler);
3330        });
3331    }
3332
3333    pub fn roving_add_on_typeahead(&mut self, handler: OnRovingTypeahead) {
3334        self.root_state(RovingActionHooks::default, |hooks| {
3335            hooks.on_typeahead = match hooks.on_typeahead.clone() {
3336                None => Some(handler),
3337                Some(prev) => {
3338                    let next = handler.clone();
3339                    Some(Arc::new(move |host, cx, it| {
3340                        prev(host, cx, it.clone()).or_else(|| next(host, cx, it))
3341                    }))
3342                }
3343            };
3344        });
3345    }
3346
3347    pub fn roving_clear_on_typeahead(&mut self) {
3348        self.root_state(RovingActionHooks::default, |hooks| {
3349            hooks.on_typeahead = None;
3350        });
3351    }
3352
3353    pub fn roving_on_key_down(&mut self, handler: OnKeyDown) {
3354        self.root_state(RovingActionHooks::default, |hooks| {
3355            hooks.on_key_down.clear();
3356            hooks.on_key_down.push(handler);
3357        });
3358    }
3359
3360    pub fn roving_add_on_key_down(&mut self, handler: OnKeyDown) {
3361        self.root_state(RovingActionHooks::default, |hooks| {
3362            hooks.on_key_down.push(handler);
3363        });
3364    }
3365
3366    pub fn roving_clear_on_key_down(&mut self) {
3367        self.root_state(RovingActionHooks::default, |hooks| {
3368            hooks.on_key_down.clear();
3369        });
3370    }
3371
3372    /// Register a component-owned roving navigation handler for the current roving element.
3373    ///
3374    /// This is invoked for key down events that bubble through the roving container so component
3375    /// code can decide which child should become focused (arrow keys, Home/End, etc.).
3376    pub fn roving_on_navigate(&mut self, handler: OnRovingNavigate) {
3377        self.root_state(RovingActionHooks::default, |hooks| {
3378            hooks.on_navigate = Some(handler);
3379        });
3380    }
3381
3382    pub fn roving_add_on_navigate(&mut self, handler: OnRovingNavigate) {
3383        self.root_state(RovingActionHooks::default, |hooks| {
3384            hooks.on_navigate = match hooks.on_navigate.clone() {
3385                None => Some(handler),
3386                Some(prev) => {
3387                    let next = handler.clone();
3388                    Some(Arc::new(move |host, cx, it| {
3389                        match prev(host, cx, it.clone()) {
3390                            crate::action::RovingNavigateResult::NotHandled => next(host, cx, it),
3391                            other => other,
3392                        }
3393                    }))
3394                }
3395            };
3396        });
3397    }
3398
3399    pub fn roving_clear_on_navigate(&mut self) {
3400        self.root_state(RovingActionHooks::default, |hooks| {
3401            hooks.on_navigate = None;
3402        });
3403    }
3404
3405    #[track_caller]
3406    pub fn stack<I>(&mut self, f: impl FnOnce(&mut Self) -> I) -> AnyElement
3407    where
3408        I: IntoIterator<Item = AnyElement>,
3409    {
3410        self.stack_props(StackProps::default(), f)
3411    }
3412
3413    #[track_caller]
3414    pub fn stack_props<I>(
3415        &mut self,
3416        props: StackProps,
3417        f: impl FnOnce(&mut Self) -> I,
3418    ) -> AnyElement
3419    where
3420        I: IntoIterator<Item = AnyElement>,
3421    {
3422        self.scope(|cx| {
3423            let id = cx.root_id();
3424            let built = f(cx);
3425            let children = cx.collect_children(built);
3426            cx.new_any_element(id, ElementKind::Stack(props), children)
3427        })
3428    }
3429
3430    #[track_caller]
3431    pub fn column<I>(&mut self, props: ColumnProps, f: impl FnOnce(&mut Self) -> I) -> AnyElement
3432    where
3433        I: IntoIterator<Item = AnyElement>,
3434    {
3435        self.scope(|cx| {
3436            let id = cx.root_id();
3437            let built = f(cx);
3438            let children = cx.collect_children(built);
3439            cx.new_any_element(id, ElementKind::Column(props), children)
3440        })
3441    }
3442
3443    #[track_caller]
3444    pub fn row<I>(&mut self, props: RowProps, f: impl FnOnce(&mut Self) -> I) -> AnyElement
3445    where
3446        I: IntoIterator<Item = AnyElement>,
3447    {
3448        self.scope(|cx| {
3449            let id = cx.root_id();
3450            let built = f(cx);
3451            let children = cx.collect_children(built);
3452            cx.new_any_element(id, ElementKind::Row(props), children)
3453        })
3454    }
3455
3456    #[track_caller]
3457    pub fn spacer(&mut self, props: SpacerProps) -> AnyElement {
3458        self.scope(|cx| {
3459            let id = cx.root_id();
3460            cx.new_any_element(id, ElementKind::Spacer(props), Vec::new())
3461        })
3462    }
3463
3464    #[track_caller]
3465    pub fn text(&mut self, text: impl Into<std::sync::Arc<str>>) -> AnyElement {
3466        self.scope(|cx| {
3467            let id = cx.root_id();
3468            cx.new_any_element(id, ElementKind::Text(TextProps::new(text)), Vec::new())
3469        })
3470    }
3471
3472    #[track_caller]
3473    pub fn text_props(&mut self, props: TextProps) -> AnyElement {
3474        self.scope(|cx| {
3475            let id = cx.root_id();
3476            cx.new_any_element(id, ElementKind::Text(props), Vec::new())
3477        })
3478    }
3479
3480    #[track_caller]
3481    pub fn styled_text(&mut self, rich: fret_core::AttributedText) -> AnyElement {
3482        self.scope(|cx| {
3483            let id = cx.root_id();
3484            cx.new_any_element(
3485                id,
3486                ElementKind::StyledText(StyledTextProps::new(rich)),
3487                Vec::new(),
3488            )
3489        })
3490    }
3491
3492    #[track_caller]
3493    pub fn styled_text_props(&mut self, props: StyledTextProps) -> AnyElement {
3494        self.scope(|cx| {
3495            let id = cx.root_id();
3496            cx.new_any_element(id, ElementKind::StyledText(props), Vec::new())
3497        })
3498    }
3499
3500    #[track_caller]
3501    pub fn selectable_text(&mut self, rich: fret_core::AttributedText) -> AnyElement {
3502        self.scope(|cx| {
3503            let id = cx.root_id();
3504            cx.selectable_text_clear_on_activate_span();
3505            cx.new_any_element(
3506                id,
3507                ElementKind::SelectableText(SelectableTextProps::new(rich)),
3508                Vec::new(),
3509            )
3510        })
3511    }
3512
3513    #[track_caller]
3514    pub fn selectable_text_with_id_props(
3515        &mut self,
3516        f: impl FnOnce(&mut Self, GlobalElementId) -> SelectableTextProps,
3517    ) -> AnyElement {
3518        self.scope(|cx| {
3519            let id = cx.root_id();
3520            cx.selectable_text_clear_on_activate_span();
3521            let props = f(cx, id);
3522            cx.new_any_element(id, ElementKind::SelectableText(props), Vec::new())
3523        })
3524    }
3525
3526    #[track_caller]
3527    pub fn selectable_text_props(&mut self, props: SelectableTextProps) -> AnyElement {
3528        self.scope(|cx| {
3529            let id = cx.root_id();
3530            cx.selectable_text_clear_on_activate_span();
3531            cx.new_any_element(id, ElementKind::SelectableText(props), Vec::new())
3532        })
3533    }
3534
3535    #[track_caller]
3536    pub fn text_input(&mut self, props: TextInputProps) -> AnyElement {
3537        self.scope(|cx| {
3538            let id = cx.root_id();
3539            cx.new_any_element(id, ElementKind::TextInput(props), Vec::new())
3540        })
3541    }
3542
3543    #[track_caller]
3544    pub fn text_input_with_id_props(
3545        &mut self,
3546        f: impl FnOnce(&mut Self, GlobalElementId) -> TextInputProps,
3547    ) -> AnyElement {
3548        self.scope(|cx| {
3549            let id = cx.root_id();
3550            let props = f(cx, id);
3551            cx.new_any_element(id, ElementKind::TextInput(props), Vec::new())
3552        })
3553    }
3554
3555    #[track_caller]
3556    pub fn text_area(&mut self, props: TextAreaProps) -> AnyElement {
3557        self.scope(|cx| {
3558            let id = cx.root_id();
3559            cx.new_any_element(id, ElementKind::TextArea(props), Vec::new())
3560        })
3561    }
3562
3563    #[track_caller]
3564    pub fn text_area_with_id_props(
3565        &mut self,
3566        f: impl FnOnce(&mut Self, GlobalElementId) -> TextAreaProps,
3567    ) -> AnyElement {
3568        self.scope(|cx| {
3569            let id = cx.root_id();
3570            let props = f(cx, id);
3571            cx.new_any_element(id, ElementKind::TextArea(props), Vec::new())
3572        })
3573    }
3574
3575    #[track_caller]
3576    pub fn resizable_panel_group<I>(
3577        &mut self,
3578        props: ResizablePanelGroupProps,
3579        f: impl FnOnce(&mut Self) -> I,
3580    ) -> AnyElement
3581    where
3582        I: IntoIterator<Item = AnyElement>,
3583    {
3584        self.scope(|cx| {
3585            let id = cx.root_id();
3586            let built = f(cx);
3587            let children = cx.collect_children(built);
3588            cx.new_any_element(id, ElementKind::ResizablePanelGroup(props), children)
3589        })
3590    }
3591
3592    #[track_caller]
3593    pub fn image(&mut self, image: fret_core::ImageId) -> AnyElement {
3594        self.scope(|cx| {
3595            let id = cx.root_id();
3596            cx.new_any_element(id, ElementKind::Image(ImageProps::new(image)), Vec::new())
3597        })
3598    }
3599
3600    #[track_caller]
3601    pub fn image_props(&mut self, props: ImageProps) -> AnyElement {
3602        self.scope(|cx| {
3603            let id = cx.root_id();
3604            cx.new_any_element(id, ElementKind::Image(props), Vec::new())
3605        })
3606    }
3607
3608    #[track_caller]
3609    pub fn canvas(
3610        &mut self,
3611        props: CanvasProps,
3612        paint: impl for<'p> Fn(&mut CanvasPainter<'p>) + 'static,
3613    ) -> AnyElement {
3614        let on_paint: OnCanvasPaint = Arc::new(paint);
3615        self.scope(|cx| {
3616            let id = cx.root_id();
3617            cx.state_for(id, CanvasPaintHooks::default, |hooks| {
3618                hooks.on_paint = Some(on_paint.clone());
3619            });
3620            cx.new_any_element(id, ElementKind::Canvas(props), Vec::new())
3621        })
3622    }
3623
3624    #[cfg(feature = "unstable-retained-bridge")]
3625    #[track_caller]
3626    pub fn retained_subtree(
3627        &mut self,
3628        props: crate::retained_bridge::RetainedSubtreeProps,
3629    ) -> AnyElement {
3630        self.scope(|cx| {
3631            let id = cx.root_id();
3632            cx.new_any_element(id, ElementKind::RetainedSubtree(props), Vec::new())
3633        })
3634    }
3635
3636    #[track_caller]
3637    pub fn viewport_surface(&mut self, target: fret_core::RenderTargetId) -> AnyElement {
3638        self.viewport_surface_props(ViewportSurfaceProps::new(target))
3639    }
3640
3641    #[track_caller]
3642    pub fn viewport_surface_mapped(
3643        &mut self,
3644        target: fret_core::RenderTargetId,
3645        target_px_size: (u32, u32),
3646        fit: fret_core::ViewportFit,
3647    ) -> AnyElement {
3648        self.viewport_surface_props(ViewportSurfaceProps {
3649            target_px_size,
3650            fit,
3651            ..ViewportSurfaceProps::new(target)
3652        })
3653    }
3654
3655    #[track_caller]
3656    pub fn viewport_surface_props(&mut self, props: ViewportSurfaceProps) -> AnyElement {
3657        self.scope(|cx| {
3658            let id = cx.root_id();
3659            cx.new_any_element(id, ElementKind::ViewportSurface(props), Vec::new())
3660        })
3661    }
3662
3663    #[track_caller]
3664    pub fn svg_icon(&mut self, svg: SvgSource) -> AnyElement {
3665        self.svg_icon_props(SvgIconProps::new(svg))
3666    }
3667
3668    #[track_caller]
3669    pub fn svg_icon_props(&mut self, props: SvgIconProps) -> AnyElement {
3670        self.scope(|cx| {
3671            let id = cx.root_id();
3672            cx.new_any_element(id, ElementKind::SvgIcon(props), Vec::new())
3673        })
3674    }
3675
3676    #[track_caller]
3677    pub fn spinner(&mut self) -> AnyElement {
3678        self.spinner_props(SpinnerProps::default())
3679    }
3680
3681    #[track_caller]
3682    pub fn spinner_props(&mut self, props: SpinnerProps) -> AnyElement {
3683        self.scope(|cx| {
3684            let id = cx.root_id();
3685            cx.new_any_element(id, ElementKind::Spinner(props), Vec::new())
3686        })
3687    }
3688
3689    #[track_caller]
3690    pub fn hover_region<I>(
3691        &mut self,
3692        props: HoverRegionProps,
3693        f: impl FnOnce(&mut Self, bool) -> I,
3694    ) -> AnyElement
3695    where
3696        I: IntoIterator<Item = AnyElement>,
3697    {
3698        self.scope(|cx| {
3699            let id = cx.root_id();
3700            let hovered = cx.window_state.hovered_hover_region == Some(id);
3701            let built = f(cx, hovered);
3702            let children = cx.collect_children(built);
3703            cx.new_any_element(id, ElementKind::HoverRegion(props), children)
3704        })
3705    }
3706
3707    #[track_caller]
3708    pub fn wheel_region<I>(
3709        &mut self,
3710        props: crate::element::WheelRegionProps,
3711        f: impl FnOnce(&mut Self) -> I,
3712    ) -> AnyElement
3713    where
3714        I: IntoIterator<Item = AnyElement>,
3715    {
3716        self.scope(|cx| {
3717            let id = cx.root_id();
3718            let built = f(cx);
3719            let children = cx.collect_children(built);
3720            cx.new_any_element(id, ElementKind::WheelRegion(props), children)
3721        })
3722    }
3723
3724    #[track_caller]
3725    pub fn scroll<I>(&mut self, props: ScrollProps, f: impl FnOnce(&mut Self) -> I) -> AnyElement
3726    where
3727        I: IntoIterator<Item = AnyElement>,
3728    {
3729        self.scope(|cx| {
3730            let id = cx.root_id();
3731            let built = f(cx);
3732            let children = cx.collect_children(built);
3733            cx.new_any_element(id, ElementKind::Scroll(props), children)
3734        })
3735    }
3736
3737    #[track_caller]
3738    pub fn scrollbar(&mut self, props: ScrollbarProps) -> AnyElement {
3739        self.scope(|cx| {
3740            let id = cx.root_id();
3741            cx.new_any_element(id, ElementKind::Scrollbar(props), Vec::new())
3742        })
3743    }
3744
3745    #[track_caller]
3746    pub fn virtual_list<F, I>(
3747        &mut self,
3748        len: usize,
3749        options: VirtualListOptions,
3750        scroll_handle: &crate::scroll::VirtualListScrollHandle,
3751        f: F,
3752    ) -> AnyElement
3753    where
3754        F: FnOnce(&mut Self, &[crate::virtual_list::VirtualItem]) -> I,
3755        I: IntoIterator<Item = AnyElement>,
3756    {
3757        self.virtual_list_with_layout(LayoutStyle::default(), len, options, scroll_handle, f)
3758    }
3759
3760    #[track_caller]
3761    pub fn virtual_list_with_range_extractor<F, I>(
3762        &mut self,
3763        len: usize,
3764        options: VirtualListOptions,
3765        scroll_handle: &crate::scroll::VirtualListScrollHandle,
3766        range_extractor: impl FnOnce(crate::virtual_list::VirtualRange) -> Vec<usize>,
3767        f: F,
3768    ) -> AnyElement
3769    where
3770        F: FnOnce(&mut Self, &[crate::virtual_list::VirtualItem]) -> I,
3771        I: IntoIterator<Item = AnyElement>,
3772    {
3773        self.virtual_list_with_layout_and_range_extractor(
3774            LayoutStyle::default(),
3775            len,
3776            options,
3777            scroll_handle,
3778            range_extractor,
3779            f,
3780        )
3781    }
3782
3783    #[track_caller]
3784    pub fn virtual_list_with_layout<F, I>(
3785        &mut self,
3786        layout: LayoutStyle,
3787        len: usize,
3788        options: VirtualListOptions,
3789        scroll_handle: &crate::scroll::VirtualListScrollHandle,
3790        f: F,
3791    ) -> AnyElement
3792    where
3793        F: FnOnce(&mut Self, &[crate::virtual_list::VirtualItem]) -> I,
3794        I: IntoIterator<Item = AnyElement>,
3795    {
3796        self.virtual_list_with_layout_and_range_extractor(
3797            layout,
3798            len,
3799            options,
3800            scroll_handle,
3801            crate::virtual_list::default_range_extractor,
3802            f,
3803        )
3804    }
3805
3806    #[track_caller]
3807    pub fn virtual_list_with_layout_and_range_extractor<F, I>(
3808        &mut self,
3809        layout: LayoutStyle,
3810        len: usize,
3811        options: VirtualListOptions,
3812        scroll_handle: &crate::scroll::VirtualListScrollHandle,
3813        range_extractor: impl FnOnce(crate::virtual_list::VirtualRange) -> Vec<usize>,
3814        f: F,
3815    ) -> AnyElement
3816    where
3817        F: FnOnce(&mut Self, &[crate::virtual_list::VirtualItem]) -> I,
3818        I: IntoIterator<Item = AnyElement>,
3819    {
3820        self.virtual_list_with_layout_and_keys(
3821            layout,
3822            len,
3823            options,
3824            scroll_handle,
3825            |i| i as crate::ItemKey,
3826            range_extractor,
3827            f,
3828        )
3829    }
3830
3831    #[allow(clippy::too_many_arguments)]
3832    fn virtual_list_with_layout_and_keys<F, I>(
3833        &mut self,
3834        layout: LayoutStyle,
3835        len: usize,
3836        options: VirtualListOptions,
3837        scroll_handle: &crate::scroll::VirtualListScrollHandle,
3838        mut item_key_at: impl FnMut(usize) -> crate::ItemKey,
3839        range_extractor: impl FnOnce(crate::virtual_list::VirtualRange) -> Vec<usize>,
3840        f: F,
3841    ) -> AnyElement
3842    where
3843        F: FnOnce(&mut Self, &[crate::virtual_list::VirtualItem]) -> I,
3844        I: IntoIterator<Item = AnyElement>,
3845    {
3846        self.scope(|cx| {
3847            let id = cx.root_id();
3848
3849            let scroll_handle = scroll_handle.clone();
3850            scroll_handle.set_items_count(len);
3851
3852            let key_cache = match options.measure_mode {
3853                crate::element::VirtualListMeasureMode::Measured => {
3854                    crate::element::VirtualListKeyCacheMode::AllKeys
3855                }
3856                crate::element::VirtualListMeasureMode::Fixed => options.key_cache,
3857                crate::element::VirtualListMeasureMode::Known => options.key_cache,
3858            };
3859
3860            let range = cx.root_state(VirtualListState::default, |state| {
3861                let axis = options.axis;
3862                let (viewport, offset) = match axis {
3863                    fret_core::Axis::Vertical => (state.viewport_h, state.offset_y),
3864                    fret_core::Axis::Horizontal => (state.viewport_w, state.offset_x),
3865                };
3866
3867                // For large scroll jumps, rendering the full overscan window can produce a
3868                // one-frame layout spike (we're attaching and solving many item roots at once).
3869                //
3870                // Prefer rendering the true visible window for the jump frame and let overscan
3871                // catch up on subsequent frames.
3872                let mut overscan_for_range = if scroll_handle.deferred_scroll_to_item().is_some() {
3873                    0
3874                } else {
3875                    options.overscan
3876                };
3877
3878                let prev_anchor = if viewport.0 > 0.0 && len > 0 {
3879                    state.metrics.visible_range(offset, viewport, 0).map(|r| {
3880                        let idx = r.start_index;
3881                        let key = if idx >= len {
3882                            idx as crate::ItemKey
3883                        } else {
3884                            match key_cache {
3885                                crate::element::VirtualListKeyCacheMode::AllKeys => state
3886                                    .keys
3887                                    .get(idx)
3888                                    .copied()
3889                                    .unwrap_or_else(|| item_key_at(idx)),
3890                                crate::element::VirtualListKeyCacheMode::VisibleOnly => {
3891                                    item_key_at(idx)
3892                                }
3893                            }
3894                        };
3895                        let start = state.metrics.offset_for_index(idx);
3896                        let offset_in_viewport = Px((offset.0 - start.0).max(0.0));
3897                        (key, offset_in_viewport)
3898                    })
3899                } else {
3900                    None
3901                };
3902
3903                let prev_cfg = (
3904                    state.metrics.estimate(),
3905                    state.metrics.gap(),
3906                    state.metrics.scroll_margin(),
3907                );
3908                let cfg = (
3909                    options.estimate_row_height,
3910                    options.gap,
3911                    options.scroll_margin,
3912                );
3913
3914                state.metrics.ensure_with_mode(
3915                    options.measure_mode,
3916                    len,
3917                    options.estimate_row_height,
3918                    options.gap,
3919                    options.scroll_margin,
3920                );
3921
3922                let needs_rebuild = state.items_revision != options.items_revision
3923                    || state.items_len != len
3924                    || state.key_cache != key_cache
3925                    || prev_cfg != cfg;
3926
3927                if needs_rebuild {
3928                    state.items_revision = options.items_revision;
3929                    state.items_len = len;
3930                    state.key_cache = key_cache;
3931
3932                    match key_cache {
3933                        crate::element::VirtualListKeyCacheMode::AllKeys => {
3934                            state.keys.clear();
3935                            state.keys.reserve(len);
3936
3937                            for i in 0..len {
3938                                let key = item_key_at(i);
3939                                state.keys.push(key);
3940                            }
3941                        }
3942                        crate::element::VirtualListKeyCacheMode::VisibleOnly => {
3943                            state.keys.clear();
3944                        }
3945                    }
3946
3947                    state.metrics.sync_keys(&state.keys, options.items_revision);
3948
3949                    if options.measure_mode == crate::element::VirtualListMeasureMode::Known
3950                        && let Some(height_at) = options.known_row_height_at.as_ref()
3951                    {
3952                        let heights = (0..len).map(|i| height_at(i)).collect::<Vec<_>>();
3953                        state.metrics.rebuild_from_known_heights(
3954                            heights,
3955                            options.estimate_row_height,
3956                            options.gap,
3957                            options.scroll_margin,
3958                        );
3959                    }
3960
3961                    if key_cache == crate::element::VirtualListKeyCacheMode::AllKeys {
3962                        let has_deferred_scroll = scroll_handle.deferred_scroll_to_item().is_some();
3963                        if !has_deferred_scroll
3964                            && let Some((key, offset_in_viewport)) = prev_anchor
3965                            && let Some(index) = state.keys.iter().position(|&k| k == key)
3966                        {
3967                            let start = state.metrics.offset_for_index(index);
3968                            let desired = Px(start.0 + offset_in_viewport.0);
3969                            let prev = scroll_handle.offset();
3970                            let clamped = state.metrics.clamp_offset(desired, viewport);
3971                            match axis {
3972                                fret_core::Axis::Vertical => {
3973                                    scroll_handle
3974                                        .set_offset(fret_core::Point::new(prev.x, clamped));
3975                                    state.offset_y = clamped;
3976                                }
3977                                fret_core::Axis::Horizontal => {
3978                                    scroll_handle
3979                                        .set_offset(fret_core::Point::new(clamped, prev.y));
3980                                    state.offset_x = clamped;
3981                                }
3982                            }
3983                        }
3984                    }
3985                }
3986
3987                let viewport = Px(viewport.0.max(0.0));
3988                let offset = state.metrics.clamp_offset(offset, viewport);
3989
3990                state.deferred_scroll_offset_hint = None;
3991
3992                let mut range = state.render_window_range.filter(|r| {
3993                    r.count == len && r.overscan == options.overscan && r.start_index <= r.end_index
3994                });
3995                if range.is_none() {
3996                    range = state.window_range.filter(|r| {
3997                        r.count == len
3998                            && r.overscan == options.overscan
3999                            && r.start_index <= r.end_index
4000                    });
4001                }
4002
4003                // When a scroll handle offset changes out-of-band (wheel, inertial scroll, or a
4004                // component-driven `set_offset`), the handle's current offset may lead the
4005                // element-local `state.offset_*` which is only updated during layout.
4006                //
4007                // If we are rerendering this frame, compute the visible range against the latest
4008                // scroll handle offset so "window jump" frames can rebuild the correct visible
4009                // items without requiring a follow-up rerender.
4010                let mut preview_offset = offset;
4011                if state.has_final_viewport && viewport.0 > 0.0 && len > 0 {
4012                    let handle_offset = scroll_handle.offset();
4013                    let handle_axis = match axis {
4014                        fret_core::Axis::Vertical => handle_offset.y,
4015                        fret_core::Axis::Horizontal => handle_offset.x,
4016                    };
4017                    let handle_axis = state.metrics.clamp_offset(handle_axis, viewport);
4018                    if (handle_axis.0 - offset.0).abs() > 0.01 {
4019                        // If the handle jumps far outside the previously committed visible range,
4020                        // skip overscan for this render so we don't attach/solve a large window of
4021                        // off-screen items in a single frame.
4022                        if overscan_for_range > 0 {
4023                            let prev_visible = state.metrics.visible_range(offset, viewport, 0);
4024                            let next_visible =
4025                                state.metrics.visible_range(handle_axis, viewport, 0);
4026                            let large_jump = match (prev_visible, next_visible) {
4027                                (Some(prev), Some(next)) => {
4028                                    let prev_len = prev
4029                                        .end_index
4030                                        .saturating_sub(prev.start_index)
4031                                        .saturating_add(1);
4032                                    let threshold = prev_len
4033                                        .saturating_mul(4)
4034                                        .max(options.overscan.saturating_mul(8));
4035                                    next.start_index.abs_diff(prev.start_index) > threshold
4036                                }
4037                                _ => {
4038                                    let delta_px = (handle_axis.0 - offset.0).abs();
4039                                    delta_px > (viewport.0 * 3.0)
4040                                }
4041                            };
4042
4043                            if large_jump {
4044                                overscan_for_range = 0;
4045                                range = None;
4046                            }
4047                        }
4048                        preview_offset = handle_axis;
4049                    }
4050                }
4051
4052                // Preview deferred scroll-to-item requests during render so we compute the correct
4053                // visible range without consuming the request. The final layout pass will apply
4054                // the scroll offset and clear the request.
4055                if state.has_final_viewport
4056                    && viewport.0 > 0.0
4057                    && len > 0
4058                    && let Some((index, strategy)) = scroll_handle.deferred_scroll_to_item()
4059                {
4060                    let desired = state
4061                        .metrics
4062                        .scroll_offset_for_item(index, viewport, offset, strategy);
4063                    let desired = state.metrics.clamp_offset(desired, viewport);
4064                    preview_offset = desired;
4065                    state.deferred_scroll_offset_hint = Some(desired);
4066                    range = state
4067                        .metrics
4068                        .visible_range(desired, viewport, overscan_for_range);
4069                }
4070
4071                if state.has_final_viewport && viewport.0 > 0.0 && len > 0 {
4072                    let visible = state.metrics.visible_range(preview_offset, viewport, 0);
4073                    if let (Some(prev), Some(visible)) = (range, visible) {
4074                        let prev_visible_len = prev
4075                            .end_index
4076                            .saturating_sub(prev.start_index)
4077                            .saturating_add(1);
4078                        let visible_len = visible
4079                            .end_index
4080                            .saturating_sub(visible.start_index)
4081                            .saturating_add(1);
4082
4083                        let win_start = prev.start_index.saturating_sub(prev.overscan);
4084                        let win_end =
4085                            (prev.end_index + prev.overscan).min(prev.count.saturating_sub(1));
4086                        let out_of_window =
4087                            visible.start_index < win_start || visible.end_index > win_end;
4088
4089                        // If the viewport grows (e.g. after intrinsic probes settle), the stored
4090                        // render-derived window may under-estimate the visible span while still
4091                        // appearing "within overscan". Force a one-shot recompute so we don't get
4092                        // stuck in a too-small window forever under view-cache reuse.
4093                        if visible_len > prev_visible_len || out_of_window {
4094                            range = state.metrics.visible_range(
4095                                preview_offset,
4096                                viewport,
4097                                overscan_for_range,
4098                            );
4099                        }
4100                    } else if range.is_none() {
4101                        range = state.metrics.visible_range(
4102                            preview_offset,
4103                            viewport,
4104                            overscan_for_range,
4105                        );
4106                    }
4107                } else if range.is_none() {
4108                    range = state
4109                        .metrics
4110                        .visible_range(offset, viewport, overscan_for_range);
4111                }
4112
4113                state.render_window_range = range;
4114                range
4115            });
4116
4117            let mut indices = range
4118                .map(range_extractor)
4119                .unwrap_or_default()
4120                .into_iter()
4121                .filter(|&idx| idx < len)
4122                .collect::<Vec<_>>();
4123            indices.sort_unstable();
4124            indices.dedup();
4125
4126            let visible_items = cx.root_state(VirtualListState::default, |state| {
4127                indices
4128                    .iter()
4129                    .map(|&idx| {
4130                        let key = if idx >= len {
4131                            idx as crate::ItemKey
4132                        } else {
4133                            match key_cache {
4134                                crate::element::VirtualListKeyCacheMode::AllKeys => state
4135                                    .keys
4136                                    .get(idx)
4137                                    .copied()
4138                                    .unwrap_or_else(|| item_key_at(idx)),
4139                                crate::element::VirtualListKeyCacheMode::VisibleOnly => {
4140                                    item_key_at(idx)
4141                                }
4142                            }
4143                        };
4144                        state.metrics.virtual_item(idx, key)
4145                    })
4146                    .collect::<Vec<_>>()
4147            });
4148
4149            let built = f(cx, &visible_items);
4150            let children = cx.collect_children(built);
4151            cx.new_any_element(
4152                id,
4153                ElementKind::VirtualList(VirtualListProps {
4154                    layout,
4155                    axis: options.axis,
4156                    len,
4157                    items_revision: options.items_revision,
4158                    estimate_row_height: options.estimate_row_height,
4159                    measure_mode: options.measure_mode,
4160                    key_cache,
4161                    overscan: options.overscan,
4162                    keep_alive: options.keep_alive,
4163                    scroll_margin: options.scroll_margin,
4164                    gap: options.gap,
4165                    scroll_handle,
4166                    visible_items,
4167                }),
4168                children,
4169            )
4170        })
4171    }
4172
4173    #[track_caller]
4174    pub fn flex<I>(&mut self, props: FlexProps, f: impl FnOnce(&mut Self) -> I) -> AnyElement
4175    where
4176        I: IntoIterator<Item = AnyElement>,
4177    {
4178        self.scope(|cx| {
4179            let id = cx.root_id();
4180            let built = f(cx);
4181            let children = cx.collect_children(built);
4182            cx.new_any_element(id, ElementKind::Flex(props), children)
4183        })
4184    }
4185
4186    #[track_caller]
4187    pub fn roving_flex<I>(
4188        &mut self,
4189        props: crate::element::RovingFlexProps,
4190        f: impl FnOnce(&mut Self) -> I,
4191    ) -> AnyElement
4192    where
4193        I: IntoIterator<Item = AnyElement>,
4194    {
4195        self.scope(|cx| {
4196            let id = cx.root_id();
4197            cx.roving_clear_on_active_change();
4198            cx.roving_clear_on_typeahead();
4199            cx.roving_clear_on_key_down();
4200            cx.roving_clear_on_navigate();
4201            let built = f(cx);
4202            let children = cx.collect_children(built);
4203            cx.new_any_element(id, ElementKind::RovingFlex(props), children)
4204        })
4205    }
4206
4207    #[track_caller]
4208    pub fn grid<I>(&mut self, props: GridProps, f: impl FnOnce(&mut Self) -> I) -> AnyElement
4209    where
4210        I: IntoIterator<Item = AnyElement>,
4211    {
4212        self.scope(|cx| {
4213            let id = cx.root_id();
4214            let built = f(cx);
4215            let children = cx.collect_children(built);
4216            cx.new_any_element(id, ElementKind::Grid(props), children)
4217        })
4218    }
4219
4220    /// Virtualized list helper that enforces stable element identity by entering a keyed scope
4221    /// for each visible row.
4222    ///
4223    /// Prefer this over index-identity list rendering for any dynamic collection that can reorder,
4224    /// so element-local state (caret/selection/scroll) does not “stick to positions”.
4225    #[track_caller]
4226    pub fn virtual_list_keyed(
4227        &mut self,
4228        len: usize,
4229        options: VirtualListOptions,
4230        scroll_handle: &crate::scroll::VirtualListScrollHandle,
4231        key_at: impl FnMut(usize) -> crate::ItemKey,
4232        row: impl FnMut(&mut Self, usize) -> AnyElement,
4233    ) -> AnyElement {
4234        self.virtual_list_keyed_with_layout(
4235            LayoutStyle::default(),
4236            len,
4237            options,
4238            scroll_handle,
4239            key_at,
4240            row,
4241        )
4242    }
4243
4244    /// Retained-host VirtualList helper (ADR 0192).
4245    ///
4246    /// This is an opt-in surface that stores `'static` row callbacks in element-local state so
4247    /// the runtime can attach/detach row subtrees when a cache root reuses without rerendering.
4248    #[track_caller]
4249    pub fn virtual_list_keyed_retained(
4250        &mut self,
4251        len: usize,
4252        options: VirtualListOptions,
4253        scroll_handle: &crate::scroll::VirtualListScrollHandle,
4254        key_at: crate::windowed_surface_host::RetainedVirtualListKeyAtFn,
4255        row: crate::windowed_surface_host::RetainedVirtualListRowFn<H>,
4256    ) -> AnyElement
4257    where
4258        H: 'static,
4259    {
4260        self.virtual_list_keyed_retained_with_layout(
4261            LayoutStyle::default(),
4262            len,
4263            options,
4264            scroll_handle,
4265            key_at,
4266            row,
4267        )
4268    }
4269
4270    #[track_caller]
4271    pub fn virtual_list_keyed_retained_fn(
4272        &mut self,
4273        len: usize,
4274        options: VirtualListOptions,
4275        scroll_handle: &crate::scroll::VirtualListScrollHandle,
4276        key_at: impl Fn(usize) -> crate::ItemKey + 'static,
4277        row: impl for<'b> Fn(&mut ElementContext<'b, H>, usize) -> AnyElement + 'static,
4278    ) -> AnyElement
4279    where
4280        H: 'static,
4281    {
4282        self.virtual_list_keyed_retained_with_layout_fn(
4283            LayoutStyle::default(),
4284            len,
4285            options,
4286            scroll_handle,
4287            key_at,
4288            row,
4289        )
4290    }
4291
4292    #[track_caller]
4293    pub fn virtual_list_keyed_retained_with_layout(
4294        &mut self,
4295        layout: LayoutStyle,
4296        len: usize,
4297        options: VirtualListOptions,
4298        scroll_handle: &crate::scroll::VirtualListScrollHandle,
4299        key_at: crate::windowed_surface_host::RetainedVirtualListKeyAtFn,
4300        row: crate::windowed_surface_host::RetainedVirtualListRowFn<H>,
4301    ) -> AnyElement
4302    where
4303        H: 'static,
4304    {
4305        self.virtual_list_keyed_retained_with_layout_and_range_extractor(
4306            layout,
4307            len,
4308            options,
4309            scroll_handle,
4310            key_at,
4311            crate::virtual_list::default_range_extractor,
4312            row,
4313        )
4314    }
4315
4316    #[track_caller]
4317    pub fn virtual_list_keyed_retained_with_layout_fn(
4318        &mut self,
4319        layout: LayoutStyle,
4320        len: usize,
4321        options: VirtualListOptions,
4322        scroll_handle: &crate::scroll::VirtualListScrollHandle,
4323        key_at: impl Fn(usize) -> crate::ItemKey + 'static,
4324        row: impl for<'b> Fn(&mut ElementContext<'b, H>, usize) -> AnyElement + 'static,
4325    ) -> AnyElement
4326    where
4327        H: 'static,
4328    {
4329        let key_at: crate::windowed_surface_host::RetainedVirtualListKeyAtFn = Arc::new(key_at);
4330        let row: crate::windowed_surface_host::RetainedVirtualListRowFn<H> = Arc::new(row);
4331        self.virtual_list_keyed_retained_with_layout(
4332            layout,
4333            len,
4334            options,
4335            scroll_handle,
4336            key_at,
4337            row,
4338        )
4339    }
4340
4341    #[track_caller]
4342    #[allow(clippy::too_many_arguments)]
4343    pub fn virtual_list_keyed_retained_with_layout_and_range_extractor(
4344        &mut self,
4345        layout: LayoutStyle,
4346        len: usize,
4347        options: VirtualListOptions,
4348        scroll_handle: &crate::scroll::VirtualListScrollHandle,
4349        key_at: crate::windowed_surface_host::RetainedVirtualListKeyAtFn,
4350        range_extractor: crate::windowed_surface_host::RetainedVirtualListRangeExtractor,
4351        row: crate::windowed_surface_host::RetainedVirtualListRowFn<H>,
4352    ) -> AnyElement
4353    where
4354        H: 'static,
4355    {
4356        let key_at_for_keys = Arc::clone(&key_at);
4357        self.virtual_list_with_layout_and_keys(
4358            layout,
4359            len,
4360            options,
4361            scroll_handle,
4362            move |i| (key_at_for_keys)(i),
4363            range_extractor,
4364            move |cx, items| {
4365                cx.root_state(
4366                    crate::windowed_surface_host::RetainedVirtualListHostMarker::default,
4367                    |_| {},
4368                );
4369                // Keep the retained keep-alive bucket's element-local state alive across frames
4370                // (including view-cache hits) so window shifts can actually reuse previously
4371                // mounted item subtrees. The actual budget is controlled by `VirtualListProps`.
4372                cx.root_state(
4373                    crate::windowed_surface_host::RetainedVirtualListKeepAliveState::default,
4374                    |_| {},
4375                );
4376                cx.root_state(
4377                    || crate::windowed_surface_host::RetainedVirtualListHostCallbacks::<H> {
4378                        key_at: Arc::clone(&key_at),
4379                        row: Arc::clone(&row),
4380                        range_extractor,
4381                    },
4382                    |st| {
4383                        st.key_at = Arc::clone(&key_at);
4384                        st.row = Arc::clone(&row);
4385                        st.range_extractor = range_extractor;
4386                    },
4387                );
4388
4389                items
4390                    .iter()
4391                    .copied()
4392                    .map(|item| {
4393                        cx.retained_virtual_list_row_any_element(item.key, item.index, &row)
4394                    })
4395                    .collect::<Vec<_>>()
4396            },
4397        )
4398    }
4399
4400    #[track_caller]
4401    #[allow(clippy::too_many_arguments)]
4402    pub fn virtual_list_keyed_retained_with_layout_and_range_extractor_fn(
4403        &mut self,
4404        layout: LayoutStyle,
4405        len: usize,
4406        options: VirtualListOptions,
4407        scroll_handle: &crate::scroll::VirtualListScrollHandle,
4408        key_at: impl Fn(usize) -> crate::ItemKey + 'static,
4409        range_extractor: crate::windowed_surface_host::RetainedVirtualListRangeExtractor,
4410        row: impl for<'b> Fn(&mut ElementContext<'b, H>, usize) -> AnyElement + 'static,
4411    ) -> AnyElement
4412    where
4413        H: 'static,
4414    {
4415        let key_at: crate::windowed_surface_host::RetainedVirtualListKeyAtFn = Arc::new(key_at);
4416        let row: crate::windowed_surface_host::RetainedVirtualListRowFn<H> = Arc::new(row);
4417        self.virtual_list_keyed_retained_with_layout_and_range_extractor(
4418            layout,
4419            len,
4420            options,
4421            scroll_handle,
4422            key_at,
4423            range_extractor,
4424            row,
4425        )
4426    }
4427
4428    #[track_caller]
4429    pub fn virtual_list_keyed_with_range_extractor(
4430        &mut self,
4431        len: usize,
4432        options: VirtualListOptions,
4433        scroll_handle: &crate::scroll::VirtualListScrollHandle,
4434        key_at: impl FnMut(usize) -> crate::ItemKey,
4435        range_extractor: impl FnOnce(crate::virtual_list::VirtualRange) -> Vec<usize>,
4436        row: impl FnMut(&mut Self, usize) -> AnyElement,
4437    ) -> AnyElement {
4438        self.virtual_list_keyed_with_layout_and_range_extractor(
4439            LayoutStyle::default(),
4440            len,
4441            options,
4442            scroll_handle,
4443            key_at,
4444            range_extractor,
4445            row,
4446        )
4447    }
4448
4449    #[track_caller]
4450    pub fn virtual_list_keyed_with_layout(
4451        &mut self,
4452        layout: LayoutStyle,
4453        len: usize,
4454        options: VirtualListOptions,
4455        scroll_handle: &crate::scroll::VirtualListScrollHandle,
4456        key_at: impl FnMut(usize) -> crate::ItemKey,
4457        row: impl FnMut(&mut Self, usize) -> AnyElement,
4458    ) -> AnyElement {
4459        self.virtual_list_keyed_with_layout_and_range_extractor(
4460            layout,
4461            len,
4462            options,
4463            scroll_handle,
4464            key_at,
4465            crate::virtual_list::default_range_extractor,
4466            row,
4467        )
4468    }
4469
4470    #[track_caller]
4471    #[allow(clippy::too_many_arguments)]
4472    pub fn virtual_list_keyed_with_layout_and_range_extractor(
4473        &mut self,
4474        layout: LayoutStyle,
4475        len: usize,
4476        options: VirtualListOptions,
4477        scroll_handle: &crate::scroll::VirtualListScrollHandle,
4478        key_at: impl FnMut(usize) -> crate::ItemKey,
4479        range_extractor: impl FnOnce(crate::virtual_list::VirtualRange) -> Vec<usize>,
4480        mut row: impl FnMut(&mut Self, usize) -> AnyElement,
4481    ) -> AnyElement {
4482        let loc = Location::caller();
4483        self.virtual_list_with_layout_and_keys(
4484            layout,
4485            len,
4486            options,
4487            scroll_handle,
4488            key_at,
4489            range_extractor,
4490            |cx, items| {
4491                if cfg!(debug_assertions) && items.len() > 1 {
4492                    let mut first_dup: Option<(crate::ItemKey, usize, usize)> = None;
4493                    let mut seen: HashMap<crate::ItemKey, usize> = HashMap::new();
4494                    for (pos, item) in items.iter().enumerate() {
4495                        if first_dup.is_none()
4496                            && let Some(prev) = seen.insert(item.key, pos)
4497                        {
4498                            first_dup = Some((item.key, prev, pos));
4499                        }
4500                    }
4501
4502                    if let Some((key, a, b)) = first_dup {
4503                        let element_path: Option<String> = {
4504                            #[cfg(feature = "diagnostics")]
4505                            {
4506                                cx.window_state.debug_path_for_element(cx.root_id())
4507                            }
4508                            #[cfg(not(feature = "diagnostics"))]
4509                            {
4510                                None
4511                            }
4512                        };
4513
4514                        tracing::warn!(
4515                            file = loc.file(),
4516                            line = loc.line(),
4517                            column = loc.column(),
4518                            key = format_args!("{key:#x}"),
4519                            first_visible_pos = a,
4520                            second_visible_pos = b,
4521                            element_path = element_path.as_deref().unwrap_or("<unknown>"),
4522                            "duplicate virtual_list item key; element identity may collide"
4523                        );
4524                    }
4525                }
4526
4527                items
4528                    .iter()
4529                    .copied()
4530                    .map(|item| cx.keyed(item.key, |cx| row(cx, item.index)))
4531                    .collect::<Vec<_>>()
4532            },
4533        )
4534    }
4535}