Skip to main content

anathema_default_widgets/
alignment.rs

1use std::ops::ControlFlow;
2
3use anathema_geometry::{Pos, Size};
4use anathema_value_resolver::AttributeStorage;
5use anathema_widgets::error::Result;
6use anathema_widgets::layout::{Constraints, LayoutCtx, PositionCtx};
7use anathema_widgets::{LayoutForEach, PositionChildren, Widget, WidgetId};
8
9use crate::layout::alignment::{ALIGNMENT, Alignment};
10
11#[derive(Default)]
12pub struct Align;
13
14impl Widget for Align {
15    fn layout<'bp>(
16        &mut self,
17        mut children: LayoutForEach<'_, 'bp>,
18        constraints: Constraints,
19        _: WidgetId,
20        ctx: &mut LayoutCtx<'_, 'bp>,
21    ) -> Result<Size> {
22        _ = children.each(ctx, |ctx, widget, children| {
23            _ = widget.layout(children, constraints, ctx);
24            Ok(ControlFlow::Break(()))
25        })?;
26
27        Ok(constraints.max_size())
28    }
29
30    fn position<'bp>(
31        &mut self,
32        mut children: PositionChildren<'_, 'bp>,
33        id: WidgetId,
34        attribute_storage: &AttributeStorage<'bp>,
35        ctx: PositionCtx,
36    ) {
37        let attributes = attribute_storage.get(id);
38        let alignment = attributes.get_as::<Alignment>(ALIGNMENT).unwrap_or_default();
39
40        _ = children.each(|child, children| {
41            let width = ctx.inner_size.width as i32;
42            let height = ctx.inner_size.height as i32;
43            let child_width = child.size().width as i32;
44            let child_height = child.size().height as i32;
45
46            let child_offset = match alignment {
47                Alignment::TopLeft => Pos::ZERO,
48                Alignment::Top => Pos::new(width / 2 - child_width / 2, 0),
49                Alignment::TopRight => Pos::new(width - child_width, 0),
50                Alignment::Right => Pos::new(width - child_width, height / 2 - child_height / 2),
51                Alignment::BottomRight => Pos::new(width - child_width, height - child_height),
52                Alignment::Bottom => Pos::new(width / 2 - child_width / 2, height - child_height),
53                Alignment::BottomLeft => Pos::new(0, height - child_height),
54                Alignment::Left => Pos::new(0, height / 2 - child_height / 2),
55                Alignment::Centre => Pos::new(width / 2 - child_width / 2, height / 2 - child_height / 2),
56            };
57
58            child.position(children, ctx.pos + child_offset, attribute_storage, ctx.viewport);
59            ControlFlow::Break(())
60        });
61    }
62}
63
64#[cfg(test)]
65mod test {
66    use crate::testing::TestRunner;
67
68    #[test]
69    fn top_left() {
70        let tpl = "
71            align [alignment: 'top_left']
72                text 'x'
73        ";
74
75        let expected = "
76            ╔═══╗
77            ║x  ║
78            ║   ║
79            ║   ║
80            ╚═══╝
81        ";
82
83        TestRunner::new(tpl, (3, 3)).instance().render_assert(expected);
84    }
85
86    #[test]
87    fn top() {
88        let tpl = "
89            align [alignment: 'top']
90                text 'x'
91        ";
92
93        let expected = "
94            ╔═══╗
95            ║ x ║
96            ║   ║
97            ║   ║
98            ╚═══╝
99        ";
100
101        TestRunner::new(tpl, (3, 3)).instance().render_assert(expected);
102    }
103
104    #[test]
105    fn top_right() {
106        let tpl = "
107            align [alignment: 'top_right']
108                text 'x'
109        ";
110
111        let expected = "
112            ╔═══╗
113            ║  x║
114            ║   ║
115            ║   ║
116            ╚═══╝
117        ";
118
119        TestRunner::new(tpl, (3, 3)).instance().render_assert(expected);
120    }
121
122    #[test]
123    fn right() {
124        let tpl = "
125            align [alignment: 'right']
126                text 'x'
127        ";
128
129        let expected = "
130            ╔═══╗
131            ║   ║
132            ║  x║
133            ║   ║
134            ╚═══╝
135        ";
136
137        TestRunner::new(tpl, (3, 3)).instance().render_assert(expected);
138    }
139
140    #[test]
141    fn bottom_right() {
142        let tpl = "
143            align [alignment: 'bottom_right']
144                text 'x'
145        ";
146
147        let expected = "
148            ╔═══╗
149            ║   ║
150            ║   ║
151            ║  x║
152            ╚═══╝
153        ";
154
155        TestRunner::new(tpl, (3, 3)).instance().render_assert(expected);
156    }
157
158    #[test]
159    fn bottom() {
160        let tpl = "
161            align [alignment: 'bottom']
162                text 'x'
163        ";
164
165        let expected = "
166            ╔═══╗
167            ║   ║
168            ║   ║
169            ║ x ║
170            ╚═══╝
171        ";
172
173        TestRunner::new(tpl, (3, 3)).instance().render_assert(expected);
174    }
175
176    #[test]
177    fn bottom_left() {
178        let tpl = "
179            align [alignment: 'bottom_left']
180                text 'x'
181        ";
182
183        let expected = "
184            ╔═══╗
185            ║   ║
186            ║   ║
187            ║x  ║
188            ╚═══╝
189        ";
190
191        TestRunner::new(tpl, (3, 3)).instance().render_assert(expected);
192    }
193
194    #[test]
195    fn left() {
196        let tpl = "
197            align [alignment: 'left']
198                text 'x'
199        ";
200
201        let expected = "
202            ╔═══╗
203            ║   ║
204            ║x  ║
205            ║   ║
206            ╚═══╝
207        ";
208
209        TestRunner::new(tpl, (3, 3)).instance().render_assert(expected);
210    }
211
212    #[test]
213    fn centre() {
214        let tpl = "
215            align [alignment: 'centre']
216                text 'x'
217        ";
218
219        let expected = "
220            ╔═══╗
221            ║   ║
222            ║ x ║
223            ║   ║
224            ╚═══╝
225        ";
226
227        TestRunner::new(tpl, (3, 3)).instance().render_assert(expected);
228    }
229}