tuxtui_termion/
lib.rs

1//! # tuxtui-termion
2//!
3//! Termion backend implementation for tuxtui.
4//!
5//! This crate provides a backend implementation using the `termion` crate
6//! for Unix-based terminal manipulation.
7//!
8//! ## Example
9//!
10//! ```no_run
11//! use tuxtui_termion::TermionBackend;
12//! use tuxtui_core::terminal::Terminal;
13//! use std::io::stdout;
14//!
15//! let backend = TermionBackend::new(stdout());
16//! let mut terminal = Terminal::new(backend).unwrap();
17//! ```
18
19#![forbid(unsafe_code)]
20#![warn(missing_docs)]
21
22use std::io::{self, Write};
23use termion::{clear, cursor, style};
24use tuxtui_core::backend::Backend;
25use tuxtui_core::buffer::Cell;
26use tuxtui_core::geometry::{Position, Rect};
27use tuxtui_core::style::{Color as TuxColor, Modifier, Style};
28
29/// Termion backend.
30pub struct TermionBackend<W: Write> {
31    writer: W,
32}
33
34impl<W: Write> TermionBackend<W> {
35    /// Create a new termion backend.
36    pub fn new(writer: W) -> Self {
37        Self { writer }
38    }
39
40    fn convert_fg_color(&mut self, color: TuxColor) -> io::Result<()> {
41        use termion::color::*;
42        match color {
43            TuxColor::Reset => write!(self.writer, "{}", Fg(Reset)),
44            TuxColor::Black => write!(self.writer, "{}", Fg(Black)),
45            TuxColor::Red => write!(self.writer, "{}", Fg(Red)),
46            TuxColor::Green => write!(self.writer, "{}", Fg(Green)),
47            TuxColor::Yellow => write!(self.writer, "{}", Fg(Yellow)),
48            TuxColor::Blue => write!(self.writer, "{}", Fg(Blue)),
49            TuxColor::Magenta => write!(self.writer, "{}", Fg(Magenta)),
50            TuxColor::Cyan => write!(self.writer, "{}", Fg(Cyan)),
51            TuxColor::White | TuxColor::Gray => write!(self.writer, "{}", Fg(White)),
52            TuxColor::LightRed => write!(self.writer, "{}", Fg(LightRed)),
53            TuxColor::LightGreen => write!(self.writer, "{}", Fg(LightGreen)),
54            TuxColor::LightYellow => write!(self.writer, "{}", Fg(LightYellow)),
55            TuxColor::LightBlue => write!(self.writer, "{}", Fg(LightBlue)),
56            TuxColor::LightMagenta => write!(self.writer, "{}", Fg(LightMagenta)),
57            TuxColor::LightCyan => write!(self.writer, "{}", Fg(LightCyan)),
58            TuxColor::LightGray => write!(self.writer, "{}", Fg(LightWhite)),
59            TuxColor::Indexed(i) => write!(self.writer, "{}", Fg(AnsiValue(i))),
60            TuxColor::Rgb(r, g, b) => write!(self.writer, "{}", Fg(Rgb(r, g, b))),
61        }
62    }
63
64    fn convert_bg_color(&mut self, color: TuxColor) -> io::Result<()> {
65        use termion::color::*;
66        match color {
67            TuxColor::Reset => write!(self.writer, "{}", Bg(Reset)),
68            TuxColor::Black => write!(self.writer, "{}", Bg(Black)),
69            TuxColor::Red => write!(self.writer, "{}", Bg(Red)),
70            TuxColor::Green => write!(self.writer, "{}", Bg(Green)),
71            TuxColor::Yellow => write!(self.writer, "{}", Bg(Yellow)),
72            TuxColor::Blue => write!(self.writer, "{}", Bg(Blue)),
73            TuxColor::Magenta => write!(self.writer, "{}", Bg(Magenta)),
74            TuxColor::Cyan => write!(self.writer, "{}", Bg(Cyan)),
75            TuxColor::White | TuxColor::Gray => write!(self.writer, "{}", Bg(White)),
76            TuxColor::LightRed => write!(self.writer, "{}", Bg(LightRed)),
77            TuxColor::LightGreen => write!(self.writer, "{}", Bg(LightGreen)),
78            TuxColor::LightYellow => write!(self.writer, "{}", Bg(LightYellow)),
79            TuxColor::LightBlue => write!(self.writer, "{}", Bg(LightBlue)),
80            TuxColor::LightMagenta => write!(self.writer, "{}", Bg(LightMagenta)),
81            TuxColor::LightCyan => write!(self.writer, "{}", Bg(LightCyan)),
82            TuxColor::LightGray => write!(self.writer, "{}", Bg(LightWhite)),
83            TuxColor::Indexed(i) => write!(self.writer, "{}", Bg(AnsiValue(i))),
84            TuxColor::Rgb(r, g, b) => write!(self.writer, "{}", Bg(Rgb(r, g, b))),
85        }
86    }
87
88    fn apply_modifiers(&mut self, modifiers: Modifier) -> io::Result<()> {
89        if modifiers.contains(Modifier::BOLD) {
90            write!(self.writer, "{}", style::Bold)?;
91        }
92        if modifiers.contains(Modifier::DIM) {
93            write!(self.writer, "{}", style::Faint)?;
94        }
95        if modifiers.contains(Modifier::ITALIC) {
96            write!(self.writer, "{}", style::Italic)?;
97        }
98        if modifiers.contains(Modifier::UNDERLINED) {
99            write!(self.writer, "{}", style::Underline)?;
100        }
101        if modifiers.contains(Modifier::SLOW_BLINK) {
102            write!(self.writer, "{}", style::Blink)?;
103        }
104        if modifiers.contains(Modifier::REVERSED) {
105            write!(self.writer, "{}", style::Invert)?;
106        }
107        if modifiers.contains(Modifier::CROSSED_OUT) {
108            write!(self.writer, "{}", style::CrossedOut)?;
109        }
110        Ok(())
111    }
112}
113
114impl<W: Write> Backend for TermionBackend<W> {
115    type Error = io::Error;
116
117    fn size(&self) -> Result<Rect, Self::Error> {
118        let (width, height) = termion::terminal_size()?;
119        Ok(Rect::new(0, 0, width, height))
120    }
121
122    fn clear(&mut self) -> Result<(), Self::Error> {
123        write!(self.writer, "{}", clear::All)
124    }
125
126    fn hide_cursor(&mut self) -> Result<(), Self::Error> {
127        write!(self.writer, "{}", cursor::Hide)
128    }
129
130    fn show_cursor(&mut self) -> Result<(), Self::Error> {
131        write!(self.writer, "{}", cursor::Show)
132    }
133
134    fn get_cursor(&mut self) -> Result<Position, Self::Error> {
135        Ok(Position::new(0, 0)) // Termion doesn't easily support cursor position query
136    }
137
138    fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), Self::Error> {
139        write!(self.writer, "{}", cursor::Goto(x + 1, y + 1))
140    }
141
142    fn draw_cell(&mut self, x: u16, y: u16, cell: &Cell) -> Result<(), Self::Error> {
143        if cell.skip {
144            return Ok(());
145        }
146
147        write!(self.writer, "{}", cursor::Goto(x + 1, y + 1))?;
148
149        if let Some(fg) = cell.style.fg {
150            self.convert_fg_color(fg)?;
151        }
152        if let Some(bg) = cell.style.bg {
153            self.convert_bg_color(bg)?;
154        }
155
156        self.apply_modifiers(cell.style.add_modifier)?;
157
158        write!(self.writer, "{}", cell.symbol)?;
159        write!(self.writer, "{}", style::Reset)?;
160
161        Ok(())
162    }
163
164    fn set_style(&mut self, style: Style) -> Result<(), Self::Error> {
165        if let Some(fg) = style.fg {
166            self.convert_fg_color(fg)?;
167        }
168        if let Some(bg) = style.bg {
169            self.convert_bg_color(bg)?;
170        }
171        self.apply_modifiers(style.add_modifier)?;
172        Ok(())
173    }
174
175    fn reset_style(&mut self) -> Result<(), Self::Error> {
176        write!(self.writer, "{}", style::Reset)
177    }
178
179    fn flush(&mut self) -> Result<(), Self::Error> {
180        self.writer.flush()
181    }
182
183    fn enable_raw_mode(&mut self) -> Result<(), Self::Error> {
184        // Termion handles raw mode through RawTerminal wrapper
185        Ok(())
186    }
187
188    fn disable_raw_mode(&mut self) -> Result<(), Self::Error> {
189        // Termion handles raw mode through RawTerminal wrapper
190        Ok(())
191    }
192
193    fn enter_alternate_screen(&mut self) -> Result<(), Self::Error> {
194        write!(self.writer, "{}", termion::screen::ToAlternateScreen)
195    }
196
197    fn leave_alternate_screen(&mut self) -> Result<(), Self::Error> {
198        write!(self.writer, "{}", termion::screen::ToMainScreen)
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_backend_creation() {
208        let buffer = Vec::new();
209        let _backend = TermionBackend::new(buffer);
210    }
211}