use crate::style::Color;
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Modifier: u8 {
const BOLD = 0b00000001;
const ITALIC = 0b00000010;
const UNDERLINE = 0b00000100;
const DIM = 0b00001000;
const CROSSED_OUT = 0b00010000;
const REVERSE = 0b00100000;
}
}
impl Modifier {
pub fn merge(&self, other: &Modifier) -> Modifier {
*self | *other
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cell {
pub symbol: char,
pub fg: Option<Color>,
pub bg: Option<Color>,
pub modifier: Modifier,
pub hyperlink_id: Option<u16>,
pub sequence_id: Option<u16>,
}
impl Default for Cell {
fn default() -> Self {
Self {
symbol: ' ',
fg: None,
bg: None,
modifier: Modifier::empty(),
hyperlink_id: None,
sequence_id: None,
}
}
}
impl Cell {
pub fn new(symbol: char) -> Self {
Self {
symbol,
fg: None,
bg: None,
modifier: Modifier::empty(),
hyperlink_id: None,
sequence_id: None,
}
}
pub fn empty() -> Self {
Self::new(' ')
}
pub fn is_continuation(&self) -> bool {
self.symbol == '\0'
}
pub fn continuation() -> Self {
Self {
symbol: '\0',
fg: None,
bg: None,
modifier: Modifier::empty(),
hyperlink_id: None,
sequence_id: None,
}
}
pub fn sequence(mut self, id: u16) -> Self {
self.sequence_id = Some(id);
self
}
pub fn hyperlink(mut self, id: u16) -> Self {
self.hyperlink_id = Some(id);
self
}
pub fn fg(mut self, color: Color) -> Self {
self.fg = Some(color);
self
}
pub fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
pub fn bold(mut self) -> Self {
self.modifier |= Modifier::BOLD;
self
}
pub fn italic(mut self) -> Self {
self.modifier |= Modifier::ITALIC;
self
}
pub fn underline(mut self) -> Self {
self.modifier |= Modifier::UNDERLINE;
self
}
pub fn dim(mut self) -> Self {
self.modifier |= Modifier::DIM;
self
}
pub fn reverse(mut self) -> Self {
self.modifier |= Modifier::REVERSE;
self
}
pub fn reset(&mut self) {
self.symbol = ' ';
self.fg = None;
self.bg = None;
self.modifier = Modifier::empty();
self.hyperlink_id = None;
self.sequence_id = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_modifier_flags() {
assert_eq!(Modifier::BOLD.bits(), 0b00000001);
assert_eq!(Modifier::ITALIC.bits(), 0b00000010);
assert_eq!(Modifier::UNDERLINE.bits(), 0b00000100);
assert_eq!(Modifier::DIM.bits(), 0b00001000);
assert_eq!(Modifier::CROSSED_OUT.bits(), 0b00010000);
assert_eq!(Modifier::REVERSE.bits(), 0b00100000);
}
#[test]
fn test_modifier_combine() {
let combined = Modifier::BOLD | Modifier::ITALIC;
assert!(combined.contains(Modifier::BOLD));
assert!(combined.contains(Modifier::ITALIC));
assert!(!combined.contains(Modifier::UNDERLINE));
}
#[test]
fn test_modifier_merge() {
let m1 = Modifier::BOLD;
let m2 = Modifier::ITALIC;
let merged = m1.merge(&m2);
assert!(merged.contains(Modifier::BOLD));
assert!(merged.contains(Modifier::ITALIC));
}
#[test]
fn test_modifier_default() {
let m = Modifier::default();
assert_eq!(m, Modifier::empty());
}
#[test]
fn test_cell_new() {
let cell = Cell::new('A');
assert_eq!(cell.symbol, 'A');
assert!(cell.fg.is_none());
assert!(cell.bg.is_none());
assert_eq!(cell.modifier, Modifier::empty());
}
#[test]
fn test_cell_default() {
let cell = Cell::default();
assert_eq!(cell.symbol, ' ');
assert!(cell.fg.is_none());
assert!(cell.bg.is_none());
assert!(cell.hyperlink_id.is_none());
assert!(cell.sequence_id.is_none());
}
#[test]
fn test_cell_empty() {
let cell = Cell::empty();
assert_eq!(cell.symbol, ' ');
}
#[test]
fn test_cell_is_continuation() {
let cell = Cell::continuation();
assert!(cell.is_continuation());
assert!(!Cell::new('A').is_continuation());
}
#[test]
fn test_cell_builder_fg() {
let cell = Cell::new('A').fg(Color::RED);
assert_eq!(cell.symbol, 'A');
assert_eq!(cell.fg, Some(Color::RED));
assert!(cell.bg.is_none());
}
#[test]
fn test_cell_builder_bg() {
let cell = Cell::new('A').bg(Color::BLUE);
assert_eq!(cell.symbol, 'A');
assert!(cell.fg.is_none());
assert_eq!(cell.bg, Some(Color::BLUE));
}
#[test]
fn test_cell_builder_bold() {
let cell = Cell::new('A').bold();
assert!(cell.modifier.contains(Modifier::BOLD));
}
#[test]
fn test_cell_builder_italic() {
let cell = Cell::new('A').italic();
assert!(cell.modifier.contains(Modifier::ITALIC));
}
#[test]
fn test_cell_builder_underline() {
let cell = Cell::new('A').underline();
assert!(cell.modifier.contains(Modifier::UNDERLINE));
}
#[test]
fn test_cell_builder_dim() {
let cell = Cell::new('A').dim();
assert!(cell.modifier.contains(Modifier::DIM));
}
#[test]
fn test_cell_builder_reverse() {
let cell = Cell::new('A').reverse();
assert!(cell.modifier.contains(Modifier::REVERSE));
}
#[test]
fn test_cell_builder_chaining() {
let cell = Cell::new('X')
.fg(Color::GREEN)
.bg(Color::BLACK)
.bold()
.underline();
assert_eq!(cell.fg, Some(Color::GREEN));
assert_eq!(cell.bg, Some(Color::BLACK));
assert!(cell.modifier.contains(Modifier::BOLD));
assert!(cell.modifier.contains(Modifier::UNDERLINE));
}
#[test]
fn test_cell_sequence() {
let cell = Cell::new('A').sequence(42);
assert_eq!(cell.sequence_id, Some(42));
}
#[test]
fn test_cell_hyperlink() {
let cell = Cell::new('A').hyperlink(100);
assert_eq!(cell.hyperlink_id, Some(100));
}
#[test]
fn test_cell_reset() {
let mut cell = Cell::new('X').fg(Color::RED).bg(Color::BLUE).bold();
cell.reset();
assert_eq!(cell.symbol, ' ');
assert!(cell.fg.is_none());
assert!(cell.bg.is_none());
assert_eq!(cell.modifier, Modifier::empty());
assert!(cell.hyperlink_id.is_none());
assert!(cell.sequence_id.is_none());
}
#[test]
fn test_cell_equality() {
let cell1 = Cell::new('A').fg(Color::RED);
let cell2 = Cell::new('A').fg(Color::RED);
let cell3 = Cell::new('B').fg(Color::RED);
assert_eq!(cell1, cell2);
assert_ne!(cell1, cell3);
}
}