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