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 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub enum GraphRebuildReason {
65 RootLayerUnavailable,
67 DirtyLayerUnavailable,
69 UnmatchedDirtyNodes(usize),
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum GraphUpdate {
79 Patched,
81 NeedsRebuild(GraphRebuildReason),
83}
84
85#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87pub struct GraphUpdateReport {
88 pub update: GraphUpdate,
90 pub hit_graph_dirty: bool,
93}
94
95impl GraphUpdateReport {
96 pub fn applied(self) -> bool {
98 matches!(self.update, GraphUpdate::Patched)
99 }
100
101 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
128pub 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
137pub 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
149pub 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
172pub 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
183pub 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 Moved,
211 MovedLayer,
214}
215
216fn 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 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
262fn 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 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
366fn 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
386fn 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
422struct 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 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
450const DIRTY_ANCESTOR: u8 = 1;
452const DIRTY_NODE: u8 = 2;
455
456fn 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#[derive(Clone)]
502pub(crate) struct RetainedPath {
503 node: NodeId,
504 steps: Vec<(usize, Option<NodeId>)>,
507 pass: ParentPass,
508 update: u64,
510 in_space: bool,
513}
514
515impl RetainedPath {
516 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
550fn 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
584fn 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
612fn 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
623fn 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 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
764fn note_updated_children(parent: &mut LayerNode, update: u64) {
767 parent.refresh_child_facts();
768 parent.has_origin_sinks |= children_have_origin_sinks(&parent.children);
772 parent.note_descendant_change(update);
773}
774
775#[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 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
798fn 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 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 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
890fn 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
921fn 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 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
997pub(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
1028fn 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
1138struct TranslatedContainer {
1141 node_id: NodeId,
1142 node_bounds: Option<Rect>,
1143}
1144
1145fn 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 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#[derive(Default)]
1202pub(crate) struct TranslateScratch {
1203 placed_fresh: Vec<PlacedChild>,
1205 old_ids: Vec<NodeId>,
1207 old_index_by_id: HashMap<NodeId, usize>,
1208 retained: Vec<Option<Box<LayerNode>>>,
1211 kept: Vec<Option<Box<LayerNode>>>,
1214 entering: Vec<(NodeId, Box<LayerNode>)>,
1216}
1217
1218struct PlacedChild {
1220 id: NodeId,
1221 geometry: PlacedGeometry,
1222 old_index: Option<usize>,
1225 keeps_layer: bool,
1229}
1230
1231enum ChildOrder {
1233 Shifted {
1239 kept: Range<usize>,
1240 entering_before: usize,
1241 },
1242 Mixed,
1245}
1246
1247fn 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
1310const MATCH_SEARCHES: usize = 16;
1313
1314fn 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
1378fn 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 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
1461fn 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
1496fn 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 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
1519fn 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
1553fn 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
1647fn 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 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
1698fn 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 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
1812fn 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
1827fn 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
1856fn 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 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#[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
1925fn 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
2113fn placed_transform(
2117 local_bounds: Rect,
2118 placement: Point,
2119 graphics_layer: Option<&GraphicsLayer>,
2120 parent_content_offset: Point,
2121) -> ProjectiveTransform {
2122 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
2147fn 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 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#[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
2370fn 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#[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
2463fn 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
2474fn 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
2615fn 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
2644fn 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 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#[derive(Clone, Copy)]
2713struct DrawTarget<'a> {
2714 size: Size,
2715 motion_context_animated: bool,
2716 layer: Option<&'a GraphicsLayer>,
2717}
2718
2719struct GroupedDraws<'a> {
2722 id: DrawCommandId,
2723 phase: PrimitivePhase,
2724 recording: &'a Rc<CommandRecording>,
2725 motion_context_animated: bool,
2726 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 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 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 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 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 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 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#[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
3231fn 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
3246fn 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
3266fn 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
3317pub 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;