use ratatui::layout::Rect;
use super::types::{Annotation, WidgetType};
#[derive(Clone, Debug)]
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct RegionInfo {
pub area: SerializableRect,
pub annotation: Annotation,
pub parent: Option<usize>,
pub children: Vec<usize>,
pub depth: usize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct SerializableRect {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}
impl From<Rect> for SerializableRect {
fn from(rect: Rect) -> Self {
Self {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
}
}
}
impl From<SerializableRect> for Rect {
fn from(rect: SerializableRect) -> Self {
Rect::new(rect.x, rect.y, rect.width, rect.height)
}
}
impl SerializableRect {
pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
Self {
x,
y,
width,
height,
}
}
pub fn contains(&self, x: u16, y: u16) -> bool {
x >= self.x
&& x < self.x.saturating_add(self.width)
&& y >= self.y
&& y < self.y.saturating_add(self.height)
}
pub fn intersects(&self, other: &Self) -> bool {
self.x < other.x.saturating_add(other.width)
&& self.x.saturating_add(self.width) > other.x
&& self.y < other.y.saturating_add(other.height)
&& self.y.saturating_add(self.height) > other.y
}
}
#[derive(Clone, Debug, Default)]
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct AnnotationRegistry {
regions: Vec<RegionInfo>,
#[cfg_attr(feature = "serialization", serde(skip))]
open_stack: Vec<usize>,
#[cfg_attr(feature = "serialization", serde(skip))]
current_depth: usize,
}
impl AnnotationRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn clear(&mut self) {
self.regions.clear();
self.open_stack.clear();
self.current_depth = 0;
}
pub fn register(&mut self, area: Rect, annotation: Annotation) -> usize {
let parent = self.open_stack.last().copied();
let index = self.regions.len();
self.regions.push(RegionInfo {
area: area.into(),
annotation,
parent,
children: Vec::new(),
depth: self.current_depth,
});
if let Some(parent_idx) = parent {
self.regions[parent_idx].children.push(index);
}
index
}
pub fn open(&mut self, area: Rect, annotation: Annotation) -> usize {
let index = self.register(area, annotation);
self.open_stack.push(index);
self.current_depth += 1;
index
}
pub fn close(&mut self) {
self.open_stack.pop();
self.current_depth = self.current_depth.saturating_sub(1);
}
pub fn len(&self) -> usize {
self.regions.len()
}
pub fn is_empty(&self) -> bool {
self.regions.is_empty()
}
pub fn regions(&self) -> &[RegionInfo] {
&self.regions
}
pub fn get(&self, index: usize) -> Option<&RegionInfo> {
self.regions.get(index)
}
pub fn region_at(&self, x: u16, y: u16) -> Option<&RegionInfo> {
self.regions
.iter()
.filter(|r| r.area.contains(x, y))
.max_by_key(|r| r.depth)
}
pub fn regions_at(&self, x: u16, y: u16) -> Vec<&RegionInfo> {
self.regions
.iter()
.filter(|r| r.area.contains(x, y))
.collect()
}
pub fn find_by_id(&self, id: &str) -> Vec<&RegionInfo> {
self.regions
.iter()
.filter(|r| r.annotation.has_id(id))
.collect()
}
pub fn get_by_id(&self, id: &str) -> Option<&RegionInfo> {
self.regions.iter().find(|r| r.annotation.has_id(id))
}
pub fn find_by_type(&self, widget_type: &WidgetType) -> Vec<&RegionInfo> {
self.regions
.iter()
.filter(|r| r.annotation.is_type(widget_type))
.collect()
}
pub fn interactive_regions(&self) -> Vec<&RegionInfo> {
self.regions
.iter()
.filter(|r| r.annotation.is_interactive())
.collect()
}
pub fn focused_region(&self) -> Option<&RegionInfo> {
self.regions.iter().find(|r| r.annotation.focused)
}
pub fn root_regions(&self) -> Vec<&RegionInfo> {
self.regions.iter().filter(|r| r.depth == 0).collect()
}
pub fn children_of(&self, index: usize) -> Vec<&RegionInfo> {
if let Some(region) = self.regions.get(index) {
region
.children
.iter()
.filter_map(|&i| self.regions.get(i))
.collect()
} else {
Vec::new()
}
}
pub fn format_tree(&self) -> String {
let mut output = String::new();
for region in &self.regions {
if region.parent.is_none() {
self.format_region(&mut output, region, 0);
}
}
output
}
fn format_region(&self, output: &mut String, region: &RegionInfo, indent: usize) {
let prefix = " ".repeat(indent);
output.push_str(&format!(
"{}[{},{}+{}x{}] {}\n",
prefix,
region.area.x,
region.area.y,
region.area.width,
region.area.height,
region.annotation.description()
));
for &child_idx in ®ion.children {
if let Some(child) = self.regions.get(child_idx) {
self.format_region(output, child, indent + 1);
}
}
}
}
#[cfg(test)]
mod tests;