use core::fmt;
use std::hash::{DefaultHasher, Hash, Hasher};
use crate::{
buffer::Buffer,
enums::Wrap,
prelude::{Rect, Vec2},
text::Text,
widgets::{Element, LayoutNode, Widget},
};
#[derive(Debug)]
pub struct Paragraph {
children: Vec<Box<dyn Text>>,
separator: String,
wrap: Wrap,
}
impl Paragraph {
#[must_use]
pub fn new<I>(children: I) -> Self
where
I: IntoIterator,
I::Item: Into<Box<dyn Text>>,
{
Self {
children: children.into_iter().map(|i| i.into()).collect(),
..Default::default()
}
}
#[must_use]
pub fn empty() -> Self {
Default::default()
}
pub fn get(&self) -> String {
let mut res = "".to_string();
for child in self.children.iter() {
if !res.is_empty() {
res += &self.separator;
}
res += &child.get();
}
res
}
#[must_use]
pub fn separator(mut self, sep: &str) -> Self {
self.separator = sep.to_string();
self
}
#[must_use]
pub fn wrap(mut self, wrap: Wrap) -> Self {
self.wrap = wrap;
self
}
pub fn push<T>(&mut self, child: T)
where
T: Into<Box<dyn Text>>,
{
self.children.push(child.into());
}
}
impl<M: Clone + 'static> Widget<M> for Paragraph {
fn render(&self, buffer: &mut Buffer, layout: &LayoutNode) {
let rect = layout.area;
let mut pos = Vec2::new(rect.x(), rect.y());
let mut size = Vec2::new(rect.width(), rect.height());
let mut offset = 0;
for child in self.children.iter() {
let crect = Rect::from_coords(pos, size);
let end =
child.render_offset(buffer, crect, offset, Some(self.wrap));
size.y = size.y.saturating_sub(end.y - pos.y);
pos.y = end.y;
offset = end.x + self.separator.len();
if end.y >= rect.y() + rect.height()
&& end.x >= rect.x() + rect.width()
{
break;
}
if offset + self.separator.len() <= rect.width() && offset != 0 {
buffer.set_str(
&self.separator,
&Vec2::new(rect.x() + offset - 1, pos.y),
);
}
}
}
fn height(&self, size: &Vec2) -> usize {
match self.wrap {
Wrap::Letter => self.size_letter_wrap(size.x),
Wrap::Word => self.height_word_wrap(size),
}
}
fn width(&self, size: &Vec2) -> usize {
match self.wrap {
Wrap::Letter => self.size_letter_wrap(size.y),
Wrap::Word => self.width_word_wrap(size),
}
}
fn layout_hash(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.children
.iter()
.for_each(|c| c.get_text().hash(&mut hasher));
self.separator.hash(&mut hasher);
self.wrap.hash(&mut hasher);
hasher.finish()
}
}
impl fmt::Display for Paragraph {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.get())
}
}
impl Default for Paragraph {
fn default() -> Self {
Self {
children: Vec::new(),
separator: " ".to_string(),
wrap: Wrap::Word,
}
}
}
impl Paragraph {
fn height_word_wrap(&self, size: &Vec2) -> usize {
let mut coords = Vec2::new(0, 0);
for child in self.children.iter() {
let words: Vec<&str> =
child.get_text().split_whitespace().collect();
for word in words {
if (coords.x == 0 && coords.x + word.len() > size.x)
|| (coords.x != 0 && coords.x + word.len() + 1 > size.x)
{
coords.y += 1;
coords.x = 0;
}
if coords.x != 0 {
coords.x += 1;
}
coords.x += word.len();
}
}
coords.y + 1
}
fn width_word_wrap(&self, size: &Vec2) -> usize {
let mut guess = Vec2::new(self.size_letter_wrap(size.y), 0);
while self.height_word_wrap(&guess) > size.y {
guess.x += 1;
}
guess.x
}
fn size_letter_wrap(&self, size: usize) -> usize {
let mut len = 0;
for child in self.children.iter() {
len += child.get_text().len();
}
(len as f32 / size as f32).ceil() as usize
}
}
impl<M: Clone + 'static> From<Paragraph> for Box<dyn Widget<M>> {
fn from(value: Paragraph) -> Self {
Box::new(value)
}
}
impl<M: Clone + 'static> From<Paragraph> for Element<M> {
fn from(value: Paragraph) -> Self {
Element::new(value)
}
}