1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
//! A module to represent a terminal and operations on it.

use std::{fmt::Display, io};

/// Gets the default [`Backend`] based on the features enabled.
#[cfg(any(feature = "crossterm", feature = "termion"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "crossterm", feature = "termion"))))]
pub fn get_backend<W: io::Write>(buf: W) -> impl Backend {
    #[cfg(feature = "crossterm")]
    return CrosstermBackend::new(buf);

    // XXX: Only works when crossterm and termion are the only two available backends
    //
    // Instead of directly checking for termion, we check for not crossterm so that compiling
    // (documentation) with both features enabled will not error
    #[cfg(not(feature = "crossterm"))]
    return TermionBackend::new(buf);
}

mod test_backend;
pub use test_backend::TestBackend;

#[cfg(feature = "termion")]
mod termion;

#[cfg(feature = "termion")]
pub use self::termion::TermionBackend;

#[cfg(feature = "crossterm")]
mod crossterm;

#[cfg(feature = "crossterm")]
pub use self::crossterm::CrosstermBackend;

use crate::style::{Attributes, Color, Styled};

/// A 2D size.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Default)]
#[allow(missing_docs)]
pub struct Size {
    pub width: u16,
    pub height: u16,
}

impl Size {
    /// The area of the size
    pub fn area(self) -> u16 {
        self.width * self.height
    }
}

impl From<(u16, u16)> for Size {
    fn from((width, height): (u16, u16)) -> Self {
        Size { width, height }
    }
}

/// The different parts of the terminal that can be cleared at once.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum ClearType {
    /// All cells.
    All,
    /// All cells from the cursor position downwards.
    FromCursorDown,
    /// All cells from the cursor position upwards.
    FromCursorUp,
    /// All cells at the cursor row.
    CurrentLine,
    /// All cells from the cursor position until the new line.
    UntilNewLine,
}

/// The directions the terminal cursor can be moved relative to the current position.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum MoveDirection {
    /// Moves a given number of rows up.
    Up(u16),
    /// Moves a given number of rows down.
    Down(u16),
    /// Moves a given number of columns left.
    Left(u16),
    /// Moves a given number of columns right.
    Right(u16),
    /// Moves a given number of rows down and goes to the start of the line.
    NextLine(u16),
    /// Moves a given number of rows up and goes to the start of the line.
    PrevLine(u16),
    /// Goes to a given column.
    Column(u16),
}

/// A trait to represent a terminal that can be rendered to.
pub trait Backend: io::Write {
    /// Enables raw mode.
    fn enable_raw_mode(&mut self) -> io::Result<()>;
    /// Disables raw mode.
    fn disable_raw_mode(&mut self) -> io::Result<()>;
    /// Hides the cursor.
    fn hide_cursor(&mut self) -> io::Result<()>;
    /// Shows the cursor.
    fn show_cursor(&mut self) -> io::Result<()>;

    /// Gets the cursor position as (col, row). The top-left cell is (0, 0).
    fn get_cursor_pos(&mut self) -> io::Result<(u16, u16)>;
    /// Moves the cursor to given position. The top-left cell is (0, 0).
    fn move_cursor_to(&mut self, x: u16, y: u16) -> io::Result<()>;
    /// Moves the cursor relative to the current position as per the `direction`.
    fn move_cursor(&mut self, direction: MoveDirection) -> io::Result<()> {
        default_move_cursor(self, direction)
    }
    /// Scrolls the terminal the given number of rows.
    ///
    /// A negative number means the terminal scrolls upwards, while a positive number means the
    /// terminal scrolls downwards.
    fn scroll(&mut self, dist: i16) -> io::Result<()>;

    /// Sets the given `attributes` removing ones which were previous applied.
    fn set_attributes(&mut self, attributes: Attributes) -> io::Result<()>;
    /// Sets the foreground color.
    fn set_fg(&mut self, color: Color) -> io::Result<()>;
    /// Sets the background color.
    fn set_bg(&mut self, color: Color) -> io::Result<()>;
    /// Write a styled object to the backend.
    ///
    /// See also [`Styled`] and [`Stylize`].
    ///
    /// [`Stylize`]: crate::style::Stylize
    fn write_styled(&mut self, styled: &Styled<dyn Display + '_>) -> io::Result<()> {
        styled.write(self)
    }

    /// Clears the cells given by clear_type
    fn clear(&mut self, clear_type: ClearType) -> io::Result<()>;
    /// Gets the size of the terminal in rows and columns.
    fn size(&self) -> io::Result<Size>;
}

fn default_move_cursor<B: Backend + ?Sized>(
    backend: &mut B,
    direction: MoveDirection,
) -> io::Result<()> {
    let (mut x, mut y) = backend.get_cursor_pos()?;

    match direction {
        MoveDirection::Up(dy) => y = y.saturating_sub(dy),
        MoveDirection::Down(dy) => y = y.saturating_add(dy),
        MoveDirection::Left(dx) => x = x.saturating_sub(dx),
        MoveDirection::Right(dx) => x = x.saturating_add(dx),
        MoveDirection::NextLine(dy) => {
            x = 0;
            y = y.saturating_add(dy);
        }
        MoveDirection::Column(new_x) => x = new_x,
        MoveDirection::PrevLine(dy) => {
            x = 0;
            y = y.saturating_sub(dy);
        }
    }

    backend.move_cursor_to(x, y)
}

impl<'a, B: Backend> Backend for &'a mut B {
    fn enable_raw_mode(&mut self) -> io::Result<()> {
        (**self).enable_raw_mode()
    }
    fn disable_raw_mode(&mut self) -> io::Result<()> {
        (**self).disable_raw_mode()
    }
    fn hide_cursor(&mut self) -> io::Result<()> {
        (**self).hide_cursor()
    }
    fn show_cursor(&mut self) -> io::Result<()> {
        (**self).show_cursor()
    }
    fn get_cursor_pos(&mut self) -> io::Result<(u16, u16)> {
        (**self).get_cursor_pos()
    }
    fn move_cursor_to(&mut self, x: u16, y: u16) -> io::Result<()> {
        (**self).move_cursor_to(x, y)
    }
    fn move_cursor(&mut self, direction: MoveDirection) -> io::Result<()> {
        (**self).move_cursor(direction)
    }
    fn scroll(&mut self, dist: i16) -> io::Result<()> {
        (**self).scroll(dist)
    }
    fn set_attributes(&mut self, attributes: Attributes) -> io::Result<()> {
        (**self).set_attributes(attributes)
    }
    fn set_fg(&mut self, color: Color) -> io::Result<()> {
        (**self).set_fg(color)
    }
    fn set_bg(&mut self, color: Color) -> io::Result<()> {
        (**self).set_bg(color)
    }
    fn write_styled(&mut self, styled: &Styled<dyn Display + '_>) -> io::Result<()> {
        (**self).write_styled(styled)
    }
    fn clear(&mut self, clear_type: ClearType) -> io::Result<()> {
        (**self).clear(clear_type)
    }
    fn size(&self) -> io::Result<Size> {
        (**self).size()
    }
}