1#![forbid(unsafe_code)]
30#![warn(missing_docs)]
31
32use crossterm::{
33 cursor, execute, queue,
34 style::{
35 self, Attribute, Color as CColor, SetAttribute, SetBackgroundColor, SetForegroundColor,
36 },
37 terminal::{self, Clear, ClearType},
38};
39use std::io::{self, Write};
40use tuxtui_core::backend::Backend;
41use tuxtui_core::buffer::Cell;
42use tuxtui_core::geometry::{Position, Rect};
43use tuxtui_core::style::{Color, Modifier, Style};
44
45pub struct CrosstermBackend<W: Write> {
49 writer: W,
50}
51
52impl<W: Write> CrosstermBackend<W> {
53 pub fn new(writer: W) -> Self {
64 Self { writer }
65 }
66
67 pub fn writer(&self) -> &W {
69 &self.writer
70 }
71
72 pub fn writer_mut(&mut self) -> &mut W {
74 &mut self.writer
75 }
76
77 fn convert_color(color: Color) -> CColor {
78 match color {
79 Color::Reset => CColor::Reset,
80 Color::Black => CColor::Black,
81 Color::Red => CColor::DarkRed,
82 Color::Green => CColor::DarkGreen,
83 Color::Yellow => CColor::DarkYellow,
84 Color::Blue => CColor::DarkBlue,
85 Color::Magenta => CColor::DarkMagenta,
86 Color::Cyan => CColor::DarkCyan,
87 Color::White => CColor::Grey,
88 Color::Gray => CColor::DarkGrey,
89 Color::LightRed => CColor::Red,
90 Color::LightGreen => CColor::Green,
91 Color::LightYellow => CColor::Yellow,
92 Color::LightBlue => CColor::Blue,
93 Color::LightMagenta => CColor::Magenta,
94 Color::LightCyan => CColor::Cyan,
95 Color::LightGray => CColor::White,
96 Color::Indexed(i) => CColor::AnsiValue(i),
97 Color::Rgb(r, g, b) => CColor::Rgb { r, g, b },
98 }
99 }
100
101 fn apply_modifiers(&mut self, modifiers: Modifier) -> io::Result<()> {
102 if modifiers.contains(Modifier::BOLD) {
103 queue!(self.writer, SetAttribute(Attribute::Bold))?;
104 }
105 if modifiers.contains(Modifier::DIM) {
106 queue!(self.writer, SetAttribute(Attribute::Dim))?;
107 }
108 if modifiers.contains(Modifier::ITALIC) {
109 queue!(self.writer, SetAttribute(Attribute::Italic))?;
110 }
111 if modifiers.contains(Modifier::UNDERLINED) {
112 queue!(self.writer, SetAttribute(Attribute::Underlined))?;
113 }
114 if modifiers.contains(Modifier::SLOW_BLINK) {
115 queue!(self.writer, SetAttribute(Attribute::SlowBlink))?;
116 }
117 if modifiers.contains(Modifier::RAPID_BLINK) {
118 queue!(self.writer, SetAttribute(Attribute::RapidBlink))?;
119 }
120 if modifiers.contains(Modifier::REVERSED) {
121 queue!(self.writer, SetAttribute(Attribute::Reverse))?;
122 }
123 if modifiers.contains(Modifier::HIDDEN) {
124 queue!(self.writer, SetAttribute(Attribute::Hidden))?;
125 }
126 if modifiers.contains(Modifier::CROSSED_OUT) {
127 queue!(self.writer, SetAttribute(Attribute::CrossedOut))?;
128 }
129 Ok(())
130 }
131}
132
133impl<W: Write> Backend for CrosstermBackend<W> {
134 type Error = io::Error;
135
136 fn size(&self) -> Result<Rect, Self::Error> {
137 let (width, height) = terminal::size()?;
138 Ok(Rect::new(0, 0, width, height))
139 }
140
141 fn clear(&mut self) -> Result<(), Self::Error> {
142 execute!(self.writer, Clear(ClearType::All))
143 }
144
145 fn clear_region(&mut self, region: Rect) -> Result<(), Self::Error> {
146 for y in region.top()..region.bottom() {
147 queue!(self.writer, cursor::MoveTo(region.left(), y))?;
148 for _ in region.left()..region.right() {
149 queue!(self.writer, style::Print(" "))?;
150 }
151 }
152 Ok(())
153 }
154
155 fn hide_cursor(&mut self) -> Result<(), Self::Error> {
156 execute!(self.writer, cursor::Hide)
157 }
158
159 fn show_cursor(&mut self) -> Result<(), Self::Error> {
160 execute!(self.writer, cursor::Show)
161 }
162
163 fn get_cursor(&mut self) -> Result<Position, Self::Error> {
164 Ok(Position::new(0, 0))
167 }
168
169 fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), Self::Error> {
170 queue!(self.writer, cursor::MoveTo(x, y))?;
171 Ok(())
172 }
173
174 fn draw_cell(&mut self, x: u16, y: u16, cell: &Cell) -> Result<(), Self::Error> {
175 if cell.skip {
176 return Ok(());
177 }
178
179 queue!(self.writer, cursor::MoveTo(x, y))?;
180
181 if let Some(fg) = cell.style.fg {
182 queue!(self.writer, SetForegroundColor(Self::convert_color(fg)))?;
183 }
184 if let Some(bg) = cell.style.bg {
185 queue!(self.writer, SetBackgroundColor(Self::convert_color(bg)))?;
186 }
187
188 self.apply_modifiers(cell.style.add_modifier)?;
189
190 queue!(self.writer, style::Print(&cell.symbol))?;
191
192 if !cell.style.add_modifier.is_empty() || cell.style.fg.is_some() || cell.style.bg.is_some()
194 {
195 queue!(self.writer, SetAttribute(Attribute::Reset))?;
196 }
197
198 Ok(())
199 }
200
201 fn set_style(&mut self, style: Style) -> Result<(), Self::Error> {
202 if let Some(fg) = style.fg {
203 queue!(self.writer, SetForegroundColor(Self::convert_color(fg)))?;
204 }
205 if let Some(bg) = style.bg {
206 queue!(self.writer, SetBackgroundColor(Self::convert_color(bg)))?;
207 }
208 self.apply_modifiers(style.add_modifier)?;
209 Ok(())
210 }
211
212 fn reset_style(&mut self) -> Result<(), Self::Error> {
213 queue!(self.writer, SetAttribute(Attribute::Reset))?;
214 Ok(())
215 }
216
217 fn flush(&mut self) -> Result<(), Self::Error> {
218 self.writer.flush()
219 }
220
221 fn enable_raw_mode(&mut self) -> Result<(), Self::Error> {
222 terminal::enable_raw_mode()
223 }
224
225 fn disable_raw_mode(&mut self) -> Result<(), Self::Error> {
226 terminal::disable_raw_mode()
227 }
228
229 fn enter_alternate_screen(&mut self) -> Result<(), Self::Error> {
230 execute!(self.writer, terminal::EnterAlternateScreen)
231 }
232
233 fn leave_alternate_screen(&mut self) -> Result<(), Self::Error> {
234 execute!(self.writer, terminal::LeaveAlternateScreen)
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use std::io::Cursor;
242
243 #[test]
244 fn test_backend_creation() {
245 let cursor = Cursor::new(Vec::new());
246 let backend = CrosstermBackend::new(cursor);
247 assert!(backend.writer().get_ref().is_empty());
248 }
249
250 #[test]
251 fn test_color_conversion() {
252 assert!(matches!(
253 CrosstermBackend::<Vec<u8>>::convert_color(Color::Red),
254 CColor::DarkRed
255 ));
256 assert!(matches!(
257 CrosstermBackend::<Vec<u8>>::convert_color(Color::Rgb(255, 128, 0)),
258 CColor::Rgb {
259 r: 255,
260 g: 128,
261 b: 0
262 }
263 ));
264 }
265}