Skip to main content

spec_ai/spec_ai_tui/style/
modifier.rs

1//! Text style modifiers (bold, italic, underline, etc.)
2
3use crossterm::style::Attribute;
4use std::ops::{BitOr, BitOrAssign};
5
6/// Style modifiers as a bitfield
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
8pub struct Modifier(u16);
9
10impl Modifier {
11    /// No modifiers
12    pub const NONE: Self = Self(0);
13    /// Bold text
14    pub const BOLD: Self = Self(1 << 0);
15    /// Dim/faint text
16    pub const DIM: Self = Self(1 << 1);
17    /// Italic text
18    pub const ITALIC: Self = Self(1 << 2);
19    /// Underlined text
20    pub const UNDERLINED: Self = Self(1 << 3);
21    /// Slow blink
22    pub const SLOW_BLINK: Self = Self(1 << 4);
23    /// Rapid blink
24    pub const RAPID_BLINK: Self = Self(1 << 5);
25    /// Reversed (swap fg/bg)
26    pub const REVERSED: Self = Self(1 << 6);
27    /// Hidden text
28    pub const HIDDEN: Self = Self(1 << 7);
29    /// Strikethrough
30    pub const CROSSED_OUT: Self = Self(1 << 8);
31
32    /// Create an empty modifier set
33    pub const fn empty() -> Self {
34        Self::NONE
35    }
36
37    /// Check if no modifiers are set
38    pub const fn is_empty(&self) -> bool {
39        self.0 == 0
40    }
41
42    /// Check if a modifier is set
43    pub const fn contains(&self, other: Self) -> bool {
44        (self.0 & other.0) == other.0
45    }
46
47    /// Create union of modifiers
48    pub const fn union(self, other: Self) -> Self {
49        Self(self.0 | other.0)
50    }
51
52    /// Create intersection of modifiers
53    pub const fn intersection(self, other: Self) -> Self {
54        Self(self.0 & other.0)
55    }
56
57    /// Remove modifiers
58    pub const fn difference(self, other: Self) -> Self {
59        Self(self.0 & !other.0)
60    }
61
62    /// Insert a modifier
63    pub fn insert(&mut self, other: Self) {
64        self.0 |= other.0;
65    }
66
67    /// Remove a modifier
68    pub fn remove(&mut self, other: Self) {
69        self.0 &= !other.0;
70    }
71
72    /// Get crossterm attributes for this modifier
73    pub fn attributes(&self) -> Vec<Attribute> {
74        let mut attrs = Vec::new();
75        if self.contains(Self::BOLD) {
76            attrs.push(Attribute::Bold);
77        }
78        if self.contains(Self::DIM) {
79            attrs.push(Attribute::Dim);
80        }
81        if self.contains(Self::ITALIC) {
82            attrs.push(Attribute::Italic);
83        }
84        if self.contains(Self::UNDERLINED) {
85            attrs.push(Attribute::Underlined);
86        }
87        if self.contains(Self::SLOW_BLINK) {
88            attrs.push(Attribute::SlowBlink);
89        }
90        if self.contains(Self::RAPID_BLINK) {
91            attrs.push(Attribute::RapidBlink);
92        }
93        if self.contains(Self::REVERSED) {
94            attrs.push(Attribute::Reverse);
95        }
96        if self.contains(Self::HIDDEN) {
97            attrs.push(Attribute::Hidden);
98        }
99        if self.contains(Self::CROSSED_OUT) {
100            attrs.push(Attribute::CrossedOut);
101        }
102        attrs
103    }
104}
105
106impl BitOr for Modifier {
107    type Output = Self;
108
109    fn bitor(self, rhs: Self) -> Self::Output {
110        self.union(rhs)
111    }
112}
113
114impl BitOrAssign for Modifier {
115    fn bitor_assign(&mut self, rhs: Self) {
116        self.insert(rhs);
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn test_modifier_empty() {
126        let m = Modifier::empty();
127        assert!(m.is_empty());
128        assert!(!m.contains(Modifier::BOLD));
129    }
130
131    #[test]
132    fn test_modifier_union() {
133        let m = Modifier::BOLD | Modifier::ITALIC;
134        assert!(m.contains(Modifier::BOLD));
135        assert!(m.contains(Modifier::ITALIC));
136        assert!(!m.contains(Modifier::UNDERLINED));
137    }
138
139    #[test]
140    fn test_modifier_insert_remove() {
141        let mut m = Modifier::BOLD;
142        m.insert(Modifier::ITALIC);
143        assert!(m.contains(Modifier::BOLD));
144        assert!(m.contains(Modifier::ITALIC));
145
146        m.remove(Modifier::BOLD);
147        assert!(!m.contains(Modifier::BOLD));
148        assert!(m.contains(Modifier::ITALIC));
149    }
150
151    #[test]
152    fn test_modifier_attributes() {
153        let m = Modifier::BOLD | Modifier::UNDERLINED;
154        let attrs = m.attributes();
155        assert_eq!(attrs.len(), 2);
156        assert!(attrs.contains(&Attribute::Bold));
157        assert!(attrs.contains(&Attribute::Underlined));
158    }
159}