Skip to main content

cranpose_render_common/
scene_builder.rs

1use std::{
2    any::Any,
3    borrow::Cow,
4    ops::{Range, RangeInclusive},
5    rc::Rc,
6};
7
8use cranpose_core::{MemoryApplier, NodeId, NodeMarks, collections::map::HashMap};
9use cranpose_ui::{
10    DrawCommand, LayoutBox, LayoutNode, ModifierNodeSlices, Point, PreparedTextLayout, Rect, Size,
11    SubcomposeLayoutNode, TextLayoutOptions, TextOverflow, TextPanResolver, text::TextStyle,
12};
13use cranpose_ui_graphics::{
14    CommandRecording, CompositingStrategy, CornerRadii, DrawGroup, GraphicsLayer, LayerShape,
15    RecordLane, RoundedCornerShape, rounded_corner_alpha_mask_effect,
16};
17
18use crate::{
19    SceneUpdates,
20    graph::{
21        CachePolicy, ContentChanges, DrawCommandId, DrawRunNode, HitTestNode, IsolationReasons,
22        LayerNode, PrimitiveEntry, PrimitiveNode, PrimitivePhase, ProjectiveTransform, RenderGraph,
23        RenderNode, TextPrimitiveNode,
24    },
25    layer_transform::{
26        layer_scales_or_rotates, layer_transform_to_parent, layer_transform_to_window,
27    },
28    style_shared::{DrawPlacement, recording_for_placement_reusing},
29    text_paint::{TextPaint, TextPaintCache},
30};
31
32const TEXT_CLIP_PAD: f32 = 1.0;
33const ROUNDED_CLIP_EDGE_FEATHER: f32 = 1.0;
34
35#[derive(Clone, Default)]
36struct BuildNodeSnapshot {
37    node_id: NodeId,
38    placement: Point,
39    size: Size,
40    content_offset: Point,
41    /// The node's modifier slices, shared: its draw commands, handlers and
42    /// text are read from them rather than copied out.
43    slices: Rc<ModifierNodeSlices>,
44    graphics_layer: Option<GraphicsLayer>,
45    children: Vec<Self>,
46}
47
48struct SnapshotNodeData<'a> {
49    layout_state: cranpose_ui::widgets::LayoutState,
50    modifier_slices: Rc<ModifierNodeSlices>,
51    children: &'a [NodeId],
52    window_root: bool,
53}
54
55/// Why a scoped scene update could not be applied, forcing the caller to throw
56/// the render graph away and build it again from the applier.
57///
58/// The reason has to travel with the outcome because the shell picks the
59/// scoped path from the shape of the dirty set, before this code runs, and
60/// logs that choice. A frame that chose the scoped path and then rebuilt the
61/// whole scene is the expensive case, and in the log it reads exactly like a
62/// cheap patch -- so the fallback says so itself.
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub enum GraphRebuildReason {
65    /// The root was dirty and its replacement layer could not be built.
66    RootLayerUnavailable,
67    /// A dirty subtree's replacement layer could not be built.
68    DirtyLayerUnavailable,
69    /// This many dirty nodes own no layer in the render graph, so the scoped
70    /// walk never reached them. A node enters the graph only as a `LayerNode`;
71    /// a dirty node that never produced one -- or whose layer left the graph
72    /// this frame -- cannot be patched in place.
73    UnmatchedDirtyNodes(usize),
74}
75
76/// The outcome of a scoped scene update.
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum GraphUpdate {
79    /// The dirty nodes were updated in place.
80    Patched,
81    /// The update could not be applied and the graph must be built again, for the given reason.
82    NeedsRebuild(GraphRebuildReason),
83}
84
85/// The outcome of a scoped update together with its effect on hit testing.
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87pub struct GraphUpdateReport {
88    /// Whether the graph was patched or must be rebuilt.
89    pub update: GraphUpdate,
90    /// Whether the update changed hit geometry or handlers, so the hit targets must be collected
91    /// again.
92    pub hit_graph_dirty: bool,
93}
94
95impl GraphUpdateReport {
96    /// Whether the graph was patched in place.
97    pub fn applied(self) -> bool {
98        matches!(self.update, GraphUpdate::Patched)
99    }
100
101    /// Why the graph must be rebuilt, or `None` when it was patched.
102    pub fn rebuild_reason(self) -> Option<GraphRebuildReason> {
103        match self.update {
104            GraphUpdate::Patched => None,
105            GraphUpdate::NeedsRebuild(reason) => Some(reason),
106        }
107    }
108}
109
110fn note_layer_lowered() {
111    #[cfg(test)]
112    crate::thread_state::with(|state| {
113        let count = &state.lowered_layer_count;
114        count.set(count.get() + 1);
115    });
116}
117
118#[cfg(test)]
119fn reset_lowered_layer_count() {
120    crate::thread_state::with(|state| state.lowered_layer_count.set(0));
121}
122
123#[cfg(test)]
124fn lowered_layer_count() -> usize {
125    crate::thread_state::with(|state| state.lowered_layer_count.get())
126}
127
128/// Builds the render graph of the layout box tree rooted at `root`.
129pub fn build_graph_from_layout_tree(root: &LayoutBox, _scale: f32) -> RenderGraph {
130    bump_recording_generation();
131    let root_snapshot = layout_box_to_snapshot(root, None);
132    let mut graph = RenderGraph::new(LayerNode::default());
133    write_snapshot_layer(root_snapshot, LowerContext::ROOT, &mut graph.root);
134    graph
135}
136
137/// Builds the render graph of the layout tree below `root` by reading nodes from `applier`.
138/// Returns `None` when `root` cannot be lowered to a layer.
139pub fn build_graph_from_applier(
140    applier: &MemoryApplier,
141    root: NodeId,
142    _scale: f32,
143) -> Option<RenderGraph> {
144    bump_recording_generation();
145    let mut graph = RenderGraph::new(LayerNode::default());
146    lower_root_into(applier, root, &mut graph.root).then_some(graph)
147}
148
149/// Builds `root`'s graph again, its layers taking the allocations of
150/// `previous`, the graph it replaces.
151pub fn rebuild_graph_from_applier(
152    applier: &MemoryApplier,
153    root: NodeId,
154    scale: f32,
155    previous: Option<RenderGraph>,
156) -> Option<RenderGraph> {
157    let graph = match previous {
158        Some(mut graph) => {
159            release_for_rebuild(&mut graph.root);
160            bump_recording_generation();
161            graph.retained_path = None;
162            graph.update += 1;
163            graph.root.note_content_change(graph.update);
164            lower_root_into(applier, root, &mut graph.root).then_some(graph)
165        }
166        None => build_graph_from_applier(applier, root, scale),
167    };
168    crate::layer_recycling::release();
169    graph
170}
171
172/// Applies `updates` to `graph` in place. Returns `false` when the update could not be applied
173/// and the graph must be built again.
174pub fn update_graph_from_applier(
175    applier: &MemoryApplier,
176    graph: &mut RenderGraph,
177    updates: SceneUpdates<'_>,
178    scale: f32,
179) -> bool {
180    update_graph_from_applier_report(applier, graph, updates, scale).applied()
181}
182
183/// Like `update_graph_from_applier`, and also reports whether the hit targets must be collected
184/// again or why the graph must be rebuilt.
185pub fn update_graph_from_applier_report(
186    applier: &MemoryApplier,
187    graph: &mut RenderGraph,
188    updates: SceneUpdates<'_>,
189    _scale: f32,
190) -> GraphUpdateReport {
191    let report = update_graph_from_applier_report_inner(applier, graph, updates);
192    crate::layer_recycling::release();
193    if let GraphUpdate::NeedsRebuild(reason) = report.update
194        && cranpose_core::env_flag!("CRANPOSE_SCENE_UPDATE_DIAG")
195    {
196        eprintln!(
197            "[scene-update-diag] scoped update abandoned, whole scene rebuilt: {reason:?} dirty={}",
198            updates.content.len() + updates.layers.len() + updates.moved.len()
199        );
200    }
201    report
202}
203
204#[derive(Clone, Copy)]
205pub(crate) enum NodeUpdate {
206    Content,
207    Layer,
208    /// The node moved in its parent and kept its size: its layer moves and
209    /// keeps what it drew.
210    Moved,
211    /// The node moved and its layer properties changed: its layer moves,
212    /// takes the new properties and keeps what it drew.
213    MovedLayer,
214}
215
216/// Puts into `dirty` each dirty node with the update it needs: content wins
217/// over the others, and a moved node whose layer properties changed too
218/// moves with them.
219fn dirty_node_updates(updates: SceneUpdates<'_>, dirty: &mut HashMap<NodeId, NodeUpdate>) {
220    dirty.clear();
221    dirty.reserve(updates.moved.len() + updates.layers.len() + updates.content.len());
222    dirty.extend(updates.moved.iter().map(|&id| (id, NodeUpdate::Moved)));
223    for &id in updates.layers {
224        let kind = match dirty.get(&id) {
225            Some(NodeUpdate::Moved) => NodeUpdate::MovedLayer,
226            _ => NodeUpdate::Layer,
227        };
228        dirty.insert(id, kind);
229    }
230    dirty.extend(updates.content.iter().map(|&id| (id, NodeUpdate::Content)));
231}
232
233fn update_graph_from_applier_report_inner(
234    applier: &MemoryApplier,
235    graph: &mut RenderGraph,
236    updates: SceneUpdates<'_>,
237) -> GraphUpdateReport {
238    if updates.is_empty() {
239        return GraphUpdateReport {
240            update: GraphUpdate::Patched,
241            hit_graph_dirty: false,
242        };
243    }
244    bump_recording_generation();
245    graph.update += 1;
246    let update = graph.update;
247
248    if cranpose_core::env_flag!("CRANPOSE_SCENE_UPDATE_DIAG") {
249        eprintln!("[scene-update-diag] dirty={updates:?}");
250    }
251
252    // The map of the frame before keeps its storage for this one.
253    let mut remaining_dirty_nodes =
254        crate::thread_state::with(|state| state.dirty_node_scratch.take()).unwrap_or_default();
255    dirty_node_updates(updates, &mut remaining_dirty_nodes);
256    let report = update_dirty_nodes(applier, graph, &mut remaining_dirty_nodes, update);
257    remaining_dirty_nodes.clear();
258    crate::thread_state::with(|state| state.dirty_node_scratch.set(Some(remaining_dirty_nodes)));
259    report
260}
261
262/// Updates `graph` for `remaining_dirty_nodes`, removing each node it
263/// updates from the map.
264fn update_dirty_nodes(
265    applier: &MemoryApplier,
266    graph: &mut RenderGraph,
267    remaining_dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
268    update: u64,
269) -> GraphUpdateReport {
270    if let Some(root_id) = layer_identity(&graph.root)
271        && let Some(kind) = remaining_dirty_nodes.remove(&root_id)
272    {
273        if let Some(hit_graph_dirty) = try_update_retained_layer(
274            applier,
275            &mut graph.root,
276            remaining_dirty_nodes,
277            kind,
278            true,
279            TranslateAncestorContext {
280                inherited_motion_context_animated: false,
281                update,
282                inherited_translated_content_context: false,
283                parent_content_offset: Point::default(),
284                parent_abs: AbsOrigin::ROOT,
285            },
286        ) {
287            if remaining_dirty_nodes.is_empty() {
288                return GraphUpdateReport {
289                    update: GraphUpdate::Patched,
290                    hit_graph_dirty,
291                };
292            }
293            let walked = walk_dirty_layers(applier, &mut graph.root, remaining_dirty_nodes, update)
294                .map(|(report, _)| report);
295            return GraphUpdateReport {
296                update: classify_walk(applier, walked.is_some(), remaining_dirty_nodes),
297                hit_graph_dirty: hit_graph_dirty || walked.is_none_or(|r| r.hit_graph_dirty),
298            };
299        }
300
301        let Some(hit_graph_dirty) = rebuild_tracking_hits(&mut graph.root, |root| {
302            release_for_rebuild(root);
303            lower_root_into(applier, root_id, root)
304        }) else {
305            return GraphUpdateReport {
306                update: GraphUpdate::NeedsRebuild(GraphRebuildReason::RootLayerUnavailable),
307                hit_graph_dirty: true,
308            };
309        };
310        graph.root.note_content_change(update);
311
312        return GraphUpdateReport {
313            update: GraphUpdate::Patched,
314            hit_graph_dirty,
315        };
316    }
317
318    // An update that does not go along the retained path may move the
319    // layers on it, so only one that does keeps it.
320    if let Some(path) = graph.retained_path.take()
321        && let Some(report) = update_along_retained_path(
322            applier,
323            &mut graph.root,
324            &path,
325            remaining_dirty_nodes,
326            update,
327        )
328    {
329        if report.is_some() {
330            graph.retained_path = Some(RetainedPath { update, ..path });
331        }
332        return match report {
333            Some(report) => GraphUpdateReport {
334                update: GraphUpdate::Patched,
335                hit_graph_dirty: report.hit_graph_dirty,
336            },
337            None => GraphUpdateReport {
338                update: GraphUpdate::NeedsRebuild(GraphRebuildReason::DirtyLayerUnavailable),
339                hit_graph_dirty: true,
340            },
341        };
342    }
343
344    let Some((report, path)) =
345        walk_dirty_layers(applier, &mut graph.root, remaining_dirty_nodes, update)
346    else {
347        return GraphUpdateReport {
348            update: GraphUpdate::NeedsRebuild(GraphRebuildReason::DirtyLayerUnavailable),
349            hit_graph_dirty: true,
350        };
351    };
352    graph.retained_path = path;
353
354    match classify_walk(applier, true, remaining_dirty_nodes) {
355        GraphUpdate::Patched => GraphUpdateReport {
356            update: GraphUpdate::Patched,
357            hit_graph_dirty: report.hit_graph_dirty,
358        },
359        update => GraphUpdateReport {
360            update,
361            hit_graph_dirty: true,
362        },
363    }
364}
365
366/// The update a walk made. A dirty node the walk did not find needs the whole
367/// scene built again, unless no layer draws it.
368fn classify_walk(
369    applier: &MemoryApplier,
370    walked: bool,
371    remaining_dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
372) -> GraphUpdate {
373    if !walked {
374        return GraphUpdate::NeedsRebuild(GraphRebuildReason::DirtyLayerUnavailable);
375    }
376    remaining_dirty_nodes.retain(|&node, _| is_drawn(applier, node));
377    if !remaining_dirty_nodes.is_empty() {
378        GraphUpdate::NeedsRebuild(GraphRebuildReason::UnmatchedDirtyNodes(
379            remaining_dirty_nodes.len(),
380        ))
381    } else {
382        GraphUpdate::Patched
383    }
384}
385
386/// Whether the scene draws `node`: each subcompose node above it draws the
387/// slot `node` is in. A slot a lazy list keeps for reuse, or composes before
388/// it shows it, stays in the applier's tree without being drawn, and its
389/// nodes still change.
390fn is_drawn(applier: &MemoryApplier, node: NodeId) -> bool {
391    let mut child = node;
392    let mut current = node;
393    while let Some(parent) = applier
394        .get_ref(current)
395        .ok()
396        .and_then(cranpose_core::Node::parent)
397    {
398        let Ok(parent_node) = applier.get_ref(parent) else {
399            return true;
400        };
401        current = parent;
402        if parent_node.is_virtual() {
403            continue;
404        }
405        let parent_node: &dyn Any = parent_node;
406        if let Some(subcompose) = parent_node.downcast_ref::<SubcomposeLayoutNode>()
407            && !subcompose.with_active_children(|children| children.contains(&child))
408        {
409            return false;
410        }
411        child = parent;
412    }
413    true
414}
415
416#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
417struct ReplaceDirtyLayersReport {
418    updated: bool,
419    hit_graph_dirty: bool,
420}
421
422/// The dirty nodes a scene update has yet to find, and every node above
423/// them in the applier: a layer is lowered from its node's subtree, so a
424/// layer whose node is in neither holds no dirty layer.
425struct DirtyWalk<'a> {
426    nodes: &'a mut HashMap<NodeId, NodeUpdate>,
427    ancestry: &'a NodeMarks,
428    applier: &'a MemoryApplier,
429}
430
431impl DirtyWalk<'_> {
432    fn reborrow(&mut self) -> DirtyWalk<'_> {
433        DirtyWalk {
434            nodes: self.nodes,
435            ancestry: self.ancestry,
436            applier: self.applier,
437        }
438    }
439
440    /// The marks of the layer with `identity`: whether its node is dirty and
441    /// whether its subtree may hold a dirty layer. Both for a layer without
442    /// a node.
443    fn marks(&self, identity: Option<NodeId>) -> u8 {
444        identity.map_or(DIRTY_NODE | DIRTY_ANCESTOR, |id| {
445            self.ancestry.get(self.applier, id)
446        })
447    }
448}
449
450/// The mark of a node above a dirty node.
451const DIRTY_ANCESTOR: u8 = 1;
452/// The mark of a dirty node: a layer whose node has no mark needs no map
453/// lookup.
454const DIRTY_NODE: u8 = 2;
455
456/// Walks from `root` down to the layers of the `dirty` nodes and updates
457/// them. With the report comes the way to the layer of a lone dirty node
458/// the walk updated, for the next update that changes that node alone.
459fn walk_dirty_layers(
460    applier: &MemoryApplier,
461    root: &mut LayerNode,
462    dirty: &mut HashMap<NodeId, NodeUpdate>,
463    update: u64,
464) -> Option<(ReplaceDirtyLayersReport, Option<RetainedPath>)> {
465    let inherited = root.translated_content_context;
466    let root_children = AbsOrigin::ROOT.children_of(root);
467    let lone = match dirty.keys().next() {
468        Some(&node) if dirty.len() == 1 => Some(node),
469        _ => None,
470    };
471    let mut ancestry = crate::thread_state::with(|state| state.dirty_ancestry.take());
472    mark_dirty_ancestry(applier, dirty, &mut ancestry);
473    let mut walk = DirtyWalk {
474        nodes: dirty,
475        ancestry: &ancestry,
476        applier,
477    };
478    let walked = replace_dirty_layers_from_applier(
479        applier,
480        root,
481        root_children,
482        walk.reborrow(),
483        inherited,
484        update,
485    );
486    let path = match (&walked, lone) {
487        (Some(report), Some(node)) if report.updated && walk.nodes.is_empty() => {
488            RetainedPath::to(root, &walk, node, update)
489        }
490        _ => None,
491    };
492    crate::thread_state::with(|state| state.dirty_ancestry.set(ancestry));
493    walked.map(|report| (report, path))
494}
495
496/// The way from the root to the layer of a node that the last scene
497/// updates changed alone, as an animation changes one layer each frame,
498/// and what the layer's parent passes it. The next update that changes that
499/// node alone goes along it instead of walking down from the root: no
500/// other update ran since, so the layers on the way are where they were.
501#[derive(Clone)]
502pub(crate) struct RetainedPath {
503    node: NodeId,
504    /// The index of each layer on the way in its parent's children and the
505    /// identity it had there, from a child of the root to the node's layer.
506    steps: Vec<(usize, Option<NodeId>)>,
507    pass: ParentPass,
508    /// The update that went along the path last.
509    update: u64,
510    /// Whether a layer on the way is a scene, a surface or a 3D transform,
511    /// so a change at the end of the path changes what lies in 3D.
512    in_space: bool,
513}
514
515impl RetainedPath {
516    /// The way to the layer of `node` through the layers `walk` marks, as
517    /// the walk of update `update` went.
518    fn to(root: &LayerNode, walk: &DirtyWalk<'_>, node: NodeId, update: u64) -> Option<Self> {
519        let mut steps = Vec::new();
520        let pass = path_steps(
521            root,
522            AbsOrigin::ROOT.children_of(root),
523            root.translated_content_context,
524            walk,
525            node,
526            &mut steps,
527        )?;
528        let in_space = root.graphics_layer.space.is_some() || {
529            let mut layer = root;
530            steps
531                .iter()
532                .any(|&(index, _)| match layer.children.get(index) {
533                    Some(RenderNode::Layer(child)) => {
534                        layer = child;
535                        child.graphics_layer.space.is_some()
536                    }
537                    _ => false,
538                })
539        };
540        Some(Self {
541            node,
542            steps,
543            pass,
544            update,
545            in_space,
546        })
547    }
548}
549
550/// Appends to `steps` the way from `parent`, whose children sit at
551/// `parent_children`, to the layer of `node`, searching the children as the
552/// dirty walk does, and returns what that layer's parent passes it.
553fn path_steps(
554    parent: &LayerNode,
555    parent_children: AbsOrigin,
556    inherited_translated_content_context: bool,
557    walk: &DirtyWalk<'_>,
558    node: NodeId,
559    steps: &mut Vec<(usize, Option<NodeId>)>,
560) -> Option<ParentPass> {
561    let translated = inherited_translated_content_context || parent.translated_content_context;
562    for (index, child) in parent.children.iter().enumerate() {
563        let RenderNode::Layer(layer) = child else {
564            continue;
565        };
566        let identity = layer_identity(layer);
567        if identity == Some(node) {
568            steps.push((index, identity));
569            return Some(ParentPass::of(parent, parent_children, translated));
570        }
571        if walk.marks(identity) & DIRTY_ANCESTOR == 0 {
572            continue;
573        }
574        steps.push((index, identity));
575        let children = parent_children.children_of(layer);
576        if let Some(pass) = path_steps(layer, children, translated, walk, node, steps) {
577            return Some(pass);
578        }
579        steps.pop();
580    }
581    None
582}
583
584/// Updates the lone dirty node in `dirty` along `path` when the path leads
585/// to it: `None` when it does not, and nothing changed. Otherwise the
586/// update's report, `None` when the node's data is gone.
587fn update_along_retained_path(
588    applier: &MemoryApplier,
589    root: &mut LayerNode,
590    path: &RetainedPath,
591    dirty: &mut HashMap<NodeId, NodeUpdate>,
592    update: u64,
593) -> Option<Option<ReplaceDirtyLayersReport>> {
594    if dirty.len() != 1 || path.update + 1 != update || !leads_to(root, &path.steps) {
595        return None;
596    }
597    let kind = dirty.remove(&path.node)?;
598    if path.in_space {
599        note_space_change();
600    }
601    Some(update_at_step(
602        applier,
603        root,
604        path,
605        &path.steps,
606        kind,
607        dirty,
608        update,
609    ))
610}
611
612/// Whether each step of `steps` from `layer` finds the layer it names.
613fn leads_to(mut layer: &LayerNode, steps: &[(usize, Option<NodeId>)]) -> bool {
614    for &(index, identity) in steps {
615        match layer.children.get(index) {
616            Some(RenderNode::Layer(child)) if layer_identity(child) == identity => layer = child,
617            _ => return false,
618        }
619    }
620    !steps.is_empty()
621}
622
623/// Goes down `steps` from `layer` to the path's layer, updates it for
624/// `kind`, and brings the facts of each layer on the way up to date.
625fn update_at_step(
626    applier: &MemoryApplier,
627    layer: &mut LayerNode,
628    path: &RetainedPath,
629    steps: &[(usize, Option<NodeId>)],
630    kind: NodeUpdate,
631    dirty: &mut HashMap<NodeId, NodeUpdate>,
632    update: u64,
633) -> Option<ReplaceDirtyLayersReport> {
634    let (&(index, _), rest) = steps.split_first()?;
635    let Some(RenderNode::Layer(child)) = layer.children.get_mut(index) else {
636        return None;
637    };
638    let hit_graph_dirty = if rest.is_empty() {
639        update_dirty_layer(applier, child, path.node, kind, dirty, path.pass, update)?.0
640    } else {
641        update_at_step(applier, child, path, rest, kind, dirty, update)?.hit_graph_dirty
642    };
643    note_updated_children(layer, update);
644    Some(ReplaceDirtyLayersReport {
645        updated: true,
646        hit_graph_dirty,
647    })
648}
649
650fn mark_dirty_ancestry(
651    applier: &MemoryApplier,
652    dirty: &HashMap<NodeId, NodeUpdate>,
653    ancestry: &mut NodeMarks,
654) {
655    ancestry.reset(applier);
656    for &node in dirty.keys() {
657        ancestry.add(applier, node, DIRTY_NODE);
658        let mut current = node;
659        while let Some(parent) = applier
660            .get_ref(current)
661            .ok()
662            .and_then(cranpose_core::Node::parent)
663        {
664            if ancestry.add(applier, parent, DIRTY_ANCESTOR) & DIRTY_ANCESTOR != 0 {
665                break;
666            }
667            current = parent;
668        }
669    }
670}
671
672fn replace_dirty_layers_from_applier(
673    applier: &MemoryApplier,
674    parent: &mut LayerNode,
675    parent_children: AbsOrigin,
676    mut dirty: DirtyWalk<'_>,
677    inherited_translated_content_context: bool,
678    update: u64,
679) -> Option<ReplaceDirtyLayersReport> {
680    if dirty.nodes.is_empty() {
681        return Some(ReplaceDirtyLayersReport::default());
682    }
683
684    let child_inherited_translated_content_context =
685        inherited_translated_content_context || parent.translated_content_context;
686    let pass = ParentPass::of(
687        parent,
688        parent_children,
689        child_inherited_translated_content_context,
690    );
691    let mut report = ReplaceDirtyLayersReport::default();
692
693    for child in &mut parent.children {
694        if dirty.nodes.is_empty() {
695            break;
696        }
697        let RenderNode::Layer(child_layer) = child else {
698            continue;
699        };
700
701        let identity = layer_identity(child_layer);
702        let marks = dirty.marks(identity);
703        if marks == 0 {
704            continue;
705        }
706        // A dirty layer in 3D, or one above a dirty layer, changes what lies
707        // in 3D.
708        if child_layer.graphics_layer.space.is_some() {
709            note_space_change();
710        }
711        if marks & DIRTY_NODE != 0
712            && let Some(node_id) = identity
713            && let Some(kind) = dirty.nodes.remove(&node_id)
714        {
715            let (hit_graph_dirty, retained) = update_dirty_layer(
716                applier,
717                child_layer,
718                node_id,
719                kind,
720                dirty.nodes,
721                pass,
722                update,
723            )?;
724            report.hit_graph_dirty |= hit_graph_dirty;
725            report.updated = true;
726            if retained {
727                let child_children = parent_children.children_of(child_layer);
728                let child_report = replace_dirty_layers_from_applier(
729                    applier,
730                    child_layer,
731                    child_children,
732                    dirty.reborrow(),
733                    child_inherited_translated_content_context,
734                    update,
735                )?;
736                report.hit_graph_dirty |= child_report.hit_graph_dirty;
737            }
738            continue;
739        }
740
741        if marks & DIRTY_ANCESTOR == 0 {
742            continue;
743        }
744        let child_children = parent_children.children_of(child_layer);
745        let child_report = replace_dirty_layers_from_applier(
746            applier,
747            child_layer,
748            child_children,
749            dirty.reborrow(),
750            child_inherited_translated_content_context,
751            update,
752        )?;
753        report.updated |= child_report.updated;
754        report.hit_graph_dirty |= child_report.hit_graph_dirty;
755    }
756
757    if report.updated {
758        note_updated_children(parent, update);
759    }
760
761    Some(report)
762}
763
764/// Brings `parent`'s facts about its children up to date after scene update
765/// `update` changed a layer beneath it.
766fn note_updated_children(parent: &mut LayerNode, update: u64) {
767    parent.refresh_child_facts();
768    // A layer's own sinks are not kept apart from its children's, so a
769    // child that stopped publishing leaves the flag set, which only keeps
770    // the scroll fast path off; a child that started must set it.
771    parent.has_origin_sinks |= children_have_origin_sinks(&parent.children);
772    parent.note_descendant_change(update);
773}
774
775/// What a layer passes the children it lays out to a scene update that
776/// changes one of them.
777#[derive(Clone, Copy)]
778pub(crate) struct ParentPass {
779    motion_context_animated: bool,
780    translated_content_context: bool,
781    content_offset: Point,
782    children: AbsOrigin,
783}
784
785impl ParentPass {
786    /// What `parent`, whose children sit at `children`, passes them, where
787    /// the layers above it set `translated_content_context`.
788    fn of(parent: &LayerNode, children: AbsOrigin, translated_content_context: bool) -> Self {
789        Self {
790            motion_context_animated: parent.motion_context_animated,
791            translated_content_context,
792            content_offset: parent.content_offset,
793            children,
794        }
795    }
796}
797
798/// Updates `layer`, the layer of the dirty node `node_id`, for the change
799/// `kind`: in place when the layer can keep what it drew, else lowered
800/// again from the node. Returns whether hit geometry changed and whether
801/// the layer was kept, whose children may hold dirty layers yet; a layer
802/// lowered again drops the dirty nodes beneath it from `dirty_nodes`.
803/// `None` when the node's data is gone.
804fn update_dirty_layer(
805    applier: &MemoryApplier,
806    layer: &mut LayerNode,
807    node_id: NodeId,
808    kind: NodeUpdate,
809    dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
810    pass: ParentPass,
811    update: u64,
812) -> Option<(bool, bool)> {
813    if let Some(hit_graph_dirty) = try_update_retained_layer(
814        applier,
815        layer,
816        dirty_nodes,
817        kind,
818        false,
819        TranslateAncestorContext {
820            inherited_motion_context_animated: pass.motion_context_animated,
821            update,
822            inherited_translated_content_context: pass.translated_content_context,
823            parent_content_offset: pass.content_offset,
824            parent_abs: pass.children,
825        },
826    ) {
827        return Some((hit_graph_dirty, true));
828    }
829    // Nodes that leave this subtree are gone from the scene: nothing
830    // remains to update for them, and a dirty one would otherwise go
831    // unmatched and rebuild the whole scene.
832    remove_dirty_descendants(layer, dirty_nodes);
833    let context = LowerContext {
834        inherited_motion_context_animated: pass.motion_context_animated,
835        inherited_translated_content_context: pass.translated_content_context,
836        parent_abs: Some(pass.children),
837        parent_content_offset: pass.content_offset,
838    };
839    let hit_graph_dirty = rebuild_tracking_hits(layer, |layer| {
840        release_for_rebuild(layer);
841        read_placed_node_data(applier, node_id, false, |data| {
842            write_node_layer(applier, node_id, data, context, layer);
843        })
844        .is_some()
845    })?;
846    remove_dirty_descendants(layer, dirty_nodes);
847    layer.note_content_change(update);
848    Some((hit_graph_dirty, false))
849}
850
851fn translate_bail(reason: &str) -> bool {
852    if cranpose_core::env_flag!("CRANPOSE_SCENE_UPDATE_DIAG") {
853        eprintln!("[scene-update-diag] translate bail: {reason}");
854    }
855    false
856}
857
858#[derive(Clone, Copy)]
859struct TranslateAncestorContext {
860    inherited_motion_context_animated: bool,
861    /// The scene update in progress.
862    update: u64,
863    inherited_translated_content_context: bool,
864    parent_content_offset: Point,
865    parent_abs: AbsOrigin,
866}
867
868fn try_update_retained_layer(
869    applier: &MemoryApplier,
870    layer: &mut LayerNode,
871    dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
872    kind: NodeUpdate,
873    root: bool,
874    ancestors: TranslateAncestorContext,
875) -> Option<bool> {
876    match kind {
877        NodeUpdate::Moved if !root => {
878            move_retained_layer(applier, layer, ancestors).or_else(|| {
879                try_translate_scrolled_layer(applier, layer, dirty_nodes, ancestors).then_some(true)
880            })
881        }
882        NodeUpdate::Content | NodeUpdate::Moved => {
883            try_translate_scrolled_layer(applier, layer, dirty_nodes, ancestors).then_some(true)
884        }
885        NodeUpdate::Layer => update_layer_properties(applier, layer, root, false, ancestors),
886        NodeUpdate::MovedLayer => update_layer_properties(applier, layer, root, true, ancestors),
887    }
888}
889
890/// Moves the layer of a node that only moved in its parent, keeping what it
891/// drew, and returns whether hit geometry changed. A subtree that publishes
892/// window origins publishes them again from where it now lies. `None` when
893/// the layer cannot keep its content: its size changed.
894fn move_retained_layer(
895    applier: &MemoryApplier,
896    layer: &mut LayerNode,
897    ancestors: TranslateAncestorContext,
898) -> Option<bool> {
899    let placed = placed_child_geometry(applier, layer_identity(layer)?)?;
900    if Rect::from_size(placed.size) != layer.node_rect() {
901        return None;
902    }
903    let previous = HitGraphState::of(layer);
904    translate_retained_child(layer, placed.position, ancestors.parent_content_offset);
905    if layer.has_origin_sinks {
906        publish_retained_window_geometry(applier, layer, ancestors.parent_abs);
907    }
908    Some(previous.dirty_against(&HitGraphState::of(layer)))
909}
910
911fn content_layer_mut(container: &mut LayerNode) -> Option<&mut LayerNode> {
912    let Some(node_id) = container.wraps else {
913        return Some(container);
914    };
915    container.children.iter_mut().find_map(|child| match child {
916        RenderNode::Layer(layer) if layer.node_id == Some(node_id) => Some(&mut **layer),
917        _ => None,
918    })
919}
920
921/// Whether `layer` still fits its node's layout, so new layer properties
922/// can update it in place: at `placement`, unless the node `moved`.
923fn retained_layer_matches_layout(
924    layer: &LayerNode,
925    data: &SnapshotNodeData<'_>,
926    placement: Point,
927    wrapped: bool,
928    moved: bool,
929) -> bool {
930    let state = &data.layout_state;
931    let slices = &data.modifier_slices;
932    (moved || state.position() == placement)
933        && Rect::from_size(state.size()) == layer.node_rect()
934        && slices.layer_bounds(state.size()) == layer.local_bounds
935        && state.content_offset() == layer.content_offset
936        && (slices.outer_draw_command_count() > 0) == wrapped
937}
938
939fn update_layer_properties(
940    applier: &MemoryApplier,
941    container: &mut LayerNode,
942    root: bool,
943    moved: bool,
944    ancestors: TranslateAncestorContext,
945) -> Option<bool> {
946    let node_id = layer_identity(container)?;
947    let wrapped = container.wraps.is_some();
948    // The wrapper of a node's outer draws holds its placement; a moved
949    // wrapped node is drawn again.
950    if moved && wrapped {
951        return None;
952    }
953    let placement = container.origin_in_parent;
954    let hit_graph_dirty = read_placed_node_data(applier, node_id, root, |data| {
955        let target = content_layer_mut(container)?;
956        let slices = &data.modifier_slices;
957        let state = &data.layout_state;
958        if !retained_layer_matches_layout(target, &data, placement, wrapped, moved) {
959            return None;
960        }
961        let context = LowerContext {
962            inherited_motion_context_animated: ancestors.inherited_motion_context_animated,
963            inherited_translated_content_context: ancestors.inherited_translated_content_context,
964            parent_abs: Some(ancestors.parent_abs),
965            parent_content_offset: ancestors.parent_content_offset,
966        };
967        let previous = HitGraphState::of(target);
968        let (head, child_context) = prepare_node_layer(node_id, slices, state, context);
969        assign_layer(target, head);
970        if wrapped {
971            target.transform_to_parent = layer_transform_to_parent(
972                target.local_bounds,
973                Point::default(),
974                &target.graphics_layer,
975            );
976            target.origin_in_parent = Point::default();
977        }
978        if target.has_origin_sinks
979            && let Some(child_abs) = child_context.parent_abs
980        {
981            for child in &target.children {
982                if let RenderNode::Layer(child) = child {
983                    publish_retained_window_geometry(applier, child, child_abs);
984                }
985            }
986        }
987        Some(previous.dirty_against(&HitGraphState::of(target)))
988    })??;
989    if wrapped {
990        container.refresh_child_facts();
991        container.has_origin_sinks = children_have_origin_sinks(&container.children);
992        container.forget_own_raster_cache_hashes();
993    }
994    Some(hit_graph_dirty)
995}
996
997/// After a scene build or update that changed a layer in 3D, publishes
998/// where the window's scenes show the layers on their surfaces that publish
999/// window origins, as the eye sees them, and leaves each scene's eye the
1000/// world it walks in. A flat window asks nothing of its layers.
1001pub(crate) fn publish_space(applier: &MemoryApplier, graph: &RenderGraph) {
1002    if !crate::thread_state::with(|state| state.space_changed.replace(false)) {
1003        return;
1004    }
1005    publish_space_now(applier, graph);
1006}
1007
1008#[cold]
1009#[inline(never)]
1010fn publish_space_now(applier: &MemoryApplier, graph: &RenderGraph) {
1011    let root = ProjectiveTransform::identity();
1012    crate::space::for_each_window_eye(&graph.root, root, &mut |tree, view, to_window| {
1013        if let Some(eye) = &tree.cells[0].scene.eye {
1014            eye.world.rebuild(|world| view.build_world(tree, world));
1015        }
1016        for seen in view
1017            .surfaces
1018            .iter()
1019            .filter(|seen| seen.layer.has_origin_sinks)
1020        {
1021            if let Some(content) = view.content_to_window(seen, to_window) {
1022                publish_content_geometry(applier, seen.layer, content);
1023            }
1024        }
1025    });
1026}
1027
1028/// Publishes the window geometry of `layer`, whose local space `to_window`
1029/// maps to the window, and of the layers on it that publish window origins,
1030/// as far as the next layer in 3D.
1031fn publish_content_geometry(
1032    applier: &MemoryApplier,
1033    layer: &LayerNode,
1034    to_window: ProjectiveTransform,
1035) {
1036    if let Some(node_id) = layer.node_id {
1037        read_node_data(applier, node_id, |data| {
1038            data.modifier_slices.publish_window_geometry(
1039                Point::default(),
1040                to_window,
1041                data.layout_state.size(),
1042            );
1043        });
1044    }
1045    for child in &layer.children {
1046        if let RenderNode::Layer(child) = child
1047            && child.has_origin_sinks
1048            && child.graphics_layer.space.is_none()
1049        {
1050            publish_content_geometry(applier, child, child.transform_to_parent.then(to_window));
1051        }
1052    }
1053}
1054
1055fn publish_retained_window_geometry(
1056    applier: &MemoryApplier,
1057    layer: &LayerNode,
1058    parent_abs: AbsOrigin,
1059) {
1060    if !layer.has_origin_sinks {
1061        return;
1062    }
1063    let child_abs = parent_abs.children_of(layer);
1064    if let Some(node_id) = layer.node_id {
1065        read_node_data(applier, node_id, |data| {
1066            data.modifier_slices.publish_window_geometry(
1067                Point {
1068                    x: parent_abs.content_origin.x + layer.origin_in_parent.x,
1069                    y: parent_abs.content_origin.y + layer.origin_in_parent.y,
1070                },
1071                child_abs.window_transform,
1072                data.layout_state.size(),
1073            );
1074        });
1075    }
1076    for child in &layer.children {
1077        if let RenderNode::Layer(child) = child {
1078            publish_retained_window_geometry(applier, child, child_abs);
1079        }
1080    }
1081}
1082
1083fn try_translate_scrolled_layer(
1084    applier: &MemoryApplier,
1085    container: &mut LayerNode,
1086    dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
1087    ancestors: TranslateAncestorContext,
1088) -> bool {
1089    let Some(node_id) = layer_identity(container) else {
1090        return translate_bail("no node id");
1091    };
1092    read_node_data(applier, node_id, |data| {
1093        if container.wraps.is_none() {
1094            return translate_layer_from_data(
1095                applier,
1096                container,
1097                dirty_nodes,
1098                ancestors,
1099                data,
1100                false,
1101            );
1102        }
1103        let outer_count = data.modifier_slices.outer_draw_command_count();
1104        if outer_count == 0 {
1105            return translate_bail("outer draws removed");
1106        }
1107        let size = data.layout_state.size();
1108        let placement = data.layout_state.position();
1109        let slices = Rc::clone(&data.modifier_slices);
1110        let Some(inner) = content_layer_mut(container) else {
1111            return translate_bail("wrapped layer missing");
1112        };
1113        if !translate_layer_from_data(applier, inner, dirty_nodes, ancestors, data, true) {
1114            return false;
1115        }
1116        let draws = DrawTarget {
1117            size,
1118            motion_context_animated: inner.motion_context_animated,
1119            layer: None,
1120        };
1121        let outer = outer_draws(node_id, slices.draw_commands(), outer_count, draws)
1122            .expect("outer command count is nonzero");
1123        let layer =
1124            take_wrapped_layer(container, node_id).expect("the wrapped layer was found above");
1125        write_wrapper(
1126            container,
1127            layer,
1128            placement,
1129            outer,
1130            ancestors.parent_content_offset,
1131        );
1132        container.note_content_change(ancestors.update);
1133        true
1134    })
1135    .unwrap_or_else(|| translate_bail("container snapshot read failed"))
1136}
1137
1138/// The node of a container that keeps its children, and its node bounds at
1139/// its current size.
1140struct TranslatedContainer {
1141    node_id: NodeId,
1142    node_bounds: Option<Rect>,
1143}
1144
1145/// Checks that `container` can keep its children and brings its bounds and
1146/// graphics layer to its current size: a container that kept its size keeps
1147/// its layer as it is.
1148fn translated_container(
1149    container: &mut LayerNode,
1150    layout_state: &cranpose_ui::widgets::LayoutState,
1151    modifier_slices: &ModifierNodeSlices,
1152    inherited_motion_context_animated: bool,
1153    wrapped: bool,
1154) -> Result<TranslatedContainer, &'static str> {
1155    if cranpose_core::env_flag!("CRANPOSE_DISABLE_SCROLL_TRANSLATE") {
1156        return Err("fast path disabled by ablation switch");
1157    }
1158    let Some(node_id) = container.node_id else {
1159        return Err("no node id");
1160    };
1161    if !layout_state.is_placed() {
1162        return Err("container unplaced");
1163    }
1164    let outer_count = modifier_slices.outer_draw_command_count();
1165    if (outer_count > 0 && !wrapped)
1166        || (inherited_motion_context_animated || modifier_slices.motion_context_animated())
1167            != container.motion_context_animated
1168        || modifier_slices.translated_content_context() != container.translated_content_context
1169    {
1170        return Err("container outer draws or translated context changed");
1171    }
1172    let clip_to_bounds = modifier_slices.clip_to_bounds();
1173    if clip_to_bounds != container.clip_to_bounds {
1174        return Err("container clip changed");
1175    }
1176    // A resized container keeps its children: only its bounds and the clip
1177    // shaped to them follow its size. Any other change to its graphics layer
1178    // may change what its children inherit.
1179    if !container
1180        .graphics_layer
1181        .reads_as(shaped_graphics_layer(modifier_slices, container.local_bounds).as_deref())
1182    {
1183        return Err("container graphics layer changed");
1184    }
1185    let (local_bounds, node_bounds) = layer_and_node_bounds(modifier_slices, layout_state.size());
1186    if local_bounds != container.local_bounds {
1187        container.local_bounds = local_bounds;
1188        container
1189            .graphics_layer
1190            .replace(shaped_graphics_layer(modifier_slices, local_bounds).map(Cow::into_owned));
1191    }
1192    Ok(TranslatedContainer {
1193        node_id,
1194        node_bounds,
1195    })
1196}
1197
1198/// What a scroll step reconciles a container's children in, kept between
1199/// steps so a step allocates nothing of its own. Every list is empty
1200/// between steps, so it holds no layer.
1201#[derive(Default)]
1202pub(crate) struct TranslateScratch {
1203    /// The placed children, in their new order.
1204    placed_fresh: Vec<PlacedChild>,
1205    /// The ids of the container's previous children, in their order.
1206    old_ids: Vec<NodeId>,
1207    old_index_by_id: HashMap<NodeId, usize>,
1208    /// The container's previous children by their index, until each is
1209    /// kept or recycled.
1210    retained: Vec<Option<Box<LayerNode>>>,
1211    /// The previous children that stay, in their new order; `None` where a
1212    /// child enters.
1213    kept: Vec<Option<Box<LayerNode>>>,
1214    /// The children built for the step, in their new order.
1215    entering: Vec<(NodeId, Box<LayerNode>)>,
1216}
1217
1218/// A placed child of a container a scroll step translates.
1219struct PlacedChild {
1220    id: NodeId,
1221    geometry: PlacedGeometry,
1222    /// The child's index among the container's previous children; `None`
1223    /// for a child that enters.
1224    old_index: Option<usize>,
1225    /// Whether the child keeps its layer and follows its placement: it was
1226    /// not dirty, it only moved, which this step settles, or its layer
1227    /// properties changed, which the walk beneath the container settles.
1228    keeps_layer: bool,
1229}
1230
1231/// How a translated container's placed children stand to its previous ones.
1232enum ChildOrder {
1233    /// The kept children lie together and in their order, at `kept` among
1234    /// the previous children, and the entering ones come before or after
1235    /// them: `entering_before` of the placed children, then the kept ones,
1236    /// then the rest. A scroll leaves its children so, and so do unchanged
1237    /// children.
1238    Shifted {
1239        kept: Range<usize>,
1240        entering_before: usize,
1241    },
1242    /// The kept children changed their order, or children enter between
1243    /// them.
1244    Mixed,
1245}
1246
1247/// Fills `scratch` with `container`'s placed children, each with its index
1248/// among the previous ones, and returns how they stand to those.
1249fn translated_children(
1250    applier: &MemoryApplier,
1251    container: &LayerNode,
1252    dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
1253    fresh_children: &[NodeId],
1254    scratch: &mut TranslateScratch,
1255) -> Result<ChildOrder, &'static str> {
1256    let placed_fresh = &mut scratch.placed_fresh;
1257    placed_fresh.clear();
1258    for &id in fresh_children {
1259        let Some(geometry) = placed_child_geometry(applier, id) else {
1260            continue;
1261        };
1262        placed_fresh.push(PlacedChild {
1263            id,
1264            geometry,
1265            old_index: None,
1266            keeps_layer: false,
1267        });
1268    }
1269    let children_unchanged = container.children.len() == placed_fresh.len()
1270        && container
1271            .children
1272            .iter()
1273            .zip(placed_fresh.iter())
1274            .all(|(child, placed)| {
1275                matches!(child, RenderNode::Layer(layer) if layer_identity(layer) == Some(placed.id))
1276            });
1277    let order = if children_unchanged {
1278        for (index, placed) in placed_fresh.iter_mut().enumerate() {
1279            placed.old_index = Some(index);
1280        }
1281        ChildOrder::Shifted {
1282            kept: 0..placed_fresh.len(),
1283            entering_before: 0,
1284        }
1285    } else {
1286        let old_ids = &mut scratch.old_ids;
1287        old_ids.clear();
1288        let (mut lowest, mut highest) = (NodeId::MAX, NodeId::MIN);
1289        for child in &container.children {
1290            let RenderNode::Layer(layer) = child else {
1291                return Err("child without node id");
1292            };
1293            let Some(id) = layer_identity(layer) else {
1294                return Err("child without node id");
1295            };
1296            lowest = lowest.min(id);
1297            highest = highest.max(id);
1298            old_ids.push(id);
1299        }
1300        match_old_children(
1301            old_ids,
1302            lowest..=highest,
1303            placed_fresh,
1304            &mut scratch.old_index_by_id,
1305        )
1306    };
1307    check_retained_children(container, dirty_nodes, placed_fresh).map(|()| order)
1308}
1309
1310/// The searches of the previous children a match makes before it maps them:
1311/// a search reads each id once, a map hashes each of them in.
1312const MATCH_SEARCHES: usize = 16;
1313
1314/// Notes each placed child's index among the previous children `old_ids`,
1315/// whose ids lie in `old_id_range`.
1316/// Children that kept their order match in turn, as a scroll that brings
1317/// rows in at one end and lets them go at the other leaves them; a child
1318/// past a gap is searched for, and the match continues after it. A child
1319/// whose id lies outside the range enters without a search: a node made
1320/// since the previous children takes an id past all of theirs. Once the
1321/// order is mixed past [`MATCH_SEARCHES`] searches, `old_index_by_id` maps
1322/// the previous children for the rest. An id is a node's, so it is once
1323/// among the previous children. Returns how the placed children stand to
1324/// the previous ones.
1325fn match_old_children(
1326    old_ids: &[NodeId],
1327    old_id_range: RangeInclusive<NodeId>,
1328    placed_fresh: &mut [PlacedChild],
1329    old_index_by_id: &mut HashMap<NodeId, usize>,
1330) -> ChildOrder {
1331    let mut cursor = 0;
1332    let mut searches = 0;
1333    let mut mapped = false;
1334    let mut kept: Option<Range<usize>> = None;
1335    let mut entering_before = 0;
1336    let mut entering_after = 0;
1337    let mut shifted = true;
1338    for placed in placed_fresh {
1339        let old_index = if old_ids.get(cursor) == Some(&placed.id) {
1340            Some(cursor)
1341        } else if !old_id_range.contains(&placed.id) {
1342            None
1343        } else if searches < MATCH_SEARCHES {
1344            searches += 1;
1345            old_ids.iter().position(|&id| id == placed.id)
1346        } else {
1347            if !mapped {
1348                old_index_by_id.clear();
1349                old_index_by_id.extend(old_ids.iter().enumerate().map(|(index, &id)| (id, index)));
1350                mapped = true;
1351            }
1352            old_index_by_id.get(&placed.id).copied()
1353        };
1354        match (old_index, kept.as_mut()) {
1355            (Some(index), Some(kept)) => {
1356                shifted &= entering_after == 0 && index == kept.end;
1357                kept.end = index + 1;
1358            }
1359            (Some(index), None) => kept = Some(index..index + 1),
1360            (None, Some(_)) => entering_after += 1,
1361            (None, None) => entering_before += 1,
1362        }
1363        if let Some(index) = old_index {
1364            cursor = index + 1;
1365        }
1366        placed.old_index = old_index;
1367    }
1368    if shifted {
1369        ChildOrder::Shifted {
1370            kept: kept.unwrap_or(0..0),
1371            entering_before,
1372        }
1373    } else {
1374        ChildOrder::Mixed
1375    }
1376}
1377
1378/// Checks that every child kept as it is still fits its layer, and notes
1379/// which children keep their layers. A moved child that no longer fits is
1380/// drawn again: it becomes a content update. A moved child that fits leaves
1381/// the dirty set, which this step settles.
1382fn check_retained_children(
1383    container: &LayerNode,
1384    dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
1385    placed_fresh: &mut [PlacedChild],
1386) -> Result<(), &'static str> {
1387    for placed in placed_fresh {
1388        let Some(old_index) = placed.old_index else {
1389            continue;
1390        };
1391        let RenderNode::Layer(layer) = &container.children[old_index] else {
1392            return Err("retained child slot is not a layer");
1393        };
1394        let fits = Rect::from_size(placed.geometry.size) == layer.node_rect();
1395        placed.keeps_layer = match dirty_nodes.get_mut(&placed.id) {
1396            None if !fits => return Err("child resized"),
1397            None => true,
1398            Some(kind @ (NodeUpdate::Moved | NodeUpdate::MovedLayer)) if !fits => {
1399                *kind = NodeUpdate::Content;
1400                false
1401            }
1402            Some(NodeUpdate::Moved) => {
1403                dirty_nodes.remove(&placed.id);
1404                true
1405            }
1406            // The child moves with the others; the walk beneath the
1407            // container then gives it its new layer properties where it
1408            // now lies.
1409            Some(NodeUpdate::Layer) => fits,
1410            Some(NodeUpdate::Content | NodeUpdate::MovedLayer) => false,
1411        };
1412    }
1413    Ok(())
1414}
1415
1416#[derive(Clone, Copy)]
1417struct TranslateGeometry {
1418    content_offset: Point,
1419    window_transform: ProjectiveTransform,
1420    top_left: Point,
1421    child_origin: Point,
1422}
1423
1424impl TranslateGeometry {
1425    fn new(
1426        layout_state: &cranpose_ui::widgets::LayoutState,
1427        graphics_layer: &GraphicsLayer,
1428        local_bounds: Rect,
1429        parent_abs: AbsOrigin,
1430    ) -> Self {
1431        let content_offset = layout_state.content_offset();
1432        let top_left = Point {
1433            x: parent_abs.content_origin.x + layout_state.position().x,
1434            y: parent_abs.content_origin.y + layout_state.position().y,
1435        };
1436        let window_transform = layer_transform_to_window(
1437            parent_abs.window_transform,
1438            top_left,
1439            local_bounds,
1440            graphics_layer,
1441        );
1442        Self {
1443            content_offset,
1444            window_transform,
1445            top_left,
1446            child_origin: Point {
1447                x: top_left.x + content_offset.x,
1448                y: top_left.y + content_offset.y,
1449            },
1450        }
1451    }
1452
1453    fn child_abs(self) -> AbsOrigin {
1454        AbsOrigin {
1455            content_origin: self.child_origin,
1456            window_transform: self.window_transform,
1457        }
1458    }
1459}
1460
1461/// Moves `container`'s previous children that stay into `scratch.kept`, in
1462/// their new order, and the subtrees of those that leave into the layer
1463/// pool, so the entering children are built in their allocations.
1464/// Recycles the children that leave `container`. Their nodes leave the
1465/// dirty set with them: nothing remains to update for a node the scene no
1466/// longer shows, and one left dirty would go unmatched and rebuild the whole
1467/// scene.
1468fn recycle_leaving_children(
1469    container: &mut LayerNode,
1470    scratch: &mut TranslateScratch,
1471    dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
1472) {
1473    let TranslateScratch {
1474        placed_fresh,
1475        retained,
1476        kept,
1477        ..
1478    } = scratch;
1479    retained.clear();
1480    retained.extend(container.children.drain(..).map(|child| match child {
1481        RenderNode::Layer(layer) => Some(layer),
1482        RenderNode::Primitive(_) | RenderNode::DrawRun(_) => None,
1483    }));
1484    kept.clear();
1485    kept.extend(
1486        placed_fresh
1487            .iter()
1488            .map(|placed| placed.old_index.and_then(|index| retained[index].take())),
1489    );
1490    for leaving in retained.drain(..).flatten() {
1491        forget_dirty_subtree(&leaving, dirty_nodes);
1492        crate::layer_recycling::recycle(leaving);
1493    }
1494}
1495
1496/// Recycles the children that leave `container` around the `kept` ones,
1497/// which stay where they lie, already in their new order. Their nodes
1498/// leave the dirty set with them, as in [`recycle_leaving_children`].
1499fn recycle_children_around(
1500    container: &mut LayerNode,
1501    kept: Range<usize>,
1502    dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
1503) {
1504    let mut recycle = |leaving| {
1505        if let RenderNode::Layer(leaving) = leaving {
1506            forget_dirty_subtree(&leaving, dirty_nodes);
1507            crate::layer_recycling::recycle(leaving);
1508        }
1509    };
1510    // Most steps keep every child.
1511    if kept.end < container.children.len() {
1512        container.children.drain(kept.end..).for_each(&mut recycle);
1513    }
1514    if kept.start > 0 {
1515        container.children.drain(..kept.start).for_each(recycle);
1516    }
1517}
1518
1519/// Builds the children that enter `container`. They are built from the
1520/// applier's current state, so their nodes leave the dirty set: the walk
1521/// beneath would otherwise build each of them a second time.
1522fn build_entering_children(
1523    applier: &MemoryApplier,
1524    container: &LayerNode,
1525    scratch: &mut TranslateScratch,
1526    dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
1527    geometry: TranslateGeometry,
1528    inherited: (bool, u64),
1529) {
1530    let (child_inherited_translated_content_context, update) = inherited;
1531    let entering = &mut scratch.entering;
1532    entering.clear();
1533    for placed in &scratch.placed_fresh {
1534        if placed.old_index.is_some() {
1535            continue;
1536        }
1537        let child_id = &placed.id;
1538        let context = LowerContext {
1539            inherited_motion_context_animated: container.motion_context_animated,
1540            inherited_translated_content_context: child_inherited_translated_content_context,
1541            parent_abs: Some(geometry.child_abs()),
1542            parent_content_offset: geometry.content_offset,
1543        };
1544        let Some(mut lowered) = lower_child(applier, *child_id, context) else {
1545            continue;
1546        };
1547        lowered.note_content_change(update);
1548        forget_dirty_subtree(&lowered, dirty_nodes);
1549        entering.push((*child_id, lowered));
1550    }
1551}
1552
1553/// Records `container`'s own draws and text around the child layers its list
1554/// holds, in the order [`write_node_content`] writes a layer.
1555fn write_own_content_around_children(
1556    container: &mut LayerNode,
1557    node_id: NodeId,
1558    modifier_slices: &ModifierNodeSlices,
1559    size: Size,
1560) {
1561    if modifier_slices.draw_commands()[modifier_slices.outer_draw_command_count()..].is_empty()
1562        && !modifier_slices.has_text()
1563    {
1564        return;
1565    }
1566    let kept = container.children.len();
1567    let draws = DrawTarget {
1568        size,
1569        motion_context_animated: container.motion_context_animated,
1570        layer: container.graphics_layer.get(),
1571    };
1572    write_node_content(
1573        &mut container.children,
1574        node_id,
1575        modifier_slices,
1576        draws,
1577        (kept, 0),
1578        |_| {},
1579    );
1580}
1581
1582fn apply_translated_container_state(
1583    container: &mut LayerNode,
1584    modifier_slices: &ModifierNodeSlices,
1585    layout_state: &cranpose_ui::widgets::LayoutState,
1586    parent_content_offset: Point,
1587    geometry: TranslateGeometry,
1588) {
1589    container.transform_to_parent = placed_transform(
1590        container.local_bounds,
1591        layout_state.position(),
1592        container.graphics_layer.get(),
1593        parent_content_offset,
1594    );
1595    container.content_offset = geometry.content_offset;
1596    if container.translated_content_context {
1597        container.translated_content_offset = modifier_slices
1598            .translated_content_offset()
1599            .unwrap_or(geometry.content_offset);
1600    }
1601    modifier_slices.publish_window_geometry(
1602        geometry.top_left,
1603        geometry.window_transform,
1604        layout_state.size(),
1605    );
1606    container.origin_in_parent = layout_state.position();
1607}
1608
1609fn reconcile_translated_children(
1610    applier: &MemoryApplier,
1611    container: &mut LayerNode,
1612    dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
1613    scratch: &mut TranslateScratch,
1614    order: ChildOrder,
1615    geometry: TranslateGeometry,
1616) {
1617    if let ChildOrder::Shifted {
1618        entering_before, ..
1619    } = order
1620    {
1621        reconcile_shifted_children(
1622            applier,
1623            container,
1624            dirty_nodes,
1625            scratch,
1626            entering_before,
1627            geometry,
1628        );
1629        return;
1630    }
1631    let mut entering = scratch.entering.drain(..).peekable();
1632    for (placed, kept) in scratch.placed_fresh.iter().zip(scratch.kept.drain(..)) {
1633        if let Some(mut layer) = kept {
1634            if placed.keeps_layer {
1635                move_kept_child(applier, &mut layer, placed.geometry.position, geometry);
1636            }
1637            container.children.push(RenderNode::Layer(layer));
1638        } else if let Some((_, lowered)) = entering.next_if(|(id, _)| *id == placed.id) {
1639            dirty_nodes.remove(&placed.id);
1640            remove_dirty_descendants(&lowered, dirty_nodes);
1641
1642            container.children.push(RenderNode::Layer(lowered));
1643        }
1644    }
1645}
1646
1647/// Moves the kept children, which lie in `container` in their new order, to
1648/// their placements, and puts the entering children before and after them:
1649/// those of the first `entering_before` placed children before.
1650fn reconcile_shifted_children(
1651    applier: &MemoryApplier,
1652    container: &mut LayerNode,
1653    dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
1654    scratch: &mut TranslateScratch,
1655    entering_before: usize,
1656    geometry: TranslateGeometry,
1657) {
1658    let TranslateScratch {
1659        placed_fresh,
1660        entering,
1661        ..
1662    } = scratch;
1663    for (child, placed) in container
1664        .children
1665        .iter_mut()
1666        .zip(&placed_fresh[entering_before..])
1667    {
1668        let RenderNode::Layer(layer) = child else {
1669            unreachable!("retained child identities were checked");
1670        };
1671        if placed.keeps_layer {
1672            move_kept_child(applier, layer, placed.geometry.position, geometry);
1673        }
1674    }
1675    if entering.is_empty() {
1676        return;
1677    }
1678    // The children built for the step lie in their placed order, and those
1679    // placed before the kept ones come first.
1680    let mut built = entering.iter().peekable();
1681    let built_before = placed_fresh[..entering_before]
1682        .iter()
1683        .filter(|placed| built.next_if(|(id, _)| *id == placed.id).is_some())
1684        .count();
1685    for (id, lowered) in entering.iter() {
1686        dirty_nodes.remove(id);
1687        remove_dirty_descendants(lowered, dirty_nodes);
1688    }
1689    let layer = |(_, lowered): (NodeId, Box<LayerNode>)| RenderNode::Layer(lowered);
1690    if built_before > 0 {
1691        container
1692            .children
1693            .splice(0..0, entering.drain(..built_before).map(layer));
1694    }
1695    container.children.extend(entering.drain(..).map(layer));
1696}
1697
1698/// Runs `f` on the thread's translate scratch where it lies, or on a new one
1699/// while another translate holds it.
1700fn with_translate_scratch<R>(f: impl FnOnce(&mut TranslateScratch) -> R) -> R {
1701    crate::thread_state::with(|state| match state.translate_scratch.try_borrow_mut() {
1702        Ok(mut scratch) => f(&mut scratch),
1703        Err(_) => f(&mut TranslateScratch::default()),
1704    })
1705}
1706
1707fn translate_layer_from_data(
1708    applier: &MemoryApplier,
1709    container: &mut LayerNode,
1710    dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
1711    ancestors: TranslateAncestorContext,
1712    data: SnapshotNodeData<'_>,
1713    wrapped: bool,
1714) -> bool {
1715    let TranslateAncestorContext {
1716        inherited_motion_context_animated,
1717        update,
1718        inherited_translated_content_context,
1719        parent_content_offset,
1720        parent_abs,
1721    } = ancestors;
1722    let SnapshotNodeData {
1723        layout_state,
1724        modifier_slices,
1725        children: fresh_children,
1726        window_root,
1727    } = data;
1728    let layout_state = if window_root {
1729        layout_state.at_origin()
1730    } else {
1731        layout_state
1732    };
1733    let container_plan = match translated_container(
1734        container,
1735        &layout_state,
1736        &modifier_slices,
1737        inherited_motion_context_animated,
1738        wrapped,
1739    ) {
1740        Ok(plan) => plan,
1741        Err(reason) => return translate_bail(reason),
1742    };
1743    let TranslatedContainer {
1744        node_id,
1745        node_bounds,
1746    } = container_plan;
1747    container.node_bounds = node_bounds;
1748    // The container's own draws and text are recorded again around the
1749    // children it keeps: an unchanged draw reuses its recording, while
1750    // building the container anew lowered every child layer beneath it too.
1751    crate::layer_recycling::recycle_primitives(container);
1752    let translated = with_translate_scratch(|scratch| {
1753        let order = translated_children(applier, container, dirty_nodes, fresh_children, scratch)?;
1754        let geometry = TranslateGeometry::new(
1755            &layout_state,
1756            &container.graphics_layer,
1757            container.local_bounds,
1758            parent_abs,
1759        );
1760        let child_inherited_translated_content_context =
1761            inherited_translated_content_context || container.translated_content_context;
1762        match &order {
1763            ChildOrder::Shifted { kept, .. } => {
1764                recycle_children_around(container, kept.clone(), dirty_nodes);
1765            }
1766            ChildOrder::Mixed => recycle_leaving_children(container, scratch, dirty_nodes),
1767        }
1768        build_entering_children(
1769            applier,
1770            container,
1771            scratch,
1772            dirty_nodes,
1773            geometry,
1774            (child_inherited_translated_content_context, update),
1775        );
1776
1777        apply_translated_container_state(
1778            container,
1779            &modifier_slices,
1780            &layout_state,
1781            parent_content_offset,
1782            geometry,
1783        );
1784
1785        reconcile_translated_children(applier, container, dirty_nodes, scratch, order, geometry);
1786        Ok(())
1787    });
1788    if let Err(reason) = translated {
1789        return translate_bail(reason);
1790    }
1791    write_own_content_around_children(container, node_id, &modifier_slices, layout_state.size());
1792    container.hit_test = hit_test_from_slices(&modifier_slices);
1793
1794    container.has_origin_sinks = modifier_slices_have_origin_sinks(&modifier_slices)
1795        || children_have_origin_sinks(&container.children);
1796    container.refresh_child_facts();
1797    container.note_content_change(update);
1798
1799    true
1800}
1801
1802fn translate_retained_child(layer: &mut LayerNode, position: Point, content_offset: Point) {
1803    layer.transform_to_parent = placed_transform(
1804        layer.local_bounds,
1805        position,
1806        layer.graphics_layer.get(),
1807        content_offset,
1808    );
1809    layer.origin_in_parent = position;
1810}
1811
1812/// Moves a child a translated container keeps to its placement in the
1813/// container laid out with `geometry`. A subtree that publishes window
1814/// origins publishes them again from where it now lies.
1815fn move_kept_child(
1816    applier: &MemoryApplier,
1817    layer: &mut LayerNode,
1818    position: Point,
1819    geometry: TranslateGeometry,
1820) {
1821    translate_retained_child(layer, position, geometry.content_offset);
1822    if layer.has_origin_sinks {
1823        publish_retained_window_geometry(applier, layer, geometry.child_abs());
1824    }
1825}
1826
1827/// Rebuilds `layer` with `rebuild`, which reports whether it succeeded, and
1828/// returns whether the hit geometry of the layer or of a layer under it
1829/// changed; `None` when the rebuild failed. A rebuild takes in the moves and
1830/// layer changes of the nodes under the layer, which its own state does not
1831/// show: a sheet that slides inside a container that redraws keeps the
1832/// container's state.
1833fn rebuild_tracking_hits(
1834    layer: &mut LayerNode,
1835    rebuild: impl FnOnce(&mut LayerNode) -> bool,
1836) -> Option<bool> {
1837    let previous = HitGraphState::of(layer);
1838    let mut descendants = crate::thread_state::with(|state| state.hit_geometry_scratch.take());
1839    descendants.clear();
1840    visit_hit_layers(layer, &mut |child| {
1841        descendants.push(HitGraphState::of(child));
1842        true
1843    });
1844    let dirty = rebuild(layer).then(|| {
1845        let mut expected = descendants.iter();
1846        previous.dirty_against(&HitGraphState::of(layer))
1847            || !visit_hit_layers(layer, &mut |child| {
1848                expected.next() == Some(&HitGraphState::of(child))
1849            })
1850            || expected.next().is_some()
1851    });
1852    crate::thread_state::with(|state| state.hit_geometry_scratch.set(descendants));
1853    dirty
1854}
1855
1856/// Visits the layers under `layer` that are hit targets or hold one, in draw
1857/// order, while `visit` returns true; returns whether every visit did.
1858fn visit_hit_layers(layer: &LayerNode, visit: &mut impl FnMut(&LayerNode) -> bool) -> bool {
1859    for child in &layer.children {
1860        let RenderNode::Layer(child) = child else {
1861            continue;
1862        };
1863        if child.hit_test.is_none() && !child.has_hit_targets {
1864            continue;
1865        }
1866        if !visit(child) || !visit_hit_layers(child, visit) {
1867            return false;
1868        }
1869    }
1870    true
1871}
1872
1873#[derive(PartialEq)]
1874pub(crate) struct HitGraphState {
1875    hit_test: bool,
1876    has_hit_targets: bool,
1877    local_bounds: Rect,
1878    node_bounds: Option<Rect>,
1879    transform_to_parent: ProjectiveTransform,
1880    clip_rect: Option<Rect>,
1881    shape: LayerShape,
1882    /// Whether the layer is a scene, a mesh surface or a 3D transform. Each
1883    /// update of such a layer runs its closure again, which may move where
1884    /// the surfaces of its scene take input or hide one another.
1885    space: bool,
1886}
1887
1888impl HitGraphState {
1889    fn of(layer: &LayerNode) -> Self {
1890        Self {
1891            hit_test: layer.hit_test.is_some(),
1892            has_hit_targets: layer.has_hit_targets,
1893            local_bounds: layer.local_bounds,
1894            node_bounds: layer.node_bounds,
1895            transform_to_parent: layer.transform_to_parent,
1896            clip_rect: layer.clip_rect(),
1897            shape: layer.graphics_layer.shape,
1898            space: layer.graphics_layer.space.is_some(),
1899        }
1900    }
1901
1902    fn dirty_against(&self, replacement: &Self) -> bool {
1903        self.hit_test
1904            || replacement.hit_test
1905            || self.space
1906            || replacement.space
1907            || ((self.has_hit_targets || replacement.has_hit_targets) && self != replacement)
1908    }
1909}
1910
1911/// Notes that the scene update changes a layer that is a scene, a surface or
1912/// a 3D transform, or lies beneath one: the window's scenes publish where
1913/// their layers show and their eyes' worlds again.
1914#[cold]
1915#[inline(never)]
1916fn note_space_change() {
1917    crate::thread_state::with(|state| state.space_changed.set(true));
1918}
1919
1920fn release_for_rebuild(layer: &mut LayerNode) {
1921    layer.hit_test = None;
1922    crate::layer_recycling::recycle_children(layer);
1923}
1924
1925/// Takes `layer`'s node and every node beneath it out of `dirty_nodes`.
1926fn forget_dirty_subtree(layer: &LayerNode, dirty_nodes: &mut HashMap<NodeId, NodeUpdate>) {
1927    if let Some(node_id) = layer_identity(layer) {
1928        dirty_nodes.remove(&node_id);
1929    }
1930    remove_dirty_descendants(layer, dirty_nodes);
1931}
1932
1933fn remove_dirty_descendants(layer: &LayerNode, dirty_nodes: &mut HashMap<NodeId, NodeUpdate>) {
1934    for child in &layer.children {
1935        let RenderNode::Layer(child_layer) = child else {
1936            continue;
1937        };
1938        if let Some(node_id) = child_layer.node_id {
1939            dirty_nodes.remove(&node_id);
1940        }
1941        remove_dirty_descendants(child_layer, dirty_nodes);
1942    }
1943}
1944
1945#[derive(Clone, Copy)]
1946struct LowerContext {
1947    inherited_motion_context_animated: bool,
1948    inherited_translated_content_context: bool,
1949    parent_abs: Option<AbsOrigin>,
1950    parent_content_offset: Point,
1951}
1952
1953impl LowerContext {
1954    const ROOT: Self = Self {
1955        inherited_motion_context_animated: false,
1956        inherited_translated_content_context: false,
1957        parent_abs: Some(AbsOrigin::ROOT),
1958        parent_content_offset: Point { x: 0.0, y: 0.0 },
1959    };
1960
1961    fn for_children(
1962        self,
1963        slices: &ModifierNodeSlices,
1964        content_offset: Point,
1965        parent_abs: Option<AbsOrigin>,
1966    ) -> Self {
1967        Self {
1968            inherited_motion_context_animated: self.inherited_motion_context_animated
1969                || slices.motion_context_animated(),
1970            inherited_translated_content_context: self.inherited_translated_content_context
1971                || slices.translated_content_context(),
1972            parent_abs,
1973            parent_content_offset: content_offset,
1974        }
1975    }
1976}
1977
1978struct LayerHead {
1979    node_id: Option<NodeId>,
1980    wraps: Option<NodeId>,
1981    local_bounds: Rect,
1982    node_bounds: Option<Rect>,
1983    transform_to_parent: ProjectiveTransform,
1984    content_offset: Point,
1985    motion_context_animated: bool,
1986    translated_content_context: bool,
1987    translated_content_offset: Point,
1988    origin_in_parent: Point,
1989    graphics_layer: Option<GraphicsLayer>,
1990    clip_to_bounds: bool,
1991    hit_test: Option<HitTestNode>,
1992    has_origin_sinks: bool,
1993    isolation: IsolationReasons,
1994    cache_policy: CachePolicy,
1995    draw_phase: Option<PrimitivePhase>,
1996}
1997
1998struct NodeFrame {
1999    local_bounds: Rect,
2000    node_bounds: Option<Rect>,
2001    placement: Point,
2002    content_offset: Point,
2003    translated_content_offset: Point,
2004}
2005
2006fn node_layer_head(
2007    node_id: NodeId,
2008    slices: &Rc<ModifierNodeSlices>,
2009    frame: NodeFrame,
2010    graphics_layer: Option<GraphicsLayer>,
2011    context: LowerContext,
2012) -> LayerHead {
2013    let clip_to_bounds = slices.clip_to_bounds();
2014    let translated_content_context = slices.translated_content_context();
2015    let properties = graphics_layer.as_ref().unwrap_or(&GraphicsLayer::DEFAULT);
2016    let isolation = isolation_reasons(properties);
2017    LayerHead {
2018        node_id: Some(node_id),
2019        wraps: None,
2020        local_bounds: frame.local_bounds,
2021        node_bounds: frame.node_bounds,
2022        transform_to_parent: placed_transform(
2023            frame.local_bounds,
2024            frame.placement,
2025            graphics_layer.as_ref(),
2026            context.parent_content_offset,
2027        ),
2028        content_offset: frame.content_offset,
2029        motion_context_animated: context.inherited_motion_context_animated
2030            || slices.motion_context_animated(),
2031        translated_content_context,
2032        translated_content_offset: if translated_content_context {
2033            frame.translated_content_offset
2034        } else {
2035            Point::default()
2036        },
2037        origin_in_parent: frame.placement,
2038        cache_policy: layer_cache_policy(properties, isolation),
2039        isolation,
2040        graphics_layer,
2041        clip_to_bounds,
2042        hit_test: hit_test_from_slices(slices),
2043        has_origin_sinks: modifier_slices_have_origin_sinks(slices),
2044        draw_phase: None,
2045    }
2046}
2047
2048fn assign_layer(layer: &mut LayerNode, head: LayerHead) {
2049    let LayerNode {
2050        node_id,
2051        wraps,
2052        local_bounds,
2053        node_bounds,
2054        transform_to_parent,
2055        content_offset,
2056        motion_context_animated,
2057        translated_content_context,
2058        translated_content_offset,
2059        origin_in_parent,
2060        graphics_layer,
2061        clip_to_bounds,
2062        hit_test,
2063        has_hit_targets,
2064        space,
2065        has_origin_sinks,
2066        contained,
2067        isolation,
2068        draw_phase,
2069        cache_policy,
2070        cache_hashes: _,
2071        content_changes,
2072        change: _,
2073        own_change: _,
2074        children,
2075    } = layer;
2076    if *node_id != head.node_id {
2077        *content_changes = ContentChanges::default();
2078    }
2079    *node_id = head.node_id;
2080    *wraps = head.wraps;
2081    *local_bounds = head.local_bounds;
2082    *node_bounds = head.node_bounds;
2083    *transform_to_parent = head.transform_to_parent;
2084    *content_offset = head.content_offset;
2085    *motion_context_animated = head.motion_context_animated;
2086    *translated_content_context = head.translated_content_context;
2087    *translated_content_offset = head.translated_content_offset;
2088    *origin_in_parent = head.origin_in_parent;
2089    graphics_layer.replace(head.graphics_layer);
2090    if graphics_layer.space.is_some() {
2091        note_space_change();
2092    }
2093    *clip_to_bounds = head.clip_to_bounds;
2094    *hit_test = head.hit_test;
2095    *has_hit_targets = false;
2096    space.set(None);
2097    *has_origin_sinks = head.has_origin_sinks || children_have_origin_sinks(children);
2098    contained.set(None);
2099    *isolation = head.isolation;
2100    *draw_phase = head.draw_phase;
2101    *cache_policy = head.cache_policy;
2102    layer.forget_own_raster_cache_hashes();
2103    layer.refresh_child_facts();
2104}
2105
2106fn children_have_origin_sinks(children: &[RenderNode]) -> bool {
2107    children.iter().any(|child| match child {
2108        RenderNode::Layer(child_layer) => child_layer.has_origin_sinks,
2109        RenderNode::Primitive(_) | RenderNode::DrawRun(_) => false,
2110    })
2111}
2112
2113/// The transform that places a layer with `graphics_layer`, `None` for the
2114/// default properties, at `placement` in a parent whose content is offset by
2115/// `parent_content_offset`.
2116fn placed_transform(
2117    local_bounds: Rect,
2118    placement: Point,
2119    graphics_layer: Option<&GraphicsLayer>,
2120    parent_content_offset: Point,
2121) -> ProjectiveTransform {
2122    // Most layers only move: one translation places them.
2123    let graphics_layer = match graphics_layer {
2124        Some(layer) if layer_scales_or_rotates(layer) => layer,
2125        moved => {
2126            let offset = moved.map_or_else(Point::default, |layer| Point {
2127                x: layer.translation_x,
2128                y: layer.translation_y,
2129            });
2130            return ProjectiveTransform::translation(
2131                placement.x + offset.x + parent_content_offset.x,
2132                placement.y + offset.y + parent_content_offset.y,
2133            );
2134        }
2135    };
2136    let transform = layer_transform_to_parent(local_bounds, placement, graphics_layer);
2137    if parent_content_offset == Point::default() {
2138        transform
2139    } else {
2140        transform.then(ProjectiveTransform::translation(
2141            parent_content_offset.x,
2142            parent_content_offset.y,
2143        ))
2144    }
2145}
2146
2147/// Writes a node's own draws and text around its child layers: `kept`
2148/// layers already end `list`, and `write_children` appends `written` more.
2149fn write_node_content(
2150    list: &mut Vec<RenderNode>,
2151    node_id: NodeId,
2152    slices: &ModifierNodeSlices,
2153    draws: DrawTarget,
2154    (kept, written): (usize, usize),
2155    write_children: impl FnOnce(&mut Vec<RenderNode>),
2156) {
2157    let size = draws.size;
2158    let outer_count = slices.outer_draw_command_count();
2159    let commands = &slices.draw_commands()[outer_count..];
2160    crate::layer_recycling::reserve_nodes(
2161        list,
2162        layer_node_capacity(commands, written, slices.has_text()),
2163    );
2164    let first_kept = list.len() - kept;
2165    let first_draw = list.len();
2166    append_draw_nodes(
2167        list,
2168        node_id,
2169        commands,
2170        outer_count,
2171        DrawPass::Record(DrawPlacement::Behind),
2172        draws,
2173        PrimitivePhase::BeforeChildren,
2174    );
2175    if let Some(text) = text_node_from_parts(TextNodeParts {
2176        node_id,
2177        text_rect: slices.text_content_rect(size),
2178        text_style: slices.text_style().as_deref(),
2179        text_layout_options: slices.text_layout_options(),
2180        text_pan: slices.text_pan_resolver(),
2181        measured_layout: slices.measured_text_layout(),
2182    }) {
2183        list.push(RenderNode::Primitive(PrimitiveEntry {
2184            phase: PrimitivePhase::BeforeChildren,
2185            node: PrimitiveNode::Text(crate::layer_recycling::boxed_text(text)),
2186        }));
2187    }
2188    // The kept layers go after the draws and text just written.
2189    list[first_kept..].rotate_left(kept);
2190    let first_draw = first_draw - kept;
2191    write_children(list);
2192    append_draw_nodes(
2193        list,
2194        node_id,
2195        commands,
2196        outer_count,
2197        DrawPass::OverlayFromOutput(first_draw),
2198        draws,
2199        PrimitivePhase::AfterChildren,
2200    );
2201}
2202
2203fn write_layer_with_outer(
2204    target: &mut LayerNode,
2205    head: LayerHead,
2206    outer: Option<OuterDraws>,
2207    placement: Point,
2208    parent_content_offset: Point,
2209    write_children: impl FnOnce(&mut Vec<RenderNode>, Option<&GraphicsLayer>),
2210) {
2211    debug_assert!(target.children.is_empty(), "a layer is written emptied");
2212    let Some(outer) = outer else {
2213        write_children(&mut target.children, head.graphics_layer.as_ref());
2214        assign_layer(target, head);
2215        return;
2216    };
2217    let mut layer = crate::layer_recycling::layer_box();
2218    write_children(&mut layer.children, head.graphics_layer.as_ref());
2219    assign_layer(&mut layer, head);
2220    write_wrapper(target, layer, placement, outer, parent_content_offset);
2221}
2222
2223fn write_snapshot_layer(
2224    snapshot: BuildNodeSnapshot,
2225    context: LowerContext,
2226    target: &mut LayerNode,
2227) {
2228    let BuildNodeSnapshot {
2229        node_id,
2230        placement,
2231        size,
2232        content_offset,
2233        slices,
2234        graphics_layer,
2235        children,
2236    } = snapshot;
2237    let (local_bounds, node_bounds) = layer_and_node_bounds(&slices, size);
2238    let child_context = context.for_children(&slices, content_offset, None);
2239    let head = node_layer_head(
2240        node_id,
2241        &slices,
2242        NodeFrame {
2243            local_bounds,
2244            node_bounds,
2245            placement,
2246            content_offset,
2247            translated_content_offset: content_offset,
2248        },
2249        graphics_layer,
2250        context,
2251    );
2252    let draws = DrawTarget {
2253        size,
2254        motion_context_animated: head.motion_context_animated,
2255        layer: None,
2256    };
2257    let outer = outer_draws(
2258        node_id,
2259        slices.draw_commands(),
2260        slices.outer_draw_command_count(),
2261        draws,
2262    );
2263    write_layer_with_outer(
2264        target,
2265        head,
2266        outer,
2267        placement,
2268        context.parent_content_offset,
2269        |list, layer| {
2270            let draws = DrawTarget { layer, ..draws };
2271            write_node_content(list, node_id, &slices, draws, (0, children.len()), |list| {
2272                for child in children {
2273                    let mut layer = crate::layer_recycling::layer_box();
2274                    write_snapshot_layer(child, child_context, &mut layer);
2275                    list.push(RenderNode::Layer(layer));
2276                }
2277            });
2278        },
2279    );
2280}
2281
2282#[derive(Clone, Copy)]
2283struct AbsOrigin {
2284    content_origin: Point,
2285    window_transform: ProjectiveTransform,
2286}
2287
2288impl AbsOrigin {
2289    const ROOT: AbsOrigin = AbsOrigin {
2290        content_origin: Point { x: 0.0, y: 0.0 },
2291        window_transform: ProjectiveTransform::identity(),
2292    };
2293
2294    fn children_of(self, layer: &LayerNode) -> AbsOrigin {
2295        let top_left = Point {
2296            x: self.content_origin.x + layer.origin_in_parent.x,
2297            y: self.content_origin.y + layer.origin_in_parent.y,
2298        };
2299        AbsOrigin {
2300            content_origin: Point {
2301                x: self.content_origin.x + layer.origin_in_parent.x + layer.content_offset.x,
2302                y: self.content_origin.y + layer.origin_in_parent.y + layer.content_offset.y,
2303            },
2304            window_transform: layer_transform_to_window(
2305                self.window_transform,
2306                top_left,
2307                layer.local_bounds,
2308                &layer.graphics_layer,
2309            ),
2310        }
2311    }
2312}
2313
2314fn read_placed_node_data<R>(
2315    applier: &MemoryApplier,
2316    node_id: NodeId,
2317    root: bool,
2318    read: impl FnOnce(SnapshotNodeData<'_>) -> R,
2319) -> Option<R> {
2320    read_node_data(applier, node_id, |mut data| {
2321        if data.window_root {
2322            if !root {
2323                return None;
2324            }
2325            data.layout_state = data.layout_state.at_origin();
2326        }
2327        note_layer_lowered();
2328        if !data.layout_state.is_placed() {
2329            return None;
2330        }
2331        Some(read(data))
2332    })?
2333}
2334
2335fn lower_root_into(applier: &MemoryApplier, node_id: NodeId, target: &mut LayerNode) -> bool {
2336    read_placed_node_data(applier, node_id, true, |data| {
2337        write_node_layer(applier, node_id, data, LowerContext::ROOT, target);
2338    })
2339    .is_some()
2340}
2341
2342fn lower_child(
2343    applier: &MemoryApplier,
2344    node_id: NodeId,
2345    context: LowerContext,
2346) -> Option<Box<LayerNode>> {
2347    read_placed_node_data(applier, node_id, false, |data| {
2348        let mut layer = crate::layer_recycling::layer_box();
2349        write_node_layer(applier, node_id, data, context, &mut layer);
2350        layer
2351    })
2352}
2353
2354/// Where a placed child sits in its parent and how large it is.
2355#[derive(Clone, Copy)]
2356struct PlacedGeometry {
2357    position: Point,
2358    size: Size,
2359}
2360
2361impl PlacedGeometry {
2362    fn of_placed(state: &cranpose_ui::widgets::LayoutState) -> Option<Self> {
2363        state.is_placed().then(|| Self {
2364            position: state.position(),
2365            size: state.size(),
2366        })
2367    }
2368}
2369
2370/// The geometry of the placed child `node_id`; `None` for a child that is
2371/// not placed or is a window root, drawn in a surface of its own.
2372fn placed_child_geometry(applier: &MemoryApplier, node_id: NodeId) -> Option<PlacedGeometry> {
2373    let node: &dyn Any = applier.get_ref(node_id).ok()?;
2374    if let Some(node) = node.downcast_ref::<LayoutNode>() {
2375        if node.is_window_root() {
2376            return None;
2377        }
2378        return PlacedGeometry::of_placed(&node.layout_state_handle().borrow());
2379    }
2380    PlacedGeometry::of_placed(&node.downcast_ref::<SubcomposeLayoutNode>()?.layout_state())
2381}
2382
2383fn read_node_data<R>(
2384    applier: &MemoryApplier,
2385    node_id: NodeId,
2386    read: impl FnOnce(SnapshotNodeData<'_>) -> R,
2387) -> Option<R> {
2388    let node: &dyn Any = applier.get_ref(node_id).ok()?;
2389    if let Some(node) = node.downcast_ref::<LayoutNode>() {
2390        let z_order = z_ordered_children(applier, &node.children);
2391        return Some(read(SnapshotNodeData {
2392            layout_state: node.layout_state(),
2393            modifier_slices: node.modifier_slices_snapshot(),
2394            children: z_order.as_deref().unwrap_or(&node.children),
2395            window_root: node.is_window_root(),
2396        }));
2397    }
2398    let node = node.downcast_ref::<SubcomposeLayoutNode>()?;
2399    let layout_state = node.layout_state();
2400    let modifier_slices = node.modifier_slices_snapshot();
2401    Some(node.with_active_children(|children| {
2402        let z_order = z_ordered_children(applier, children);
2403        read(SnapshotNodeData {
2404            layout_state,
2405            modifier_slices,
2406            children: z_order.as_deref().unwrap_or(children),
2407            window_root: false,
2408        })
2409    }))
2410}
2411
2412/// `children` in the order they draw and take taps, when a `z_index`
2413/// modifier moves one of them: by z index, and in composition order among
2414/// equal ones, as Compose's `zSortedChildren`. `None` while the composition
2415/// order already is that order, which it always is while no `z_index`
2416/// modifier exists.
2417#[inline]
2418fn z_ordered_children(applier: &MemoryApplier, children: &[NodeId]) -> Option<Vec<NodeId>> {
2419    if children.len() < 2 || !cranpose_ui::z_index_in_use() {
2420        return None;
2421    }
2422    z_sorted_children(applier, children)
2423}
2424
2425#[inline(never)]
2426fn z_sorted_children(applier: &MemoryApplier, children: &[NodeId]) -> Option<Vec<NodeId>> {
2427    let mut previous = f32::NEG_INFINITY;
2428    let in_order = children.iter().all(|&child| {
2429        let z_index = node_z_index(applier, child);
2430        let ordered = z_index >= previous;
2431        previous = z_index;
2432        ordered
2433    });
2434    if in_order {
2435        return None;
2436    }
2437    let mut ordered: Vec<(f32, NodeId)> = children
2438        .iter()
2439        .map(|&child| (node_z_index(applier, child), child))
2440        .collect();
2441    ordered.sort_by(|a, b| a.0.total_cmp(&b.0));
2442    Some(ordered.into_iter().map(|(_, child)| child).collect())
2443}
2444
2445fn node_z_index(applier: &MemoryApplier, node_id: NodeId) -> f32 {
2446    let Ok(node) = applier.get_ref(node_id) else {
2447        return 0.0;
2448    };
2449    let node: &dyn Any = node;
2450    if let Some(node) = node.downcast_ref::<LayoutNode>() {
2451        return node.modifier_slices_snapshot().z_index();
2452    }
2453    node.downcast_ref::<SubcomposeLayoutNode>()
2454        .map_or(0.0, |node| node.modifier_slices_snapshot().z_index())
2455}
2456
2457fn hit_test_from_slices(slices: &Rc<ModifierNodeSlices>) -> Option<HitTestNode> {
2458    slices_hit_something(slices).then(|| HitTestNode {
2459        handlers: Rc::clone(slices),
2460    })
2461}
2462
2463/// A node's layer bounds and, when they differ from it, its own rect (see
2464/// [`LayerNode::node_bounds`]).
2465fn layer_and_node_bounds(slices: &ModifierNodeSlices, size: Size) -> (Rect, Option<Rect>) {
2466    let node_bounds = Rect::from_size(size);
2467    let layer_bounds = slices.layer_bounds(size);
2468    (
2469        layer_bounds,
2470        (layer_bounds != node_bounds).then_some(node_bounds),
2471    )
2472}
2473
2474/// Whether a node's slices make it a hit target: a pointer input or a
2475/// pointer icon.
2476fn slices_hit_something(slices: &ModifierNodeSlices) -> bool {
2477    !slices.pointer_inputs().is_empty() || slices.pointer_icon().is_some()
2478}
2479
2480fn prepare_node_layer(
2481    node_id: NodeId,
2482    slices: &Rc<ModifierNodeSlices>,
2483    layout_state: &cranpose_ui::widgets::LayoutState,
2484    context: LowerContext,
2485) -> (LayerHead, LowerContext) {
2486    let size = layout_state.size();
2487    let placement = layout_state.position();
2488    let (local_bounds, node_bounds) = layer_and_node_bounds(slices, size);
2489    let graphics_layer = shaped_graphics_layer(slices, local_bounds);
2490    let geometry = context.parent_abs.map(|parent_abs| {
2491        TranslateGeometry::new(
2492            layout_state,
2493            graphics_layer.as_deref().unwrap_or(&GraphicsLayer::DEFAULT),
2494            local_bounds,
2495            parent_abs,
2496        )
2497    });
2498    if let Some(geometry) = geometry {
2499        slices.publish_window_geometry(geometry.top_left, geometry.window_transform, size);
2500    } else {
2501        slices.publish_pointer_input_size(size);
2502    }
2503    let content_offset = layout_state.content_offset();
2504    let child_context = context.for_children(
2505        slices,
2506        content_offset,
2507        geometry.map(TranslateGeometry::child_abs),
2508    );
2509    let head = node_layer_head(
2510        node_id,
2511        slices,
2512        NodeFrame {
2513            local_bounds,
2514            node_bounds,
2515            placement,
2516            content_offset,
2517            translated_content_offset: slices.translated_content_offset().unwrap_or(content_offset),
2518        },
2519        graphics_layer.map(Cow::into_owned),
2520        context,
2521    );
2522    (head, child_context)
2523}
2524
2525fn write_node_layer(
2526    applier: &MemoryApplier,
2527    node_id: NodeId,
2528    data: SnapshotNodeData<'_>,
2529    context: LowerContext,
2530    target: &mut LayerNode,
2531) {
2532    let SnapshotNodeData {
2533        layout_state,
2534        modifier_slices: slices,
2535        children,
2536        window_root: _,
2537    } = data;
2538    let size = layout_state.size();
2539    let placement = layout_state.position();
2540    if cranpose_core::env_flag!("CRANPOSE_SCENE_UPDATE_DIAG") {
2541        eprintln!(
2542            "[scene-update-diag] build layer node={node_id:?} size=({:.2},{:.2}) pos=({:.2},{:.2})",
2543            size.width, size.height, placement.x, placement.y,
2544        );
2545    }
2546    let (head, child_context) = prepare_node_layer(node_id, &slices, &layout_state, context);
2547    let draws = DrawTarget {
2548        size,
2549        motion_context_animated: head.motion_context_animated,
2550        layer: None,
2551    };
2552    let outer = outer_draws(
2553        node_id,
2554        slices.draw_commands(),
2555        slices.outer_draw_command_count(),
2556        draws,
2557    );
2558    write_layer_with_outer(
2559        target,
2560        head,
2561        outer,
2562        placement,
2563        context.parent_content_offset,
2564        |list, layer| {
2565            let draws = DrawTarget { layer, ..draws };
2566            write_node_content(list, node_id, &slices, draws, (0, children.len()), |list| {
2567                for &child_id in children {
2568                    if let Some(child) = lower_child(applier, child_id, child_context) {
2569                        list.push(RenderNode::Layer(child));
2570                    }
2571                }
2572            });
2573        },
2574    );
2575}
2576
2577pub(crate) struct RecorderSlot {
2578    generation: u64,
2579    handles: [Option<Rc<CommandRecording>>; 3],
2580}
2581
2582#[derive(Clone, Copy, PartialEq, Eq, Hash)]
2583pub(crate) struct RecorderKey {
2584    node_id: NodeId,
2585    command_index: u32,
2586}
2587
2588impl From<DrawCommandId> for RecorderKey {
2589    fn from(id: DrawCommandId) -> Self {
2590        Self {
2591            node_id: id.node_id,
2592            command_index: id.command_index,
2593        }
2594    }
2595}
2596
2597#[doc(hidden)]
2598pub fn clear_command_recordings_for_tests() {
2599    crate::thread_state::with(|state| state.command_recordings.borrow_mut().clear());
2600}
2601
2602fn bump_recording_generation() {
2603    crate::thread_state::with(|state| {
2604        let generation = state.recording_generation.get().wrapping_add(1);
2605        state.recording_generation.set(generation);
2606        if generation.is_multiple_of(512) {
2607            state
2608                .command_recordings
2609                .borrow_mut()
2610                .retain(|_, slot| generation.wrapping_sub(slot.generation) <= 64);
2611        }
2612    });
2613}
2614
2615/// A recording of the command `id` that no reader holds any more, to draw
2616/// the command again in place: first one whose shape columns no reader
2617/// shares either, then the oldest of three, whose recording takes new
2618/// columns with the room the old ones had. A new one when none is free.
2619fn acquire_storage(id: DrawCommandId) -> Rc<CommandRecording> {
2620    crate::thread_state::with(|state| {
2621        let mut map = state.command_recordings.borrow_mut();
2622        let Some(slot) = map.get_mut(&RecorderKey::from(id)) else {
2623            return Rc::default();
2624        };
2625        let free = |handle: &Option<Rc<CommandRecording>>| {
2626            handle.as_ref().is_some_and(|recording| {
2627                Rc::strong_count(recording) == 1 && Rc::weak_count(recording) == 0
2628            })
2629        };
2630        let unshared = slot.handles.iter().position(|handle| {
2631            free(handle)
2632                && handle
2633                    .as_ref()
2634                    .is_some_and(|recording| !recording.shares_shapes())
2635        });
2636        let at = unshared.or_else(|| {
2637            (slot.handles.iter().all(Option::is_some) && free(&slot.handles[2])).then_some(2)
2638        });
2639        at.and_then(|at| slot.handles[at].take())
2640            .unwrap_or_default()
2641    })
2642}
2643
2644/// Keeps `recording`, the command `id` drew this frame, as the newest of
2645/// the command's three, and hands it back for the frame's scene.
2646fn publish_recording(id: DrawCommandId, recording: Rc<CommandRecording>) -> Rc<CommandRecording> {
2647    crate::thread_state::with(|state| {
2648        let mut map = state.command_recordings.borrow_mut();
2649        let generation = state.recording_generation.get();
2650        let slot = map
2651            .entry(RecorderKey::from(id))
2652            .or_insert_with(|| RecorderSlot {
2653                generation,
2654                handles: [None, None, None],
2655            });
2656        slot.generation = generation;
2657        slot.handles[2] = slot.handles[1].take();
2658        slot.handles[1] = slot.handles[0].take();
2659        slot.handles[0] = Some(Rc::clone(&recording));
2660        recording
2661    })
2662}
2663
2664enum DrawPass<'a> {
2665    Record(DrawPlacement),
2666    OverlayFrom(std::slice::Iter<'a, RenderNode>),
2667    OverlayFromOutput(usize),
2668}
2669
2670impl DrawPass<'_> {
2671    fn placement(&self) -> DrawPlacement {
2672        match self {
2673            Self::Record(placement) => *placement,
2674            Self::OverlayFrom(_) | Self::OverlayFromOutput(_) => DrawPlacement::Overlay,
2675        }
2676    }
2677
2678    fn take_recording(
2679        &mut self,
2680        command: &DrawCommand,
2681        output: &[RenderNode],
2682    ) -> Option<Rc<CommandRecording>> {
2683        if matches!(command, DrawCommand::Overlay(_)) {
2684            return None;
2685        }
2686        let node = match self {
2687            Self::Record(_) => return None,
2688            Self::OverlayFrom(nodes) => nodes.next().expect("each behind command has a draw run"),
2689            Self::OverlayFromOutput(index) => {
2690                let node = &output[*index];
2691                *index += 1;
2692                node
2693            }
2694        };
2695        // A command whose draws a group places is a layer that starts
2696        // with the run before its first group.
2697        let run = match node {
2698            RenderNode::DrawRun(run) => run,
2699            RenderNode::Layer(layer) => match layer.children.first() {
2700                Some(RenderNode::DrawRun(run)) => run,
2701                _ => return None,
2702            },
2703            RenderNode::Primitive(_) => return None,
2704        };
2705        matches!(command, DrawCommand::WithContent(_)).then(|| Rc::clone(&run.recording))
2706    }
2707}
2708
2709/// Where a node's draw commands draw: the scope size, and what the layers
2710/// of their groups take from the node: its motion context and the paint of
2711/// its graphics layer, which the node's own draws take.
2712#[derive(Clone, Copy)]
2713struct DrawTarget<'a> {
2714    size: Size,
2715    motion_context_animated: bool,
2716    layer: Option<&'a GraphicsLayer>,
2717}
2718
2719/// Writes the part of one draw command's recording that a draw pass draws,
2720/// turning each group its draws open into a layer that the group places.
2721struct GroupedDraws<'a> {
2722    id: DrawCommandId,
2723    phase: PrimitivePhase,
2724    recording: &'a Rc<CommandRecording>,
2725    motion_context_animated: bool,
2726    /// The paint of the node's graphics layer that its own draws take, when
2727    /// it paints them: the group layers take it as well, as the group's
2728    /// draws are the node's own, where the node's child layers do not.
2729    paint: Option<GraphicsLayer>,
2730}
2731
2732impl<'a> GroupedDraws<'a> {
2733    fn new(
2734        id: DrawCommandId,
2735        phase: PrimitivePhase,
2736        recording: &'a Rc<CommandRecording>,
2737        draws: DrawTarget<'_>,
2738    ) -> Self {
2739        Self {
2740            id,
2741            phase,
2742            recording,
2743            motion_context_animated: draws.motion_context_animated,
2744            paint: draws
2745                .layer
2746                .map(crate::layer_composition::local_content_layer_for)
2747                .filter(|paint| paint.alpha != 1.0 || paint.color_filter.is_some())
2748                .map(std::borrow::Cow::into_owned),
2749        }
2750    }
2751
2752    /// One layer that holds the draws of `segments` in the parent's own
2753    /// draws, the groups among them as layers. It starts with the run
2754    /// before the first group, even an empty one, where the overlay pass of
2755    /// a with-content command finds the recording.
2756    fn wrapper(&self, segments: Range<u32>, size: Size) -> RenderNode {
2757        let first_group = self
2758            .recording
2759            .segments_in(&segments)
2760            .position(|segment| segment.lane == RecordLane::GroupStart)
2761            .map_or(segments.end, |offset| segments.start + offset as u32);
2762        let mut wrapper = crate::layer_recycling::layer_box();
2763        wrapper.children.push(self.run(segments.start..first_group));
2764        self.write(&mut wrapper.children, first_group, segments.end);
2765        assign_layer(
2766            &mut wrapper,
2767            self.head(&DrawGroup::plain(Rect::from_size(size), false)),
2768        );
2769        RenderNode::Layer(wrapper)
2770    }
2771
2772    /// Writes the segments from `position` into `list` up to the end of the
2773    /// group open at `position` or up to `end`, and returns the position
2774    /// after what it wrote.
2775    fn write(&self, list: &mut Vec<RenderNode>, mut position: u32, end: u32) -> u32 {
2776        let segments = self.recording.segments();
2777        let mut run_start = position;
2778        while position < end {
2779            let segment = segments[position as usize];
2780            match segment.lane {
2781                RecordLane::GroupStart => {
2782                    self.push_run(list, run_start..position);
2783                    let mut layer = crate::layer_recycling::layer_box();
2784                    position = self.write(&mut layer.children, position + 1, end);
2785                    let group = self.recording.groups()[segment.start as usize];
2786                    assign_layer(&mut layer, self.head(&group));
2787                    list.push(RenderNode::Layer(layer));
2788                    run_start = position;
2789                }
2790                RecordLane::GroupEnd => {
2791                    self.push_run(list, run_start..position);
2792                    return position + 1;
2793                }
2794                RecordLane::Shapes | RecordLane::Others | RecordLane::Content => position += 1,
2795            }
2796        }
2797        self.push_run(list, run_start..end);
2798        end
2799    }
2800
2801    fn run(&self, segments: Range<u32>) -> RenderNode {
2802        RenderNode::DrawRun(DrawRunNode::for_command_shared(
2803            self.phase,
2804            Some(self.id),
2805            Rc::clone(self.recording),
2806            segments,
2807        ))
2808    }
2809
2810    fn push_run(&self, list: &mut Vec<RenderNode>, segments: Range<u32>) {
2811        if !segments.is_empty() {
2812            list.push(self.run(segments));
2813        }
2814    }
2815
2816    /// The graphics layer of a group's layer: the group's scale and turn,
2817    /// and the node's paint. An isolated group takes the alpha as it
2818    /// composites, so the mask it ends with keeps its own.
2819    fn group_layer(&self, group: &DrawGroup) -> Option<GraphicsLayer> {
2820        if self.paint.is_none() && !group.scales_or_turns() {
2821            return None;
2822        }
2823        let mut layer = group.layer();
2824        if let Some(paint) = &self.paint {
2825            layer.alpha = paint.alpha;
2826            layer.color_filter.clone_from(&paint.color_filter);
2827            if !group.isolated {
2828                layer.compositing_strategy = CompositingStrategy::ModulateAlpha;
2829            }
2830        }
2831        Some(layer)
2832    }
2833
2834    fn head(&self, group: &DrawGroup) -> LayerHead {
2835        let bounds = group.bounds;
2836        // The group's draws sit in its coordinates, whose origin a clip's
2837        // bounds may leave, as a clip bounds a node's layer away from it.
2838        let node_bounds = (bounds.x != 0.0 || bounds.y != 0.0).then(|| Rect {
2839            x: 0.0,
2840            y: 0.0,
2841            width: (bounds.x + bounds.width).max(0.0),
2842            height: (bounds.y + bounds.height).max(0.0),
2843        });
2844        LayerHead {
2845            node_id: None,
2846            wraps: None,
2847            local_bounds: bounds,
2848            node_bounds,
2849            transform_to_parent: group.transform_to_parent(),
2850            content_offset: Point::default(),
2851            motion_context_animated: self.motion_context_animated,
2852            translated_content_context: false,
2853            translated_content_offset: Point::default(),
2854            origin_in_parent: Point::default(),
2855            graphics_layer: self.group_layer(group),
2856            clip_to_bounds: group.clip,
2857            hit_test: None,
2858            has_origin_sinks: false,
2859            isolation: IsolationReasons {
2860                explicit_offscreen: group.isolated,
2861                group_opacity: group.isolated
2862                    && self.paint.as_ref().is_some_and(|paint| paint.alpha < 1.0),
2863                ..IsolationReasons::default()
2864            },
2865            cache_policy: CachePolicy::None,
2866            draw_phase: Some(self.phase),
2867        }
2868    }
2869}
2870
2871fn layer_node_capacity(commands: &[DrawCommand], children: usize, has_text: bool) -> usize {
2872    children
2873        + usize::from(has_text)
2874        + commands.len()
2875        + commands
2876            .iter()
2877            .filter(|command| matches!(command, DrawCommand::WithContent(_)))
2878            .count()
2879}
2880
2881fn draw_nodes(
2882    node_id: NodeId,
2883    commands: &[DrawCommand],
2884    first_command_index: usize,
2885    placement: DrawPlacement,
2886    draws: DrawTarget,
2887    phase: PrimitivePhase,
2888) -> Vec<RenderNode> {
2889    let mut nodes = crate::layer_recycling::child_list(commands.len());
2890    append_draw_nodes(
2891        &mut nodes,
2892        node_id,
2893        commands,
2894        first_command_index,
2895        DrawPass::Record(placement),
2896        draws,
2897        phase,
2898    );
2899    nodes
2900}
2901
2902fn append_draw_nodes(
2903    nodes: &mut Vec<RenderNode>,
2904    node_id: NodeId,
2905    commands: &[DrawCommand],
2906    first_command_index: usize,
2907    mut pass: DrawPass<'_>,
2908    draws: DrawTarget,
2909    phase: PrimitivePhase,
2910) {
2911    let size = draws.size;
2912    let placement = pass.placement();
2913    for (command_index, command) in commands.iter().enumerate() {
2914        let id = DrawCommandId {
2915            node_id,
2916            command_index: (first_command_index + command_index) as u32,
2917            placement,
2918        };
2919        let (shared, segments) = if let Some(shared) = pass.take_recording(command, nodes) {
2920            let segments = shared.content_split(false);
2921            (shared, segments)
2922        } else {
2923            let Some((recording, segments)) =
2924                recording_for_placement_reusing(command, placement, size, || acquire_storage(id))
2925            else {
2926                continue;
2927            };
2928            (publish_recording(id, recording), segments)
2929        };
2930        let grouped = shared.summary().has_groups
2931            && shared
2932                .segments_in(&segments)
2933                .any(|segment| segment.lane == RecordLane::GroupStart);
2934        nodes.push(if grouped {
2935            GroupedDraws::new(id, phase, &shared, draws).wrapper(segments, size)
2936        } else {
2937            RenderNode::DrawRun(DrawRunNode::for_command_shared(
2938                phase,
2939                Some(id),
2940                shared,
2941                segments,
2942            ))
2943        });
2944    }
2945}
2946
2947#[doc(hidden)]
2948pub fn draw_command_nodes_for_tests(
2949    node_id: NodeId,
2950    commands: &[DrawCommand],
2951    placement: DrawPlacement,
2952    size: Size,
2953    phase: PrimitivePhase,
2954) -> Vec<RenderNode> {
2955    bump_recording_generation();
2956    let draws = DrawTarget {
2957        size,
2958        motion_context_animated: false,
2959        layer: None,
2960    };
2961    draw_nodes(node_id, commands, 0, placement, draws, phase)
2962}
2963
2964struct OuterDraws {
2965    behind: Vec<RenderNode>,
2966    overlay: Vec<RenderNode>,
2967}
2968
2969fn outer_draws(
2970    node_id: NodeId,
2971    draw_commands: &[DrawCommand],
2972    outer_draw_command_count: usize,
2973    draws: DrawTarget,
2974) -> Option<OuterDraws> {
2975    (outer_draw_command_count > 0).then(|| {
2976        let commands = &draw_commands[..outer_draw_command_count];
2977        let behind = draw_nodes(
2978            node_id,
2979            commands,
2980            0,
2981            DrawPlacement::Behind,
2982            draws,
2983            PrimitivePhase::BeforeChildren,
2984        );
2985        let mut overlay = crate::layer_recycling::child_list(commands.len());
2986        append_draw_nodes(
2987            &mut overlay,
2988            node_id,
2989            commands,
2990            0,
2991            DrawPass::OverlayFrom(behind.iter()),
2992            draws,
2993            PrimitivePhase::AfterChildren,
2994        );
2995        OuterDraws { behind, overlay }
2996    })
2997}
2998
2999fn write_wrapper(
3000    wrapper: &mut LayerNode,
3001    mut layer: Box<LayerNode>,
3002    placement: Point,
3003    outer: OuterDraws,
3004    parent_content_offset: Point,
3005) {
3006    debug_assert!(wrapper.children.is_empty(), "a wrapper is written emptied");
3007    layer.transform_to_parent =
3008        layer_transform_to_parent(layer.local_bounds, Point::default(), &layer.graphics_layer);
3009    layer.origin_in_parent = Point::default();
3010    let node_rect = layer.node_rect();
3011    let graphics_layer = None;
3012    let head = LayerHead {
3013        node_id: None,
3014        wraps: layer.node_id,
3015        local_bounds: node_rect,
3016        node_bounds: None,
3017        transform_to_parent: placed_transform(node_rect, placement, None, parent_content_offset),
3018        content_offset: Point::default(),
3019        motion_context_animated: layer.motion_context_animated,
3020        translated_content_context: false,
3021        translated_content_offset: Point::default(),
3022        origin_in_parent: placement,
3023        graphics_layer,
3024        clip_to_bounds: false,
3025        hit_test: None,
3026        has_origin_sinks: false,
3027        isolation: IsolationReasons::default(),
3028        cache_policy: CachePolicy::None,
3029        draw_phase: None,
3030    };
3031    let OuterDraws {
3032        mut behind,
3033        mut overlay,
3034    } = outer;
3035    let children = &mut wrapper.children;
3036    crate::layer_recycling::reserve_nodes(children, behind.len() + 1 + overlay.len());
3037    children.append(&mut behind);
3038    children.push(RenderNode::Layer(layer));
3039    children.append(&mut overlay);
3040    crate::layer_recycling::recycle_list(behind);
3041    crate::layer_recycling::recycle_list(overlay);
3042    assign_layer(wrapper, head);
3043}
3044
3045fn take_wrapped_layer(wrapper: &mut LayerNode, node_id: NodeId) -> Option<Box<LayerNode>> {
3046    let mut wrapped = None;
3047    for child in wrapper.children.drain(..) {
3048        if let RenderNode::Layer(layer) = child
3049            && layer.node_id == Some(node_id)
3050        {
3051            wrapped = Some(layer);
3052        }
3053    }
3054    wrapped
3055}
3056
3057fn layer_identity(layer: &LayerNode) -> Option<NodeId> {
3058    layer.node_id.or(layer.wraps)
3059}
3060
3061struct TextNodeParts<'a> {
3062    node_id: NodeId,
3063    /// Where the text node was placed in its layout node.
3064    text_rect: Rect,
3065    text_style: Option<&'a TextStyle>,
3066    text_layout_options: Option<TextLayoutOptions>,
3067    text_pan: Option<TextPanResolver>,
3068    measured_layout: Option<Rc<PreparedTextLayout>>,
3069}
3070
3071fn text_node_from_parts(parts: TextNodeParts<'_>) -> Option<TextPrimitiveNode> {
3072    let TextNodeParts {
3073        node_id,
3074        text_rect,
3075        text_style,
3076        text_layout_options,
3077        text_pan,
3078        measured_layout,
3079    } = parts;
3080    let prepared = measured_layout?;
3081    let default_text_style;
3082    let text_style = match text_style {
3083        Some(style) => style,
3084        None => {
3085            default_text_style = TextStyle::default();
3086            &default_text_style
3087        }
3088    };
3089    let options = text_layout_options.unwrap_or_default().normalized();
3090    let content_width = text_rect.width.max(0.0);
3091    if content_width <= 0.0 {
3092        return None;
3093    }
3094
3095    let pan_offset = text_pan
3096        .as_ref()
3097        .map_or(0.0, |resolve| resolve(content_width));
3098    let pans_horizontally = text_pan.is_some();
3099
3100    let visual_style = &prepared.visual_style;
3101    let measured_draw_width = prepared.metrics.width.max(0.0);
3102    let draw_width = if options.overflow == TextOverflow::Visible || pans_horizontally {
3103        measured_draw_width
3104    } else {
3105        measured_draw_width.min(content_width)
3106    };
3107    let alignment_offset = resolve_text_horizontal_offset(
3108        text_style,
3109        prepared.text.text.as_str(),
3110        content_width,
3111        prepared.metrics.width,
3112    );
3113    let rect = Rect {
3114        x: text_rect.x + alignment_offset - pan_offset,
3115        y: text_rect.y,
3116        width: draw_width,
3117        height: prepared.metrics.height,
3118    };
3119    let text_bounds = Rect {
3120        width: content_width,
3121        height: text_rect.height.max(0.0),
3122        ..text_rect
3123    };
3124    let font_size = visual_style.resolve_font_size(14.0);
3125    let expanded_bounds =
3126        expand_text_bounds_for_baseline_shift(text_bounds, visual_style, font_size);
3127    let clip = if options.overflow == TextOverflow::Visible && !pans_horizontally {
3128        None
3129    } else {
3130        Some(pad_clip_rect(expanded_bounds))
3131    };
3132
3133    Some(TextPrimitiveNode {
3134        node_id,
3135        rect,
3136        text: Rc::clone(&prepared.text),
3137        render_text: prepared.render_text(),
3138        text_style: std::sync::Arc::clone(visual_style),
3139        font_size,
3140        layout_options: options,
3141        clip,
3142        paint: TextPaintCache::holding(TextPaint::of(
3143            visual_style,
3144            prepared.visual_style_hash(),
3145            &prepared.text.span_styles,
3146            font_size,
3147            (prepared.render_text_hash(), prepared.text.text.is_empty()),
3148        )),
3149    })
3150}
3151
3152fn layout_box_to_snapshot(node: &LayoutBox, parent: Option<&LayoutBox>) -> BuildNodeSnapshot {
3153    let placement = parent
3154        .map(|parent_box| Point {
3155            x: node.rect.x - parent_box.rect.x - parent_box.content_offset.x,
3156            y: node.rect.y - parent_box.rect.y - parent_box.content_offset.y,
3157        })
3158        .unwrap_or_default();
3159    let mut children = Vec::with_capacity(node.children.len());
3160    for child in &node.children {
3161        children.push(layout_box_to_snapshot(child, Some(node)));
3162    }
3163    if cranpose_ui::z_index_in_use() {
3164        // Stable: equal z indices keep the composition order.
3165        children.sort_by(|a, b| a.slices.z_index().total_cmp(&b.slices.z_index()));
3166    }
3167    let mut graphics_layer = node.node_data.modifier_slices.graphics_layer();
3168    let has_graphics_layer = graphics_layer.is_some();
3169    shape_clip(
3170        &mut graphics_layer,
3171        node.node_data.modifier_slices.clip_to_bounds(),
3172        node.node_data.modifier_slices.corner_shape(),
3173        Rect {
3174            x: 0.0,
3175            y: 0.0,
3176            width: node.rect.width,
3177            height: node.rect.height,
3178        },
3179    );
3180    let has_graphics_layer = has_graphics_layer
3181        || graphics_layer
3182            .as_ref()
3183            .is_some_and(|layer| layer.render_effect.is_some());
3184
3185    BuildNodeSnapshot {
3186        node_id: node.node_id,
3187        placement,
3188        size: Size {
3189            width: node.rect.width,
3190            height: node.rect.height,
3191        },
3192        content_offset: node.content_offset,
3193        slices: Rc::clone(&node.node_data.modifier_slices),
3194        graphics_layer: graphics_layer
3195            .filter(|_| has_graphics_layer)
3196            .map(Cow::into_owned),
3197        children,
3198    }
3199}
3200
3201fn modifier_slices_have_origin_sinks(slices: &ModifierNodeSlices) -> bool {
3202    slices.text_window().is_some() || slices.viewport_window_rect().is_some()
3203}
3204
3205/// The graphics layer of a node with `slices` and bounds `local_bounds`:
3206/// the chain's own, its clip shaped as [`shape_clip`] shapes it. Lent by
3207/// the slices unless the shaping changes it, and returned straight from
3208/// them when nothing shapes it: a layer kept in a local first is copied out.
3209#[inline]
3210fn shaped_graphics_layer(
3211    slices: &ModifierNodeSlices,
3212    local_bounds: Rect,
3213) -> Option<Cow<'_, GraphicsLayer>> {
3214    match clip_corners(slices.clip_to_bounds(), slices.corner_shape(), local_bounds) {
3215        None => slices.graphics_layer(),
3216        Some(corners) => rounded_graphics_layer(slices, corners, local_bounds),
3217    }
3218}
3219
3220#[inline(never)]
3221fn rounded_graphics_layer(
3222    slices: &ModifierNodeSlices,
3223    corners: (RoundedCornerShape, CornerRadii),
3224    local_bounds: Rect,
3225) -> Option<Cow<'_, GraphicsLayer>> {
3226    let mut graphics_layer = slices.graphics_layer();
3227    round_clip(&mut graphics_layer, corners, local_bounds);
3228    graphics_layer
3229}
3230
3231/// Shapes the clip of `graphics_layer`, a layer's own, to the corner shape
3232/// of a layer that clips to its bounds `local_bounds`. In place: the layer
3233/// moved in and out of a call is hundreds of bytes copied each way, and a
3234/// lent layer is copied only when the shaping changes it.
3235fn shape_clip(
3236    graphics_layer: &mut Option<Cow<'_, GraphicsLayer>>,
3237    clip_to_bounds: bool,
3238    corner_shape: Option<RoundedCornerShape>,
3239    local_bounds: Rect,
3240) {
3241    if let Some(corners) = clip_corners(clip_to_bounds, corner_shape, local_bounds) {
3242        round_clip(graphics_layer, corners, local_bounds);
3243    }
3244}
3245
3246/// The corner shape a layer that clips to its bounds `local_bounds` rounds
3247/// its clip with, and its radii there: `None` when it does not clip to its
3248/// bounds or its corners are square.
3249fn clip_corners(
3250    clip_to_bounds: bool,
3251    corner_shape: Option<RoundedCornerShape>,
3252    local_bounds: Rect,
3253) -> Option<(RoundedCornerShape, CornerRadii)> {
3254    if !clip_to_bounds {
3255        return None;
3256    }
3257    let corner_shape = corner_shape?;
3258    let radii = corner_shape.resolve(local_bounds.width, local_bounds.height);
3259    let square = radii.top_left <= f32::EPSILON
3260        && radii.top_right <= f32::EPSILON
3261        && radii.bottom_right <= f32::EPSILON
3262        && radii.bottom_left <= f32::EPSILON;
3263    (!square).then_some((corner_shape, radii))
3264}
3265
3266/// Rounds the clip of `graphics_layer` with `corners`, as [`clip_corners`]
3267/// found them for a layer at `local_bounds`.
3268fn round_clip(
3269    graphics_layer: &mut Option<Cow<'_, GraphicsLayer>>,
3270    (corner_shape, radii): (RoundedCornerShape, CornerRadii),
3271    local_bounds: Rect,
3272) {
3273    let properties = graphics_layer.get_or_insert_default().to_mut();
3274    if let Some(existing) = properties.render_effect.take() {
3275        let rounded_clip = rounded_corner_alpha_mask_effect(
3276            local_bounds.width,
3277            local_bounds.height,
3278            radii,
3279            ROUNDED_CLIP_EDGE_FEATHER,
3280        );
3281        properties.render_effect = Some(existing.then(rounded_clip));
3282    } else {
3283        properties.shape = LayerShape::Rounded(corner_shape);
3284        properties.clip = true;
3285    }
3286}
3287
3288fn layer_cache_policy(layer: &GraphicsLayer, isolation: IsolationReasons) -> CachePolicy {
3289    if isolation.has_any() || layer_scales_or_rotates(layer) {
3290        CachePolicy::Auto
3291    } else {
3292        CachePolicy::None
3293    }
3294}
3295
3296fn isolation_reasons(layer: &GraphicsLayer) -> IsolationReasons {
3297    IsolationReasons {
3298        explicit_offscreen: layer.compositing_strategy == CompositingStrategy::Offscreen,
3299        shape_clip: layer.clip && !matches!(layer.shape, LayerShape::Rectangle),
3300        effect: layer.render_effect.is_some(),
3301        backdrop: layer.backdrop_effect.is_some(),
3302        group_opacity: layer.compositing_strategy != CompositingStrategy::ModulateAlpha
3303            && layer.alpha < 1.0,
3304        blend_mode: layer.blend_mode != cranpose_ui::BlendMode::SrcOver,
3305    }
3306}
3307
3308fn pad_clip_rect(rect: Rect) -> Rect {
3309    Rect {
3310        x: rect.x - TEXT_CLIP_PAD,
3311        y: rect.y - TEXT_CLIP_PAD,
3312        width: (rect.width + TEXT_CLIP_PAD * 2.0).max(0.0),
3313        height: (rect.height + TEXT_CLIP_PAD * 2.0).max(0.0),
3314    }
3315}
3316
3317/// Grows `text_bounds` in the direction of the style's baseline shift, by the shift in pixels at
3318/// `font_size`, so a clip does not cut the shifted glyphs. Returns the bounds unchanged when
3319/// there is no baseline shift.
3320pub fn expand_text_bounds_for_baseline_shift(
3321    text_bounds: Rect,
3322    text_style: &TextStyle,
3323    font_size: f32,
3324) -> Rect {
3325    let baseline_shift_px = text_style
3326        .span_style
3327        .baseline_shift
3328        .filter(|shift| shift.is_specified())
3329        .map_or(0.0, |shift| -(shift.0 * font_size));
3330    if baseline_shift_px == 0.0 {
3331        return text_bounds;
3332    }
3333
3334    if baseline_shift_px < 0.0 {
3335        Rect {
3336            x: text_bounds.x,
3337            y: text_bounds.y + baseline_shift_px,
3338            width: text_bounds.width,
3339            height: (text_bounds.height - baseline_shift_px).max(0.0),
3340        }
3341    } else {
3342        Rect {
3343            x: text_bounds.x,
3344            y: text_bounds.y,
3345            width: text_bounds.width,
3346            height: (text_bounds.height + baseline_shift_px).max(0.0),
3347        }
3348    }
3349}
3350
3351fn resolve_text_horizontal_offset(
3352    text_style: &TextStyle,
3353    text: &str,
3354    content_width: f32,
3355    measured_width: f32,
3356) -> f32 {
3357    let remaining = (content_width - measured_width).max(0.0);
3358    if remaining == 0.0 {
3359        return 0.0;
3360    }
3361    remaining * cranpose_ui::text::text_align_fraction(text_style, text)
3362}
3363
3364#[cfg(test)]
3365#[path = "tests/scene_builder_tests.rs"]
3366mod tests;