use super::types::{DiagramColors, DiagramEdge, DiagramNode, DiagramType, Direction};
use crate::widget::traits::WidgetProps;
use std::collections::HashMap;
pub struct Diagram {
pub title: String,
pub diagram_type: DiagramType,
pub nodes: Vec<DiagramNode>,
pub edges: Vec<DiagramEdge>,
pub colors: DiagramColors,
pub direction: Direction,
pub positions: HashMap<String, (u16, u16)>,
pub sizes: HashMap<String, (u16, u16)>,
pub props: WidgetProps,
}
impl Default for Diagram {
fn default() -> Self {
Self::new()
}
}
impl Diagram {
pub fn new() -> Self {
Self {
title: String::new(),
diagram_type: DiagramType::default(),
nodes: Vec::new(),
edges: Vec::new(),
colors: DiagramColors::default(),
direction: Direction::default(),
positions: HashMap::new(),
sizes: HashMap::new(),
props: WidgetProps::new(),
}
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = title.into();
self
}
pub fn diagram_type(mut self, dt: DiagramType) -> Self {
self.diagram_type = dt;
self
}
pub fn direction(mut self, dir: Direction) -> Self {
self.direction = dir;
self
}
pub fn colors(mut self, colors: DiagramColors) -> Self {
self.colors = colors;
self
}
pub fn node(mut self, node: DiagramNode) -> Self {
self.nodes.push(node);
self
}
pub fn edge(mut self, edge: DiagramEdge) -> Self {
self.edges.push(edge);
self
}
pub fn parse(mut self, source: &str) -> Self {
for line in source.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with("%%") {
continue;
}
if let Some((left, right)) = line.split_once("-->") {
let from = left.trim();
let (label, to) = if right.contains('|') {
let parts: Vec<&str> = right.split('|').collect();
if parts.len() >= 3 {
(Some(parts[1].to_string()), parts[2].trim())
} else {
(None, right.trim())
}
} else {
(None, right.trim())
};
let (from_id, from_label) = Self::parse_node_def(from);
let (to_id, to_label) = Self::parse_node_def(to);
if !self.nodes.iter().any(|n| n.id == from_id) {
self.nodes.push(DiagramNode::new(
&from_id,
from_label.unwrap_or_else(|| from_id.clone()),
));
}
if !self.nodes.iter().any(|n| n.id == to_id) {
self.nodes.push(DiagramNode::new(
&to_id,
to_label.unwrap_or_else(|| to_id.clone()),
));
}
let mut edge = DiagramEdge::new(from_id, to_id);
if let Some(l) = label {
edge = edge.label(l);
}
self.edges.push(edge);
}
}
self
}
#[doc(hidden)]
pub fn get_nodes(&self) -> &Vec<DiagramNode> {
&self.nodes
}
#[doc(hidden)]
pub fn get_edges(&self) -> &Vec<DiagramEdge> {
&self.edges
}
fn parse_node_def(s: &str) -> (String, Option<String>) {
let s = s.trim();
if let Some(bracket_start) = s.find('[') {
if let Some(bracket_end) = s.find(']') {
let id = s[..bracket_start].trim().to_string();
let label = s[bracket_start + 1..bracket_end].to_string();
return (id, Some(label));
}
}
if let Some(brace_start) = s.find('{') {
if let Some(brace_end) = s.find('}') {
let id = s[..brace_start].trim().to_string();
let label = s[brace_start + 1..brace_end].to_string();
return (id, Some(label));
}
}
if let Some(paren_start) = s.find('(') {
if let Some(paren_end) = s.rfind(')') {
let id = s[..paren_start].trim().to_string();
let label = s[paren_start + 1..paren_end].to_string();
return (id, Some(label));
}
}
(s.to_string(), None)
}
pub fn compute_layout(&mut self, width: u16, height: u16) {
self.positions.clear();
self.sizes.clear();
if self.nodes.is_empty() {
return;
}
let rows = ((self.nodes.len() as f32).sqrt().ceil() as u16).max(1);
let cols = (self.nodes.len() as u16).div_ceil(rows).max(1);
let cell_width = width / cols;
let cell_height = height / rows;
for (i, node) in self.nodes.iter().enumerate() {
let row = i as u16 / cols;
let col = i as u16 % cols;
let node_width = (node.label.chars().count() as u16 + 4).min(cell_width - 2);
let node_height = 3u16;
let x = col * cell_width + (cell_width - node_width) / 2;
let y = row * cell_height + (cell_height - node_height) / 2;
self.positions.insert(node.id.clone(), (x, y));
self.sizes
.insert(node.id.clone(), (node_width, node_height));
}
}
}