1use crate::{
2 style::Color,
3 widgets::canvas::{Painter, Shape},
4};
5
6#[derive(Debug, Default, Clone, PartialEq)]
8pub struct Line {
9 pub x1: f64,
11 pub y1: f64,
13 pub x2: f64,
15 pub y2: f64,
17 pub color: Color,
19}
20
21impl Line {
22 pub const fn new(x1: f64, y1: f64, x2: f64, y2: f64, color: Color) -> Self {
24 Self {
25 x1,
26 y1,
27 x2,
28 y2,
29 color,
30 }
31 }
32}
33
34impl Shape for Line {
35 fn draw(&self, painter: &mut Painter) {
36 let Some((x1, y1)) = painter.get_point(self.x1, self.y1) else {
37 return;
38 };
39 let Some((x2, y2)) = painter.get_point(self.x2, self.y2) else {
40 return;
41 };
42 let (dx, x_range) = if x2 >= x1 {
43 (x2 - x1, x1..=x2)
44 } else {
45 (x1 - x2, x2..=x1)
46 };
47 let (dy, y_range) = if y2 >= y1 {
48 (y2 - y1, y1..=y2)
49 } else {
50 (y1 - y2, y2..=y1)
51 };
52
53 if dx == 0 {
54 for y in y_range {
55 painter.paint(x1, y, self.color);
56 }
57 } else if dy == 0 {
58 for x in x_range {
59 painter.paint(x, y1, self.color);
60 }
61 } else if dy < dx {
62 if x1 > x2 {
63 draw_line_low(painter, x2, y2, x1, y1, self.color);
64 } else {
65 draw_line_low(painter, x1, y1, x2, y2, self.color);
66 }
67 } else if y1 > y2 {
68 draw_line_high(painter, x2, y2, x1, y1, self.color);
69 } else {
70 draw_line_high(painter, x1, y1, x2, y2, self.color);
71 }
72 }
73}
74
75fn draw_line_low(painter: &mut Painter, x1: usize, y1: usize, x2: usize, y2: usize, color: Color) {
76 let dx = (x2 - x1) as isize;
77 let dy = (y2 as isize - y1 as isize).abs();
78 let mut d = 2 * dy - dx;
79 let mut y = y1;
80 for x in x1..=x2 {
81 painter.paint(x, y, color);
82 if d > 0 {
83 y = if y1 > y2 {
84 y.saturating_sub(1)
85 } else {
86 y.saturating_add(1)
87 };
88 d -= 2 * dx;
89 }
90 d += 2 * dy;
91 }
92}
93
94fn draw_line_high(painter: &mut Painter, x1: usize, y1: usize, x2: usize, y2: usize, color: Color) {
95 let dx = (x2 as isize - x1 as isize).abs();
96 let dy = (y2 - y1) as isize;
97 let mut d = 2 * dx - dy;
98 let mut x = x1;
99 for y in y1..=y2 {
100 painter.paint(x, y, color);
101 if d > 0 {
102 x = if x1 > x2 {
103 x.saturating_sub(1)
104 } else {
105 x.saturating_add(1)
106 };
107 d -= 2 * dy;
108 }
109 d += 2 * dx;
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use rstest::rstest;
116
117 use super::*;
118 use crate::{
119 buffer::Buffer,
120 layout::Rect,
121 style::{Style, Stylize},
122 symbols::Marker,
123 widgets::{canvas::Canvas, Widget},
124 };
125
126 #[rstest]
127 #[case::off_grid(&Line::new(-1.0, -1.0, 10.0, 10.0, Color::Red), [" "; 10])]
128 #[case::off_grid(&Line::new(0.0, 0.0, 11.0, 11.0, Color::Red), [" "; 10])]
129 #[case::horizontal(&Line::new(0.0, 0.0, 10.0, 0.0, Color::Red), [
130 " ",
131 " ",
132 " ",
133 " ",
134 " ",
135 " ",
136 " ",
137 " ",
138 " ",
139 "••••••••••",
140 ])]
141 #[case::horizontal(&Line::new(10.0, 10.0, 0.0, 10.0, Color::Red), [
142 "••••••••••",
143 " ",
144 " ",
145 " ",
146 " ",
147 " ",
148 " ",
149 " ",
150 " ",
151 " ",
152 ])]
153 #[case::vertical(&Line::new(0.0, 0.0, 0.0, 10.0, Color::Red), ["• "; 10])]
154 #[case::vertical(&Line::new(10.0, 10.0, 10.0, 0.0, Color::Red), [" •"; 10])]
155 #[case::diagonal(&Line::new(0.0, 0.0, 10.0, 5.0, Color::Red), [
157 " ",
158 " ",
159 " ",
160 " ",
161 " •",
162 " •• ",
163 " •• ",
164 " •• ",
165 " •• ",
166 "• ",
167 ])]
168 #[case::diagonal(&Line::new(10.0, 0.0, 0.0, 5.0, Color::Red), [
170 " ",
171 " ",
172 " ",
173 " ",
174 "• ",
175 " •• ",
176 " •• ",
177 " •• ",
178 " •• ",
179 " •",
180 ])]
181 #[case::diagonal(&Line::new(0.0, 0.0, 5.0, 10.0, Color::Red), [
183 " • ",
184 " • ",
185 " • ",
186 " • ",
187 " • ",
188 " • ",
189 " • ",
190 " • ",
191 "• ",
192 "• ",
193 ])]
194 #[case::diagonal(&Line::new(0.0, 10.0, 5.0, 0.0, Color::Red), [
196 "• ",
197 "• ",
198 " • ",
199 " • ",
200 " • ",
201 " • ",
202 " • ",
203 " • ",
204 " • ",
205 " • ",
206 ])]
207 fn tests<'expected_line, ExpectedLines>(#[case] line: &Line, #[case] expected: ExpectedLines)
208 where
209 ExpectedLines: IntoIterator,
210 ExpectedLines::Item: Into<crate::text::Line<'expected_line>>,
211 {
212 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
213 let canvas = Canvas::default()
214 .marker(Marker::Dot)
215 .x_bounds([0.0, 10.0])
216 .y_bounds([0.0, 10.0])
217 .paint(|context| context.draw(line));
218 canvas.render(buffer.area, &mut buffer);
219
220 let mut expected = Buffer::with_lines(expected);
221 for cell in &mut expected.content {
222 if cell.symbol() == "•" {
223 cell.set_style(Style::new().red());
224 }
225 }
226 assert_eq!(buffer, expected);
227 }
228}