use std::{borrow::Cow, fmt::Display};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use crate::{
buffer::Buffer,
prelude::{Rect, TextAlign},
text::{StrStyle, StyledStr},
};
#[derive(Debug)]
pub struct Line<'a> {
pub parts: Vec<StyledStr<'a>>,
pub width: usize,
}
impl<'a> Line<'a> {
pub fn empty() -> Self {
Self {
parts: vec![],
width: 0,
}
}
pub fn push<T, S>(&mut self, text: T, width: usize, style: S)
where
T: Into<Cow<'a, str>>,
S: Into<StrStyle>,
{
let frag = StyledStr::styled(text, width, style);
self.parts.push(frag);
self.width += width;
}
pub fn pop(&mut self) {
if let Some(frag) = self.parts.pop() {
self.width -= frag.width;
}
}
pub fn render(&self, buffer: &mut Buffer, rect: Rect, align: TextAlign) {
let x_offset = self.align_offset(&rect, align);
let mut pos = *rect.pos();
pos.x += x_offset;
for frag in self.parts.iter() {
frag.render(buffer, &pos, &rect);
pos.x += frag.width;
}
}
pub fn add_ellipsis(&mut self, max_width: usize, ellipsis: &str) {
let width = ellipsis.width();
let target = max_width.saturating_sub(width);
while let Some(mut frag) = self.parts.pop() {
self.width -= frag.width;
let mut fwidth = frag.width;
let mut sid = frag.text.len();
for (idx, grapheme) in frag.text.grapheme_indices(true).rev() {
if self.width + fwidth <= target
&& !grapheme.starts_with(char::is_whitespace)
{
break;
}
fwidth -= grapheme.width();
sid = idx;
}
if sid > 0 {
let trunc = format!("{}{}", &frag.text[..sid], ellipsis);
frag.text = Cow::Owned(trunc);
frag.width = fwidth + width;
self.width += frag.width;
self.parts.push(frag);
break;
}
}
}
pub fn align_offset(&self, rect: &Rect, align: TextAlign) -> usize {
match align {
TextAlign::Left => 0,
TextAlign::Center => rect.width().saturating_sub(self.width) / 2,
TextAlign::Right => rect.width().saturating_sub(self.width),
}
}
}
impl<'a> Display for Line<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.parts.iter().try_for_each(|p| write!(f, "{p}"))
}
}