taffy/util/
debug.rs

1#![allow(dead_code)]
2
3use crate::tree::NodeId;
4use crate::{style, LayoutTree};
5
6#[cfg(any(feature = "debug", feature = "profile"))]
7use core::fmt::{Debug, Display, Write};
8#[cfg(any(feature = "debug", feature = "profile"))]
9use std::sync::Mutex;
10
11/// Prints a debug representation of the computed layout for a tree of nodes, starting with the passed root node.
12#[cfg(feature = "std")]
13pub fn print_tree(tree: &impl LayoutTree, root: NodeId) {
14    println!("TREE");
15    print_node(tree, root, false, String::new());
16}
17
18#[cfg(feature = "std")]
19fn print_node(tree: &impl LayoutTree, node: NodeId, has_sibling: bool, lines_string: String) {
20    let layout = &tree.get_final_layout(node);
21    let style = &tree.get_style(node);
22    let num_children = tree.child_count(node);
23
24    let display = match (num_children, style.display) {
25        (_, style::Display::None) => "NONE",
26        (0, _) => "LEAF",
27        #[cfg(feature = "block_layout")]
28        (_, style::Display::Block) => "BLOCK",
29        #[cfg(feature = "flexbox")]
30        (_, style::Display::Flex) => "FLEX",
31        #[cfg(feature = "grid")]
32        (_, style::Display::Grid) => "GRID",
33    };
34
35    let fork_string = if has_sibling { "├── " } else { "└── " };
36    #[cfg(feature = "content_size")]
37    println!(
38            "{lines}{fork} {display} [x: {x:<4} y: {y:<4} w: {width:<4} h: {height:<4} content_w: {content_width:<4} content_h: {content_height:<4} border: l:{bl} r:{br} t:{bt} b:{bb}, padding: l:{pl} r:{pr} t:{pt} b:{pb}] ({key:?})",
39            lines = lines_string,
40            fork = fork_string,
41            display = display,
42            x = layout.location.x,
43            y = layout.location.y,
44            width = layout.size.width,
45            height = layout.size.height,
46            content_width = layout.content_size.width,
47            content_height = layout.content_size.height,
48            bl = layout.border.left,
49            br = layout.border.right,
50            bt = layout.border.top,
51            bb = layout.border.bottom,
52            pl = layout.padding.left,
53            pr = layout.padding.right,
54            pt = layout.padding.top,
55            pb = layout.padding.bottom,
56            key = node,
57        );
58    #[cfg(not(feature = "content_size"))]
59    println!(
60        "{lines}{fork} {display} [x: {x:<4} y: {y:<4} width: {width:<4} height: {height:<4}] ({key:?})",
61        lines = lines_string,
62        fork = fork_string,
63        display = display,
64        x = layout.location.x,
65        y = layout.location.y,
66        width = layout.size.width,
67        height = layout.size.height,
68        key = node,
69    );
70    let bar = if has_sibling { "│   " } else { "    " };
71    let new_string = lines_string + bar;
72
73    // Recurse into children
74    for (index, child) in tree.child_ids(node).enumerate() {
75        let has_sibling = index < num_children - 1;
76        print_node(tree, child, has_sibling, new_string.clone());
77    }
78}
79
80#[doc(hidden)]
81#[cfg(any(feature = "debug", feature = "profile"))]
82pub struct DebugLogger {
83    stack: Mutex<Vec<String>>,
84}
85
86#[cfg(any(feature = "debug", feature = "profile"))]
87static EMPTY_STRING: String = String::new();
88#[cfg(any(feature = "debug", feature = "profile"))]
89impl DebugLogger {
90    pub const fn new() -> Self {
91        Self { stack: Mutex::new(Vec::new()) }
92    }
93
94    pub fn push_node(&self, new_key: NodeId) {
95        let mut stack = self.stack.lock().unwrap();
96        let mut key_string = String::new();
97        write!(&mut key_string, "{:?}", new_key).unwrap();
98        stack.push(key_string);
99    }
100
101    pub fn pop_node(&self) {
102        let mut stack = self.stack.lock().unwrap();
103        stack.pop();
104    }
105
106    pub fn log(&self, message: impl Display) {
107        let stack = self.stack.lock().unwrap();
108        let key = stack.last().unwrap_or(&EMPTY_STRING);
109        let level = stack.len() * 4;
110        let space = " ";
111        println!("{space:level$}{key}: {message}");
112    }
113
114    pub fn labelled_log(&self, label: &str, message: impl Display) {
115        let stack = self.stack.lock().unwrap();
116        let key = stack.last().unwrap_or(&EMPTY_STRING);
117        let level = stack.len() * 4;
118        let space = " ";
119        println!("{space:level$}{key}: {label} {message}");
120    }
121
122    pub fn debug_log(&self, message: impl Debug) {
123        let stack = self.stack.lock().unwrap();
124        let key = stack.last().unwrap_or(&EMPTY_STRING);
125        let level = stack.len() * 4;
126        let space = " ";
127        println!("{space:level$}{key}: {message:?}");
128    }
129
130    pub fn labelled_debug_log(&self, label: &str, message: impl Debug) {
131        let stack = self.stack.lock().unwrap();
132        let key = stack.last().unwrap_or(&EMPTY_STRING);
133        let level = stack.len() * 4;
134        let space = " ";
135        println!("{space:level$}{key}: {label} {message:?}");
136    }
137}
138
139#[cfg(any(feature = "debug", feature = "profile"))]
140pub(crate) static NODE_LOGGER: DebugLogger = DebugLogger::new();
141
142macro_rules! debug_log {
143    // String literal label with debug printing
144    ($label:literal, dbg:$item:expr) => {
145        #[cfg(feature = "debug")]
146        $crate::util::debug::NODE_LOGGER.labelled_debug_log($label, $item);
147    };
148    // String literal label with display printing
149    ($label:literal, $item:expr) => {
150        #[cfg(feature = "debug")]
151        $crate::util::debug::NODE_LOGGER.labelled_log($label, $item);
152    };
153    // Debug printing
154    (dbg:$item:expr) => {
155        #[cfg(feature = "debug")]
156        $crate::util::debug::NODE_LOGGER.debug_log($item);
157    };
158    // Display printing
159    ($item:expr) => {
160        #[cfg(feature = "debug")]
161        $crate::util::debug::NODE_LOGGER.log($item);
162    };
163    // Blank newline
164    () => {
165        #[cfg(feature = "debug")]
166        println!();
167    };
168}
169
170macro_rules! debug_log_node {
171    ($known_dimensions: expr, $parent_size: expr, $available_space: expr, $run_mode: expr, $sizing_mode: expr) => {
172        debug_log!(dbg:$run_mode);
173        debug_log!("sizing_mode", dbg:$sizing_mode);
174        debug_log!("known_dimensions", dbg:$known_dimensions);
175        debug_log!("parent_size", dbg:$parent_size);
176        debug_log!("available_space", dbg:$available_space);
177    };
178}
179
180macro_rules! debug_push_node {
181    ($node_id:expr) => {
182        #[cfg(any(feature = "debug", feature = "profile"))]
183        $crate::util::debug::NODE_LOGGER.push_node($node_id);
184        debug_log!("");
185    };
186}
187
188macro_rules! debug_pop_node {
189    () => {
190        #[cfg(any(feature = "debug", feature = "profile"))]
191        $crate::util::debug::NODE_LOGGER.pop_node();
192    };
193}
194
195#[cfg(feature = "profile")]
196#[allow(unused_macros)]
197macro_rules! time {
198    ($label:expr, $($code:tt)*) => {
199        let start = ::std::time::Instant::now();
200        $($code)*
201        let duration = ::std::time::Instant::now().duration_since(start);
202        crate::util::debug::NODE_LOGGER.log(format_args!("Performed {} in {}ms", $label, duration.as_millis()));
203    };
204}
205
206#[cfg(not(feature = "profile"))]
207#[allow(unused_macros)]
208macro_rules! time {
209    ($label:expr, $($code:tt)*) => {
210        $($code)*
211    };
212}
213
214#[allow(unused_imports)]
215pub(crate) use {debug_log, debug_log_node, debug_pop_node, debug_push_node, time};