stylish_stringlike/widget/
hbox.rs1use crate::text::{Pushable, Width};
2use crate::widget::{Fitable, Truncateable};
3use std::iter::FromIterator;
4
5pub 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 pub fn push(&mut self, element: Box<dyn Fitable<T> + 'a>) {
24 self.elements.push(element);
25 }
26 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}