1use std::{fmt::Write, sync::Arc};
2
3use cranpose_foundation::{ModifierNodeChain, NodeCapabilities};
4
5use crate::{
6 layout::{LayoutBox, LayoutTree},
7 modifier::{ModifierChainInspectorNode, ModifierInspectorRecord},
8 renderer::{RecordedRenderScene, RenderOp},
9};
10
11pub fn log_layout_tree(layout: &LayoutTree) {
13 log::info!(
14 target: "cranpose::debug::layout",
15 "\n{}",
16 format_layout_tree(layout)
17 );
18}
19
20pub fn log_render_scene(scene: &RecordedRenderScene) {
22 log::info!(
23 target: "cranpose::debug::render",
24 "\n{}",
25 format_render_scene(scene)
26 );
27}
28
29pub fn format_layout_tree(layout: &LayoutTree) -> String {
31 let mut output = String::new();
32 writeln!(output, "=== LAYOUT TREE (Current Screen) ===").ok();
33 format_layout_box(&mut output, layout.root(), 0);
34 writeln!(output, "=== END LAYOUT TREE ===").ok();
35 output
36}
37
38fn format_layout_box(output: &mut String, layout_box: &LayoutBox, depth: usize) {
39 let indent = " ".repeat(depth);
40 let rect = &layout_box.rect;
41
42 writeln!(
43 output,
44 "{}[Node #{}] pos: ({:.1}, {:.1}), size: ({:.1}x{:.1})",
45 indent, layout_box.node_id, rect.x, rect.y, rect.width, rect.height
46 )
47 .ok();
48
49 for child in &layout_box.children {
50 format_layout_box(output, child, depth + 1);
51 }
52}
53
54pub fn format_render_scene(scene: &RecordedRenderScene) -> String {
56 let mut output = String::new();
57 writeln!(output, "=== RENDER SCENE (Current Screen) ===").ok();
58 writeln!(output, "Total operations: {}", scene.operations().len()).ok();
59
60 for (idx, op) in scene.operations().iter().enumerate() {
61 match op {
62 RenderOp::Primitive {
63 node_id,
64 layer,
65 primitive,
66 } => {
67 writeln!(
68 output,
69 "[{}] Node #{} - Layer: {:?}, Primitive: {:?}",
70 idx, node_id, layer, primitive
71 )
72 .ok();
73 }
74 RenderOp::Text {
75 node_id,
76 rect,
77 value,
78 } => {
79 writeln!(
80 output,
81 "[{}] Node #{} - Text at ({:.1}, {:.1}): \"{}\"",
82 idx, node_id, rect.x, rect.y, value
83 )
84 .ok();
85 }
86 }
87 }
88 writeln!(output, "=== END RENDER SCENE ===").ok();
89 output
90}
91
92pub fn format_screen_summary(layout: &LayoutTree, scene: &RecordedRenderScene) -> String {
94 let mut output = String::new();
95 writeln!(output, "=== SCREEN SUMMARY ===").ok();
96 writeln!(
97 output,
98 "Total nodes in layout: {}",
99 count_nodes(layout.root())
100 )
101 .ok();
102
103 let mut text_count = 0;
104 let mut primitive_count = 0;
105
106 for op in scene.operations() {
107 match op {
108 RenderOp::Text { .. } => text_count += 1,
109 RenderOp::Primitive { .. } => primitive_count += 1,
110 }
111 }
112
113 writeln!(output, "Render operations:").ok();
114 writeln!(output, " - Text elements: {}", text_count).ok();
115 writeln!(output, " - Primitive shapes: {}", primitive_count).ok();
116 writeln!(output, "=== END SUMMARY ===").ok();
117 output
118}
119
120pub fn log_screen_summary(layout: &LayoutTree, scene: &RecordedRenderScene) {
122 log::info!(
123 target: "cranpose::debug::screen",
124 "\n{}",
125 format_screen_summary(layout, scene)
126 );
127}
128
129fn count_nodes(layout_box: &LayoutBox) -> usize {
130 1 + layout_box.children.iter().map(count_nodes).sum::<usize>()
131}
132
133pub fn log_modifier_chain(chain: &ModifierNodeChain, nodes: &[ModifierChainInspectorNode]) {
135 log::info!(
136 target: "cranpose::debug::modifier",
137 "\n{}",
138 format_modifier_chain(chain, nodes)
139 );
140}
141
142pub fn format_modifier_chain(
144 chain: &ModifierNodeChain,
145 nodes: &[ModifierChainInspectorNode],
146) -> String {
147 let mut output = String::new();
148 writeln!(output, "\n=== MODIFIER CHAIN ===").ok();
149 writeln!(
150 output,
151 "Total nodes: {} (entries: {})",
152 nodes.len(),
153 chain.len()
154 )
155 .ok();
156 writeln!(
157 output,
158 "Aggregated capabilities: {}",
159 describe_capabilities(chain.capabilities())
160 )
161 .ok();
162 for node in nodes {
163 let indent = " ".repeat(node.depth);
164 let inspector = node
165 .inspector
166 .as_ref()
167 .map(describe_inspector)
168 .unwrap_or_default();
169 let inspector_suffix = if inspector.is_empty() {
170 String::new()
171 } else {
172 format!(" {inspector}")
173 };
174 writeln!(
175 output,
176 "{}- {} caps={} agg={}{}",
177 indent,
178 node.type_name,
179 describe_capabilities(node.capabilities),
180 describe_capabilities(node.aggregate_child_capabilities),
181 inspector_suffix,
182 )
183 .ok();
184 }
185 writeln!(output, "=== END MODIFIER CHAIN ===\n").ok();
186 output
187}
188
189fn describe_capabilities(mask: NodeCapabilities) -> String {
190 let mut parts = Vec::new();
191 if mask.contains(NodeCapabilities::LAYOUT) {
192 parts.push("LAYOUT");
193 }
194 if mask.contains(NodeCapabilities::DRAW) {
195 parts.push("DRAW");
196 }
197 if mask.contains(NodeCapabilities::POINTER_INPUT) {
198 parts.push("POINTER_INPUT");
199 }
200 if mask.contains(NodeCapabilities::SEMANTICS) {
201 parts.push("SEMANTICS");
202 }
203 if mask.contains(NodeCapabilities::MODIFIER_LOCALS) {
204 parts.push("MODIFIER_LOCALS");
205 }
206 if mask.contains(NodeCapabilities::FOCUS) {
207 parts.push("FOCUS");
208 }
209 if parts.is_empty() {
210 "[NONE]".to_string()
211 } else {
212 format!("[{}]", parts.join("|"))
213 }
214}
215
216fn describe_inspector(record: &ModifierInspectorRecord) -> String {
217 if record.properties.is_empty() {
218 record.name.to_string()
219 } else {
220 let props = record
221 .properties
222 .iter()
223 .map(|prop| format!("{}={}", prop.name, prop.value))
224 .collect::<Vec<_>>()
225 .join(", ");
226 format!("{}({})", record.name, props)
227 }
228}
229
230pub struct ModifierChainTraceGuard {
232 context_id: Option<crate::render_state::AppContextId>,
233}
234
235impl Drop for ModifierChainTraceGuard {
236 fn drop(&mut self) {
237 if let Some(context_id) = self.context_id.take() {
238 crate::render_state::clear_modifier_chain_trace(context_id);
239 }
240 }
241}
242
243pub fn install_modifier_chain_trace<F>(callback: F) -> ModifierChainTraceGuard
245where
246 F: Fn(&[ModifierChainInspectorNode]) + Send + Sync + 'static,
247{
248 let context_id = crate::render_state::set_modifier_chain_trace(Arc::new(callback));
249 ModifierChainTraceGuard {
250 context_id: Some(context_id),
251 }
252}
253
254pub(crate) fn emit_modifier_chain_trace(nodes: &[ModifierChainInspectorNode]) {
255 crate::render_state::emit_modifier_chain_trace(nodes);
256}
257
258#[cfg(test)]
259#[path = "tests/debug_tests.rs"]
260mod tests;