use std::io; use crate::backend::Backend; use crate::buffer::Cell; use crate::layout::Rect; use crate::style::{Color, Modifier}; use crate::symbols::{bar, block}; #[cfg(unix)] use crate::symbols::{line, DOT}; #[cfg(unix)] use pancurses::{chtype, ToChtype}; use unicode_segmentation::UnicodeSegmentation; pub struct CursesBackend { curses: easycurses::EasyCurses, } impl CursesBackend { pub fn new() -> Option { let curses = easycurses::EasyCurses::initialize_system()?; Some(CursesBackend { curses }) } pub fn with_curses(curses: easycurses::EasyCurses) -> CursesBackend { CursesBackend { curses } } pub fn get_curses(&self) -> &easycurses::EasyCurses { &self.curses } pub fn get_curses_mut(&mut self) -> &mut easycurses::EasyCurses { &mut self.curses } } impl Backend for CursesBackend { fn draw<'a, I>(&mut self, content: I) -> io::Result<()> where I: Iterator, { let mut last_col = 0; let mut last_row = 0; let mut fg = Color::Reset; let mut bg = Color::Reset; let mut modifier = Modifier::empty(); let mut curses_style = CursesStyle { fg: easycurses::Color::White, bg: easycurses::Color::Black, }; let mut update_color = false; for (col, row, cell) in content { if row != last_row || col != last_col + 1 { self.curses.move_rc(i32::from(row), i32::from(col)); } last_col = col; last_row = row; if cell.modifier != modifier { apply_modifier_diff(&mut self.curses.win, modifier, cell.modifier); modifier = cell.modifier; }; if cell.fg != fg { update_color = true; if let Some(ccolor) = cell.fg.into() { fg = cell.fg; curses_style.fg = ccolor; } else { fg = Color::White; curses_style.fg = easycurses::Color::White; } }; if cell.bg != bg { update_color = true; if let Some(ccolor) = cell.bg.into() { bg = cell.bg; curses_style.bg = ccolor; } else { bg = Color::Black; curses_style.bg = easycurses::Color::Black; } }; if update_color { self.curses .set_color_pair(easycurses::ColorPair::new(curses_style.fg, curses_style.bg)); }; update_color = false; draw(&mut self.curses, cell.symbol.as_str()); } self.curses.win.attrset(pancurses::Attribute::Normal); self.curses.set_color_pair(easycurses::ColorPair::new( easycurses::Color::White, easycurses::Color::Black, )); Ok(()) } fn hide_cursor(&mut self) -> io::Result<()> { self.curses .set_cursor_visibility(easycurses::CursorVisibility::Invisible); Ok(()) } fn show_cursor(&mut self) -> io::Result<()> { self.curses .set_cursor_visibility(easycurses::CursorVisibility::Visible); Ok(()) } fn get_cursor(&mut self) -> io::Result<(u16, u16)> { let (y, x) = self.curses.get_cursor_rc(); Ok((x as u16, y as u16)) } fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> { self.curses.move_rc(i32::from(y), i32::from(x)); Ok(()) } fn clear(&mut self) -> io::Result<()> { self.curses.clear(); // self.curses.refresh(); Ok(()) } fn size(&self) -> Result { let (nrows, ncols) = self.curses.get_row_col_count(); Ok(Rect::new(0, 0, ncols as u16, nrows as u16)) } fn flush(&mut self) -> io::Result<()> { self.curses.refresh(); Ok(()) } } struct CursesStyle { fg: easycurses::Color, bg: easycurses::Color, } #[cfg(unix)] /// Deals with lack of unicode support for ncurses on unix fn draw(curses: &mut easycurses::EasyCurses, symbol: &str) { for grapheme in symbol.graphemes(true) { let ch = match grapheme { line::TOP_RIGHT => pancurses::ACS_URCORNER(), line::VERTICAL => pancurses::ACS_VLINE(), line::HORIZONTAL => pancurses::ACS_HLINE(), line::TOP_LEFT => pancurses::ACS_ULCORNER(), line::BOTTOM_RIGHT => pancurses::ACS_LRCORNER(), line::BOTTOM_LEFT => pancurses::ACS_LLCORNER(), line::VERTICAL_LEFT => pancurses::ACS_RTEE(), line::VERTICAL_RIGHT => pancurses::ACS_LTEE(), line::HORIZONTAL_DOWN => pancurses::ACS_TTEE(), line::HORIZONTAL_UP => pancurses::ACS_BTEE(), block::FULL => pancurses::ACS_BLOCK(), block::SEVEN_EIGHTHS => pancurses::ACS_BLOCK(), block::THREE_QUARTERS => pancurses::ACS_BLOCK(), block::FIVE_EIGHTHS => pancurses::ACS_BLOCK(), block::HALF => pancurses::ACS_BLOCK(), block::THREE_EIGHTHS => ' ' as chtype, block::ONE_QUARTER => ' ' as chtype, block::ONE_EIGHTH => ' ' as chtype, bar::SEVEN_EIGHTHS => pancurses::ACS_BLOCK(), bar::THREE_QUARTERS => pancurses::ACS_BLOCK(), bar::FIVE_EIGHTHS => pancurses::ACS_BLOCK(), bar::HALF => pancurses::ACS_BLOCK(), bar::THREE_EIGHTHS => pancurses::ACS_S9(), bar::ONE_QUARTER => pancurses::ACS_S9(), bar::ONE_EIGHTH => pancurses::ACS_S9(), DOT => pancurses::ACS_BULLET(), unicode_char => { if unicode_char.is_ascii() { let mut chars = unicode_char.chars(); if let Some(ch) = chars.next() { ch.to_chtype() } else { pancurses::ACS_BLOCK() } } else { pancurses::ACS_BLOCK() } } }; curses.win.addch(ch); } } #[cfg(windows)] fn draw(curses: &mut easycurses::EasyCurses, symbol: &str) { for grapheme in symbol.graphemes(true) { let ch = match grapheme { block::SEVEN_EIGHTHS => block::FULL, block::THREE_QUARTERS => block::FULL, block::FIVE_EIGHTHS => block::HALF, block::THREE_EIGHTHS => block::HALF, block::ONE_QUARTER => block::HALF, block::ONE_EIGHTH => " ", bar::SEVEN_EIGHTHS => bar::FULL, bar::THREE_QUARTERS => bar::FULL, bar::FIVE_EIGHTHS => bar::HALF, bar::THREE_EIGHTHS => bar::HALF, bar::ONE_QUARTER => bar::HALF, bar::ONE_EIGHTH => " ", ch => ch, }; // curses.win.addch(ch); curses.print(ch); } } impl From for Option { fn from(color: Color) -> Option { match color { Color::Reset => None, Color::Black => Some(easycurses::Color::Black), Color::Red | Color::LightRed => Some(easycurses::Color::Red), Color::Green | Color::LightGreen => Some(easycurses::Color::Green), Color::Yellow | Color::LightYellow => Some(easycurses::Color::Yellow), Color::Magenta | Color::LightMagenta => Some(easycurses::Color::Magenta), Color::Cyan | Color::LightCyan => Some(easycurses::Color::Cyan), Color::White | Color::Gray | Color::DarkGray => Some(easycurses::Color::White), Color::Blue | Color::LightBlue => Some(easycurses::Color::Blue), Color::Indexed(_) => None, Color::Rgb(_, _, _) => None, } } } fn apply_modifier_diff(win: &mut pancurses::Window, from: Modifier, to: Modifier) { remove_modifier(win, from - to); add_modifier(win, to - from); } fn remove_modifier(win: &mut pancurses::Window, remove: Modifier) { if remove.contains(Modifier::BOLD) { win.attroff(pancurses::Attribute::Bold); } if remove.contains(Modifier::DIM) { win.attroff(pancurses::Attribute::Dim); } if remove.contains(Modifier::ITALIC) { win.attroff(pancurses::Attribute::Italic); } if remove.contains(Modifier::UNDERLINED) { win.attroff(pancurses::Attribute::Underline); } if remove.contains(Modifier::SLOW_BLINK) || remove.contains(Modifier::RAPID_BLINK) { win.attroff(pancurses::Attribute::Blink); } if remove.contains(Modifier::REVERSED) { win.attroff(pancurses::Attribute::Reverse); } if remove.contains(Modifier::HIDDEN) { win.attroff(pancurses::Attribute::Invisible); } if remove.contains(Modifier::CROSSED_OUT) { win.attroff(pancurses::Attribute::Strikeout); } } fn add_modifier(win: &mut pancurses::Window, add: Modifier) { if add.contains(Modifier::BOLD) { win.attron(pancurses::Attribute::Bold); } if add.contains(Modifier::DIM) { win.attron(pancurses::Attribute::Dim); } if add.contains(Modifier::ITALIC) { win.attron(pancurses::Attribute::Italic); } if add.contains(Modifier::UNDERLINED) { win.attron(pancurses::Attribute::Underline); } if add.contains(Modifier::SLOW_BLINK) || add.contains(Modifier::RAPID_BLINK) { win.attron(pancurses::Attribute::Blink); } if add.contains(Modifier::REVERSED) { win.attron(pancurses::Attribute::Reverse); } if add.contains(Modifier::HIDDEN) { win.attron(pancurses::Attribute::Invisible); } if add.contains(Modifier::CROSSED_OUT) { win.attron(pancurses::Attribute::Strikeout); } }