stylish_stringlike/widget/
hbox.rs

1use crate::text::{Pushable, Width};
2use crate::widget::{Fitable, Truncateable};
3use std::iter::FromIterator;
4
5/// A displayable box of text widgets.
6pub struct HBox<'a, T: Truncateable> {
7    elements: Vec<Box<dyn Fitable<T> + 'a>>,
8}
9
10impl<'a, T: Truncateable> Default for HBox<'a, T> {
11    fn default() -> Self {
12        HBox { elements: vec![] }
13    }
14}
15
16impl<'a, T: Truncateable> HBox<'a, T> {
17    pub fn new() -> Self {
18        HBox {
19            elements: Vec::new(),
20        }
21    }
22    /// Adds an element.
23    pub fn push(&mut self, element: Box<dyn Fitable<T> + 'a>) {
24        self.elements.push(element);
25    }
26    /// Truncates this widget to a given size.
27    pub fn truncate(&self, width: usize) -> T
28    where
29        T: Pushable<T> + Pushable<T::Output> + Default,
30    {
31        let mut space = width;
32        let mut todo: Vec<(usize, _)> = self
33            .elements
34            .iter()
35            .enumerate()
36            .filter_map(|(index, element)| {
37                if let Width::Bounded(_w) = element.width() {
38                    Some((index, element))
39                } else {
40                    None
41                }
42            })
43            .collect();
44        let mut to_fit = todo.len();
45        let mut widths: std::collections::HashMap<usize, usize> = Default::default();
46        while to_fit > 0 {
47            let target_width: f32 = space as f32 / to_fit as f32;
48            let mut to_pop = vec![];
49            for (rel_index, (index, element)) in todo.iter().enumerate() {
50                if let Width::Bounded(w) = element.width() {
51                    if (w as f32) <= target_width {
52                        space -= w;
53                        to_fit -= 1;
54                        widths.insert(*index, w);
55                        to_pop.push(rel_index)
56                    }
57                }
58            }
59            for index in to_pop.iter().rev() {
60                todo.remove(*index);
61            }
62            if to_pop.is_empty() {
63                let target_width = space / todo.len();
64                let rem = space % todo.len();
65                for (i, (index, _widget)) in todo.iter().enumerate() {
66                    let w = if i < rem {
67                        target_width + 1
68                    } else {
69                        target_width
70                    };
71                    space -= w;
72                    widths.insert(*index, w);
73                }
74                break;
75            }
76        }
77        let infinite_widths: Vec<(usize, _)> = self
78            .elements
79            .iter()
80            .enumerate()
81            .filter_map(|(index, element)| {
82                if let Width::Unbounded = element.width() {
83                    Some((index, element))
84                } else {
85                    None
86                }
87            })
88            .collect();
89        if !infinite_widths.is_empty() {
90            let target_width = space / infinite_widths.len();
91            let rem = space % infinite_widths.len();
92            for (rel_index, (abs_index, _element)) in infinite_widths.iter().enumerate() {
93                let w = if rel_index < rem {
94                    target_width + 1
95                } else {
96                    target_width
97                };
98                widths.insert(*abs_index, w);
99            }
100        }
101
102        let mut res: T = Default::default();
103        let elements = self
104            .elements
105            .iter()
106            .enumerate()
107            .map(move |(i, widget)| widget.truncate(widths[&i]))
108            .flatten();
109        for elem in elements {
110            res.push(&elem)
111        }
112        res
113    }
114}
115
116impl<'a, T: Truncateable> FromIterator<Box<dyn Fitable<T> + 'a>> for HBox<'a, T> {
117    fn from_iter<I>(iter: I) -> HBox<'a, T>
118    where
119        I: IntoIterator<Item = Box<dyn Fitable<T> + 'a>>,
120    {
121        let mut result: HBox<T> = Default::default();
122        for item in iter {
123            result.push(item)
124        }
125        result
126    }
127}
128
129#[cfg(test)]
130mod test {
131    use super::*;
132    use crate::text::*;
133    use crate::widget::{Repeat, TextWidget, TruncationStyle};
134    use std::borrow::Cow;
135    #[test]
136    fn make_hbox() {
137        let fmt_1 = Tag::new("<1>", "</1>");
138        let fmt_2 = Tag::new("<2>", "</2>");
139        let fmt_3 = Tag::new("<3>", "</3>");
140        let mut spans: Spans<Tag> = Default::default();
141        spans.push(&Span::new(Cow::Borrowed(&fmt_2), Cow::Borrowed("01234")));
142        spans.push(&Span::new(Cow::Borrowed(&fmt_3), Cow::Borrowed("56789")));
143        let truncator = {
144            let mut ellipsis = Spans::<Tag>::default();
145            ellipsis.push(&Span::new(Cow::Borrowed(&fmt_1), Cow::Borrowed("...")));
146            TruncationStyle::Left(ellipsis)
147        };
148        let widget = TextWidget::new(Cow::Borrowed(&spans), Cow::Borrowed(&truncator));
149        let mut hbox: HBox<Spans<Tag>> = Default::default();
150        hbox.push(Box::new(widget));
151        let actual = format!("{}", hbox.truncate(9));
152        let expected = String::from("<2>01234</2><3>5</3><1>...</1>");
153        assert_eq!(expected, actual);
154    }
155    #[test]
156    fn make_hbox_infinite() {
157        let fmt_1 = Tag::new("<1>", "</1>");
158        let fmt_2 = Tag::new("<2>", "</2>");
159        let span = Span::new(Cow::Borrowed(&fmt_2), Cow::Borrowed("="));
160        let repeat = Repeat::new(span);
161        let truncator =
162            TruncationStyle::Left(Span::new(Cow::Borrowed(&fmt_1), Cow::Borrowed("...")));
163        let widget = TextWidget::new(Cow::Borrowed(&repeat), Cow::Borrowed(&truncator));
164        let mut hbox: HBox<Spans<Tag>> = Default::default();
165        hbox.push(Box::new(widget));
166        let actual = format!("{}", hbox.truncate(5));
167        let expected = String::from("<2>==</2><1>...</1>");
168        assert_eq!(expected, actual);
169    }
170    #[test]
171    fn make_hbox_literal() {
172        let fmt_2 = Tag::new("<2>", "</2>");
173        let fmt_3 = Tag::new("<3>", "</3>");
174        let mut spans: Spans<Tag> = Default::default();
175        spans.push(&Span::new(Cow::Borrowed(&fmt_2), Cow::Borrowed("01234")));
176        spans.push(&Span::new(Cow::Borrowed(&fmt_3), Cow::Borrowed("56789")));
177        let truncator = TruncationStyle::Left("...");
178        let widget = TextWidget::new(Cow::Borrowed(&spans), Cow::Borrowed(&truncator));
179        let mut hbox: HBox<Spans<Tag>> = Default::default();
180        hbox.push(Box::new(widget));
181        let actual = format!("{}", hbox.truncate(9));
182        let expected = String::from("<2>01234</2><3>5...</3>");
183        assert_eq!(expected, actual);
184    }
185}