1 | use crate::{ |
2 | grid::{ |
3 | ansi::ANSIBuf, |
4 | config::{self, ColoredConfig, SpannedConfig}, |
5 | dimension::{Dimension, Estimate}, |
6 | records::{ExactRecords, Records}, |
7 | }, |
8 | settings::{ |
9 | object::{Column, FirstColumn, FirstRow, LastColumn, LastRow, Row}, |
10 | Color, TableOption, |
11 | }, |
12 | }; |
13 | |
14 | use super::Offset; |
15 | |
16 | /// [`LineText`] writes a custom text on a border. |
17 | /// |
18 | /// # Example |
19 | /// |
20 | /// ```rust |
21 | /// use tabled::{Table, settings::style::LineText, settings::object::Rows}; |
22 | /// |
23 | /// let mut table = Table::new(["Hello World" ]); |
24 | /// table.with(LineText::new("+-.table" , Rows::first())); |
25 | /// |
26 | /// assert_eq!( |
27 | /// table.to_string(), |
28 | /// "+-.table------+ \n\ |
29 | /// | &str | \n\ |
30 | /// +-------------+ \n\ |
31 | /// | Hello World | \n\ |
32 | /// +-------------+" |
33 | /// ); |
34 | /// ``` |
35 | #[derive (Debug)] |
36 | pub struct LineText<L> { |
37 | // todo: change to T and specify to be As<str> |
38 | text: String, |
39 | offset: Offset, |
40 | color: Option<ANSIBuf>, |
41 | line: L, |
42 | } |
43 | |
44 | impl<Line> LineText<Line> { |
45 | /// Creates a [`LineText`] instance. |
46 | /// |
47 | /// Lines are numbered from 0 to the `count_rows` included |
48 | /// (`line >= 0 && line <= count_rows`). |
49 | pub fn new<S>(text: S, line: Line) -> Self |
50 | where |
51 | S: Into<String>, |
52 | { |
53 | LineText { |
54 | text: text.into(), |
55 | line, |
56 | offset: Offset::Begin(0), |
57 | color: None, |
58 | } |
59 | } |
60 | |
61 | /// Set an offset from which the text will be started. |
62 | pub fn offset(self, offset: impl Into<Offset>) -> Self { |
63 | LineText { |
64 | offset: offset.into(), |
65 | text: self.text, |
66 | line: self.line, |
67 | color: self.color, |
68 | } |
69 | } |
70 | |
71 | /// Set a color of the text. |
72 | pub fn color(self, color: Color) -> Self { |
73 | LineText { |
74 | color: Some(color.into()), |
75 | text: self.text, |
76 | line: self.line, |
77 | offset: self.offset, |
78 | } |
79 | } |
80 | } |
81 | |
82 | impl<R, D> TableOption<R, ColoredConfig, D> for LineText<Row> |
83 | where |
84 | R: Records + ExactRecords, |
85 | for<'a> &'a R: Records, |
86 | for<'a> D: Estimate<&'a R, ColoredConfig>, |
87 | D: Dimension, |
88 | { |
89 | fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { |
90 | let line: usize = self.line.into(); |
91 | change_horizontal_chars(records, dims, cfg, line, self.text, self.offset, self.color) |
92 | } |
93 | } |
94 | |
95 | impl<R, D> TableOption<R, ColoredConfig, D> for LineText<FirstRow> |
96 | where |
97 | R: Records + ExactRecords, |
98 | for<'a> &'a R: Records, |
99 | for<'a> D: Estimate<&'a R, ColoredConfig>, |
100 | D: Dimension, |
101 | { |
102 | fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { |
103 | change_horizontal_chars(records, dims, cfg, line:0, self.text, self.offset, self.color) |
104 | } |
105 | } |
106 | |
107 | impl<R, D> TableOption<R, ColoredConfig, D> for LineText<LastRow> |
108 | where |
109 | R: Records + ExactRecords, |
110 | for<'a> &'a R: Records, |
111 | for<'a> D: Estimate<&'a R, ColoredConfig>, |
112 | D: Dimension, |
113 | { |
114 | fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { |
115 | let line: usize = records.count_rows(); |
116 | change_horizontal_chars(records, dims, cfg, line, self.text, self.offset, self.color) |
117 | } |
118 | } |
119 | |
120 | impl<R, D> TableOption<R, ColoredConfig, D> for LineText<Column> |
121 | where |
122 | R: Records + ExactRecords, |
123 | for<'a> &'a R: Records, |
124 | for<'a> D: Estimate<&'a R, ColoredConfig>, |
125 | D: Dimension, |
126 | { |
127 | fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { |
128 | let line: usize = self.line.into(); |
129 | change_vertical_chars(records, dims, cfg, line, self.text, self.offset, self.color) |
130 | } |
131 | } |
132 | |
133 | impl<R, D> TableOption<R, ColoredConfig, D> for LineText<FirstColumn> |
134 | where |
135 | R: Records + ExactRecords, |
136 | for<'a> &'a R: Records, |
137 | for<'a> D: Estimate<&'a R, ColoredConfig>, |
138 | D: Dimension, |
139 | { |
140 | fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { |
141 | change_vertical_chars(records, dims, cfg, line:0, self.text, self.offset, self.color) |
142 | } |
143 | } |
144 | |
145 | impl<R, D> TableOption<R, ColoredConfig, D> for LineText<LastColumn> |
146 | where |
147 | R: Records + ExactRecords, |
148 | for<'a> &'a R: Records, |
149 | for<'a> D: Estimate<&'a R, ColoredConfig>, |
150 | D: Dimension, |
151 | { |
152 | fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { |
153 | let line: usize = records.count_rows(); |
154 | change_vertical_chars(records, dims, cfg, line, self.text, self.offset, self.color) |
155 | } |
156 | } |
157 | |
158 | fn set_horizontal_chars<D: Dimension>( |
159 | cfg: &mut SpannedConfig, |
160 | dims: &D, |
161 | offset: Offset, |
162 | line: usize, |
163 | text: &str, |
164 | color: &Option<ANSIBuf>, |
165 | shape: (usize, usize), |
166 | ) { |
167 | let (_, count_columns) = shape; |
168 | let total_width = total_width(cfg, dims, count_columns); |
169 | let pos = get_start_pos(offset, total_width); |
170 | |
171 | let pos = match pos { |
172 | Some(pos) => pos, |
173 | None => return, |
174 | }; |
175 | |
176 | let mut chars = text.chars(); |
177 | let mut i = cfg.has_vertical(0, count_columns) as usize; |
178 | if i == 1 && pos == 0 { |
179 | let c = match chars.next() { |
180 | Some(c) => c, |
181 | None => return, |
182 | }; |
183 | |
184 | let mut b = cfg.get_border((line, 0), shape); |
185 | b.left_top_corner = b.left_top_corner.map(|_| c); |
186 | cfg.set_border((line, 0), b); |
187 | |
188 | if let Some(color) = color.as_ref() { |
189 | let mut b = cfg.get_border_color((line, 0), shape).cloned(); |
190 | b.left_top_corner = Some(color.clone()); |
191 | cfg.set_border_color((line, 0), b); |
192 | } |
193 | } |
194 | |
195 | for col in 0..count_columns { |
196 | let w = dims.get_width(col); |
197 | if i + w > pos { |
198 | for off in 0..w { |
199 | if i + off < pos { |
200 | continue; |
201 | } |
202 | |
203 | let c = match chars.next() { |
204 | Some(c) => c, |
205 | None => return, |
206 | }; |
207 | |
208 | cfg.set_horizontal_char((line, col), c, config::Offset::Begin(off)); |
209 | if let Some(color) = color.as_ref() { |
210 | cfg.set_horizontal_color( |
211 | (line, col), |
212 | color.clone(), |
213 | config::Offset::Begin(off), |
214 | ); |
215 | } |
216 | } |
217 | } |
218 | |
219 | i += w; |
220 | |
221 | if cfg.has_vertical(col + 1, count_columns) { |
222 | i += 1; |
223 | |
224 | if i > pos { |
225 | let c = match chars.next() { |
226 | Some(c) => c, |
227 | None => return, |
228 | }; |
229 | |
230 | let mut b = cfg.get_border((line, col), shape); |
231 | b.right_top_corner = b.right_top_corner.map(|_| c); |
232 | cfg.set_border((line, col), b); |
233 | |
234 | if let Some(color) = color.as_ref() { |
235 | let mut b = cfg.get_border_color((line, col), shape).cloned(); |
236 | b.right_top_corner = Some(color.clone()); |
237 | cfg.set_border_color((line, col), b); |
238 | } |
239 | } |
240 | } |
241 | } |
242 | } |
243 | |
244 | fn set_vertical_chars<D>( |
245 | cfg: &mut SpannedConfig, |
246 | dims: &D, |
247 | offset: Offset, |
248 | line: usize, |
249 | text: &str, |
250 | color: &Option<ANSIBuf>, |
251 | shape: (usize, usize), |
252 | ) where |
253 | D: Dimension, |
254 | { |
255 | let (count_rows, _) = shape; |
256 | let total_width = total_height(cfg, dims, count_rows); |
257 | let pos = get_start_pos(offset, total_width); |
258 | |
259 | let pos = match pos { |
260 | Some(pos) => pos, |
261 | None => return, |
262 | }; |
263 | |
264 | let mut chars = text.chars(); |
265 | let mut i = cfg.has_horizontal(0, count_rows) as usize; |
266 | if i == 1 && pos == 0 { |
267 | let c = match chars.next() { |
268 | Some(c) => c, |
269 | None => return, |
270 | }; |
271 | |
272 | let mut b = cfg.get_border((0, line), shape); |
273 | b.left_top_corner = b.left_top_corner.map(|_| c); |
274 | cfg.set_border((0, line), b); |
275 | |
276 | if let Some(color) = color.as_ref() { |
277 | let mut b = cfg.get_border_color((0, line), shape).cloned(); |
278 | b.left_top_corner = Some(color.clone()); |
279 | cfg.set_border_color((0, line), b); |
280 | } |
281 | } |
282 | |
283 | for row in 0..count_rows { |
284 | let row_height = dims.get_height(row); |
285 | if i + row_height > pos { |
286 | for off in 0..row_height { |
287 | if i + off < pos { |
288 | continue; |
289 | } |
290 | |
291 | let c = match chars.next() { |
292 | Some(c) => c, |
293 | None => return, |
294 | }; |
295 | |
296 | cfg.set_vertical_char((row, line), c, config::Offset::Begin(off)); // todo: is this correct? I thik it shall be off + i |
297 | |
298 | if let Some(color) = color.as_ref() { |
299 | cfg.set_vertical_color((row, line), color.clone(), config::Offset::Begin(off)); |
300 | } |
301 | } |
302 | } |
303 | |
304 | i += row_height; |
305 | |
306 | if cfg.has_horizontal(row + 1, count_rows) { |
307 | i += 1; |
308 | |
309 | if i > pos { |
310 | let c = match chars.next() { |
311 | Some(c) => c, |
312 | None => return, |
313 | }; |
314 | |
315 | let mut b = cfg.get_border((row, line), shape); |
316 | b.left_bottom_corner = b.left_bottom_corner.map(|_| c); |
317 | cfg.set_border((row, line), b); |
318 | |
319 | if let Some(color) = color.as_ref() { |
320 | let mut b = cfg.get_border_color((row, line), shape).cloned(); |
321 | b.left_bottom_corner = Some(color.clone()); |
322 | cfg.set_border_color((row, line), b); |
323 | } |
324 | } |
325 | } |
326 | } |
327 | } |
328 | |
329 | fn get_start_pos(offset: Offset, total: usize) -> Option<usize> { |
330 | match offset { |
331 | Offset::Begin(i: usize) => { |
332 | if i > total { |
333 | None |
334 | } else { |
335 | Some(i) |
336 | } |
337 | } |
338 | Offset::End(i: usize) => { |
339 | if i > total { |
340 | None |
341 | } else { |
342 | Some(total - i) |
343 | } |
344 | } |
345 | } |
346 | } |
347 | |
348 | fn total_width<D>(cfg: &SpannedConfig, dims: &D, count_columns: usize) -> usize |
349 | where |
350 | D: Dimension, |
351 | { |
352 | let mut total: usize = cfg.has_vertical(col:count_columns, count_columns) as usize; |
353 | for col: usize in 0..count_columns { |
354 | total += dims.get_width(column:col); |
355 | total += cfg.has_vertical(col, count_columns) as usize; |
356 | } |
357 | |
358 | total |
359 | } |
360 | |
361 | fn total_height<D>(cfg: &SpannedConfig, dims: &D, count_rows: usize) -> usize |
362 | where |
363 | D: Dimension, |
364 | { |
365 | let mut total: usize = cfg.has_horizontal(row:count_rows, count_rows) as usize; |
366 | for row: usize in 0..count_rows { |
367 | total += dims.get_height(row); |
368 | total += cfg.has_horizontal(row, count_rows) as usize; |
369 | } |
370 | |
371 | total |
372 | } |
373 | |
374 | fn change_horizontal_chars<R, D>( |
375 | records: &mut R, |
376 | dims: &mut D, |
377 | cfg: &mut ColoredConfig, |
378 | line: usize, |
379 | text: String, |
380 | offset: Offset, |
381 | color: Option<ANSIBuf>, |
382 | ) where |
383 | R: Records + ExactRecords, |
384 | for<'a> D: Estimate<&'a R, ColoredConfig>, |
385 | D: Dimension, |
386 | { |
387 | dims.estimate(records, config:cfg); |
388 | let shape: (usize, usize) = (records.count_rows(), records.count_columns()); |
389 | set_horizontal_chars(cfg, dims, offset, line, &text, &color, shape); |
390 | } |
391 | |
392 | fn change_vertical_chars<R, D>( |
393 | records: &mut R, |
394 | dims: &mut D, |
395 | cfg: &mut ColoredConfig, |
396 | line: usize, |
397 | text: String, |
398 | offset: Offset, |
399 | color: Option<ANSIBuf>, |
400 | ) where |
401 | R: Records + ExactRecords, |
402 | for<'a> D: Estimate<&'a R, ColoredConfig>, |
403 | D: Dimension, |
404 | { |
405 | dims.estimate(records, config:cfg); |
406 | let shape: (usize, usize) = (records.count_rows(), records.count_columns()); |
407 | set_vertical_chars(cfg, dims, offset, line, &text, &color, shape); |
408 | } |
409 | |