use super::super::helpers::draw_text_overlay;
use super::super::DevToolsConfig;
use super::picker::ComponentPicker;
use super::types::{InspectorConfig, WidgetNode};
use crate::layout::Rect;
use crate::render::Buffer;
use crate::style::Color;
use std::collections::HashMap;
#[derive(Debug, Default)]
pub struct Inspector {
nodes: HashMap<usize, WidgetNode>,
roots: Vec<usize>,
selected: Option<usize>,
next_id: usize,
config: InspectorConfig,
scroll: usize,
picker: ComponentPicker,
}
impl Inspector {
pub fn new() -> Self {
Self::default()
}
pub fn config(mut self, config: InspectorConfig) -> Self {
self.config = config;
self
}
pub fn show_bounds(mut self, show: bool) -> Self {
self.config.show_bounds = show;
self
}
pub fn show_ids(mut self, show: bool) -> Self {
self.config.show_ids = show;
self
}
pub fn show_classes(mut self, show: bool) -> Self {
self.config.show_classes = show;
self
}
pub fn clear(&mut self) {
self.nodes.clear();
self.roots.clear();
self.selected = None;
self.next_id = 0;
}
pub fn add_root(&mut self, type_name: impl Into<String>) -> usize {
let id = self.next_id;
self.next_id += 1;
let node = WidgetNode::new(id, type_name);
self.nodes.insert(id, node);
self.roots.push(id);
id
}
pub fn add_child(&mut self, parent_id: usize, type_name: impl Into<String>) -> usize {
let id = self.next_id;
self.next_id += 1;
let mut node = WidgetNode::new(id, type_name);
node.parent = Some(parent_id);
if let Some(parent) = self.nodes.get_mut(&parent_id) {
parent.children.push(id);
}
self.nodes.insert(id, node);
id
}
pub fn get(&self, id: usize) -> Option<&WidgetNode> {
self.nodes.get(&id)
}
pub fn get_mut(&mut self, id: usize) -> Option<&mut WidgetNode> {
self.nodes.get_mut(&id)
}
pub fn select(&mut self, id: Option<usize>) {
if let Some(current) = self.selected {
if let Some(node) = self.nodes.get_mut(¤t) {
node.selected = false;
}
}
self.selected = id;
if let Some(id) = id {
if let Some(node) = self.nodes.get_mut(&id) {
node.selected = true;
}
}
}
pub fn selected(&self) -> Option<&WidgetNode> {
self.selected.and_then(|id| self.nodes.get(&id))
}
pub fn toggle_expand(&mut self, id: usize) {
if let Some(node) = self.nodes.get_mut(&id) {
node.expanded = !node.expanded;
}
}
pub fn select_next(&mut self) {
let visible = self.visible_nodes();
if visible.is_empty() {
return;
}
let current_idx = self
.selected
.and_then(|id| visible.iter().position(|&i| i == id))
.unwrap_or(0);
let next_idx = (current_idx + 1).min(visible.len() - 1);
self.select(Some(visible[next_idx]));
}
pub fn select_prev(&mut self) {
let visible = self.visible_nodes();
if visible.is_empty() {
return;
}
let current_idx = self
.selected
.and_then(|id| visible.iter().position(|&i| i == id))
.unwrap_or(0);
let prev_idx = current_idx.saturating_sub(1);
self.select(Some(visible[prev_idx]));
}
pub fn picker(&self) -> &ComponentPicker {
&self.picker
}
pub fn picker_mut(&mut self) -> &mut ComponentPicker {
&mut self.picker
}
pub fn toggle_picker(&mut self) {
self.picker.toggle();
}
pub fn enable_picker(&mut self) {
self.picker.enable();
}
pub fn disable_picker(&mut self) {
self.picker.disable();
}
pub fn is_picker_active(&self) -> bool {
self.picker.is_active()
}
pub fn picker_mouse_move(&mut self, x: u16, y: u16) {
if !self.picker.is_active() {
return;
}
self.picker.set_mouse_pos(x, y);
let hovered = self.picker.find_node_at(x, y, &self.nodes);
self.picker.set_hovered(hovered);
}
pub fn picker_click(&mut self, x: u16, y: u16) -> Option<usize> {
if !self.picker.is_active() {
return None;
}
if let Some(node_id) = self.picker.find_node_at(x, y, &self.nodes) {
self.select(Some(node_id));
self.picker.disable();
self.reveal_node(node_id);
return Some(node_id);
}
None
}
pub fn reveal_node(&mut self, node_id: usize) {
let mut parents = Vec::new();
let mut current = self.nodes.get(&node_id).and_then(|n| n.parent);
while let Some(parent_id) = current {
parents.push(parent_id);
current = self.nodes.get(&parent_id).and_then(|n| n.parent);
}
for parent_id in parents {
if let Some(node) = self.nodes.get_mut(&parent_id) {
node.expanded = true;
}
}
}
pub fn render_picker_overlay(&self, buffer: &mut Buffer, area: Rect) {
self.picker.render_overlay(buffer, area, &self.nodes);
}
fn visible_nodes(&self) -> Vec<usize> {
let mut result = Vec::new();
for &root in &self.roots {
self.collect_visible(root, &mut result);
}
result
}
fn collect_visible(&self, id: usize, result: &mut Vec<usize>) {
result.push(id);
if let Some(node) = self.nodes.get(&id) {
if node.expanded {
for &child in &node.children {
self.collect_visible(child, result);
}
}
}
}
pub fn render_content(&self, buffer: &mut Buffer, area: Rect, config: &DevToolsConfig) {
let mut y = area.y;
let max_y = area.y + area.height;
let visible = self.visible_nodes();
for (_i, &id) in visible.iter().enumerate().skip(self.scroll) {
if y >= max_y {
break;
}
if let Some(node) = self.nodes.get(&id) {
self.render_node(buffer, area.x, y, area.width, node, config);
y += 1;
}
}
if let Some(node) = self.selected() {
if y + 3 < max_y {
y += 1;
self.render_separator(buffer, area.x, y, area.width, config);
y += 1;
self.render_details(buffer, area.x, y, area.width, node, config);
}
}
}
fn render_node(
&self,
buffer: &mut Buffer,
x: u16,
y: u16,
width: u16,
node: &WidgetNode,
config: &DevToolsConfig,
) {
let depth = node.depth(&self.nodes);
let indent = (depth * 2) as u16;
let mut px = x + indent;
let indicator = if node.children.is_empty() {
" "
} else if node.expanded {
"â–¼ "
} else {
"â–¶ "
};
for ch in indicator.chars() {
if px < x + width {
if let Some(cell) = buffer.get_mut(px, y) {
cell.symbol = ch;
cell.fg = Some(config.accent_color);
}
px += 1;
}
}
let label = node.label();
let fg = if node.selected {
config.bg_color
} else {
config.fg_color
};
let bg = if node.selected {
Some(config.accent_color)
} else {
None
};
for ch in label.chars() {
if px < x + width {
if let Some(cell) = buffer.get_mut(px, y) {
cell.symbol = ch;
cell.fg = Some(fg);
if let Some(b) = bg {
cell.bg = Some(b);
}
}
px += 1;
}
}
}
fn render_separator(
&self,
buffer: &mut Buffer,
x: u16,
y: u16,
width: u16,
config: &DevToolsConfig,
) {
for px in x..x + width {
if let Some(cell) = buffer.get_mut(px, y) {
cell.symbol = '─';
cell.fg = Some(config.accent_color);
}
}
}
fn render_details(
&self,
buffer: &mut Buffer,
x: u16,
y: u16,
_width: u16,
node: &WidgetNode,
config: &DevToolsConfig,
) {
let mut py = y;
let type_line = format!("Type: {}", node.type_name);
self.draw_text(buffer, x, py, &type_line, config.fg_color);
py += 1;
if self.config.show_rect {
let rect_line = format!(
"Rect: {}x{} @ ({}, {})",
node.rect.width, node.rect.height, node.rect.x, node.rect.y
);
self.draw_text(buffer, x, py, &rect_line, config.fg_color);
}
}
fn draw_text(&self, buffer: &mut Buffer, x: u16, y: u16, text: &str, color: Color) {
draw_text_overlay(buffer, x, y, text, color);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_inspector_new() {
let inspector = Inspector::new();
assert!(inspector.nodes.is_empty());
assert!(inspector.roots.is_empty());
assert!(inspector.selected.is_none());
assert_eq!(inspector.next_id, 0);
}
#[test]
fn test_inspector_default() {
let inspector = Inspector::default();
assert!(inspector.nodes.is_empty());
assert!(inspector.roots.is_empty());
}
#[test]
fn test_show_bounds() {
let inspector = Inspector::new().show_bounds(true);
assert!(inspector.config.show_bounds);
}
#[test]
fn test_show_ids() {
let inspector = Inspector::new().show_ids(true);
assert!(inspector.config.show_ids);
}
#[test]
fn test_show_classes() {
let inspector = Inspector::new().show_classes(true);
assert!(inspector.config.show_classes);
}
#[test]
fn test_config() {
let config = InspectorConfig {
show_bounds: true,
show_ids: true,
show_classes: true,
show_rect: true,
highlight_color: Color::rgb(255, 255, 255),
};
let inspector = Inspector::new().config(config);
assert!(inspector.config.show_bounds);
assert!(inspector.config.show_ids);
assert!(inspector.config.show_classes);
assert!(inspector.config.show_rect);
}
#[test]
fn test_clear() {
let mut inspector = Inspector::new();
inspector.add_root("Root");
inspector.select(Some(0));
assert_eq!(inspector.nodes.len(), 1);
assert!(inspector.selected.is_some());
inspector.clear();
assert!(inspector.nodes.is_empty());
assert!(inspector.roots.is_empty());
assert!(inspector.selected.is_none());
assert_eq!(inspector.next_id, 0);
}
#[test]
fn test_add_root() {
let mut inspector = Inspector::new();
let id1 = inspector.add_root("Root1");
assert_eq!(id1, 0);
assert_eq!(inspector.nodes.len(), 1);
assert_eq!(inspector.roots.len(), 1);
assert_eq!(inspector.next_id, 1);
let id2 = inspector.add_root("Root2");
assert_eq!(id2, 1);
assert_eq!(inspector.nodes.len(), 2);
assert_eq!(inspector.roots.len(), 2);
assert_eq!(inspector.next_id, 2);
}
#[test]
fn test_add_child() {
let mut inspector = Inspector::new();
let root = inspector.add_root("Root");
assert_eq!(root, 0);
let child = inspector.add_child(root, "Child");
assert_eq!(child, 1);
assert_eq!(inspector.nodes.len(), 2);
let parent = inspector.get(root).unwrap();
assert_eq!(parent.children.len(), 1);
assert_eq!(parent.children[0], child);
let child_node = inspector.get(child).unwrap();
assert_eq!(child_node.parent, Some(root));
}
#[test]
fn test_get() {
let mut inspector = Inspector::new();
let id = inspector.add_root("Root");
let node = inspector.get(id);
assert!(node.is_some());
assert_eq!(node.unwrap().type_name, "Root");
let non_existent = inspector.get(999);
assert!(non_existent.is_none());
}
#[test]
fn test_get_mut() {
let mut inspector = Inspector::new();
let id = inspector.add_root("Root");
let node = inspector.get_mut(id);
assert!(node.is_some());
assert_eq!(node.unwrap().type_name, "Root");
let non_existent = inspector.get_mut(999);
assert!(non_existent.is_none());
}
#[test]
fn test_select() {
let mut inspector = Inspector::new();
let id1 = inspector.add_root("Root1");
let id2 = inspector.add_root("Root2");
inspector.select(Some(id1));
assert_eq!(inspector.selected, Some(id1));
let node = inspector.get(id1).unwrap();
assert!(node.selected);
inspector.select(Some(id2));
assert_eq!(inspector.selected, Some(id2));
let node1 = inspector.get(id1).unwrap();
assert!(!node1.selected);
let node2 = inspector.get(id2).unwrap();
assert!(node2.selected);
inspector.select(None);
assert!(inspector.selected.is_none());
let node2 = inspector.get(id2).unwrap();
assert!(!node2.selected);
}
#[test]
fn test_selected() {
let mut inspector = Inspector::new();
assert!(inspector.selected().is_none());
let id = inspector.add_root("Root");
inspector.select(Some(id));
let selected = inspector.selected();
assert!(selected.is_some());
assert_eq!(selected.unwrap().type_name, "Root");
}
#[test]
fn test_toggle_expand() {
let mut inspector = Inspector::new();
let root = inspector.add_root("Root");
inspector.add_child(root, "Child");
let node = inspector.get(root).unwrap();
assert!(node.expanded);
inspector.toggle_expand(root);
let node = inspector.get(root).unwrap();
assert!(!node.expanded);
inspector.toggle_expand(root);
let node = inspector.get(root).unwrap();
assert!(node.expanded);
}
#[test]
fn test_select_next() {
let mut inspector = Inspector::new();
let _id1 = inspector.add_root("Root1");
let id2 = inspector.add_root("Root2");
let id3 = inspector.add_root("Root3");
inspector.select_next();
assert_eq!(inspector.selected, Some(id2));
inspector.select_next();
assert_eq!(inspector.selected, Some(id3));
inspector.select_next();
assert_eq!(inspector.selected, Some(id3));
inspector.select_next();
assert_eq!(inspector.selected, Some(id3));
}
#[test]
fn test_select_prev() {
let mut inspector = Inspector::new();
let id1 = inspector.add_root("Root1");
let id2 = inspector.add_root("Root2");
let id3 = inspector.add_root("Root3");
inspector.select(Some(id3));
inspector.select_prev();
assert_eq!(inspector.selected, Some(id2));
inspector.select_prev();
assert_eq!(inspector.selected, Some(id1));
inspector.select_prev();
assert_eq!(inspector.selected, Some(id1));
}
#[test]
fn test_select_next_with_children() {
let mut inspector = Inspector::new();
let root = inspector.add_root("Root");
let child1 = inspector.add_child(root, "Child1");
let child2 = inspector.add_child(root, "Child2");
inspector.select_next();
assert_eq!(inspector.selected, Some(child1));
inspector.select_next();
assert_eq!(inspector.selected, Some(child2));
}
#[test]
fn test_picker() {
let inspector = Inspector::new();
let _picker = inspector.picker();
}
#[test]
fn test_picker_mut() {
let mut inspector = Inspector::new();
let _picker = inspector.picker_mut();
}
#[test]
fn test_is_picker_active() {
let inspector = Inspector::new();
assert!(!inspector.is_picker_active());
}
#[test]
fn test_toggle_picker() {
let mut inspector = Inspector::new();
assert!(!inspector.is_picker_active());
inspector.toggle_picker();
inspector.toggle_picker();
}
#[test]
fn test_enable_picker() {
let mut inspector = Inspector::new();
inspector.enable_picker();
}
#[test]
fn test_disable_picker() {
let mut inspector = Inspector::new();
inspector.disable_picker();
}
#[test]
fn test_picker_mouse_move() {
let mut inspector = Inspector::new();
inspector.picker_mouse_move(10, 20);
}
#[test]
fn test_picker_click() {
let mut inspector = Inspector::new();
let result = inspector.picker_click(10, 20);
assert!(result.is_none());
}
#[test]
fn test_reveal_node() {
let mut inspector = Inspector::new();
let root = inspector.add_root("Root");
let child1 = inspector.add_child(root, "Child1");
let child2 = inspector.add_child(child1, "Child2");
let root_node = inspector.get(root).unwrap();
assert!(root_node.expanded);
let child1_node = inspector.get(child1).unwrap();
assert!(child1_node.expanded);
inspector.toggle_expand(root);
inspector.toggle_expand(child1);
assert!(!inspector.get(root).unwrap().expanded);
assert!(!inspector.get(child1).unwrap().expanded);
inspector.reveal_node(child2);
let root_node = inspector.get(root).unwrap();
assert!(root_node.expanded);
let child1_node = inspector.get(child1).unwrap();
assert!(child1_node.expanded);
}
#[test]
fn test_reveal_root_node() {
let mut inspector = Inspector::new();
let root = inspector.add_root("Root");
inspector.reveal_node(root);
let root_node = inspector.get(root).unwrap();
assert!(root_node.expanded);
}
#[test]
fn test_visible_nodes_single() {
let mut inspector = Inspector::new();
inspector.add_root("Root");
let visible_count = {
let inspector = &inspector;
inspector.visible_nodes().len()
};
assert_eq!(visible_count, 1);
}
#[test]
fn test_visible_nodes_with_hierarchy() {
let mut inspector = Inspector::new();
let root = inspector.add_root("Root");
inspector.add_child(root, "Child1");
inspector.add_child(root, "Child2");
let visible = inspector.visible_nodes();
assert_eq!(visible.len(), 3);
assert_eq!(visible[0], root);
inspector.toggle_expand(root);
let visible = inspector.visible_nodes();
assert_eq!(visible.len(), 1);
assert_eq!(visible[0], root);
}
#[test]
fn test_multiple_roots() {
let mut inspector = Inspector::new();
let root1 = inspector.add_root("Root1");
let _root2 = inspector.add_root("Root2");
let _child1 = inspector.add_child(root1, "Child1");
let visible = inspector.visible_nodes();
assert_eq!(visible.len(), 3); }
#[test]
fn test_select_wrapping() {
let mut inspector = Inspector::new();
let id = inspector.add_root("Root");
inspector.select_next();
assert_eq!(inspector.selected, Some(id));
inspector.select_prev();
assert_eq!(inspector.selected, Some(id));
}
}