Skip to main content

cranpose_ui/
debug.rs

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
11/// Logs the current layout tree through the logger with indentation showing hierarchy.
12pub fn log_layout_tree(layout: &LayoutTree) {
13    log::info!(
14        target: "cranpose::debug::layout",
15        "\n{}",
16        format_layout_tree(layout)
17    );
18}
19
20/// Logs the current render scene through the logger.
21pub fn log_render_scene(scene: &RecordedRenderScene) {
22    log::info!(
23        target: "cranpose::debug::render",
24        "\n{}",
25        format_render_scene(scene)
26    );
27}
28
29/// Returns a formatted string representation of the layout tree
30pub 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
54/// Returns a formatted string representation of the render scene
55pub 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
92/// Returns a compact summary of what's on screen (counts by type).
93pub 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
120/// Logs a compact summary of what's on screen (counts by type).
121pub 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
133/// Logs the contents of a modifier node chain including capabilities.
134pub 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
142/// Formats the modifier chain using inspector data.
143pub 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
230/// RAII guard returned when installing a modifier chain trace subscriber.
231pub 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
243/// Installs a callback that receives modifier chain snapshots when debugging is enabled.
244pub 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;