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                    "[{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
91/// Returns a compact summary of what's on screen (counts by type).
92pub 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
119/// Logs a compact summary of what's on screen (counts by type).
120pub 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
132/// Logs the contents of a modifier node chain including capabilities.
133pub 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
141/// Formats the modifier chain using inspector data.
142pub 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
229/// RAII guard returned when installing a modifier chain trace subscriber.
230pub 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
242/// Installs a callback that receives modifier chain snapshots when debugging is enabled.
243pub 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;