use super::{layout::push_spaces, validation::validate_non_empty_display_text};
use crate::{Line, Render, Span, Style, Text, TextError};
use unicode_width::UnicodeWidthStr;
const DEFAULT_BULLET: &str = "•";
const DEFAULT_GAP: usize = 1;
pub fn list_item(content: impl AsRef<str>) -> Result<ListItem<Text>, TextError> {
Ok(ListItem::new(Text::from_plain(content)?))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListItem<T = Text> {
content: T,
bullet: String,
bullet_width: usize,
bullet_style: Style,
gap: usize,
}
impl<T> ListItem<T> {
pub fn new(content: T) -> Self {
Self {
content,
bullet: DEFAULT_BULLET.to_owned(),
bullet_width: UnicodeWidthStr::width(DEFAULT_BULLET),
bullet_style: Style::new(),
gap: DEFAULT_GAP,
}
}
pub fn bullet(mut self, bullet: impl Into<String>) -> Result<Self, TextError> {
let bullet = bullet.into();
self.bullet_width = validate_non_empty_display_text(&bullet)?;
self.bullet = bullet;
Ok(self)
}
pub fn bullet_style(mut self, style: Style) -> Self {
self.bullet_style = style;
self
}
pub fn gap(mut self, gap: usize) -> Self {
self.gap = gap;
self
}
pub fn content(&self) -> &T {
&self.content
}
pub fn content_mut(&mut self) -> &mut T {
&mut self.content
}
pub fn bullet_content(&self) -> &str {
&self.bullet
}
pub fn bullet_style_value(&self) -> Style {
self.bullet_style
}
pub fn gap_width(&self) -> usize {
self.gap
}
pub fn bullet_width(&self) -> usize {
self.bullet_width
}
fn prefix_width(&self) -> usize {
self.bullet_width.saturating_add(self.gap)
}
fn first_prefix_line(&self, fitting_gap: usize) -> Line {
let mut spans = Vec::with_capacity(2);
spans.push(self.bullet_span());
push_spaces(&mut spans, fitting_gap);
Line::from_spans(spans)
}
fn first_line(&self, content: Line) -> Line {
let content_spans = content.into_spans();
let mut spans = Vec::with_capacity(content_spans.len() + 2);
spans.push(self.bullet_span());
push_spaces(&mut spans, self.gap);
spans.extend(content_spans);
Line::from_spans(spans)
}
fn continuation_line(&self, content: Line) -> Line {
let content_spans = content.into_spans();
let mut spans = Vec::with_capacity(content_spans.len() + 1);
push_spaces(&mut spans, self.prefix_width());
spans.extend(content_spans);
Line::from_spans(spans)
}
fn bullet_span(&self) -> Span {
Span::from_trusted_content(self.bullet.clone(), self.bullet_style)
}
}
impl<T: Render> Render for ListItem<T> {
fn render(&self, width: u16) -> Text {
let width = usize::from(width);
if width == 0 || width < self.bullet_width {
return Text::empty();
}
let prefix_width = self.prefix_width();
if width <= prefix_width {
let content = self.content.render(1);
if content.lines().is_empty() {
Text::empty()
} else {
Text::from_lines(vec![self.first_prefix_line(width - self.bullet_width)])
}
} else {
let content_width = width - prefix_width;
let content = self
.content
.render(content_width as u16)
.into_wrapped(content_width);
let content_lines = content.into_lines();
if content_lines.is_empty() {
return Text::empty();
}
let mut lines = Vec::with_capacity(content_lines.len());
for (index, line) in content_lines.into_iter().enumerate() {
if index == 0 {
lines.push(self.first_line(line));
} else {
lines.push(self.continuation_line(line));
}
}
Text::from_lines(lines)
}
}
fn render_every_frame(&self) -> bool {
self.content.render_every_frame()
}
}