use ratatui::buffer::Buffer;
use ratatui::style::Color as RatatuiColor;
use super::set_cell;
const UP: u8 = 0b0001;
const DOWN: u8 = 0b0010;
const LEFT: u8 = 0b0100;
const RIGHT: u8 = 0b1000;
fn connections(ch: char) -> u8 {
match ch {
'│' => UP | DOWN,
'─' => LEFT | RIGHT,
'┼' => UP | DOWN | LEFT | RIGHT,
'┬' => DOWN | LEFT | RIGHT,
'┴' => UP | LEFT | RIGHT,
'├' => UP | DOWN | RIGHT,
'┤' => UP | DOWN | LEFT,
_ => 0,
}
}
fn glyph_for(up: bool, down: bool, left: bool, right: bool, vertical: bool) -> char {
match (up, down, left, right) {
(true, true, true, true) => '┼',
(false, true, true, true) => '┬',
(true, false, true, true) => '┴',
(true, true, false, true) => '├',
(true, true, true, false) => '┤',
_ if left || right => '─',
_ if up || down => '│',
_ => {
if vertical {
'│'
} else {
'─'
}
}
}
}
fn in_area(buf: &Buffer, x: u16, y: u16) -> bool {
let area = buf.area;
x >= area.x && y >= area.y && x < area.x + area.width && y < area.y + area.height
}
fn cell_char(buf: &Buffer, x: u16, y: u16) -> char {
if !in_area(buf, x, y) {
return ' ';
}
buf[(x, y)].symbol().chars().next().unwrap_or(' ')
}
pub(super) fn upgrade_junctions(
buf: &mut Buffer,
cells: &[(u16, u16)],
vertical: bool,
fg: RatatuiColor,
bg: RatatuiColor,
) {
for (i, &(x, y)) in cells.iter().enumerate() {
if !in_area(buf, x, y) {
continue;
}
let (mut up, mut down, mut left, mut right) = if vertical {
(i > 0, i + 1 < cells.len(), false, false)
} else {
(false, false, i > 0, i + 1 < cells.len())
};
if y > 0 {
up = up || (connections(cell_char(buf, x, y - 1)) & DOWN) != 0;
}
down = down || (connections(cell_char(buf, x, y + 1)) & UP) != 0;
if x > 0 {
left = left || (connections(cell_char(buf, x - 1, y)) & RIGHT) != 0;
}
right = right || (connections(cell_char(buf, x + 1, y)) & LEFT) != 0;
let glyph = glyph_for(up, down, left, right, vertical);
set_cell(buf, x, y, glyph, fg, bg);
}
}
#[cfg(test)]
mod tests {
use super::*;
use ratatui::layout::Rect as RRect;
use ratatui::style::Color;
fn buf(w: u16, h: u16) -> Buffer {
Buffer::empty(RRect::new(0, 0, w, h))
}
fn paint_run(
buf: &mut Buffer,
cells: &[(u16, u16)],
base: char,
fg: RatatuiColor,
bg: RatatuiColor,
) {
for &(x, y) in cells {
set_cell(buf, x, y, base, fg, bg);
}
}
#[test]
fn plain_runs_stay_plain_when_isolated() {
let mut b = buf(5, 5);
let fg = Color::White;
let bg = Color::Black;
let vcells: Vec<(u16, u16)> = (0..5).map(|y| (2, y)).collect();
paint_run(&mut b, &vcells, '│', fg, bg);
upgrade_junctions(&mut b, &vcells, true, fg, bg);
for &(x, y) in &vcells {
assert_eq!(cell_char(&b, x, y), '│');
}
}
#[test]
fn run_clipped_past_the_far_edge_does_not_panic() {
let fg = Color::White;
let bg = Color::Black;
let mut b = buf(5, 5);
let vcells: Vec<(u16, u16)> = (0..6).map(|y| (2, y)).collect();
paint_run(&mut b, &vcells, '│', fg, bg);
upgrade_junctions(&mut b, &vcells, true, fg, bg);
assert_eq!(cell_char(&b, 2, 4), '│');
let mut b = buf(5, 5);
let hcells: Vec<(u16, u16)> = (0..6).map(|x| (x, 2)).collect();
paint_run(&mut b, &hcells, '─', fg, bg);
upgrade_junctions(&mut b, &hcells, false, fg, bg);
assert_eq!(cell_char(&b, 4, 2), '─');
let mut b = buf(5, 5);
let outside: Vec<(u16, u16)> = (7..10).map(|y| (9, y)).collect();
upgrade_junctions(&mut b, &outside, true, fg, bg);
assert_eq!(cell_char(&b, 4, 4), ' ');
}
#[test]
fn run_clipped_before_the_area_origin_does_not_panic() {
let fg = Color::White;
let bg = Color::Black;
let mut b = Buffer::empty(RRect::new(5, 5, 5, 5));
let vcells: Vec<(u16, u16)> = (3..10).map(|y| (7, y)).collect();
paint_run(&mut b, &vcells, '│', fg, bg);
upgrade_junctions(&mut b, &vcells, true, fg, bg);
assert_eq!(cell_char(&b, 7, 5), '│');
let hcells: Vec<(u16, u16)> = (2..11).map(|x| (x, 7)).collect();
paint_run(&mut b, &hcells, '─', fg, bg);
upgrade_junctions(&mut b, &hcells, false, fg, bg);
assert_eq!(cell_char(&b, 7, 7), '┼');
assert_eq!(cell_char(&b, 5, 7), '─');
}
#[test]
fn crossing_runs_form_a_plus() {
let mut b = buf(5, 5);
let fg = Color::White;
let bg = Color::Black;
let vcells: Vec<(u16, u16)> = (0..5).map(|y| (2, y)).collect();
let hcells: Vec<(u16, u16)> = (0..5).map(|x| (x, 2)).collect();
paint_run(&mut b, &vcells, '│', fg, bg);
upgrade_junctions(&mut b, &vcells, true, fg, bg);
paint_run(&mut b, &hcells, '─', fg, bg);
upgrade_junctions(&mut b, &hcells, false, fg, bg);
assert_eq!(cell_char(&b, 2, 2), '┼');
assert_eq!(cell_char(&b, 2, 0), '│');
assert_eq!(cell_char(&b, 0, 2), '─');
}
#[test]
fn crossing_order_is_symmetric() {
let mut b = buf(5, 5);
let fg = Color::White;
let bg = Color::Black;
let vcells: Vec<(u16, u16)> = (0..5).map(|y| (2, y)).collect();
let hcells: Vec<(u16, u16)> = (0..5).map(|x| (x, 2)).collect();
paint_run(&mut b, &hcells, '─', fg, bg);
upgrade_junctions(&mut b, &hcells, false, fg, bg);
paint_run(&mut b, &vcells, '│', fg, bg);
upgrade_junctions(&mut b, &vcells, true, fg, bg);
assert_eq!(cell_char(&b, 2, 2), '┼');
}
#[test]
fn t_junction_each_side() {
let fg = Color::White;
let bg = Color::Black;
let mut b = buf(5, 5);
let hcells: Vec<(u16, u16)> = (0..5).map(|x| (x, 0)).collect();
paint_run(&mut b, &hcells, '─', fg, bg);
upgrade_junctions(&mut b, &hcells, false, fg, bg);
let vcells: Vec<(u16, u16)> = (0..3).map(|y| (2, y)).collect();
paint_run(&mut b, &vcells, '│', fg, bg);
upgrade_junctions(&mut b, &vcells, true, fg, bg);
assert_eq!(cell_char(&b, 2, 0), '┬');
let mut b = buf(5, 5);
let hcells: Vec<(u16, u16)> = (0..5).map(|x| (x, 4)).collect();
paint_run(&mut b, &hcells, '─', fg, bg);
upgrade_junctions(&mut b, &hcells, false, fg, bg);
let vcells: Vec<(u16, u16)> = (0..5).map(|y| (2, y)).collect();
paint_run(&mut b, &vcells, '│', fg, bg);
upgrade_junctions(&mut b, &vcells, true, fg, bg);
assert_eq!(cell_char(&b, 2, 4), '┴');
let mut b = buf(5, 5);
let vcells: Vec<(u16, u16)> = (0..5).map(|y| (0, y)).collect();
paint_run(&mut b, &vcells, '│', fg, bg);
upgrade_junctions(&mut b, &vcells, true, fg, bg);
let hcells: Vec<(u16, u16)> = (0..3).map(|x| (x, 2)).collect();
paint_run(&mut b, &hcells, '─', fg, bg);
upgrade_junctions(&mut b, &hcells, false, fg, bg);
assert_eq!(cell_char(&b, 0, 2), '├');
let mut b = buf(5, 5);
let vcells: Vec<(u16, u16)> = (0..5).map(|y| (4, y)).collect();
paint_run(&mut b, &vcells, '│', fg, bg);
upgrade_junctions(&mut b, &vcells, true, fg, bg);
let hcells: Vec<(u16, u16)> = (2..5).map(|x| (x, 2)).collect();
paint_run(&mut b, &hcells, '─', fg, bg);
upgrade_junctions(&mut b, &hcells, false, fg, bg);
assert_eq!(cell_char(&b, 4, 2), '┤');
}
#[test]
fn run_ending_on_a_status_bar_row_forms_a_tee() {
let mut b = buf(5, 5);
let fg = Color::White;
let bg = Color::Black;
let status_row: Vec<(u16, u16)> = (0..5).map(|x| (x, 4)).collect();
paint_run(&mut b, &status_row, '─', fg, bg);
upgrade_junctions(&mut b, &status_row, false, fg, bg);
let vcells: Vec<(u16, u16)> = (0..5).map(|y| (1, y)).collect();
paint_run(&mut b, &vcells, '│', fg, bg);
upgrade_junctions(&mut b, &vcells, true, fg, bg);
assert_eq!(cell_char(&b, 1, 4), '┴');
assert_eq!(cell_char(&b, 3, 4), '─');
}
}