1use crate::components::Blank;
16use crate::Component;
17use crate::Dimensions;
18use crate::DrawMode;
19use crate::Lines;
20
21#[derive(Debug, Eq, PartialEq, Clone, Copy)]
23pub enum VerticalAlignmentKind {
24 Top,
26 Center,
28 Bottom,
30}
31
32#[derive(Debug, Eq, PartialEq, Clone, Copy)]
34pub enum HorizontalAlignmentKind {
35 Left(bool),
38 Center,
40 Right,
42}
43
44#[derive(Debug)]
48pub struct Aligned<C: Component = Box<dyn Component>> {
49 pub child: C,
50 pub horizontal: HorizontalAlignmentKind,
51 pub vertical: VerticalAlignmentKind,
52}
53
54impl<C: Component> Aligned<C> {
55 pub fn new(
57 child: C,
58 horizontal: HorizontalAlignmentKind,
59 vertical: VerticalAlignmentKind,
60 ) -> Self {
61 Self {
62 child,
63 horizontal,
64 vertical,
65 }
66 }
67}
68
69impl Default for Aligned {
70 fn default() -> Self {
71 Self {
72 child: Box::new(Blank),
73 horizontal: HorizontalAlignmentKind::Left(false),
74 vertical: VerticalAlignmentKind::Top,
75 }
76 }
77}
78
79impl<C: Component> Component for Aligned<C> {
80 fn draw_unchecked(&self, dimensions: Dimensions, mode: DrawMode) -> anyhow::Result<Lines> {
81 let Dimensions { width, height } = dimensions;
82 let mut output = self.child.draw(dimensions, mode)?;
83
84 let number_of_lines = output.len();
85 let padding_needed = height.saturating_sub(number_of_lines);
86 match self.vertical {
87 VerticalAlignmentKind::Top => {}
88 VerticalAlignmentKind::Center => {
89 let top_pad = padding_needed / 2;
90 output.pad_lines_top(top_pad);
91 output.pad_lines_bottom(padding_needed - top_pad);
92 }
93 VerticalAlignmentKind::Bottom => {
94 output.pad_lines_top(padding_needed);
95 }
96 }
97
98 match self.horizontal {
99 HorizontalAlignmentKind::Left(justified) => {
100 if justified {
101 output.justify();
102 }
103 }
104 HorizontalAlignmentKind::Center => {
105 for line in output.iter_mut() {
106 let output_len = line.len();
107 let padding_needed = width.saturating_sub(output_len);
108 let left_pad = padding_needed / 2;
109 line.pad_left(left_pad);
110 line.pad_right(padding_needed - left_pad);
112 }
113 }
114 HorizontalAlignmentKind::Right => {
115 for line in output.iter_mut() {
116 line.pad_left(width.saturating_sub(line.len()));
117 }
118 }
119 }
120 Ok(output)
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use derive_more::AsRef;
127
128 use crate::components::alignment::HorizontalAlignmentKind;
129 use crate::components::alignment::VerticalAlignmentKind;
130 use crate::components::echo::Echo;
131 use crate::components::Aligned;
132 use crate::components::DrawMode;
133 use crate::Component;
134 use crate::Dimensions;
135 use crate::Line;
136 use crate::Lines;
137
138 #[derive(AsRef, Debug)]
139 struct Msg(Lines);
140
141 #[test]
142 fn test_align_left_unjustified() {
143 let original = Lines(vec![
144 vec!["hello world"].try_into().unwrap(),
145 vec!["pretty normal test"].try_into().unwrap(),
146 ]);
147 let component = Aligned::new(
148 Echo(original.clone()),
149 HorizontalAlignmentKind::Left(false),
150 VerticalAlignmentKind::Top,
151 );
152 let dimensions = Dimensions::new(20, 20);
153 let actual = component.draw(dimensions, DrawMode::Normal).unwrap();
154
155 assert_eq!(actual, original);
156 }
157
158 #[test]
159 fn test_align_left_justified() {
160 let original = Lines(vec![
161 vec!["hello world"].try_into().unwrap(), vec!["pretty normal test"].try_into().unwrap(), vec!["short"].try_into().unwrap(), ]);
165 let component = Aligned::new(
166 Echo(original),
167 HorizontalAlignmentKind::Left(true),
168 VerticalAlignmentKind::Top,
169 );
170 let dimensions = Dimensions::new(20, 20);
171 let actual = component.draw(dimensions, DrawMode::Normal).unwrap();
172 let expected = Lines(vec![
173 vec!["hello world", &" ".repeat(18 - 11)]
174 .try_into()
175 .unwrap(),
176 vec!["pretty normal test"].try_into().unwrap(),
177 vec!["short", &" ".repeat(18 - 5)].try_into().unwrap(),
178 ]);
179
180 assert_eq!(actual, expected,);
181 }
182
183 #[test]
184 fn test_align_col_center() {
185 let original = Lines(vec![
186 vec!["hello world"].try_into().unwrap(), vec!["pretty normal testss"].try_into().unwrap(), vec!["shorts"].try_into().unwrap(), ]);
190 let component = Aligned::new(
191 Echo(original),
192 HorizontalAlignmentKind::Center,
193 VerticalAlignmentKind::Top,
194 );
195 let dimensions = Dimensions::new(20, 20);
196 let actual = component.draw(dimensions, DrawMode::Normal).unwrap();
197 let expected = Lines(vec![
198 vec![" ".repeat(4).as_ref(), "hello world", &" ".repeat(5)]
199 .try_into()
200 .unwrap(),
201 vec!["pretty normal testss"].try_into().unwrap(),
202 vec![" ".repeat(7).as_ref(), "shorts", &" ".repeat(7)]
203 .try_into()
204 .unwrap(),
205 ]);
206
207 assert_eq!(actual, expected);
208 }
209
210 #[test]
211 fn test_align_right() {
212 let original = Lines(vec![
213 vec!["hello world"].try_into().unwrap(), vec!["pretty normal testsss"].try_into().unwrap(), vec!["shorts"].try_into().unwrap(), ]);
217 let component = Aligned::new(
218 Echo(original),
219 HorizontalAlignmentKind::Right,
220 VerticalAlignmentKind::Top,
221 );
222 let dimensions = Dimensions::new(20, 20);
223 let actual = component.draw(dimensions, DrawMode::Normal).unwrap();
224 let expected = Lines(vec![
225 vec![" ".repeat(9).as_ref(), "hello world"]
226 .try_into()
227 .unwrap(),
228 vec!["pretty normal testss"].try_into().unwrap(),
229 vec![" ".repeat(14).as_ref(), "shorts"].try_into().unwrap(),
230 ]);
231
232 assert_eq!(actual, expected);
233 }
234
235 #[test]
236 fn test_align_top() {
237 let original = Lines(vec![
238 vec!["hello world"].try_into().unwrap(), vec!["pretty normal testsss"].try_into().unwrap(), vec!["shorts"].try_into().unwrap(), ]);
242 let component = Aligned::new(
243 Echo(original),
244 HorizontalAlignmentKind::Left(false),
245 VerticalAlignmentKind::Top,
246 );
247 let dimensions = Dimensions::new(20, 20);
248 let actual = component.draw(dimensions, DrawMode::Normal).unwrap();
249 let expected = Lines(vec![
250 vec!["hello world"].try_into().unwrap(),
251 vec!["pretty normal testss"].try_into().unwrap(),
252 vec!["shorts"].try_into().unwrap(),
253 ]);
254
255 assert_eq!(actual, expected);
256 }
257
258 #[test]
259 fn test_align_row_center() {
260 let original = Lines(vec![
261 vec!["hello world"].try_into().unwrap(), vec!["pretty normal testsss"].try_into().unwrap(), vec!["shorts"].try_into().unwrap(), ]);
265 let component = Aligned::new(
266 Echo(original),
267 HorizontalAlignmentKind::Left(false),
268 VerticalAlignmentKind::Center,
269 );
270 let dimensions = Dimensions::new(20, 10);
271 let actual = component.draw(dimensions, DrawMode::Normal).unwrap();
272 let expected = Lines(vec![
273 Line::default(),
274 Line::default(),
275 Line::default(),
276 vec!["hello world"].try_into().unwrap(),
277 vec!["pretty normal testss"].try_into().unwrap(),
278 vec!["shorts"].try_into().unwrap(),
279 Line::default(),
280 Line::default(),
281 Line::default(),
282 Line::default(),
283 ]);
284
285 assert_eq!(actual, expected);
286 }
287
288 #[test]
289 fn test_align_bottom() {
290 let original = Lines(vec![
291 vec!["hello world"].try_into().unwrap(), vec!["pretty normal testsss"].try_into().unwrap(), vec!["shorts"].try_into().unwrap(), ]);
295 let component = Aligned::new(
296 Echo(original),
297 HorizontalAlignmentKind::Left(false),
298 VerticalAlignmentKind::Bottom,
299 );
300 let dimensions = Dimensions::new(20, 10);
301 let actual = component.draw(dimensions, DrawMode::Normal).unwrap();
302 let expected = Lines(vec![
303 Line::default(),
304 Line::default(),
305 Line::default(),
306 Line::default(),
307 Line::default(),
308 Line::default(),
309 Line::default(),
310 vec!["hello world"].try_into().unwrap(),
311 vec!["pretty normal testss"].try_into().unwrap(),
312 vec!["shorts"].try_into().unwrap(),
313 ]);
314
315 assert_eq!(actual, expected);
316 }
317}