ratatui_widgets/canvas/
rectangle.rs

1use ratatui_core::style::Color;
2
3use crate::canvas::{Line, Painter, Shape};
4
5/// A rectangle to draw on a [`Canvas`](crate::canvas::Canvas)
6///
7/// Sizes used here are **not** in terminal cell. This is much more similar to the
8/// mathematic coordinate system.
9#[derive(Debug, Default, Clone, PartialEq)]
10pub struct Rectangle {
11    /// The `x` position of the rectangle.
12    ///
13    /// The rectangle is positioned from its bottom left corner.
14    pub x: f64,
15    /// The `y` position of the rectangle.
16    ///
17    /// The rectangle is positioned from its bottom left corner.
18    pub y: f64,
19    /// The width of the rectangle.
20    pub width: f64,
21    /// The height of the rectangle.
22    pub height: f64,
23    /// The color of the rectangle.
24    pub color: Color,
25}
26
27impl Rectangle {
28    /// Create a new rectangle with the given position, size, and color
29    pub const fn new(x: f64, y: f64, width: f64, height: f64, color: Color) -> Self {
30        Self {
31            x,
32            y,
33            width,
34            height,
35            color,
36        }
37    }
38}
39
40impl Shape for Rectangle {
41    fn draw(&self, painter: &mut Painter) {
42        let lines: [Line; 4] = [
43            Line {
44                x1: self.x,
45                y1: self.y,
46                x2: self.x,
47                y2: self.y + self.height,
48                color: self.color,
49            },
50            Line {
51                x1: self.x,
52                y1: self.y + self.height,
53                x2: self.x + self.width,
54                y2: self.y + self.height,
55                color: self.color,
56            },
57            Line {
58                x1: self.x + self.width,
59                y1: self.y,
60                x2: self.x + self.width,
61                y2: self.y + self.height,
62                color: self.color,
63            },
64            Line {
65                x1: self.x,
66                y1: self.y,
67                x2: self.x + self.width,
68                y2: self.y,
69                color: self.color,
70            },
71        ];
72        for line in &lines {
73            line.draw(painter);
74        }
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use ratatui_core::buffer::Buffer;
81    use ratatui_core::layout::{Margin, Rect};
82    use ratatui_core::style::Style;
83    use ratatui_core::symbols::Marker;
84    use ratatui_core::widgets::Widget;
85
86    use super::*;
87    use crate::canvas::Canvas;
88
89    #[test]
90    fn draw_block_lines() {
91        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
92        let canvas = Canvas::default()
93            .marker(Marker::Block)
94            .x_bounds([0.0, 10.0])
95            .y_bounds([0.0, 10.0])
96            .paint(|context| {
97                context.draw(&Rectangle {
98                    x: 0.0,
99                    y: 0.0,
100                    width: 10.0,
101                    height: 10.0,
102                    color: Color::Red,
103                });
104            });
105        canvas.render(buffer.area, &mut buffer);
106        let mut expected = Buffer::with_lines([
107            "██████████",
108            "█        █",
109            "█        █",
110            "█        █",
111            "█        █",
112            "█        █",
113            "█        █",
114            "█        █",
115            "█        █",
116            "██████████",
117        ]);
118        expected.set_style(buffer.area, Style::new().red().on_red());
119        expected.set_style(buffer.area.inner(Margin::new(1, 1)), Style::reset());
120        assert_eq!(buffer, expected);
121    }
122
123    #[test]
124    fn draw_half_block_lines() {
125        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
126        let canvas = Canvas::default()
127            .marker(Marker::HalfBlock)
128            .x_bounds([0.0, 10.0])
129            .y_bounds([0.0, 10.0])
130            .paint(|context| {
131                context.draw(&Rectangle {
132                    x: 0.0,
133                    y: 0.0,
134                    width: 10.0,
135                    height: 10.0,
136                    color: Color::Red,
137                });
138            });
139        canvas.render(buffer.area, &mut buffer);
140        let mut expected = Buffer::with_lines([
141            "█▀▀▀▀▀▀▀▀█",
142            "█        █",
143            "█        █",
144            "█        █",
145            "█        █",
146            "█        █",
147            "█        █",
148            "█        █",
149            "█        █",
150            "█▄▄▄▄▄▄▄▄█",
151        ]);
152        expected.set_style(buffer.area, Style::new().red().on_red());
153        expected.set_style(buffer.area.inner(Margin::new(1, 0)), Style::reset().red());
154        expected.set_style(buffer.area.inner(Margin::new(1, 1)), Style::reset());
155        assert_eq!(buffer, expected);
156    }
157
158    #[test]
159    fn draw_braille_lines() {
160        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
161        let canvas = Canvas::default()
162            .marker(Marker::Braille)
163            .x_bounds([0.0, 20.0])
164            .y_bounds([0.0, 20.0])
165            .paint(|context| {
166                // a rectangle that will draw the outside part of the braille
167                context.draw(&Rectangle {
168                    x: 0.0,
169                    y: 0.0,
170                    width: 20.0,
171                    height: 20.0,
172                    color: Color::Red,
173                });
174                // a rectangle that will draw the inside part of the braille
175                context.draw(&Rectangle {
176                    x: 4.0,
177                    y: 4.0,
178                    width: 12.0,
179                    height: 12.0,
180                    color: Color::Green,
181                });
182            });
183        canvas.render(buffer.area, &mut buffer);
184        let mut expected = Buffer::with_lines([
185            "⡏⠉⠉⠉⠉⠉⠉⠉⠉⢹",
186            "⡇        ⢸",
187            "⡇ ⡏⠉⠉⠉⠉⢹ ⢸",
188            "⡇ ⡇    ⢸ ⢸",
189            "⡇ ⡇    ⢸ ⢸",
190            "⡇ ⡇    ⢸ ⢸",
191            "⡇ ⡇    ⢸ ⢸",
192            "⡇ ⣇⣀⣀⣀⣀⣸ ⢸",
193            "⡇        ⢸",
194            "⣇⣀⣀⣀⣀⣀⣀⣀⣀⣸",
195        ]);
196        expected.set_style(buffer.area, Style::new().red());
197        expected.set_style(buffer.area.inner(Margin::new(1, 1)), Style::reset());
198        expected.set_style(buffer.area.inner(Margin::new(2, 2)), Style::new().green());
199        expected.set_style(buffer.area.inner(Margin::new(3, 3)), Style::reset());
200        assert_eq!(buffer, expected);
201    }
202}