1use crate::ui::prelude::*;
2#[cfg(feature = "serde")]
3use serde::{Deserialize, Serialize};
4
5#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
6#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
7pub enum ColumnGravity {
8 Left,
9 Center,
10 Right,
11}
12
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16#[derive(Debug, Clone, Eq, PartialEq)]
17pub struct ColumnLayout {
18 pub padding: usize,
19 pub spacing: usize,
20 pub bounds: Rect,
21 pub gravity: ColumnGravity,
22}
23
24impl ColumnLayout {
25 pub fn new(padding: usize, spacing: usize, bounds: Rect, gravity: ColumnGravity) -> Self {
38 Self {
39 padding,
40 spacing,
41 bounds,
42 gravity,
43 }
44 }
45
46 pub fn new_from_topleft(topleft: Coord) -> Self {
47 Self::new(
48 0,
49 0,
50 Rect::new_with_size(topleft, 1000000, 100000),
51 ColumnGravity::Left,
52 )
53 }
54
55 pub fn new_bounded(bounds: Rect) -> Self {
56 Self::new(0, 0, bounds, ColumnGravity::Left)
57 }
58}
59
60impl ColumnLayout {
61 pub fn layout(&self, views: &mut [&mut dyn PixelView]) {
63 let mut y = self.padding;
64 for view in views {
65 let x = match self.gravity {
66 ColumnGravity::Left => self.padding,
67 ColumnGravity::Center => {
68 (self.bounds.width() / 2).saturating_sub(view.bounds().width() / 2)
69 }
70 ColumnGravity::Right => self
71 .bounds
72 .width()
73 .saturating_sub(view.bounds().width())
74 .saturating_sub(self.padding),
75 };
76 view.set_position(self.bounds.top_left() + (x, y));
77 y += view.bounds().height();
78 y += self.spacing;
79 }
80 }
81}
82
83#[cfg(test)]
84mod test {
85 use crate::ui::prelude::*;
86
87 #[test]
88 fn column_defaults() {
89 let style = UiStyle::default();
90 let mut view1 = Button::new((0, 0), "Test", None, &style.button);
91 let mut view2 = Button::new((0, 0), "Another", None, &style.button);
92 let layout = ColumnLayout {
93 padding: 0,
94 spacing: 0,
95 bounds: Rect::new((0, 0), (100, 100)),
96 gravity: ColumnGravity::Left,
97 };
98 layout.layout(&mut [&mut view1, &mut view2]);
99
100 assert_eq!(view1.bounds().top_left(), coord!(0, 0));
101 assert_eq!(view2.bounds().top_left(), coord!(0, 16));
102 }
103
104 #[test]
105 fn column_defaults_with_padding() {
106 let style = UiStyle::default();
107 let mut view1 = Button::new((0, 0), "Test", None, &style.button);
108 let mut view2 = Button::new((0, 0), "Another", None, &style.button);
109 let layout = ColumnLayout {
110 padding: 10,
111 spacing: 0,
112 bounds: Rect::new((0, 0), (100, 100)),
113 gravity: ColumnGravity::Left,
114 };
115 layout.layout(&mut [&mut view1, &mut view2]);
116
117 assert_eq!(view1.bounds().top_left(), coord!(10, 10));
118 assert_eq!(view2.bounds().top_left(), coord!(10, 26));
119 }
120
121 #[test]
122 fn column_defaults_with_spacing() {
123 let style = UiStyle::default();
124 let mut view1 = Button::new((0, 0), "Test", None, &style.button);
125 let mut view2 = Button::new((0, 0), "Another", None, &style.button);
126 let layout = ColumnLayout {
127 padding: 0,
128 spacing: 8,
129 bounds: Rect::new((0, 0), (100, 100)),
130 gravity: ColumnGravity::Left,
131 };
132 layout.layout(&mut [&mut view1, &mut view2]);
133
134 assert_eq!(view1.bounds().top_left(), coord!(0, 0));
135 assert_eq!(view2.bounds().top_left(), coord!(0, 24));
136 }
137
138 #[test]
139 fn column_defaults_with_spacing_and_padding() {
140 let style = UiStyle::default();
141 let mut view1 = Button::new((0, 0), "Test", None, &style.button);
142 let mut view2 = Button::new((0, 0), "Another", None, &style.button);
143 let layout = ColumnLayout {
144 padding: 10,
145 spacing: 8,
146 bounds: Rect::new((0, 0), (100, 100)),
147 gravity: ColumnGravity::Left,
148 };
149 layout.layout(&mut [&mut view1, &mut view2]);
150
151 assert_eq!(view1.bounds().top_left(), coord!(10, 10));
152 assert_eq!(view2.bounds().top_left(), coord!(10, 34));
153 }
154
155 #[test]
156 fn column_defaults_with_gravity_right() {
157 let style = UiStyle::default();
158 let mut view1 = Button::new((0, 0), "Test", None, &style.button);
159 let mut view2 = Button::new((0, 0), "Another", None, &style.button);
160 let layout = ColumnLayout {
161 padding: 0,
162 spacing: 0,
163 bounds: Rect::new((0, 0), (100, 100)),
164 gravity: ColumnGravity::Right,
165 };
166 layout.layout(&mut [&mut view1, &mut view2]);
167
168 assert_eq!(view1.bounds().top_left(), coord!(67, 0));
169 assert_eq!(view2.bounds().top_left(), coord!(42, 16));
170 }
171
172 #[test]
173 fn column_defaults_with_gravity_right_and_padding() {
174 let style = UiStyle::default();
175 let mut view1 = Button::new((0, 0), "Test", None, &style.button);
176 let mut view2 = Button::new((0, 0), "Another", None, &style.button);
177 let layout = ColumnLayout {
178 padding: 20,
179 spacing: 0,
180 bounds: Rect::new((0, 0), (100, 100)),
181 gravity: ColumnGravity::Right,
182 };
183 layout.layout(&mut [&mut view1, &mut view2]);
184
185 assert_eq!(view1.bounds().top_left(), coord!(47, 20));
186 assert_eq!(view2.bounds().top_left(), coord!(22, 36));
187 }
188
189 #[test]
190 fn column_defaults_with_gravity_center() {
191 let style = UiStyle::default();
192 let mut view1 = Button::new((0, 0), "Test", None, &style.button);
193 let mut view2 = Button::new((0, 0), "Another", None, &style.button);
194 let mut layout = ColumnLayout {
195 padding: 0,
196 spacing: 0,
197 bounds: Rect::new((0, 0), (100, 100)),
198 gravity: ColumnGravity::Center,
199 };
200 layout.layout(&mut [&mut view1, &mut view2]);
201
202 assert_eq!(view1.bounds().top_left(), coord!(34, 0));
203 assert_eq!(view2.bounds().top_left(), coord!(21, 16));
204
205 layout.padding = 20;
206 layout.layout(&mut [&mut view1, &mut view2]);
207
208 assert_eq!(view1.bounds().top_left(), coord!(34, 20));
209 assert_eq!(view2.bounds().top_left(), coord!(21, 36));
210 }
211}