1use ratatui::layout::Position;
2
3use super::*;
4
5const MARGIN: u16 = 5;
6
7#[derive(Debug)]
8pub struct NodeGraph<'a> {
9 nodes: Vec<NodeLayout<'a>>,
10 connections: Vec<Connection>,
11 placements: Map<usize, Rect>,
12 pub conn_layout: ConnectionsLayout,
13 width: usize,
14}
15
16impl<'a> NodeGraph<'a> {
17 pub fn new(
18 nodes: Vec<NodeLayout<'a>>,
19 connections: Vec<Connection>,
20 width: usize,
21 height: usize,
22 ) -> Self {
23 Self {
24 nodes,
25 connections,
26 conn_layout: ConnectionsLayout::new(width, height),
27 placements: Default::default(),
28 width,
29 }
30 }
31
32 pub fn calculate(&mut self) {
33 self.placements.clear();
34
35 let mut roots: Set<_> = (0..self.nodes.len()).collect();
37 for ea_connection in self.connections.iter() {
38 roots.remove(&ea_connection.from_node);
39 }
40
41 let mut main_chain = Vec::new();
43 for ea_root in roots {
44 self.place_node(ea_root, 0, 0, &mut main_chain);
45 assert!(main_chain.is_empty());
46 }
47
48 let mut conn_map = Map::<(usize, usize), usize>::new();
53 let mut next_idx = 1;
54 for ea_conn in self.connections.iter() {
55 let a_pos = self.placements[&ea_conn.from_node];
56 let b_pos = self.placements[&ea_conn.to_node];
57 let a_point = (
59 self.width.saturating_sub(a_pos.left().into()),
60 a_pos.top() as usize + ea_conn.from_port + 1,
61 );
62 let b_point = (
63 self.width.saturating_sub(b_pos.right() as usize + 1),
64 b_pos.top() as usize + ea_conn.to_port + 1,
65 );
66 self.conn_layout.insert_port(
67 false,
68 ea_conn.from_node,
69 ea_conn.from_port,
70 a_point,
71 );
72 self.conn_layout.insert_port(true, ea_conn.to_node, ea_conn.to_port, b_point);
73 let key = (ea_conn.from_node, ea_conn.from_port);
74 if !conn_map.contains_key(&key) {
75 conn_map.insert(key, next_idx);
76 next_idx += 1;
77 }
78 self.conn_layout.push_connection((*ea_conn, conn_map[&key]));
79 self.conn_layout.block_port(a_point);
80 self.conn_layout.block_port(b_point);
81 }
82 for mut ea_placement in self.placements.values().cloned() {
83 ea_placement.x =
84 (self.width as u16).saturating_sub(ea_placement.x + ea_placement.width);
85 self.conn_layout.block_zone(ea_placement);
86 }
87 self.conn_layout.calculate();
88 }
89
90 fn place_node(
94 &mut self,
95 idx_node: usize,
96 x: u16,
97 y: u16,
98 main_chain: &mut Vec<usize>,
99 ) {
100 let size_me = self.nodes[idx_node].size;
102 let mut rect_me = Rect { x, y, width: size_me.0, height: size_me.1 };
103
104 'outer: loop {
112 for (_, ea_them) in self.placements.iter() {
113 if rect_me.intersects(*ea_them) {
114 rect_me.y = rect_me.y.max(ea_them.bottom());
115 continue 'outer;
116 }
117 }
118 break;
119 }
120 for ea_node in main_chain.iter() {
121 let y = self.placements[ea_node].y.max(rect_me.y);
122 self.placements.get_mut(ea_node).unwrap().y = y;
123 }
124 self.placements.insert(idx_node, rect_me);
125
126 let mut y = y;
128 main_chain.push(idx_node);
129 for ea_child in get_upstream(&self.connections, idx_node) {
130 if self.placements.contains_key(&ea_child.from_node) {
131 self.nudge(ea_child.from_node, rect_me.x + rect_me.width + MARGIN);
133 } else {
134 self.place_node(
136 ea_child.from_node,
137 x + rect_me.width + MARGIN,
138 y,
139 main_chain,
140 );
141 main_chain.clear();
142 y += self.placements[&ea_child.from_node].height;
143 }
144 }
145 main_chain.pop();
146 }
147
148 fn nudge(&mut self, idx_node: usize, x: u16) {
149 let rect_me = self.placements[&idx_node];
150 if rect_me.x < x {
151 self.placements.get_mut(&idx_node).unwrap().x = x;
152 for ea_child in get_upstream(&self.connections, idx_node) {
153 assert!(self.placements.contains_key(&ea_child.from_node));
154 self.nudge(ea_child.from_node, x + rect_me.width + MARGIN);
155 }
156 }
157 }
158
159 pub fn split(&self, area: Rect) -> Vec<Rect> {
160 (0..self.nodes.len())
161 .map(|idx_node| {
162 self.placements
163 .get(&idx_node)
164 .map(|pos| {
165 if pos.right() > area.width || pos.bottom() > area.height {
166 return Rect { x: 0, y: 0, width: 0, height: 0 };
167 }
168 let mut pos = *pos;
169 pos.x = area.width - pos.right() + area.x;
170 pos.y += area.y;
171 pos.inner(Margin { horizontal: 1, vertical: 1 })
172 })
173 .unwrap_or_default()
174 })
175 .collect()
176 }
177}
178
179fn get_upstream(conns: &[Connection], idx_node: usize) -> Vec<Connection> {
180 let mut upstream: Vec<_> =
182 conns.iter().filter(|ea| ea.to_node == idx_node).copied().collect();
183 upstream.sort_by(|a, b| a.to_port.cmp(&b.to_port));
184 upstream
185}
186
187fn get_downstream(conns: &[Connection], idx_node: usize) -> Vec<Connection> {
188 let mut downstream: Vec<_> =
190 conns.iter().filter(|ea| ea.from_node == idx_node).copied().collect();
191 downstream.sort_by(|a, b| a.from_port.cmp(&b.from_port));
192 downstream
193}
194
195impl<'a> ratatui::widgets::StatefulWidget for NodeGraph<'a> {
196 type State = ();
199
200 fn render(self, area: Rect, buf: &mut Buffer, _state: &mut Self::State) {
201 self.conn_layout.render(area, buf);
203
204 'node: for (idx_node, ea_node) in self.nodes.into_iter().enumerate() {
206 if let Some(mut pos) = self.placements.get(&idx_node).copied() {
207 if pos.right() > area.width || pos.bottom() > area.height {
208 continue 'node;
209 }
210 pos.x = area.left() + area.width - pos.right();
212 pos.y += area.top();
213 let block = ea_node.block();
214 block.render(pos, buf);
215 for ea_conn in get_upstream(&self.connections, idx_node) {
217 if let Some(alias_char) = self.conn_layout.alias_connections.get(&(
219 true,
220 idx_node,
221 ea_conn.to_port,
222 )) {
223 let y = pos.top() + ea_conn.to_port as u16 + 1;
224 if pos.left() > 0 && y < area.width {
225 buf.cell_mut(Position::new(pos.left() - 1, y))
226 .unwrap()
227 .set_symbol(alias_char)
228 .set_style(
229 Style::default()
230 .add_modifier(Modifier::BOLD)
231 .bg(Color::Red),
232 );
233 }
234 }
235
236 buf.cell_mut(Position::new(
238 pos.left(),
239 pos.top() + ea_conn.to_port as u16 + 1,
240 ))
241 .unwrap()
242 .set_symbol(conn_symbol(
243 true,
244 ea_node.border_type(),
245 ea_conn.line_type(),
246 ));
247 }
248 for ea_conn in get_downstream(&self.connections, idx_node) {
249 if let Some(alias_char) = self.conn_layout.alias_connections.get(&(
251 false,
252 idx_node,
253 ea_conn.from_port,
254 )) {
255 buf.cell_mut(Position::new(
256 pos.right(),
257 pos.top() + ea_conn.from_port as u16 + 1,
258 ))
259 .unwrap()
260 .set_symbol(alias_char)
261 .set_style(
262 Style::default().add_modifier(Modifier::BOLD).bg(Color::Red),
263 );
264 }
265
266 buf.cell_mut(Position::new(
268 pos.right() - 1,
269 pos.top() + ea_conn.from_port as u16 + 1,
270 ))
271 .unwrap()
272 .set_symbol(conn_symbol(
273 false,
274 ea_node.border_type(),
275 ea_conn.line_type(),
276 ));
277 }
278 } else {
279 buf.set_string(
280 0,
281 idx_node as u16,
282 format!("{idx_node}"),
283 Style::default(),
284 );
285 }
286 }
287 }
288}