Skip to main content

aetna_core/state/
query.rs

1//! Layout/key lookup helpers for [`UiState`](super::UiState).
2
3use crate::event::UiTarget;
4use crate::tree::{El, Rect};
5
6use super::UiState;
7
8impl UiState {
9    /// Look up the layout-assigned rect for `id`; returns a zero rect
10    /// when `id` is unknown (pre-layout, or not in the laid-out tree).
11    pub fn rect(&self, id: &str) -> Rect {
12        self.layout
13            .computed_rects
14            .get(id)
15            .copied()
16            .unwrap_or_default()
17    }
18
19    /// Look up the layout-assigned rect for an app-supplied element
20    /// key. Returns `None` when the key is absent from `root` or layout
21    /// has not written a rect for that node yet.
22    pub fn rect_of_key(&self, root: &El, key: &str) -> Option<Rect> {
23        find_target_by_key(root, key)
24            .and_then(|target| self.layout.computed_rects.get(&target.node_id).copied())
25    }
26
27    /// Build a [`UiTarget`] for an app-supplied element key using the
28    /// current layout rect. Useful for hosts that need to anchor native
29    /// overlays or forward events into externally painted regions.
30    pub fn target_of_key(&self, root: &El, key: &str) -> Option<UiTarget> {
31        let target = find_target_by_key(root, key)?;
32        let rect = self.layout.computed_rects.get(&target.node_id).copied()?;
33        Some(UiTarget { rect, ..target })
34    }
35
36    /// The keyed leaf currently under the pointer, or `None` when
37    /// nothing is hovered. Mirrors pointer hit-test target data, but
38    /// is read-only and stable across rebuilds so apps can branch the
39    /// build output on "what is hovered right now."
40    ///
41    /// Returns the *leaf* — the deepest keyed hit-test target. Use
42    /// [`Self::is_hovering_within`] for subtree-aware queries
43    /// ("is anything inside this row hovered?"), which matches the
44    /// semantics of [`crate::tree::El::hover_alpha`].
45    pub fn hovered_key(&self) -> Option<&str> {
46        self.hovered.as_ref().map(|t| t.key.as_str())
47    }
48
49    /// True iff `key`'s node — or any descendant of it — is the
50    /// current hover target. Subtree-aware: a focusable card with
51    /// keyed icon-buttons inside it reports `true` whether the cursor
52    /// is on the card body or on one of the buttons. Same predicate
53    /// `El::hover_alpha` uses to drive its declarative reveal, exposed
54    /// for app-side reads.
55    ///
56    /// Reads the underlying tracker, not the eased subtree envelope —
57    /// the boolean flips immediately on hit-test identity change. For
58    /// reactions tied to the eased animation, drive visuals through
59    /// `hover_alpha` instead.
60    ///
61    /// Returns `false` when `key` isn't in the current tree (pre-
62    /// layout, or the keyed node was removed in a recent build).
63    pub fn is_hovering_within(&self, key: &str) -> bool {
64        let Some(target) = self.hovered.as_ref() else {
65            return false;
66        };
67        let Some(node_id) = self.layout.key_index.get(key) else {
68            return false;
69        };
70        target_in_subtree(node_id, &target.node_id)
71    }
72}
73
74/// True when `target_id` names `node_id` itself or any descendant of it
75/// in the path-shaped `computed_id` namespace (`root.x.y.z`). Pure
76/// string-prefix predicate; doesn't touch the tree, so callers can use
77/// it from any context that already holds the two ids.
78pub(crate) fn target_in_subtree(node_id: &str, target_id: &str) -> bool {
79    if node_id.is_empty() || target_id.is_empty() {
80        return false;
81    }
82    if target_id == node_id {
83        return true;
84    }
85    target_id
86        .strip_prefix(node_id)
87        .is_some_and(|rest| rest.starts_with('.'))
88}
89
90fn find_target_by_key(root: &El, key: &str) -> Option<UiTarget> {
91    if root.key.as_deref() == Some(key) {
92        return Some(UiTarget {
93            key: key.to_string(),
94            node_id: root.computed_id.clone(),
95            rect: Rect::default(),
96            tooltip: root.tooltip.clone(),
97            scroll_offset_y: 0.0,
98        });
99    }
100    root.children
101        .iter()
102        .find_map(|child| find_target_by_key(child, key))
103}