1use ratatui::{
2 buffer::Buffer,
3 layout::{Position, Rect},
4 style::Style,
5 symbols::line,
6 widgets::BorderType,
7};
8use std::collections::{BTreeMap as Map, BinaryHeap};
9
10const SEARCH_TIMEOUT: usize = 5000;
11
12#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum LineType {
14 #[default]
15 Plain,
16 Rounded,
17 Double,
18 Thick,
19}
20
21impl LineType {
22 fn to_line_set(&self) -> line::Set {
23 match self {
24 LineType::Plain => line::NORMAL,
25 LineType::Rounded => line::ROUNDED,
26 LineType::Double => line::DOUBLE,
27 LineType::Thick => line::THICK,
28 }
29 }
30}
31
32impl From<BorderType> for LineType {
33 fn from(value: BorderType) -> Self {
34 match value {
35 BorderType::Plain => LineType::Plain,
36 BorderType::Rounded => LineType::Rounded,
37 BorderType::Double => LineType::Double,
38 BorderType::Thick => LineType::Thick,
39 BorderType::LightDoubleDashed
40 | BorderType::LightTripleDashed
41 | BorderType::LightQuadrupleDashed
42 => LineType::Plain,
43 BorderType::HeavyDoubleDashed
44 | BorderType::HeavyTripleDashed
45 | BorderType::HeavyQuadrupleDashed
46 => LineType::Thick,
47 BorderType::QuadrantInside | BorderType::QuadrantOutside
48 => unimplemented!(),
49 }
50 }
51}
52
53#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
54pub enum Direction {
55 North = 0,
56 South = 1,
57 East = 2,
58 West = 3,
59}
60
61impl Direction {
62 fn is_vertical(&self) -> bool {
63 (*self as usize) < 2
64 }
65 }
86
87impl std::fmt::Debug for Direction {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 let print = match self {
90 Direction::North => '↑',
91 Direction::South => '↓',
92 Direction::East => '→',
93 Direction::West => '←',
94 };
95 write!(f, "{}", print)
96 }
97}
98
99#[derive(Debug, Clone, Copy)]
100pub struct Connection {
101 pub from_node: usize,
102 pub from_port: usize,
103 pub to_node: usize,
104 pub to_port: usize,
105 line_type: LineType,
106 line_style: Style,
107}
108
109impl Connection {
110 pub fn new(
111 from_node: usize,
112 from_port: usize,
113 to_node: usize,
114 to_port: usize,
115 ) -> Self {
116 Self {
117 from_node,
118 from_port,
119 to_node,
120 to_port,
121 line_type: LineType::Rounded,
122 line_style: Style::default(),
123 }
124 }
125
126 pub fn with_line_type(mut self, line_type: LineType) -> Self {
127 self.line_type = line_type;
128 self
129 }
130
131 pub fn line_type(&self) -> LineType {
132 self.line_type
133 }
134
135 pub fn with_line_style(mut self, line_style: Style) -> Self {
136 self.line_style = line_style;
137 self
138 }
139
140 pub fn line_style(&self) -> Style {
141 self.line_style
142 }
143}
144
145pub fn conn_symbol(
147 is_input: bool,
148 block_style: BorderType,
149 conn_style: LineType,
150) -> &'static str {
151 let out = match (block_style, conn_style) {
152 (BorderType::Plain | BorderType::Rounded, LineType::Thick) => ("┥", "┝"),
153 (BorderType::Plain | BorderType::Rounded, LineType::Double) => ("╡", "╞"),
154 (
155 BorderType::Plain | BorderType::Rounded,
156 LineType::Plain | LineType::Rounded,
157 ) => ("┤", "├"),
158
159 (BorderType::Thick, LineType::Thick) => ("┫", "┣"),
160 (BorderType::Thick, LineType::Double) => ("╡", "╞"), (BorderType::Thick, LineType::Plain | LineType::Rounded) => ("┨", "┠"),
162
163 (BorderType::Double, LineType::Thick) => ("╢", "╟"), (BorderType::Double, LineType::Double) => ("╣", "╠"),
165 (BorderType::Double, LineType::Plain | LineType::Rounded) => ("╢", "╟"),
166
167 (
168 BorderType::LightDoubleDashed | BorderType::LightTripleDashed | BorderType::LightQuadrupleDashed,
169 _,
170 ) => ("u", "u"),
171 (
172 BorderType::HeavyDoubleDashed | BorderType::HeavyTripleDashed | BorderType::HeavyQuadrupleDashed,
173 _,
174 ) => ("u", "u"),
175 (BorderType::QuadrantInside | BorderType::QuadrantOutside, _) => ("u", "u"),
176 };
177 if is_input {
178 out.0
179 } else {
180 out.1
181 }
182}
183
184pub const ALIAS_CHARS: [&str; 24] = [
185 "α", "β", "γ", "δ", "ε", "ζ", "η", "θ", "ι", "κ", "λ", "μ", "ν", "ξ", "ο", "π", "ρ",
186 "σ", "τ", "υ", "φ", "χ", "ψ", "ω",
187];
188
189#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
190pub enum Edge {
191 #[default]
192 Empty,
193 Blocked,
194 Connection(usize),
195}
196const E: Edge = Edge::Empty;
197const B: Edge = Edge::Blocked;
198
199#[derive(Debug)]
200pub struct ConnectionsLayout {
201 ports: Map<(bool, usize, usize), (usize, usize)>, connections: Vec<(Connection, usize)>, edge_field: Betweens<Edge>,
204 width: usize,
205 height: usize,
206 pub alias_connections: Map<(bool, usize, usize), &'static str>,
207 line_types: Map<usize, LineType>,
208 line_styles: Map<usize, Style>,
209}
210
211impl ConnectionsLayout {
212 pub fn new(width: usize, height: usize) -> Self {
213 Self {
214 ports: Map::new(),
215 connections: Vec::new(),
216 edge_field: Betweens::new(width, height),
217 width,
218 height,
219 alias_connections: Map::new(),
220 line_types: Map::new(),
221 line_styles: Map::new(),
222 }
223 }
224
225 pub fn push_connection(&mut self, connection: (Connection, usize)) {
226 self.connections.push(connection)
227 }
228
229 pub fn insert_port(
230 &mut self,
231 is_input: bool,
232 node: usize,
233 port: usize,
234 pos: (usize, usize),
235 ) {
236 self.ports.insert((is_input, node, port), pos);
237 }
238
239 pub fn block_zone(&mut self, area: Rect) {
240 for x in 0..area.width {
241 for y in 0..area.height {
242 if x != area.width - 1 {
243 self.edge_field[(
244 ((x + area.x) as usize, (y + area.y) as usize),
245 Direction::East,
246 )
247 .into()] = Edge::Blocked;
248 }
249 if y != area.height - 1 {
250 self.edge_field[(
251 ((x + area.x) as usize, (y + area.y) as usize),
252 Direction::South,
253 )
254 .into()] = Edge::Blocked;
255 }
256 }
257 }
258 }
259
260 pub fn block_port(&mut self, coord: (usize, usize)) {
261 self.edge_field[(coord, Direction::North).into()] = Edge::Blocked;
262 self.edge_field[(coord, Direction::South).into()] = Edge::Blocked;
263 }
264
265 pub fn calculate(&mut self) {
266 let mut idx_next_alias = 0;
267 'outer: for ea_conn in &self.connections {
268 self.line_types.insert(ea_conn.1, ea_conn.0.line_type());
269 self.line_styles.insert(ea_conn.1, ea_conn.0.line_style());
270 let start = (
271 self.ports[&(false, ea_conn.0.from_node, ea_conn.0.from_port)],
272 Direction::West,
273 );
274 let goal = (
275 self.ports[&(true, ea_conn.0.to_node, ea_conn.0.to_port)],
276 Direction::East,
277 );
278 if start.0 .0 > self.edge_field.width || start.0 .1 > self.edge_field.height {
279 continue;
280 }
281 if goal.0 .0 > self.edge_field.width || goal.0 .1 > self.edge_field.height {
282 continue;
283 }
284 let mut frontier = BinaryHeap::new();
286 let mut came_from = Betweens::<Option<_>>::new(self.width, self.height);
287 let mut cost = Betweens::<isize>::new(self.width, self.height);
288 frontier.push(((0, 0), start));
289 let mut count = 0;
290 while let Some((_, current)) = frontier.pop() {
291 count += 1;
292 if count > SEARCH_TIMEOUT {
293 break;
294 }
295 if current == goal {
296 break;
297 }
298 for ea_nei in neighbors(current.0, self.width, self.height) {
299 let ea_edge = ea_nei.into();
300 let current_cost = cost[current.into()];
301 let new_cost = current_cost.saturating_add(
303 self.calc_cost(current, ea_nei, start.0, goal.0, ea_conn.1),
304 );
305 if came_from[ea_edge].is_none() || new_cost < cost[ea_edge] {
306 let prio = (-new_cost, -Self::heuristic(ea_nei.0, goal.0));
307 if new_cost != isize::MAX {
308 frontier.push((prio, ea_nei));
309 }
310 came_from[ea_edge] = Some(current);
311 cost[ea_edge] = new_cost;
312 }
313 }
314 }
338 let mut next = goal;
340 loop {
341 if next == start {
342 break;
343 }
344 if let Some(from) = came_from[next.into()] {
345 next = from;
346 } else {
347 if !self.alias_connections.contains_key(&(
349 false,
350 ea_conn.0.from_node,
351 ea_conn.0.from_port,
352 )) {
353 self.alias_connections.insert(
354 (false, ea_conn.0.from_node, ea_conn.0.from_port),
355 ALIAS_CHARS[idx_next_alias],
356 );
357 idx_next_alias += 1;
358 }
359 let alias = self.alias_connections
360 [&(false, ea_conn.0.from_node, ea_conn.0.from_port)];
361 self.alias_connections
362 .insert((true, ea_conn.0.to_node, ea_conn.0.to_port), alias);
363 continue 'outer;
364 }
365 }
366
367 let mut next = goal;
369 loop {
370 if next == start {
371 break;
372 }
373 self.edge_field[next.into()] = Edge::Connection(ea_conn.1);
374 next = came_from[next.into()].unwrap();
375 }
376 }
377 }
378
379 pub fn render(&self, area: Rect, buf: &mut Buffer) {
380 let bor = |idx: Edge| -> line::Set {
381 if let Edge::Connection(idx) = idx {
382 self.line_types[&idx].to_line_set()
383 } else if idx == Edge::Blocked {
384 line::THICK
385 } else {
386 line::Set {
387 vertical: " ",
388 horizontal: " ",
389 top_right: " ",
390 top_left: " ",
391 bottom_right: " ",
392 bottom_left: " ",
393 vertical_left: " ",
394 vertical_right: " ",
395 horizontal_down: " ",
396 horizontal_up: " ",
397 cross: " ",
398 }
399 }
400 };
401
402 let get_line_style = |idx: Edge| -> Style {
403 if let Edge::Connection(idx) = idx {
404 self.line_styles[&idx]
405 } else {
406 Style::default()
407 }
408 };
409 for y in 0..self.height {
410 for x in 0..self.width {
411 let pos = (x, y);
412 let north = self.edge_field[(pos, Direction::North).into()];
413 let south = self.edge_field[(pos, Direction::South).into()];
414 let east = self.edge_field[(pos, Direction::East).into()];
415 let west = self.edge_field[(pos, Direction::West).into()];
416 #[rustfmt::skip]
417 let (symbol, line_style) = match (north, south, east, west) {
418 (B | E, B | E, B | E, B | E) => continue,
419 (n, s, e, w) if n == B || s == B || e == B || w == B => {
420 if n == B && s == B && e != E || w != E && e == w {
421 (bor(e).horizontal, get_line_style(e))
422 } else if e == B && w == B && n != E && s != E && n == s {
423 (bor(n).vertical, get_line_style(n))
424 } else {
425 ("*", Style::default())
426 }
427 }
428 (n, E, E, E) => (bor(n).vertical, get_line_style(n)),
429 (E, s, E, E) => (bor(s).vertical, get_line_style(s)),
430 (E, E, e, E) => (bor(e).horizontal, get_line_style(e)),
431 (E, E, E, w) => (bor(w).horizontal, get_line_style(w)),
432
433 (n, s, E, w) if n == s && n == w => (bor(n).vertical_left, get_line_style(n)),
434 (n, E, e, w) if n == e && n == w => (bor(n).horizontal_up, get_line_style(n)),
435 (n, s, e, E) if n == s && n == e => (bor(n).vertical_right, get_line_style(n)),
436 (E, s, e, w) if s == e && s == w => (bor(s).horizontal_down, get_line_style(s)),
437 (E, s, E, w) if s == w => (bor(s).top_right, get_line_style(s)),
438 (n, E, E, w) if n == w => (bor(n).bottom_right, get_line_style(n)),
439 (n, E, e, E) if n == e => (bor(n).bottom_left, get_line_style(n)),
440 (E, s, e, E) if s == e => (bor(s).top_left, get_line_style(s)),
441
442 (n, s, E, E) if n == s => (bor(n).vertical, get_line_style(n)),
443 (E, E, e, w) if e == w => (bor(e).horizontal, get_line_style(e)),
444
445 (n, s, e, w) if n == s && n == e && n == w => (bor(n).cross, get_line_style(n)),
446 (n, s, e, w) if n == s && e == w && n != E && e != E => (bor(n).vertical, get_line_style(n)),
448 (_, _, _, _) => ("?", Style::default()),
449 };
450
451 buf.cell_mut(Position::new(
452 x as u16 + area.left(),
453 y as u16 + area.top(),
454 ))
455 .unwrap()
456 .set_symbol(symbol)
457 .set_style(line_style);
458 }
459 }
460 }
461
462 fn heuristic(from: (usize, usize), to: (usize, usize)) -> isize {
463 (from.0 as isize - to.0 as isize).pow(2)
464 + (from.1 as isize - to.1 as isize).pow(2)
465 }
466
467 fn calc_cost(
468 &self,
469 current: ((usize, usize), Direction),
470 neigh: ((usize, usize), Direction),
471 start: (usize, usize),
472 end: (usize, usize),
473 conn_t: usize,
474 ) -> isize {
475 let conn_t = Edge::Connection(conn_t);
476 let north = self.edge_field[(current.0, Direction::North).into()];
477 let south = self.edge_field[(current.0, Direction::South).into()];
478 let east = self.edge_field[(current.0, Direction::East).into()];
479 let west = self.edge_field[(current.0, Direction::West).into()];
480
481 let in_dir = self.edge_field[current.into()];
482 if !(in_dir == Edge::Empty || in_dir == conn_t) {
484 return isize::MAX;
485 }
486 let out_dir = self.edge_field[neigh.into()];
488 if out_dir == conn_t {
489 1
491 } else if out_dir == Edge::Empty {
492 if north == conn_t || south == conn_t || east == conn_t || west == conn_t {
493 2 } else {
496 let in_is_vert = current.1.is_vertical();
497 let out_is_vert = neigh.1.is_vertical();
498 let straight = in_is_vert == out_is_vert;
499 if straight {
500 if north == Edge::Empty
501 && south == Edge::Empty && east == Edge::Empty
502 && west == Edge::Empty
503 {
504 2
505 } else {
506 4
507 }
508 } else {
509 if north != Edge::Empty
511 || south != Edge::Empty || east != Edge::Empty
512 || west != Edge::Empty
513 {
514 isize::MAX
515 } else {
516 let ax = current.0 .0 as isize;
517 let ay = current.0 .1 as isize;
518 let sx = start.0 as isize;
519 let sy = start.1 as isize;
520 let ex = end.0 as isize;
521 let ey = end.1 as isize;
522 4 + ((ax - sx).pow(2)
523 + (ay - sy).pow(2) + (ax - ex).pow(2)
524 + (ay - ey).pow(2))
525 }
526 }
527 }
528 } else {
529 isize::MAX
530 }
531 }
532}
533
534fn neighbors(
535 pos: (usize, usize),
536 width: usize,
537 height: usize,
538) -> Vec<((usize, usize), Direction)> {
539 let mut out = Vec::new();
540 if pos.0 < width - 1 {
541 out.push(((pos.0 + 1, pos.1), Direction::West));
542 }
543 if pos.1 < height - 1 {
544 out.push(((pos.0, pos.1 + 1), Direction::North));
545 }
546 if pos.0 > 0 {
547 out.push(((pos.0 - 1, pos.1), Direction::East));
548 }
549 if pos.1 > 0 {
550 out.push(((pos.0, pos.1 - 1), Direction::South));
551 }
552 out
553}
554
555use core::ops::{Index, IndexMut};
556
557#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
558struct EdgeIdx {
559 x: usize,
560 y: usize,
561 is_vertical: bool,
562}
563impl From<((usize, usize), Direction)> for EdgeIdx {
571 fn from(value: ((usize, usize), Direction)) -> Self {
572 match value.1 {
573 Direction::North => Self { x: value.0 .0, y: value.0 .1, is_vertical: true },
574 Direction::South => Self {
575 x: value.0 .0,
576 y: value.0 .1 + 1,
577 is_vertical: true,
578 },
579 Direction::East => Self {
580 x: value.0 .0 + 1,
581 y: value.0 .1,
582 is_vertical: false,
583 },
584 Direction::West => Self { x: value.0 .0, y: value.0 .1, is_vertical: false },
585 }
586 }
587}
588
589#[derive(Debug)]
591struct Betweens<T: Default> {
592 horizontal: Vec<Vec<T>>,
593 vertical: Vec<Vec<T>>,
594 width: usize,
595 height: usize,
596}
597impl<T: Default> Index<EdgeIdx> for Betweens<T> {
598 type Output = T;
599 fn index(&self, index: EdgeIdx) -> &Self::Output {
600 if index.is_vertical {
601 &self.vertical[index.y][index.x]
602 } else {
603 &self.horizontal[index.y][index.x]
604 }
605 }
606}
607impl<T: Default> IndexMut<EdgeIdx> for Betweens<T> {
608 fn index_mut(&mut self, index: EdgeIdx) -> &mut T {
609 if index.is_vertical {
610 &mut self.vertical[index.y][index.x]
611 } else {
612 &mut self.horizontal[index.y][index.x]
613 }
614 }
615}
616
617impl<T: Default> Betweens<T> {
618 fn new(x: usize, y: usize) -> Self {
619 let mut out = Self {
620 horizontal: Vec::new(),
621 vertical: Vec::new(),
622 width: 0,
623 height: 0,
624 };
625 out.set_size(x, y);
626 out
627 }
628
629 fn set_size(&mut self, x: usize, y: usize) {
630 self.horizontal.resize_with(y, || {
631 let mut inner = Vec::new();
632 inner.resize_with(x + 1, Default::default);
633 inner
634 });
635 self.vertical.resize_with(y + 1, || {
636 let mut inner = Vec::new();
637 inner.resize_with(x, Default::default);
638 inner
639 });
640 self.width = x;
641 self.height = y;
642 }
643
644 #[allow(unused)]
645 fn print_with(&self, width: usize, f: impl Fn(&T)) {
646 for y in 0..(self.height + 1) {
647 for x in 0..self.width {
648 print!("{} ", "-".repeat(width));
649 f(&self.vertical[y][x]);
650 }
651 println!("{}", "-".repeat(width));
652 if y < self.height {
653 for x in 0..(self.width + 1) {
654 f(&self.horizontal[y][x]);
655 if x < self.width {
656 print!("{} ", "-".repeat(width));
657 }
658 }
659 }
660 println!();
661 }
662 }
663}