1use log::debug;
2use std::fmt;
3use std::io;
4use std::io::Write;
5
6use super::Backend;
7use crate::buffer::Cell;
8use crate::layout::Rect;
9use crate::style;
10
11pub struct TermionBackend<W>
12where
13 W: Write,
14{
15 stdout: W,
16}
17
18impl<W> TermionBackend<W>
19where
20 W: Write,
21{
22 pub fn new(stdout: W) -> TermionBackend<W> {
23 TermionBackend { stdout }
24 }
25}
26
27impl<W> Write for TermionBackend<W>
28where
29 W: Write,
30{
31 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
32 self.stdout.write(buf)
33 }
34
35 fn flush(&mut self) -> io::Result<()> {
36 self.stdout.flush()
37 }
38}
39
40impl<W> Backend for TermionBackend<W>
41where
42 W: Write,
43{
44 fn clear(&mut self) -> io::Result<()> {
46 write!(self.stdout, "{}", termion::clear::All)?;
47 write!(self.stdout, "{}", termion::cursor::Goto(1, 1))?;
48 self.stdout.flush()
49 }
50
51 fn hide_cursor(&mut self) -> io::Result<()> {
53 write!(self.stdout, "{}", termion::cursor::Hide)?;
54 self.stdout.flush()
55 }
56
57 fn show_cursor(&mut self) -> io::Result<()> {
59 write!(self.stdout, "{}", termion::cursor::Show)?;
60 self.stdout.flush()
61 }
62
63 fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
65 termion::cursor::DetectCursorPos::cursor_pos(&mut self.stdout).map(|(x, y)| (x - 1, y - 1))
66 }
67
68 fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
70 write!(self.stdout, "{}", termion::cursor::Goto(x + 1, y + 1))?;
71 self.stdout.flush()
72 }
73
74 fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
75 where
76 I: Iterator<Item = (u16, u16, &'a Cell)>,
77 {
78 use std::fmt::Write;
79
80 let mut string = String::with_capacity(content.size_hint().0 * 3);
81 let mut style = style::Style::default();
82 let mut last_y = 0;
83 let mut last_x = 0;
84 let mut inst = 0;
85 for (x, y, cell) in content {
86 if y != last_y || x != last_x + 1 || inst == 0 {
87 write!(string, "{}", termion::cursor::Goto(x + 1, y + 1)).unwrap();
88 inst += 1;
89 }
90 last_x = x;
91 last_y = y;
92 if cell.style.modifier != style.modifier {
93 write!(
94 string,
95 "{}",
96 ModifierDiff {
97 from: style.modifier,
98 to: cell.style.modifier
99 }
100 )
101 .unwrap();
102 style.modifier = cell.style.modifier;
103 inst += 1;
104 }
105 if cell.style.fg != style.fg {
106 write!(string, "{}", Fg(cell.style.fg)).unwrap();
107 style.fg = cell.style.fg;
108 inst += 1;
109 }
110 if cell.style.bg != style.bg {
111 write!(string, "{}", Bg(cell.style.bg)).unwrap();
112 style.bg = cell.style.bg;
113 inst += 1;
114 }
115 string.push_str(&cell.symbol);
116 inst += 1;
117 }
118 debug!("{} instructions outputed.", inst);
119 write!(
120 self.stdout,
121 "{}{}{}{}",
122 string,
123 Fg(style::Color::Reset),
124 Bg(style::Color::Reset),
125 termion::style::Reset,
126 )
127 }
128
129 fn size(&self) -> io::Result<Rect> {
131 let terminal = termion::terminal_size()?;
132 Ok(Rect::new(0, 0, terminal.0, terminal.1))
133 }
134
135 fn flush(&mut self) -> io::Result<()> {
136 self.stdout.flush()
137 }
138}
139
140struct Fg(style::Color);
141
142struct Bg(style::Color);
143
144struct ModifierDiff {
145 from: style::Modifier,
146 to: style::Modifier,
147}
148
149impl fmt::Display for Fg {
150 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151 use termion::color::Color;
152 match self.0 {
153 style::Color::Reset => termion::color::Reset.write_fg(f),
154 style::Color::Black => termion::color::Black.write_fg(f),
155 style::Color::Red => termion::color::Red.write_fg(f),
156 style::Color::Green => termion::color::Green.write_fg(f),
157 style::Color::Yellow => termion::color::Yellow.write_fg(f),
158 style::Color::Blue => termion::color::Blue.write_fg(f),
159 style::Color::Magenta => termion::color::Magenta.write_fg(f),
160 style::Color::Cyan => termion::color::Cyan.write_fg(f),
161 style::Color::Gray => termion::color::White.write_fg(f),
162 style::Color::DarkGray => termion::color::LightBlack.write_fg(f),
163 style::Color::LightRed => termion::color::LightRed.write_fg(f),
164 style::Color::LightGreen => termion::color::LightGreen.write_fg(f),
165 style::Color::LightBlue => termion::color::LightBlue.write_fg(f),
166 style::Color::LightYellow => termion::color::LightYellow.write_fg(f),
167 style::Color::LightMagenta => termion::color::LightMagenta.write_fg(f),
168 style::Color::LightCyan => termion::color::LightCyan.write_fg(f),
169 style::Color::White => termion::color::LightWhite.write_fg(f),
170 style::Color::Indexed(i) => termion::color::AnsiValue(i).write_fg(f),
171 style::Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_fg(f),
172 }
173 }
174}
175impl fmt::Display for Bg {
176 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177 use termion::color::Color;
178 match self.0 {
179 style::Color::Reset => termion::color::Reset.write_bg(f),
180 style::Color::Black => termion::color::Black.write_bg(f),
181 style::Color::Red => termion::color::Red.write_bg(f),
182 style::Color::Green => termion::color::Green.write_bg(f),
183 style::Color::Yellow => termion::color::Yellow.write_bg(f),
184 style::Color::Blue => termion::color::Blue.write_bg(f),
185 style::Color::Magenta => termion::color::Magenta.write_bg(f),
186 style::Color::Cyan => termion::color::Cyan.write_bg(f),
187 style::Color::Gray => termion::color::White.write_bg(f),
188 style::Color::DarkGray => termion::color::LightBlack.write_bg(f),
189 style::Color::LightRed => termion::color::LightRed.write_bg(f),
190 style::Color::LightGreen => termion::color::LightGreen.write_bg(f),
191 style::Color::LightBlue => termion::color::LightBlue.write_bg(f),
192 style::Color::LightYellow => termion::color::LightYellow.write_bg(f),
193 style::Color::LightMagenta => termion::color::LightMagenta.write_bg(f),
194 style::Color::LightCyan => termion::color::LightCyan.write_bg(f),
195 style::Color::White => termion::color::LightWhite.write_bg(f),
196 style::Color::Indexed(i) => termion::color::AnsiValue(i).write_bg(f),
197 style::Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_bg(f),
198 }
199 }
200}
201
202impl fmt::Display for ModifierDiff {
203 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
204 let remove = self.from - self.to;
205 if remove.contains(style::Modifier::REVERSED) {
206 write!(f, "{}", termion::style::NoInvert)?;
207 }
208 if remove.contains(style::Modifier::BOLD) {
209 write!(f, "{}", termion::style::NoFaint)?;
213
214 if self.to.contains(style::Modifier::DIM) {
215 write!(f, "{}", termion::style::Faint)?;
216 }
217 }
218 if remove.contains(style::Modifier::ITALIC) {
219 write!(f, "{}", termion::style::NoItalic)?;
220 }
221 if remove.contains(style::Modifier::UNDERLINED) {
222 write!(f, "{}", termion::style::NoUnderline)?;
223 }
224 if remove.contains(style::Modifier::DIM) {
225 write!(f, "{}", termion::style::NoFaint)?;
226
227 if self.to.contains(style::Modifier::BOLD) {
230 write!(f, "{}", termion::style::Bold)?;
231 }
232 }
233 if remove.contains(style::Modifier::CROSSED_OUT) {
234 write!(f, "{}", termion::style::NoCrossedOut)?;
235 }
236 if remove.contains(style::Modifier::SLOW_BLINK)
237 || remove.contains(style::Modifier::RAPID_BLINK)
238 {
239 write!(f, "{}", termion::style::NoBlink)?;
240 }
241
242 let add = self.to - self.from;
243 if add.contains(style::Modifier::REVERSED) {
244 write!(f, "{}", termion::style::Invert)?;
245 }
246 if add.contains(style::Modifier::BOLD) {
247 write!(f, "{}", termion::style::Bold)?;
248 }
249 if add.contains(style::Modifier::ITALIC) {
250 write!(f, "{}", termion::style::Italic)?;
251 }
252 if add.contains(style::Modifier::UNDERLINED) {
253 write!(f, "{}", termion::style::Underline)?;
254 }
255 if add.contains(style::Modifier::DIM) {
256 write!(f, "{}", termion::style::Faint)?;
257 }
258 if add.contains(style::Modifier::CROSSED_OUT) {
259 write!(f, "{}", termion::style::CrossedOut)?;
260 }
261 if add.contains(style::Modifier::SLOW_BLINK) || add.contains(style::Modifier::RAPID_BLINK) {
262 write!(f, "{}", termion::style::Blink)?;
263 }
264
265 Ok(())
266 }
267}