anathema_default_widgets/
padding.rs1use std::ops::ControlFlow;
2
3use anathema_geometry::{Pos, Region, Size};
4use anathema_value_resolver::AttributeStorage;
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 PADDING: &str = "padding";
13
14#[derive(Default)]
15struct PaddingValues {
16 top: u16,
17 right: u16,
18 bottom: u16,
19 left: u16,
20}
21
22impl PaddingValues {
23 fn size(&self) -> Size {
24 Size::new(self.left + self.right, self.top + self.bottom)
25 }
26}
27
28#[derive(Default)]
29pub struct Padding(PaddingValues);
30
31impl Widget for Padding {
32 fn layout<'bp>(
33 &mut self,
34 mut children: LayoutForEach<'_, 'bp>,
35 constraints: Constraints,
36 id: WidgetId,
37 ctx: &mut LayoutCtx<'_, 'bp>,
38 ) -> Result<Size> {
39 let attributes = ctx.attribute_storage.get(id);
40 let mut size = Size::ZERO;
41 let padding = attributes.get_as::<u16>(PADDING).unwrap_or(0);
42
43 self.0.top = attributes
44 .get_as::<usize>(TOP)
45 .and_then(|v| v.try_into().ok())
46 .unwrap_or(padding);
47 self.0.right = attributes
48 .get_as::<usize>(RIGHT)
49 .and_then(|v| v.try_into().ok())
50 .unwrap_or(padding);
51 self.0.bottom = attributes
52 .get_as::<usize>(BOTTOM)
53 .and_then(|v| v.try_into().ok())
54 .unwrap_or(padding);
55 self.0.left = attributes
56 .get_as::<usize>(LEFT)
57 .and_then(|v| v.try_into().ok())
58 .unwrap_or(padding);
59
60 let padding_size = self.0.size();
61
62 _ = children.each(ctx, |ctx, child, children| {
63 let mut child_constraints = constraints;
64 child_constraints.sub_max_width(padding_size.width);
65 child_constraints.sub_max_height(padding_size.height);
66 let mut child_size = Size::from(child.layout(children, child_constraints, ctx)?);
67 child_size += padding_size;
68 size.width = child_size.width.max(size.width);
69 size.height = child_size.height.max(size.height);
70
71 Ok(ControlFlow::Break(()))
72 })?;
73
74 size.width = constraints.min_width.max(size.width).min(constraints.max_width());
75 size.height = constraints.min_height.max(size.height).min(constraints.max_height());
76
77 Ok(size)
78 }
79
80 fn position<'bp>(
81 &mut self,
82 mut children: PositionChildren<'_, 'bp>,
83 _: WidgetId,
84 attribute_storage: &AttributeStorage<'bp>,
85 mut ctx: PositionCtx,
86 ) {
87 _ = children.each(|child, children| {
88 ctx.pos.y += self.0.top as i32;
89 ctx.pos.x += self.0.left as i32;
90
91 child.position(children, ctx.pos, attribute_storage, ctx.viewport);
92 ControlFlow::Break(())
93 });
94 }
95
96 fn paint<'bp>(
97 &mut self,
98 mut children: PaintChildren<'_, 'bp>,
99 _id: WidgetId,
100 attribute_storage: &AttributeStorage<'bp>,
101 mut ctx: PaintCtx<'_, SizePos>,
102 ) {
103 _ = children.each(|child, children| {
104 let mut ctx = ctx.to_unsized();
105 if let Some(clip) = ctx.clip.as_mut() {
106 clip.from.x += self.0.left as i32;
107 clip.from.y += self.0.top as i32;
108 clip.to.x -= self.0.right as i32;
109 clip.to.y -= self.0.bottom as i32;
110 }
111 child.paint(children, ctx, attribute_storage);
112 ControlFlow::Break(())
113 });
114 }
115
116 fn inner_bounds(&self, mut pos: Pos, mut size: Size) -> Region {
117 pos.x += self.0.left as i32;
118 pos.y += self.0.top as i32;
119 size.width = size.width.saturating_sub(self.0.right);
120 size.height = size.height.saturating_sub(self.0.bottom);
121 Region::from((pos, size))
122 }
123}
124
125#[cfg(test)]
126mod test {
127
128 use crate::testing::TestRunner;
129
130 #[test]
131 fn padding_all() {
132 let tpl = "
133 padding [padding: 1]
134 text 'a'
135 ";
136
137 let expected = "
138 ╔═══╗
139 ║ ║
140 ║ a ║
141 ║ ║
142 ╚═══╝
143 ";
144
145 TestRunner::new(tpl, (3, 3)).instance().render_assert(expected);
146 }
147
148 #[test]
149 fn padding_top_inclusive() {
150 let tpl = "
151 padding [padding: 1, top: 2]
152 text 'a'
153 ";
154
155 let expected = "
156 ╔════╗
157 ║ ║
158 ║ ║
159 ║ a ║
160 ║ ║
161 ╚════╝
162 ";
163
164 TestRunner::new(tpl, (4, 4)).instance().render_assert(expected);
165 }
166
167 #[test]
168 fn padding_top() {
169 let tpl = "
170 padding [top: 2]
171 text 'a'
172 ";
173
174 let expected = "
175 ╔════╗
176 ║ ║
177 ║ ║
178 ║a ║
179 ╚════╝
180 ";
181
182 TestRunner::new(tpl, (4, 3)).instance().render_assert(expected);
183 }
184
185 #[test]
186 fn padding_right_inclusive() {
187 let tpl = "
188 padding [padding: 1, right: 2]
189 text 'a'
190 ";
191
192 let expected = "
193 ╔════╗
194 ║ ║
195 ║ a ║
196 ║ ║
197 ╚════╝
198 ";
199
200 TestRunner::new(tpl, (4, 3)).instance().render_assert(expected);
201 }
202
203 #[test]
204 fn padding_right() {
205 let tpl = "
206 padding [right: 2]
207 text 'a'
208 ";
209
210 let expected = "
211 ╔════╗
212 ║a ║
213 ║ ║
214 ║ ║
215 ╚════╝
216 ";
217
218 TestRunner::new(tpl, (4, 3)).instance().render_assert(expected);
219 }
220
221 #[test]
222 fn padding_bottom_inclusive() {
223 let tpl = "
224 padding [padding: 1, bottom: 2]
225 text 'a'
226 ";
227
228 let expected = "
229 ╔════╗
230 ║ ║
231 ║ a ║
232 ║ ║
233 ║ ║
234 ╚════╝
235 ";
236
237 TestRunner::new(tpl, (4, 4)).instance().render_assert(expected);
238 }
239
240 #[test]
241 fn padding_bottom() {
242 let tpl = "
243 padding [bottom: 2]
244 text 'a'
245 ";
246
247 let expected = "
248 ╔════╗
249 ║a ║
250 ║ ║
251 ║ ║
252 ╚════╝
253 ";
254
255 TestRunner::new(tpl, (4, 3)).instance().render_assert(expected);
256 }
257
258 #[test]
259 fn padding_left_inclusive() {
260 let tpl = "
261 padding [padding: 1, left: 2]
262 text 'a'
263 ";
264
265 let expected = "
266 ╔════╗
267 ║ ║
268 ║ a ║
269 ║ ║
270 ╚════╝
271 ";
272
273 TestRunner::new(tpl, (4, 3)).instance().render_assert(expected);
274 }
275
276 #[test]
277 fn padding_left() {
278 let tpl = "
279 padding [left: 2]
280 text 'a'
281 ";
282
283 let expected = "
284 ╔════╗
285 ║ a ║
286 ║ ║
287 ║ ║
288 ╚════╝
289 ";
290
291 TestRunner::new(tpl, (4, 3)).instance().render_assert(expected);
292 }
293}