use crate::color::{Color, ColorPair};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Cell {
pub ch: char,
pub colors: ColorPair,
pub bold: bool,
pub dim: bool,
pub italic: bool,
pub underline: bool,
}
impl Cell {
pub fn new(ch: char) -> Self {
Self {
ch,
..Self::default()
}
}
pub fn styled(ch: char, fg: impl Into<Option<Color>>, bg: impl Into<Option<Color>>) -> Self {
Self {
ch,
colors: ColorPair::new(fg.into(), bg.into()),
..Self::default()
}
}
pub fn with_fg(mut self, color: Color) -> Self {
self.colors.fg = Some(color);
self
}
pub fn with_bg(mut self, color: Color) -> Self {
self.colors.bg = Some(color);
self
}
pub fn has_style(&self) -> bool {
self.colors.fg.is_some()
|| self.colors.bg.is_some()
|| self.bold
|| self.dim
|| self.italic
|| self.underline
}
}
impl Default for Cell {
fn default() -> Self {
Self {
ch: ' ',
colors: ColorPair::default(),
bold: false,
dim: false,
italic: false,
underline: false,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Frame {
width: usize,
height: usize,
cells: Vec<Cell>,
}
impl Frame {
pub fn new(width: usize, height: usize) -> Self {
let width = width.max(1);
let height = height.max(1);
Self {
width,
height,
cells: vec![Cell::default(); width * height],
}
}
pub fn with_background(width: usize, height: usize, background: Option<Color>) -> Self {
let mut frame = Self::new(width, height);
if let Some(bg) = background {
for cell in &mut frame.cells {
cell.colors.bg = Some(bg);
}
}
frame
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn cells(&self) -> &[Cell] {
&self.cells
}
pub fn cell(&self, x: usize, y: usize) -> Option<&Cell> {
if x < self.width && y < self.height {
Some(&self.cells[y * self.width + x])
} else {
None
}
}
pub fn set(&mut self, x: usize, y: usize, cell: Cell) {
if x < self.width && y < self.height {
self.cells[y * self.width + x] = cell;
}
}
pub fn set_i32(&mut self, x: i32, y: i32, cell: Cell) {
if x >= 0 && y >= 0 {
self.set(x as usize, y as usize, cell);
}
}
pub fn to_plain_string(&self) -> String {
let mut out = String::new();
for y in 0..self.height {
if y > 0 {
out.push('\n');
}
for x in 0..self.width {
out.push(self.cells[y * self.width + x].ch);
}
}
out
}
pub fn to_ansi_string(&self) -> String {
let mut out = String::new();
for y in 0..self.height {
if y > 0 {
out.push('\n');
}
for x in 0..self.width {
let cell = &self.cells[y * self.width + x];
if cell.has_style() {
out.push_str(&ansi_prefix(cell));
out.push(cell.ch);
out.push_str("\x1b[0m");
} else {
out.push(cell.ch);
}
}
}
out
}
}
fn ansi_prefix(cell: &Cell) -> String {
let mut parts = Vec::new();
if cell.bold {
parts.push("1".to_string());
}
if cell.dim {
parts.push("2".to_string());
}
if cell.italic {
parts.push("3".to_string());
}
if cell.underline {
parts.push("4".to_string());
}
if let Some(fg) = cell.colors.fg {
parts.push(format!("38;2;{};{};{}", fg.r, fg.g, fg.b));
}
if let Some(bg) = cell.colors.bg {
parts.push(format!("48;2;{};{};{}", bg.r, bg.g, bg.b));
}
if parts.is_empty() {
String::new()
} else {
format!("\x1b[{}m", parts.join(";"))
}
}