Skip to main content

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 expected = Buffer::with_lines([
92            "██████████",
93            "█        █",
94            "█        █",
95            "█        █",
96            "█        █",
97            "█        █",
98            "█        █",
99            "█        █",
100            "█        █",
101            "██████████",
102        ]);
103        let rect = Rect::new(0, 0, 10, 10);
104        expected.set_style(rect, Style::new().red().on_red());
105        expected.set_style(rect.inner(Margin::new(1, 1)), Style::reset());
106        let rendered = render_rectangle(Marker::Block, rect);
107        assert_eq!(expected, rendered);
108    }
109
110    #[test]
111    fn draw_half_block_lines() {
112        let mut expected = Buffer::with_lines([
113            "█▀▀▀▀▀▀▀▀█",
114            "█        █",
115            "█        █",
116            "█        █",
117            "█        █",
118            "█        █",
119            "█        █",
120            "█        █",
121            "█        █",
122            "█▄▄▄▄▄▄▄▄█",
123        ]);
124        let rect = Rect::new(0, 0, 10, 10);
125        expected.set_style(rect, Style::new().red().on_red());
126        expected.set_style(rect.inner(Margin::new(1, 0)), Style::reset().red());
127        expected.set_style(rect.inner(Margin::new(1, 1)), Style::reset());
128        let rendered = render_rectangle(Marker::HalfBlock, rect);
129        assert_eq!(expected, rendered);
130    }
131
132    #[test]
133    fn draw_braille_lines() {
134        let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
135        let canvas = Canvas::default()
136            .marker(Marker::Braille)
137            .x_bounds([0.0, 20.0])
138            .y_bounds([0.0, 20.0])
139            .paint(|context| {
140                // a rectangle that will draw the outside part of the braille
141                context.draw(&Rectangle {
142                    x: 0.0,
143                    y: 0.0,
144                    width: 20.0,
145                    height: 20.0,
146                    color: Color::Red,
147                });
148                // a rectangle that will draw the inside part of the braille
149                context.draw(&Rectangle {
150                    x: 4.0,
151                    y: 4.0,
152                    width: 12.0,
153                    height: 12.0,
154                    color: Color::Green,
155                });
156            });
157        canvas.render(buffer.area, &mut buffer);
158        let mut expected = Buffer::with_lines([
159            "⡏⠉⠉⠉⠉⠉⠉⠉⠉⢹",
160            "⡇        ⢸",
161            "⡇ ⡏⠉⠉⠉⠉⢹ ⢸",
162            "⡇ ⡇    ⢸ ⢸",
163            "⡇ ⡇    ⢸ ⢸",
164            "⡇ ⡇    ⢸ ⢸",
165            "⡇ ⡇    ⢸ ⢸",
166            "⡇ ⣇⣀⣀⣀⣀⣸ ⢸",
167            "⡇        ⢸",
168            "⣇⣀⣀⣀⣀⣀⣀⣀⣀⣸",
169        ]);
170        expected.set_style(buffer.area, Style::new().red());
171        expected.set_style(buffer.area.inner(Margin::new(1, 1)), Style::reset());
172        expected.set_style(buffer.area.inner(Margin::new(2, 2)), Style::new().green());
173        expected.set_style(buffer.area.inner(Margin::new(3, 3)), Style::reset());
174        assert_eq!(buffer, expected);
175    }
176
177    #[test]
178    fn draw_x_lines() {
179        let mut expected = Buffer::with_lines([
180            "××××××××××",
181            "×        ×",
182            "×        ×",
183            "×        ×",
184            "×        ×",
185            "×        ×",
186            "×        ×",
187            "×        ×",
188            "×        ×",
189            "××××××××××",
190        ]);
191        let rect = Rect::new(0, 0, 10, 10);
192        expected.set_style(rect, Style::new().red());
193        expected.set_style(rect.inner(Margin::new(1, 0)), Style::reset().red());
194        expected.set_style(rect.inner(Margin::new(1, 1)), Style::reset());
195        let rendered = render_rectangle(Marker::Custom('×'), rect);
196        assert_eq!(expected, rendered);
197    }
198
199    #[test]
200    fn draw_plus_lines() {
201        let mut expected = Buffer::with_lines([
202            "++++++++++",
203            "+        +",
204            "+        +",
205            "+        +",
206            "+        +",
207            "+        +",
208            "+        +",
209            "+        +",
210            "+        +",
211            "++++++++++",
212        ]);
213        let rect = Rect::new(0, 0, 10, 10);
214        expected.set_style(rect, Style::new().red());
215        expected.set_style(rect.inner(Margin::new(1, 0)), Style::reset().red());
216        expected.set_style(rect.inner(Margin::new(1, 1)), Style::reset());
217        let rendered = render_rectangle(Marker::Custom('+'), rect);
218        assert_eq!(expected, rendered);
219    }
220
221    fn render_rectangle(marker: Marker, rect: Rect) -> Buffer {
222        let mut buffer = Buffer::empty(rect);
223        let canvas = Canvas::default()
224            .marker(marker)
225            .x_bounds([0.0, 10.0])
226            .y_bounds([0.0, 10.0])
227            .paint(|context| {
228                context.draw(&Rectangle {
229                    x: 0.0,
230                    y: 0.0,
231                    width: 10.0,
232                    height: 10.0,
233                    color: Color::Red,
234                });
235            });
236        canvas.render(buffer.area, &mut buffer);
237        buffer
238    }
239}