///|
fn RenderNode::collect_draw_commands(
self : RenderNode,
commands : Array[DrawCommand],
) -> Unit {
let overlays : Array[DrawCommand] = []
let deferred : Array[RenderNode] = []
self.collect_draw_commands_with_overlays(commands, overlays, deferred)
RenderNode::drain_deferred_draw(deferred, commands, overlays)
for command in overlays {
commands.push(command)
}
}
///|
fn RenderNode::drain_deferred_draw(
deferred : Array[RenderNode],
commands : Array[DrawCommand],
overlays : Array[DrawCommand],
) -> Unit {
let mut head = 0
while head < deferred.length() {
deferred[head].collect_draw_commands_with_overlays(
commands, overlays, deferred,
)
head = head + 1
}
}
///|
fn RenderNode::drain_deferred_frame(
deferred : Array[RenderNode],
commands : Array[DrawCommand],
overlays : Array[DrawCommand],
) -> Unit {
let mut head = 0
while head < deferred.length() {
deferred[head].collect_frame_commands_with_overlays(
commands, overlays, deferred,
)
head = head + 1
}
}
///|
/// Return the union of view-level overlay paint bounds. Native platform views
/// may use this to expose the already-rendered overlay pixels above their own
/// surface without introducing a second renderer.
fn RenderNode::overlay_bounds(self : RenderNode) -> @core.Rect? {
let mut bounds : @core.Rect? = None
for command in self.overlay_paint_commands {
bounds = @core.optional_rect_union(bounds, command.bounds())
}
for child in self.children {
bounds = @core.optional_rect_union(bounds, child.overlay_bounds())
}
bounds
}
///|
fn RenderNode::collect_frame_commands(
self : RenderNode,
commands : Array[DrawCommand],
) -> Unit {
let overlays : Array[DrawCommand] = []
let deferred : Array[RenderNode] = []
self.collect_frame_commands_with_overlays(commands, overlays, deferred)
RenderNode::drain_deferred_frame(deferred, commands, overlays)
for command in overlays {
commands.push(command)
}
}
///|
fn RenderNode::collect_frame_commands_with_overlays(
self : RenderNode,
commands : Array[DrawCommand],
overlays : Array[DrawCommand],
deferred : Array[RenderNode],
) -> Unit {
let fade = match self.render_opacity {
Some(opacity) => opacity < 1.0
None => false
}
if fade {
commands.push(
PushOpacity(
match self.render_opacity {
Some(opacity) => opacity
None => 1.0
},
),
)
}
if self.is_repaint_boundary {
match self.cached_layer_spec() {
Some(spec) => {
commands.push(BeginRetainedLayer(spec))
self.collect_uncached_frame_commands_with_overlays(
commands, overlays, deferred,
)
commands.push(EndRetainedLayer(spec.key))
}
None =>
self.collect_uncached_frame_commands_with_overlays(
commands, overlays, deferred,
)
}
} else {
self.collect_uncached_frame_commands_with_overlays(
commands, overlays, deferred,
)
}
}
///|
fn RenderNode::collect_draw_commands_with_overlays(
self : RenderNode,
commands : Array[DrawCommand],
overlays : Array[DrawCommand],
deferred : Array[RenderNode],
) -> Unit {
let fade = match self.render_opacity {
Some(opacity) => opacity < 1.0
None => false
}
if fade {
commands.push(
PushOpacity(
match self.render_opacity {
Some(opacity) => opacity
None => 1.0
},
),
)
}
match self.cached_draw_commands {
Some(cached) => {
for command in cached {
commands.push(command)
}
match self.cached_overlay_draw_commands {
Some(cached_overlays) =>
for command in cached_overlays {
overlays.push(command)
}
None => ()
}
return
}
None => ()
}
for command in self.paint_commands {
commands.push(command)
}
for child in self.children {
if child.is_overlay_child {
deferred.push(child)
} else {
child.collect_draw_commands_with_overlays(commands, overlays, deferred)
}
}
for command in self.post_paint_commands {
commands.push(command)
}
for command in self.overlay_paint_commands {
overlays.push(command)
}
}
///|
fn RenderNode::collect_uncached_frame_commands_with_overlays(
self : RenderNode,
commands : Array[DrawCommand],
overlays : Array[DrawCommand],
deferred : Array[RenderNode],
) -> Unit {
for command in self.paint_commands {
commands.push(command)
}
for child in self.children {
if child.is_overlay_child {
deferred.push(child)
} else {
child.collect_frame_commands_with_overlays(commands, overlays, deferred)
}
}
for command in self.post_paint_commands {
commands.push(command)
}
for command in self.overlay_paint_commands {
overlays.push(command)
}
}
///|
fn RenderNode::with_repaint_boundary_cache(self : RenderNode) -> RenderNode {
let commands : Array[DrawCommand] = []
let overlays : Array[DrawCommand] = []
// Cache building inlines hoisted overlay children so the retained layer is
// self-contained; only the root traversal defers them.
let deferred : Array[RenderNode] = []
self.collect_draw_commands_with_overlays(commands, overlays, deferred)
RenderNode::drain_deferred_draw(deferred, commands, overlays)
{
..self,
is_repaint_boundary: true,
layer_cache_key: Some(self.repaint_boundary_cache_key()),
cached_draw_commands: Some(commands),
cached_overlay_draw_commands: Some(overlays),
}
}
///|
fn RenderNode::cached_layer_spec(self : RenderNode) -> RetainedLayerSpec? {
match self.layer_cache_key {
Some(key) =>
Some(
RetainedLayerSpec::new(
key~,
frame=self.paint_bounds,
content_revision=self.content_revision,
),
)
None => None
}
}
///|
fn RenderNode::repaint_boundary_cache_key(self : RenderNode) -> String {
"element=\{self.element_id.value()};x=\{self.frame.origin.x};y=\{self.frame.origin.y};w=\{self.frame.size.width};h=\{self.frame.size.height}"
}
///|
/// True when any hoisted overlay subtree under this node (including hoisted
/// children of descendants) covers `point`. Pointer dispatch and focus pickup
/// use this as a global prepass so a popup anywhere in the tree wins over
/// in-flow content painted underneath it.
fn RenderNode::subtree_overlay_contains(
self : RenderNode,
point : Point,
) -> Bool {
for child in self.children {
if child.is_overlay_child && child.hit_bounds().contains(point) {
return true
}
if child.subtree_overlay_contains(point) {
return true
}
}
false
}
///|
/// One-pass per-tree index for damage computation. Built once per frame per
/// previous/current tree in `compute_damage_region`, replacing the previous
/// up-to-two full DFS traversals per dirty id with O(1) lookups and O(depth)
/// parent-chain walks.
priv struct DamageIndexEntry {
paint_bounds : Rect
parent_id : Int?
is_repaint_boundary : Bool
}
///|
priv struct DamageIndex {
entries : Map[Int, DamageIndexEntry]
}
///|
fn DamageIndex::build(root : RenderNode) -> DamageIndex {
let entries : Map[Int, DamageIndexEntry] = Map([])
DamageIndex::build_node(root, None, entries)
{ entries, }
}
///|
fn DamageIndex::build_node(
node : RenderNode,
parent_id : Int?,
entries : Map[Int, DamageIndexEntry],
) -> Unit {
for child in node.children {
DamageIndex::build_node(child, Some(node.element_id.value()), entries)
}
entries[node.element_id.value()] = {
paint_bounds: node.paint_bounds,
parent_id,
is_repaint_boundary: node.is_repaint_boundary,
}
}
///|
/// Nearest self-or-ancestor repaint-boundary `paint_bounds`, falling back to
/// the node's own bounds — same contract as the removed `find_damage_bounds`.
fn DamageIndex::damage_bounds(self : DamageIndex, id : ElementId) -> Rect? {
match self.entries.get(id.value()) {
Some(entry) => {
if entry.is_repaint_boundary {
return Some(entry.paint_bounds)
}
let mut cursor = entry.parent_id
while cursor is Some(pid) {
match self.entries.get(pid) {
Some(parent) => {
if parent.is_repaint_boundary {
return Some(parent.paint_bounds)
}
cursor = parent.parent_id
}
// Unreachable while every entry except the root has a parent in
// the map; fall back to the node's own bounds like the old DFS.
None => return Some(entry.paint_bounds)
}
}
Some(entry.paint_bounds)
}
None => None
}
}
///|
fn RenderNode::find_render_node(
self : RenderNode,
id : ElementId,
) -> RenderNode? {
if self.element_id == id {
Some(self)
} else {
for child in self.children {
match child.find_render_node(id) {
Some(node) => return Some(node)
None => ()
}
}
None
}
}
///|
fn ElementNode::render_focused_with_text_system(
self : ElementNode,
placed : PlacedNode,
focused~ : ElementId?,
environment? : Environment = Environment::neutral(),
text_system~ : TextSystem,
now_ms~ : Double,
overlay_enter? : Array[(ElementId, Double)] = [],
) -> RenderNode {
self.render_with_environment(
placed,
focused,
RenderEnvironment::from_environment(environment)
.with_text_system(text_system)
.with_now_ms(now_ms),
overlay_enter~,
)
}