Skip to main content

fret_ui/elements/
queries.rs

1use fret_core::{AppWindowId, NodeId, Rect};
2
3use super::with_window_state;
4use super::{ElementContext, ElementRuntime, GlobalElementId};
5use crate::UiHost;
6
7pub fn with_element_cx<H: UiHost, R>(
8    app: &mut H,
9    window: AppWindowId,
10    bounds: Rect,
11    root_name: &str,
12    f: impl FnOnce(&mut ElementContext<'_, H>) -> R,
13) -> R {
14    app.with_global_mut_untracked(ElementRuntime::new, |runtime, app| {
15        let mut cx = ElementContext::new_for_root_name(app, runtime, window, bounds, root_name);
16        f(&mut cx)
17    })
18}
19
20pub fn root_bounds_for_element<H: UiHost>(
21    app: &mut H,
22    window: AppWindowId,
23    element: GlobalElementId,
24) -> Option<Rect> {
25    app.with_global_mut_untracked(ElementRuntime::new, |runtime, _app| {
26        let state = runtime.for_window_mut(window);
27        let root = state.node_entry(element).map(|e| e.root)?;
28        state.root_bounds(root)
29    })
30}
31
32/// Returns the most recent retained `NodeId` mapping for `element`.
33///
34/// This is intentionally a last-known query surface for post-frame / component-policy code.
35/// Mechanism paths that already have access to `UiTree` or the declarative window frame should
36/// resolve a live attached node instead of treating this mapping as authoritative truth.
37pub fn node_for_element<H: UiHost>(
38    app: &mut H,
39    window: AppWindowId,
40    element: GlobalElementId,
41) -> Option<NodeId> {
42    with_window_state(app, window, |st| st.node_entry(element).map(|e| e.node))
43}
44
45/// Returns the current-frame node mapping for `element`, if available.
46///
47/// The authoritative liveness signal is the retained `node_entry` record being touched in the
48/// current frame (`last_seen_frame == app.frame_id()`). The declarative window-frame cache keeps
49/// stale records until subtree GC, so it must not be treated as a standalone liveness oracle.
50///
51/// Prefer this for render-time/component-policy code that needs a node that is still live for the
52/// current frame rather than a purely cross-frame cached mapping.
53pub fn live_node_for_element<H: UiHost>(
54    app: &mut H,
55    window: AppWindowId,
56    element: GlobalElementId,
57) -> Option<NodeId> {
58    let frame_id = app.frame_id();
59    with_window_state(app, window, |st| {
60        st.node_entry(element)
61            .and_then(|entry| (entry.last_seen_frame == frame_id).then_some(entry.node))
62    })
63}
64
65/// Returns the most recent `NodeId` mapping for `element` without preparing element runtime for the
66/// current frame.
67///
68/// This is intended for callers that run *during* tree construction (e.g. overlay policy hooks)
69/// where calling `prepare_window_for_frame` can reset `*_next` state.
70///
71/// Prefer `node_for_element` for normal post-frame queries.
72pub fn peek_node_for_element<H: UiHost>(
73    app: &mut H,
74    window: AppWindowId,
75    element: GlobalElementId,
76) -> Option<NodeId> {
77    app.with_global_mut_untracked(ElementRuntime::new, |runtime, _app| {
78        let state = runtime.for_window(window)?;
79        state.node_entry(element).map(|e| e.node)
80    })
81}
82
83/// Returns whether `element` is known to be mounted in the **current frame**.
84///
85/// `node_for_element` may return a stale mapping by design (cross-frame queries). For policies
86/// that need a liveness gate (e.g. cached overlay request synthesis), prefer this check.
87pub fn element_is_live_in_current_frame<H: UiHost>(
88    app: &mut H,
89    window: AppWindowId,
90    element: GlobalElementId,
91) -> bool {
92    let frame_id = app.frame_id();
93    with_window_state(app, window, |st| {
94        st.node_entry(element)
95            .map(|e| e.last_seen_frame == frame_id)
96            .unwrap_or(false)
97    })
98}
99
100/// Returns whether `element` participated in the current frame's declarative authoring pass.
101///
102/// Unlike [`element_is_live_in_current_frame`], this also returns `true` for scope-only identities
103/// created via `scope` / `keyed` that may not map to a mounted `NodeId` themselves.
104///
105/// This is the correct liveness predicate for policy caches keyed by declarative authoring
106/// identities (for example, overlay request owners).
107pub fn element_identity_is_live_in_current_frame<H: UiHost>(
108    app: &mut H,
109    window: AppWindowId,
110    element: GlobalElementId,
111) -> bool {
112    with_window_state(app, window, |st| {
113        st.element_identity_is_live_in_current_frame(element)
114    })
115}
116
117/// Returns the most recent **committed** bounds for a declarative element, if available.
118///
119/// This is a cross-frame geometry query intended for component-layer policies (e.g. anchored
120/// overlays) that need a stable anchor rect. The value is committed at frame boundaries to avoid
121/// feedback loops during layout (see `ElementRuntime` geometry buffering notes).
122pub fn bounds_for_element<H: UiHost>(
123    app: &mut H,
124    window: AppWindowId,
125    element: GlobalElementId,
126) -> Option<Rect> {
127    with_window_state(app, window, |st| st.last_bounds(element))
128}
129
130/// Returns the most recent recorded bounds for `element`, including the current frame's
131/// in-progress layout deltas.
132///
133/// Prefer [`bounds_for_element`] for policy code that must avoid layout feedback loops (e.g.
134/// anchored overlays). This helper is intended for mechanism code that runs *during* layout and
135/// needs immediate access to the bounds just recorded for the current frame.
136pub fn current_bounds_for_element<H: UiHost>(
137    app: &mut H,
138    window: AppWindowId,
139    element: GlobalElementId,
140) -> Option<Rect> {
141    with_window_state(app, window, |st| st.current_bounds(element))
142}
143
144/// Returns the most recent committed **visual** bounds (post-`render_transform` AABB) for a
145/// declarative element, if available.
146///
147/// This is a cross-frame geometry query intended for component-layer anchored overlay policies
148/// that must track render transforms (ADR 0082) while keeping layout authoritative.
149pub fn visual_bounds_for_element<H: UiHost>(
150    app: &mut H,
151    window: AppWindowId,
152    element: GlobalElementId,
153) -> Option<Rect> {
154    with_window_state(app, window, |st| {
155        st.last_visual_bounds(element)
156            .or_else(|| st.last_bounds(element))
157    })
158}
159
160/// Returns the most recent recorded **visual** bounds for `element`, including the current
161/// frame's in-progress layout deltas.
162pub fn current_visual_bounds_for_element<H: UiHost>(
163    app: &mut H,
164    window: AppWindowId,
165    element: GlobalElementId,
166) -> Option<Rect> {
167    with_window_state(app, window, |st| {
168        st.current_visual_bounds(element)
169            .or_else(|| st.current_bounds(element))
170    })
171}
172
173pub(crate) fn record_bounds_for_element<H: UiHost>(
174    app: &mut H,
175    window: AppWindowId,
176    element: GlobalElementId,
177    bounds: Rect,
178) {
179    with_window_state(app, window, |st| st.record_bounds(element, bounds));
180}
181
182pub(crate) fn record_visual_bounds_for_element<H: UiHost>(
183    app: &mut H,
184    window: AppWindowId,
185    element: GlobalElementId,
186    bounds: Rect,
187) {
188    with_window_state(app, window, |st| st.record_visual_bounds(element, bounds));
189}