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