Skip to content

Commit

Permalink
feat(style): Enable setting the underline color for crossterm (ratatu…
Browse files Browse the repository at this point in the history
…i#308) (ratatui#310)

This commit adds the underline_color() function to the Style and Cell
structs. This enables setting the underline color of text on the
crossterm backend. This is a no-op for the termion and termwiz backends
as they do not support this feature.
  • Loading branch information
Nogesma authored and a-kenji committed Jul 16, 2023
1 parent 5ac0f31 commit 9b13cef
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 7 deletions.
9 changes: 8 additions & 1 deletion src/backend/crossterm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crossterm::{
execute, queue,
style::{
Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor,
SetForegroundColor,
SetForegroundColor, SetUnderlineColor,
},
terminal::{self, Clear},
};
Expand Down Expand Up @@ -81,6 +81,7 @@ where
{
let mut fg = Color::Reset;
let mut bg = Color::Reset;
let mut underline_color = Color::Reset;
let mut modifier = Modifier::empty();
let mut last_pos: Option<(u16, u16)> = None;
for (x, y, cell) in content {
Expand All @@ -107,6 +108,11 @@ where
map_error(queue!(self.buffer, SetBackgroundColor(color)))?;
bg = cell.bg;
}
if cell.underline_color != underline_color {
let color = CColor::from(cell.underline_color);
map_error(queue!(self.buffer, SetUnderlineColor(color)))?;
underline_color = cell.underline_color;
}

map_error(queue!(self.buffer, Print(&cell.symbol)))?;
}
Expand All @@ -115,6 +121,7 @@ where
self.buffer,
SetForegroundColor(CColor::Reset),
SetBackgroundColor(CColor::Reset),
SetUnderlineColor(CColor::Reset),
SetAttribute(CAttribute::Reset)
))
}
Expand Down
68 changes: 64 additions & 4 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub struct Cell {
pub symbol: String,
pub fg: Color,
pub bg: Color,
#[cfg(feature = "crossterm")]
pub underline_color: Color,
pub modifier: Modifier,
}

Expand Down Expand Up @@ -52,11 +54,25 @@ impl Cell {
if let Some(c) = style.bg {
self.bg = c;
}
#[cfg(feature = "crossterm")]
if let Some(c) = style.underline_color {
self.underline_color = c;
}
self.modifier.insert(style.add_modifier);
self.modifier.remove(style.sub_modifier);
self
}

#[cfg(feature = "crossterm")]
pub fn style(&self) -> Style {
Style::default()
.fg(self.fg)
.bg(self.bg)
.underline_color(self.underline_color)
.add_modifier(self.modifier)
}

