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
///! A canvas is a trait defining the draw actions
use crate::attr::Attr;
use crate::cell::Cell;
use std::error::Error;
use unicode_width::UnicodeWidthChar;

pub type Result<T> = std::result::Result<T, Box<dyn Error>>;

pub trait Canvas {
    /// Get the canvas size (width, height)
    fn size(&self) -> Result<(usize, usize)>;

    /// clear the canvas
    fn clear(&mut self) -> Result<()>;

    /// change a cell of position `(row, col)` to `cell`
    /// if `(row, col)` is out of boundary, `Ok` is returned, but no operation is taken
    /// return the width of the character/cell
    fn put_cell(&mut self, row: usize, col: usize, cell: Cell) -> Result<usize>;

    /// just like put_cell, except it accept (char & attr)
    /// return the width of the character/cell
    fn put_char_with_attr(
        &mut self,
        row: usize,
        col: usize,
        ch: char,
        attr: Attr,
    ) -> Result<usize> {
        self.put_cell(row, col, Cell { ch, attr })
    }

    /// print `content` starting with position `(row, col)` with `attr`
    /// - canvas should NOT wrap to y+1 if the content is too long
    /// - canvas should handle wide characters
    /// return the printed width of the content
    fn print_with_attr(
        &mut self,
        row: usize,
        col: usize,
        content: &str,
        attr: Attr,
    ) -> Result<usize> {
        let mut cell = Cell {
            attr,
            ..Cell::default()
        };

        let mut width = 0;
        for ch in content.chars() {
            cell.ch = ch;
            width += self.put_cell(row, col + width, cell)?;
        }
        Ok(width)
    }

    /// print `content` starting with position `(row, col)` with default attribute
    fn print(&mut self, row: usize, col: usize, content: &str) -> Result<usize> {
        self.print_with_attr(row, col, content, Attr::default())
    }

    /// move cursor position (row, col) and show cursor
    fn set_cursor(&mut self, row: usize, col: usize) -> Result<()>;

    /// show/hide cursor, set `show` to `false` to hide the cursor
    fn show_cursor(&mut self, show: bool) -> Result<()>;
}

/// A sub-area of a canvas.
/// It will handle the adjustments of cursor movement, so that you could write
/// to for example (0, 0) and BoundedCanvas will adjust it to real position.
pub struct BoundedCanvas<'a> {
    canvas: &'a mut Canvas,
    top: usize,
    left: usize,
    width: usize,
    height: usize,
}

impl<'a> BoundedCanvas<'a> {
    pub fn new(
        top: usize,
        left: usize,
        width: usize,
        height: usize,
        canvas: &'a mut Canvas,
    ) -> Self {
        Self {
            canvas,
            top,
            left,
            width,
            height,
        }
    }
}

impl<'a> Canvas for BoundedCanvas<'a> {
    fn size(&self) -> Result<(usize, usize)> {
        Ok((self.width, self.height))
    }

    fn clear(&mut self) -> Result<()> {
        for row in self.top..(self.top + self.height) {
            for col in self.left..(self.left + self.width) {
                let _ = self.canvas.put_cell(row, col, Cell::empty());
            }
        }

        Ok(())
    }

    fn put_cell(&mut self, row: usize, col: usize, cell: Cell) -> Result<usize> {
        if row >= self.height || col >= self.width {
            // do nothing
            Ok(cell.ch.width().unwrap_or(2))
        } else {
            self.canvas.put_cell(row + self.top, col + self.left, cell)
        }
    }

    fn set_cursor(&mut self, row: usize, col: usize) -> Result<()> {
        if row >= self.height || col >= self.width {
            // do nothing
            Ok(())
        } else {
            self.canvas.set_cursor(row + self.top, col + self.left)
        }
    }

    fn show_cursor(&mut self, show: bool) -> Result<()> {
        self.canvas.show_cursor(show)
    }
}