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