use crate::text::{Pushable, Width};
use crate::widget::{Fitable, Truncateable};
use std::iter::FromIterator;
pub struct HBox<'a, T: Truncateable> {
elements: Vec<Box<dyn Fitable<T> + 'a>>,
}
impl<'a, T: Truncateable> Default for HBox<'a, T> {
fn default() -> Self {
HBox { elements: vec![] }
}
}
impl<'a, T: Truncateable> HBox<'a, T> {
pub fn new() -> Self {
HBox {
elements: Vec::new(),
}
}
pub fn push(&mut self, element: Box<dyn Fitable<T> + 'a>) {
self.elements.push(element);
}
pub fn truncate(&self, width: usize) -> T
where
T: Pushable<T> + Pushable<T::Output> + Default,
{
let mut space = width;
let mut todo: Vec<(usize, _)> = self
.elements
.iter()
.enumerate()
.filter_map(|(index, element)| {
if let Width::Bounded(_w) = element.width() {
Some((index, element))
} else {
None
}
})
.collect();
let mut to_fit = todo.len();
let mut widths: std::collections::HashMap<usize, usize> = Default::default();
while to_fit > 0 {
let target_width: f32 = space as f32 / to_fit as f32;
let mut to_pop = vec![];
for (rel_index, (index, element)) in todo.iter().enumerate() {
if let Width::Bounded(w) = element.width() {
if (w as f32) <= target_width {
space -= w;
to_fit -= 1;
widths.insert(*index, w);
to_pop.push(rel_index)
}
}
}
for index in to_pop.iter().rev() {
todo.remove(*index);
}
if to_pop.is_empty() {
let target_width = space / todo.len();
let rem = space % todo.len();
for (i, (index, _widget)) in todo.iter().enumerate() {
let w = if i < rem {
target_width + 1
} else {
target_width
};
space -= w;
widths.insert(*index, w);
}
break;
}
}
let infinite_widths: Vec<(usize, _)> = self
.elements
.iter()
.enumerate()
.filter_map(|(index, element)| {
if let Width::Unbounded = element.width() {
Some((index, element))
} else {
None
}
})
.collect();
if !infinite_widths.is_empty() {
let target_width = space / infinite_widths.len();
let rem = space % infinite_widths.len();
for (rel_index, (abs_index, _element)) in infinite_widths.iter().enumerate() {
let w = if rel_index < rem {
target_width + 1
} else {
target_width
};
widths.insert(*abs_index, w);
}
}
let mut res: T = Default::default();
let elements = self
.elements
.iter()
.enumerate()
.map(move |(i, widget)| widget.truncate(widths[&i]))
.flatten();
for elem in elements {
res.push(&elem)
}
res
}
}
impl<'a, T: Truncateable> FromIterator<Box<dyn Fitable<T> + 'a>> for HBox<'a, T> {
fn from_iter<I>(iter: I) -> HBox<'a, T>
where
I: IntoIterator<Item = Box<dyn Fitable<T> + 'a>>,
{
let mut result: HBox<T> = Default::default();
for item in iter {
result.push(item)
}
result
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::text::*;
use crate::widget::{Repeat, TextWidget, TruncationStyle};
use std::borrow::Cow;
#[test]
fn make_hbox() {
let fmt_1 = Tag::new("<1>", "</1>");
let fmt_2 = Tag::new("<2>", "</2>");
let fmt_3 = Tag::new("<3>", "</3>");
let mut spans: Spans<Tag> = Default::default();
spans.push(&Span::new(Cow::Borrowed(&fmt_2), Cow::Borrowed("01234")));
spans.push(&Span::new(Cow::Borrowed(&fmt_3), Cow::Borrowed("56789")));
let truncator = {
let mut ellipsis = Spans::<Tag>::default();
ellipsis.push(&Span::new(Cow::Borrowed(&fmt_1), Cow::Borrowed("...")));
TruncationStyle::Left(ellipsis)
};
let widget = TextWidget::new(Cow::Borrowed(&spans), Cow::Borrowed(&truncator));
let mut hbox: HBox<Spans<Tag>> = Default::default();
hbox.push(Box::new(widget));
let actual = format!("{}", hbox.truncate(9));
let expected = String::from("<2>01234</2><3>5</3><1>...</1>");
assert_eq!(expected, actual);
}
#[test]
fn make_hbox_infinite() {
let fmt_1 = Tag::new("<1>", "</1>");
let fmt_2 = Tag::new("<2>", "</2>");
let span = Span::new(Cow::Borrowed(&fmt_2), Cow::Borrowed("="));
let repeat = Repeat::new(span);
let truncator =
TruncationStyle::Left(Span::new(Cow::Borrowed(&fmt_1), Cow::Borrowed("...")));
let widget = TextWidget::new(Cow::Borrowed(&repeat), Cow::Borrowed(&truncator));
let mut hbox: HBox<Spans<Tag>> = Default::default();
hbox.push(Box::new(widget));
let actual = format!("{}", hbox.truncate(5));
let expected = String::from("<2>==</2><1>...</1>");
assert_eq!(expected, actual);
}
#[test]
fn make_hbox_literal() {
let fmt_2 = Tag::new("<2>", "</2>");
let fmt_3 = Tag::new("<3>", "</3>");
let mut spans: Spans<Tag> = Default::default();
spans.push(&Span::new(Cow::Borrowed(&fmt_2), Cow::Borrowed("01234")));
spans.push(&Span::new(Cow::Borrowed(&fmt_3), Cow::Borrowed("56789")));
let truncator = TruncationStyle::Left("...");
let widget = TextWidget::new(Cow::Borrowed(&spans), Cow::Borrowed(&truncator));
let mut hbox: HBox<Spans<Tag>> = Default::default();
hbox.push(Box::new(widget));
let actual = format!("{}", hbox.truncate(9));
let expected = String::from("<2>01234</2><3>5...</3>");
assert_eq!(expected, actual);
}
}