1use std::fmt::Debug;
4
5use cranpose_core::NodeId;
6use cranpose_render_common::Renderer;
7use cranpose_ui::{KeyCode, KeyEvent, KeyEventType, SemanticsTree};
8use cranpose_ui_graphics::{EdgeInsets, Point, Rect, Size};
9
10use crate::{AppShell, RootSurface, ShellApp, SurfaceMut};
11
12#[path = "inspector_draw.rs"]
13mod draw;
14
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
17pub enum InspectorMode {
18 #[default]
20 Normal,
21 Overlay,
23 Accessibility,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub enum InspectorAction {
30 Toggle,
32 Move,
34 Normal,
36 Overlay,
38 Accessibility,
40 Pick,
42 Previous,
44 Next,
46 DetailsUp,
48 DetailsDown,
50 Select(usize),
52}
53
54#[derive(Clone, Debug, PartialEq)]
56pub struct InspectorNode {
57 pub node_id: NodeId,
59 pub canvas_key: Option<u64>,
61 pub bounds: Rect,
63 pub label: String,
65 pub details: String,
67 pub focused: bool,
69 pub issue: bool,
71}
72
73#[derive(Clone, Debug, PartialEq)]
75pub struct InspectorControl {
76 pub action: InspectorAction,
78 pub bounds: Rect,
80}
81
82#[derive(Clone, Debug, Default, PartialEq)]
84pub struct InspectorState {
85 pub open: bool,
87 pub mode: InspectorMode,
89 pub picking: bool,
91 pub nodes: Vec<InspectorNode>,
93 pub selected: Option<usize>,
95 pub detail_offset: usize,
97 pub controls: Vec<InspectorControl>,
99 pub launcher_position: Option<Point>,
101 pub panel_position: Option<Point>,
103}
104
105pub type InspectorProjector = fn(&SemanticsTree) -> Vec<InspectorNode>;
107
108#[derive(Default)]
109pub(crate) struct DeveloperInspector {
110 pub(crate) state: InspectorState,
111 revision: Option<u64>,
112 viewport: Option<Size>,
113 insets: EdgeInsets,
115 dirty: bool,
116 pub(crate) pointer_captured: bool,
117 keyboard: bool,
118 installed: bool,
119 drag: Option<InspectorDrag>,
120}
121
122struct InspectorDrag {
123 action: InspectorAction,
124 start: Point,
125 origin: Point,
126 moved: bool,
127}
128
129impl DeveloperInspector {
130 fn area(&self) -> Rect {
133 let viewport = self.viewport.unwrap_or_default();
134 let insets = self.insets;
135 Rect {
136 x: insets.left.min(viewport.width),
137 y: insets.top.min(viewport.height),
138 width: (viewport.width - insets.left - insets.right).max(0.0),
139 height: (viewport.height - insets.top - insets.bottom).max(0.0),
140 }
141 }
142
143 fn apply(&mut self, action: InspectorAction) {
144 match action {
145 InspectorAction::Move => return,
146 InspectorAction::Toggle => {
147 self.state.open = !self.state.open;
148 self.state.picking = false;
149 self.keyboard = self.state.open;
150 if !self.state.open {
151 self.state.mode = InspectorMode::Normal;
152 self.state.nodes.clear();
153 self.state.selected = None;
154 self.revision = None;
155 }
156 }
157 InspectorAction::Normal => self.state.mode = InspectorMode::Normal,
158 InspectorAction::Overlay => self.state.mode = InspectorMode::Overlay,
159 InspectorAction::Accessibility => self.state.mode = InspectorMode::Accessibility,
160 InspectorAction::Pick => self.state.picking = !self.state.picking,
161 InspectorAction::Previous => self.select_relative(false),
162 InspectorAction::Next => self.select_relative(true),
163 InspectorAction::DetailsUp => {
164 self.state.detail_offset = self.state.detail_offset.saturating_sub(3);
165 }
166 InspectorAction::DetailsDown => {
167 let count = draw::detail_line_count(&self.state, self.area());
168 self.state.detail_offset =
169 (self.state.detail_offset + 3).min(count.saturating_sub(1));
170 }
171 InspectorAction::Select(index) => {
172 self.state.selected = (index < self.state.nodes.len()).then_some(index);
173 self.state.detail_offset = 0;
174 }
175 }
176 self.dirty = true;
177 }
178
179 fn select_relative(&mut self, forward: bool) {
180 self.state.detail_offset = 0;
181 let len = self.state.nodes.len();
182 if len == 0 {
183 self.state.selected = None;
184 return;
185 }
186 self.state.selected = Some(match self.state.selected {
187 Some(index) if forward => (index + 1) % len,
188 Some(index) => (index + len - 1) % len,
189 None if forward => 0,
190 None => len - 1,
191 });
192 }
193
194 fn replace_nodes(&mut self, nodes: Vec<InspectorNode>) {
195 if self.state.nodes == nodes {
196 return;
197 }
198 let identity = self
199 .state
200 .selected
201 .and_then(|index| self.state.nodes.get(index))
202 .map(|node| (node.node_id, node.canvas_key));
203 self.state.selected = identity.and_then(|identity| {
204 nodes
205 .iter()
206 .position(|node| (node.node_id, node.canvas_key) == identity)
207 });
208 self.state.detail_offset = 0;
209 self.state.nodes = nodes;
210 self.dirty = true;
211 }
212
213 fn pick(&mut self, x: f32, y: f32) {
214 self.state.selected = self
215 .state
216 .nodes
217 .iter()
218 .enumerate()
219 .filter(|(_, node)| node.bounds.contains(x, y))
220 .min_by(|(_, a), (_, b)| {
221 (a.bounds.width * a.bounds.height).total_cmp(&(b.bounds.width * b.bounds.height))
222 })
223 .map(|(index, _)| index);
224 self.state.picking = false;
225 self.state.detail_offset = 0;
226 self.keyboard = true;
227 self.dirty = true;
228 }
229
230 fn start_drag(&mut self, action: InspectorAction, x: f32, y: f32) {
231 let area = self.area();
232 let bounds = if action == InspectorAction::Move {
233 draw::panel_bounds(&self.state, area)
234 } else {
235 draw::launcher_bounds(&self.state, area)
236 };
237 self.drag = Some(InspectorDrag {
238 action,
239 start: Point { x, y },
240 origin: Point {
241 x: bounds.x,
242 y: bounds.y,
243 },
244 moved: false,
245 });
246 }
247
248 fn move_pointer(&mut self, x: f32, y: f32) -> bool {
249 let Some(drag) = &mut self.drag else {
250 return false;
251 };
252 let dx = x - drag.start.x;
253 let dy = y - drag.start.y;
254 drag.moved |= dx * dx + dy * dy >= 16.0;
255 if !drag.moved {
256 return false;
257 }
258 let position = Some(Point {
259 x: drag.origin.x + dx,
260 y: drag.origin.y + dy,
261 });
262 if drag.action == InspectorAction::Move {
263 self.state.panel_position = position;
264 } else {
265 self.state.launcher_position = position;
266 }
267 self.dirty = true;
268 true
269 }
270
271 pub(crate) fn release_pointer(&mut self) -> bool {
272 if !std::mem::take(&mut self.pointer_captured) {
273 return false;
274 }
275 if let Some(drag) = self.drag.take()
276 && !drag.moved
277 && drag.action != InspectorAction::Move
278 {
279 self.apply(drag.action);
280 }
281 true
282 }
283
284 pub(crate) fn cancel_pointer(&mut self) {
285 self.pointer_captured = false;
286 self.drag = None;
287 }
288}
289
290impl<R: Renderer> AppShell<R>
291where
292 R::Error: Debug,
293{
294 pub fn set_inspector_projector(&mut self, projector: Option<InspectorProjector>) {
299 self.app.inspector_projector = projector;
300 for surface in &mut self.surfaces {
301 surface.inspector = DeveloperInspector {
302 insets: surface.inspector.insets,
303 dirty: true,
304 ..Default::default()
305 };
306 if projector.is_none() {
307 surface.renderer.set_inspector_overlay(None);
308 }
309 surface.is_dirty = true;
310 }
311 }
312
313 pub fn set_safe_area(&mut self, insets: EdgeInsets) {
318 let surface = &mut self.surfaces[0];
319 if surface.inspector.insets != insets {
320 surface.inspector.insets = insets;
321 surface.inspector.dirty = true;
322 surface.is_dirty = true;
323 }
324 }
325
326 pub fn inspector_state(&self) -> &InspectorState {
328 &self.surfaces[0].inspector.state
329 }
330}
331
332impl<R: Renderer> SurfaceMut<'_, R>
333where
334 R::Error: Debug,
335{
336 pub fn inspector_state(&self) -> &InspectorState {
338 &self.surface().inspector.state
339 }
340
341 pub(crate) fn inspector_owns_keyboard(&self) -> bool {
342 let inspector = &self.surface().inspector;
343 inspector.state.open && inspector.keyboard
344 }
345
346 pub(crate) fn inspector_blocks_pointer(&self, x: f32, y: f32) -> bool {
347 let inspector = &self.surface().inspector;
348 self.shell_app_ref().inspector_projector.is_some()
349 && (inspector.pointer_captured
350 || inspector.state.picking
351 || inspector
352 .state
353 .controls
354 .iter()
355 .any(|control| control.bounds.contains(x, y))
356 || (inspector.state.open
357 && draw::panel_bounds(&inspector.state, inspector.area()).contains(x, y)))
358 }
359
360 pub(crate) fn inspector_move(&mut self, x: f32, y: f32) -> bool {
361 self.surface_mut().inspector.move_pointer(x, y)
362 }
363
364 pub(crate) fn inspector_scroll(&mut self, delta: f32) -> bool {
365 let (x, y) = self.surface().cursor;
366 if !self.inspector_blocks_pointer(x, y) {
367 return false;
368 }
369 let inspector = &mut self.surface_mut().inspector;
370 if inspector.state.open && delta != 0.0 {
371 inspector.apply(if delta < 0.0 {
372 InspectorAction::DetailsDown
373 } else {
374 InspectorAction::DetailsUp
375 });
376 self.mark_dirty();
377 }
378 true
379 }
380
381 pub(crate) fn inspector_press(&mut self, x: f32, y: f32) -> bool {
382 if self.shell_app_ref().inspector_projector.is_none() {
383 return false;
384 }
385 let inspector = &mut self.surface_mut().inspector;
386 let action = inspector
387 .state
388 .controls
389 .iter()
390 .find(|control| control.bounds.contains(x, y))
391 .map(|control| control.action);
392 let consumed = if let Some(action) = action {
393 if action == InspectorAction::Move || !inspector.state.open || inspector.state.picking {
394 inspector.start_drag(action, x, y);
395 } else {
396 inspector.apply(action);
397 }
398 inspector.keyboard = inspector.state.open;
399 true
400 } else if inspector.state.open && inspector.state.picking {
401 inspector.pick(x, y);
402 true
403 } else {
404 inspector.keyboard = false;
405 inspector.state.open
406 && draw::panel_bounds(&inspector.state, inspector.area()).contains(x, y)
407 };
408 if consumed {
409 inspector.keyboard = inspector.state.open;
410 inspector.pointer_captured = true;
411 self.mark_dirty();
412 }
413 consumed
414 }
415
416 pub(crate) fn inspector_key(&mut self, event: &KeyEvent) -> bool {
417 if self.shell_app_ref().inspector_projector.is_none() {
418 return false;
419 }
420 let inspector = &mut self.surface_mut().inspector;
421 let action = if inspector.state.open && inspector.keyboard {
422 match event.key_code {
423 KeyCode::Escape => Some(InspectorAction::Toggle),
424 KeyCode::ArrowUp | KeyCode::ArrowLeft => Some(InspectorAction::Previous),
425 KeyCode::ArrowDown | KeyCode::ArrowRight => Some(InspectorAction::Next),
426 KeyCode::Digit1 => Some(InspectorAction::Normal),
427 KeyCode::Digit2 => Some(InspectorAction::Overlay),
428 KeyCode::Digit3 => Some(InspectorAction::Accessibility),
429 KeyCode::P => Some(InspectorAction::Pick),
430 KeyCode::PageUp => Some(InspectorAction::DetailsUp),
431 KeyCode::PageDown => Some(InspectorAction::DetailsDown),
432 _ => None,
433 }
434 } else {
435 None
436 };
437 let Some(action) = action else {
438 return inspector.state.open && inspector.keyboard;
439 };
440 if event.event_type == KeyEventType::KeyDown {
441 inspector.apply(action);
442 self.mark_dirty();
443 }
444 true
445 }
446}
447
448pub(crate) fn refresh<R: Renderer>(
449 app: &mut ShellApp,
450 surface: &mut RootSurface<R>,
451 revision: u64,
452) -> bool {
453 let Some(projector) = app.inspector_projector else {
454 return false;
455 };
456 let viewport = surface.viewport_size();
457 if surface.inspector.viewport != Some(viewport) {
458 surface.inspector.viewport = Some(viewport);
459 surface.inspector.dirty = true;
460 }
461 if surface.inspector.state.open && surface.inspector.revision != Some(revision) {
462 surface.semantics_tree_in_context(app);
463 let nodes = surface
464 .semantics_tree
465 .as_ref()
466 .map_or_else(Vec::new, projector);
467 surface.inspector.replace_nodes(nodes);
468 surface.inspector.revision = Some(revision);
469 }
470 if !surface.inspector.dirty && surface.inspector.installed {
471 return false;
472 }
473 let area = surface.inspector.area();
474 let graph = draw::build(&mut surface.inspector.state, viewport, area);
475 surface.renderer.set_inspector_overlay(Some(graph));
476 surface.inspector.installed = true;
477 surface.inspector.dirty = false;
478 true
479}
480
481#[cfg(test)]
482#[path = "tests/inspector_tests.rs"]
483mod tests;