pub struct RenderTree { /* private fields */ }Expand description
Arena-allocated persistent render tree. Node 0 is always the root.
Implementations§
Source§impl RenderTree
impl RenderTree
pub const ROOT: NodeId = 0
pub fn new() -> Self
Sourcepub fn start_frame(&mut self)
pub fn start_frame(&mut self)
Start a new frame and begin the root. Must be called before painting.
Sourcepub fn reset(&mut self, node: NodeId)
pub fn reset(&mut self, node: NodeId)
Reset a node for a fresh paint: clears its declarations (the picture cache fields persist — the walker manages those explicitly).
Sourcepub fn slot(&mut self, parent: NodeId, reset: bool) -> NodeId
pub fn slot(&mut self, parent: NodeId, reset: bool) -> NodeId
Consume the next child slot of parent.
reset == true (normal paint descent): the child is begun — its
declared data is cleared for re-declaration.
reset == false (cache-hit replay): the slot is consumed so siblings
stay positionally aligned, but the child subtree keeps all its state.
Sourcepub fn keyed_slot(&mut self, parent: NodeId, key: u64) -> NodeId
pub fn keyed_slot(&mut self, parent: NodeId, key: u64) -> NodeId
Like Self::slot, but the returned NodeId is resolved by an
explicit stable key instead of “whatever was previously at this
position” — see the module doc’s “Identity” section. Reusing an
existing key’s node preserves ALL its sticky state (scroll_ctrl,
anim_channels, hover/press, and everything underneath it in the
subtree, however deep) exactly like an ordinary same-position
repaint does; a new key gets a brand-new node with empty
children/keyed_children, so nothing nested under it — however
many ScrollViews/Tabs/TextAreas it contains — can possibly
alias whatever a DIFFERENT key’s subtree left behind.
The resolved node is ALSO written into parent’s ordinary
children/cursor slot, same as slot() — the key only changes
which NodeId ends up at that position, not how it’s found
afterward. This matters: hit-testing, hover, semantics/accessibility,
and the picture-cache walk all traverse children, not
keyed_children — a node reachable ONLY through the keyed map would
be invisible to all of them (found via a real test failure —
semantic_labels came back empty for a screen reached this way).
Sourcepub fn prune_keyed_children(&mut self, parent: NodeId, valid_keys: &[u64])
pub fn prune_keyed_children(&mut self, parent: NodeId, valid_keys: &[u64])
Drop any of parent’s keyed children whose key is no longer in
valid_keys — called once per frame by ScreenTransitionView with
the navigation stack’s current keys, so a screen’s cached subtree
(scroll position, animation state, everything) is released once
it’s actually been popped, not retained forever. The dropped node’s
arena slot itself isn’t reclaimed (this arena never frees — same
tradeoff slot()’s positional children already have for any widget
that stops being painted), only the reference to it.
Sourcepub fn finalize(&mut self)
pub fn finalize(&mut self)
End of frame: drop unused child slots of every node repainted this frame, so removed widgets cannot leave ghost hit regions behind.
pub fn node_mut(&mut self, id: NodeId) -> &mut TreeNode
pub fn node(&self, id: NodeId) -> &TreeNode
Sourcepub fn nodes_iter(&self) -> impl Iterator<Item = &TreeNode>
pub fn nodes_iter(&self) -> impl Iterator<Item = &TreeNode>
Every node in the arena, for callers that need to scan rather than look up a specific id (e.g. tests asserting some node reached a given interaction state without knowing its id in advance).
Sourcepub fn nodes_indexed(&self) -> impl Iterator<Item = (NodeId, &TreeNode)>
pub fn nodes_indexed(&self) -> impl Iterator<Item = (NodeId, &TreeNode)>
Same as Self::nodes_iter, paired with each node’s NodeId —
needed by callers that must look the node back up for a second,
mutable pass (D116’s EditController draining: the engine collects
(NodeId, controller, ops) immutably first, since it can’t mutate
the tree while iterating it).
Sourcepub fn hit_test(
&self,
x: f32,
y: f32,
) -> (Option<(HitHandler, bool)>, Vec<ScrollHandler>)
pub fn hit_test( &self, x: f32, y: f32, ) -> (Option<(HitHandler, bool)>, Vec<ScrollHandler>)
Hit-test walk: children before own regions, later siblings first —
paint order is z-order, so the topmost match wins structurally (D092).
Returns the topmost hit callback, whether it is POSITIONAL —
positional hits become the active drag grab (streamed MouseMove
positions until release); plain hits fire once — and, when the
winner is a plain hit, so a touch/mouse gesture that starts on a
plain-hit child (Button, ListTile, …) sitting inside e.g. a
ScrollView can still fall back to dragging that ancestor once
movement shows it’s a scroll, not a tap (2026-08-02, real Android
touch testing — without this a plain-hit child sitting anywhere in
a scrollable page permanently shadowed the ScrollView’s own drag
region, so touch-drag scrolling silently did nothing on any page
with interactive content — desktop was unaffected since
wheel/trackpad scroll is a wholly separate InputEvent::Scroll
path).
The chain is the SECOND return value, always present — collected
independently of what the leaf hit resolves to (None, a plain
tap, or even a positional widget like a Slider), so touching
blank scrollable space directly (no leaf hit at all) still yields
a usable chain even though the first value is None.
Sourcepub fn hover_test(&self, x: f32, y: f32) -> Option<NodeId>
pub fn hover_test(&self, x: f32, y: f32) -> Option<NodeId>
Topmost node under the cursor that owns any interactive or hover region — drives hover state (buttons, tiles, tooltips).
Sourcepub fn long_press_test(
&self,
x: f32,
y: f32,
) -> Option<Arc<dyn Fn() + Send + Sync>>
pub fn long_press_test( &self, x: f32, y: f32, ) -> Option<Arc<dyn Fn() + Send + Sync>>
Topmost long-press callback under the cursor.
Sourcepub fn set_hover(&mut self, target: Option<NodeId>) -> bool
pub fn set_hover(&mut self, target: Option<NodeId>) -> bool
Set the hovered node, clearing the previous one. Marks both the old and new node dirty so the next walk repaints exactly them (localized damage). Returns true when the hover target changed.
Sourcepub fn set_pressed(&mut self, target: Option<NodeId>) -> bool
pub fn set_pressed(&mut self, target: Option<NodeId>) -> bool
Set the pressed node, clearing the previous one — same shape as
Self::set_hover, driven by MouseDown/MouseUp instead of
MouseMove. Returns true when the pressed target changed.
Sourcepub fn scroll_test(
&self,
x: f32,
y: f32,
dx: f32,
dy: f32,
) -> Option<HitHandler>
pub fn scroll_test( &self, x: f32, y: f32, dx: f32, dy: f32, ) -> Option<HitHandler>
Axis-aware scroll routing: among the viewports under the cursor (innermost first), pick the first that handles the DOMINANT axis of the delta; fall back to the innermost that handles the other axis. A horizontal carousel no longer intercepts a vertical page scroll.
Sourcepub fn zoom_test(
&self,
x: f32,
y: f32,
) -> Option<Arc<dyn Fn(f32) + Send + Sync>>
pub fn zoom_test( &self, x: f32, y: f32, ) -> Option<Arc<dyn Fn(f32) + Send + Sync>>
Innermost registered zoom region under (x, y) (trackpad pinch,
InteractiveViewer) — same innermost-first, later-sibling-first
priority as scroll_test, but with no axis-selection step (a pinch
gesture has no “axis”, just one delta).
Sourcepub fn collect_hits(&self) -> Vec<HitRegion> ⓘ
pub fn collect_hits(&self) -> Vec<HitRegion> ⓘ
All hit regions in tree (paint) order — used by the overlay pass to flatten a per-entry subtree into a dispatch list.
Sourcepub fn collect_scrolls(&self) -> Vec<ScrollRegion> ⓘ
pub fn collect_scrolls(&self) -> Vec<ScrollRegion> ⓘ
All scroll regions in tree (paint) order.
Sourcepub fn collect_focus(&self) -> Vec<FocusNode>
pub fn collect_focus(&self) -> Vec<FocusNode>
All focus nodes in tree (paint) order — feeds the Tab cycle each frame, including cache-hit frames where no widget was repainted.
Sourcepub fn focus_owner(&self, focus_id: u64) -> Option<NodeId>
pub fn focus_owner(&self, focus_id: u64) -> Option<NodeId>
The render-tree node that declared the rosace_a11y::FocusNode
with id focus_id (D112/Phase 28 Step 1) — bridges
FocusManager::focused (a FocusNode’s own global id) back to a
NodeId, so the engine’s key dispatch can find and mutate that
node’s persistent text_edit/editable state.
Sourcepub fn editable_test(&self, x: f32, y: f32) -> Option<NodeId>
pub fn editable_test(&self, x: f32, y: f32) -> Option<NodeId>
Topmost editable node whose declared rect contains (x, y) — used
by the engine to focus (and, Step 1: place the caret at the end
of) an editable widget on click (D112/Phase 28). Same z-order
traversal as Self::hover_test; editable rects live in
TreeNode::editable, declared by super::PaintCtx::register_editable.
Sourcepub fn collect_semantics(&self) -> SemanticNode
pub fn collect_semantics(&self) -> SemanticNode
Derive the accessibility tree (D099): semantics entries in paint order, nested by render-tree structure. Branches with no semantic content anywhere below them are pruned.
Sourcepub fn content_to_screen(&self, target: NodeId, p: Point) -> Point
pub fn content_to_screen(&self, target: NodeId, p: Point) -> Point
All overlay entries in tree order (insertion order = z-order, D058).
Map a point expressed in target’s CONTENT space to window/screen
space, applying the inverse of every transform-host remap on the
path from the root (each is a pure translation: + viewport origin
− scroll offset). Phase 32 bug fix (user-reported): an overlay
anchored by a widget inside a GPU scroll layer (e.g. a Tooltip’s
Absolute position) carried content coords into the window-space
overlay pass and rendered far from its anchor.
pub fn overlay_ids(&self) -> Vec<(NodeId, usize)>
Sourcepub fn transform_ids(&self) -> Vec<(NodeId, usize)>
pub fn transform_ids(&self) -> Vec<(NodeId, usize)>
All transform-layer entries in tree order.
Sourcepub fn inspect(&self) -> Vec<InspectNode>
pub fn inspect(&self) -> Vec<InspectNode>
Read-only snapshot of the live tree (D123/O2) — plain data, safe to
hand to a DevTools overlay: no callbacks, no Arc<dyn Fn>, nothing
that could be invoked or mutated through it. “Live” means reachable
from the root through children as of the last finalize() — an
arena slot orphaned by a removed widget is not included, even though
its TreeNode still physically exists until the slot is reused.
Additive and non-invasive: reads fields every node already carries, touches nothing about how painting/hit-testing/layout work.
Sourcepub fn pick(&self, x: f32, y: f32) -> Option<NodeId>
pub fn pick(&self, x: f32, y: f32) -> Option<NodeId>
The node whose rect contains (x, y) and is deepest (most
specific) in the tree — the element-picker hit target (D123/O2).
Unlike Self::hover_test/Self::hit_test, this considers EVERY
node’s paint rect, not just ones that declared an interactive
region — a plain Container/Text is pickable too. Ties (same
depth) go to the one painted later (topmost in z-order), mirroring
every other hit-order convention in this file.