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.
15mod circle;
16mod line;
17mod map;
18mod points;
19mod rectangle;
20mod world;
21
22use std::{fmt, iter::zip};
23
24use itertools::Itertools;
25
26pub use self::{
27    circle::Circle,
28    line::Line,
29    map::{Map, MapResolution},
30    points::Points,
31    rectangle::Rectangle,
32};
33use crate::{
34    buffer::Buffer,
35    layout::Rect,
36    style::{Color, Style},
37    symbols::{self, Marker},
38    text::Line as TextLine,
39    widgets::{block::BlockExt, Block, Widget, WidgetRef},
40};
41
42/// Something that can be drawn on a [`Canvas`].
43///
44/// You may implement your own canvas custom widgets by implementing this trait.
45pub trait Shape {
46    /// Draws this [`Shape`] using the given [`Painter`].
47    ///
48    /// This is the only method required to implement a custom widget that can be drawn on a
49    /// [`Canvas`].
50    fn draw(&self, painter: &mut Painter);
51}
52
53/// Label to draw some text on the canvas
54#[derive(Debug, Default, Clone, PartialEq)]
55pub struct Label<'a> {
56    x: f64,
57    y: f64,
58    line: TextLine<'a>,
59}
60
61/// A single layer of the canvas.
62///
63/// This allows the canvas to be drawn in multiple layers. This is useful if you want to draw
64/// multiple shapes on the canvas in specific order.
65#[derive(Debug)]
66struct Layer {
67    // A string of characters representing the grid. This will be wrapped to the width of the grid
68    // when rendering
69    string: String,
70    // Colors for foreground and background of each cell
71    colors: Vec<(Color, Color)>,
72}
73
74/// A grid of cells that can be painted on.
75///
76/// The grid represents a particular screen region measured in rows and columns. The underlying
77/// resolution of the grid might exceed the number of rows and columns. For example, a grid of
78/// Braille patterns will have a resolution of 2x4 dots per cell. This means that a grid of 10x10
79/// cells will have a resolution of 20x40 dots.
80trait Grid: fmt::Debug {
81    /// Get the resolution of the grid in number of dots.
82    ///
83    /// This doesn't have to be the same as the number of rows and columns of the grid. For example,
84    /// a grid of Braille patterns will have a resolution of 2x4 dots per cell. This means that a
85    /// grid of 10x10 cells will have a resolution of 20x40 dots.
86    fn resolution(&self) -> (f64, f64);
87    /// Paint a point of the grid.
88    ///
89    /// The point is expressed in number of dots starting at the origin of the grid in the top left
90    /// corner. Note that this is not the same as the `(x, y)` coordinates of the canvas.
91    fn paint(&mut self, x: usize, y: usize, color: Color);
92    /// Save the current state of the [`Grid`] as a layer to be rendered
93    fn save(&self) -> Layer;
94    /// Reset the grid to its initial state
95    fn reset(&mut self);
96}
97
98/// The `BrailleGrid` is a grid made up of cells each containing a Braille pattern.
99///
100/// This makes it possible to draw shapes with a resolution of 2x4 dots per cell. This is useful
101/// when you want to draw shapes with a high resolution. Font support for Braille patterns is
102/// required to see the dots. If your terminal or font does not support this unicode block, you
103/// will see unicode replacement characters (�) instead of braille dots.
104///
105/// This grid type only supports a single foreground color for each 2x4 dots cell. There is no way
106/// to set the individual color of each dot in the braille pattern.
107#[derive(Debug)]
108struct BrailleGrid {
109    /// Width of the grid in number of terminal columns
110    width: u16,
111    /// Height of the grid in number of terminal rows
112    height: u16,
113    /// Represents the unicode braille patterns. Will take a value between `0x2800` and `0x28FF`
114    /// this is converted to an utf16 string when converting to a layer. See
115    /// <https://en.wikipedia.org/wiki/Braille_Patterns> for more info.
116    utf16_code_points: Vec<u16>,
117    /// The color of each cell only supports foreground colors for now as there's no way to
118    /// individually set the background color of each dot in the braille pattern.
119    colors: Vec<Color>,
120}
121
122impl BrailleGrid {
123    /// Create a new `BrailleGrid` with the given width and height measured in terminal columns and
124    /// rows respectively.
125    fn new(width: u16, height: u16) -> Self {
126        let length = usize::from(width * height);
127        Self {
128            width,
129            height,
130            utf16_code_points: vec![symbols::braille::BLANK; length],
131            colors: vec![Color::Reset; length],
132        }
133    }
134}
135
136impl Grid for BrailleGrid {
137    fn resolution(&self) -> (f64, f64) {
138        (f64::from(self.width) * 2.0, f64::from(self.height) * 4.0)
139    }
140
141    fn save(&self) -> Layer {
142        let string = String::from_utf16(&self.utf16_code_points).unwrap();
143        // the background color is always reset for braille patterns
144        let colors = self.colors.iter().map(|c| (*c, Color::Reset)).collect();
145        Layer { string, colors }
146    }
147
148    fn reset(&mut self) {
149        self.utf16_code_points.fill(symbols::braille::BLANK);
150        self.colors.fill(Color::Reset);
151    }
152
153    fn paint(&mut self, x: usize, y: usize, color: Color) {
154        let index = y / 4 * self.width as usize + x / 2;
155        // using get_mut here because we are indexing the vector with usize values
156        // and we want to make sure we don't panic if the index is out of bounds
157        if let Some(c) = self.utf16_code_points.get_mut(index) {
158            *c |= symbols::braille::DOTS[y % 4][x % 2];
159        }
160        if let Some(c) = self.colors.get_mut(index) {
161            *c = color;
162        }
163    }
164}
165
166/// The `CharGrid` is a grid made up of cells each containing a single character.
167///
168/// This makes it possible to draw shapes with a resolution of 1x1 dots per cell. This is useful
169/// when you want to draw shapes with a low resolution.
170#[derive(Debug)]
171struct CharGrid {
172    /// Width of the grid in number of terminal columns
173    width: u16,
174    /// Height of the grid in number of terminal rows
175    height: u16,
176    /// Represents a single character for each cell
177    cells: Vec<char>,
178    /// The color of each cell
179    colors: Vec<Color>,
180    /// The character to use for every cell - e.g. a block, dot, etc.
181    cell_char: char,
182}
183
184impl CharGrid {
185    /// Create a new `CharGrid` with the given width and height measured in terminal columns and
186    /// rows respectively.
187    fn new(width: u16, height: u16, cell_char: char) -> Self {
188        let length = usize::from(width * height);
189        Self {
190            width,
191            height,
192            cells: vec![' '; length],
193            colors: vec![Color::Reset; length],
194            cell_char,
195        }
196    }
197}
198
199impl Grid for CharGrid {
200    fn resolution(&self) -> (f64, f64) {
201        (f64::from(self.width), f64::from(self.height))
202    }
203
204    fn save(&self) -> Layer {
205        Layer {
206            string: self.cells.iter().collect(),
207            colors: self.colors.iter().map(|c| (*c, Color::Reset)).collect(),
208        }
209    }
210
211    fn reset(&mut self) {
212        self.cells.fill(' ');
213        self.colors.fill(Color::Reset);
214    }
215
216    fn paint(&mut self, x: usize, y: usize, color: Color) {
217        let index = y * self.width as usize + x;
218        // using get_mut here because we are indexing the vector with usize values
219        // and we want to make sure we don't panic if the index is out of bounds
220        if let Some(c) = self.cells.get_mut(index) {
221            *c = self.cell_char;
222        }
223        if let Some(c) = self.colors.get_mut(index) {
224            *c = color;
225        }
226    }
227}
228
229/// The `HalfBlockGrid` is a grid made up of cells each containing a half block character.
230///
231/// In terminals, each character is usually twice as tall as it is wide. Unicode has a couple of
232/// vertical half block characters, the upper half block '▀' and lower half block '▄' which take up
233/// half the height of a normal character but the full width. Together with an empty space ' ' and a
234/// full block '█', we can effectively double the resolution of a single cell. In addition, because
235/// each character can have a foreground and background color, we can control the color of the upper
236/// and lower half of each cell. This allows us to draw shapes with a resolution of 1x2 "pixels" per
237/// cell.
238///
239/// This allows for more flexibility than the `BrailleGrid` which only supports a single
240/// foreground color for each 2x4 dots cell, and the `CharGrid` which only supports a single
241/// character for each cell.
242#[derive(Debug)]
243struct HalfBlockGrid {
244    /// Width of the grid in number of terminal columns
245    width: u16,
246    /// Height of the grid in number of terminal rows
247    height: u16,
248    /// Represents a single color for each "pixel" arranged in column, row order
249    pixels: Vec<Vec<Color>>,
250}
251
252impl HalfBlockGrid {
253    /// Create a new `HalfBlockGrid` with the given width and height measured in terminal columns
254    /// and rows respectively.
255    fn new(width: u16, height: u16) -> Self {
256        Self {
257            width,
258            height,
259            pixels: vec![vec![Color::Reset; width as usize]; height as usize * 2],
260        }
261    }
262}
263
264impl Grid for HalfBlockGrid {
265    fn resolution(&self) -> (f64, f64) {
266        (f64::from(self.width), f64::from(self.height) * 2.0)
267    }
268
269    fn save(&self) -> Layer {
270        // Given that we store the pixels in a grid, and that we want to use 2 pixels arranged
271        // vertically to form a single terminal cell, which can be either empty, upper half block,
272        // lower half block or full block, we need examine the pixels in vertical pairs to decide
273        // what character to print in each cell. So these are the 4 states we use to represent each
274        // cell:
275        //
276        // 1. upper: reset, lower: reset => ' ' fg: reset / bg: reset
277        // 2. upper: reset, lower: color => '▄' fg: lower color / bg: reset
278        // 3. upper: color, lower: reset => '▀' fg: upper color / bg: reset
279        // 4. upper: color, lower: color => '▀' fg: upper color / bg: lower color
280        //
281        // Note that because the foreground reset color (i.e. default foreground color) is usually
282        // not the same as the background reset color (i.e. default background color), we need to
283        // swap around the colors for that state (2 reset/color).
284        //
285        // When the upper and lower colors are the same, we could continue to use an upper half
286        // block, but we choose to use a full block instead. This allows us to write unit tests that
287        // treat the cell as a single character instead of two half block characters.
288
289        // first we join each adjacent row together to get an iterator that contains vertical pairs
290        // of pixels, with the lower row being the first element in the pair
291        let vertical_color_pairs = self
292            .pixels
293            .iter()
294            .tuples()
295            .flat_map(|(upper_row, lower_row)| zip(upper_row, lower_row));
296
297        // then we work out what character to print for each pair of pixels
298        let string = vertical_color_pairs
299            .clone()
300            .map(|(upper, lower)| match (upper, lower) {
301                (Color::Reset, Color::Reset) => ' ',
302                (Color::Reset, _) => symbols::half_block::LOWER,
303                (_, Color::Reset) => symbols::half_block::UPPER,
304                (&lower, &upper) => {
305                    if lower == upper {
306                        symbols::half_block::FULL
307                    } else {
308                        symbols::half_block::UPPER
309                    }
310                }
311            })
312            .collect();
313
314        // then we convert these each vertical pair of pixels into a foreground and background color
315        let colors = vertical_color_pairs
316            .map(|(upper, lower)| {
317                let (fg, bg) = match (upper, lower) {
318                    (Color::Reset, Color::Reset) => (Color::Reset, Color::Reset),
319                    (Color::Reset, &lower) => (lower, Color::Reset),
320                    (&upper, Color::Reset) => (upper, Color::Reset),
321                    (&upper, &lower) => (upper, lower),
322                };
323                (fg, bg)
324            })
325            .collect();
326
327        Layer { string, colors }
328    }
329
330    fn reset(&mut self) {
331        self.pixels.fill(vec![Color::Reset; self.width as usize]);
332    }
333
334    fn paint(&mut self, x: usize, y: usize, color: Color) {
335        self.pixels[y][x] = color;
336    }
337}
338
339/// Painter is an abstraction over the [`Context`] that allows to draw shapes on the grid.
340///
341/// It is used by the [`Shape`] trait to draw shapes on the grid. It can be useful to think of this
342/// as similar to the [`Buffer`] struct that is used to draw widgets on the terminal.
343#[derive(Debug)]
344pub struct Painter<'a, 'b> {
345    context: &'a mut Context<'b>,
346    resolution: (f64, f64),
347}
348
349impl<'a, 'b> Painter<'a, 'b> {
350    /// Convert the `(x, y)` coordinates to location of a point on the grid
351    ///
352    /// `(x, y)` coordinates are expressed in the coordinate system of the canvas. The origin is in
353    /// the lower left corner of the canvas (unlike most other coordinates in `Ratatui` where the
354    /// origin is the upper left corner). The `x` and `y` bounds of the canvas define the specific
355    /// area of some coordinate system that will be drawn on the canvas. The resolution of the grid
356    /// is used to convert the `(x, y)` coordinates to the location of a point on the grid.
357    ///
358    /// The grid coordinates are expressed in the coordinate system of the grid. The origin is in
359    /// the top left corner of the grid. The x and y bounds of the grid are always `[0, width - 1]`
360    /// and `[0, height - 1]` respectively. The resolution of the grid is used to convert the
361    /// `(x, y)` coordinates to the location of a point on the grid.
362    ///
363    /// # Examples
364    ///
365    /// ```
366    /// use ratatui::{
367    ///     symbols,
368    ///     widgets::canvas::{Context, Painter},
369    /// };
370    ///
371    /// let mut ctx = Context::new(2, 2, [1.0, 2.0], [0.0, 2.0], symbols::Marker::Braille);
372    /// let mut painter = Painter::from(&mut ctx);
373    ///
374    /// let point = painter.get_point(1.0, 0.0);
375    /// assert_eq!(point, Some((0, 7)));
376    ///
377    /// let point = painter.get_point(1.5, 1.0);
378    /// assert_eq!(point, Some((1, 3)));
379    ///
380    /// let point = painter.get_point(0.0, 0.0);
381    /// assert_eq!(point, None);
382    ///
383    /// let point = painter.get_point(2.0, 2.0);
384    /// assert_eq!(point, Some((3, 0)));
385    ///
386    /// let point = painter.get_point(1.0, 2.0);
387    /// assert_eq!(point, Some((0, 0)));
388    /// ```
389    pub fn get_point(&self, x: f64, y: f64) -> Option<(usize, usize)> {
390        let left = self.context.x_bounds[0];
391        let right = self.context.x_bounds[1];
392        let top = self.context.y_bounds[1];
393        let bottom = self.context.y_bounds[0];
394        if x < left || x > right || y < bottom || y > top {
395            return None;
396        }
397        let width = (self.context.x_bounds[1] - self.context.x_bounds[0]).abs();
398        let height = (self.context.y_bounds[1] - self.context.y_bounds[0]).abs();
399        if width == 0.0 || height == 0.0 {
400            return None;
401        }
402        let x = ((x - left) * (self.resolution.0 - 1.0) / width) as usize;
403        let y = ((top - y) * (self.resolution.1 - 1.0) / height) as usize;
404        Some((x, y))
405    }
406
407    /// Paint a point of the grid
408    ///
409    /// # Example
410    ///
411    /// ```
412    /// use ratatui::{
413    ///     style::Color,
414    ///     symbols,
415    ///     widgets::canvas::{Context, Painter},
416    /// };
417    ///
418    /// let mut ctx = Context::new(1, 1, [0.0, 2.0], [0.0, 2.0], symbols::Marker::Braille);
419    /// let mut painter = Painter::from(&mut ctx);
420    /// painter.paint(1, 3, Color::Red);
421    /// ```
422    pub fn paint(&mut self, x: usize, y: usize, color: Color) {
423        self.context.grid.paint(x, y, color);
424    }
425}
426
427impl<'a, 'b> From<&'a mut Context<'b>> for Painter<'a, 'b> {
428    fn from(context: &'a mut Context<'b>) -> Self {
429        let resolution = context.grid.resolution();
430        Self {
431            context,
432            resolution,
433        }
434    }
435}
436
437/// Holds the state of the [`Canvas`] when painting to it.
438///
439/// This is used by the [`Canvas`] widget to draw shapes on the grid. It can be useful to think of
440/// this as similar to the [`Frame`] struct that is used to draw widgets on the terminal.
441///
442/// [`Frame`]: crate::Frame
443#[derive(Debug)]
444pub struct Context<'a> {
445    x_bounds: [f64; 2],
446    y_bounds: [f64; 2],
447    grid: Box<dyn Grid>,
448    dirty: bool,
449    layers: Vec<Layer>,
450    labels: Vec<Label<'a>>,
451}
452
453impl<'a> Context<'a> {
454    /// Create a new Context with the given width and height measured in terminal columns and rows
455    /// respectively. The `x` and `y` bounds define the specific area of some coordinate system that
456    /// will be drawn on the canvas. The marker defines the type of points used to draw the shapes.
457    ///
458    /// Applications should not use this directly but rather use the [`Canvas`] widget. This will be
459    /// created by the [`Canvas::paint`] method and passed to the closure that is used to draw on
460    /// the canvas.
461    ///
462    /// The `x` and `y` bounds should be specified as left/right and bottom/top respectively. For
463    /// example, if you want to draw a map of the world, you might want to use the following bounds:
464    ///
465    /// ```
466    /// use ratatui::{symbols, widgets::canvas::Context};
467    ///
468    /// let ctx = Context::new(
469    ///     100,
470    ///     100,
471    ///     [-180.0, 180.0],
472    ///     [-90.0, 90.0],
473    ///     symbols::Marker::Braille,
474    /// );
475    /// ```
476    pub fn new(
477        width: u16,
478        height: u16,
479        x_bounds: [f64; 2],
480        y_bounds: [f64; 2],
481        marker: Marker,
482    ) -> Self {
483        let dot = symbols::DOT.chars().next().unwrap();
484        let block = symbols::block::FULL.chars().next().unwrap();
485        let bar = symbols::bar::HALF.chars().next().unwrap();
486        let grid: Box<dyn Grid> = match marker {
487            Marker::Dot => Box::new(CharGrid::new(width, height, dot)),
488            Marker::Block => Box::new(CharGrid::new(width, height, block)),
489            Marker::Bar => Box::new(CharGrid::new(width, height, bar)),
490            Marker::Braille => Box::new(BrailleGrid::new(width, height)),
491            Marker::HalfBlock => Box::new(HalfBlockGrid::new(width, height)),
492        };
493        Self {
494            x_bounds,
495            y_bounds,
496            grid,
497            dirty: false,
498            layers: Vec::new(),
499            labels: Vec::new(),
500        }
501    }
502
503    /// Draw the given [`Shape`] in this context
504    pub fn draw<S>(&mut self, shape: &S)
505    where
506        S: Shape,
507    {
508        self.dirty = true;
509        let mut painter = Painter::from(self);
510        shape.draw(&mut painter);
511    }
512
513    /// Save the existing state of the grid as a layer.
514    ///
515    /// Save the existing state as a layer to be rendered and reset the grid to its initial
516    /// state for the next layer.
517    ///
518    /// This allows the canvas to be drawn in multiple layers. This is useful if you want to
519    /// draw multiple shapes on the [`Canvas`] in specific order.
520    pub fn layer(&mut self) {
521        self.layers.push(self.grid.save());
522        self.grid.reset();
523        self.dirty = false;
524    }
525
526    /// Print a [`Text`] on the [`Canvas`] at the given position.
527    ///
528    /// Note that the text is always printed on top of the canvas and is **not** affected by the
529    /// layers.
530    ///
531    /// [`Text`]: crate::text::Text
532    pub fn print<T>(&mut self, x: f64, y: f64, line: T)
533    where
534        T: Into<TextLine<'a>>,
535    {
536        self.labels.push(Label {
537            x,
538            y,
539            line: line.into(),
540        });
541    }
542
543    /// Save the last layer if necessary
544    fn finish(&mut self) {
545        if self.dirty {
546            self.layer();
547        }
548    }
549}
550
551/// The Canvas widget provides a means to draw shapes (Lines, Rectangles, Circles, etc.) on a grid.
552///
553/// By default the grid is made of Braille patterns but you may change the marker to use a different
554/// set of symbols. If your terminal or font does not support this unicode block, you will see
555/// unicode replacement characters (�) instead of braille dots. The Braille patterns provide a more
556/// fine grained result (2x4 dots) but you might want to use a simple dot, block, or bar instead by
557/// calling the [`marker`] method if your target environment does not support those symbols,
558///
559/// See [Unicode Braille Patterns](https://en.wikipedia.org/wiki/Braille_Patterns) for more info.
560///
561/// The `HalfBlock` marker is useful when you want to draw shapes with a higher resolution than a
562/// `CharGrid` but lower than a `BrailleGrid`. This grid type supports a foreground and background
563/// color for each terminal cell. This allows for more flexibility than the `BrailleGrid` which only
564/// supports a single foreground color for each 2x4 dots cell.
565///
566/// The Canvas widget is used by calling the [`Canvas::paint`] method and passing a closure that
567/// will be used to draw on the canvas. The closure will be passed a [`Context`] object that can be
568/// used to draw shapes on the canvas.
569///
570/// The [`Context`] object provides a [`Context::draw`] method that can be used to draw shapes on
571/// the canvas. The [`Context::layer`] method can be used to save the current state of the canvas
572/// and start a new layer. This is useful if you want to draw multiple shapes on the canvas in
573/// specific order. The [`Context`] object also provides a [`Context::print`] method that can be
574/// used to print text on the canvas. Note that the text is always printed on top of the canvas and
575/// is not affected by the layers.
576///
577/// # Examples
578///
579/// ```
580/// use ratatui::{
581///     style::Color,
582///     widgets::{
583///         canvas::{Canvas, Line, Map, MapResolution, Rectangle},
584///         Block,
585///     },
586/// };
587///
588/// Canvas::default()
589///     .block(Block::bordered().title("Canvas"))
590///     .x_bounds([-180.0, 180.0])
591///     .y_bounds([-90.0, 90.0])
592///     .paint(|ctx| {
593///         ctx.draw(&Map {
594///             resolution: MapResolution::High,
595///             color: Color::White,
596///         });
597///         ctx.layer();
598///         ctx.draw(&Line {
599///             x1: 0.0,
600///             y1: 10.0,
601///             x2: 10.0,
602///             y2: 10.0,
603///             color: Color::White,
604///         });
605///         ctx.draw(&Rectangle {
606///             x: 10.0,
607///             y: 20.0,
608///             width: 10.0,
609///             height: 10.0,
610///             color: Color::Red,
611///         });
612///     });
613/// ```
614///
615/// [`marker`]: #method.marker
616#[derive(Debug, Clone, PartialEq)]
617pub struct Canvas<'a, F>
618where
619    F: Fn(&mut Context),
620{
621    block: Option<Block<'a>>,
622    x_bounds: [f64; 2],
623    y_bounds: [f64; 2],
624    paint_func: Option<F>,
625    background_color: Color,
626    marker: Marker,
627}
628
629impl<'a, F> Default for Canvas<'a, F>
630where
631    F: Fn(&mut Context),
632{
633    fn default() -> Self {
634        Self {
635            block: None,
636            x_bounds: [0.0, 0.0],
637            y_bounds: [0.0, 0.0],
638            paint_func: None,
639            background_color: Color::Reset,
640            marker: Marker::Braille,
641        }
642    }
643}
644
645impl<'a, F> Canvas<'a, F>
646where
647    F: Fn(&mut Context),
648{
649    /// Wraps the canvas with a custom [`Block`] widget.
650    ///
651    /// This is a fluent setter method which must be chained or used as it consumes self
652    #[must_use = "method moves the value of self and returns the modified value"]
653    pub fn block(mut self, block: Block<'a>) -> Self {
654        self.block = Some(block);
655        self
656    }
657
658    /// Define the viewport of the canvas.
659    ///
660    /// If you were to "zoom" to a certain part of the world you may want to choose different
661    /// bounds.
662    ///
663    /// This is a fluent setter method which must be chained or used as it consumes self
664    #[must_use = "method moves the value of self and returns the modified value"]
665    pub const fn x_bounds(mut self, bounds: [f64; 2]) -> Self {
666        self.x_bounds = bounds;
667        self
668    }
669
670    /// Define the viewport of the canvas.
671    ///
672    /// If you were to "zoom" to a certain part of the world you may want to choose different
673    /// bounds.
674    ///
675    /// This is a fluent setter method which must be chained or used as it consumes self
676    #[must_use = "method moves the value of self and returns the modified value"]
677    pub const fn y_bounds(mut self, bounds: [f64; 2]) -> Self {
678        self.y_bounds = bounds;
679        self
680    }
681
682    /// Store the closure that will be used to draw to the [`Canvas`]
683    ///
684    /// This is a fluent setter method which must be chained or used as it consumes self
685    #[must_use = "method moves the value of self and returns the modified value"]
686    pub fn paint(mut self, f: F) -> Self {
687        self.paint_func = Some(f);
688        self
689    }
690
691    /// Change the background [`Color`] of the entire canvas
692    ///
693    /// This is a fluent setter method which must be chained or used as it consumes self
694    #[must_use = "method moves the value of self and returns the modified value"]
695    pub const fn background_color(mut self, color: Color) -> Self {
696        self.background_color = color;
697        self
698    }
699
700    /// Change the type of points used to draw the shapes.
701    ///
702    /// By default the [`Braille`] patterns are used as they provide a more fine grained result,
703    /// but you might want to use the simple [`Dot`] or [`Block`] instead if the targeted terminal
704    /// does not support those symbols.
705    ///
706    /// The [`HalfBlock`] marker is useful when you want to draw shapes with a higher resolution
707    /// than with a grid of characters (e.g. with [`Block`] or [`Dot`]) but lower than with
708    /// [`Braille`]. This grid type supports a foreground and background color for each terminal
709    /// cell. This allows for more flexibility than the `BrailleGrid` which only supports a single
710    /// foreground color for each 2x4 dots cell.
711    ///
712    /// [`Braille`]: crate::symbols::Marker::Braille
713    /// [`HalfBlock`]: crate::symbols::Marker::HalfBlock
714    /// [`Dot`]: crate::symbols::Marker::Dot
715    /// [`Block`]: crate::symbols::Marker::Block
716    ///
717    /// # Examples
718    ///
719    /// ```
720    /// use ratatui::{symbols, widgets::canvas::Canvas};
721    ///
722    /// Canvas::default()
723    ///     .marker(symbols::Marker::Braille)
724    ///     .paint(|ctx| {});
725    ///
726    /// Canvas::default()
727    ///     .marker(symbols::Marker::HalfBlock)
728    ///     .paint(|ctx| {});
729    ///
730    /// Canvas::default()
731    ///     .marker(symbols::Marker::Dot)
732    ///     .paint(|ctx| {});
733    ///
734    /// Canvas::default()
735    ///     .marker(symbols::Marker::Block)
736    ///     .paint(|ctx| {});
737    /// ```
738    #[must_use = "method moves the value of self and returns the modified value"]
739    pub const fn marker(mut self, marker: Marker) -> Self {
740        self.marker = marker;
741        self
742    }
743}
744
745impl<F> Widget for Canvas<'_, F>
746where
747    F: Fn(&mut Context),
748{
749    fn render(self, area: Rect, buf: &mut Buffer) {
750        self.render_ref(area, buf);
751    }
752}
753
754impl<F> WidgetRef for Canvas<'_, F>
755where
756    F: Fn(&mut Context),
757{
758    fn render_ref(&self, area: Rect, buf: &mut Buffer) {
759        self.block.render_ref(area, buf);
760        let canvas_area = self.block.inner_if_some(area);
761        if canvas_area.is_empty() {
762            return;
763        }
764
765        buf.set_style(canvas_area, Style::default().bg(self.background_color));
766
767        let width = canvas_area.width as usize;
768
769        let Some(ref painter) = self.paint_func else {
770            return;
771        };
772
773        // Create a blank context that match the size of the canvas
774        let mut ctx = Context::new(
775            canvas_area.width,
776            canvas_area.height,
777            self.x_bounds,
778            self.y_bounds,
779            self.marker,
780        );
781        // Paint to this context
782        painter(&mut ctx);
783        ctx.finish();
784
785        // Retrieve painted points for each layer
786        for layer in ctx.layers {
787            for (index, (ch, colors)) in layer.string.chars().zip(layer.colors).enumerate() {
788                if ch != ' ' && ch != '\u{2800}' {
789                    let (x, y) = (
790                        (index % width) as u16 + canvas_area.left(),
791                        (index / width) as u16 + canvas_area.top(),
792                    );
793                    let cell = buf[(x, y)].set_char(ch);
794                    if colors.0 != Color::Reset {
795                        cell.set_fg(colors.0);
796                    }
797                    if colors.1 != Color::Reset {
798                        cell.set_bg(colors.1);
799                    }
800                }
801            }
802        }
803
804        // Finally draw the labels
805        let left = self.x_bounds[0];
806        let right = self.x_bounds[1];
807        let top = self.y_bounds[1];
808        let bottom = self.y_bounds[0];
809        let width = (self.x_bounds[1] - self.x_bounds[0]).abs();
810        let height = (self.y_bounds[1] - self.y_bounds[0]).abs();
811        let resolution = {
812            let width = f64::from(canvas_area.width - 1);
813            let height = f64::from(canvas_area.height - 1);
814            (width, height)
815        };
816        for label in ctx
817            .labels
818            .iter()
819            .filter(|l| l.x >= left && l.x <= right && l.y <= top && l.y >= bottom)
820        {
821            let x = ((label.x - left) * resolution.0 / width) as u16 + canvas_area.left();
822            let y = ((top - label.y) * resolution.1 / height) as u16 + canvas_area.top();
823            buf.set_line(x, y, &label.line, canvas_area.right() - x);
824        }
825    }
826}
827
828#[cfg(test)]
829mod tests {
830    use indoc::indoc;
831
832    use super::*;
833    use crate::buffer::Cell;
834
835    // helper to test the canvas checks that drawing a vertical and horizontal line
836    // results in the expected output
837    fn test_marker(marker: Marker, expected: &str) {
838        let area = Rect::new(0, 0, 5, 5);
839        let mut buf = Buffer::filled(area, Cell::new("x"));
840        let horizontal_line = Line {
841            x1: 0.0,
842            y1: 0.0,
843            x2: 10.0,
844            y2: 0.0,
845            color: Color::Reset,
846        };
847        let vertical_line = Line {
848            x1: 0.0,
849            y1: 0.0,
850            x2: 0.0,
851            y2: 10.0,
852            color: Color::Reset,
853        };
854        Canvas::default()
855            .marker(marker)
856            .paint(|ctx| {
857                ctx.draw(&vertical_line);
858                ctx.draw(&horizontal_line);
859            })
860            .x_bounds([0.0, 10.0])
861            .y_bounds([0.0, 10.0])
862            .render(area, &mut buf);
863        assert_eq!(buf, Buffer::with_lines(expected.lines()));
864    }
865
866    #[test]
867    fn test_bar_marker() {
868        test_marker(
869            Marker::Bar,
870            indoc!(
871                "
872                ▄xxxx
873                ▄xxxx
874                ▄xxxx
875                ▄xxxx
876                ▄▄▄▄▄"
877            ),
878        );
879    }
880
881    #[test]
882    fn test_block_marker() {
883        test_marker(
884            Marker::Block,
885            indoc!(
886                "
887                █xxxx
888                █xxxx
889                █xxxx
890                █xxxx
891                █████"
892            ),
893        );
894    }
895
896    #[test]
897    fn test_braille_marker() {
898        test_marker(
899            Marker::Braille,
900            indoc!(
901                "
902                ⡇xxxx
903                ⡇xxxx
904                ⡇xxxx
905                ⡇xxxx
906                ⣇⣀⣀⣀⣀"
907            ),
908        );
909    }
910
911    #[test]
912    fn test_dot_marker() {
913        test_marker(
914            Marker::Dot,
915            indoc!(
916                "
917                •xxxx
918                •xxxx
919                •xxxx
920                •xxxx
921                •••••"
922            ),
923        );
924    }
925}