1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! Layout/key lookup helpers for [`UiState`](super::UiState).
use crate::event::UiTarget;
use crate::tree::{El, Rect};
use super::UiState;
impl UiState {
/// Look up the layout-assigned rect for `id`; returns a zero rect
/// when `id` is unknown (pre-layout, or not in the laid-out tree).
///
/// Resolves **keyed** nodes (plus the root): the layout pass keeps
/// its id-indexed side map only for nodes carrying an author key —
/// every node's rect lives on the node itself as
/// [`crate::tree::El::computed_rect`], so callers holding the `El`
/// should read that field instead. Event targets carry their rect
/// on [`UiTarget::rect`] already.
pub fn rect(&self, id: &str) -> Rect {
self.layout.keyed_rects.get(id).copied().unwrap_or_default()
}
/// Look up the layout-assigned rect for an app-supplied element
/// key. Returns `None` when the key was absent from the last
/// laid-out tree, or layout hasn't run yet.
///
/// Answers from the key index the layout pass rebuilds each frame
/// (two hash lookups, no tree walk) — so it needs no `&El` root
/// and reflects the geometry currently on screen. Duplicate keys
/// resolve to the first occurrence in tree order, matching
/// `LayoutCtx::rect_of_key`. For the richer [`UiTarget`] (tooltip,
/// scroll offset) use [`Self::target_of_key`], which still walks
/// the tree you pass it.
pub fn rect_of_key(&self, key: &str) -> Option<Rect> {
let id = self.layout.key_index.get(key)?;
self.layout.keyed_rects.get(id).copied()
}
/// The half-open global row-index range a keyed virtual list
/// realized (made visible) in the last layout, or `None` when the
/// key isn't a virtual list in the current tree or nothing is
/// realized. Apps use it to evict caches for off-screen row
/// resources (decoded images, thumbnails) that the row builder
/// will recreate on demand when scrolled back.
pub fn visible_range(&self, key: &str) -> Option<std::ops::Range<usize>> {
let id = self.layout.key_index.get(key)?;
let (start, end) = self.scroll.visible_ranges.get(id.as_ref()).copied()?;
Some(start..end)
}
/// Build a [`UiTarget`] for an app-supplied element key using the
/// current layout rect. Useful for hosts that need to anchor native
/// overlays or forward events into externally painted regions.
pub fn target_of_key(&self, root: &El, key: &str) -> Option<UiTarget> {
let target = find_target_by_key(root, key)?;
let rect = self
.layout
.keyed_rects
.get(target.node_id.as_ref())
.copied()?;
Some(UiTarget { rect, ..target })
}
/// The keyed leaf currently under the pointer, or `None` when
/// nothing is hovered. Mirrors pointer hit-test target data, but
/// is read-only and stable across rebuilds so apps can branch the
/// build output on "what is hovered right now."
///
/// Returns the *leaf* — the deepest keyed hit-test target. Use
/// [`Self::is_hovering_within`] for subtree-aware queries
/// ("is anything inside this row hovered?"), which matches the
/// semantics of [`crate::tree::El::hover_alpha`].
pub fn hovered_key(&self) -> Option<&str> {
self.hovered.as_ref().map(|t| t.key.as_str())
}
/// True iff `key`'s node — or any descendant of it — is the
/// current hover target. Subtree-aware: a focusable card with
/// keyed icon-buttons inside it reports `true` whether the cursor
/// is on the card body or on one of the buttons. Same predicate
/// `El::hover_alpha` uses to drive its declarative reveal, exposed
/// for app-side reads.
///
/// Reads the underlying tracker, not the eased subtree envelope —
/// the boolean flips immediately on hit-test identity change. For
/// reactions tied to the eased animation, drive visuals through
/// `hover_alpha` instead.
///
/// Returns `false` when `key` isn't in the current tree (pre-
/// layout, or the keyed node was removed in a recent build).
pub fn is_hovering_within(&self, key: &str) -> bool {
let Some(target) = self.hovered.as_ref() else {
return false;
};
let Some(node_id) = self.layout.key_index.get(key) else {
return false;
};
target_in_subtree(node_id, &target.node_id)
}
}
/// True when `target_id` names `node_id` itself or any descendant of it
/// in the path-shaped `computed_id` namespace (`root.x.y.z`). Pure
/// string-prefix predicate; doesn't touch the tree, so callers can use
/// it from any context that already holds the two ids.
pub(crate) fn target_in_subtree(node_id: &str, target_id: &str) -> bool {
if node_id.is_empty() || target_id.is_empty() {
return false;
}
if target_id == node_id {
return true;
}
target_id
.strip_prefix(node_id)
.is_some_and(|rest| rest.starts_with('.'))
}
fn find_target_by_key(root: &El, key: &str) -> Option<UiTarget> {
if root.key.as_deref() == Some(key) {
return Some(UiTarget {
key: key.to_string(),
node_id: root.computed_id.clone(),
rect: Rect::default(),
tooltip: root.tooltip.clone(),
scroll_offset_y: 0.0,
});
}
root.children
.iter()
.find_map(|child| find_target_by_key(child, key))
}