use crate::{ backend::Backend, buffer::Cell, layout::Rect, style::{Color, Modifier}, terminal::CursorKind, }; use crossterm::{ cursor::{CursorShape, Hide, MoveTo, SetCursorShape, Show}, execute, queue, style::{ Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor, SetForegroundColor, }, terminal::{self, Clear, ClearType}, }; use std::io::{self, Write}; pub struct CrosstermBackend { buffer: W, } impl CrosstermBackend where W: Write, { pub fn new(buffer: W) -> CrosstermBackend { CrosstermBackend { buffer } } } impl Write for CrosstermBackend where W: Write, { fn write(&mut self, buf: &[u8]) -> io::Result { self.buffer.write(buf) } fn flush(&mut self) -> io::Result<()> { self.buffer.flush() } } impl Backend for CrosstermBackend where W: Write, { fn draw<'a, I>(&mut self, content: I) -> io::Result<()> where I: Iterator, { let mut fg = Color::Reset; let mut bg = Color::Reset; let mut modifier = Modifier::empty(); let mut last_pos: Option<(u16, u16)> = None; for (x, y, cell) in content { // Move the cursor if the previous location was not (x - 1, y) if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) { map_error(queue!(self.buffer, MoveTo(x, y)))?; } last_pos = Some((x, y)); if cell.modifier != modifier { let diff = ModifierDiff { from: modifier, to: cell.modifier, }; diff.queue(&mut self.buffer)?; modifier = cell.modifier; } if cell.fg != fg { let color = CColor::from(cell.fg); map_error(queue!(self.buffer, SetForegroundColor(color)))?; fg = cell.fg; } if cell.bg != bg { let color = CColor::from(cell.bg); map_error(queue!(self.buffer, SetBackgroundColor(color)))?; bg = cell.bg; } map_error(queue!(self.buffer, Print(&cell.symbol)))?; } map_error(queue!( self.buffer, SetForegroundColor(CColor::Reset), SetBackgroundColor(CColor::Reset), SetAttribute(CAttribute::Reset) )) } fn hide_cursor(&mut self) -> io::Result<()> { map_error(execute!(self.buffer, Hide)) } fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> { let shape = match kind { CursorKind::Block => CursorShape::Block, CursorKind::Bar => CursorShape::Line, CursorKind::Underline => CursorShape::UnderScore, CursorKind::Hidden => unreachable!(), }; map_error(execute!(self.buffer, Show, SetCursorShape(shape))) } fn get_cursor(&mut self) -> io::Result<(u16, u16)> { crossterm::cursor::position() .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string())) } fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> { map_error(execute!(self.buffer, MoveTo(x, y))) } fn clear(&mut self) -> io::Result<()> { map_error(execute!(self.buffer, Clear(ClearType::All))) } fn size(&self) -> io::Result { let (width, height) = terminal::size().map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; Ok(Rect::new(0, 0, width, height)) } fn flush(&mut self) -> io::Result<()> { self.buffer.flush() } } fn map_error(error: crossterm::Result<()>) -> io::Result<()> { error.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string())) } impl From for CColor { fn from(color: Color) -> Self { match color { Color::Reset => CColor::Reset, Color::Black => CColor::Black, Color::Red => CColor::DarkRed, Color::Green => CColor::DarkGreen, Color::Yellow => CColor::DarkYellow, Color::Blue => CColor::DarkBlue, Color::Magenta => CColor::DarkMagenta, Color::Cyan => CColor::DarkCyan, Color::Gray => CColor::Grey, Color::DarkGray => CColor::DarkGrey, Color::LightRed => CColor::Red, Color::LightGreen => CColor::Green, Color::LightBlue => CColor::Blue, Color::LightYellow => CColor::Yellow, Color::LightMagenta => CColor::Magenta, Color::LightCyan => CColor::Cyan, Color::White => CColor::White, Color::Indexed(i) => CColor::AnsiValue(i), Color::Rgb(r, g, b) => CColor::Rgb { r, g, b }, } } } #[derive(Debug)] struct ModifierDiff { pub from: Modifier, pub to: Modifier, } impl ModifierDiff { fn queue(&self, mut w: W) -> io::Result<()> where W: io::Write, { //use crossterm::Attribute; let removed = self.from - self.to; if removed.contains(Modifier::REVERSED) { map_error(queue!(w, SetAttribute(CAttribute::NoReverse)))?; } if removed.contains(Modifier::BOLD) { map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?; if self.to.contains(Modifier::DIM) { map_error(queue!(w, SetAttribute(CAttribute::Dim)))?; } } if removed.contains(Modifier::ITALIC) { map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?; } if removed.contains(Modifier::UNDERLINED) { map_error(queue!(w, SetAttribute(CAttribute::NoUnderline)))?; } if removed.contains(Modifier::DIM) { map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?; } if removed.contains(Modifier::CROSSED_OUT) { map_error(queue!(w, SetAttribute(CAttribute::NotCrossedOut)))?; } if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) { map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?; } let added = self.to - self.from; if added.contains(Modifier::REVERSED) { map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?; } if added.contains(Modifier::BOLD) { map_error(queue!(w, SetAttribute(CAttribute::Bold)))?; } if added.contains(Modifier::ITALIC) { map_error(queue!(w, SetAttribute(CAttribute::Italic)))?; } if added.contains(Modifier::UNDERLINED) { map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?; } if added.contains(Modifier::DIM) { map_error(queue!(w, SetAttribute(CAttribute::Dim)))?; } if added.contains(Modifier::CROSSED_OUT) { map_error(queue!(w, SetAttribute(CAttribute::CrossedOut)))?; } if added.contains(Modifier::SLOW_BLINK) { map_error(queue!(w, SetAttribute(CAttribute::SlowBlink)))?; } if added.contains(Modifier::RAPID_BLINK) { map_error(queue!(w, SetAttribute(CAttribute::RapidBlink)))?; } Ok(()) } }