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