Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions daemon/src/actors/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
pane::{Pane, PaneHandle},
session::SessionHandle,
},
cell::set_cursor_position,
layout::{LayoutNode, Rect, SplitDirection},
prelude::*,
};
Expand Down Expand Up @@ -183,6 +184,8 @@ impl Window {
Ok(())
}
async fn handle_redraw(&mut self) -> Result<()> {
self.draw_pane_borders().await?;

for pane in self.panes.iter() {
pane.1.rerender().await?;
}
Expand Down Expand Up @@ -218,6 +221,8 @@ impl Window {
}
};

self.draw_pane_borders().await?;

let move_cursor = format!("\x1b[{};{}H", ty, tx);
self.session_handle.window_output(Bytes::from(move_cursor)).await?;

Expand Down Expand Up @@ -309,4 +314,108 @@ impl Window {
self.handle_redraw().await?;
Ok(())
}

async fn draw_pane_borders(&mut self) -> Result<()> {
let cols = self.root_rect.width;
let rows = self.root_rect.height;

let mut output_buffer = Vec::with_capacity(cols as usize * rows as usize * 4);

// grab active pane rectangle
let active_rect = self.layout_sizing_map.get(&self.active_pane_id);

// checks if cell is in a pane or not
let is_content = |x: u16, y: u16, map: &BTreeMap<usize, Rect>| -> bool {
for rect in map.values() {
if x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height {
return true;
}
}
false
};

// reset colors
output_buffer.extend_from_slice(b"\x1b[0m");

// set initial cursor position
let mut cursor_row = 0;
let mut cursor_col = 0;
let mut cursor_invalid = true;

for y in 0..rows {
for x in 0..cols {
// skip cells in panes
if is_content(x, y, &self.layout_sizing_map) {
continue;
}

// get surrounding borders
let north = y > 0 && !is_content(x, y - 1, &self.layout_sizing_map);
let south = y < rows - 1 && !is_content(x, y + 1, &self.layout_sizing_map);
let west = x > 0 && !is_content(x - 1, y, &self.layout_sizing_map);
let east = x < cols - 1 && !is_content(x + 1, y, &self.layout_sizing_map);

// pattern match to get correct border char
let border_char = match (north, south, east, west) {
(true, true, false, false) => '│',
(false, false, true, true) => '─',
(false, true, true, false) => '┌',
(false, true, false, true) => '┐',
(true, false, true, false) => '└',
(true, false, false, true) => '┘',
(true, true, true, false) => '├',
(true, true, false, true) => '┤',
(false, true, true, true) => '┬',
(true, false, true, true) => '┴',
(true, true, true, true) => '┼',
(true, false, false, false) => '│',
(false, true, false, false) => '│',
(false, false, true, false) => '─',
(false, false, false, true) => '─',
_ => ' ',
};

// move cursor if at the wrong spot
if cursor_invalid || y != cursor_row || x != cursor_col {
set_cursor_position(&mut output_buffer, x + 1, y + 1);
cursor_invalid = false;
cursor_row = y;
cursor_col = x;
}

// if the border is on the active pane, set this to true
let mut is_active_border = false;
if let Some(rect) = active_rect {
if x >= rect.x.saturating_sub(1)
&& x < rect.x + rect.width + 1
&& y >= rect.y.saturating_sub(1)
&& y < rect.y + rect.height + 1
{
is_active_border = true;
}
}

// configurable colors later
if is_active_border {
output_buffer.extend_from_slice(b"\x1b[96m");
} else {
output_buffer.extend_from_slice(b"\x1b[90m");
}

// add ANSI
let mut border_char_buf = [0u8; 4];
let str_slice = border_char.encode_utf8(&mut border_char_buf);
output_buffer.extend_from_slice(str_slice.as_bytes());

cursor_col += 1;
}
}

// send to session
if !output_buffer.is_empty() {
self.session_handle.window_output(Bytes::from(output_buffer)).await?;
}

Ok(())
}
}
2 changes: 1 addition & 1 deletion daemon/src/cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ fn push_u16(buf: &mut Vec<u8>, mut n: u16) {
}

#[inline]
fn set_cursor_position(buf: &mut Vec<u8>, x: u16, y: u16) {
pub fn set_cursor_position(buf: &mut Vec<u8>, x: u16, y: u16) {
buf.extend_from_slice(b"\x1b[");
push_u16(buf, y);
buf.push(b';');
Expand Down
20 changes: 14 additions & 6 deletions daemon/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,21 @@ impl LayoutNode {

match direction {
SplitDirection::Vertical => {
let left_width = (area.width as u32 * left_weight / total_weight) as u16;
let right_width = area.width - left_width;
// remove a column for the border
let available_width = area.width.saturating_sub(1);

let left_width = (available_width as u32 * left_weight / total_weight) as u16;
let right_width = available_width - left_width;

let left_rect = Rect {
width: left_width,
..area
};

// +1 for the border
let right_rect = Rect {
width: right_width,
x: area.x + left_width,
x: area.x + left_width + 1,
..area
};

Expand All @@ -135,17 +139,21 @@ impl LayoutNode {
Ok(())
}
SplitDirection::Horizontal => {
let top_height = (area.height as u32 * left_weight / total_weight) as u16;
let bottom_height = area.height - top_height;
// remove a row for the border
let available_height = area.height.saturating_sub(1);

let top_height = (available_height as u32 * left_weight / total_weight) as u16;
let bottom_height = available_height - top_height;

let top_rect = Rect {
height: top_height,
..area
};

// +1 for the border
let bottom_rect = Rect {
height: bottom_height,
y: area.y + top_height,
y: area.y + top_height + 1,
..area
};

Expand Down