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 const fn rect(x: i32, y: i32, width: i32, height: i32) -> Rect {
26 Rect {
27 x,
28 y,
29 width,
30 height,
31 }
32}
33
34pub const fn origin_rect(width: i32, height: i32) -> Rect {
36 rect(0, 0, width, height)
37}
38
39pub trait RectProps: Sized {
41 fn at(self, x: i32, y: i32) -> Rect;
43
44 fn x(self, x: i32) -> Rect;
46
47 fn y(self, y: i32) -> Rect;
49
50 fn size(self, width: i32, height: i32) -> Rect;
52
53 fn width(self, width: i32) -> Rect;
55
56 fn height(self, height: i32) -> Rect;
58
59 fn translate(self, dx: i32, dy: i32) -> Rect;
61
62 fn inset(self, amount: i32) -> Rect;
64
65 fn inset_xy(self, x: i32, y: i32) -> Rect;
67
68 fn min_width(self, width: i32) -> Rect;
70
71 fn min_height(self, height: i32) -> Rect;
73
74 fn min_size(self, width: i32, height: i32) -> Rect;
76}
77
78impl RectProps for Rect {
79 fn at(mut self, x: i32, y: i32) -> Rect {
80 self.x = x;
81 self.y = y;
82 self
83 }
84
85 fn x(mut self, x: i32) -> Rect {
86 self.x = x;
87 self
88 }
89
90 fn y(mut self, y: i32) -> Rect {
91 self.y = y;
92 self
93 }
94
95 fn size(mut self, width: i32, height: i32) -> Rect {
96 self.width = width;
97 self.height = height;
98 self
99 }
100
101 fn width(mut self, width: i32) -> Rect {
102 self.width = width;
103 self
104 }
105
106 fn height(mut self, height: i32) -> Rect {
107 self.height = height;
108 self
109 }
110
111 fn translate(mut self, dx: i32, dy: i32) -> Rect {
112 self.x += dx;
113 self.y += dy;
114 self
115 }
116
117 fn inset(self, amount: i32) -> Rect {
118 self.inset_xy(amount, amount)
119 }
120
121 fn inset_xy(mut self, x: i32, y: i32) -> Rect {
122 self.x += x;
123 self.y += y;
124 self.width = (self.width - x * 2).max(0);
125 self.height = (self.height - y * 2).max(0);
126 self
127 }
128
129 fn min_width(mut self, width: i32) -> Rect {
130 self.width = self.width.max(width);
131 self
132 }
133
134 fn min_height(mut self, height: i32) -> Rect {
135 self.height = self.height.max(height);
136 self
137 }
138
139 fn min_size(self, width: i32, height: i32) -> Rect {
140 self.min_width(width).min_height(height)
141 }
142}
143
144pub struct VStack {
149 bounds: Rect,
150 spacing: i32,
151 children: Vec<Box<dyn Widget>>,
152 next_y: i32,
153}
154
155impl VStack {
156 pub fn new(width: i32) -> Self {
158 Self {
159 bounds: Rect {
160 x: 0,
161 y: 0,
162 width,
163 height: 0,
164 },
165 spacing: 0,
166 children: Vec::new(),
167 next_y: 0,
168 }
169 }
170
171 pub fn spacing(mut self, spacing: i32) -> Self {
173 self.spacing = spacing;
174 self
175 }
176
177 pub fn gap(self, gap: i32) -> Self {
179 self.spacing(gap)
180 }
181
182 pub fn child<W, F>(mut self, height: i32, builder: F) -> Self
184 where
185 W: Widget + 'static,
186 F: FnOnce(Rect) -> W,
187 {
188 let rect = Rect {
189 x: 0,
190 y: self.next_y,
191 width: self.bounds.width,
192 height,
193 };
194 self.next_y += height + self.spacing;
195 self.bounds.height = self.next_y - self.spacing;
196 self.children.push(Box::new(builder(rect)));
197 self
198 }
199}
200
201impl Widget for VStack {
202 fn bounds(&self) -> Rect {
203 self.bounds
204 }
205
206 fn draw(&self, renderer: &mut dyn Renderer) {
207 for child in &self.children {
208 child.draw(renderer);
209 }
210 }
211
212 fn handle_event(&mut self, event: &Event) -> bool {
213 for child in &mut self.children {
214 if child.handle_event(event) {
215 return true;
216 }
217 }
218 false
219 }
220}
221
222pub struct HStack {
227 bounds: Rect,
228 spacing: i32,
229 children: Vec<Box<dyn Widget>>,
230 next_x: i32,
231}
232
233impl HStack {
234 pub fn new(height: i32) -> Self {
236 Self {
237 bounds: Rect {
238 x: 0,
239 y: 0,
240 width: 0,
241 height,
242 },
243 spacing: 0,
244 children: Vec::new(),
245 next_x: 0,
246 }
247 }
248
249 pub fn spacing(mut self, spacing: i32) -> Self {
251 self.spacing = spacing;
252 self
253 }
254
255 pub fn gap(self, gap: i32) -> Self {
257 self.spacing(gap)
258 }
259
260 pub fn child<W, F>(mut self, width: i32, builder: F) -> Self
262 where
263 W: Widget + 'static,
264 F: FnOnce(Rect) -> W,
265 {
266 let rect = Rect {
267 x: self.next_x,
268 y: 0,
269 width,
270 height: self.bounds.height,
271 };
272 self.next_x += width + self.spacing;
273 self.bounds.width = self.next_x - self.spacing;
274 self.children.push(Box::new(builder(rect)));
275 self
276 }
277}
278
279impl Widget for HStack {
280 fn bounds(&self) -> Rect {
281 self.bounds
282 }
283
284 fn draw(&self, renderer: &mut dyn Renderer) {
285 for child in &self.children {
286 child.draw(renderer);
287 }
288 }
289
290 fn handle_event(&mut self, event: &Event) -> bool {
291 for child in &mut self.children {
292 if child.handle_event(event) {
293 return true;
294 }
295 }
296 false
297 }
298}
299
300pub struct Grid {
302 bounds: Rect,
303 cols: i32,
304 cell_w: i32,
305 cell_h: i32,
306 spacing: i32,
307 children: Vec<Box<dyn Widget>>,
308 next: i32,
309}
310
311impl Grid {
312 pub fn new(cols: i32, cell_w: i32, cell_h: i32) -> Self {
314 Self {
315 bounds: Rect {
316 x: 0,
317 y: 0,
318 width: 0,
319 height: 0,
320 },
321 cols,
322 cell_w,
323 cell_h,
324 spacing: 0,
325 children: Vec::new(),
326 next: 0,
327 }
328 }
329
330 pub fn spacing(mut self, spacing: i32) -> Self {
332 self.spacing = spacing;
333 self
334 }
335
336 pub fn gap(self, gap: i32) -> Self {
338 self.spacing(gap)
339 }
340
341 pub fn child<W, F>(mut self, builder: F) -> Self
343 where
344 W: Widget + 'static,
345 F: FnOnce(Rect) -> W,
346 {
347 let cols = self.cols.max(1);
351 let col = self.next % cols;
352 let row = self.next / cols;
353 let x = col * (self.cell_w + self.spacing);
354 let y = row * (self.cell_h + self.spacing);
355 let rect = Rect {
356 x,
357 y,
358 width: self.cell_w,
359 height: self.cell_h,
360 };
361 self.children.push(Box::new(builder(rect)));
362 self.next += 1;
363 let w = x + self.cell_w;
364 let h = y + self.cell_h;
365 if w > self.bounds.width {
366 self.bounds.width = w;
367 }
368 if h > self.bounds.height {
369 self.bounds.height = h;
370 }
371 self
372 }
373}
374
375impl Widget for Grid {
376 fn bounds(&self) -> Rect {
377 self.bounds
378 }
379
380 fn draw(&self, renderer: &mut dyn Renderer) {
381 for child in &self.children {
382 child.draw(renderer);
383 }
384 }
385
386 fn handle_event(&mut self, event: &Event) -> bool {
387 for child in &mut self.children {
388 if child.handle_event(event) {
389 return true;
390 }
391 }
392 false
393 }
394}
395
396pub struct BoxLayout {
398 inner: Container,
399}
400
401impl BoxLayout {
402 pub fn new(bounds: Rect) -> Self {
404 Self {
405 inner: Container::new(bounds),
406 }
407 }
408
409 pub fn style_mut(&mut self) -> &mut rlvgl_core::style::Style {
411 &mut self.inner.style
412 }
413}
414
415impl Widget for BoxLayout {
416 fn bounds(&self) -> Rect {
417 self.inner.bounds()
418 }
419
420 fn draw(&self, renderer: &mut dyn Renderer) {
421 self.inner.draw(renderer);
422 }
423
424 fn handle_event(&mut self, event: &Event) -> bool {
425 self.inner.handle_event(event)
426 }
427}
428
429pub struct GridCalc {
443 pub x: i32,
445 pub y: i32,
447 pub cols: usize,
449 pub col_w: i32,
451 pub row_h: i32,
453 pub col_gap: i32,
455 pub row_gap: i32,
457}
458
459impl GridCalc {
460 pub const fn new(x: i32, y: i32, cols: usize, col_w: i32, row_h: i32) -> Self {
462 Self {
463 x,
464 y,
465 cols,
466 col_w,
467 row_h,
468 col_gap: 0,
469 row_gap: 0,
470 }
471 }
472
473 pub const fn gap(mut self, col_gap: i32, row_gap: i32) -> Self {
475 self.col_gap = col_gap;
476 self.row_gap = row_gap;
477 self
478 }
479
480 pub const fn cell(&self, row: usize, col: usize) -> Rect {
482 Rect {
483 x: self.x + col as i32 * (self.col_w + self.col_gap),
484 y: self.y + row as i32 * (self.row_h + self.row_gap),
485 width: self.col_w,
486 height: self.row_h,
487 }
488 }
489
490 pub const fn row_span(&self, row: usize) -> Rect {
492 let total_w = if self.cols == 0 {
493 0
494 } else {
495 self.cols as i32 * self.col_w + (self.cols as i32 - 1) * self.col_gap
496 };
497 Rect {
498 x: self.x,
499 y: self.y + row as i32 * (self.row_h + self.row_gap),
500 width: total_w,
501 height: self.row_h,
502 }
503 }
504
505 pub const fn total_width(&self) -> i32 {
507 if self.cols == 0 {
508 0
509 } else {
510 self.cols as i32 * self.col_w + (self.cols as i32 - 1) * self.col_gap
511 }
512 }
513
514 pub const fn total_height(&self, rows: usize) -> i32 {
516 if rows == 0 {
517 0
518 } else {
519 rows as i32 * self.row_h + (rows as i32 - 1) * self.row_gap
520 }
521 }
522}
523
524#[cfg(test)]
525mod tests {
526 use super::*;
527 use rlvgl_widgets::label::Label;
528
529 #[test]
530 fn rect_helpers_construct_and_transform_rects() {
531 let r = origin_rect(10, 20)
532 .at(2, 3)
533 .width(30)
534 .height(40)
535 .translate(5, -1)
536 .inset(2)
537 .min_size(40, 50);
538
539 assert_eq!(
540 r,
541 Rect {
542 x: 9,
543 y: 4,
544 width: 40,
545 height: 50
546 }
547 );
548 }
549
550 #[test]
551 fn rect_inset_clamps_to_zero_size() {
552 assert_eq!(rect(0, 0, 3, 3).inset(4).width, 0);
553 assert_eq!(rect(0, 0, 3, 3).inset(4).height, 0);
554 }
555
556 #[test]
557 fn vstack_stacks_vertically() {
558 let stack = VStack::new(20)
559 .spacing(2)
560 .child(10, |r| Label::new("a", r))
561 .child(10, |r| Label::new("b", r));
562 assert_eq!(stack.bounds().height, 22);
563 }
564
565 #[test]
566 fn hstack_stacks_horizontally() {
567 let stack = HStack::new(10)
568 .spacing(1)
569 .child(5, |r| Label::new("a", r))
570 .child(5, |r| Label::new("b", r));
571 assert_eq!(stack.bounds().width, 11);
572 }
573
574 #[test]
575 fn grid_places_cells() {
576 let grid = Grid::new(2, 5, 5)
577 .spacing(1)
578 .child(|r| Label::new("a", r))
579 .child(|r| Label::new("b", r))
580 .child(|r| Label::new("c", r));
581 assert_eq!(grid.bounds().height, 11);
582 assert_eq!(grid.bounds().width, 11);
583 }
584
585 #[test]
586 fn grid_zero_cols_does_not_panic() {
587 let grid = Grid::new(0, 5, 5)
590 .child(|r| Label::new("a", r))
591 .child(|r| Label::new("b", r));
592 assert_eq!(grid.bounds().height, 10);
593 }
594
595 #[test]
596 fn grid_calc_cell() {
597 let g = GridCalc::new(10, 20, 2, 100, 40).gap(4, 2);
598 let r = g.cell(0, 0);
599 assert_eq!(
600 r,
601 Rect {
602 x: 10,
603 y: 20,
604 width: 100,
605 height: 40
606 }
607 );
608 let r = g.cell(1, 1);
609 assert_eq!(
610 r,
611 Rect {
612 x: 114,
613 y: 62,
614 width: 100,
615 height: 40
616 }
617 );
618 }
619
620 #[test]
621 fn grid_calc_row_span() {
622 let g = GridCalc::new(0, 0, 3, 50, 30).gap(10, 5);
623 let r = g.row_span(0);
624 assert_eq!(
625 r,
626 Rect {
627 x: 0,
628 y: 0,
629 width: 170,
630 height: 30
631 }
632 );
633 assert_eq!(g.total_width(), 170);
634 assert_eq!(g.total_height(2), 65);
635 }
636}