1use std::{
2 cmp::Ordering,
3 io::{self, Write},
4};
5
6use crossterm::{
7 cursor, queue,
8 style::{
9 Attribute as CAttribute, Color as CColor, SetAttribute, SetBackgroundColor,
10 SetForegroundColor,
11 },
12 terminal,
13};
14
15use super::{Attributes, Backend, ClearType, Color, DisplayBackend, MoveDirection, Size};
16
17#[derive(Debug, Clone)]
19#[cfg_attr(docsrs, doc(cfg(feature = "crossterm")))]
20pub struct CrosstermBackend<W> {
21 buffer: W,
22 attributes: Attributes,
23}
24
25impl<W> CrosstermBackend<W> {
26 pub fn new(buffer: W) -> CrosstermBackend<W> {
28 CrosstermBackend {
29 buffer,
30 attributes: Attributes::empty(),
31 }
32 }
33}
34
35impl<W: Write> Write for CrosstermBackend<W> {
36 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
37 self.buffer.write(buf)
38 }
39
40 fn flush(&mut self) -> io::Result<()> {
41 self.buffer.flush()
42 }
43}
44
45impl<W: Write> DisplayBackend for CrosstermBackend<W> {
46 fn set_attributes(&mut self, attributes: Attributes) -> io::Result<()> {
47 set_attributes(self.attributes, attributes, &mut self.buffer)?;
48 self.attributes = attributes;
49 Ok(())
50 }
51
52 fn set_fg(&mut self, color: Color) -> io::Result<()> {
53 queue!(self.buffer, SetForegroundColor(color.into()))
54 }
55
56 fn set_bg(&mut self, color: Color) -> io::Result<()> {
57 queue!(self.buffer, SetBackgroundColor(color.into()))
58 }
59}
60
61impl<W: Write> Backend for CrosstermBackend<W> {
62 fn enable_raw_mode(&mut self) -> io::Result<()> {
63 terminal::enable_raw_mode()
64 }
65
66 fn disable_raw_mode(&mut self) -> io::Result<()> {
67 terminal::disable_raw_mode()
68 }
69
70 fn hide_cursor(&mut self) -> io::Result<()> {
71 queue!(self.buffer, cursor::Hide)
72 }
73
74 fn show_cursor(&mut self) -> io::Result<()> {
75 queue!(self.buffer, cursor::Show)
76 }
77
78 fn get_cursor_pos(&mut self) -> io::Result<(u16, u16)> {
79 cursor::position()
80 }
81
82 fn move_cursor_to(&mut self, x: u16, y: u16) -> io::Result<()> {
83 queue!(self.buffer, cursor::MoveTo(x, y))
84 }
85
86 fn move_cursor(&mut self, direction: MoveDirection) -> io::Result<()> {
87 match direction {
88 MoveDirection::Up(n) => queue!(self.buffer, cursor::MoveUp(n)),
89 MoveDirection::Down(n) => queue!(self.buffer, cursor::MoveDown(n)),
90 MoveDirection::Left(n) => queue!(self.buffer, cursor::MoveLeft(n)),
91 MoveDirection::Right(n) => queue!(self.buffer, cursor::MoveRight(n)),
92 MoveDirection::NextLine(n) => {
93 queue!(self.buffer, cursor::MoveToNextLine(n))
94 }
95 MoveDirection::Column(n) => queue!(self.buffer, cursor::MoveToColumn(n)),
96 MoveDirection::PrevLine(n) => {
97 queue!(self.buffer, cursor::MoveToPreviousLine(n))
98 }
99 }
100 }
101
102 fn scroll(&mut self, dist: i16) -> io::Result<()> {
103 match dist.cmp(&0) {
104 Ordering::Greater => {
105 queue!(self.buffer, terminal::ScrollDown(dist as u16))
106 }
107 Ordering::Less => {
108 queue!(self.buffer, terminal::ScrollUp(-dist as u16))
109 }
110 Ordering::Equal => Ok(()),
111 }
112 }
113
114 fn clear(&mut self, clear_type: ClearType) -> io::Result<()> {
115 queue!(self.buffer, terminal::Clear(clear_type.into()))
116 }
117
118 fn size(&self) -> io::Result<Size> {
119 terminal::size().map(Into::into)
120 }
121}
122
123impl From<Color> for CColor {
124 fn from(color: Color) -> Self {
125 match color {
126 Color::Reset => CColor::Reset,
127 Color::Black => CColor::Black,
128 Color::Red => CColor::DarkRed,
129 Color::Green => CColor::DarkGreen,
130 Color::Yellow => CColor::DarkYellow,
131 Color::Blue => CColor::DarkBlue,
132 Color::Magenta => CColor::DarkMagenta,
133 Color::Cyan => CColor::DarkCyan,
134 Color::Grey => CColor::Grey,
135 Color::DarkGrey => CColor::DarkGrey,
136 Color::LightRed => CColor::Red,
137 Color::LightGreen => CColor::Green,
138 Color::LightBlue => CColor::Blue,
139 Color::LightYellow => CColor::Yellow,
140 Color::LightMagenta => CColor::Magenta,
141 Color::LightCyan => CColor::Cyan,
142 Color::White => CColor::White,
143 Color::Ansi(i) => CColor::AnsiValue(i),
144 Color::Rgb(r, g, b) => CColor::Rgb { r, g, b },
145 }
146 }
147}
148
149impl From<ClearType> for terminal::ClearType {
150 fn from(clear: ClearType) -> Self {
151 match clear {
152 ClearType::All => terminal::ClearType::All,
153 ClearType::FromCursorDown => terminal::ClearType::FromCursorDown,
154 ClearType::FromCursorUp => terminal::ClearType::FromCursorUp,
155 ClearType::CurrentLine => terminal::ClearType::CurrentLine,
156 ClearType::UntilNewLine => terminal::ClearType::UntilNewLine,
157 }
158 }
159}
160
161pub(super) fn set_attributes<W: Write>(
162 from: Attributes,
163 to: Attributes,
164 mut w: W,
165) -> io::Result<()> {
166 let diff = from.diff(to);
167 if diff.to_remove.contains(Attributes::REVERSED) {
168 queue!(w, SetAttribute(CAttribute::NoReverse))?;
169 }
170 if diff.to_remove.contains(Attributes::BOLD) {
171 queue!(w, SetAttribute(CAttribute::NormalIntensity))?;
172 if to.contains(Attributes::DIM) {
173 queue!(w, SetAttribute(CAttribute::Dim))?;
174 }
175 }
176 if diff.to_remove.contains(Attributes::ITALIC) {
177 queue!(w, SetAttribute(CAttribute::NoItalic))?;
178 }
179 if diff.to_remove.contains(Attributes::UNDERLINED) {
180 queue!(w, SetAttribute(CAttribute::NoUnderline))?;
181 }
182 if diff.to_remove.contains(Attributes::DIM) {
183 queue!(w, SetAttribute(CAttribute::NormalIntensity))?;
184 }
185 if diff.to_remove.contains(Attributes::CROSSED_OUT) {
186 queue!(w, SetAttribute(CAttribute::NotCrossedOut))?;
187 }
188 if diff.to_remove.contains(Attributes::SLOW_BLINK)
189 || diff.to_remove.contains(Attributes::RAPID_BLINK)
190 {
191 queue!(w, SetAttribute(CAttribute::NoBlink))?;
192 }
193
194 if diff.to_add.contains(Attributes::REVERSED) {
195 queue!(w, SetAttribute(CAttribute::Reverse))?;
196 }
197 if diff.to_add.contains(Attributes::BOLD) {
198 queue!(w, SetAttribute(CAttribute::Bold))?;
199 }
200 if diff.to_add.contains(Attributes::ITALIC) {
201 queue!(w, SetAttribute(CAttribute::Italic))?;
202 }
203 if diff.to_add.contains(Attributes::UNDERLINED) {
204 queue!(w, SetAttribute(CAttribute::Underlined))?;
205 }
206 if diff.to_add.contains(Attributes::DIM) {
207 queue!(w, SetAttribute(CAttribute::Dim))?;
208 }
209 if diff.to_add.contains(Attributes::CROSSED_OUT) {
210 queue!(w, SetAttribute(CAttribute::CrossedOut))?;
211 }
212 if diff.to_add.contains(Attributes::SLOW_BLINK) {
213 queue!(w, SetAttribute(CAttribute::SlowBlink))?;
214 }
215 if diff.to_add.contains(Attributes::RAPID_BLINK) {
216 queue!(w, SetAttribute(CAttribute::RapidBlink))?;
217 }
218
219 Ok(())
220}