1use super::*;
2
3#[cfg(any(test, feature = "test-support"))]
4type SubcomposeScopeIds = Vec<(NodeId, Vec<(u64, Vec<usize>)>)>;
5#[cfg(any(test, feature = "test-support"))]
6type SlotGroupEntry = (usize, Key, Option<usize>, usize);
7
8impl<R> AppShell<R>
9where
10 R: Renderer,
11 R::Error: Debug,
12{
13 pub fn debug_info_report(&mut self) -> String {
16 debug_info_report(&mut self.app, &mut self.surfaces[0])
17 }
18
19 pub fn log_debug_info(&mut self) -> String {
22 let report = self.debug_info_report();
23 log::info!(target: "cranpose::debug::screen", "\n{report}");
24 report
25 }
26
27 pub fn layout_tree(&mut self) -> Option<&LayoutTree> {
29 let _app_context = self.app.app_context.enter_scope();
30 self.surfaces[0].layout_tree_in_context(&mut self.app)
31 }
32
33 #[doc(hidden)]
34 pub fn with_layout_tree<T>(&mut self, block: impl FnOnce(Option<&LayoutTree>) -> T) -> T {
35 let _app_context = self.app.app_context.enter_scope();
36 let layout_tree = self.surfaces[0].layout_tree_in_context(&mut self.app);
37 block(layout_tree)
38 }
39
40 pub fn semantics_active(&self) -> bool {
44 self.app.semantics_enabled
45 }
46
47 pub fn semantics_snapshot_revision(&mut self) -> u64 {
54 if self.app.semantics_enabled
55 || self
56 .surfaces
57 .iter()
58 .any(|surface| surface.inspector.state.open)
59 {
60 let semantics_dirty = {
61 let _app_context = self.app.app_context.enter_scope();
62 self.app.flush_semantics_invalidations();
63 self.app.composition.root().is_some_and(|root| {
64 let mut applier = self.app.composition.applier_mut();
65 cranpose_ui::tree_needs_semantics(&mut *applier, root).unwrap_or(true)
66 })
67 };
68 if semantics_dirty {
69 self.app.semantics_snapshot_revision =
70 self.app.semantics_snapshot_revision.wrapping_add(1);
71 }
72 }
73 self.app.semantics_snapshot_revision
74 }
75
76 pub fn semantics_tree(&mut self) -> Option<&SemanticsTree> {
78 let _app_context = self.app.app_context.enter_scope();
79 self.surfaces[0].semantics_tree_in_context(&mut self.app)
80 }
81
82 pub fn root_layout_size(&mut self) -> Option<(f32, f32)> {
85 self.layout_tree().map(|tree| {
86 let root = tree.root();
87 (root.rect.width, root.rect.height)
88 })
89 }
90
91 pub fn node_layout_bounds(&mut self, target: NodeId) -> Option<(f32, f32, f32, f32)> {
94 self.layout_tree()
95 .and_then(|tree| find_layout_box(tree.root(), target))
96 .map(layout_box_bounds)
97 }
98
99 #[cfg(any(test, feature = "test-support"))]
100 #[doc(hidden)]
101 pub fn debug_runtime_leak_stats(&mut self) -> RuntimeLeakDebugStats {
102 let runtime = self.app.composition.runtime_handle();
103 let (applier_stats, live_node_heap_bytes, recycled_node_heap_bytes) = {
104 let applier = self.app.composition.applier_mut();
105 (
106 applier.debug_stats(),
107 applier.debug_live_node_heap_bytes(),
108 applier.debug_recycled_node_heap_bytes(),
109 )
110 };
111 RuntimeLeakDebugStats {
112 applier_stats,
113 live_node_heap_bytes,
114 recycled_node_heap_bytes,
115 slot_table_heap_bytes: self.app.composition.slot_table_heap_bytes(),
116 pass_stats: self.app.composition.debug_last_pass_stats(),
117 slot_stats: self.app.composition.debug_slot_table_stats(),
118 runtime_stats: runtime.debug_stats(),
119 state_arena_stats: runtime.state_arena_debug_stats(),
120 recompose_scope_stats: debug_recompose_scope_registry_stats(),
121 snapshot_v2_stats: debug_snapshot_v2_stats(),
122 snapshot_pinning_stats: debug_snapshot_pinning_stats(),
123 }
124 }
125
126 #[cfg(any(test, feature = "test-support"))]
127 #[doc(hidden)]
128 pub fn debug_slot_table_groups(&self) -> Vec<(usize, Key, Option<usize>, usize)> {
129 self.app.composition.debug_dump_slot_table_groups()
130 }
131
132 #[cfg(any(test, feature = "test-support"))]
133 #[doc(hidden)]
134 pub fn debug_slot_entries(&self) -> Vec<cranpose_core::SlotDebugEntry> {
135 self.app.composition.debug_dump_slot_entries()
136 }
137
138 #[cfg(any(test, feature = "test-support"))]
139 #[doc(hidden)]
140 pub fn runtime_handle(&self) -> cranpose_core::RuntimeHandle {
141 self.app.composition.runtime_handle()
142 }
143
144 #[cfg(any(test, feature = "test-support"))]
145 #[doc(hidden)]
146 pub fn debug_live_subcompose_scope_ids(&mut self) -> SubcomposeScopeIds {
147 fn collect_node_ids(layout: &LayoutBox, out: &mut Vec<NodeId>) {
148 out.push(layout.node_id);
149 for child in &layout.children {
150 collect_node_ids(child, out);
151 }
152 }
153
154 let mut node_ids = Vec::new();
155 if let Some(tree) = self.layout_tree() {
156 collect_node_ids(tree.root(), &mut node_ids);
157 }
158
159 let mut applier = self.app.composition.applier_mut();
160 let mut result = Vec::new();
161 for node_id in node_ids {
162 if let Ok(scope_ids) = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
163 node.debug_scope_ids_by_slot()
164 }) {
165 result.push((node_id, scope_ids));
166 }
167 }
168 result
169 }
170
171 #[cfg(any(test, feature = "test-support"))]
172 #[doc(hidden)]
173 pub fn debug_subcompose_slot_table(
174 &mut self,
175 node_id: NodeId,
176 slot_id: u64,
177 ) -> Option<Vec<cranpose_core::SlotDebugEntry>> {
178 let mut applier = self.app.composition.applier_mut();
179 applier
180 .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
181 node.debug_slot_table_for_slot(SlotId::new(slot_id))
182 })
183 .ok()
184 .flatten()
185 }
186
187 #[cfg(any(test, feature = "test-support"))]
188 #[doc(hidden)]
189 pub fn debug_subcompose_slot_groups(
190 &mut self,
191 node_id: NodeId,
192 slot_id: u64,
193 ) -> Option<Vec<SlotGroupEntry>> {
194 let mut applier = self.app.composition.applier_mut();
195 applier
196 .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
197 node.debug_slot_table_groups_for_slot(SlotId::new(slot_id))
198 })
199 .ok()
200 .flatten()
201 }
202}
203
204fn find_layout_box(layout_box: &LayoutBox, target: NodeId) -> Option<&LayoutBox> {
205 if layout_box.node_id == target {
206 return Some(layout_box);
207 }
208
209 layout_box
210 .children
211 .iter()
212 .find_map(|child| find_layout_box(child, target))
213}
214
215fn layout_box_bounds(layout_box: &LayoutBox) -> (f32, f32, f32, f32) {
216 (
217 layout_box.rect.x,
218 layout_box.rect.y,
219 layout_box.rect.width,
220 layout_box.rect.height,
221 )
222}
223
224fn debug_info_report<R: Renderer>(app: &mut ShellApp, surface: &mut RootSurface<R>) -> String {
225 let _app_context = app.app_context.enter_scope();
226 let mut report = String::new();
227 writeln!(report, "=== DEBUG: CURRENT SCREEN STATE ===").ok();
228 if let Some(layout_tree) = surface.layout_tree_in_context(app) {
229 let renderer = HeadlessRenderer::new();
230 let render_scene = renderer.render(layout_tree);
231 writeln!(report, "{}", format_layout_tree(layout_tree)).ok();
232 writeln!(report, "{}", format_render_scene(&render_scene)).ok();
233 writeln!(
234 report,
235 "{}",
236 format_screen_summary(layout_tree, &render_scene)
237 )
238 .ok();
239 } else {
240 writeln!(report, "No layout available").ok();
241 }
242 report
243}
244
245impl<R> SurfaceMut<'_, R>
246where
247 R: Renderer,
248 R::Error: Debug,
249{
250 pub fn debug_info_report(&mut self) -> String {
252 let (app, surface) = self.parts();
253 debug_info_report(app, surface)
254 }
255
256 pub fn log_debug_info(&mut self) -> String {
258 let report = self.debug_info_report();
259 log::info!(target: "cranpose::debug::screen", "\n{report}");
260 report
261 }
262}