Skip to main content

ratatui_widgets/
canvas.rs

1//! A [`Canvas`] and a collection of [`Shape`]s.
2//!
3//! The [`Canvas`] is a blank space on which you can draw anything manually or use one of the
4//! predefined [`Shape`]s.
5//!
6//! The available shapes are:
7//!
8//! - [`Circle`]: A basic circle
9//! - [`Line`]: A line between two points
10//! - [`Map`]: A world map
11//! - [`Points`]: A scatter of points
12//! - [`Rectangle`]: A basic rectangle
13//!
14//! You can also implement your own custom [`Shape`]s.
15
16use alloc::boxed::Box;
17use alloc::vec;
18use alloc::vec::Vec;
19use core::fmt;
20use core::iter::zip;
21
22use itertools::Itertools;
23use ratatui_core::buffer::Buffer;
24use ratatui_core::layout::Rect;
25use ratatui_core::style::{Color, Style};
26use ratatui_core::symbols::braille::BRAILLE;
27use ratatui_core::symbols::pixel::{OCTANTS, QUADRANTS, SEXTANTS};
28use ratatui_core::symbols::{self, Marker};
29use ratatui_core::text::Line as TextLine;
30use ratatui_core::widgets::Widget;
31
32pub use self::circle::Circle;
33pub use self::line::{FilledLine, Line};
34pub use self::map::{Map, MapResolution};
35pub use self::points::Points;
36pub use self::rectangle::Rectangle;
37use crate::block::{Block, BlockExt};
38#[cfg(not(feature = "std"))]
39use crate::polyfills::F64Polyfills;
40
41mod circle;
42mod line;
43mod map;
44mod points;
45mod rectangle;
46mod world;
47
48/// Something that can be drawn on a [`Canvas`].
49///
50/// You may implement your own canvas custom widgets by implementing this trait.
51pub trait Shape {
52    /// Draws this [`Shape`] using the given [`Painter`].
53    ///
54    /// This is the only method required to implement a custom widget that can be drawn on a
55    /// [`Canvas`].
56    fn draw(&self, painter: &mut Painter);
57}
58
59/// Label to draw some text on the canvas
60#[derive(Debug, Default, Clone, PartialEq)]
61pub struct Label<'a> {
62    x: f64,
63    y: f64,
64    line: TextLine<'a>,
65}
66
67/// A single layer of the canvas.
68///
69/// This allows the canvas to be drawn in multiple layers. This is useful if you want to draw
70/// multiple shapes on the canvas in specific order.
71#[derive(Debug)]
72struct Layer {
73    contents: Vec<LayerCell>,
74}
75
76/// A cell within a layer.
77///
78/// If a [`Context`] contains multiple layers, then the symbol, foreground, and background colors
79/// for a character will be determined by the top-most layer that provides a value for that
80/// character. For example, a chart drawn with [`Marker::Block`] may provide the background color,
81/// and a later chart drawn with [`Marker::Braille`] may provide the symbol and foreground color.
82#[derive(Debug)]
83struct LayerCell {
84    symbol: Option<char>,
85    fg: Option<Color>,
86    bg: Option<Color>,
87}
88
89/// A grid of cells that can be painted on.
90///
91/// The grid represents a particular screen region measured in rows and columns. The underlying
92/// resolution of the grid might exceed the number of rows and columns. For example, a grid of
93/// Braille patterns will have a resolution of 2x4 dots per cell. This means that a grid of 10x10
94/// cells will have a resolution of 20x40 dots.
95trait Grid: fmt::Debug {
96    /// Get the resolution of the grid in number of dots.
97    ///
98    /// This doesn't have to be the same as the number of rows and columns of the grid. For example,
99    /// a grid of Braille patterns will have a resolution of 2x4 dots per cell. This means that a
100    /// grid of 10x10 cells will have a resolution of 20x40 dots.
101    fn resolution(&self) -> (f64, f64);
102    /// Paint a point of the grid.
103    ///
104    /// The point is expressed in number of dots starting at the origin of the grid in the top left
105    /// corner. Note that this is not the same as the `(x, y)` coordinates of the canvas.
106    fn paint(&mut self, x: usize, y: usize, color: Color);
107    /// Save the current state of the [`Grid`] as a layer to be rendered
108    fn save(&self) -> Layer;
109    /// Reset the grid to its initial state
110    fn reset(&mut self);
111}
112
113/// The pattern and color of a `PatternGrid` cell.
114#[derive(Copy, Clone, Debug, Default)]
115struct PatternCell {
116    /// The pattern of a grid character.
117    ///
118    /// The pattern is stored in the lower bits in a row-major order. For instance, for a 2x4
119    /// pattern marker, bits 0 to 7 of this field should represent the following pseudo-pixels:
120    ///
121    /// | 0 1 |
122    /// | 2 3 |
123    /// | 4 5 |
124    /// | 6 7 |
125    pattern: u8,
126    /// The color of a cell only supports foreground colors for now as there's no way to
127    /// individually set the background color of each pseudo-pixel in a pattern character.
128    color: Option<Color>,
129}
130
131/// The `PatternGrid` is a grid made up of cells each containing a `W`x`H` pattern character.
132///
133/// This makes it possible to draw shapes with a resolution of e.g. 2x4 (Braille or unicode octant)
134/// per cell.
135/// Font support for the relevant pattern character is required. If your terminal or font does not
136/// support the relevant unicode block, you will see unicode replacement characters (�) instead.
137///
138/// This grid type only supports a single foreground color for each `W`x`H` pattern character.
139/// There is no way to set the individual color of each pseudo-pixel.
140#[derive(Debug)]
141struct PatternGrid<const W: usize, const H: usize> {
142    /// Width of the grid in number of terminal columns
143    width: u16,
144    /// Height of the grid in number of terminal rows
145    height: u16,
146    /// Pattern and color of the cells.
147    cells: Vec<PatternCell>,
148    /// Lookup table mapping patterns to characters.
149    char_table: &'static [char],
150}
151
152impl<const W: usize, const H: usize> PatternGrid<W, H> {
153    /// Statically check that the dimension of the pattern is supported.
154    const _PATTERN_DIMENSION_CHECK: usize = u8::BITS as usize - W * H;
155
156    /// Create a new `PatternGrid` with the given width and height measured in terminal columns
157    /// and rows respectively.
158    fn new(width: u16, height: u16, char_table: &'static [char]) -> Self {
159        // Cause a static error if the pattern doesn't fit within 8 bits.
160        let _ = Self::_PATTERN_DIMENSION_CHECK;
161
162        let length = usize::from(width) * usize::from(height);
163        Self {
164            width,
165            height,
166            cells: vec![PatternCell::default(); length],
167            char_table,
168        }
169    }
170}
171
172impl<const W: usize, const H: usize> Grid for PatternGrid<W, H> {
173    fn resolution(&self) -> (f64, f64) {
174        (
175            f64::from(self.width) * W as f64,
176            f64::from(self.height) * H as f64,
177        )
178    }
179
180    fn save(&self) -> Layer {
181        let contents = self
182            .cells
183            .iter()
184            .map(|&cell| {
185                let symbol = match cell.pattern {
186                    // Skip rendering blank patterns to allow layers underneath
187                    // to show through.
188                    0 => None,
189                    idx => Some(self.char_table[idx as usize]),
190                };
191
192                LayerCell {
193                    symbol,
194                    fg: cell.color,
195                    // Patterns only affect foreground.
196                    bg: None,
197                }
198            })
199            .collect();
200
201        Layer { contents }
202    }
203
204    fn reset(&mut self) {
205        self.cells.fill_with(Default::default);
206    }
207
208    fn paint(&mut self, x: usize, y: usize, color: Color) {
209        let index = y
210            .saturating_div(H)
211            .saturating_mul(self.width as usize)
212            .saturating_add(x.saturating_div(W));
213        // using get_mut here because we are indexing the vector with usize values
214        // and we want to make sure we don't panic if the index is out of bounds
215        if let Some(cell) = self.cells.get_mut(index) {
216            cell.pattern |= 1u8 << ((x % W) + W * (y % H));
217            cell.color = Some(color);
218        }
219    }
220}
221
222/// The `CharGrid` is a grid made up of cells each containing a single character.
223///
224/// This makes it possible to draw shapes with a resolution of 1x1 dots per cell. This is useful
225/// when you want to draw shapes with a low resolution.
226#[derive(Debug)]
227struct CharGrid {
228    /// Width of the grid in number of terminal columns
229    width: u16,
230    /// Height of the grid in number of terminal rows
231    height: u16,
232    /// The color of each cell
233    cells: Vec<Option<Color>>,
234
235    /// The character to use for every cell - e.g. a block, dot, etc.
236    cell_char: char,
237
238    /// If true, apply the color to the background as well as the foreground. This is used for
239    /// [`Marker::Block`], so that it will overwrite any previous foreground character, but also
240    /// leave a background that can be overlaid with an additional foreground character.
241    apply_color_to_bg: bool,
242}
243
244impl CharGrid {
245    /// Create a new `CharGrid` with the given width and height measured in terminal columns and
246    /// rows respectively.
247    fn new(width: u16, height: u16, cell_char: char) -> Self {
248        let length = usize::from(width) * usize::from(height);
249        Self {
250            width,
251            height,
252            cells: vec![None; length],
253            cell_char,
254            apply_color_to_bg: false,
255        }
256    }
257
258    fn apply_color_to_bg(self) -> Self {
259        Self {
260            apply_color_to_bg: true,
261            ..self
262        }
263    }
264}
265
266impl Grid for CharGrid {
267    fn resolution(&self) -> (f64, f64) {
268        (f64::from(self.width), f64::from(self.height))
269    }
270
271    fn save(&self) -> Layer {
272        Layer {
273            contents: self
274                .cells
275                .iter()
276                .map(|&color| LayerCell {
277                    symbol: color.map(|_| self.cell_char),
278                    fg: color,
279                    bg: color.filter(|_| self.apply_color_to_bg),
280                })
281                .collect(),
282        }
283    }
284
285    fn reset(&mut self) {
286        self.cells.fill(None);
287    }
288
289    fn paint(&mut self, x: usize, y: usize, color: Color) {
290        let index = y.saturating_mul(self.width as usize).saturating_add(x);
291        // using get_mut here because we are indexing the vector with usize values
292        // and we want to make sure we don't panic if the index is out of bounds
293        if let Some(c) = self.cells.get_mut(index) {
294            *c = Some(color);
295        }
296    }
297}
298
299/// The `HalfBlockGrid` is a grid made up of cells each containing a half block character.
300///
301/// In terminals, each character is usually twice as tall as it is wide. Unicode has a couple of
302/// vertical half block characters, the upper half block '▀' and lower half block '▄' which take up
303/// half the height of a normal character but the full width. Together with an empty space ' ' and a
304/// full block '█', we can effectively double the resolution of a single cell. In addition, because
305/// each character can have a foreground and background color, we can control the color of the upper
306/// and lower half of each cell. This allows us to draw shapes with a resolution of 1x2 "pixels" per
307/// cell.
308///
309/// This allows for more flexibility than the `PatternGrid` which only supports a single
310/// foreground color for each 2x4 dots cell, and the `CharGrid` which only supports a single
311/// character for each cell.
312#[derive(Debug)]
313struct HalfBlockGrid {
314    /// Width of the grid in number of terminal columns
315    width: u16,
316    /// Height of the grid in number of terminal rows
317    height: u16,
318    /// Represents a single color for each "pixel" arranged in column, row order
319    pixels: Vec<Vec<Option<Color>>>,
320}
321
322impl HalfBlockGrid {
323    /// Create a new `HalfBlockGrid` with the given width and height measured in terminal columns
324    /// and rows respectively.
325    fn new(width: u16, height: u16) -> Self {
326        Self {
327            width,
328            height,
329            pixels: vec![vec![None; width as usize]; (height as usize) * 2],
330        }
331    }
332}
333
334impl Grid for HalfBlockGrid {
335    fn resolution(&self) -> (f64, f64) {
336        (f64::from(self.width), f64::from(self.height) * 2.0)
337    }
338
339    fn save(&self) -> Layer {
340        // Given that we store the pixels in a grid, and that we want to use 2 pixels arranged
341        // vertically to form a single terminal cell, which can be either empty, upper half block,
342        // lower half block or full block, we need examine the pixels in vertical pairs to decide
343        // what character to print in each cell. So these are the 4 states we use to represent each
344        // cell:
345        //
346        // 1. upper: reset, lower: reset => ' ' fg: reset / bg: reset
347        // 2. upper: reset, lower: color => '▄' fg: lower color / bg: reset
348        // 3. upper: color, lower: reset => '▀' fg: upper color / bg: reset
349        // 4. upper: color, lower: color => '▀' fg: upper color / bg: lower color
350        //
351        // Note that because the foreground reset color (i.e. default foreground color) is usually
352        // not the same as the background reset color (i.e. default background color), we need to
353        // swap around the colors for that state (2 reset/color).
354        //
355        // When the upper and lower colors are the same, we could continue to use an upper half
356        // block, but we choose to use a full block instead. This allows us to write unit tests that
357        // treat the cell as a single character instead of two half block characters.
358
359        // first we join each adjacent row together to get an iterator that contains vertical pairs
360        // of pixels, with the lower row being the first element in the pair
361        let vertical_color_pairs = self
362            .pixels
363            .iter()
364            .tuples()
365            .flat_map(|(upper_row, lower_row)| zip(upper_row, lower_row));
366
367        // Then we determine the character to print for each pair, along with the color of the
368        // foreground and background.
369        let contents = vertical_color_pairs
370            .map(|(upper, lower)| {
371                let (symbol, fg, bg) = match (upper, lower) {
372                    (None, None) => (None, None, None),
373                    (None, Some(lower)) => (Some(symbols::half_block::LOWER), Some(*lower), None),
374                    (Some(upper), None) => (Some(symbols::half_block::UPPER), Some(*upper), None),
375                    (Some(upper), Some(lower)) if lower == upper => {
376                        (Some(symbols::half_block::FULL), Some(*upper), Some(*lower))
377                    }
378                    (Some(upper), Some(lower)) => {
379                        (Some(symbols::half_block::UPPER), Some(*upper), Some(*lower))
380                    }
381                };
382                LayerCell { symbol, fg, bg }
383            })
384            .collect();
385
386        Layer { contents }
387    }
388
389    fn reset(&mut self) {
390        self.pixels.fill(vec![None; self.width as usize]);
391    }
392
393    fn paint(&mut self, x: usize, y: usize, color: Color) {
394        self.pixels[y][x] = Some(color);
395    }
396}
397
398/// Painter is an abstraction over the [`Context`] that allows to draw shapes on the grid.
399///
400/// It is used by the [`Shape`] trait to draw shapes on the grid. It can be useful to think of this
401/// as similar to the [`Buffer`] struct that is used to draw widgets on the terminal.
402#[derive(Debug)]
403pub struct Painter<'a, 'b> {
404    context: &'a mut Context<'b>,
405    resolution: (f64, f64),
406}
407
408impl Painter<'_, '_> {
409    /// Convert the `(x, y)` coordinates to location of a point on the grid
410    ///
411    /// `(x, y)` coordinates are expressed in the coordinate system of the canvas. The origin is in
412    /// the lower left corner of the canvas (unlike most other coordinates in `Ratatui` where the
413    /// origin is the upper left corner). The `x` and `y` bounds of the canvas define the specific
414    /// area of some coordinate system that will be drawn on the canvas. The resolution of the grid
415    /// is used to convert the `(x, y)` coordinates to the location of a point on the grid.
416    ///
417    /// The grid coordinates are expressed in the coordinate system of the grid. The origin is in
418    /// the top left corner of the grid. The x and y bounds of the grid are always `[0, width - 1]`
419    /// and `[0, height - 1]` respectively. The resolution of the grid is used to convert the
420    /// `(x, y)` coordinates to the location of a point on the grid.
421    ///
422    /// Points are rounded to the nearest grid cell (with points exactly in the center of a cell
423    /// rounding up).
424    ///
425    /// # Examples
426    ///
427    /// ```
428    /// use ratatui::symbols;
429    /// use ratatui::widgets::canvas::{Context, Painter};
430    ///
431    /// let mut ctx = Context::new(2, 2, [1.0, 2.0], [0.0, 2.0], symbols::Marker::Braille);
432    /// let mut painter = Painter::from(&mut ctx);
433    ///
434    /// let point = painter.get_point(1.0, 0.0);
435    /// assert_eq!(point, Some((0, 7)));
436    ///
437    /// let point = painter.get_point(1.5, 1.0);
438    /// assert_eq!(point, Some((2, 4)));
439    ///
440    /// let point = painter.get_point(0.0, 0.0);
441    /// assert_eq!(point, None);
442    ///
443    /// let point = painter.get_point(2.0, 2.0);
444    /// assert_eq!(point, Some((3, 0)));
445    ///
446    /// let point = painter.get_point(1.0, 2.0);
447    /// assert_eq!(point, Some((0, 0)));
448    /// ```
449    pub fn get_point(&self, x: f64, y: f64) -> Option<(usize, usize)> {
450        let [left, right] = self.context.x_bounds;
451        let [bottom, top] = self.context.y_bounds;
452        if x < left || x > right || y < bottom || y > top {
453            return None;
454        }
455        let width = right - left;
456        let height = top - bottom;
457        if width <= 0.0 || height <= 0.0 {
458            return None;
459        }
460        let x = ((x - left) * (self.resolution.0 - 1.0) / width).round() as usize;
461        let y = ((top - y) * (self.resolution.1 - 1.0) / height).round() as usize;
462        Some((x, y))
463    }
464
465    /// Paint a point of the grid
466    ///
467    /// # Example
468    ///
469    /// ```
470    /// use ratatui::style::Color;
471    /// use ratatui::symbols;
472    /// use ratatui::widgets::canvas::{Context, Painter};
473    ///
474    /// let mut ctx = Context::new(1, 1, [0.0, 2.0], [0.0, 2.0], symbols::Marker::Braille);
475    /// let mut painter = Painter::from(&mut ctx);
476    /// painter.paint(1, 3, Color::Red);
477    /// ```
478    pub fn paint(&mut self, x: usize, y: usize, color: Color) {
479        self.context.grid.paint(x, y, color);
480    }
481
482    /// Canvas context bounds by axis.
483    ///
484    /// # Example
485    ///
486    /// ```
487    /// use ratatui::style::Color;
488    /// use ratatui::symbols;
489    /// use ratatui::widgets::canvas::{Context, Painter};
490    ///
491    /// let mut ctx = Context::new(1, 1, [0.0, 2.0], [0.0, 2.0], symbols::Marker::Braille);
492    /// let mut painter = Painter::from(&mut ctx);
493    /// assert_eq!(painter.bounds(), (&[0.0, 2.0], &[0.0, 2.0]));
494    /// ```
495    pub const fn bounds(&self) -> (&[f64; 2], &[f64; 2]) {
496        (&self.context.x_bounds, &self.context.y_bounds)
497    }
498}
499
500impl<'a, 'b> From<&'a mut Context<'b>> for Painter<'a, 'b> {
501    fn from(context: &'a mut Context<'b>) -> Self {
502        let resolution = context.grid.resolution();
503        Self {
504            context,
505            resolution,
506        }
507    }
508}
509
510/// Holds the state of the [`Canvas`] when painting to it.
511///
512/// This is used by the [`Canvas`] widget to draw shapes on the grid. It can be useful to think of
513/// this as similar to the `Frame` struct that is used to draw widgets on the terminal.
514#[derive(Debug)]
515pub struct Context<'a> {
516    // Width of the canvas in cells.
517    //
518    // This is NOT the resolution in dots/pixels as this varies by marker type.
519    width: u16,
520    // Height of the canvas in cells.
521    //
522    // This is NOT the resolution in dots/pixels as this varies by marker type.
523    height: u16,
524    // Canvas coordinate system width
525    x_bounds: [f64; 2],
526    // Canvas coordinate system height
527    y_bounds: [f64; 2],
528    grid: Box<dyn Grid>,
529    dirty: bool,
530    layers: Vec<Layer>,
531    labels: Vec<Label<'a>>,
532}
533
534impl<'a> Context<'a> {
535    /// Create a new Context with the given width and height measured in terminal columns and rows
536    /// respectively. The `x` and `y` bounds define the specific area of some coordinate system that
537    /// will be drawn on the canvas. The marker defines the type of points used to draw the shapes.
538    ///
539    /// Applications should not use this directly but rather use the [`Canvas`] widget. This will be
540    /// created by the [`Canvas::paint`] method and passed to the closure that is used to draw on
541    /// the canvas.
542    ///
543    /// The `x` and `y` bounds should be specified as left/right and bottom/top respectively. For
544    /// example, if you want to draw a map of the world, you might want to use the following bounds:
545    ///
546    /// ```
547    /// use ratatui::symbols;
548    /// use ratatui::widgets::canvas::Context;
549    ///
550    /// let ctx = Context::new(
551    ///     100,
552    ///     100,
553    ///     [-180.0, 180.0],
554    ///     [-90.0, 90.0],
555    ///     symbols::Marker::Braille,
556    /// );
557    /// ```
558    pub fn new(
559        width: u16,
560        height: u16,
561        x_bounds: [f64; 2],
562        y_bounds: [f64; 2],
563        marker: Marker,
564    ) -> Self {
565        let grid = Self::marker_to_grid(width, height, marker);
566        Self {
567            width,
568            height,
569            x_bounds,
570            y_bounds,
571            grid,
572            dirty: false,
573            layers: Vec::new(),
574            labels: Vec::new(),
575        }
576    }
577
578    fn marker_to_grid(width: u16, height: u16, marker: Marker) -> Box<dyn Grid> {
579        let dot = symbols::DOT.chars().next().unwrap();
580        let block = symbols::block::FULL.chars().next().unwrap();
581        let bar = symbols::bar::HALF.chars().next().unwrap();
582        match marker {
583            Marker::Block => Box::new(CharGrid::new(width, height, block).apply_color_to_bg()),
584            Marker::Bar => Box::new(CharGrid::new(width, height, bar)),
585            Marker::Braille => Box::new(PatternGrid::<2, 4>::new(width, height, &BRAILLE)),
586            Marker::HalfBlock => Box::new(HalfBlockGrid::new(width, height)),
587            Marker::Quadrant => Box::new(PatternGrid::<2, 2>::new(width, height, &QUADRANTS)),
588            Marker::Sextant => Box::new(PatternGrid::<2, 3>::new(width, height, &SEXTANTS)),
589            Marker::Octant => Box::new(PatternGrid::<2, 4>::new(width, height, &OCTANTS)),
590            Marker::Custom(char) => Box::new(CharGrid::new(width, height, char)),
591            Marker::Dot | _ => Box::new(CharGrid::new(width, height, dot)),
592        }
593    }
594
595    /// Change the marker being used in this context.
596    ///
597    /// This will save the last layer if necessary and reset the grid to use the new marker.
598    pub fn marker(&mut self, marker: Marker) {
599        self.finish();
600        self.grid = Self::marker_to_grid(self.width, self.height, marker);
601    }
602
603    /// Draw the given [`Shape`] in this context
604    pub fn draw<S>(&mut self, shape: &S)
605    where
606        S: Shape,
607    {
608        self.dirty = true;
609        let mut painter = Painter::from(self);
610        shape.draw(&mut painter);
611    }
612
613    /// Save the existing state of the grid as a layer.
614    ///
615    /// Save the existing state as a layer to be rendered and reset the grid to its initial
616    /// state for the next layer.
617    ///
618    /// This allows the canvas to be drawn in multiple layers. This is useful if you want to
619    /// draw multiple shapes on the [`Canvas`] in specific order.
620    pub fn layer(&mut self) {
621        self.layers.push(self.grid.save());
622        self.grid.reset();
623        self.dirty = false;
624    }
625
626    /// Print a [`Text`] on the [`Canvas`] at the given position.
627    ///
628    /// Note that the text is always printed on top of the canvas and is **not** affected by the
629    /// layers.
630    ///
631    /// [`Text`]: ratatui_core::text::Text
632    pub fn print<T>(&mut self, x: f64, y: f64, line: T)
633    where
634        T: Into<TextLine<'a>>,
635    {
636        self.labels.push(Label {
637            x,
638            y,
639            line: line.into(),
640        });
641    }
642
643    /// Save the last layer if necessary
644    fn finish(&mut self) {
645        if self.dirty {
646            self.layer();
647        }
648    }
649}
650
651/// The Canvas widget provides a means to draw shapes (Lines, Rectangles, Circles, etc.) on a grid.
652///
653/// By default the grid is made of Braille patterns but you may change the marker to use a different
654/// set of symbols. If your terminal or font does not support this unicode block, you will see
655/// unicode replacement characters (�) instead of braille dots. The Braille patterns (as well the
656/// octant character patterns) provide a more fine grained result with a 2x4 resolution per
657/// character, but you might want to use a simple dot, block, or bar instead by calling the
658/// [`marker`] method if your target environment does not support those symbols.
659///
660/// See [Unicode Braille Patterns](https://en.wikipedia.org/wiki/Braille_Patterns) for more info.
661///
662/// The `Octant` marker is similar to the `Braille` marker but, instead of sparse dots, displays
663/// densely packed and regularly spaced pseudo-pixels, without visible bands between rows and
664/// columns. However, it uses characters that are not yet as widely supported as the Braille
665/// unicode block.
666///
667/// The `Quadrant` and `Sextant` markers are in turn akin to the `Octant` marker, but with a 2x2
668/// and 2x3 resolution, respectively.
669///
670/// The `HalfBlock` marker is useful when you want to draw shapes with a higher resolution than a
671/// `CharGrid` but lower than a `PatternGrid`. This grid type supports a foreground and background
672/// color for each terminal cell. This allows for more flexibility than the `PatternGrid` which
673/// only supports a single foreground color for each 2x4 dots cell.
674///
675/// The Canvas widget is used by calling the [`Canvas::paint`] method and passing a closure that
676/// will be used to draw on the canvas. The closure will be passed a [`Context`] object that can be
677/// used to draw shapes on the canvas.
678///
679/// The [`Context`] object provides a [`Context::draw`] method that can be used to draw shapes on
680/// the canvas. The [`Context::layer`] method can be used to save the current state of the canvas
681/// and start a new layer. This is useful if you want to draw multiple shapes on the canvas in
682/// specific order. The [`Context`] object also provides a [`Context::print`] method that can be
683/// used to print text on the canvas. Note that the text is always printed on top of the canvas and
684/// is not affected by the layers.
685///
686/// # Examples
687///
688/// ```
689/// use ratatui::style::Color;
690/// use ratatui::widgets::Block;
691/// use ratatui::widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle};
692///
693/// Canvas::default()
694///     .block(Block::bordered().title("Canvas"))
695///     .x_bounds([-180.0, 180.0])
696///     .y_bounds([-90.0, 90.0])
697///     .paint(|ctx| {
698///         ctx.draw(&Map {
699///             resolution: MapResolution::High,
700///             color: Color::White,
701///         });
702///         ctx.layer();
703///         ctx.draw(&Line {
704///             x1: 0.0,
705///             y1: 10.0,
706///             x2: 10.0,
707///             y2: 10.0,
708///             color: Color::White,
709///         });
710///         ctx.draw(&Rectangle {
711///             x: 10.0,
712///             y: 20.0,
713///             width: 10.0,
714///             height: 10.0,
715///             color: Color::Red,
716///         });
717///     });
718/// ```
719///
720/// [`marker`]: #method.marker
721#[derive(Debug, Clone, PartialEq)]
722pub struct Canvas<'a, F>
723where
724    F: Fn(&mut Context),
725{
726    block: Option<Block<'a>>,
727    x_bounds: [f64; 2],
728    y_bounds: [f64; 2],
729    paint_func: Option<F>,
730    background_color: Color,
731    marker: Marker,
732}
733
734impl<F> Default for Canvas<'_, F>
735where
736    F: Fn(&mut Context),
737{
738    fn default() -> Self {
739        Self {
740            block: None,
741            x_bounds: [0.0, 0.0],
742            y_bounds: [0.0, 0.0],
743            paint_func: None,
744            background_color: Color::Reset,
745            marker: Marker::Braille,
746        }
747    }
748}
749
750impl<'a, F> Canvas<'a, F>
751where
752    F: Fn(&mut Context),
753{
754    /// Wraps the canvas with a custom [`Block`] widget.
755    ///
756    /// This is a fluent setter method which must be chained or used as it consumes self
757    #[must_use = "method moves the value of self and returns the modified value"]
758    pub fn block(mut self, block: Block<'a>) -> Self {
759        self.block = Some(block);
760        self
761    }
762
763    /// Define the viewport of the canvas.
764    ///
765    /// If you were to "zoom" to a certain part of the world you may want to choose different
766    /// bounds.
767    ///
768    /// This is a fluent setter method which must be chained or used as it consumes self
769    #[must_use = "method moves the value of self and returns the modified value"]
770    pub const fn x_bounds(mut self, bounds: [f64; 2]) -> Self {
771        self.x_bounds = bounds;
772        self
773    }
774
775    /// Define the viewport of the canvas.
776    ///
777    /// If you were to "zoom" to a certain part of the world you may want to choose different
778    /// bounds.
779    ///
780    /// This is a fluent setter method which must be chained or used as it consumes self
781    #[must_use = "method moves the value of self and returns the modified value"]
782    pub const fn y_bounds(mut self, bounds: [f64; 2]) -> Self {
783        self.y_bounds = bounds;
784        self
785    }
786
787    /// Store the closure that will be used to draw to the [`Canvas`]
788    ///
789    /// This is a fluent setter method which must be chained or used as it consumes self
790    #[must_use = "method moves the value of self and returns the modified value"]
791    pub fn paint(mut self, f: F) -> Self {
792        self.paint_func = Some(f);
793        self
794    }
795
796    /// Change the background [`Color`] of the entire canvas
797    ///
798    /// This is a fluent setter method which must be chained or used as it consumes self
799    #[must_use = "method moves the value of self and returns the modified value"]
800    pub const fn background_color(mut self, color: Color) -> Self {
801        self.background_color = color;
802        self
803    }
804
805    /// Change the type of points used to draw the shapes.
806    ///
807    /// By default the [`Braille`] patterns are used as they provide a more fine grained result,
808    /// but you might want to use the simple [`Dot`] or [`Block`] instead if the targeted terminal
809    /// does not support those symbols.
810    ///
811    /// The [`HalfBlock`] marker is useful when you want to draw shapes with a higher resolution
812    /// than with a grid of characters (e.g. with [`Block`] or [`Dot`]) but lower than with
813    /// [`Braille`]. This grid type supports a foreground and background color for each terminal
814    /// cell. This allows for more flexibility than the `PatternGrid` which only supports a single
815    /// foreground color for each 2x4 dots cell.
816    ///
817    /// [`Braille`]: ratatui_core::symbols::Marker::Braille
818    /// [`HalfBlock`]: ratatui_core::symbols::Marker::HalfBlock
819    /// [`Dot`]: ratatui_core::symbols::Marker::Dot
820    /// [`Block`]: ratatui_core::symbols::Marker::Block
821    ///
822    /// # Examples
823    ///
824    /// ```
825    /// use ratatui::symbols;
826    /// use ratatui::widgets::canvas::Canvas;
827    ///
828    /// Canvas::default()
829    ///     .marker(symbols::Marker::Braille)
830    ///     .paint(|ctx| {});
831    ///
832    /// Canvas::default()
833    ///     .marker(symbols::Marker::HalfBlock)
834    ///     .paint(|ctx| {});
835    ///
836    /// Canvas::default()
837    ///     .marker(symbols::Marker::Dot)
838    ///     .paint(|ctx| {});
839    ///
840    /// Canvas::default()
841    ///     .marker(symbols::Marker::Block)
842    ///     .paint(|ctx| {});
843    /// ```
844    #[must_use = "method moves the value of self and returns the modified value"]
845    pub const fn marker(mut self, marker: Marker) -> Self {
846        self.marker = marker;
847        self
848    }
849}
850
851impl<F> Widget for Canvas<'_, F>
852where
853    F: Fn(&mut Context),
854{
855    fn render(self, area: Rect, buf: &mut Buffer) {
856        Widget::render(&self, area, buf);
857    }
858}
859
860impl<F> Widget for &Canvas<'_, F>
861where
862    F: Fn(&mut Context),
863{
864    fn render(self, area: Rect, buf: &mut Buffer) {
865        self.block.as_ref().render(area, buf);
866        let canvas_area = self.block.inner_if_some(area);
867        if canvas_area.is_empty() {
868            return;
869        }
870
871        buf.set_style(canvas_area, Style::default().bg(self.background_color));
872
873        let width = canvas_area.width as usize;
874
875        let Some(ref painter) = self.paint_func else {
876            return;
877        };
878
879        // Create a blank context that match the size of the canvas
880        let mut ctx = Context::new(
881            canvas_area.width,
882            canvas_area.height,
883            self.x_bounds,
884            self.y_bounds,
885            self.marker,
886        );
887        // Paint to this context
888        painter(&mut ctx);
889        ctx.finish();
890
891        // Retrieve painted points for each layer
892        for layer in ctx.layers {
893            for (index, layer_cell) in layer.contents.iter().enumerate() {
894                let (x, y) = (
895                    (index % width) as u16 + canvas_area.left(),
896                    (index / width) as u16 + canvas_area.top(),
897                );
898                let cell = &mut buf[(x, y)];
899
900                if let Some(symbol) = layer_cell.symbol {
901                    cell.set_char(symbol);
902                }
903                if let Some(fg) = layer_cell.fg {
904                    cell.set_fg(fg);
905                }
906                if let Some(bg) = layer_cell.bg {
907                    cell.set_bg(bg);
908                }
909            }
910        }
911
912        // Finally draw the labels
913        let left = self.x_bounds[0];
914        let right = self.x_bounds[1];
915        let top = self.y_bounds[1];
916        let bottom = self.y_bounds[0];
917        let width = (self.x_bounds[1] - self.x_bounds[0]).abs();
918        let height = (self.y_bounds[1] - self.y_bounds[0]).abs();
919        let resolution = {
920            let width = f64::from(canvas_area.width - 1);
921            let height = f64::from(canvas_area.height - 1);
922            (width, height)
923        };
924        for label in ctx
925            .labels
926            .iter()
927            .filter(|l| l.x >= left && l.x <= right && l.y <= top && l.y >= bottom)
928        {
929            let x = ((label.x - left) * resolution.0 / width) as u16 + canvas_area.left();
930            let y = ((top - label.y) * resolution.1 / height) as u16 + canvas_area.top();
931            buf.set_line(x, y, &label.line, canvas_area.right() - x);
932        }
933    }
934}
935
936#[cfg(test)]
937mod tests {
938    use indoc::indoc;
939    use ratatui_core::buffer::Cell;
940    use rstest::rstest;
941
942    use super::*;
943
944    #[rstest]
945    #[case::block(Marker::Block, indoc!(
946                "
947                █xxxx
948                █xxxx
949                █xxxx
950                █xxxx
951                █████"
952            ))]
953    #[case::half_block(Marker::HalfBlock, indoc!(
954                "
955                █xxxx
956                █xxxx
957                █xxxx
958                █xxxx
959                █▄▄▄▄"
960            ))]
961    #[case::bar(Marker::Bar, indoc!(
962                "
963                ▄xxxx
964                ▄xxxx
965                ▄xxxx
966                ▄xxxx
967                ▄▄▄▄▄"
968            ))]
969    #[case::braille(Marker::Braille, indoc!(
970                "
971                ⡇xxxx
972                ⡇xxxx
973                ⡇xxxx
974                ⡇xxxx
975                ⣇⣀⣀⣀⣀"
976            ))]
977    #[case::quadrant(Marker::Quadrant, indoc!(
978                "
979                ▌xxxx
980                ▌xxxx
981                ▌xxxx
982                ▌xxxx
983                ▙▄▄▄▄"
984            ))]
985    #[case::sextant(Marker::Sextant, indoc!(
986                "
987                ▌xxxx
988                ▌xxxx
989                ▌xxxx
990                ▌xxxx
991                🬲🬭🬭🬭🬭"
992            ))]
993    #[case::octant(Marker::Octant, indoc!(
994                "
995                ▌xxxx
996                ▌xxxx
997                ▌xxxx
998                ▌xxxx
999                𜷀▂▂▂▂"
1000            ))]
1001    #[case::x_sign(Marker::Custom('×'), indoc!(
1002                "
1003                ×xxxx
1004                ×xxxx
1005                ×xxxx
1006                ×xxxx
1007                ×××××"
1008            ))]
1009    #[case::plus_sign(Marker::Custom('+'), indoc!(
1010                "
1011                +xxxx
1012                +xxxx
1013                +xxxx
1014                +xxxx
1015                +++++"
1016            ))]
1017    #[case::dot(Marker::Dot, indoc!(
1018                "
1019                •xxxx
1020                •xxxx
1021                •xxxx
1022                •xxxx
1023                •••••"
1024            ))]
1025    fn test_horizontal_with_vertical(#[case] marker: Marker, #[case] expected: &'static str) {
1026        let area = Rect::new(0, 0, 5, 5);
1027        let mut buf = Buffer::filled(area, Cell::new("x"));
1028        let horizontal_line = Line {
1029            x1: 0.0,
1030            y1: 0.0,
1031            x2: 10.0,
1032            y2: 0.0,
1033            color: Color::Reset,
1034        };
1035        let vertical_line = Line {
1036            x1: 0.0,
1037            y1: 0.0,
1038            x2: 0.0,
1039            y2: 10.0,
1040            color: Color::Reset,
1041        };
1042        Canvas::default()
1043            .marker(marker)
1044            .paint(|ctx| {
1045                ctx.draw(&vertical_line);
1046                ctx.draw(&horizontal_line);
1047            })
1048            .x_bounds([0.0, 10.0])
1049            .y_bounds([0.0, 10.0])
1050            .render(area, &mut buf);
1051        assert_eq!(buf, Buffer::with_lines(expected.lines()));
1052    }
1053
1054    #[rstest]
1055    #[case::block(Marker::Block, indoc!(
1056                "
1057                █xxx█
1058                x█x█x
1059                xx█xx
1060                x█x█x
1061                █xxx█"))]
1062    #[case::half_block(Marker::HalfBlock,
1063           indoc!(
1064                "
1065                █xxx█
1066                x█x█x
1067                xx█xx
1068                x█x█x
1069                █xxx█")
1070    )]
1071    #[case::bar(Marker::Bar, indoc!(
1072                "
1073                ▄xxx▄
1074                x▄x▄x
1075                xx▄xx
1076                x▄x▄x
1077                ▄xxx▄"))]
1078    #[case::braille(Marker::Braille, indoc!(
1079                "
1080                ⢣xxx⡜
1081                x⢣x⡜x
1082                xx⣿xx
1083                x⡜x⢣x
1084                ⡜xxx⢣"
1085            ))]
1086    #[case::quadrant(Marker::Quadrant, indoc!(
1087                "
1088                ▚xxx▞
1089                x▚x▞x
1090                xx█xx
1091                x▞x▚x
1092                ▞xxx▚"
1093            ))]
1094    #[case::sextant(Marker::Sextant, indoc!(
1095                "
1096                🬧xxx🬔
1097                x🬧x🬔x
1098                xx█xx
1099                x🬘x🬣x
1100                🬘xxx🬣"
1101            ))]
1102    #[case::octant(Marker::Octant, indoc!(
1103                "
1104                ▚xxx▞
1105                x▚x▞x
1106                xx█xx
1107                x▞x▚x
1108                ▞xxx▚"
1109            ))]
1110    #[case::x_sign(Marker::Custom('×'), indoc!(
1111                "
1112                ×xxx×
1113                x×x×x
1114                xx×xx
1115                x×x×x
1116                ×xxx×"
1117            ))]
1118    #[case::plus_sign(Marker::Custom('+'), indoc!(
1119                "
1120                +xxx+
1121                x+x+x
1122                xx+xx
1123                x+x+x
1124                +xxx+"
1125            ))]
1126    #[case::dot(Marker::Dot, indoc!(
1127                "
1128                •xxx•
1129                x•x•x
1130                xx•xx
1131                x•x•x
1132                •xxx•"
1133            ))]
1134    fn test_diagonal_lines(#[case] marker: Marker, #[case] expected: &'static str) {
1135        let area = Rect::new(0, 0, 5, 5);
1136        let mut buf = Buffer::filled(area, Cell::new("x"));
1137        let diagonal_up = Line {
1138            x1: 0.0,
1139            y1: 0.0,
1140            x2: 10.0,
1141            y2: 10.0,
1142            color: Color::Reset,
1143        };
1144        let diagonal_down = Line {
1145            x1: 0.0,
1146            y1: 10.0,
1147            x2: 10.0,
1148            y2: 0.0,
1149            color: Color::Reset,
1150        };
1151        Canvas::default()
1152            .marker(marker)
1153            .paint(|ctx| {
1154                ctx.draw(&diagonal_down);
1155                ctx.draw(&diagonal_up);
1156            })
1157            .x_bounds([0.0, 10.0])
1158            .y_bounds([0.0, 10.0])
1159            .render(area, &mut buf);
1160        assert_eq!(buf, Buffer::with_lines(expected.lines()));
1161    }
1162
1163    // The canvas methods work a lot with arithmetic so here we enter various width and height
1164    // values to check if there are any integer overflows we just initialize the canvas painters
1165    #[test]
1166    fn check_canvas_paint_max() {
1167        let mut b_grid = PatternGrid::<2, 4>::new(u16::MAX, 2, &OCTANTS);
1168        let mut c_grid = CharGrid::new(u16::MAX, 2, 'd');
1169
1170        let max = u16::MAX as usize;
1171
1172        b_grid.paint(0, 0, Color::Red);
1173        b_grid.paint(0, max, Color::Red);
1174        b_grid.paint(max, 0, Color::Red);
1175        b_grid.paint(max, max, Color::Red);
1176
1177        c_grid.paint(0, 0, Color::Red);
1178        c_grid.paint(0, max, Color::Red);
1179        c_grid.paint(max, 0, Color::Red);
1180        c_grid.paint(max, max, Color::Red);
1181    }
1182
1183    // We delibately cause integer overflow to check if we don't panic and don't get weird behavior
1184    #[test]
1185    fn check_canvas_paint_overflow() {
1186        let mut b_grid = PatternGrid::<2, 4>::new(u16::MAX, 3, &BRAILLE);
1187        let mut c_grid = CharGrid::new(u16::MAX, 3, 'd');
1188
1189        let max = u16::MAX as usize + 10;
1190
1191        // see if we can paint outside bounds
1192        b_grid.paint(max, max, Color::Red);
1193        c_grid.paint(max, max, Color::Red);
1194        // see if we can paint usize max bounds
1195        b_grid.paint(usize::MAX, usize::MAX, Color::Red);
1196        c_grid.paint(usize::MAX, usize::MAX, Color::Red);
1197    }
1198
1199    #[test]
1200    fn render_in_minimal_buffer() {
1201        let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
1202        let canvas = Canvas::default()
1203            .x_bounds([0.0, 10.0])
1204            .y_bounds([0.0, 10.0])
1205            .paint(|_ctx| {});
1206        // This should not panic, even if the buffer is too small to render the canvas.
1207        canvas.render(buffer.area, &mut buffer);
1208        assert_eq!(buffer, Buffer::with_lines([" "]));
1209    }
1210
1211    #[test]
1212    fn render_in_zero_size_buffer() {
1213        let mut buffer = Buffer::empty(Rect::ZERO);
1214        let canvas = Canvas::default()
1215            .x_bounds([0.0, 10.0])
1216            .y_bounds([0.0, 10.0])
1217            .paint(|_ctx| {});
1218        // This should not panic, even if the buffer has zero size.
1219        canvas.render(buffer.area, &mut buffer);
1220    }
1221}