1use std::cmp;
2
3use crate::{
4 grid::{
5 config::{AlignmentHorizontal, AlignmentVertical, ColoredConfig, Entity, Offset, Position},
6 dimension::{CompleteDimension, Dimension, Estimate},
7 records::{
8 vec_records::{Text, VecRecords},
9 ExactRecords, PeekableRecords, Records, Resizable,
10 },
11 util::string::{get_char_width, get_line_width},
12 },
13 settings::{
14 object::{Column, Row},
15 style::LineText,
16 Alignment, Color, TableOption,
17 },
18};
19
20#[derive(Debug, Clone)]
77pub struct ColumnNames {
78 names: Option<Vec<String>>,
79 colors: Option<ListValue<Color>>,
80 alignments: ListValue<Alignment>,
81 line: usize,
82}
83
84impl ColumnNames {
85 pub fn head() -> Self {
110 Self {
111 names: Default::default(),
112 colors: Default::default(),
113 line: Default::default(),
114 alignments: ListValue::Static(Alignment::left()),
115 }
116 }
117
118 pub fn new<I>(names: I) -> Self
140 where
141 I: IntoIterator,
142 I::Item: Into<String>,
143 {
144 let names = names.into_iter().map(Into::into).collect::<Vec<_>>();
145
146 Self {
147 names: Some(names),
148 alignments: ListValue::Static(Alignment::left()),
149 colors: Default::default(),
150 line: 0,
151 }
152 }
153
154 pub fn color<T>(self, color: T) -> Self
178 where
179 T: Into<ListValue<Color>>,
180 {
181 Self {
182 names: self.names,
183 line: self.line,
184 alignments: self.alignments,
185 colors: Some(color.into()),
186 }
187 }
188
189 pub fn line(self, i: usize) -> Self {
210 Self {
211 names: self.names,
212 line: i,
213 alignments: self.alignments,
214 colors: self.colors,
215 }
216 }
217
218 pub fn alignment<T>(self, alignment: T) -> Self
242 where
243 T: Into<ListValue<Alignment>>,
244 {
245 Self {
246 names: self.names,
247 line: self.line,
248 alignments: alignment.into(),
249 colors: self.colors,
250 }
251 }
252}
253
254impl TableOption<VecRecords<Text<String>>, ColoredConfig, CompleteDimension> for ColumnNames {
257 fn change(
258 self,
259 records: &mut VecRecords<Text<String>>,
260 cfg: &mut ColoredConfig,
261 dims: &mut CompleteDimension,
262 ) {
263 let count_rows = records.count_rows();
264 let count_columns = records.count_columns();
265
266 if count_columns == 0 || count_rows == 0 || self.line > count_rows {
267 return;
268 }
269
270 let alignment_horizontal = convert_alignment_value(self.alignments.clone());
271 let alignment_vertical = convert_alignment_value(self.alignments.clone());
272
273 if let Some(alignment) = alignment_horizontal {
274 let names = get_column_names(records, self.names);
275 let names = vec_set_size(names, records.count_columns());
276 set_column_text(names, self.line, alignment, self.colors, records, dims, cfg);
277 return;
278 }
279
280 if let Some(alignment) = alignment_vertical {
281 let names = get_column_names(records, self.names);
282 let names = vec_set_size(names, records.count_rows());
283 set_row_text(names, self.line, alignment, self.colors, records, dims, cfg);
284 return;
285 }
286
287 let names = get_column_names(records, self.names);
288 let names = vec_set_size(names, records.count_columns());
289 let alignment = ListValue::Static(AlignmentHorizontal::Left);
290 set_column_text(names, self.line, alignment, self.colors, records, dims, cfg);
291 }
292
293 fn hint_change(&self) -> Option<Entity> {
294 let alignment_vertical: Option<ListValue<AlignmentVertical>> =
295 convert_alignment_value(self.alignments.clone());
296 if alignment_vertical.is_some() {
297 Some(Entity::Column(0))
298 } else {
299 Some(Entity::Row(0))
300 }
301 }
302}
303
304fn set_column_text(
305 names: Vec<String>,
306 target_line: usize,
307 alignments: ListValue<AlignmentHorizontal>,
308 colors: Option<ListValue<Color>>,
309 records: &mut VecRecords<Text<String>>,
310 dims: &mut CompleteDimension,
311 cfg: &mut ColoredConfig,
312) {
313 dims.estimate(&*records, cfg);
314
315 let count_columns = names.len();
316 let widths = names
317 .iter()
318 .enumerate()
319 .map(|(col, name)| (cmp::max(get_line_width(name), dims.get_width(col))))
320 .collect::<Vec<_>>();
321
322 dims.set_widths(widths.clone());
323
324 let mut total_width = 0;
325 for (column, (width, name)) in widths.into_iter().zip(names).enumerate() {
326 let color = get_color(&colors, column);
327 let alignment = alignments.get(column).unwrap_or(AlignmentHorizontal::Left);
328 let left_vertical = get_vertical_width(cfg, (target_line, column).into(), count_columns);
329 let grid_offset =
330 total_width + left_vertical + get_horizontal_indent(&name, alignment, width);
331 let line = Row::from(target_line);
332
333 let linetext = create_line_text(&name, grid_offset, color, line);
334 linetext.change(records, cfg, dims);
335
336 total_width += width + left_vertical;
337 }
338}
339
340fn set_row_text(
341 names: Vec<String>,
342 target_line: usize,
343 alignments: ListValue<AlignmentVertical>,
344 colors: Option<ListValue<Color>>,
345 records: &mut VecRecords<Text<String>>,
346 dims: &mut CompleteDimension,
347 cfg: &mut ColoredConfig,
348) {
349 dims.estimate(&*records, cfg);
350
351 let count_rows = names.len();
352 let heights = names
353 .iter()
354 .enumerate()
355 .map(|(row, name)| (cmp::max(get_line_width(name), dims.get_height(row))))
356 .collect::<Vec<_>>();
357
358 dims.set_heights(heights.clone());
359
360 let mut total_height = 0;
361 for (row, (row_height, name)) in heights.into_iter().zip(names).enumerate() {
362 let color = get_color(&colors, row);
363 let alignment = alignments.get(row).unwrap_or(AlignmentVertical::Top);
364 let top_horizontal = get_horizontal_width(cfg, (row, target_line).into(), count_rows);
365 let cell_indent = get_vertical_indent(&name, alignment, row_height);
366 let grid_offset = total_height + top_horizontal + cell_indent;
367 let line = Column::from(target_line);
368
369 let linetext = create_line_text(&name, grid_offset, color, line);
370 linetext.change(records, cfg, dims);
371
372 total_height += row_height + top_horizontal;
373 }
374}
375
376fn get_column_names(
377 records: &mut VecRecords<Text<String>>,
378 opt: Option<Vec<String>>,
379) -> Vec<String> {
380 match opt {
381 Some(names) => names
382 .into_iter()
383 .map(|name| name.lines().next().unwrap_or("").to_string())
384 .collect::<Vec<_>>(),
385 None => collect_head(records),
386 }
387}
388
389fn vec_set_size(mut data: Vec<String>, size: usize) -> Vec<String> {
390 match data.len().cmp(&size) {
391 cmp::Ordering::Equal => {}
392 cmp::Ordering::Less => {
393 let additional_size = size - data.len();
394 data.extend(std::iter::repeat_n(String::new(), additional_size));
395 }
396 cmp::Ordering::Greater => {
397 data.truncate(size);
398 }
399 }
400
401 data
402}
403
404fn collect_head(records: &mut VecRecords<Text<String>>) -> Vec<String> {
405 if records.count_rows() == 0 || records.count_columns() == 0 {
406 return Vec::new();
407 }
408
409 let names = (0..records.count_columns())
410 .map(|column| records.get_line((0, column).into(), 0))
411 .map(ToString::to_string)
412 .collect();
413
414 records.remove_row(0);
415
416 names
417}
418
419fn create_line_text<T>(text: &str, offset: usize, color: Option<&Color>, line: T) -> LineText<T> {
420 let offset = Offset::Start(offset);
421 let mut btext = LineText::new(text, line).offset(offset);
422 if let Some(color) = color {
423 btext = btext.color(color.clone());
424 }
425
426 btext
427}
428
429fn get_color(colors: &Option<ListValue<Color>>, i: usize) -> Option<&Color> {
430 match colors {
431 Some(ListValue::List(list)) => list.get(i),
432 Some(ListValue::Static(color)) => Some(color),
433 None => None,
434 }
435}
436
437fn get_horizontal_indent(text: &str, align: AlignmentHorizontal, available: usize) -> usize {
438 match align {
439 AlignmentHorizontal::Left => 0,
440 AlignmentHorizontal::Right => available - get_line_width(text),
441 AlignmentHorizontal::Center => (available - get_line_width(text)) / 2,
442 }
443}
444
445fn get_vertical_indent(text: &str, align: AlignmentVertical, available: usize) -> usize {
446 match align {
447 AlignmentVertical::Top => 0,
448 AlignmentVertical::Bottom => available - get_line_width(text),
449 AlignmentVertical::Center => (available - get_line_width(text)) / 2,
450 }
451}
452
453fn get_vertical_width(cfg: &mut ColoredConfig, pos: Position, count_columns: usize) -> usize {
454 cfg.get_vertical(pos, count_columns)
455 .map(get_char_width)
456 .unwrap_or(0)
457}
458
459fn get_horizontal_width(cfg: &mut ColoredConfig, pos: Position, count_rows: usize) -> usize {
460 cfg.get_horizontal(pos, count_rows)
461 .map(get_char_width)
462 .unwrap_or(0)
463}
464
465fn convert_alignment_value<T>(value: ListValue<Alignment>) -> Option<ListValue<T>>
466where
467 Option<T>: From<Alignment>,
468{
469 match value {
470 ListValue::List(list) => {
471 let new = list
472 .iter()
473 .flat_map(|value| Option::from(*value))
474 .collect::<Vec<_>>();
475 if new.len() == list.len() {
476 Some(ListValue::List(new))
477 } else {
478 None
479 }
480 }
481 ListValue::Static(value) => Option::from(value).map(ListValue::Static),
482 }
483}
484
485#[derive(Debug, Clone)]
486pub enum ListValue<T> {
487 List(Vec<T>),
488 Static(T),
489}
490
491impl<T> ListValue<T> {
492 fn get(&self, i: usize) -> Option<T>
493 where
494 T: Copy,
495 {
496 match self {
497 ListValue::List(list) => list.get(i).copied(),
498 ListValue::Static(alignment) => Some(*alignment),
499 }
500 }
501}
502
503impl<T> From<T> for ListValue<T> {
504 fn from(value: T) -> Self {
505 Self::Static(value)
506 }
507}
508
509impl<T> From<Vec<T>> for ListValue<T> {
510 fn from(value: Vec<T>) -> Self {
511 Self::List(value)
512 }
513}
514
515impl<T> Default for ListValue<T>
516where
517 T: Default,
518{
519 fn default() -> Self {
520 Self::Static(T::default())
521 }
522}