tui_temp_fork/widgets/canvas/
mod.rs

1mod line;
2mod map;
3mod points;
4mod rectangle;
5mod world;
6
7pub use self::line::Line;
8pub use self::map::{Map, MapResolution};
9pub use self::points::Points;
10pub use self::rectangle::Rectangle;
11
12use crate::buffer::Buffer;
13use crate::layout::Rect;
14use crate::style::{Color, Style};
15use crate::widgets::{Block, Widget};
16
17pub const DOTS: [[u16; 2]; 4] = [
18    [0x0001, 0x0008],
19    [0x0002, 0x0010],
20    [0x0004, 0x0020],
21    [0x0040, 0x0080],
22];
23pub const BRAILLE_OFFSET: u16 = 0x2800;
24pub const BRAILLE_BLANK: char = '⠀';
25
26/// Interface for all shapes that may be drawn on a Canvas widget.
27pub trait Shape<'a> {
28    /// Returns the color of the shape
29    fn color(&self) -> Color;
30    /// Returns an iterator over all points of the shape
31    fn points(&'a self) -> Box<dyn Iterator<Item = (f64, f64)> + 'a>;
32}
33
34/// Label to draw some text on the canvas
35pub struct Label<'a> {
36    pub x: f64,
37    pub y: f64,
38    pub text: &'a str,
39    pub color: Color,
40}
41
42struct Layer {
43    string: String,
44    colors: Vec<Color>,
45}
46
47struct Grid {
48    cells: Vec<u16>,
49    colors: Vec<Color>,
50}
51
52impl Grid {
53    fn new(width: usize, height: usize) -> Grid {
54        Grid {
55            cells: vec![BRAILLE_OFFSET; width * height],
56            colors: vec![Color::Reset; width * height],
57        }
58    }
59
60    fn save(&self) -> Layer {
61        Layer {
62            string: String::from_utf16(&self.cells).unwrap(),
63            colors: self.colors.clone(),
64        }
65    }
66
67    fn reset(&mut self) {
68        for c in &mut self.cells {
69            *c = BRAILLE_OFFSET;
70        }
71        for c in &mut self.colors {
72            *c = Color::Reset;
73        }
74    }
75}
76
77/// Holds the state of the Canvas when painting to it.
78pub struct Context<'a> {
79    width: u16,
80    height: u16,
81    x_bounds: [f64; 2],
82    y_bounds: [f64; 2],
83    grid: Grid,
84    dirty: bool,
85    layers: Vec<Layer>,
86    labels: Vec<Label<'a>>,
87}
88
89impl<'a> Context<'a> {
90    /// Draw any object that may implement the Shape trait
91    pub fn draw<'b, S>(&mut self, shape: &'b S)
92    where
93        S: Shape<'b>,
94    {
95        self.dirty = true;
96        let left = self.x_bounds[0];
97        let right = self.x_bounds[1];
98        let bottom = self.y_bounds[0];
99        let top = self.y_bounds[1];
100        for (x, y) in shape
101            .points()
102            .filter(|&(x, y)| !(x <= left || x >= right || y <= bottom || y >= top))
103        {
104            let dy = ((top - y) * f64::from(self.height - 1) * 4.0 / (top - bottom)) as usize;
105            let dx = ((x - left) * f64::from(self.width - 1) * 2.0 / (right - left)) as usize;
106            let index = dy / 4 * self.width as usize + dx / 2;
107            self.grid.cells[index] |= DOTS[dy % 4][dx % 2];
108            self.grid.colors[index] = shape.color();
109        }
110    }
111
112    /// Go one layer above in the canvas.
113    pub fn layer(&mut self) {
114        self.layers.push(self.grid.save());
115        self.grid.reset();
116        self.dirty = false;
117    }
118
119    /// Print a string on the canvas at the given position
120    pub fn print(&mut self, x: f64, y: f64, text: &'a str, color: Color) {
121        self.labels.push(Label { x, y, text, color });
122    }
123
124    /// Push the last layer if necessary
125    fn finish(&mut self) {
126        if self.dirty {
127            self.layer()
128        }
129    }
130}
131
132/// The Canvas widget may be used to draw more detailed figures using braille patterns (each
133/// cell can have a braille character in 8 different positions).
134/// # Examples
135///
136/// ```
137/// # use tui_temp_fork::widgets::{Block, Borders};
138/// # use tui_temp_fork::layout::Rect;
139/// # use tui_temp_fork::widgets::canvas::{Canvas, Shape, Line, Rectangle, Map, MapResolution};
140/// # use tui_temp_fork::style::Color;
141/// # fn main() {
142/// Canvas::default()
143///     .block(Block::default().title("Canvas").borders(Borders::ALL))
144///     .x_bounds([-180.0, 180.0])
145///     .y_bounds([-90.0, 90.0])
146///     .paint(|ctx| {
147///         ctx.draw(&Map {
148///             resolution: MapResolution::High,
149///             color: Color::White
150///         });
151///         ctx.layer();
152///         ctx.draw(&Line {
153///             x1: 0.0,
154///             y1: 10.0,
155///             x2: 10.0,
156///             y2: 10.0,
157///             color: Color::White,
158///         });
159///         ctx.draw(&Rectangle {
160///             rect: Rect {
161///                 x: 10,
162///                 y: 20,
163///                 width: 10,
164///                 height: 10,
165///             },
166///             color: Color::Red
167///         });
168///     });
169/// # }
170/// ```
171pub struct Canvas<'a, F>
172where
173    F: Fn(&mut Context),
174{
175    block: Option<Block<'a>>,
176    x_bounds: [f64; 2],
177    y_bounds: [f64; 2],
178    painter: Option<F>,
179    background_color: Color,
180}
181
182impl<'a, F> Default for Canvas<'a, F>
183where
184    F: Fn(&mut Context),
185{
186    fn default() -> Canvas<'a, F> {
187        Canvas {
188            block: None,
189            x_bounds: [0.0, 0.0],
190            y_bounds: [0.0, 0.0],
191            painter: None,
192            background_color: Color::Reset,
193        }
194    }
195}
196
197impl<'a, F> Canvas<'a, F>
198where
199    F: Fn(&mut Context),
200{
201    pub fn block(mut self, block: Block<'a>) -> Canvas<'a, F> {
202        self.block = Some(block);
203        self
204    }
205    pub fn x_bounds(mut self, bounds: [f64; 2]) -> Canvas<'a, F> {
206        self.x_bounds = bounds;
207        self
208    }
209    pub fn y_bounds(mut self, bounds: [f64; 2]) -> Canvas<'a, F> {
210        self.y_bounds = bounds;
211        self
212    }
213
214    /// Store the closure that will be used to draw to the Canvas
215    pub fn paint(mut self, f: F) -> Canvas<'a, F> {
216        self.painter = Some(f);
217        self
218    }
219
220    pub fn background_color(mut self, color: Color) -> Canvas<'a, F> {
221        self.background_color = color;
222        self
223    }
224}
225
226impl<'a, F> Widget for Canvas<'a, F>
227where
228    F: Fn(&mut Context),
229{
230    fn draw(&mut self, area: Rect, buf: &mut Buffer) {
231        let canvas_area = match self.block {
232            Some(ref mut b) => {
233                b.draw(area, buf);
234                b.inner(area)
235            }
236            None => area,
237        };
238
239        let width = canvas_area.width as usize;
240        let height = canvas_area.height as usize;
241
242        if let Some(ref painter) = self.painter {
243            // Create a blank context that match the size of the terminal
244            let mut ctx = Context {
245                x_bounds: self.x_bounds,
246                y_bounds: self.y_bounds,
247                width: canvas_area.width,
248                height: canvas_area.height,
249                grid: Grid::new(width, height),
250                dirty: false,
251                layers: Vec::new(),
252                labels: Vec::new(),
253            };
254            // Paint to this context
255            painter(&mut ctx);
256            ctx.finish();
257
258            // Retreive painted points for each layer
259            for layer in ctx.layers {
260                for (i, (ch, color)) in layer
261                    .string
262                    .chars()
263                    .zip(layer.colors.into_iter())
264                    .enumerate()
265                {
266                    if ch != BRAILLE_BLANK {
267                        let (x, y) = (i % width, i / width);
268                        buf.get_mut(x as u16 + canvas_area.left(), y as u16 + canvas_area.top())
269                            .set_char(ch)
270                            .set_fg(color)
271                            .set_bg(self.background_color);
272                    }
273                }
274            }
275
276            // Finally draw the labels
277            let style = Style::default().bg(self.background_color);
278            for label in ctx.labels.iter().filter(|l| {
279                !(l.x < self.x_bounds[0]
280                    || l.x > self.x_bounds[1]
281                    || l.y < self.y_bounds[0]
282                    || l.y > self.y_bounds[1])
283            }) {
284                let dy = ((self.y_bounds[1] - label.y) * f64::from(canvas_area.height - 1)
285                    / (self.y_bounds[1] - self.y_bounds[0])) as u16;
286                let dx = ((label.x - self.x_bounds[0]) * f64::from(canvas_area.width - 1)
287                    / (self.x_bounds[1] - self.x_bounds[0])) as u16;
288                buf.set_string(
289                    dx + canvas_area.left(),
290                    dy + canvas_area.top(),
291                    label.text,
292                    style.fg(label.color),
293                );
294            }
295        }
296    }
297}