requestty_ui/backend/
mod.rs

1//! A module to represent a terminal and operations on it.
2
3#[cfg(all(not(feature = "crossterm"), feature = "termion"))]
4use std::os::fd::AsFd;
5use std::{fmt::Display, io};
6
7/// Gets the default [`Backend`] based on the features enabled.
8#[cfg(feature = "crossterm")]
9#[cfg_attr(docsrs, doc(cfg(feature = "crossterm")))]
10pub fn get_backend<W: io::Write>(buf: W) -> impl Backend {
11    CrosstermBackend::new(buf)
12}
13
14/// Gets the default [`Backend`] based on the features enabled.
15#[cfg(all(not(feature = "crossterm"), feature = "termion"))]
16#[cfg_attr(docsrs, doc(cfg(feature = "termion")))]
17pub fn get_backend<W>(buf: W) -> impl Backend
18where
19    W: io::Write + AsFd,
20{
21    TermionBackend::new(buf)
22}
23
24mod test_backend;
25pub use test_backend::TestBackend;
26
27#[cfg(feature = "termion")]
28mod termion;
29
30#[cfg(feature = "termion")]
31pub use self::termion::{TermionBackend, TermionDisplayBackend};
32
33#[cfg(feature = "crossterm")]
34mod crossterm;
35
36#[cfg(feature = "crossterm")]
37pub use self::crossterm::CrosstermBackend;
38
39use crate::style::{Attributes, Color, Styled};
40
41/// A 2D size.
42#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Default)]
43#[allow(missing_docs)]
44pub struct Size {
45    pub width: u16,
46    pub height: u16,
47}
48
49impl Size {
50    /// The area of the size
51    pub fn area(self) -> u16 {
52        self.width * self.height
53    }
54}
55
56impl From<(u16, u16)> for Size {
57    fn from((width, height): (u16, u16)) -> Self {
58        Size { width, height }
59    }
60}
61
62/// The different parts of the terminal that can be cleared at once.
63#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
64pub enum ClearType {
65    /// All cells.
66    All,
67    /// All cells from the cursor position downwards.
68    FromCursorDown,
69    /// All cells from the cursor position upwards.
70    FromCursorUp,
71    /// All cells at the cursor row.
72    CurrentLine,
73    /// All cells from the cursor position until the new line.
74    UntilNewLine,
75}
76
77/// The directions the terminal cursor can be moved relative to the current position.
78#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
79pub enum MoveDirection {
80    /// Moves a given number of rows up.
81    Up(u16),
82    /// Moves a given number of rows down.
83    Down(u16),
84    /// Moves a given number of columns left.
85    Left(u16),
86    /// Moves a given number of columns right.
87    Right(u16),
88    /// Moves a given number of rows down and goes to the start of the line.
89    NextLine(u16),
90    /// Moves a given number of rows up and goes to the start of the line.
91    PrevLine(u16),
92    /// Goes to a given column.
93    Column(u16),
94}
95
96/// A trait to represent a terminal that can be rendered to without interaction.
97pub trait DisplayBackend: io::Write {
98    /// Sets the given `attributes` removing ones which were previous applied.
99    fn set_attributes(&mut self, attributes: Attributes) -> io::Result<()>;
100    /// Sets the foreground color.
101    fn set_fg(&mut self, color: Color) -> io::Result<()>;
102    /// Sets the background color.
103    fn set_bg(&mut self, color: Color) -> io::Result<()>;
104    /// Write a styled object to the backend.
105    ///
106    /// See also [`Styled`] and [`Stylize`].
107    ///
108    /// [`Stylize`]: crate::style::Stylize
109    fn write_styled(&mut self, styled: &Styled<dyn Display + '_>) -> io::Result<()> {
110        styled.write(self)
111    }
112}
113
114/// A trait to represent a terminal that can be rendered to in an interactive manner.
115pub trait Backend: DisplayBackend {
116    /// Enables raw mode.
117    fn enable_raw_mode(&mut self) -> io::Result<()>;
118    /// Disables raw mode.
119    fn disable_raw_mode(&mut self) -> io::Result<()>;
120    /// Hides the cursor.
121    fn hide_cursor(&mut self) -> io::Result<()>;
122    /// Shows the cursor.
123    fn show_cursor(&mut self) -> io::Result<()>;
124
125    /// Gets the cursor position as (col, row). The top-left cell is (0, 0).
126    fn get_cursor_pos(&mut self) -> io::Result<(u16, u16)>;
127    /// Moves the cursor to given position. The top-left cell is (0, 0).
128    fn move_cursor_to(&mut self, x: u16, y: u16) -> io::Result<()>;
129    /// Moves the cursor relative to the current position as per the `direction`.
130    fn move_cursor(&mut self, direction: MoveDirection) -> io::Result<()> {
131        default_move_cursor(self, direction)
132    }
133    /// Scrolls the terminal the given number of rows.
134    ///
135    /// A negative number means the terminal scrolls upwards, while a positive number means the
136    /// terminal scrolls downwards.
137    fn scroll(&mut self, dist: i16) -> io::Result<()>;
138
139    /// Clears the cells given by clear_type
140    fn clear(&mut self, clear_type: ClearType) -> io::Result<()>;
141    /// Gets the size of the terminal in rows and columns.
142    fn size(&self) -> io::Result<Size>;
143}
144
145fn default_move_cursor<B: Backend + ?Sized>(
146    backend: &mut B,
147    direction: MoveDirection,
148) -> io::Result<()> {
149    let (mut x, mut y) = backend.get_cursor_pos()?;
150
151    match direction {
152        MoveDirection::Up(dy) => y = y.saturating_sub(dy),
153        MoveDirection::Down(dy) => y = y.saturating_add(dy),
154        MoveDirection::Left(dx) => x = x.saturating_sub(dx),
155        MoveDirection::Right(dx) => x = x.saturating_add(dx),
156        MoveDirection::NextLine(dy) => {
157            x = 0;
158            y = y.saturating_add(dy);
159        }
160        MoveDirection::Column(new_x) => x = new_x,
161        MoveDirection::PrevLine(dy) => {
162            x = 0;
163            y = y.saturating_sub(dy);
164        }
165    }
166
167    backend.move_cursor_to(x, y)
168}
169
170impl<B: DisplayBackend> DisplayBackend for &mut B {
171    fn set_attributes(&mut self, attributes: Attributes) -> io::Result<()> {
172        (**self).set_attributes(attributes)
173    }
174    fn set_fg(&mut self, color: Color) -> io::Result<()> {
175        (**self).set_fg(color)
176    }
177    fn set_bg(&mut self, color: Color) -> io::Result<()> {
178        (**self).set_bg(color)
179    }
180    fn write_styled(&mut self, styled: &Styled<dyn Display + '_>) -> io::Result<()> {
181        (**self).write_styled(styled)
182    }
183}
184
185impl<B: Backend> Backend for &mut B {
186    fn enable_raw_mode(&mut self) -> io::Result<()> {
187        (**self).enable_raw_mode()
188    }
189    fn disable_raw_mode(&mut self) -> io::Result<()> {
190        (**self).disable_raw_mode()
191    }
192    fn hide_cursor(&mut self) -> io::Result<()> {
193        (**self).hide_cursor()
194    }
195    fn show_cursor(&mut self) -> io::Result<()> {
196        (**self).show_cursor()
197    }
198    fn get_cursor_pos(&mut self) -> io::Result<(u16, u16)> {
199        (**self).get_cursor_pos()
200    }
201    fn move_cursor_to(&mut self, x: u16, y: u16) -> io::Result<()> {
202        (**self).move_cursor_to(x, y)
203    }
204    fn move_cursor(&mut self, direction: MoveDirection) -> io::Result<()> {
205        (**self).move_cursor(direction)
206    }
207    fn scroll(&mut self, dist: i16) -> io::Result<()> {
208        (**self).scroll(dist)
209    }
210    fn clear(&mut self, clear_type: ClearType) -> io::Result<()> {
211        (**self).clear(clear_type)
212    }
213    fn size(&self) -> io::Result<Size> {
214        (**self).size()
215    }
216}