Skip to main content

devela/sys/os/term/grid/
style.rs

1// devela::sys::os::term::grid::style
2//
3//! Defines [`TermStyle`] and [`TermStyleExt`].
4//
5
6crate::set! {
7    #[doc = crate::_tags!(term text set)]
8    /// A compact set of broadly supported terminal text styles.
9    #[doc = crate::_doc_meta!{
10        location("sys/os/term"),
11        test_size_of(TermStyle = 1|8),
12    }]
13    ///
14    /// Its bits occupy the low byte of [`TermStyleExt`] with the same meanings.
15    #[must_use]
16    pub struct TermStyle(u8) {
17        /// Increased intensity.
18        BOLD = 0;
19        /// Italic text.
20        ITALIC = 1;
21        /// Underlined text.
22        UNDERLINE = 2;
23        /// Reduced intensity.
24        DIM = 3;
25        /// Blinking text.
26        BLINK = 4;
27        /// Swapped foreground and background.
28        INVERSE = 5;
29        /// Concealed text.
30        HIDDEN = 6;
31        /// Crossed-out text.
32        CROSSED = 7;
33    }
34    impl {
35        /// Returns the corresponding extended style set.
36        pub const fn extended(self) -> TermStyleExt {
37            TermStyleExt::from_bits(self.bits as u16)
38        }
39    }
40}
41
42crate::set! {
43    #[doc = crate::_tags!(term text set)]
44    /// A complete set of terminal text styles.
45    #[doc = crate::_doc_meta!{
46        location("sys/os/term"),
47        test_size_of(TermStyleExt = 2|16),
48    }]
49    ///
50    /// The low byte has the same representation as [`TermStyle`].
51    /// Styles in the high byte generally have more limited terminal support.
52    #[must_use]
53    pub struct TermStyleExt(u16) {
54
55        /* common low byte */
56
57        /// Increased intensity.
58        BOLD = 0;
59        /// Italic text.
60        ITALIC = 1;
61        /// Underlined text.
62        UNDERLINE = 2;
63        /// Reduced intensity.
64        DIM = 3;
65        /// Blinking text.
66        BLINK = 4;
67        /// Swapped foreground and background.
68        INVERSE = 5;
69        /// Concealed text.
70        HIDDEN = 6;
71        /// Crossed-out text.
72        CROSSED = 7;
73
74        /* extended high byte */
75
76        /// Rapidly blinking text.
77        BLINK_FAST = 8;
78        /// Fraktur text.
79        FRAKTUR = 9;
80        /// Doubly underlined text.
81        UNDERLINE_DOUBLE = 10;
82        /// Framed text.
83        FRAMED = 11;
84        /// Encircled text.
85        ENCIRCLED = 12;
86        /// Overlined text.
87        OVERLINE = 13;
88        /// Superscript text.
89        SUPERSCRIPT = 14;
90        /// Subscript text.
91        SUBSCRIPT = 15;
92
93        /// All basic styles.
94        BASIC = 0..=7;
95        /// All extended styles.
96        EXTENDED = 8..=15;
97    }
98    impl {
99        /// Returns the basic styles.
100        pub const fn basic(self) -> TermStyle { TermStyle::from_bits(self.bits as u8) }
101
102        /// Returns whether no extended styles are enabled.
103        #[must_use]
104        pub const fn is_basic(self) -> bool { !self.intersects(Self::EXTENDED) }
105
106        /// Returns the basic style set when no extended styles are enabled.
107        #[must_use]
108        pub const fn try_basic(self) -> Option<TermStyle> {
109            if self.is_basic() { Some(self.basic()) } else { None }
110        }
111
112        /// Returns the enabled extended-only styles.
113        pub const fn extended_only(self) -> Self { self.intersection(Self::EXTENDED) }
114    }
115}
116impl From<TermStyle> for TermStyleExt {
117    fn from(style: TermStyle) -> Self {
118        style.extended()
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::{TermStyle as S, TermStyleExt as X};
125
126    #[test]
127    fn sizes() {
128        assert_eq!(size_of::<S>(), 1);
129        assert_eq!(size_of::<X>(), 2);
130        assert_eq!(size_of::<Option<S>>(), 2);
131        assert_eq!(size_of::<Option<X>>(), 4);
132    }
133    #[test]
134    fn basic_layout_matches() {
135        assert_eq!(S::all().extended(), X::BASIC);
136        assert_eq!(S::BOLD.extended(), X::BOLD);
137        assert_eq!(S::CROSSED.extended(), X::CROSSED);
138    }
139    #[test]
140    fn set_operations() {
141        let style = S::BOLD | S::ITALIC | S::UNDERLINE;
142        assert!(style.contains(S::BOLD | S::ITALIC));
143        assert!(style.intersects(S::UNDERLINE));
144        assert!(!style.intersects(S::BLINK));
145        assert_eq!(style.without(S::ITALIC), S::BOLD | S::UNDERLINE);
146    }
147    #[test]
148    fn extended_partition() {
149        let style = X::BOLD | X::FRAMED | X::OVERLINE;
150        assert_eq!(style.basic(), S::BOLD);
151        assert_eq!(style.extended_only(), X::FRAMED | X::OVERLINE);
152        assert!(!style.is_basic());
153        assert_eq!(style.try_basic(), None);
154        let basic = X::BOLD | X::UNDERLINE;
155        assert!(basic.is_basic());
156        assert_eq!(basic.try_basic(), Some(S::BOLD | S::UNDERLINE));
157    }
158    #[test]
159    fn style_ranges() {
160        assert_eq!(X::BASIC.bits(), 0x00FF);
161        assert_eq!(X::EXTENDED.bits(), 0xFF00);
162        assert_eq!((X::BASIC | X::EXTENDED).bits(), u16::MAX);
163        assert!(!X::BASIC.intersects(X::EXTENDED));
164    }
165}