use crate::primitives::termtui::copy_mode::{CopyMode, CopyPos};
use crate::primitives::termtui::screen::Screen;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::widgets::Widget;
pub struct TermTuiWidget<'a> {
screen: &'a Screen,
scroll_offset: usize,
copy_mode: Option<&'a CopyMode>,
}
impl<'a> TermTuiWidget<'a> {
pub fn new(screen: &'a Screen) -> Self {
Self {
screen,
scroll_offset: 0,
copy_mode: None,
}
}
pub fn scroll_offset(mut self, offset: usize) -> Self {
self.scroll_offset = offset;
self
}
pub fn copy_mode(mut self, mode: &'a CopyMode) -> Self {
self.copy_mode = Some(mode);
self
}
}
impl Widget for TermTuiWidget<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let size = self.screen.size();
let _screen_rows = size.rows as usize;
let screen_cols = size.cols as usize;
let selection = self.copy_mode.and_then(|m| m.get_selection());
let copy_cursor = self.copy_mode.and_then(|m| m.cursor());
for (row_idx, row) in self.screen.visible_rows().enumerate() {
if row_idx >= area.height as usize {
break;
}
let y = area.y + row_idx as u16;
for (col_idx, cell) in row.cells().enumerate() {
if col_idx >= area.width as usize || col_idx >= screen_cols {
break;
}
let x = area.x + col_idx as u16;
if cell.is_wide_continuation() {
continue;
}
let mut style = cell.attrs().to_ratatui();
if let Some((start, end)) = &selection {
let cell_y = row_idx as i32 - self.scroll_offset as i32;
let cell_x = col_idx as i32;
if is_in_selection(cell_x, cell_y, start, end) {
style = Style::default()
.bg(Color::Rgb(70, 130, 180))
.fg(Color::White);
}
}
let ch = cell.text().chars().next().unwrap_or(' ');
if let Some(buf_cell) = buf.cell_mut((x, y)) {
buf_cell.set_char(ch).set_style(style);
}
}
}
let cursor_pos = self.screen.cursor_pos();
if let Some(copy_cursor) = copy_cursor {
let cursor_row = (copy_cursor.y + self.scroll_offset as i32) as u16;
let cursor_col = copy_cursor.x as u16;
if cursor_row < area.height && cursor_col < area.width {
let x = area.x + cursor_col;
let y = area.y + cursor_row;
if let Some(cell) = buf.cell_mut((x, y)) {
let cursor_style = Style::default()
.bg(Color::Yellow)
.fg(Color::Black)
.add_modifier(Modifier::BOLD);
cell.set_style(cursor_style);
}
}
} else if self.screen.cursor_visible() && self.scroll_offset == 0 {
let cursor_row = cursor_pos.row;
let cursor_col = cursor_pos.col;
if cursor_row < area.height && cursor_col < area.width {
let x = area.x + cursor_col;
let y = area.y + cursor_row;
if let Some(cell) = buf.cell_mut((x, y)) {
let cursor_style = Style::default()
.bg(Color::White)
.fg(Color::Black)
.add_modifier(Modifier::REVERSED);
if cell.symbol() == " " {
cell.set_char('█');
}
cell.set_style(cursor_style);
}
}
}
}
}
fn is_in_selection(x: i32, y: i32, start: &CopyPos, end: &CopyPos) -> bool {
let (low, high) = CopyPos::to_low_high(start, end);
if y < low.y || y > high.y {
return false;
}
if y == low.y && y == high.y {
x >= low.x && x <= high.x
} else if y == low.y {
x >= low.x
} else if y == high.y {
x <= high.x
} else {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_in_selection_single_line() {
let start = CopyPos::new(5, 10);
let end = CopyPos::new(15, 10);
assert!(is_in_selection(5, 10, &start, &end));
assert!(is_in_selection(10, 10, &start, &end));
assert!(is_in_selection(15, 10, &start, &end));
assert!(!is_in_selection(4, 10, &start, &end));
assert!(!is_in_selection(16, 10, &start, &end));
assert!(!is_in_selection(10, 9, &start, &end));
}
#[test]
fn test_is_in_selection_multi_line() {
let start = CopyPos::new(5, 10);
let end = CopyPos::new(15, 12);
assert!(is_in_selection(5, 10, &start, &end));
assert!(is_in_selection(50, 10, &start, &end)); assert!(!is_in_selection(4, 10, &start, &end));
assert!(is_in_selection(0, 11, &start, &end));
assert!(is_in_selection(50, 11, &start, &end));
assert!(is_in_selection(0, 12, &start, &end));
assert!(is_in_selection(15, 12, &start, &end));
assert!(!is_in_selection(16, 12, &start, &end));
}
#[test]
fn test_is_in_selection_reversed() {
let start = CopyPos::new(15, 12);
let end = CopyPos::new(5, 10);
assert!(is_in_selection(10, 11, &start, &end));
}
}