use crate::{
grid::{
ansi::ANSIBuf,
config::{
AlignmentHorizontal, AlignmentVertical, ColoredConfig, Entity, Offset, SpannedConfig,
},
dimension::{Dimension, Estimate},
records::{ExactRecords, Records},
util::string::get_text_width,
},
settings::{
object::{
Column, FirstColumn, FirstRow, LastColumn, LastColumnOffset, LastRow, LastRowOffset,
Object, Row,
},
Alignment, Color, TableOption,
},
};
#[derive(Debug)]
pub struct LineText<Line> {
text: String,
offset: Offset,
color: Option<ANSIBuf>,
alignment: Option<Alignment>,
line: Line,
}
impl<Line> LineText<Line> {
pub fn new<S>(text: S, line: Line) -> Self
where
S: Into<String>,
{
LineText {
line,
text: text.into(),
offset: Offset::Start(0),
color: None,
alignment: None,
}
}
pub fn align(mut self, alignment: Alignment) -> Self {
self.alignment = Some(alignment);
self
}
pub fn offset(mut self, offset: impl Into<Offset>) -> Self {
self.offset = offset.into();
self
}
pub fn color(mut self, color: Color) -> Self {
self.color = Some(color.into());
self
}
}
impl<R, D> TableOption<R, ColoredConfig, D> for LineText<Row>
where
R: Records + ExactRecords,
for<'a> &'a R: Records,
for<'a> D: Estimate<&'a R, ColoredConfig>,
D: Dimension,
{
fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
let line = self.line.into();
change_horizontal_chars(records, dims, cfg, create_line(self, line))
}
}
impl<R, D> TableOption<R, ColoredConfig, D> for LineText<FirstRow>
where
R: Records + ExactRecords,
for<'a> &'a R: Records,
for<'a> D: Estimate<&'a R, ColoredConfig>,
D: Dimension,
{
fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
change_horizontal_chars(records, dims, cfg, create_line(self, 0))
}
}
impl<R, D> TableOption<R, ColoredConfig, D> for LineText<LastRow>
where
R: Records + ExactRecords,
for<'a> &'a R: Records,
for<'a> D: Estimate<&'a R, ColoredConfig>,
D: Dimension,
{
fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
let line = self.line.cells(records).next();
if let Some(Entity::Row(line)) = line {
change_horizontal_chars(records, dims, cfg, create_line(self, line))
}
}
}
impl<R, D> TableOption<R, ColoredConfig, D> for LineText<LastRowOffset>
where
R: Records + ExactRecords,
for<'a> &'a R: Records,
for<'a> D: Estimate<&'a R, ColoredConfig>,
D: Dimension,
{
fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
let line = self.line.cells(records).next();
if let Some(Entity::Row(line)) = line {
change_horizontal_chars(records, dims, cfg, create_line(self, line))
}
}
}
impl<R, D> TableOption<R, ColoredConfig, D> for LineText<Column>
where
R: Records + ExactRecords,
for<'a> &'a R: Records,
for<'a> D: Estimate<&'a R, ColoredConfig>,
D: Dimension,
{
fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
let line = self.line.into();
change_vertical_chars(records, dims, cfg, create_line(self, line))
}
}
impl<R, D> TableOption<R, ColoredConfig, D> for LineText<FirstColumn>
where
R: Records + ExactRecords,
for<'a> &'a R: Records,
for<'a> D: Estimate<&'a R, ColoredConfig>,
D: Dimension,
{
fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
change_vertical_chars(records, dims, cfg, create_line(self, 0))
}
}
impl<R, D> TableOption<R, ColoredConfig, D> for LineText<LastColumn>
where
R: Records + ExactRecords,
for<'a> &'a R: Records,
for<'a> D: Estimate<&'a R, ColoredConfig>,
D: Dimension,
{
fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
let line = self.line.cells(records).next();
if let Some(Entity::Column(line)) = line {
change_vertical_chars(records, dims, cfg, create_line(self, line))
}
}
}
impl<R, D> TableOption<R, ColoredConfig, D> for LineText<LastColumnOffset>
where
R: Records + ExactRecords,
for<'a> &'a R: Records,
for<'a> D: Estimate<&'a R, ColoredConfig>,
D: Dimension,
{
fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
let line = self.line.cells(records).next();
if let Some(Entity::Column(line)) = line {
change_vertical_chars(records, dims, cfg, create_line(self, line))
}
}
}
fn set_horizontal_chars<D>(
cfg: &mut SpannedConfig,
dims: &D,
line: LineText<usize>,
shape: (usize, usize),
) where
D: Dimension,
{
let alignment = line.alignment.and_then(|a| a.as_horizontal());
let offset = line.offset;
let text = &line.text;
let color = &line.color;
let line = line.line;
let (_, count_columns) = shape;
let total_width = total_width(cfg, dims, count_columns);
let offset = match alignment {
Some(alignment) => {
let off = get_horizontal_alignment_offset(text, alignment, total_width);
offset_sum(off, offset)
}
None => offset,
};
let pos = get_start_pos(offset, total_width);
let pos = match pos {
Some(pos) => pos,
None => return,
};
let mut chars = text.chars();
let mut i = cfg.has_vertical(0, count_columns) as usize;
if i == 1 && pos == 0 {
let c = match chars.next() {
Some(c) => c,
None => return,
};
let mut b = cfg.get_border((line, 0).into(), shape);
b.left_top_corner = b.left_top_corner.map(|_| c);
cfg.set_border((line, 0).into(), b);
if let Some(color) = color.as_ref() {
let mut b = cfg.get_border_color((line, 0).into(), shape).cloned();
b.left_top_corner = Some(color.clone());
cfg.set_border_color((line, 0).into(), b);
}
}
for col in 0..count_columns {
let w = dims.get_width(col);
if i + w > pos {
for off in 0..w {
if i + off < pos {
continue;
}
let c = match chars.next() {
Some(c) => c,
None => return,
};
cfg.set_horizontal_char((line, col).into(), Offset::Start(off), c);
if let Some(color) = color.as_ref() {
cfg.set_horizontal_char_color(
(line, col).into(),
Offset::Start(off),
color.clone(),
);
}
}
}
i += w;
if cfg.has_vertical(col + 1, count_columns) {
i += 1;
if i > pos {
let c = match chars.next() {
Some(c) => c,
None => return,
};
let mut b = cfg.get_border((line, col).into(), shape);
b.right_top_corner = b.right_top_corner.map(|_| c);
cfg.set_border((line, col).into(), b);
if let Some(color) = color.as_ref() {
let mut b = cfg.get_border_color((line, col).into(), shape).cloned();
b.right_top_corner = Some(color.clone());
cfg.set_border_color((line, col).into(), b);
}
}
}
}
}
fn set_vertical_chars<D>(
cfg: &mut SpannedConfig,
dims: &D,
line: LineText<usize>,
shape: (usize, usize),
) where
D: Dimension,
{
let alignment = line.alignment.and_then(|a| a.as_vertical());
let offset = line.offset;
let text = &line.text;
let color = &line.color;
let line = line.line;
let (count_rows, _) = shape;
let total_width = total_height(cfg, dims, count_rows);
let offset = match alignment {
Some(alignment) => {
let off = get_vertical_alignment_offset(text, alignment, total_width);
offset_sum(off, offset)
}
None => offset,
};
let pos = get_start_pos(offset, total_width);
let pos = match pos {
Some(pos) => pos,
None => return,
};
let mut chars = text.chars();
let mut i = cfg.has_horizontal(0, count_rows) as usize;
if i == 1 && pos == 0 {
let c = match chars.next() {
Some(c) => c,
None => return,
};
let mut b = cfg.get_border((0, line).into(), shape);
b.left_top_corner = b.left_top_corner.map(|_| c);
cfg.set_border((0, line).into(), b);
if let Some(color) = color.as_ref() {
let mut b = cfg.get_border_color((0, line).into(), shape).cloned();
b.left_top_corner = Some(color.clone());
cfg.set_border_color((0, line).into(), b);
}
}
for row in 0..count_rows {
let row_height = dims.get_height(row);
if i + row_height > pos {
for off in 0..row_height {
if i + off < pos {
continue;
}
let c = match chars.next() {
Some(c) => c,
None => return,
};
cfg.set_vertical_char((row, line).into(), Offset::Start(off), c);
if let Some(color) = color.as_ref() {
cfg.set_vertical_char_color(
(row, line).into(),
Offset::Start(off),
color.clone(),
);
}
}
}
i += row_height;
if cfg.has_horizontal(row + 1, count_rows) {
i += 1;
if i > pos {
let c = match chars.next() {
Some(c) => c,
None => return,
};
let mut b = cfg.get_border((row, line).into(), shape);
b.left_bottom_corner = b.left_bottom_corner.map(|_| c);
cfg.set_border((row, line).into(), b);
if let Some(color) = color.as_ref() {
let mut b = cfg.get_border_color((row, line).into(), shape).cloned();
b.left_bottom_corner = Some(color.clone());
cfg.set_border_color((row, line).into(), b);
}
}
}
}
}
fn get_start_pos(offset: Offset, total: usize) -> Option<usize> {
match offset {
Offset::Start(i) => {
if i > total {
None
} else {
Some(i)
}
}
Offset::End(i) => {
if i > total {
None
} else {
Some(total - i)
}
}
}
}
fn get_horizontal_alignment_offset(
text: &str,
alignment: AlignmentHorizontal,
total: usize,
) -> Offset {
match alignment {
AlignmentHorizontal::Center => {
let width = get_text_width(text);
let mut off = 0;
if total > width {
let center = total / 2;
let text_center = width / 2;
off = center.saturating_sub(text_center);
}
Offset::Start(off)
}
AlignmentHorizontal::Left => Offset::Start(0),
AlignmentHorizontal::Right => {
let width = get_text_width(text);
Offset::End(width)
}
}
}
fn get_vertical_alignment_offset(text: &str, alignment: AlignmentVertical, total: usize) -> Offset {
match alignment {
AlignmentVertical::Center => {
let width = get_text_width(text);
let mut off = 0;
if total > width {
let center = total / 2;
let text_center = width / 2;
off = center.saturating_sub(text_center);
}
Offset::Start(off)
}
AlignmentVertical::Top => Offset::Start(0),
AlignmentVertical::Bottom => Offset::End(0),
}
}
fn offset_sum(orig: Offset, and: Offset) -> Offset {
match (orig, and) {
(Offset::Start(a), Offset::Start(b)) => Offset::Start(a + b),
(Offset::Start(a), Offset::End(b)) => Offset::Start(a.saturating_sub(b)),
(Offset::End(a), Offset::Start(b)) => Offset::End(a + b),
(Offset::End(a), Offset::End(b)) => Offset::End(a.saturating_sub(b)),
}
}
fn total_width<D>(cfg: &SpannedConfig, dims: &D, count_columns: usize) -> usize
where
D: Dimension,
{
let mut total = cfg.has_vertical(count_columns, count_columns) as usize;
for col in 0..count_columns {
total += dims.get_width(col);
total += cfg.has_vertical(col, count_columns) as usize;
}
total
}
fn total_height<D>(cfg: &SpannedConfig, dims: &D, count_rows: usize) -> usize
where
D: Dimension,
{
let mut total = cfg.has_horizontal(count_rows, count_rows) as usize;
for row in 0..count_rows {
total += dims.get_height(row);
total += cfg.has_horizontal(row, count_rows) as usize;
}
total
}
fn change_horizontal_chars<R, D>(
records: &mut R,
dims: &mut D,
cfg: &mut ColoredConfig,
line: LineText<usize>,
) where
R: Records + ExactRecords,
for<'a> D: Estimate<&'a R, ColoredConfig>,
D: Dimension,
{
dims.estimate(records, cfg);
let shape = (records.count_rows(), records.count_columns());
set_horizontal_chars(cfg, dims, line, shape);
}
fn change_vertical_chars<R, D>(
records: &mut R,
dims: &mut D,
cfg: &mut ColoredConfig,
line: LineText<usize>,
) where
R: Records + ExactRecords,
for<'a> D: Estimate<&'a R, ColoredConfig>,
D: Dimension,
{
dims.estimate(records, cfg);
let shape = (records.count_rows(), records.count_columns());
set_vertical_chars(cfg, dims, line, shape);
}
fn create_line<T>(orig: LineText<T>, line: usize) -> LineText<usize> {
LineText {
text: orig.text,
offset: orig.offset,
color: orig.color,
alignment: orig.alignment,
line,
}
}