mod color;
pub use self::color::Color;
use std::ops::{BitOr, BitOrAssign};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Attribute(u8);
impl Attribute {
pub const NONE: Self = Self(0);
pub const BOLD: Self = Self(1 << 0);
pub const DIM: Self = Self(1 << 1);
pub const ITALIC: Self = Self(1 << 2);
pub const UNDERLINED: Self = Self(1 << 3);
pub const STRIKETHROUGH: Self = Self(1 << 4);
pub fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub fn is_empty(self) -> bool {
self.0 == 0
}
}
impl BitOr for Attribute {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl BitOrAssign for Attribute {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
pub type Modifier = Attribute;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Style {
pub fg: Option<Color>,
pub bg: Option<Color>,
pub attrs: Attribute,
}
impl Style {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn fg(mut self, color: Color) -> Self {
self.fg = Some(color);
self
}
#[must_use]
pub fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
#[must_use]
pub fn add_modifier(mut self, attr: Attribute) -> Self {
self.attrs |= attr;
self
}
#[must_use]
pub fn remove_modifier(mut self, attr: Attribute) -> Self {
self.attrs = Attribute(self.attrs.0 & !attr.0);
self
}
#[must_use]
pub fn bold(self) -> Self {
self.add_modifier(Attribute::BOLD)
}
#[must_use]
pub fn dim(self) -> Self {
self.add_modifier(Attribute::DIM)
}
#[must_use]
pub fn italic(self) -> Self {
self.add_modifier(Attribute::ITALIC)
}
#[must_use]
pub fn underlined(self) -> Self {
self.add_modifier(Attribute::UNDERLINED)
}
#[must_use]
pub fn strikethrough(self) -> Self {
self.add_modifier(Attribute::STRIKETHROUGH)
}
#[must_use]
pub fn patch(mut self, other: Style) -> Self {
if other.fg.is_some() {
self.fg = other.fg;
}
if other.bg.is_some() {
self.bg = other.bg;
}
self.attrs |= other.attrs;
self
}
}
impl From<Color> for Style {
fn from(color: Color) -> Self {
Style::new().fg(color)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn attributes_combine_and_test_membership() {
let attrs = Attribute::BOLD | Attribute::ITALIC;
assert!(attrs.contains(Attribute::BOLD));
assert!(attrs.contains(Attribute::ITALIC));
assert!(!attrs.contains(Attribute::DIM));
}
#[test]
fn patch_overrides_set_fields_only() {
let base = Style::new().fg(Color::Red).bold();
let patched = base.patch(Style::new().bg(Color::Blue).italic());
assert_eq!(patched.fg, Some(Color::Red));
assert_eq!(patched.bg, Some(Color::Blue));
assert!(patched.attrs.contains(Attribute::BOLD));
assert!(patched.attrs.contains(Attribute::ITALIC));
}
#[test]
fn remove_modifier_clears_only_the_named_attribute() {
let style = Style::new()
.bold()
.italic()
.remove_modifier(Attribute::BOLD);
assert!(!style.attrs.contains(Attribute::BOLD));
assert!(style.attrs.contains(Attribute::ITALIC));
}
}