tuikit/
canvas.rs

1///! A canvas is a trait defining the draw actions
2use crate::attr::Attr;
3use crate::cell::Cell;
4use crate::Result;
5use unicode_width::UnicodeWidthChar;
6
7pub trait Canvas {
8    /// Get the canvas size (width, height)
9    fn size(&self) -> Result<(usize, usize)>;
10
11    /// clear the canvas
12    fn clear(&mut self) -> Result<()>;
13
14    /// change a cell of position `(row, col)` to `cell`
15    /// if `(row, col)` is out of boundary, `Ok` is returned, but no operation is taken
16    /// return the width of the character/cell
17    fn put_cell(&mut self, row: usize, col: usize, cell: Cell) -> Result<usize>;
18
19    /// just like put_cell, except it accept (char & attr)
20    /// return the width of the character/cell
21    fn put_char_with_attr(
22        &mut self,
23        row: usize,
24        col: usize,
25        ch: char,
26        attr: Attr,
27    ) -> Result<usize> {
28        self.put_cell(row, col, Cell { ch, attr })
29    }
30
31    /// print `content` starting with position `(row, col)` with `attr`
32    /// - canvas should NOT wrap to y+1 if the content is too long
33    /// - canvas should handle wide characters
34    /// return the printed width of the content
35    fn print_with_attr(
36        &mut self,
37        row: usize,
38        col: usize,
39        content: &str,
40        attr: Attr,
41    ) -> Result<usize> {
42        let mut cell = Cell {
43            attr,
44            ..Cell::default()
45        };
46
47        let mut width = 0;
48        for ch in content.chars() {
49            cell.ch = ch;
50            width += self.put_cell(row, col + width, cell)?;
51        }
52        Ok(width)
53    }
54
55    /// print `content` starting with position `(row, col)` with default attribute
56    fn print(&mut self, row: usize, col: usize, content: &str) -> Result<usize> {
57        self.print_with_attr(row, col, content, Attr::default())
58    }
59
60    /// move cursor position (row, col) and show cursor
61    fn set_cursor(&mut self, row: usize, col: usize) -> Result<()>;
62
63    /// show/hide cursor, set `show` to `false` to hide the cursor
64    fn show_cursor(&mut self, show: bool) -> Result<()>;
65}
66
67/// A sub-area of a canvas.
68/// It will handle the adjustments of cursor movement, so that you could write
69/// to for example (0, 0) and BoundedCanvas will adjust it to real position.
70pub struct BoundedCanvas<'a> {
71    canvas: &'a mut dyn Canvas,
72    top: usize,
73    left: usize,
74    width: usize,
75    height: usize,
76}
77
78impl<'a> BoundedCanvas<'a> {
79    pub fn new(
80        top: usize,
81        left: usize,
82        width: usize,
83        height: usize,
84        canvas: &'a mut dyn Canvas,
85    ) -> Self {
86        Self {
87            canvas,
88            top,
89            left,
90            width,
91            height,
92        }
93    }
94}
95
96impl<'a> Canvas for BoundedCanvas<'a> {
97    fn size(&self) -> Result<(usize, usize)> {
98        Ok((self.width, self.height))
99    }
100
101    fn clear(&mut self) -> Result<()> {
102        for row in self.top..(self.top + self.height) {
103            for col in self.left..(self.left + self.width) {
104                let _ = self.canvas.put_cell(row, col, Cell::empty());
105            }
106        }
107
108        Ok(())
109    }
110
111    fn put_cell(&mut self, row: usize, col: usize, cell: Cell) -> Result<usize> {
112        if row >= self.height || col >= self.width {
113            // do nothing
114            Ok(cell.ch.width().unwrap_or(2))
115        } else {
116            self.canvas.put_cell(row + self.top, col + self.left, cell)
117        }
118    }
119
120    fn set_cursor(&mut self, row: usize, col: usize) -> Result<()> {
121        if row >= self.height || col >= self.width {
122            // do nothing
123            Ok(())
124        } else {
125            self.canvas.set_cursor(row + self.top, col + self.left)
126        }
127    }
128
129    fn show_cursor(&mut self, show: bool) -> Result<()> {
130        self.canvas.show_cursor(show)
131    }
132}