use super::*;
const MARGIN: u16 = 5;
#[derive(Debug)]
pub struct NodeGraph<'a> {
nodes: Vec<NodeLayout<'a>>,
connections: Vec<Connection>,
placements: Map<usize, Rect>,
pub conn_layout: ConnectionsLayout,
width: usize,
}
impl<'a> NodeGraph<'a> {
pub fn new(
nodes: Vec<NodeLayout<'a>>,
connections: Vec<Connection>,
width: usize,
height: usize,
) -> Self {
Self {
nodes,
connections,
conn_layout: ConnectionsLayout::new(width, height),
placements: Default::default(),
width,
}
}
pub fn calculate(&mut self) {
self.placements.clear();
let mut roots: Set<_> = (0..self.nodes.len()).collect();
for ea_connection in self.connections.iter() {
roots.remove(&ea_connection.from_node);
}
let mut main_chain = Vec::new();
for ea_root in roots {
self.place_node(ea_root, 0, 0, &mut main_chain);
assert!(main_chain.is_empty());
}
let mut conn_map = Map::<(usize, usize), usize>::new();
let mut next_idx = 1;
for ea_conn in self.connections.iter() {
let a_pos = self.placements[&ea_conn.from_node];
let b_pos = self.placements[&ea_conn.to_node];
let a_point = (self.width.saturating_sub(a_pos.left().into()), a_pos.top() as usize + ea_conn.from_port + 1);
let b_point = (self.width.saturating_sub(b_pos.right() as usize + 1), b_pos.top() as usize + ea_conn.to_port + 1);
self.conn_layout.insert_port(false, ea_conn.from_node, ea_conn.from_port, a_point);
self.conn_layout.insert_port(true, ea_conn.to_node, ea_conn.to_port, b_point);
let key = (ea_conn.from_node, ea_conn.from_port);
if !conn_map.contains_key(&key) {
conn_map.insert(key, next_idx);
next_idx += 1;
}
self.conn_layout.push_connection((*ea_conn, conn_map[&key]));
self.conn_layout.block_port(a_point);
self.conn_layout.block_port(b_point);
}
for mut ea_placement in self.placements.values().cloned() {
ea_placement.x = (self.width as u16).saturating_sub(ea_placement.x + ea_placement.width);
self.conn_layout.block_zone(ea_placement);
}
self.conn_layout.calculate();
}
fn place_node(&mut self, idx_node: usize, x: u16, y: u16, main_chain: &mut Vec<usize>) {
let size_me = self.nodes[idx_node].size;
let mut rect_me = Rect { x, y, width: size_me.0, height: size_me.1 };
'outer: loop {
for (_, ea_them) in self.placements.iter() {
if rect_me.intersects(*ea_them) {
rect_me.y = rect_me.y.max(ea_them.bottom());
continue 'outer
}
}
break
}
for ea_node in main_chain.iter() {
let y = self.placements[ea_node].y.max(rect_me.y);
self.placements.get_mut(ea_node).unwrap().y = y;
}
self.placements.insert(idx_node, rect_me);
let mut y = y;
main_chain.push(idx_node);
for ea_child in get_upstream(&self.connections, idx_node) {
if self.placements.contains_key(&ea_child.from_node) {
self.nudge(ea_child.from_node, rect_me.x + rect_me.width + MARGIN);
}
else {
self.place_node(ea_child.from_node, x + rect_me.width + MARGIN, y, main_chain);
main_chain.clear();
y += self.placements[&ea_child.from_node].height;
}
}
main_chain.pop();
}
fn nudge(&mut self, idx_node: usize, x: u16) {
let rect_me = self.placements[&idx_node];
if rect_me.x < x {
self.placements.get_mut(&idx_node).unwrap().x = x;
for ea_child in get_upstream(&self.connections, idx_node) {
assert!(self.placements.contains_key(&ea_child.from_node));
self.nudge(ea_child.from_node, x + rect_me.width + MARGIN);
}
}
}
pub fn split(&self, area: Rect) -> Vec<Rect> {
(0..self.nodes.len()).map(|idx_node| {
self.placements.get(&idx_node).map(|pos| {
if pos.right() > area.width || pos.bottom() > area.height {
return Rect { x: 0, y: 0, width: 0, height: 0, }
}
let mut pos = *pos;
pos.x = area.width - pos.right();
pos.inner(&Margin { horizontal: 1, vertical: 1 })
})
.unwrap_or_default()
}).collect()
}
}
fn get_upstream(conns: &Vec<Connection>, idx_node: usize) -> Vec<Connection> {
let mut upstream: Vec<_> = conns.iter()
.filter(|ea| { ea.to_node == idx_node })
.copied()
.collect();
upstream.sort_by(|a,b| a.to_port.cmp(&b.to_port));
upstream
}
fn get_downstream(conns: &Vec<Connection>, idx_node: usize) -> Vec<Connection> {
let mut downstream: Vec<_> = conns.iter()
.filter(|ea| { ea.from_node == idx_node })
.copied()
.collect()
;
downstream.sort_by(|a,b| a.from_port.cmp(&b.from_port));
downstream
}
impl<'a> ratatui::widgets::StatefulWidget for NodeGraph<'a> {
type State = ();
fn render(self, area: Rect, buf: &mut Buffer, _state: &mut Self::State) {
self.conn_layout.render(area, buf);
'node: for (idx_node, ea_node) in self.nodes.into_iter().enumerate() {
if let Some(mut pos) = self.placements.get(&idx_node).copied() {
if pos.right() > area.width || pos.bottom() > area.height { continue 'node }
pos.x = area.left() + area.width - pos.right();
pos.y += area.top();
let block = ea_node.block();
block.render(pos, buf);
for ea_conn in get_upstream(&self.connections, idx_node) {
if let Some(alias_char) = self.conn_layout.alias_connections.get(&(true, idx_node, ea_conn.to_port)) {
let y = pos.top() + ea_conn.to_port as u16 + 1;
if pos.left() > 0 && y < area.width {
buf.get_mut(pos.left() - 1, y)
.set_symbol(alias_char)
.set_style(Style::default().add_modifier(Modifier::BOLD).bg(Color::Red))
;
}
}
buf.get_mut(pos.left(), pos.top() + ea_conn.to_port as u16 + 1)
.set_symbol(conn_symbol(true, ea_node.border_type(), ea_conn.border_type()))
;
}
for ea_conn in get_downstream(&self.connections, idx_node) {
if let Some(alias_char) = self.conn_layout.alias_connections.get(&(false, idx_node, ea_conn.from_port)) {
buf.get_mut(pos.right(), pos.top() + ea_conn.from_port as u16 + 1)
.set_symbol(alias_char)
.set_style(Style::default().add_modifier(Modifier::BOLD).bg(Color::Red))
;
}
buf.get_mut(pos.right() - 1, pos.top() + ea_conn.from_port as u16 + 1)
.set_symbol(conn_symbol(false, ea_node.border_type(), ea_conn.border_type()))
;
}
}
else {
buf.set_string(0, idx_node as u16, format!("{idx_node}"), Style::default());
}
}
}
}