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