From 3c7328d90121b5024d4ffb7ab54f2cc3a5d8ef94 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Mon, 27 Jan 2020 17:07:29 +0200 Subject: [PATCH] ui: correctly turn on/off terminal attributes in draw_horizontal_segment() `Attr` (terminal attributes such as bold, underline, etc) were not being printed properly: their bitmap representation was printed instead of the correct ANSI codes to turn them on/off. This worked so far because the attributes and {fore,back}ground color was reset in every character print. draw_horizontal_segment() now keeps state of current_{fg,bg,attr} to keep from resetting in each column draw. --- ui/src/state.rs | 49 +++++++++------------ ui/src/terminal/cells.rs | 93 +++++++++++++++++++++++++++------------- 2 files changed, 85 insertions(+), 57 deletions(-) diff --git a/ui/src/state.rs b/ui/src/state.rs index 3436eb58..43303ab8 100644 --- a/ui/src/state.rs +++ b/ui/src/state.rs @@ -491,39 +491,32 @@ impl State { cursor::Goto(x_start as u16 + 1, (y + 1) as u16) ) .unwrap(); + let mut current_fg = Color::Default; + let mut current_bg = Color::Default; + let mut current_attrs = Attr::Default; + let Self { + ref grid, + ref mut stdout, + .. + } = self; + let stdout = stdout.as_mut().unwrap(); + write!(stdout, "\x1B[m").unwrap(); for x in x_start..=x_end { - let c = self.grid[(x, y)]; - if c.bg() != Color::Default { - c.bg().write_bg(self.stdout()).unwrap(); + let c = &grid[(x, y)]; + if c.attrs() != current_attrs { + c.attrs().write(current_attrs, stdout).unwrap(); + current_attrs = c.attrs(); } - if c.fg() != Color::Default { - c.fg().write_fg(self.stdout()).unwrap(); + if c.bg() != current_bg { + c.bg().write_bg(stdout).unwrap(); + current_bg = c.bg(); } - if c.attrs() != Attr::Default { - write!(self.stdout(), "\x1B[{}m", c.attrs() as u8).unwrap(); + if c.fg() != current_fg { + c.fg().write_fg(stdout).unwrap(); + current_fg = c.fg(); } if !c.empty() { - write!(self.stdout(), "{}", c.ch()).unwrap(); - } - - if c.bg() != Color::Default { - write!( - self.stdout(), - "{}", - termion::color::Bg(termion::color::Reset) - ) - .unwrap(); - } - if c.fg() != Color::Default { - write!( - self.stdout(), - "{}", - termion::color::Fg(termion::color::Reset) - ) - .unwrap(); - } - if c.attrs() != Attr::Default { - write!(self.stdout(), "\x1B[{}m", Attr::Default as u8).unwrap(); + write!(stdout, "{}", c.ch()).unwrap(); } } } diff --git a/ui/src/terminal/cells.rs b/ui/src/terminal/cells.rs index 9e6451bf..7777ec2b 100644 --- a/ui/src/terminal/cells.rs +++ b/ui/src/terminal/cells.rs @@ -601,20 +601,23 @@ impl Cell { self.empty } - pub fn set_empty(&mut self, new_val: bool) { + pub fn set_empty(&mut self, new_val: bool) -> &mut Cell { self.empty = new_val; + self } /// Sets `keep_fg` field. If true, the foreground color will not be altered if attempted so /// until the character content of the cell is changed. - pub fn set_keep_fg(&mut self, new_val: bool) { + pub fn set_keep_fg(&mut self, new_val: bool) -> &mut Cell { self.keep_fg = new_val; + self } /// Sets `keep_bg` field. If true, the background color will not be altered if attempted so /// until the character content of the cell is changed. - pub fn set_keep_bg(&mut self, new_val: bool) { + pub fn set_keep_bg(&mut self, new_val: bool) -> &mut Cell { self.keep_bg = new_val; + self } } @@ -711,6 +714,7 @@ impl Color { pub fn write_fg(self, stdout: &mut crate::StateStdout) -> std::io::Result<()> { use std::io::Write; match self { + Color::Default => write!(stdout, "{}", termion::color::Fg(termion::color::Reset)), Color::Rgb(r, g, b) => write!(stdout, "{}", termion::color::Fg(TermionRgb(r, g, b))), _ => write!(stdout, "{}", termion::color::Fg(self.as_termion())), } @@ -719,6 +723,7 @@ impl Color { pub fn write_bg(self, stdout: &mut crate::StateStdout) -> std::io::Result<()> { use std::io::Write; match self { + Color::Default => write!(stdout, "{}", termion::color::Bg(termion::color::Reset)), Color::Rgb(r, g, b) => write!(stdout, "{}", termion::color::Bg(TermionRgb(r, g, b))), _ => write!(stdout, "{}", termion::color::Bg(self.as_termion())), } @@ -1001,6 +1006,7 @@ impl Color { "Yellow4" => 100, "Yellow5" => 106, "Yellow6" => 148, + "Default" => return Ok(Color::Default), s if s.starts_with("#") && s.len() == 7 && s[1..].as_bytes().iter().all(|&b| { @@ -1388,6 +1394,14 @@ pub enum Attr { BoldReverseUnderline = 0b111, } +impl core::ops::BitAnd for Attr { + type Output = bool; + + fn bitand(self, rhs: Self) -> Self::Output { + self as u8 & rhs as u8 > 0 + } +} + impl Default for Attr { fn default() -> Self { Attr::Default @@ -1442,6 +1456,24 @@ impl Attr { _ => Err(de::Error::custom("invalid attr value")), } } + pub fn write(self, prev: Attr, stdout: &mut crate::StateStdout) -> std::io::Result<()> { + use std::io::Write; + match (self & Attr::Bold, prev & Attr::Bold) { + (true, true) | (false, false) => Ok(()), + (false, true) => write!(stdout, "\x1B[22m"), + (true, false) => write!(stdout, "\x1B[1m"), + } + .and_then(|_| match (self & Attr::Underline, prev & Attr::Underline) { + (true, true) | (false, false) => Ok(()), + (false, true) => write!(stdout, "\x1B[24m"), + (true, false) => write!(stdout, "\x1B[4m"), + }) + .and_then(|_| match (self & Attr::Reverse, prev & Attr::Reverse) { + (true, true) | (false, false) => Ok(()), + (false, true) => write!(stdout, "\x1B[27m"), + (true, false) => write!(stdout, "\x1B[7m"), + }) + } } pub fn copy_area_with_break( @@ -1542,26 +1574,27 @@ pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area, /// Change foreground and background colors in an `Area` pub fn change_colors(grid: &mut CellBuffer, area: Area, fg_color: Color, bg_color: Color) { - let bounds = grid.size(); - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - let (x, y) = upper_left; - if y > (get_y(bottom_right)) - || x > get_x(bottom_right) - || y >= get_y(bounds) - || x >= get_x(bounds) - { - debug!("BUG: Invalid area in change_colors:\n area: {:?}", area); - return; + if cfg!(feature = "debug-tracing") { + let bounds = grid.size(); + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + let (x, y) = upper_left; + if y > (get_y(bottom_right)) + || x > get_x(bottom_right) + || y >= get_y(bounds) + || x >= get_x(bounds) + { + debug!("BUG: Invalid area in change_colors:\n area: {:?}", area); + return; + } + if !is_valid_area!(area) { + debug!("BUG: Invalid area in change_colors:\n area: {:?}", area); + return; + } } - if !is_valid_area!(area) { - debug!("BUG: Invalid area in change_colors:\n area: {:?}", area); - return; - } - for y in get_y(upper_left!(area))..=get_y(bottom_right!(area)) { - for x in get_x(upper_left!(area))..=get_x(bottom_right!(area)) { - grid[(x, y)].set_fg(fg_color); - grid[(x, y)].set_bg(bg_color); + for row in grid.bounds_iter(area) { + for c in row { + grid[c].set_fg(fg_color).set_bg(bg_color); } } } @@ -1648,9 +1681,10 @@ pub fn write_string_to_grid( } else { grid[(x, y)].set_ch(c); } - grid[(x, y)].set_attrs(attrs); - grid[(x, y)].set_fg(fg_color); - grid[(x, y)].set_bg(bg_color); + grid[(x, y)] + .set_fg(fg_color) + .set_bg(bg_color) + .set_attrs(attrs); match wcwidth(u32::from(c)) { Some(0) | None => { @@ -1663,10 +1697,11 @@ pub fn write_string_to_grid( x += 1; inspect_bounds!(grid, area, x, y, line_break); grid[(x, y)] = Cell::default(); - grid[(x, y)].set_attrs(attrs); - grid[(x, y)].set_fg(fg_color); - grid[(x, y)].set_bg(bg_color); - grid[(x, y)].empty = true; + grid[(x, y)] + .set_fg(fg_color) + .set_bg(bg_color) + .set_attrs(attrs) + .set_empty(true); } _ => {} }