use super::{Color, Modifier};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Style {
pub fg: Option<Color>,
pub bg: Option<Color>,
pub add_modifier: Modifier,
pub sub_modifier: Modifier,
}
impl Style {
pub fn new() -> Self {
Self::default()
}
pub fn reset() -> Self {
Self {
fg: Some(Color::Reset),
bg: Some(Color::Reset),
add_modifier: Modifier::empty(),
sub_modifier: Modifier::all(),
}
}
pub fn fg(mut self, c: Color) -> Self {
self.fg = Some(c);
self
}
pub fn bg(mut self, c: Color) -> Self {
self.bg = Some(c);
self
}
pub fn add_modifier(mut self, m: Modifier) -> Self {
self.sub_modifier.remove(m);
self.add_modifier |= m;
self
}
pub fn remove_modifier(mut self, m: Modifier) -> Self {
self.add_modifier.remove(m);
self.sub_modifier |= m;
self
}
pub fn patch(self, other: Style) -> Style {
Style {
fg: other.fg.or(self.fg),
bg: other.bg.or(self.bg),
add_modifier: self.add_modifier.difference(other.sub_modifier) | other.add_modifier,
sub_modifier: self.sub_modifier.difference(other.add_modifier) | other.sub_modifier,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_noop() {
let s = Style::default();
assert_eq!(s.fg, None);
assert_eq!(s.bg, None);
assert!(s.add_modifier.is_empty());
assert!(s.sub_modifier.is_empty());
}
#[test]
fn fg_bg_setters() {
let s = Style::new()
.fg(Color::Rgb(255, 0, 0))
.bg(Color::Rgb(0, 0, 0));
assert_eq!(s.fg, Some(Color::Rgb(255, 0, 0)));
assert_eq!(s.bg, Some(Color::Rgb(0, 0, 0)));
}
#[test]
fn add_modifier_sets_bit() {
let s = Style::new().add_modifier(Modifier::BOLD);
assert!(s.add_modifier.contains(Modifier::BOLD));
assert!(!s.sub_modifier.contains(Modifier::BOLD));
}
#[test]
fn remove_modifier_sets_sub_bit() {
let s = Style::new().remove_modifier(Modifier::ITALIC);
assert!(s.sub_modifier.contains(Modifier::ITALIC));
assert!(!s.add_modifier.contains(Modifier::ITALIC));
}
#[test]
fn add_then_remove_same_bit_is_removed() {
let s = Style::new()
.add_modifier(Modifier::BOLD)
.remove_modifier(Modifier::BOLD);
assert!(!s.add_modifier.contains(Modifier::BOLD));
assert!(s.sub_modifier.contains(Modifier::BOLD));
}
#[test]
fn remove_then_add_same_bit_is_added() {
let s = Style::new()
.remove_modifier(Modifier::BOLD)
.add_modifier(Modifier::BOLD);
assert!(s.add_modifier.contains(Modifier::BOLD));
assert!(!s.sub_modifier.contains(Modifier::BOLD));
}
#[test]
fn patch_other_fg_overrides_self() {
let a = Style::new().fg(Color::Rgb(255, 0, 0));
let b = Style::new().fg(Color::Rgb(0, 0, 255));
assert_eq!(a.patch(b).fg, Some(Color::Rgb(0, 0, 255)));
}
#[test]
fn patch_none_inherits_self() {
let a = Style::new().fg(Color::Rgb(255, 0, 0));
let b = Style::new(); assert_eq!(a.patch(b).fg, Some(Color::Rgb(255, 0, 0)));
}
#[test]
fn patch_both_none_is_none() {
assert_eq!(Style::new().patch(Style::new()).fg, None);
}
#[test]
fn patch_accumulates_modifiers() {
let a = Style::new().add_modifier(Modifier::BOLD);
let b = Style::new().add_modifier(Modifier::ITALIC);
let merged = a.patch(b);
assert!(merged.add_modifier.contains(Modifier::BOLD));
assert!(merged.add_modifier.contains(Modifier::ITALIC));
}
#[test]
fn patch_other_remove_clears_self_add() {
let a = Style::new().add_modifier(Modifier::BOLD | Modifier::ITALIC);
let b = Style::new().remove_modifier(Modifier::BOLD);
let merged = a.patch(b);
assert!(!merged.add_modifier.contains(Modifier::BOLD));
assert!(merged.add_modifier.contains(Modifier::ITALIC));
assert!(merged.sub_modifier.contains(Modifier::BOLD));
}
#[test]
fn patch_other_add_clears_self_sub() {
let a = Style::new().remove_modifier(Modifier::BOLD);
let b = Style::new().add_modifier(Modifier::BOLD);
let merged = a.patch(b);
assert!(merged.add_modifier.contains(Modifier::BOLD));
assert!(!merged.sub_modifier.contains(Modifier::BOLD));
}
#[test]
fn patch_chain_preserves_order() {
let merged = Style::new()
.fg(Color::Rgb(255, 0, 0))
.add_modifier(Modifier::BOLD)
.patch(Style::new().add_modifier(Modifier::ITALIC))
.patch(
Style::new()
.fg(Color::Rgb(0, 0, 255))
.remove_modifier(Modifier::BOLD),
);
assert_eq!(merged.fg, Some(Color::Rgb(0, 0, 255)));
assert!(!merged.add_modifier.contains(Modifier::BOLD));
assert!(merged.add_modifier.contains(Modifier::ITALIC));
assert!(merged.sub_modifier.contains(Modifier::BOLD));
}
#[test]
fn reset_style_wipes_everything_when_patched_on() {
let a = Style::new()
.fg(Color::Rgb(255, 0, 0))
.bg(Color::Rgb(0, 0, 0))
.add_modifier(Modifier::BOLD | Modifier::ITALIC);
let merged = a.patch(Style::reset());
assert_eq!(merged.fg, Some(Color::Reset));
assert_eq!(merged.bg, Some(Color::Reset));
assert!(merged.add_modifier.is_empty());
}
#[test]
fn copy_semantics() {
let a = Style::new().fg(Color::Rgb(255, 0, 0));
let b = a; assert_eq!(a, b);
}
}