1use crate::console::RenderContext;
4use crate::renderable::{BoxedRenderable, Renderable, Segment};
5use crate::text::{Alignment, Span};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum VerticalAlignment {
10 #[default]
12 Top,
13 Middle,
15 Bottom,
17}
18
19pub struct Align {
21 child: BoxedRenderable,
22 align: Alignment,
23 vertical: VerticalAlignment,
24 height: Option<usize>,
25 pad: bool,
26}
27
28impl Align {
29 pub fn left(child: impl Renderable + Send + Sync + 'static) -> Self {
31 Align {
32 child: Box::new(child),
33 align: Alignment::Left,
34 vertical: VerticalAlignment::Top,
35 height: None,
36 pad: true,
37 }
38 }
39
40 pub fn center(child: impl Renderable + Send + Sync + 'static) -> Self {
42 Align {
43 child: Box::new(child),
44 align: Alignment::Center,
45 vertical: VerticalAlignment::Top,
46 height: None,
47 pad: true,
48 }
49 }
50
51 pub fn right(child: impl Renderable + Send + Sync + 'static) -> Self {
53 Align {
54 child: Box::new(child),
55 align: Alignment::Right,
56 vertical: VerticalAlignment::Top,
57 height: None,
58 pad: true,
59 }
60 }
61
62 pub fn vertical(mut self, vertical: VerticalAlignment) -> Self {
64 self.vertical = vertical;
65 self
66 }
67
68 pub fn height(mut self, height: usize) -> Self {
70 self.height = Some(height);
71 self
72 }
73}
74
75impl Renderable for Align {
76 fn render(&self, context: &RenderContext) -> Vec<Segment> {
77 let segments = self.child.render(context);
78 let width = context.width;
79 let mut aligned_segments = Vec::with_capacity(segments.len());
80
81 for segment in segments {
82 let mut line = segment.spans.clone();
83 let line_width: usize = line.iter().map(|s| s.width()).sum();
84
85 if line_width < width {
86 let padding = width - line_width;
87 match self.align {
88 Alignment::Left => {
89 if self.pad {
90 line.push(Span::raw(" ".repeat(padding)));
91 }
92 }
93 Alignment::Right => {
94 let mut new_line = vec![Span::raw(" ".repeat(padding))];
95 new_line.extend(line);
96 line = new_line;
97 }
98 Alignment::Center => {
99 let left_pad = padding / 2;
100 let right_pad = padding - left_pad;
101 let mut new_line = vec![Span::raw(" ".repeat(left_pad))];
102 new_line.extend(line);
103 if self.pad {
104 new_line.push(Span::raw(" ".repeat(right_pad)));
105 }
106 line = new_line;
107 }
108 }
109 }
110
111 if segment.newline {
113 aligned_segments.push(Segment::line(line));
114 } else {
115 aligned_segments.push(Segment::new(line));
116 }
117 }
118
119 if let Some(target_height) = self.height {
122 let current_height = aligned_segments.len();
123 if current_height < target_height {
124 let diff = target_height - current_height;
125 match self.vertical {
126 VerticalAlignment::Top => {
127 for _ in 0..diff {
129 aligned_segments
130 .push(Segment::line(vec![Span::raw(" ".repeat(width))]));
131 }
132 }
133 VerticalAlignment::Bottom => {
134 let mut new_segments = Vec::with_capacity(target_height);
135 for _ in 0..diff {
136 new_segments.push(Segment::line(vec![Span::raw(" ".repeat(width))]));
137 }
138 new_segments.extend(aligned_segments);
139 aligned_segments = new_segments;
140 }
141 VerticalAlignment::Middle => {
142 let top_pad = diff / 2;
143 let bottom_pad = diff - top_pad;
144 let mut new_segments = Vec::with_capacity(target_height);
145 for _ in 0..top_pad {
146 new_segments.push(Segment::line(vec![Span::raw(" ".repeat(width))]));
147 }
148 new_segments.extend(aligned_segments);
149 for _ in 0..bottom_pad {
150 new_segments.push(Segment::line(vec![Span::raw(" ".repeat(width))]));
151 }
152 aligned_segments = new_segments;
153 }
154 }
155 }
156 }
157
158 aligned_segments
159 }
160}