use crate::base::Rgba;
use super::cell::{Attrs, Cell};
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct Style {
pub fg: Option<Rgba>,
pub bg: Option<Rgba>,
pub ul: Option<Rgba>,
pub add: Attrs,
pub remove: Attrs,
pub link: u16,
}
impl Style {
pub const EMPTY: Style = Style {
fg: None,
bg: None,
ul: None,
add: Attrs::NONE,
remove: Attrs::NONE,
link: 0,
};
pub fn new() -> Style {
Style::EMPTY
}
pub fn absolute() -> Style {
Style {
remove: Attrs::ALL,
..Style::EMPTY
}
}
pub fn fg(mut self, color: Rgba) -> Style {
self.fg = Some(color);
self
}
pub fn bg(mut self, color: Rgba) -> Style {
self.bg = Some(color);
self
}
pub fn underline_color(mut self, color: Rgba) -> Style {
self.ul = Some(color);
self
}
pub fn attrs(mut self, attrs: Attrs) -> Style {
self.add = self.add.with(attrs);
self.remove = self.remove.without(attrs);
self
}
pub fn bold(self) -> Style {
self.attrs(Attrs::BOLD)
}
pub fn dim(self) -> Style {
self.attrs(Attrs::DIM)
}
pub fn italic(self) -> Style {
self.attrs(Attrs::ITALIC)
}
pub fn underline(self) -> Style {
self.attrs(Attrs::UNDERLINE)
}
pub fn strike(self) -> Style {
self.attrs(Attrs::STRIKE)
}
pub fn reverse(self) -> Style {
self.attrs(Attrs::REVERSE)
}
pub fn without_attrs(mut self, attrs: Attrs) -> Style {
self.remove = self.remove.with(attrs);
self.add = self.add.without(attrs);
self
}
pub fn link(mut self, id: u16) -> Style {
self.link = id;
self
}
pub fn merge(self, over: Style) -> Style {
Style {
fg: over.fg.or(self.fg),
bg: over.bg.or(self.bg),
ul: over.ul.or(self.ul),
add: self.add.without(over.remove).with(over.add),
remove: self.remove.without(over.add).with(over.remove),
link: if over.link != 0 { over.link } else { self.link },
}
}
pub fn apply(self, cell: &Cell) -> Cell {
Cell {
glyph: cell.glyph,
fg: self.fg.unwrap_or(cell.fg),
bg: self.bg.unwrap_or(cell.bg),
ul: self.ul.unwrap_or(cell.ul),
attrs: cell.attrs.without(self.remove).with(self.add),
link: self.link,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::render::cell::Glyph;
#[test]
fn patch_keeps_unset_fields() {
let base = Cell::new(Glyph::SPACE)
.with_fg(Rgba::rgb(1, 1, 1))
.with_bg(Rgba::rgb(9, 9, 9))
.with_attrs(Attrs::DIM);
let styled = Style::new()
.fg(Rgba::rgb(2, 2, 2))
.attrs(Attrs::BOLD)
.apply(&base);
assert_eq!(styled.fg, Rgba::rgb(2, 2, 2));
assert_eq!(styled.bg, Rgba::rgb(9, 9, 9), "unset bg keeps the cell's");
assert_eq!(styled.attrs, Attrs::DIM | Attrs::BOLD);
}
#[test]
fn absolute_replaces_attrs() {
let base = Cell::new(Glyph::SPACE).with_attrs(Attrs::DIM | Attrs::ITALIC);
let styled = Style::absolute().attrs(Attrs::BOLD).apply(&base);
assert_eq!(styled.attrs, Attrs::BOLD);
}
#[test]
fn merge_is_sequential_application() {
let a = Style::new().fg(Rgba::rgb(1, 0, 0)).attrs(Attrs::BOLD);
let b = Style::new()
.fg(Rgba::rgb(0, 1, 0))
.without_attrs(Attrs::BOLD)
.attrs(Attrs::DIM);
let m = a.merge(b);
let cell = Cell::EMPTY;
assert_eq!(m.apply(&cell), b.apply(&a.apply(&cell)));
assert_eq!(m.fg, Some(Rgba::rgb(0, 1, 0)));
assert!(m.remove.contains(Attrs::BOLD));
}
#[test]
fn terse_builders_equal_attrs_form() {
assert_eq!(Style::new().bold(), Style::new().attrs(Attrs::BOLD));
assert_eq!(
Style::new()
.fg(Rgba::rgb(1, 2, 3))
.bold()
.italic()
.underline(),
Style::new()
.fg(Rgba::rgb(1, 2, 3))
.attrs(Attrs::BOLD | Attrs::ITALIC | Attrs::UNDERLINE)
);
assert_eq!(
Style::new().dim().strike().reverse().add,
Attrs::DIM | Attrs::STRIKE | Attrs::REVERSE
);
assert_eq!(
Style::absolute().bold().remove,
Attrs::ALL.without(Attrs::BOLD)
);
}
#[test]
fn link_always_overwrites() {
let linked = Cell::new(Glyph::SPACE).with_link(4);
assert_eq!(
Style::new().apply(&linked).link,
0,
"no-link style clears stale link"
);
assert_eq!(Style::new().link(2).apply(&linked).link, 2);
}
}