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 "[{idx}] Node #{node_id} - Layer: {layer:?}, Primitive: {primitive:?}"
70 )
71 .ok();
72 }
73 RenderOp::Text {
74 node_id,
75 rect,
76 value,
77 } => {
78 writeln!(
79 output,
80 "[{}] Node #{} - Text at ({:.1}, {:.1}): \"{}\"",
81 idx, node_id, rect.x, rect.y, value
82 )
83 .ok();
84 }
85 }
86 }
87 writeln!(output, "=== END RENDER SCENE ===").ok();
88 output
89}
90
91pub fn format_screen_summary(layout: &LayoutTree, scene: &RecordedRenderScene) -> String {
93 let mut output = String::new();
94 writeln!(output, "=== SCREEN SUMMARY ===").ok();
95 writeln!(
96 output,
97 "Total nodes in layout: {}",
98 count_nodes(layout.root())
99 )
100 .ok();
101
102 let mut text_count = 0;
103 let mut primitive_count = 0;
104
105 for op in scene.operations() {
106 match op {
107 RenderOp::Text { .. } => text_count += 1,
108 RenderOp::Primitive { .. } => primitive_count += 1,
109 }
110 }
111
112 writeln!(output, "Render operations:").ok();
113 writeln!(output, " - Text elements: {text_count}").ok();
114 writeln!(output, " - Primitive shapes: {primitive_count}").ok();
115 writeln!(output, "=== END SUMMARY ===").ok();
116 output
117}
118
119pub fn log_screen_summary(layout: &LayoutTree, scene: &RecordedRenderScene) {
121 log::info!(
122 target: "cranpose::debug::screen",
123 "\n{}",
124 format_screen_summary(layout, scene)
125 );
126}
127
128fn count_nodes(layout_box: &LayoutBox) -> usize {
129 1 + layout_box.children.iter().map(count_nodes).sum::<usize>()
130}
131
132pub fn log_modifier_chain(chain: &ModifierNodeChain, nodes: &[ModifierChainInspectorNode]) {
134 log::info!(
135 target: "cranpose::debug::modifier",
136 "\n{}",
137 format_modifier_chain(chain, nodes)
138 );
139}
140
141pub fn format_modifier_chain(
143 chain: &ModifierNodeChain,
144 nodes: &[ModifierChainInspectorNode],
145) -> String {
146 let mut output = String::new();
147 writeln!(output, "\n=== MODIFIER CHAIN ===").ok();
148 writeln!(
149 output,
150 "Total nodes: {} (entries: {})",
151 nodes.len(),
152 chain.len()
153 )
154 .ok();
155 writeln!(
156 output,
157 "Aggregated capabilities: {}",
158 describe_capabilities(chain.capabilities())
159 )
160 .ok();
161 for node in nodes {
162 let indent = " ".repeat(node.depth);
163 let inspector = node
164 .inspector
165 .as_ref()
166 .map(describe_inspector)
167 .unwrap_or_default();
168 let inspector_suffix = if inspector.is_empty() {
169 String::new()
170 } else {
171 format!(" {inspector}")
172 };
173 writeln!(
174 output,
175 "{}- {} caps={} agg={}{}",
176 indent,
177 node.type_name,
178 describe_capabilities(node.capabilities),
179 describe_capabilities(node.aggregate_child_capabilities),
180 inspector_suffix,
181 )
182 .ok();
183 }
184 writeln!(output, "=== END MODIFIER CHAIN ===\n").ok();
185 output
186}
187
188fn describe_capabilities(mask: NodeCapabilities) -> String {
189 let mut parts = Vec::new();
190 if mask.contains(NodeCapabilities::LAYOUT) {
191 parts.push("LAYOUT");
192 }
193 if mask.contains(NodeCapabilities::DRAW) {
194 parts.push("DRAW");
195 }
196 if mask.contains(NodeCapabilities::POINTER_INPUT) {
197 parts.push("POINTER_INPUT");
198 }
199 if mask.contains(NodeCapabilities::SEMANTICS) {
200 parts.push("SEMANTICS");
201 }
202 if mask.contains(NodeCapabilities::MODIFIER_LOCALS) {
203 parts.push("MODIFIER_LOCALS");
204 }
205 if mask.contains(NodeCapabilities::FOCUS) {
206 parts.push("FOCUS");
207 }
208 if parts.is_empty() {
209 "[NONE]".to_string()
210 } else {
211 format!("[{}]", parts.join("|"))
212 }
213}
214
215fn describe_inspector(record: &ModifierInspectorRecord) -> String {
216 if record.properties.is_empty() {
217 record.name.to_string()
218 } else {
219 let props = record
220 .properties
221 .iter()
222 .map(|prop| format!("{}={}", prop.name, prop.value))
223 .collect::<Vec<_>>()
224 .join(", ");
225 format!("{}({})", record.name, props)
226 }
227}
228
229pub struct ModifierChainTraceGuard {
231 context_id: Option<crate::render_state::AppContextId>,
232}
233
234impl Drop for ModifierChainTraceGuard {
235 fn drop(&mut self) {
236 if let Some(context_id) = self.context_id.take() {
237 crate::render_state::clear_modifier_chain_trace(context_id);
238 }
239 }
240}
241
242pub fn install_modifier_chain_trace<F>(callback: F) -> ModifierChainTraceGuard
244where
245 F: Fn(&[ModifierChainInspectorNode]) + Send + Sync + 'static,
246{
247 let context_id = crate::render_state::set_modifier_chain_trace(Arc::new(callback));
248 ModifierChainTraceGuard {
249 context_id: Some(context_id),
250 }
251}
252
253pub(crate) fn emit_modifier_chain_trace(nodes: &[ModifierChainInspectorNode]) {
254 crate::render_state::emit_modifier_chain_trace(nodes);
255}
256
257#[cfg(test)]
258#[path = "tests/debug_tests.rs"]
259mod tests;