1use 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
14use 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)]
36pub 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
44impl<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
82impl<R, D> TableOption<R, ColoredConfig, D> for LineText<Row>
83where
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
95impl<R, D> TableOption<R, ColoredConfig, D> for LineText<FirstRow>
96where
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
107impl<R, D> TableOption<R, ColoredConfig, D> for LineText<LastRow>
108where
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
120impl<R, D> TableOption<R, ColoredConfig, D> for LineText<Column>
121where
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
133impl<R, D> TableOption<R, ColoredConfig, D> for LineText<FirstColumn>
134where
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
145impl<R, D> TableOption<R, ColoredConfig, D> for LineText<LastColumn>
146where
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
158fn 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
244fn 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
329fn 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
348fn total_width<D>(cfg: &SpannedConfig, dims: &D, count_columns: usize) -> usize
349where
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
361fn total_height<D>(cfg: &SpannedConfig, dims: &D, count_rows: usize) -> usize
362where
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
374fn 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
392fn 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