diff options
| author | Conrad Ludgate <conradludgate@gmail.com> | 2023-02-10 17:25:43 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-02-10 17:25:43 +0000 |
| commit | edda1b741a4a0816eb6e62eafd69fc9896603cf5 (patch) | |
| tree | cc5cb45caecc4fbe6b34e08f2347fdfdf897d0b5 /src/tui/style.rs | |
| parent | Bump debian from bullseye-20221205-slim to bullseye-20230208-slim (#701) (diff) | |
| download | atuin-edda1b741a4a0816eb6e62eafd69fc9896603cf5.zip | |
crossterm support (#331)
* crossterm v2
* patch crossterm
* fix-version
* no more tui dependency
* lints
Diffstat (limited to 'src/tui/style.rs')
| -rw-r--r-- | src/tui/style.rs | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/src/tui/style.rs b/src/tui/style.rs new file mode 100644 index 00000000..4aa19d4a --- /dev/null +++ b/src/tui/style.rs @@ -0,0 +1,278 @@ +//! `style` contains the primitives used to control how your user interface will look. + +use bitflags::bitflags; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Color { + Reset, + Black, + Red, + Green, + Yellow, + Blue, + Magenta, + Cyan, + Gray, + DarkGray, + LightRed, + LightGreen, + LightYellow, + LightBlue, + LightMagenta, + LightCyan, + White, + Rgb(u8, u8, u8), + Indexed(u8), +} + +bitflags! { + /// Modifier changes the way a piece of text is displayed. + /// + /// They are bitflags so they can easily be composed. + /// + /// ## Examples + /// + /// ```rust + /// # use tui::style::Modifier; + /// + /// let m = Modifier::BOLD | Modifier::ITALIC; + /// ``` + pub struct Modifier: u16 { + const BOLD = 0b0000_0000_0001; + const DIM = 0b0000_0000_0010; + const ITALIC = 0b0000_0000_0100; + const UNDERLINED = 0b0000_0000_1000; + const SLOW_BLINK = 0b0000_0001_0000; + const RAPID_BLINK = 0b0000_0010_0000; + const REVERSED = 0b0000_0100_0000; + const HIDDEN = 0b0000_1000_0000; + const CROSSED_OUT = 0b0001_0000_0000; + } +} + +/// Style let you control the main characteristics of the displayed elements. +/// +/// ```rust +/// # use tui::style::{Color, Modifier, Style}; +/// Style::default() +/// .fg(Color::Black) +/// .bg(Color::Green) +/// .add_modifier(Modifier::ITALIC | Modifier::BOLD); +/// ``` +/// +/// It represents an incremental change. If you apply the styles S1, S2, S3 to a cell of the +/// terminal buffer, the style of this cell will be the result of the merge of S1, S2 and S3, not +/// just S3. +/// +/// ```rust +/// # use tui::style::{Color, Modifier, Style}; +/// # use tui::buffer::Buffer; +/// # use tui::layout::Rect; +/// let styles = [ +/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC), +/// Style::default().bg(Color::Red), +/// Style::default().fg(Color::Yellow).remove_modifier(Modifier::ITALIC), +/// ]; +/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1)); +/// for style in &styles { +/// buffer.get_mut(0, 0).set_style(*style); +/// } +/// assert_eq!( +/// Style { +/// fg: Some(Color::Yellow), +/// bg: Some(Color::Red), +/// add_modifier: Modifier::BOLD, +/// sub_modifier: Modifier::empty(), +/// }, +/// buffer.get(0, 0).style(), +/// ); +/// ``` +/// +/// The default implementation returns a `Style` that does not modify anything. If you wish to +/// reset all properties until that point use [`Style::reset`]. +/// +/// ``` +/// # use tui::style::{Color, Modifier, Style}; +/// # use tui::buffer::Buffer; +/// # use tui::layout::Rect; +/// let styles = [ +/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC), +/// Style::reset().fg(Color::Yellow), +/// ]; +/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1)); +/// for style in &styles { +/// buffer.get_mut(0, 0).set_style(*style); +/// } +/// assert_eq!( +/// Style { +/// fg: Some(Color::Yellow), +/// bg: Some(Color::Reset), +/// add_modifier: Modifier::empty(), +/// sub_modifier: Modifier::empty(), +/// }, +/// buffer.get(0, 0).style(), +/// ); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Style { + pub fg: Option<Color>, + pub bg: Option<Color>, + pub add_modifier: Modifier, + pub sub_modifier: Modifier, +} + +impl Default for Style { + fn default() -> Style { + Style { + fg: None, + bg: None, + add_modifier: Modifier::empty(), + sub_modifier: Modifier::empty(), + } + } +} + +impl Style { + /// Returns a `Style` resetting all properties. + pub fn reset() -> Style { + Style { + fg: Some(Color::Reset), + bg: Some(Color::Reset), + add_modifier: Modifier::empty(), + sub_modifier: Modifier::all(), + } + } + + /// Changes the foreground color. + /// + /// ## Examples + /// + /// ```rust + /// # use tui::style::{Color, Style}; + /// let style = Style::default().fg(Color::Blue); + /// let diff = Style::default().fg(Color::Red); + /// assert_eq!(style.patch(diff), Style::default().fg(Color::Red)); + /// ``` + pub fn fg(mut self, color: Color) -> Style { + self.fg = Some(color); + self + } + + /// Changes the background color. + /// + /// ## Examples + /// + /// ```rust + /// # use tui::style::{Color, Style}; + /// let style = Style::default().bg(Color::Blue); + /// let diff = Style::default().bg(Color::Red); + /// assert_eq!(style.patch(diff), Style::default().bg(Color::Red)); + /// ``` + pub fn bg(mut self, color: Color) -> Style { + self.bg = Some(color); + self + } + + /// Changes the text emphasis. + /// + /// When applied, it adds the given modifier to the `Style` modifiers. + /// + /// ## Examples + /// + /// ```rust + /// # use tui::style::{Color, Modifier, Style}; + /// let style = Style::default().add_modifier(Modifier::BOLD); + /// let diff = Style::default().add_modifier(Modifier::ITALIC); + /// let patched = style.patch(diff); + /// assert_eq!(patched.add_modifier, Modifier::BOLD | Modifier::ITALIC); + /// assert_eq!(patched.sub_modifier, Modifier::empty()); + /// ``` + pub fn add_modifier(mut self, modifier: Modifier) -> Style { + self.sub_modifier.remove(modifier); + self.add_modifier.insert(modifier); + self + } + + /// Changes the text emphasis. + /// + /// When applied, it removes the given modifier from the `Style` modifiers. + /// + /// ## Examples + /// + /// ```rust + /// # use tui::style::{Color, Modifier, Style}; + /// let style = Style::default().add_modifier(Modifier::BOLD | Modifier::ITALIC); + /// let diff = Style::default().remove_modifier(Modifier::ITALIC); + /// let patched = style.patch(diff); + /// assert_eq!(patched.add_modifier, Modifier::BOLD); + /// assert_eq!(patched.sub_modifier, Modifier::ITALIC); + /// ``` + pub fn remove_modifier(mut self, modifier: Modifier) -> Style { + self.add_modifier.remove(modifier); + self.sub_modifier.insert(modifier); + self + } + + /// Results in a combined style that is equivalent to applying the two individual styles to + /// a style one after the other. + /// + /// ## Examples + /// ``` + /// # use tui::style::{Color, Modifier, Style}; + /// let style_1 = Style::default().fg(Color::Yellow); + /// let style_2 = Style::default().bg(Color::Red); + /// let combined = style_1.patch(style_2); + /// assert_eq!( + /// Style::default().patch(style_1).patch(style_2), + /// Style::default().patch(combined)); + /// ``` + pub fn patch(mut self, other: Style) -> Style { + self.fg = other.fg.or(self.fg); + self.bg = other.bg.or(self.bg); + + self.add_modifier.remove(other.sub_modifier); + self.add_modifier.insert(other.add_modifier); + self.sub_modifier.remove(other.add_modifier); + self.sub_modifier.insert(other.sub_modifier); + + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn styles() -> Vec<Style> { + vec![ + Style::default(), + Style::default().fg(Color::Yellow), + Style::default().bg(Color::Yellow), + Style::default().add_modifier(Modifier::BOLD), + Style::default().remove_modifier(Modifier::BOLD), + Style::default().add_modifier(Modifier::ITALIC), + Style::default().remove_modifier(Modifier::ITALIC), + Style::default().add_modifier(Modifier::ITALIC | Modifier::BOLD), + Style::default().remove_modifier(Modifier::ITALIC | Modifier::BOLD), + ] + } + + #[test] + fn combined_patch_gives_same_result_as_individual_patch() { + let styles = styles(); + for &a in &styles { + for &b in &styles { + for &c in &styles { + for &d in &styles { + let combined = a.patch(b.patch(c.patch(d))); + + assert_eq!( + Style::default().patch(a).patch(b).patch(c).patch(d), + Style::default().patch(combined) + ); + } + } + } + } + } +} |
