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