diff --git a/daemon/src/actors/window.rs b/daemon/src/actors/window.rs index a6b7adb..5180661 100644 --- a/daemon/src/actors/window.rs +++ b/daemon/src/actors/window.rs @@ -10,6 +10,7 @@ use crate::{ pane::{Pane, PaneHandle}, session::SessionHandle, }, + cell::set_cursor_position, layout::{LayoutNode, Rect, SplitDirection}, prelude::*, }; @@ -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?; } @@ -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?; @@ -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| -> 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(()) + } } diff --git a/daemon/src/cell.rs b/daemon/src/cell.rs index 2043754..b1b3257 100644 --- a/daemon/src/cell.rs +++ b/daemon/src/cell.rs @@ -319,7 +319,7 @@ fn push_u16(buf: &mut Vec, mut n: u16) { } #[inline] -fn set_cursor_position(buf: &mut Vec, x: u16, y: u16) { +pub fn set_cursor_position(buf: &mut Vec, x: u16, y: u16) { buf.extend_from_slice(b"\x1b["); push_u16(buf, y); buf.push(b';'); diff --git a/daemon/src/layout.rs b/daemon/src/layout.rs index 8651012..9d858f7 100644 --- a/daemon/src/layout.rs +++ b/daemon/src/layout.rs @@ -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 }; @@ -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 };