tui_nodes/
connection.rs

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	/*
66	fn invert(self) -> Self {
67		use Direction as D;
68		match self {
69			D::North => D::South,
70			D::South => D::North,
71			D::East => D::West,
72			D::West => D::East,
73		}
74	}
75	fn rotate(self) -> Self {
76		use Direction as D;
77		match self {
78			D::North => D::East,
79			D::East => D::South,
80			D::South => D::West,
81			D::West => D::North,
82		}
83	}
84	*/
85}
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
145/// Generate the correct connection symbol for this node
146pub 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) => ("╡", "╞"), // fallback
161		(BorderType::Thick, LineType::Plain | LineType::Rounded) => ("┨", "┠"),
162
163		(BorderType::Double, LineType::Thick) => ("╢", "╟"), // fallback
164		(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)>, // (x,y)
202	connections: Vec<(Connection, usize)>,            // ((from, to), class)
203	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			//println!("drawing connection {start:?} to {goal:?}");
285			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					//println!("{current_cost}");
302					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				/*
315				print!("\x1b[2J\x1b[1;1H");
316				println!("{frontier:?}");
317				let mut prio = Betweens::new(self.width, self.height);
318				for ea_front in frontier.iter() {
319					prio[ea_front.1.into()] = ea_front.0;
320				}
321				println!("prio\n");
322				prio.print_with(4, |ea| print!("{:>4} ", ea.0));
323				prio.print_with(4, |ea| print!("{:>4} ", ea.1));
324				println!("cost\n");
325				cost.print_with(4, |ea| print!("{:>4} ", ea));
326				println!("from\n");
327				came_from.print_with(1, |ea| {
328					if let Some(inner) = ea {
329						print!("{:?} ", inner.1);
330					}
331					else {
332						print!("_ ");
333					}
334				});
335				std::io::stdin().read_line(&mut String::new()).unwrap();
336				*/
337			}
338			// first pass: mark connections that didnt reach the goal
339			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					// register alias character
348					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			// second pass: draw edges
368			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					// intersections should just be verticals
447					(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		// TODO: fix
483		if !(in_dir == Edge::Empty || in_dir == conn_t) {
484			return isize::MAX;
485		}
486		//	assert!(in_dir == 0 || in_dir == conn_t); // should only calculate cost if its possible
487		let out_dir = self.edge_field[neigh.into()];
488		if out_dir == conn_t {
489			// already exists
490			1
491		} else if out_dir == Edge::Empty {
492			if north == conn_t || south == conn_t || east == conn_t || west == conn_t {
493				// intersecting with an existing connection
494				2 // maybe multiply with distances?
495			} 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					// curved
510					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}
563/*
564impl EdgeIdx {
565	fn pos(self) -> (usize, usize) {
566		(self.0, self.1)
567	}
568}
569*/
570impl 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// the outermost values are unnecessary
590#[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}