1use crate::style::Style;
6use std::cmp;
7use std::iter::ExactSizeIterator;
8use std::ops::Range;
9use term::Terminal;
10
11mod row;
12#[cfg(test)]
13mod test;
14#[cfg(test)]
15mod test_util;
16
17pub mod style;
18
19pub use self::row::Row;
20
21pub trait AsciiView {
29 fn columns(&self) -> usize;
30 fn read_char(&mut self, row: usize, column: usize) -> char;
31 fn write_char(&mut self, row: usize, column: usize, ch: char, style: Style);
32}
33
34impl<'a> dyn AsciiView + 'a {
35 fn add_box_dirs(&mut self, row: usize, column: usize, dirs: u8) {
36 let old_ch = self.read_char(row, column);
37 let new_ch = add_dirs(old_ch, dirs);
38 self.write_char(row, column, new_ch, Style::new());
39 }
40
41 pub fn draw_vertical_line(&mut self, rows: Range<usize>, column: usize) {
43 let len = rows.len();
44 for (index, r) in rows.enumerate() {
45 let new_dirs = if index == 0 {
46 DOWN
47 } else if index == len - 1 {
48 UP
49 } else {
50 UP | DOWN
51 };
52 self.add_box_dirs(r, column, new_dirs);
53 }
54 }
55
56 pub fn draw_horizontal_line(&mut self, row: usize, columns: Range<usize>) {
59 let len = columns.len();
60 for (index, c) in columns.enumerate() {
61 let new_dirs = if index == 0 {
62 RIGHT
63 } else if index == len - 1 {
64 LEFT
65 } else {
66 LEFT | RIGHT
67 };
68 self.add_box_dirs(row, c, new_dirs);
69 }
70 }
71
72 pub fn write_chars<I>(&mut self, row: usize, column: usize, chars: I, style: Style)
74 where
75 I: Iterator<Item = char>,
76 {
77 for (i, ch) in chars.enumerate() {
78 self.write_char(row, column + i, ch, style);
79 }
80 }
81
82 pub fn shift(&mut self, row: usize, column: usize) -> ShiftedView {
84 ShiftedView::new(self, row, column)
85 }
86
87 pub fn styled(&mut self, style: Style) -> StyleView {
90 StyleView::new(self, style)
91 }
92}
93
94pub struct AsciiCanvas {
95 columns: usize,
96 rows: usize,
97 characters: Vec<char>,
98 styles: Vec<Style>,
99}
100
101impl AsciiCanvas {
106 pub fn new(rows: usize, columns: usize) -> Self {
109 AsciiCanvas {
110 rows,
111 columns,
112 characters: vec![' '; columns * rows],
113 styles: vec![Style::new(); columns * rows],
114 }
115 }
116
117 fn grow_rows_if_needed(&mut self, new_rows: usize) {
118 if new_rows >= self.rows {
119 let new_chars = (new_rows - self.rows) * self.columns;
120 self.characters.extend((0..new_chars).map(|_| ' '));
121 self.styles.extend((0..new_chars).map(|_| Style::new()));
122 self.rows = new_rows;
123 }
124 }
125
126 fn index(&mut self, r: usize, c: usize) -> usize {
127 self.grow_rows_if_needed(r + 1);
128 self.in_range_index(r, c)
129 }
130
131 fn in_range_index(&self, r: usize, c: usize) -> usize {
132 assert!(r < self.rows);
133 assert!(c <= self.columns);
134 r * self.columns + c
135 }
136
137 fn start_index(&self, r: usize) -> usize {
138 self.in_range_index(r, 0)
139 }
140
141 fn end_index(&self, r: usize) -> usize {
142 self.in_range_index(r, self.columns)
143 }
144
145 pub fn write_to<T: Terminal + ?Sized>(&self, term: &mut T) -> term::Result<()> {
146 for row in self.to_strings() {
147 row.write_to(term)?;
148 writeln!(term)?;
149 }
150 Ok(())
151 }
152
153 pub fn to_strings(&self) -> Vec<Row> {
154 (0..self.rows)
155 .map(|row| {
156 let start = self.start_index(row);
157 let end = self.end_index(row);
158 let chars = &self.characters[start..end];
159 let styles = &self.styles[start..end];
160 Row::new(chars, styles)
161 })
162 .collect()
163 }
164}
165
166impl AsciiView for AsciiCanvas {
167 fn columns(&self) -> usize {
168 self.columns
169 }
170
171 fn read_char(&mut self, row: usize, column: usize) -> char {
172 assert!(column < self.columns);
173 let index = self.index(row, column);
174 self.characters[index]
175 }
176
177 fn write_char(&mut self, row: usize, column: usize, ch: char, style: Style) {
178 assert!(column < self.columns);
179 let index = self.index(row, column);
180 self.characters[index] = ch;
181 self.styles[index] = style;
182 }
183}
184
185#[derive(Copy, Clone)]
186struct Point {
187 row: usize,
188 column: usize,
189}
190
191pub struct ShiftedView<'canvas> {
199 base: &'canvas mut dyn AsciiView,
201
202 upper_left: Point,
205
206 lower_right: Point,
208}
209
210impl<'canvas> ShiftedView<'canvas> {
211 fn new(base: &'canvas mut dyn AsciiView, row: usize, column: usize) -> Self {
212 let upper_left = Point { row, column };
213 ShiftedView {
214 base,
215 upper_left,
216 lower_right: upper_left,
217 }
218 }
219
220 pub fn close(self) -> (usize, usize) {
227 (self.lower_right.row, self.lower_right.column)
228 }
229
230 fn track_max(&mut self, row: usize, column: usize) {
231 self.lower_right.row = cmp::max(self.lower_right.row, row);
232 self.lower_right.column = cmp::max(self.lower_right.column, column);
233 }
234}
235
236impl<'canvas> AsciiView for ShiftedView<'canvas> {
237 fn columns(&self) -> usize {
238 self.base.columns() - self.upper_left.column
239 }
240
241 fn read_char(&mut self, row: usize, column: usize) -> char {
242 let row = self.upper_left.row + row;
243 let column = self.upper_left.column + column;
244 self.base.read_char(row, column)
245 }
246
247 fn write_char(&mut self, row: usize, column: usize, ch: char, style: Style) {
248 let row = self.upper_left.row + row;
249 let column = self.upper_left.column + column;
250 self.track_max(row, column);
251 self.base.write_char(row, column, ch, style)
252 }
253}
254
255pub struct StyleView<'canvas> {
259 base: &'canvas mut dyn AsciiView,
260 style: Style,
261}
262
263impl<'canvas> StyleView<'canvas> {
264 fn new(base: &'canvas mut dyn AsciiView, style: Style) -> Self {
265 StyleView { base, style }
266 }
267}
268
269impl<'canvas> AsciiView for StyleView<'canvas> {
270 fn columns(&self) -> usize {
271 self.base.columns()
272 }
273
274 fn read_char(&mut self, row: usize, column: usize) -> char {
275 self.base.read_char(row, column)
276 }
277
278 fn write_char(&mut self, row: usize, column: usize, ch: char, style: Style) {
279 self.base
280 .write_char(row, column, ch, style.with(self.style))
281 }
282}
283
284const UP: u8 = 0b0001;
288const DOWN: u8 = 0b0010;
289const LEFT: u8 = 0b0100;
290const RIGHT: u8 = 0b1000;
291
292const BOX_CHARS: &[(char, u8)] = &[
293 ('╵', UP),
294 ('│', UP | DOWN),
295 ('┤', UP | DOWN | LEFT),
296 ('├', UP | DOWN | RIGHT),
297 ('┼', UP | DOWN | LEFT | RIGHT),
298 ('┘', UP | LEFT),
299 ('└', UP | RIGHT),
300 ('┴', UP | LEFT | RIGHT),
301 ('╷', DOWN),
303 ('┐', DOWN | LEFT),
304 ('┌', DOWN | RIGHT),
305 ('┬', DOWN | LEFT | RIGHT),
306 ('╶', LEFT),
308 ('─', LEFT | RIGHT),
309 ('╴', RIGHT),
311 (' ', 0),
313];
314
315fn box_char_for_dirs(dirs: u8) -> char {
316 for &(c, d) in BOX_CHARS {
317 if dirs == d {
318 return c;
319 }
320 }
321 panic!("no box character for dirs: {:b}", dirs);
322}
323
324fn dirs_for_box_char(ch: char) -> Option<u8> {
325 for &(c, d) in BOX_CHARS {
326 if c == ch {
327 return Some(d);
328 }
329 }
330 None
331}
332
333fn add_dirs(old_ch: char, new_dirs: u8) -> char {
334 let old_dirs = dirs_for_box_char(old_ch).unwrap_or(0);
335 box_char_for_dirs(old_dirs | new_dirs)
336}