1 use std::io;
2 
3 use crate::backend::Backend;
4 use crate::buffer::Cell;
5 use crate::layout::Rect;
6 use crate::style::{Color, Modifier, Style};
7 use crate::symbols::{bar, block};
8 #[cfg(unix)]
9 use crate::symbols::{line, DOT};
10 #[cfg(unix)]
11 use pancurses::{chtype, ToChtype};
12 use unicode_segmentation::UnicodeSegmentation;
13 
14 pub struct CursesBackend {
15     curses: easycurses::EasyCurses,
16 }
17 
18 impl CursesBackend {
new() -> Option<CursesBackend>19     pub fn new() -> Option<CursesBackend> {
20         let curses = easycurses::EasyCurses::initialize_system()?;
21         Some(CursesBackend { curses })
22     }
23 
with_curses(curses: easycurses::EasyCurses) -> CursesBackend24     pub fn with_curses(curses: easycurses::EasyCurses) -> CursesBackend {
25         CursesBackend { curses }
26     }
27 
get_curses(&self) -> &easycurses::EasyCurses28     pub fn get_curses(&self) -> &easycurses::EasyCurses {
29         &self.curses
30     }
31 
get_curses_mut(&mut self) -> &mut easycurses::EasyCurses32     pub fn get_curses_mut(&mut self) -> &mut easycurses::EasyCurses {
33         &mut self.curses
34     }
35 }
36 
37 impl Backend for CursesBackend {
draw<'a, I>(&mut self, content: I) -> io::Result<()> where I: Iterator<Item = (u16, u16, &'a Cell)>,38     fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
39     where
40         I: Iterator<Item = (u16, u16, &'a Cell)>,
41     {
42         let mut last_col = 0;
43         let mut last_row = 0;
44         let mut style = Style {
45             fg: Color::Reset,
46             bg: Color::Reset,
47             modifier: Modifier::empty(),
48         };
49         let mut curses_style = CursesStyle {
50             fg: easycurses::Color::White,
51             bg: easycurses::Color::Black,
52         };
53         let mut update_color = false;
54         for (col, row, cell) in content {
55             // eprintln!("{:?}", cell);
56             if row != last_row || col != last_col + 1 {
57                 self.curses.move_rc(i32::from(row), i32::from(col));
58             }
59             last_col = col;
60             last_row = row;
61             if cell.style.modifier != style.modifier {
62                 apply_modifier_diff(&mut self.curses.win, style.modifier, cell.style.modifier);
63                 style.modifier = cell.style.modifier;
64             };
65             if cell.style.fg != style.fg {
66                 update_color = true;
67                 if let Some(ccolor) = cell.style.fg.into() {
68                     style.fg = cell.style.fg;
69                     curses_style.fg = ccolor;
70                 } else {
71                     style.fg = Color::White;
72                     curses_style.fg = easycurses::Color::White;
73                 }
74             };
75             if cell.style.bg != style.bg {
76                 update_color = true;
77                 if let Some(ccolor) = cell.style.bg.into() {
78                     style.bg = cell.style.bg;
79                     curses_style.bg = ccolor;
80                 } else {
81                     style.bg = Color::Black;
82                     curses_style.bg = easycurses::Color::Black;
83                 }
84             };
85             if update_color {
86                 self.curses
87                     .set_color_pair(easycurses::ColorPair::new(curses_style.fg, curses_style.bg));
88             };
89             update_color = false;
90             draw(&mut self.curses, cell.symbol.as_str());
91         }
92         self.curses.win.attrset(pancurses::Attribute::Normal);
93         self.curses.set_color_pair(easycurses::ColorPair::new(
94             easycurses::Color::White,
95             easycurses::Color::Black,
96         ));
97         Ok(())
98     }
hide_cursor(&mut self) -> io::Result<()>99     fn hide_cursor(&mut self) -> io::Result<()> {
100         self.curses
101             .set_cursor_visibility(easycurses::CursorVisibility::Invisible);
102         Ok(())
103     }
show_cursor(&mut self) -> io::Result<()>104     fn show_cursor(&mut self) -> io::Result<()> {
105         self.curses
106             .set_cursor_visibility(easycurses::CursorVisibility::Visible);
107         Ok(())
108     }
get_cursor(&mut self) -> io::Result<(u16, u16)>109     fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
110         let (y, x) = self.curses.get_cursor_rc();
111         Ok((x as u16, y as u16))
112     }
set_cursor(&mut self, x: u16, y: u16) -> io::Result<()>113     fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
114         self.curses.move_rc(i32::from(y), i32::from(x));
115         Ok(())
116     }
clear(&mut self) -> io::Result<()>117     fn clear(&mut self) -> io::Result<()> {
118         self.curses.clear();
119         // self.curses.refresh();
120         Ok(())
121     }
size(&self) -> Result<Rect, io::Error>122     fn size(&self) -> Result<Rect, io::Error> {
123         let (nrows, ncols) = self.curses.get_row_col_count();
124         Ok(Rect::new(0, 0, ncols as u16, nrows as u16))
125     }
flush(&mut self) -> io::Result<()>126     fn flush(&mut self) -> io::Result<()> {
127         self.curses.refresh();
128         Ok(())
129     }
130 }
131 
132 struct CursesStyle {
133     fg: easycurses::Color,
134     bg: easycurses::Color,
135 }
136 
137 #[cfg(unix)]
138 /// Deals with lack of unicode support for ncurses on unix
draw(curses: &mut easycurses::EasyCurses, symbol: &str)139 fn draw(curses: &mut easycurses::EasyCurses, symbol: &str) {
140     for grapheme in symbol.graphemes(true) {
141         let ch = match grapheme {
142             line::TOP_RIGHT => pancurses::ACS_URCORNER(),
143             line::VERTICAL => pancurses::ACS_VLINE(),
144             line::HORIZONTAL => pancurses::ACS_HLINE(),
145             line::TOP_LEFT => pancurses::ACS_ULCORNER(),
146             line::BOTTOM_RIGHT => pancurses::ACS_LRCORNER(),
147             line::BOTTOM_LEFT => pancurses::ACS_LLCORNER(),
148             line::VERTICAL_LEFT => pancurses::ACS_RTEE(),
149             line::VERTICAL_RIGHT => pancurses::ACS_LTEE(),
150             line::HORIZONTAL_DOWN => pancurses::ACS_TTEE(),
151             line::HORIZONTAL_UP => pancurses::ACS_BTEE(),
152             block::FULL => pancurses::ACS_BLOCK(),
153             block::SEVEN_EIGHTHS => pancurses::ACS_BLOCK(),
154             block::THREE_QUARTERS => pancurses::ACS_BLOCK(),
155             block::FIVE_EIGHTHS => pancurses::ACS_BLOCK(),
156             block::HALF => pancurses::ACS_BLOCK(),
157             block::THREE_EIGHTHS => ' ' as chtype,
158             block::ONE_QUARTER => ' ' as chtype,
159             block::ONE_EIGHTH => ' ' as chtype,
160             bar::SEVEN_EIGHTHS => pancurses::ACS_BLOCK(),
161             bar::THREE_QUARTERS => pancurses::ACS_BLOCK(),
162             bar::FIVE_EIGHTHS => pancurses::ACS_BLOCK(),
163             bar::HALF => pancurses::ACS_BLOCK(),
164             bar::THREE_EIGHTHS => pancurses::ACS_S9(),
165             bar::ONE_QUARTER => pancurses::ACS_S9(),
166             bar::ONE_EIGHTH => pancurses::ACS_S9(),
167             DOT => pancurses::ACS_BULLET(),
168             unicode_char => {
169                 if unicode_char.is_ascii() {
170                     let mut chars = unicode_char.chars();
171                     if let Some(ch) = chars.next() {
172                         ch.to_chtype()
173                     } else {
174                         pancurses::ACS_BLOCK()
175                     }
176                 } else {
177                     pancurses::ACS_BLOCK()
178                 }
179             }
180         };
181         curses.win.addch(ch);
182     }
183 }
184 
185 #[cfg(windows)]
draw(curses: &mut easycurses::EasyCurses, symbol: &str)186 fn draw(curses: &mut easycurses::EasyCurses, symbol: &str) {
187     for grapheme in symbol.graphemes(true) {
188         let ch = match grapheme {
189             block::SEVEN_EIGHTHS => block::FULL,
190             block::THREE_QUARTERS => block::FULL,
191             block::FIVE_EIGHTHS => block::HALF,
192             block::THREE_EIGHTHS => block::HALF,
193             block::ONE_QUARTER => block::HALF,
194             block::ONE_EIGHTH => " ",
195             bar::SEVEN_EIGHTHS => bar::FULL,
196             bar::THREE_QUARTERS => bar::FULL,
197             bar::FIVE_EIGHTHS => bar::HALF,
198             bar::THREE_EIGHTHS => bar::HALF,
199             bar::ONE_QUARTER => bar::HALF,
200             bar::ONE_EIGHTH => " ",
201             ch => ch,
202         };
203         // curses.win.addch(ch);
204         curses.print(ch);
205     }
206 }
207 
208 impl From<Color> for Option<easycurses::Color> {
from(color: Color) -> Option<easycurses::Color>209     fn from(color: Color) -> Option<easycurses::Color> {
210         match color {
211             Color::Reset => None,
212             Color::Black => Some(easycurses::Color::Black),
213             Color::Red | Color::LightRed => Some(easycurses::Color::Red),
214             Color::Green | Color::LightGreen => Some(easycurses::Color::Green),
215             Color::Yellow | Color::LightYellow => Some(easycurses::Color::Yellow),
216             Color::Magenta | Color::LightMagenta => Some(easycurses::Color::Magenta),
217             Color::Cyan | Color::LightCyan => Some(easycurses::Color::Cyan),
218             Color::White | Color::Gray | Color::DarkGray => Some(easycurses::Color::White),
219             Color::Blue | Color::LightBlue => Some(easycurses::Color::Blue),
220             Color::Indexed(_) => None,
221             Color::Rgb(_, _, _) => None,
222         }
223     }
224 }
225 
apply_modifier_diff(win: &mut pancurses::Window, from: Modifier, to: Modifier)226 fn apply_modifier_diff(win: &mut pancurses::Window, from: Modifier, to: Modifier) {
227     remove_modifier(win, from - to);
228     add_modifier(win, to - from);
229 }
230 
remove_modifier(win: &mut pancurses::Window, remove: Modifier)231 fn remove_modifier(win: &mut pancurses::Window, remove: Modifier) {
232     if remove.contains(Modifier::BOLD) {
233         win.attroff(pancurses::Attribute::Bold);
234     }
235     if remove.contains(Modifier::DIM) {
236         win.attroff(pancurses::Attribute::Dim);
237     }
238     if remove.contains(Modifier::ITALIC) {
239         win.attroff(pancurses::Attribute::Italic);
240     }
241     if remove.contains(Modifier::UNDERLINED) {
242         win.attroff(pancurses::Attribute::Underline);
243     }
244     if remove.contains(Modifier::SLOW_BLINK) || remove.contains(Modifier::RAPID_BLINK) {
245         win.attroff(pancurses::Attribute::Blink);
246     }
247     if remove.contains(Modifier::REVERSED) {
248         win.attroff(pancurses::Attribute::Reverse);
249     }
250     if remove.contains(Modifier::HIDDEN) {
251         win.attroff(pancurses::Attribute::Invisible);
252     }
253     if remove.contains(Modifier::CROSSED_OUT) {
254         win.attroff(pancurses::Attribute::Strikeout);
255     }
256 }
257 
add_modifier(win: &mut pancurses::Window, add: Modifier)258 fn add_modifier(win: &mut pancurses::Window, add: Modifier) {
259     if add.contains(Modifier::BOLD) {
260         win.attron(pancurses::Attribute::Bold);
261     }
262     if add.contains(Modifier::DIM) {
263         win.attron(pancurses::Attribute::Dim);
264     }
265     if add.contains(Modifier::ITALIC) {
266         win.attron(pancurses::Attribute::Italic);
267     }
268     if add.contains(Modifier::UNDERLINED) {
269         win.attron(pancurses::Attribute::Underline);
270     }
271     if add.contains(Modifier::SLOW_BLINK) || add.contains(Modifier::RAPID_BLINK) {
272         win.attron(pancurses::Attribute::Blink);
273     }
274     if add.contains(Modifier::REVERSED) {
275         win.attron(pancurses::Attribute::Reverse);
276     }
277     if add.contains(Modifier::HIDDEN) {
278         win.attron(pancurses::Attribute::Invisible);
279     }
280     if add.contains(Modifier::CROSSED_OUT) {
281         win.attron(pancurses::Attribute::Strikeout);
282     }
283 }
284