#[cfg(not(feature = "crossterm"))]
pub fn style(&self) -> Style {
Style::default()
.fg(self.fg)
Expand All @@ -69,6 +85,10 @@ impl Cell {
self.symbol.push(' ');
self.fg = Color::Reset;
self.bg = Color::Reset;
#[cfg(feature = "crossterm")]
{
self.underline_color = Color::Reset;
}
self.modifier = Modifier::empty();
}
}
Expand All @@ -79,6 +99,8 @@ impl Default for Cell {
symbol: " ".into(),
fg: Color::Reset,
bg: Color::Reset,
#[cfg(feature = "crossterm")]
underline_color: Color::Reset,
modifier: Modifier::empty(),
}
}
Expand Down Expand Up @@ -106,6 +128,8 @@ impl Default for Cell {
/// symbol: String::from("r"),
/// fg: Color::Red,
/// bg: Color::White,
/// #[cfg(feature = "crossterm")]
/// underline_color: Color::Reset,
/// modifier: Modifier::empty()
/// });
/// buf.get_mut(5, 0).set_char('x');
Expand Down Expand Up @@ -559,10 +583,21 @@ impl Debug for Buffer {
overwritten.push((x, &c.symbol));
}
skip = std::cmp::max(skip, c.symbol.width()).saturating_sub(1);
let style = (c.fg, c.bg, c.modifier);
if last_style != Some(style) {
last_style = Some(style);
styles.push((x, y, c.fg, c.bg, c.modifier));
#[cfg(feature = "crossterm")]
{
let style = (c.fg, c.bg, c.underline_color, c.modifier);
if last_style != Some(style) {
last_style = Some(style);
styles.push((x, y, c.fg, c.bg, c.underline_color, c.modifier));
}
}
#[cfg(not(feature = "crossterm"))]
{
let style = (c.fg, c.bg, c.modifier);
if last_style != Some(style) {
last_style = Some(style);
styles.push((x, y, c.fg, c.bg, c.modifier));
}
}
}
if !overwritten.is_empty() {
Expand All @@ -574,6 +609,12 @@ impl Debug for Buffer {
}
f.write_str(" ],\n styles: [\n")?;
for s in styles {
#[cfg(feature = "crossterm")]
f.write_fmt(format_args!(
" x: {}, y: {}, fg: {:?}, bg: {:?}, underline: {:?}, modifier: {:?},\n",
s.0, s.1, s.2, s.3, s.4, s.5
))?;
#[cfg(not(feature = "crossterm"))]
f.write_fmt(format_args!(
" x: {}, y: {}, fg: {:?}, bg: {:?}, modifier: {:?},\n",
s.0, s.1, s.2, s.3, s.4
Expand Down Expand Up @@ -607,6 +648,25 @@ mod tests {
.bg(Color::Yellow)
.add_modifier(Modifier::BOLD),
);
#[cfg(feature = "crossterm")]
assert_eq!(
format!("{buf:?}"),
indoc::indoc!(
"
Buffer {
area: Rect { x: 0, y: 0, width: 12, height: 2 },
content: [
\"Hello World!\",
\"G'day World!\",
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 0, y: 1, fg: Green, bg: Yellow, underline: Reset, modifier: BOLD,
]
}"
)
);
#[cfg(not(feature = "crossterm"))]
assert_eq!(
format!("{buf:?}"),
indoc::indoc!(
Expand Down
42 changes: 40 additions & 2 deletions src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,9 @@ impl fmt::Debug for Modifier {
/// # use ratatui::layout::Rect;
/// let styles = [
/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
/// Style::default().bg(Color::Red),
/// Style::default().bg(Color::Red).add_modifier(Modifier::UNDERLINED),
/// #[cfg(feature = "crossterm")]
/// Style::default().underline_color(Color::Green),
/// Style::default().fg(Color::Yellow).remove_modifier(Modifier::ITALIC),
/// ];
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
Expand All @@ -209,7 +211,9 @@ impl fmt::Debug for Modifier {
/// Style {
/// fg: Some(Color::Yellow),
/// bg: Some(Color::Red),
/// add_modifier: Modifier::BOLD,
/// #[cfg(feature = "crossterm")]
/// underline_color: Some(Color::Green),
/// add_modifier: Modifier::BOLD | Modifier::UNDERLINED,
/// sub_modifier: Modifier::empty(),
/// },
/// buffer.get(0, 0).style(),
Expand All @@ -235,6 +239,8 @@ impl fmt::Debug for Modifier {
/// Style {
/// fg: Some(Color::Yellow),
/// bg: Some(Color::Reset),
/// #[cfg(feature = "crossterm")]
/// underline_color: Some(Color::Reset),
/// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// },
Expand All @@ -246,6 +252,8 @@ impl fmt::Debug for Modifier {
pub struct Style {
pub fg: Option<Color>,
pub bg: Option<Color>,
#[cfg(feature = "crossterm")]
pub underline_color: Option<Color>,
pub add_modifier: Modifier,
pub sub_modifier: Modifier,
}
Expand All @@ -272,6 +280,8 @@ impl Style {
Style {
fg: None,
bg: None,
#[cfg(feature = "crossterm")]
underline_color: None,
add_modifier: Modifier::empty(),
sub_modifier: Modifier::empty(),
}
Expand All @@ -282,6 +292,8 @@ impl Style {
Style {
fg: Some(Color::Reset),
bg: Some(Color::Reset),
#[cfg(feature = "crossterm")]
underline_color: Some(Color::Reset),
add_modifier: Modifier::empty(),
sub_modifier: Modifier::all(),
}
Expand Down Expand Up @@ -317,6 +329,27 @@ impl Style {
self
}

/// Changes the underline color. The text must be underlined with a modifier for this to work.
///
/// This uses a non-standard ANSI escape sequence. It is supported by most terminal emulators,
/// but is only implemented in the crossterm backend.
///
/// See [Wikipedia](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters) code `58` and `59` for more information.
///
/// ## Examples
///
/// ```rust
/// # use ratatui::style::{Color, Modifier, Style};
/// let style = Style::default().underline_color(Color::Blue).add_modifier(Modifier::UNDERLINED);
/// let diff = Style::default().underline_color(Color::Red).add_modifier(Modifier::UNDERLINED);
/// assert_eq!(style.patch(diff), Style::default().underline_color(Color::Red).add_modifier(Modifier::UNDERLINED));
/// ```
#[cfg(feature = "crossterm")]
pub const fn underline_color(mut self, color: Color) -> Style {
self.underline_color = Some(color);
self
}

/// Changes the text emphasis.
///
/// When applied, it adds the given modifier to the `Style` modifiers.
Expand Down Expand Up @@ -374,6 +407,11 @@ impl Style {
self.fg = other.fg.or(self.fg);
self.bg = other.bg.or(self.bg);

#[cfg(feature = "crossterm")]
{
self.underline_color = other.underline_color.or(self.underline_color);
}

self.add_modifier.remove(other.sub_modifier);
self.add_modifier.insert(other.add_modifier);
self.sub_modifier.remove(other.add_modifier);
Expand Down
8 changes: 8 additions & 0 deletions src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ impl<'a> Span<'a> {
/// style: Style {
/// fg: Some(Color::Yellow),
/// bg: Some(Color::Black),
/// #[cfg(feature = "crossterm")]
/// underline_color: None,
/// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// },
Expand All @@ -150,6 +152,8 @@ impl<'a> Span<'a> {
/// style: Style {
/// fg: Some(Color::Yellow),
/// bg: Some(Color::Black),
/// #[cfg(feature = "crossterm")]
/// underline_color: None,
/// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// },
Expand All @@ -159,6 +163,8 @@ impl<'a> Span<'a> {
/// style: Style {
/// fg: Some(Color::Yellow),
/// bg: Some(Color::Black),
/// #[cfg(feature = "crossterm")]
/// underline_color: None,
/// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// },
Expand All @@ -168,6 +174,8 @@ impl<'a> Span<'a> {
/// style: Style {
/// fg: Some(Color::Yellow),
/// bg: Some(Color::Black),
/// #[cfg(feature = "crossterm")]
/// underline_color: None,
/// add_modifier: Modifier::empty(),
/// sub_modifier: Modifier::empty(),
/// },
Expand Down
4 changes: 4 additions & 0 deletions src/widgets/gauge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ impl<'a> Widget for LineGauge<'a> {
.set_style(Style {
fg: self.gauge_style.fg,
bg: None,
#[cfg(feature = "crossterm")]
underline_color: self.gauge_style.underline_color,
add_modifier: self.gauge_style.add_modifier,
sub_modifier: self.gauge_style.sub_modifier,
});
Expand All @@ -289,6 +291,8 @@ impl<'a> Widget for LineGauge<'a> {
.set_style(Style {
fg: self.gauge_style.bg,
bg: None,
#[cfg(feature = "crossterm")]
underline_color: self.gauge_style.underline_color,
add_modifier: self.gauge_style.add_modifier,
sub_modifier: self.gauge_style.sub_modifier,
});
Expand Down

0 comments on commit 9b13cef

Please sign in to comment.