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::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::Dot | _ => Box::new(CharGrid::new(width, height, dot)),
591        }
592    }
593
594    /// Change the marker being used in this context.
595    ///
596    /// This will save the last layer if necessary and reset the grid to use the new marker.
597    pub fn marker(&mut self, marker: Marker) {
598        self.finish();
599        self.grid = Self::marker_to_grid(self.width, self.height, marker);
600    }
601
602    /// Draw the given [`Shape`] in this context
603    pub fn draw<S>(&mut self, shape: &S)
604    where
605        S: Shape,
606    {
607        self.dirty = true;
608        let mut painter = Painter::from(self);
609        shape.draw(&mut painter);
610    }
611
612    /// Save the existing state of the grid as a layer.
613    ///
614    /// Save the existing state as a layer to be rendered and reset the grid to its initial
615    /// state for the next layer.
616    ///
617    /// This allows the canvas to be drawn in multiple layers. This is useful if you want to
618    /// draw multiple shapes on the [`Canvas`] in specific order.
619    pub fn layer(&mut self) {
620        self.layers.push(self.grid.save());
621        self.grid.reset();
622        self.dirty = false;
623    }
624
625    /// Print a [`Text`] on the [`Canvas`] at the given position.
626    ///
627    /// Note that the text is always printed on top of the canvas and is **not** affected by the
628    /// layers.
629    ///
630    /// [`Text`]: ratatui_core::text::Text
631    pub fn print<T>(&mut self, x: f64, y: f64, line: T)
632    where
633        T: Into<TextLine<'a>>,
634    {
635        self.labels.push(Label {
636            x,
637            y,
638            line: line.into(),
639        });
640    }
641
642    /// Save the last layer if necessary
643    fn finish(&mut self) {
644        if self.dirty {
645            self.layer();
646        }
647    }
648}
649
650/// The Canvas widget provides a means to draw shapes (Lines, Rectangles, Circles, etc.) on a grid.
651///
652/// By default the grid is made of Braille patterns but you may change the marker to use a different
653/// set of symbols. If your terminal or font does not support this unicode block, you will see
654/// unicode replacement characters (�) instead of braille dots. The Braille patterns (as well the
655/// octant character patterns) provide a more fine grained result with a 2x4 resolution per
656/// character, but you might want to use a simple dot, block, or bar instead by calling the
657/// [`marker`] method if your target environment does not support those symbols.
658///
659/// See [Unicode Braille Patterns](https://en.wikipedia.org/wiki/Braille_Patterns) for more info.
660///
661/// The `Octant` marker is similar to the `Braille` marker but, instead of sparse dots, displays
662/// densely packed and regularly spaced pseudo-pixels, without visible bands between rows and
663/// columns. However, it uses characters that are not yet as widely supported as the Braille
664/// unicode block.
665///
666/// The `Quadrant` and `Sextant` markers are in turn akin to the `Octant` marker, but with a 2x2
667/// and 2x3 resolution, respectively.
668///
669/// The `HalfBlock` marker is useful when you want to draw shapes with a higher resolution than a
670/// `CharGrid` but lower than a `PatternGrid`. This grid type supports a foreground and background
671/// color for each terminal cell. This allows for more flexibility than the `PatternGrid` which
672/// only supports a single foreground color for each 2x4 dots cell.
673///
674/// The Canvas widget is used by calling the [`Canvas::paint`] method and passing a closure that
675/// will be used to draw on the canvas. The closure will be passed a [`Context`] object that can be
676/// used to draw shapes on the canvas.
677///
678/// The [`Context`] object provides a [`Context::draw`] method that can be used to draw shapes on
679/// the canvas. The [`Context::layer`] method can be used to save the current state of the canvas
680/// and start a new layer. This is useful if you want to draw multiple shapes on the canvas in
681/// specific order. The [`Context`] object also provides a [`Context::print`] method that can be
682/// used to print text on the canvas. Note that the text is always printed on top of the canvas and
683/// is not affected by the layers.
684///
685/// # Examples
686///
687/// ```
688/// use ratatui::style::Color;
689/// use ratatui::widgets::Block;
690/// use ratatui::widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle};
691///
692/// Canvas::default()
693///     .block(Block::bordered().title("Canvas"))
694///     .x_bounds([-180.0, 180.0])
695///     .y_bounds([-90.0, 90.0])
696///     .paint(|ctx| {
697///         ctx.draw(&Map {
698///             resolution: MapResolution::High,
699///             color: Color::White,
700///         });
701///         ctx.layer();
702///         ctx.draw(&Line {
703///             x1: 0.0,
704///             y1: 10.0,
705///             x2: 10.0,
706///             y2: 10.0,
707///             color: Color::White,
708///         });
709///         ctx.draw(&Rectangle {
710///             x: 10.0,
711///             y: 20.0,
712///             width: 10.0,
713///             height: 10.0,
714///             color: Color::Red,
715///         });
716///     });
717/// ```
718///
719/// [`marker`]: #method.marker
720#[derive(Debug, Clone, PartialEq)]
721pub struct Canvas<'a, F>
722where
723    F: Fn(&mut Context),
724{
725    block: Option<Block<'a>>,
726    x_bounds: [f64; 2],
727    y_bounds: [f64; 2],
728    paint_func: Option<F>,
729    background_color: Color,
730    marker: Marker,
731}
732
733impl<F> Default for Canvas<'_, F>
734where
735    F: Fn(&mut Context),
736{
737    fn default() -> Self {
738        Self {
739            block: None,
740            x_bounds: [0.0, 0.0],
741            y_bounds: [0.0, 0.0],
742            paint_func: None,
743            background_color: Color::Reset,
744            marker: Marker::Braille,
745        }
746    }
747}
748
749impl<'a, F> Canvas<'a, F>
750where
751    F: Fn(&mut Context),
752{
753    /// Wraps the canvas with a custom [`Block`] widget.
754    ///
755    /// This is a fluent setter method which must be chained or used as it consumes self
756    #[must_use = "method moves the value of self and returns the modified value"]
757    pub fn block(mut self, block: Block<'a>) -> Self {
758        self.block = Some(block);
759        self
760    }
761
762    /// Define the viewport of the canvas.
763    ///
764    /// If you were to "zoom" to a certain part of the world you may want to choose different
765    /// bounds.
766    ///
767    /// This is a fluent setter method which must be chained or used as it consumes self
768    #[must_use = "method moves the value of self and returns the modified value"]
769    pub const fn x_bounds(mut self, bounds: [f64; 2]) -> Self {
770        self.x_bounds = bounds;
771        self
772    }
773
774    /// Define the viewport of the canvas.
775    ///
776    /// If you were to "zoom" to a certain part of the world you may want to choose different
777    /// bounds.
778    ///
779    /// This is a fluent setter method which must be chained or used as it consumes self
780    #[must_use = "method moves the value of self and returns the modified value"]
781    pub const fn y_bounds(mut self, bounds: [f64; 2]) -> Self {
782        self.y_bounds = bounds;
783        self
784    }
785
786    /// Store the closure that will be used to draw to the [`Canvas`]
787    ///
788    /// This is a fluent setter method which must be chained or used as it consumes self
789    #[must_use = "method moves the value of self and returns the modified value"]
790    pub fn paint(mut self, f: F) -> Self {
791        self.paint_func = Some(f);
792        self
793    }
794
795    /// Change the background [`Color`] of the entire canvas
796    ///
797    /// This is a fluent setter method which must be chained or used as it consumes self
798    #[must_use = "method moves the value of self and returns the modified value"]
799    pub const fn background_color(mut self, color: Color) -> Self {
800        self.background_color = color;
801        self
802    }
803
804    /// Change the type of points used to draw the shapes.
805    ///
806    /// By default the [`Braille`] patterns are used as they provide a more fine grained result,
807    /// but you might want to use the simple [`Dot`] or [`Block`] instead if the targeted terminal
808    /// does not support those symbols.
809    ///
810    /// The [`HalfBlock`] marker is useful when you want to draw shapes with a higher resolution
811    /// than with a grid of characters (e.g. with [`Block`] or [`Dot`]) but lower than with
812    /// [`Braille`]. This grid type supports a foreground and background color for each terminal
813    /// cell. This allows for more flexibility than the `PatternGrid` which only supports a single
814    /// foreground color for each 2x4 dots cell.
815    ///
816    /// [`Braille`]: ratatui_core::symbols::Marker::Braille
817    /// [`HalfBlock`]: ratatui_core::symbols::Marker::HalfBlock
818    /// [`Dot`]: ratatui_core::symbols::Marker::Dot
819    /// [`Block`]: ratatui_core::symbols::Marker::Block
820    ///
821    /// # Examples
822    ///
823    /// ```
824    /// use ratatui::symbols;
825    /// use ratatui::widgets::canvas::Canvas;
826    ///
827    /// Canvas::default()
828    ///     .marker(symbols::Marker::Braille)
829    ///     .paint(|ctx| {});
830    ///
831    /// Canvas::default()
832    ///     .marker(symbols::Marker::HalfBlock)
833    ///     .paint(|ctx| {});
834    ///
835    /// Canvas::default()
836    ///     .marker(symbols::Marker::Dot)
837    ///     .paint(|ctx| {});
838    ///
839    /// Canvas::default()
840    ///     .marker(symbols::Marker::Block)
841    ///     .paint(|ctx| {});
842    /// ```
843    #[must_use = "method moves the value of self and returns the modified value"]
844    pub const fn marker(mut self, marker: Marker) -> Self {
845        self.marker = marker;
846        self
847    }
848}
849
850impl<F> Widget for Canvas<'_, F>
851where
852    F: Fn(&mut Context),
853{
854    fn render(self, area: Rect, buf: &mut Buffer) {
855        Widget::render(&self, area, buf);
856    }
857}
858
859impl<F> Widget for &Canvas<'_, F>
860where
861    F: Fn(&mut Context),
862{
863    fn render(self, area: Rect, buf: &mut Buffer) {
864        self.block.as_ref().render(area, buf);
865        let canvas_area = self.block.inner_if_some(area);
866        if canvas_area.is_empty() {
867            return;
868        }
869
870        buf.set_style(canvas_area, Style::default().bg(self.background_color));
871
872        let width = canvas_area.width as usize;
873
874        let Some(ref painter) = self.paint_func else {
875            return;
876        };
877
878        // Create a blank context that match the size of the canvas
879        let mut ctx = Context::new(
880            canvas_area.width,
881            canvas_area.height,
882            self.x_bounds,
883            self.y_bounds,
884            self.marker,
885        );
886        // Paint to this context
887        painter(&mut ctx);
888        ctx.finish();
889
890        // Retrieve painted points for each layer
891        for layer in ctx.layers {
892            for (index, layer_cell) in layer.contents.iter().enumerate() {
893                let (x, y) = (
894                    (index % width) as u16 + canvas_area.left(),
895                    (index / width) as u16 + canvas_area.top(),
896                );
897                let cell = &mut buf[(x, y)];
898
899                if let Some(symbol) = layer_cell.symbol {
900                    cell.set_char(symbol);
901                }
902                if let Some(fg) = layer_cell.fg {
903                    cell.set_fg(fg);
904                }
905                if let Some(bg) = layer_cell.bg {
906                    cell.set_bg(bg);
907                }
908            }
909        }
910
911        // Finally draw the labels
912        let left = self.x_bounds[0];
913        let right = self.x_bounds[1];
914        let top = self.y_bounds[1];
915        let bottom = self.y_bounds[0];
916        let width = (self.x_bounds[1] - self.x_bounds[0]).abs();
917        let height = (self.y_bounds[1] - self.y_bounds[0]).abs();
918        let resolution = {
919            let width = f64::from(canvas_area.width - 1);
920            let height = f64::from(canvas_area.height - 1);
921            (width, height)
922        };
923        for label in ctx
924            .labels
925            .iter()
926            .filter(|l| l.x >= left && l.x <= right && l.y <= top && l.y >= bottom)
927        {
928            let x = ((label.x - left) * resolution.0 / width) as u16 + canvas_area.left();
929            let y = ((top - label.y) * resolution.1 / height) as u16 + canvas_area.top();
930            buf.set_line(x, y, &label.line, canvas_area.right() - x);
931        }
932    }
933}
934
935#[cfg(test)]
936mod tests {
937    use indoc::indoc;
938    use ratatui_core::buffer::Cell;
939    use rstest::rstest;
940
941    use super::*;
942
943    #[rstest]
944    #[case::block(Marker::Block, indoc!(
945                "
946                █xxxx
947                █xxxx
948                █xxxx
949                █xxxx
950                █████"
951            ))]
952    #[case::half_block(Marker::HalfBlock, indoc!(
953                "
954                █xxxx
955                █xxxx
956                █xxxx
957                █xxxx
958                █▄▄▄▄"
959            ))]
960    #[case::bar(Marker::Bar, indoc!(
961                "
962                ▄xxxx
963                ▄xxxx
964                ▄xxxx
965                ▄xxxx
966                ▄▄▄▄▄"
967            ))]
968    #[case::braille(Marker::Braille, indoc!(
969                "
970                ⡇xxxx
971                ⡇xxxx
972                ⡇xxxx
973                ⡇xxxx
974                ⣇⣀⣀⣀⣀"
975            ))]
976    #[case::quadrant(Marker::Quadrant, indoc!(
977                "
978                ▌xxxx
979                ▌xxxx
980                ▌xxxx
981                ▌xxxx
982                ▙▄▄▄▄"
983            ))]
984    #[case::sextant(Marker::Sextant, indoc!(
985                "
986                ▌xxxx
987                ▌xxxx
988                ▌xxxx
989                ▌xxxx
990                🬲🬭🬭🬭🬭"
991            ))]
992    #[case::octant(Marker::Octant, indoc!(
993                "
994                ▌xxxx
995                ▌xxxx
996                ▌xxxx
997                ▌xxxx
998                𜷀▂▂▂▂"
999            ))]
1000    #[case::dot(Marker::Dot, indoc!(
1001                "
1002                •xxxx
1003                •xxxx
1004                •xxxx
1005                •xxxx
1006                •••••"
1007            ))]
1008    fn test_horizontal_with_vertical(#[case] marker: Marker, #[case] expected: &'static str) {
1009        let area = Rect::new(0, 0, 5, 5);
1010        let mut buf = Buffer::filled(area, Cell::new("x"));
1011        let horizontal_line = Line {
1012            x1: 0.0,
1013            y1: 0.0,
1014            x2: 10.0,
1015            y2: 0.0,
1016            color: Color::Reset,
1017        };
1018        let vertical_line = Line {
1019            x1: 0.0,
1020            y1: 0.0,
1021            x2: 0.0,
1022            y2: 10.0,
1023            color: Color::Reset,
1024        };
1025        Canvas::default()
1026            .marker(marker)
1027            .paint(|ctx| {
1028                ctx.draw(&vertical_line);
1029                ctx.draw(&horizontal_line);
1030            })
1031            .x_bounds([0.0, 10.0])
1032            .y_bounds([0.0, 10.0])
1033            .render(area, &mut buf);
1034        assert_eq!(buf, Buffer::with_lines(expected.lines()));
1035    }
1036
1037    #[rstest]
1038    #[case::block(Marker::Block, indoc!(
1039                "
1040                █xxx█
1041                x█x█x
1042                xx█xx
1043                x█x█x
1044                █xxx█"))]
1045    #[case::half_block(Marker::HalfBlock,
1046           indoc!(
1047                "
1048                █xxx█
1049                x█x█x
1050                xx█xx
1051                x█x█x
1052                █xxx█")
1053    )]
1054    #[case::bar(Marker::Bar, indoc!(
1055                "
1056                ▄xxx▄
1057                x▄x▄x
1058                xx▄xx
1059                x▄x▄x
1060                ▄xxx▄"))]
1061    #[case::braille(Marker::Braille, indoc!(
1062                "
1063                ⢣xxx⡜
1064                x⢣x⡜x
1065                xx⣿xx
1066                x⡜x⢣x
1067                ⡜xxx⢣"
1068            ))]
1069    #[case::quadrant(Marker::Quadrant, indoc!(
1070                "
1071                ▚xxx▞
1072                x▚x▞x
1073                xx█xx
1074                x▞x▚x
1075                ▞xxx▚"
1076            ))]
1077    #[case::sextant(Marker::Sextant, indoc!(
1078                "
1079                🬧xxx🬔
1080                x🬧x🬔x
1081                xx█xx
1082                x🬘x🬣x
1083                🬘xxx🬣"
1084            ))]
1085    #[case::octant(Marker::Octant, indoc!(
1086                "
1087                ▚xxx▞
1088                x▚x▞x
1089                xx█xx
1090                x▞x▚x
1091                ▞xxx▚"
1092            ))]
1093    #[case::dot(Marker::Dot, indoc!(
1094                "
1095                •xxx•
1096                x•x•x
1097                xx•xx
1098                x•x•x
1099                •xxx•"
1100            ))]
1101    fn test_diagonal_lines(#[case] marker: Marker, #[case] expected: &'static str) {
1102        let area = Rect::new(0, 0, 5, 5);
1103        let mut buf = Buffer::filled(area, Cell::new("x"));
1104        let diagonal_up = Line {
1105            x1: 0.0,
1106            y1: 0.0,
1107            x2: 10.0,
1108            y2: 10.0,
1109            color: Color::Reset,
1110        };
1111        let diagonal_down = Line {
1112            x1: 0.0,
1113            y1: 10.0,
1114            x2: 10.0,
1115            y2: 0.0,
1116            color: Color::Reset,
1117        };
1118        Canvas::default()
1119            .marker(marker)
1120            .paint(|ctx| {
1121                ctx.draw(&diagonal_down);
1122                ctx.draw(&diagonal_up);
1123            })
1124            .x_bounds([0.0, 10.0])
1125            .y_bounds([0.0, 10.0])
1126            .render(area, &mut buf);
1127        assert_eq!(buf, Buffer::with_lines(expected.lines()));
1128    }
1129
1130    // The canvas methods work a lot with arithmetic so here we enter various width and height
1131    // values to check if there are any integer overflows we just initialize the canvas painters
1132    #[test]
1133    fn check_canvas_paint_max() {
1134        let mut b_grid = PatternGrid::<2, 4>::new(u16::MAX, 2, &OCTANTS);
1135        let mut c_grid = CharGrid::new(u16::MAX, 2, 'd');
1136
1137        let max = u16::MAX as usize;
1138
1139        b_grid.paint(0, 0, Color::Red);
1140        b_grid.paint(0, max, Color::Red);
1141        b_grid.paint(max, 0, Color::Red);
1142        b_grid.paint(max, max, Color::Red);
1143
1144        c_grid.paint(0, 0, Color::Red);
1145        c_grid.paint(0, max, Color::Red);
1146        c_grid.paint(max, 0, Color::Red);
1147        c_grid.paint(max, max, Color::Red);
1148    }
1149
1150    // We delibately cause integer overflow to check if we don't panic and don't get weird behavior
1151    #[test]
1152    fn check_canvas_paint_overflow() {
1153        let mut b_grid = PatternGrid::<2, 4>::new(u16::MAX, 3, &BRAILLE);
1154        let mut c_grid = CharGrid::new(u16::MAX, 3, 'd');
1155
1156        let max = u16::MAX as usize + 10;
1157
1158        // see if we can paint outside bounds
1159        b_grid.paint(max, max, Color::Red);
1160        c_grid.paint(max, max, Color::Red);
1161        // see if we can paint usize max bounds
1162        b_grid.paint(usize::MAX, usize::MAX, Color::Red);
1163        c_grid.paint(usize::MAX, usize::MAX, Color::Red);
1164    }
1165
1166    #[test]
1167    fn render_in_minimal_buffer() {
1168        let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
1169        let canvas = Canvas::default()
1170            .x_bounds([0.0, 10.0])
1171            .y_bounds([0.0, 10.0])
1172            .paint(|_ctx| {});
1173        // This should not panic, even if the buffer is too small to render the canvas.
1174        canvas.render(buffer.area, &mut buffer);
1175        assert_eq!(buffer, Buffer::with_lines([" "]));
1176    }
1177
1178    #[test]
1179    fn render_in_zero_size_buffer() {
1180        let mut buffer = Buffer::empty(Rect::ZERO);
1181        let canvas = Canvas::default()
1182            .x_bounds([0.0, 10.0])
1183            .y_bounds([0.0, 10.0])
1184            .paint(|_ctx| {});
1185        // This should not panic, even if the buffer has zero size.
1186        canvas.render(buffer.area, &mut buffer);
1187    }
1188}