1use alloc::{boxed::Box, vec::Vec};
9use rlvgl_core::{
10 event::Event,
11 renderer::Renderer,
12 widget::{Rect, Widget},
13};
14use rlvgl_widgets::container::Container;
15
16pub struct VStack {
21 bounds: Rect,
22 spacing: i32,
23 children: Vec<Box<dyn Widget>>,
24 next_y: i32,
25}
26
27impl VStack {
28 pub fn new(width: i32) -> Self {
30 Self {
31 bounds: Rect {
32 x: 0,
33 y: 0,
34 width,
35 height: 0,
36 },
37 spacing: 0,
38 children: Vec::new(),
39 next_y: 0,
40 }
41 }
42
43 pub fn spacing(mut self, spacing: i32) -> Self {
45 self.spacing = spacing;
46 self
47 }
48
49 pub fn child<W, F>(mut self, height: i32, builder: F) -> Self
51 where
52 W: Widget + 'static,
53 F: FnOnce(Rect) -> W,
54 {
55 let rect = Rect {
56 x: 0,
57 y: self.next_y,
58 width: self.bounds.width,
59 height,
60 };
61 self.next_y += height + self.spacing;
62 self.bounds.height = self.next_y - self.spacing;
63 self.children.push(Box::new(builder(rect)));
64 self
65 }
66}
67
68impl Widget for VStack {
69 fn bounds(&self) -> Rect {
70 self.bounds
71 }
72
73 fn draw(&self, renderer: &mut dyn Renderer) {
74 for child in &self.children {
75 child.draw(renderer);
76 }
77 }
78
79 fn handle_event(&mut self, event: &Event) -> bool {
80 for child in &mut self.children {
81 if child.handle_event(event) {
82 return true;
83 }
84 }
85 false
86 }
87}
88
89pub struct HStack {
94 bounds: Rect,
95 spacing: i32,
96 children: Vec<Box<dyn Widget>>,
97 next_x: i32,
98}
99
100impl HStack {
101 pub fn new(height: i32) -> Self {
103 Self {
104 bounds: Rect {
105 x: 0,
106 y: 0,
107 width: 0,
108 height,
109 },
110 spacing: 0,
111 children: Vec::new(),
112 next_x: 0,
113 }
114 }
115
116 pub fn spacing(mut self, spacing: i32) -> Self {
118 self.spacing = spacing;
119 self
120 }
121
122 pub fn child<W, F>(mut self, width: i32, builder: F) -> Self
124 where
125 W: Widget + 'static,
126 F: FnOnce(Rect) -> W,
127 {
128 let rect = Rect {
129 x: self.next_x,
130 y: 0,
131 width,
132 height: self.bounds.height,
133 };
134 self.next_x += width + self.spacing;
135 self.bounds.width = self.next_x - self.spacing;
136 self.children.push(Box::new(builder(rect)));
137 self
138 }
139}
140
141impl Widget for HStack {
142 fn bounds(&self) -> Rect {
143 self.bounds
144 }
145
146 fn draw(&self, renderer: &mut dyn Renderer) {
147 for child in &self.children {
148 child.draw(renderer);
149 }
150 }
151
152 fn handle_event(&mut self, event: &Event) -> bool {
153 for child in &mut self.children {
154 if child.handle_event(event) {
155 return true;
156 }
157 }
158 false
159 }
160}
161
162pub struct Grid {
164 bounds: Rect,
165 cols: i32,
166 cell_w: i32,
167 cell_h: i32,
168 spacing: i32,
169 children: Vec<Box<dyn Widget>>,
170 next: i32,
171}
172
173impl Grid {
174 pub fn new(cols: i32, cell_w: i32, cell_h: i32) -> Self {
176 Self {
177 bounds: Rect {
178 x: 0,
179 y: 0,
180 width: 0,
181 height: 0,
182 },
183 cols,
184 cell_w,
185 cell_h,
186 spacing: 0,
187 children: Vec::new(),
188 next: 0,
189 }
190 }
191
192 pub fn spacing(mut self, spacing: i32) -> Self {
194 self.spacing = spacing;
195 self
196 }
197
198 pub fn child<W, F>(mut self, builder: F) -> Self
200 where
201 W: Widget + 'static,
202 F: FnOnce(Rect) -> W,
203 {
204 let col = self.next % self.cols;
205 let row = self.next / self.cols;
206 let x = col * (self.cell_w + self.spacing);
207 let y = row * (self.cell_h + self.spacing);
208 let rect = Rect {
209 x,
210 y,
211 width: self.cell_w,
212 height: self.cell_h,
213 };
214 self.children.push(Box::new(builder(rect)));
215 self.next += 1;
216 let w = x + self.cell_w;
217 let h = y + self.cell_h;
218 if w > self.bounds.width {
219 self.bounds.width = w;
220 }
221 if h > self.bounds.height {
222 self.bounds.height = h;
223 }
224 self
225 }
226}
227
228impl Widget for Grid {
229 fn bounds(&self) -> Rect {
230 self.bounds
231 }
232
233 fn draw(&self, renderer: &mut dyn Renderer) {
234 for child in &self.children {
235 child.draw(renderer);
236 }
237 }
238
239 fn handle_event(&mut self, event: &Event) -> bool {
240 for child in &mut self.children {
241 if child.handle_event(event) {
242 return true;
243 }
244 }
245 false
246 }
247}
248
249pub struct BoxLayout {
251 inner: Container,
252}
253
254impl BoxLayout {
255 pub fn new(bounds: Rect) -> Self {
257 Self {
258 inner: Container::new(bounds),
259 }
260 }
261
262 pub fn style_mut(&mut self) -> &mut rlvgl_core::style::Style {
264 &mut self.inner.style
265 }
266}
267
268impl Widget for BoxLayout {
269 fn bounds(&self) -> Rect {
270 self.inner.bounds()
271 }
272
273 fn draw(&self, renderer: &mut dyn Renderer) {
274 self.inner.draw(renderer);
275 }
276
277 fn handle_event(&mut self, event: &Event) -> bool {
278 self.inner.handle_event(event)
279 }
280}
281
282pub struct GridCalc {
296 pub x: i32,
298 pub y: i32,
300 pub cols: usize,
302 pub col_w: i32,
304 pub row_h: i32,
306 pub col_gap: i32,
308 pub row_gap: i32,
310}
311
312impl GridCalc {
313 pub const fn new(x: i32, y: i32, cols: usize, col_w: i32, row_h: i32) -> Self {
315 Self {
316 x,
317 y,
318 cols,
319 col_w,
320 row_h,
321 col_gap: 0,
322 row_gap: 0,
323 }
324 }
325
326 pub const fn gap(mut self, col_gap: i32, row_gap: i32) -> Self {
328 self.col_gap = col_gap;
329 self.row_gap = row_gap;
330 self
331 }
332
333 pub const fn cell(&self, row: usize, col: usize) -> Rect {
335 Rect {
336 x: self.x + col as i32 * (self.col_w + self.col_gap),
337 y: self.y + row as i32 * (self.row_h + self.row_gap),
338 width: self.col_w,
339 height: self.row_h,
340 }
341 }
342
343 pub const fn row_span(&self, row: usize) -> Rect {
345 let total_w = if self.cols == 0 {
346 0
347 } else {
348 self.cols as i32 * self.col_w + (self.cols as i32 - 1) * self.col_gap
349 };
350 Rect {
351 x: self.x,
352 y: self.y + row as i32 * (self.row_h + self.row_gap),
353 width: total_w,
354 height: self.row_h,
355 }
356 }
357
358 pub const fn total_width(&self) -> i32 {
360 if self.cols == 0 {
361 0
362 } else {
363 self.cols as i32 * self.col_w + (self.cols as i32 - 1) * self.col_gap
364 }
365 }
366
367 pub const fn total_height(&self, rows: usize) -> i32 {
369 if rows == 0 {
370 0
371 } else {
372 rows as i32 * self.row_h + (rows as i32 - 1) * self.row_gap
373 }
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380 use rlvgl_widgets::label::Label;
381
382 #[test]
383 fn vstack_stacks_vertically() {
384 let stack = VStack::new(20)
385 .spacing(2)
386 .child(10, |r| Label::new("a", r))
387 .child(10, |r| Label::new("b", r));
388 assert_eq!(stack.bounds().height, 22);
389 }
390
391 #[test]
392 fn hstack_stacks_horizontally() {
393 let stack = HStack::new(10)
394 .spacing(1)
395 .child(5, |r| Label::new("a", r))
396 .child(5, |r| Label::new("b", r));
397 assert_eq!(stack.bounds().width, 11);
398 }
399
400 #[test]
401 fn grid_places_cells() {
402 let grid = Grid::new(2, 5, 5)
403 .spacing(1)
404 .child(|r| Label::new("a", r))
405 .child(|r| Label::new("b", r))
406 .child(|r| Label::new("c", r));
407 assert_eq!(grid.bounds().height, 11);
408 assert_eq!(grid.bounds().width, 11);
409 }
410
411 #[test]
412 fn grid_calc_cell() {
413 let g = GridCalc::new(10, 20, 2, 100, 40).gap(4, 2);
414 let r = g.cell(0, 0);
415 assert_eq!(
416 r,
417 Rect {
418 x: 10,
419 y: 20,
420 width: 100,
421 height: 40
422 }
423 );
424 let r = g.cell(1, 1);
425 assert_eq!(
426 r,
427 Rect {
428 x: 114,
429 y: 62,
430 width: 100,
431 height: 40
432 }
433 );
434 }
435
436 #[test]
437 fn grid_calc_row_span() {
438 let g = GridCalc::new(0, 0, 3, 50, 30).gap(10, 5);
439 let r = g.row_span(0);
440 assert_eq!(
441 r,
442 Rect {
443 x: 0,
444 y: 0,
445 width: 170,
446 height: 30
447 }
448 );
449 assert_eq!(g.total_width(), 170);
450 assert_eq!(g.total_height(2), 65);
451 }
452}