anathema_default_widgets/
position.rs1use std::ops::ControlFlow;
2
3use anathema_geometry::{Pos, Size};
4use anathema_value_resolver::{AttributeStorage, ValueKind};
5use anathema_widgets::error::Result;
6use anathema_widgets::layout::{Constraints, LayoutCtx, PositionCtx};
7use anathema_widgets::paint::{PaintCtx, SizePos};
8use anathema_widgets::{LayoutForEach, PaintChildren, PositionChildren, Widget, WidgetId};
9
10use crate::{BOTTOM, LEFT, RIGHT, TOP};
11
12const RELATIVE: &str = "relative";
13const ABSOLUTE: &str = "absolute";
14const PLACEMENT: &str = "placement";
15
16#[derive(Debug, Copy, Clone, PartialEq)]
17pub enum HorzEdge {
18 Left(i32),
19 Right(i32),
20}
21
22#[derive(Debug, Copy, Clone, PartialEq)]
23pub enum VertEdge {
24 Top(i32),
25 Bottom(i32),
26}
27
28#[derive(Debug, Default, Copy, Clone, PartialEq)]
29pub enum Placement {
30 #[default]
32 Relative,
33 Absolute,
35}
36
37impl TryFrom<&ValueKind<'_>> for Placement {
38 type Error = ();
39
40 fn try_from(value: &ValueKind<'_>) -> Result<Self, Self::Error> {
41 match value {
42 ValueKind::Str(wrap) => match wrap.as_ref() {
43 RELATIVE => Ok(Placement::Relative),
44 ABSOLUTE => Ok(Placement::Absolute),
45 _ => Err(()),
46 },
47 _ => Err(()),
48 }
49 }
50}
51
52#[derive(Debug)]
53pub struct Position {
54 horz_edge: HorzEdge,
55 vert_edge: VertEdge,
56 placement: Placement,
57}
58
59impl Default for Position {
60 fn default() -> Self {
61 Self {
62 horz_edge: HorzEdge::Left(0),
63 vert_edge: VertEdge::Top(0),
64 placement: Placement::Relative,
65 }
66 }
67}
68
69impl Widget for Position {
70 fn floats(&self) -> bool {
71 true
72 }
73
74 fn layout<'bp>(
75 &mut self,
76 mut children: LayoutForEach<'_, 'bp>,
77 constraints: Constraints,
78 id: WidgetId,
79 ctx: &mut LayoutCtx<'_, 'bp>,
80 ) -> Result<Size> {
81 let attribs = ctx.attribute_storage.get(id);
82 self.placement = attribs.get_as::<Placement>(PLACEMENT).unwrap_or_default();
83
84 self.horz_edge = match attribs.get_as::<i32>(LEFT) {
85 Some(left) => HorzEdge::Left(left),
86 None => match attribs.get_as::<i32>(RIGHT) {
87 Some(right) => HorzEdge::Right(right),
88 None => HorzEdge::Left(0),
89 },
90 };
91
92 self.vert_edge = match attribs.get_as::<i32>(TOP) {
93 Some(top) => VertEdge::Top(top),
94 None => match attribs.get_as::<i32>(BOTTOM) {
95 Some(bottom) => VertEdge::Bottom(bottom),
96 None => VertEdge::Top(0),
97 },
98 };
99
100 let size = constraints.max_size();
101
102 _ = children.each(ctx, |ctx, child, children| {
103 _ = child.layout(children, ctx.viewport.constraints(), ctx)?;
105 Ok(ControlFlow::Break(()))
106 })?;
107
108 Ok(size)
109 }
110
111 fn position<'bp>(
112 &mut self,
113 mut children: PositionChildren<'_, 'bp>,
114 _: WidgetId,
115 attribute_storage: &AttributeStorage<'bp>,
116 mut ctx: PositionCtx,
117 ) {
118 if let Placement::Absolute = self.placement {
119 ctx.pos = Pos::ZERO;
120 }
121
122 _ = children.each(|child, children| {
123 match self.horz_edge {
124 HorzEdge::Left(left) => ctx.pos.x += left,
125 HorzEdge::Right(right) => {
126 let offset = ctx.pos.x + ctx.inner_size.width as i32 - child.size().width as i32 - right;
127 ctx.pos.x = offset;
128 }
129 }
130
131 match self.vert_edge {
132 VertEdge::Top(top) => ctx.pos.y += top,
133 VertEdge::Bottom(bottom) => {
134 let offset = ctx.pos.y + ctx.inner_size.height as i32 - child.size().height as i32 - bottom;
135 ctx.pos.y = offset;
136 }
137 }
138
139 child.position(children, ctx.pos, attribute_storage, ctx.viewport);
140 ControlFlow::Break(())
141 });
142 }
143
144 fn paint<'bp>(
145 &mut self,
146 mut children: PaintChildren<'_, 'bp>,
147 _id: WidgetId,
148 attribute_storage: &AttributeStorage<'bp>,
149 mut ctx: PaintCtx<'_, SizePos>,
150 ) {
151 _ = children.each(|child, children| {
152 let mut ctx = ctx.to_unsized();
153 ctx.clip = None;
154 child.paint(children, ctx, attribute_storage);
155 ControlFlow::Continue(())
156 });
157 }
158}
159
160#[cfg(test)]
161mod test {
162 use crate::testing::TestRunner;
163
164 #[test]
165 fn position_top_left() {
166 let tpl = "
167 position [top: 0, left: 0]
168 text 'hi'
169 ";
170
171 let expected = "
172 ╔════╗
173 ║hi ║
174 ║ ║
175 ╚════╝
176 ";
177
178 TestRunner::new(tpl, (4, 2)).instance().render_assert(expected);
179 }
180
181 #[test]
182 fn position_top() {
183 let tpl = "
184 position [top: 1]
185 text 'hi'
186 ";
187
188 let expected = "
189 ╔════╗
190 ║ ║
191 ║hi ║
192 ╚════╝
193 ";
194
195 TestRunner::new(tpl, (4, 2)).instance().render_assert(expected);
196 }
197
198 #[test]
199 fn position_top_right() {
200 let tpl = "
201 position [top: 1, right: 0]
202 text 'hi'
203 ";
204
205 let expected = "
206 ╔════╗
207 ║ ║
208 ║ hi║
209 ╚════╝
210 ";
211
212 TestRunner::new(tpl, (4, 2)).instance().render_assert(expected);
213 }
214
215 #[test]
216 fn position_right() {
217 let tpl = "
218 position [right: 0]
219 text 'hi'
220 ";
221
222 let expected = "
223 ╔════╗
224 ║ hi║
225 ║ ║
226 ╚════╝
227 ";
228
229 TestRunner::new(tpl, (4, 2)).instance().render_assert(expected);
230 }
231
232 #[test]
233 fn position_bottom_right() {
234 let tpl = "
235 position [placement: 'relative', bottom: 0, right: 0]
236 text 'hi'
237 ";
238
239 let expected = "
240 ╔════╗
241 ║ ║
242 ║ hi║
243 ╚════╝
244 ";
245
246 TestRunner::new(tpl, (4, 2)).instance().render_assert(expected);
247 }
248
249 #[test]
250 fn position_bottom() {
251 let tpl = "
252 position [bottom: 0]
253 text 'hi'
254 ";
255
256 let expected = "
257 ╔════╗
258 ║ ║
259 ║hi ║
260 ╚════╝
261 ";
262
263 TestRunner::new(tpl, (4, 2)).instance().render_assert(expected);
264 }
265
266 #[test]
267 fn position_bottom_left() {
268 let tpl = "
269 position [bottom: 0, left: 1]
270 text 'hi'
271 ";
272
273 let expected = "
274 ╔════╗
275 ║ ║
276 ║ hi ║
277 ╚════╝
278 ";
279
280 TestRunner::new(tpl, (4, 2)).instance().render_assert(expected);
281 }
282}