tui/backend/
crossterm.rs

1use crate::{
2    backend::Backend,
3    buffer::Cell,
4    layout::Rect,
5    style::{Color, Modifier},
6};
7use crossterm::{
8    cursor::{Hide, MoveTo, Show},
9    execute, queue,
10    style::{
11        Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor,
12        SetForegroundColor,
13    },
14    terminal::{self, Clear, ClearType},
15};
16use std::io::{self, Write};
17
18pub struct CrosstermBackend<W: Write> {
19    buffer: W,
20}
21
22impl<W> CrosstermBackend<W>
23where
24    W: Write,
25{
26    pub fn new(buffer: W) -> CrosstermBackend<W> {
27        CrosstermBackend { buffer }
28    }
29}
30
31impl<W> Write for CrosstermBackend<W>
32where
33    W: Write,
34{
35    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
36        self.buffer.write(buf)
37    }
38
39    fn flush(&mut self) -> io::Result<()> {
40        self.buffer.flush()
41    }
42}
43
44impl<W> Backend for CrosstermBackend<W>
45where
46    W: Write,
47{
48    fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
49    where
50        I: Iterator<Item = (u16, u16, &'a Cell)>,
51    {
52        let mut fg = Color::Reset;
53        let mut bg = Color::Reset;
54        let mut modifier = Modifier::empty();
55        let mut last_pos: Option<(u16, u16)> = None;
56        for (x, y, cell) in content {
57            // Move the cursor if the previous location was not (x - 1, y)
58            if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
59                map_error(queue!(self.buffer, MoveTo(x, y)))?;
60            }
61            last_pos = Some((x, y));
62            if cell.modifier != modifier {
63                let diff = ModifierDiff {
64                    from: modifier,
65                    to: cell.modifier,
66                };
67                diff.queue(&mut self.buffer)?;
68                modifier = cell.modifier;
69            }
70            if cell.fg != fg {
71                let color = CColor::from(cell.fg);
72                map_error(queue!(self.buffer, SetForegroundColor(color)))?;
73                fg = cell.fg;
74            }
75            if cell.bg != bg {
76                let color = CColor::from(cell.bg);
77                map_error(queue!(self.buffer, SetBackgroundColor(color)))?;
78                bg = cell.bg;
79            }
80
81            map_error(queue!(self.buffer, Print(&cell.symbol)))?;
82        }
83
84        map_error(queue!(
85            self.buffer,
86            SetForegroundColor(CColor::Reset),
87            SetBackgroundColor(CColor::Reset),
88            SetAttribute(CAttribute::Reset)
89        ))
90    }
91
92    fn hide_cursor(&mut self) -> io::Result<()> {
93        map_error(execute!(self.buffer, Hide))
94    }
95
96    fn show_cursor(&mut self) -> io::Result<()> {
97        map_error(execute!(self.buffer, Show))
98    }
99
100    fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
101        crossterm::cursor::position()
102            .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
103    }
104
105    fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
106        map_error(execute!(self.buffer, MoveTo(x, y)))
107    }
108
109    fn clear(&mut self) -> io::Result<()> {
110        map_error(execute!(self.buffer, Clear(ClearType::All)))
111    }
112
113    fn size(&self) -> io::Result<Rect> {
114        let (width, height) =
115            terminal::size().map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
116
117        Ok(Rect::new(0, 0, width, height))
118    }
119
120    fn flush(&mut self) -> io::Result<()> {
121        self.buffer.flush()
122    }
123}
124
125fn map_error(error: crossterm::Result<()>) -> io::Result<()> {
126    error.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
127}
128
129impl From<Color> for CColor {
130    fn from(color: Color) -> Self {
131        match color {
132            Color::Reset => CColor::Reset,
133            Color::Black => CColor::Black,
134            Color::Red => CColor::DarkRed,
135            Color::Green => CColor::DarkGreen,
136            Color::Yellow => CColor::DarkYellow,
137            Color::Blue => CColor::DarkBlue,
138            Color::Magenta => CColor::DarkMagenta,
139            Color::Cyan => CColor::DarkCyan,
140            Color::Gray => CColor::Grey,
141            Color::DarkGray => CColor::DarkGrey,
142            Color::LightRed => CColor::Red,
143            Color::LightGreen => CColor::Green,
144            Color::LightBlue => CColor::Blue,
145            Color::LightYellow => CColor::Yellow,
146            Color::LightMagenta => CColor::Magenta,
147            Color::LightCyan => CColor::Cyan,
148            Color::White => CColor::White,
149            Color::Indexed(i) => CColor::AnsiValue(i),
150            Color::Rgb(r, g, b) => CColor::Rgb { r, g, b },
151        }
152    }
153}
154
155#[derive(Debug)]
156struct ModifierDiff {
157    pub from: Modifier,
158    pub to: Modifier,
159}
160
161impl ModifierDiff {
162    fn queue<W>(&self, mut w: W) -> io::Result<()>
163    where
164        W: io::Write,
165    {
166        //use crossterm::Attribute;
167        let removed = self.from - self.to;
168        if removed.contains(Modifier::REVERSED) {
169            map_error(queue!(w, SetAttribute(CAttribute::NoReverse)))?;
170        }
171        if removed.contains(Modifier::BOLD) {
172            map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
173            if self.to.contains(Modifier::DIM) {
174                map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
175            }
176        }
177        if removed.contains(Modifier::ITALIC) {
178            map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?;
179        }
180        if removed.contains(Modifier::UNDERLINED) {
181            map_error(queue!(w, SetAttribute(CAttribute::NoUnderline)))?;
182        }
183        if removed.contains(Modifier::DIM) {
184            map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
185        }
186        if removed.contains(Modifier::CROSSED_OUT) {
187            map_error(queue!(w, SetAttribute(CAttribute::NotCrossedOut)))?;
188        }
189        if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
190            map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?;
191        }
192
193        let added = self.to - self.from;
194        if added.contains(Modifier::REVERSED) {
195            map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?;
196        }
197        if added.contains(Modifier::BOLD) {
198            map_error(queue!(w, SetAttribute(CAttribute::Bold)))?;
199        }
200        if added.contains(Modifier::ITALIC) {
201            map_error(queue!(w, SetAttribute(CAttribute::Italic)))?;
202        }
203        if added.contains(Modifier::UNDERLINED) {
204            map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?;
205        }
206        if added.contains(Modifier::DIM) {
207            map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
208        }
209        if added.contains(Modifier::CROSSED_OUT) {
210            map_error(queue!(w, SetAttribute(CAttribute::CrossedOut)))?;
211        }
212        if added.contains(Modifier::SLOW_BLINK) {
213            map_error(queue!(w, SetAttribute(CAttribute::SlowBlink)))?;
214        }
215        if added.contains(Modifier::RAPID_BLINK) {
216            map_error(queue!(w, SetAttribute(CAttribute::RapidBlink)))?;
217        }
218
219        Ok(())
220    }
221}