pixels_graphics_lib/ui/layout/
row.rs

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/// Position a collection of views into a row
14/// This doesn't act as a container or parent and only moves the views and isn't needed after it's been used
15#[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    ///# Arguments
26    ///* `padding` - Pixel distance to move all views from the topleft or bottomright (depending on gravity)
27    ///* `spacing` - Pixel distance to leave between views
28    ///* `bounds` - Depends on gravity, if it's
29    ///
30    /// | Gravity | Effects |
31    /// |---------|---------|
32    /// |Top| bounds.top_left() is the starting point, bottom and right are ignored|
33    /// |Bottom| bounds.bottom_right() is the starting point, top and left are ignored|
34    /// |Center| bounds.top_left() is the starting point, views will be positioned between top and bottom|
35    ///
36    ///* `gravity` - Effects how views are positioned, see above
37    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    /// Reposition views in a row
62    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}