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