use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::widgets::{Padding, Scrollbar, ScrollbarOrientation, ScrollbarState};
use crate::popup::PopupSize;
pub const BORDER_GRAY: Color = Color::Rgb(100, 100, 100);
pub const OUTPUT_GRAY: Color = Color::Rgb(120, 120, 120);
pub const FOOTER_GRAY: Color = Color::Rgb(140, 140, 140);
pub const CHECK: &str = "✓";
pub const CROSS: &str = "✗";
pub const HIGHLIGHT_SYMBOL: &str = "> ";
pub const BACKGROUND_DOT_SYMBOL: &str = "+";
pub const POPUP_WIDTH: PopupSize = PopupSize::Fixed(44);
pub const DEFAULT_HIGHLIGHT: Style = Style::new()
.fg(Color::Blue)
.bg(Color::Black)
.add_modifier(Modifier::REVERSED);
pub const TEXT_PADDING: Padding = Padding::new(1, 1, 0, 0);
pub const DEFAULT_FRAMES: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DotPattern {
Diagonal,
#[default]
Grid,
}
pub fn decorate_with_dots(buf: &mut Buffer, area: Rect, color: Color, symbol: &str, density: u16) {
decorate_with_dots_with_pattern(buf, area, color, symbol, density, density, DotPattern::default());
}
pub fn decorate_with_dots_with_pattern(
buf: &mut Buffer,
area: Rect,
color: Color,
symbol: &str,
density_x: u16,
density_y: u16,
pattern: DotPattern,
) {
if density_x == 0 || density_y == 0 {
return;
}
for y in area.y..area.bottom() {
for x in area.x..area.right() {
let hit = match pattern {
DotPattern::Diagonal => (x + y) % density_x == 0,
DotPattern::Grid => x % density_x == 0 && y % density_y == 0,
};
if !hit {
continue;
}
let cell = &mut buf[(x, y)];
if cell.symbol() == " " {
cell.set_symbol(symbol);
cell.set_fg(color);
}
}
}
}
pub fn default_scrollbar() -> Scrollbar<'static> {
Scrollbar::new(ScrollbarOrientation::VerticalRight)
.begin_symbol(None)
.end_symbol(None)
}
pub fn default_scrollbar_state(total: usize, position: usize) -> ScrollbarState {
ScrollbarState::new(total).position(position)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decorate_with_dots_paints_empty_cells() {
let mut buf = Buffer::empty(Rect::new(0, 0, 10, 5));
decorate_with_dots(&mut buf, Rect::new(0, 0, 10, 5), Color::Red, "·", 2);
assert_eq!(buf[(0, 0)].symbol(), "·");
assert_eq!(buf[(0, 0)].fg, Color::Red);
assert_eq!(buf[(2, 0)].symbol(), "·");
assert_eq!(buf[(0, 2)].symbol(), "·");
assert_eq!(buf[(2, 2)].symbol(), "·");
assert_eq!(buf[(1, 0)].symbol(), " ");
assert_eq!(buf[(0, 1)].symbol(), " ");
assert_eq!(buf[(1, 1)].symbol(), " ");
}
#[test]
fn decorate_with_dots_skips_non_empty_cells() {
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
buf[(2, 0)].set_symbol("X");
decorate_with_dots(&mut buf, Rect::new(0, 0, 5, 1), Color::Blue, "·", 1);
assert_eq!(buf[(2, 0)].symbol(), "X");
assert_eq!(buf[(0, 0)].symbol(), "·");
assert_eq!(buf[(3, 0)].symbol(), "·");
}
#[test]
fn decorate_with_dots_density_5() {
let mut buf = Buffer::empty(Rect::new(0, 0, 10, 1));
decorate_with_dots(&mut buf, Rect::new(0, 0, 10, 1), Color::White, "·", 5);
assert_eq!(buf[(0, 0)].symbol(), "·");
assert_eq!(buf[(5, 0)].symbol(), "·");
assert_eq!(buf[(1, 0)].symbol(), " ");
assert_eq!(buf[(3, 0)].symbol(), " ");
}
#[test]
fn decorate_with_dots_respects_area_bounds() {
let mut buf = Buffer::empty(Rect::new(0, 0, 10, 10));
decorate_with_dots(&mut buf, Rect::new(5, 5, 3, 3), Color::Green, "·", 1);
assert_eq!(buf[(5, 5)].symbol(), "·");
assert_eq!(buf[(7, 7)].symbol(), "·");
assert_eq!(buf[(0, 0)].symbol(), " ");
assert_eq!(buf[(8, 5)].symbol(), " ");
}
#[test]
fn decorate_with_dots_density_zero_is_noop() {
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
decorate_with_dots(&mut buf, Rect::new(0, 0, 5, 1), Color::Red, "·", 0);
assert_eq!(buf[(0, 0)].symbol(), " ");
}
#[test]
fn decorate_with_dots_custom_symbol() {
let mut buf = Buffer::empty(Rect::new(0, 0, 3, 1));
decorate_with_dots(&mut buf, Rect::new(0, 0, 3, 1), Color::Cyan, "•", 1);
assert_eq!(buf[(0, 0)].symbol(), "•");
assert_eq!(buf[(0, 0)].fg, Color::Cyan);
}
#[test]
fn decorate_with_dots_grid_pattern() {
let mut buf = Buffer::empty(Rect::new(0, 0, 10, 5));
decorate_with_dots_with_pattern(
&mut buf, Rect::new(0, 0, 10, 5), Color::Red, "·", 4, 2, DotPattern::Grid,
);
assert_eq!(buf[(0, 0)].symbol(), "·");
assert_eq!(buf[(4, 0)].symbol(), "·");
assert_eq!(buf[(0, 4)].symbol(), "·");
assert_eq!(buf[(4, 4)].symbol(), "·");
assert_eq!(buf[(1, 1)].symbol(), " ");
assert_eq!(buf[(2, 2)].symbol(), " ");
}
#[test]
fn decorate_with_dots_default_is_grid() {
let mut buf = Buffer::empty(Rect::new(0, 0, 4, 1));
decorate_with_dots(&mut buf, Rect::new(0, 0, 4, 1), Color::White, "·", 2);
assert_eq!(buf[(0, 0)].symbol(), "·");
assert_eq!(buf[(2, 0)].symbol(), "·");
assert_eq!(buf[(1, 0)].symbol(), " ");
assert_eq!(buf[(3, 0)].symbol(), " ");
}
#[test]
fn dot_pattern_default_is_grid() {
assert_eq!(DotPattern::default(), DotPattern::Grid);
}
#[test]
fn decorate_with_dots_color_persists() {
let mut buf = Buffer::empty(Rect::new(0, 0, 4, 1));
decorate_with_dots(&mut buf, Rect::new(0, 0, 4, 1), Color::Cyan, "·", 2);
assert_eq!(buf[(0, 0)].fg, Color::Cyan);
}
}