Skip to main content

matrix_gui/
region.rs

1//! Region and layout management module for the matrix_gui framework.
2//!
3//! This module provides utilities for defining and managing widget regions within
4//! the GUI. It includes:
5//!
6//! - [`Region`]: A struct representing a rectangular area with an associated widget ID
7//! - Layout functions for arranging widgets in grid patterns
8//! - Macros for generating region IDs and layouts at compile time
9//!
10//! # Core Concepts
11//!
12//! ## Regions
13//!
14//! A [`Region`] represents a rectangular area on the screen where a widget can be
15//! placed. Each region has an associated widget ID that uniquely identifies it.
16//!
17//! ## Layouts
18//!
19//! The module provides several layout functions for arranging regions in grids:
20//!
21//! - **Row-major**: Regions are arranged left-to-right, top-to-bottom
22//! - **Column-major**: Regions are arranged top-to-bottom, left-to-right
23//! - Both runtime and compile-time variants are available
24//!
25//! # Macros
26//!
27//! The module provides several macros for generating region layouts:
28//!
29//! - [`free_form_region!`]: Define regions with custom positions and sizes
30//! - [`region_id!`]: Generate region ID enums
31//! - [`grid_layout_row_major!`]: Create row-major grid layouts
32//! - [`grid_layout_column_major!`]: Create column-major grid layouts
33//! - [`grid_layout_row_major_with_start!`]: Create grid layouts with custom start ID
34//! - [`grid_layout_column_major_with_start!`]: Create grid layouts with custom start ID
35
36use embedded_graphics::{
37    geometry::AnchorPoint,
38    prelude::{Point, Size},
39    primitives::Rectangle,
40};
41
42use crate::{
43    prelude::{DeltaResize, LwPoint, LwRectangle, LwSize},
44    widget_state::WidgetId,
45};
46
47/// A region representing a rectangular area with an associated widget ID.
48///
49/// This struct is used to define the position and size of widgets within the GUI.
50#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
51pub struct Region<ID> {
52    /// The widget ID associated with this region.
53    id: ID,
54    /// The rectangular area of this region.
55    area: LwRectangle<i16, u16>,
56}
57
58impl<ID: WidgetId> Region<ID> {
59    /// Creates a new region with the specified ID and dimensions.
60    ///
61    /// # Arguments
62    ///
63    /// * `id` - The widget ID for this region
64    /// * `x` - The x-coordinate of the top-left corner
65    /// * `y` - The y-coordinate of the top-left corner
66    /// * `w` - The width of the region
67    /// * `h` - The height of the region
68    pub const fn new(id: ID, x: i16, y: i16, w: u16, h: u16) -> Self {
69        let area = LwRectangle::new(LwPoint::new(x, y), LwSize::new(w, h));
70        Region { id, area }
71    }
72
73    /// Creates a new region with the specified ID and area.
74    pub const fn new_with_area(id: ID, area: LwRectangle<i16, u16>) -> Self {
75        Region { id, area }
76    }
77
78    /// Creates a new region with zero dimensions at the origin.
79    ///
80    /// # Arguments
81    ///
82    /// * `id` - The widget ID for this region
83    pub const fn zero(id: ID) -> Self {
84        Self::new(id, 0, 0, 0, 0)
85    }
86
87    /// Returns the widget ID associated with this region.
88    pub const fn id(&self) -> ID {
89        self.id
90    }
91
92    /// Replace the widget ID of the region.
93    pub const fn replace_id(&self, id: ID) -> Self {
94        Self {
95            id,
96            area: self.area,
97        }
98    }
99
100    /// Returns the top-left corner of the region as a `Point`.
101    pub fn top_left(&self) -> Point {
102        self.area.top_left.into()
103    }
104
105    /// Returns the size of the region as a `Size`.
106    pub fn size(&self) -> Size {
107        self.area.size.into()
108    }
109
110    /// Returns the region as a `Rectangle`.
111    pub fn rectangle(&self) -> Rectangle {
112        self.area.into()
113    }
114
115    /// Returns the x-coordinate of the top-left corner as an `i32`.
116    pub const fn x(&self) -> i32 {
117        self.area.top_left.x as i32
118    }
119
120    /// Returns the y-coordinate of the top-left corner as an `i32`.
121    pub const fn y(&self) -> i32 {
122        self.area.top_left.y as i32
123    }
124
125    /// Returns the width of the region as a `u32`.
126    pub const fn width(&self) -> u32 {
127        self.area.size.width as u32
128    }
129
130    /// Returns the height of the region as a `u32`.
131    pub const fn height(&self) -> u32 {
132        self.area.size.height as u32
133    }
134
135    /// Returns a new region resized according to the specified delta.
136    ///
137    /// # Arguments
138    ///
139    /// * `delta` - The resize delta to use for resizing
140    pub fn delta_resize(&self, delta: DeltaResize) -> Region<ID> {
141        let area = self.area.delta_resize(delta);
142
143        Region { id: self.id, area }
144    }
145
146    /// Moves the region by the specified amount.
147    ///
148    /// # Arguments
149    ///
150    /// * `dx` - The x-direction offset
151    /// * `dy` - The y-direction offset
152    ///
153    /// # Returns
154    ///
155    /// A new `Region` instance with the moved position.
156    pub fn move_by(&self, dx: i16, dy: i16) -> Self {
157        Self {
158            id: self.id,
159            area: self.area.move_by(dx, dy),
160        }
161    }
162
163    /// Returns a new region resized according to the specified width and height.
164    ///
165    /// # Arguments
166    ///
167    /// * `width` - The new width of the region
168    /// * `height` - The new height of the region
169    /// * `anchor_point` - The anchor point to use for resizing
170    ///
171    /// # Returns
172    ///
173    /// A new `Region` instance with the resized dimensions.
174    pub fn resized(&self, width: u16, height: u16, anchor_point: AnchorPoint) -> Self {
175        Self {
176            id: self.id,
177            area: self.area.resized(width, height, anchor_point),
178        }
179    }
180}
181
182/// Creates a free-form region definition with multiple regions.
183///
184/// This macro generates:
185/// - A `RegionID` enum with variants for each region
186/// - A `REGIONID_COUNT` constant representing the number of regions
187/// - Constants for each region as uppercase identifiers
188///
189/// # Usage
190///
191/// ```rust
192/// use matrix_gui::free_form_region;
193/// use matrix_gui::prelude::*;
194///
195/// free_form_region!(
196///     RegionID,
197///     (button, 10, 10, 100, 40),
198///     (label, 10, 60, 100, 20),
199/// );
200/// ```
201///
202/// This will generate:
203/// - A `RegionID` enum with variants `Button` and `Label`
204/// - A `REGIONID_COUNT` constant set to 2
205/// - Constants `BUTTON` and `LABEL` pointing to the respective regions
206#[macro_export]
207macro_rules! free_form_region {
208    (
209        $enum_name:ident,
210        $(($name:ident, $x:expr, $y:expr, $width:expr, $height:expr)),+
211        $(,)?
212    ) => {
213        paste::paste! {
214            pub const [<$enum_name:upper _COUNT>]: usize = enum_iterator::cardinality::<[<$enum_name:camel>]>();
215
216            #[derive(Debug, PartialEq, Clone, Copy, Default, enum_iterator::Sequence)]
217            #[repr(u16)]
218            pub enum [<$enum_name:camel>] {
219                #[default]
220                $([<$name:camel>]),+
221            }
222
223            impl $crate::widget_state::WidgetId for [<$enum_name:camel>] {
224                fn id(&self) -> usize {
225                    *self as usize
226                }
227            }
228        }
229
230        $(paste::paste! {
231            pub const [<$name:upper>]: &$crate::region::Region<[<$enum_name:camel>]> = &$crate::region::Region::new(
232                [<$enum_name:camel>]::[<$name:camel>],
233                $x, $y, $width, $height
234            );
235        })+
236    };
237}
238
239/// Creates a region ID enum starting from 0.
240///
241/// This macro generates an enum with the specified name and variants, implementing
242/// the `WidgetId` trait. It calls `region_id_with_start!` with a start value of 0.
243///
244/// # Usage
245///
246/// ```rust
247/// use matrix_gui::region_id;
248/// use matrix_gui::prelude::*;
249///
250/// region_id!(Button, [Ok, Cancel, Reset]);
251/// ```
252///
253/// This will generate a `Button` enum with variants `Ok`, `Cancel`, and `Reset`
254/// starting from 0.
255#[macro_export]
256macro_rules! region_id {
257    ($name:ident, [$first:ident, $($rest:ident),+ $(,)?]) => {
258        matrix_gui::region_id_with_start!($name, 0, [$first, $($rest),+]);
259    };
260}
261
262/// Creates a region ID enum with a specified starting value.
263///
264/// This macro generates an enum with the specified name and variants, implementing
265/// the `WidgetId` trait. It also generates a constant for the count of variants
266/// and an `all()` method that returns all enum variants.
267///
268/// # Usage
269///
270/// ```rust
271/// use matrix_gui::region_id_with_start;
272/// use matrix_gui::prelude::*;
273///
274/// region_id_with_start!(Button, 5, [Ok, Cancel, Reset]);
275/// ```
276///
277/// This will generate a `Button` enum with variants `Ok` (5), `Cancel` (6), and `Reset` (7).
278#[macro_export]
279macro_rules! region_id_with_start {
280    ($name:ident, $start:expr, [$first:ident, $($rest:ident),+ $(,)?]) => {
281        paste::paste! {
282            pub const [<$name:upper _COUNT>] : usize = enum_iterator::cardinality::<[<$name:camel>]>();
283            #[derive(Debug, PartialEq, Clone, Copy, Default, enum_iterator::Sequence)]
284            #[repr(u16)]
285            pub enum [<$name:camel>] {
286                #[default]
287                [<$first:camel>] = $start as u16,
288                $([<$rest:camel>]),+
289            }
290
291            impl $crate::widget_state::WidgetId for [<$name:camel>] {
292                fn id(&self) -> usize {
293                    *self as usize
294                }
295            }
296
297            impl [<$name:camel>] {
298                /// Returns an array containing all enum variants.
299                pub const fn all() -> [[<$name:camel>]; [<$name:upper _COUNT>]] {
300                [
301                    [<$name:camel>]::[<$first:camel>],
302                    $([<$name:camel>]::[<$rest:camel>]),+
303                ]
304                }
305            }
306        }
307    };
308
309    ($name:ident, $start:expr, [$only:ident]) => {
310        paste::paste! {
311            #[repr(u16)]
312            pub enum [<$name:camel>] {
313                [<$only:camel>] = $start
314            }
315        }
316    };
317}
318
319/// Creates a column-major grid layout in place.
320///
321/// This function fills the provided region slice with regions arranged in a grid layout,
322/// ordered by columns (filling each column top to bottom before moving to the next column).
323///
324/// # Arguments
325///
326/// * `start_id` - The starting widget ID
327/// * `area` - The bounding rectangle for the entire grid
328/// * `rows` - The number of rows in the grid
329/// * `cols` - The number of columns in the grid
330/// * `gap` - The gap between adjacent regions
331/// * `regions` - The slice of regions to fill
332pub fn grid_layout_column_major_mut<ID: WidgetId>(
333    start_id: ID,
334    area: &Rectangle,
335    rows: u32,
336    cols: u32,
337    gap: u32,
338    regions: &mut [Region<ID>],
339) {
340    let col_width = (area.size.width - gap * (cols.max(1) - 1)) / cols.max(1);
341    let row_height = (area.size.height - gap * (rows.max(1) - 1)) / rows.max(1);
342    let region_size = Size::new(col_width, row_height);
343    let mut region_top_left = area.top_left;
344    let mut rows_count = 0;
345    let mut curr_id = start_id;
346
347    for region in regions {
348        region.id = curr_id;
349        region.area = Rectangle::new(region_top_left, region_size).into();
350
351        rows_count += 1;
352        if rows_count < rows {
353            region_top_left.y += row_height as i32 + gap as i32;
354        } else {
355            if region_top_left.x - area.top_left.x >= area.size.width as i32 {
356                break;
357            }
358            region_top_left.x += col_width as i32 + gap as i32;
359            region_top_left.y = area.top_left.y;
360            rows_count = 0;
361        }
362
363        if let Some(id) = curr_id.next() {
364            curr_id = id;
365        }
366    }
367}
368
369/// Creates a column-major grid layout and returns it as an array.
370///
371/// This function creates an array of regions arranged in a grid layout,
372/// ordered by columns (filling each column top to bottom before moving to the next column).
373///
374/// # Arguments
375///
376/// * `start_id` - The starting widget ID
377/// * `area` - The bounding rectangle for the entire grid
378/// * `rows` - The number of rows in the grid
379/// * `cols` - The number of columns in the grid
380/// * `gap` - The gap between adjacent regions
381///
382/// # Returns
383///
384/// An array of regions arranged in a column-major grid layout
385pub fn grid_layout_column_major<ID: WidgetId, const N: usize>(
386    start_id: ID,
387    area: &Rectangle,
388    rows: u32,
389    cols: u32,
390    gap: u32,
391) -> [Region<ID>; N] {
392    let mut regions = [Region::zero(start_id); N];
393    grid_layout_column_major_mut(start_id, area, rows, cols, gap, &mut regions);
394
395    regions
396}
397
398/// Creates a row-major grid layout in place.
399///
400/// This function fills the provided region slice with regions arranged in a grid layout,
401/// ordered by rows (filling each row left to right before moving to the next row).
402///
403/// # Arguments
404///
405/// * `start_id` - The starting widget ID
406/// * `area` - The bounding rectangle for the entire grid
407/// * `rows` - The number of rows in the grid
408/// * `cols` - The number of columns in the grid
409/// * `gap` - The gap between adjacent regions
410/// * `regions` - The slice of regions to fill
411pub fn grid_layout_row_major_mut<ID: WidgetId>(
412    start_id: ID,
413    area: &Rectangle,
414    rows: u32,
415    cols: u32,
416    gap: u32,
417    regions: &mut [Region<ID>],
418) {
419    let col_width = (area.size.width - gap * (cols.max(1) - 1)) / cols.max(1);
420    let row_height = (area.size.height - gap * (rows.max(1) - 1)) / rows.max(1);
421    let region_size = Size::new(col_width, row_height);
422    let mut region_top_left = area.top_left;
423    let mut cols_count = 0;
424    let mut curr_id = start_id;
425
426    for region in regions {
427        region.id = curr_id;
428        region.area = Rectangle::new(region_top_left, region_size).into();
429
430        cols_count += 1;
431        if cols_count < cols {
432            region_top_left.x += col_width as i32 + gap as i32;
433        } else {
434            if region_top_left.y - area.top_left.y >= area.size.height as i32 {
435                break;
436            }
437            region_top_left.y += row_height as i32 + gap as i32;
438            region_top_left.x = area.top_left.x;
439            cols_count = 0;
440        }
441
442        if let Some(id) = curr_id.next() {
443            curr_id = id;
444        }
445    }
446}
447
448/// Creates a row-major grid layout and returns it as an array.
449///
450/// This function creates an array of regions arranged in a grid layout,
451/// ordered by rows (filling each row left to right before moving to the next row).
452///
453/// # Arguments
454///
455/// * `start_id` - The starting widget ID
456/// * `area` - The bounding rectangle for the entire grid
457/// * `rows` - The number of rows in the grid
458/// * `cols` - The number of columns in the grid
459/// * `gap` - The gap between adjacent regions
460///
461/// # Returns
462///
463/// An array of regions arranged in a row-major grid layout
464pub fn grid_layout_row_major<ID: WidgetId, const N: usize>(
465    start_id: ID,
466    area: &Rectangle,
467    rows: u32,
468    cols: u32,
469    gap: u32,
470) -> [Region<ID>; N] {
471    let mut regions = [Region::zero(start_id); N];
472    grid_layout_row_major_mut(start_id, area, rows, cols, gap, &mut regions);
473
474    regions
475}
476
477/// Creates a column-major grid layout at compile time.
478///
479/// This const function creates an array of regions arranged in a grid layout,
480/// ordered by columns (filling each column top to bottom before moving to the next column).
481///
482/// # Arguments
483///
484/// * `id_list` - An array of widget IDs to use for the regions
485/// * `area` - The bounding rectangle for the entire grid
486/// * `rows` - The number of rows in the grid
487/// * `cols` - The number of columns in the grid
488/// * `gap` - The gap between adjacent regions
489///
490/// # Returns
491///
492/// An array of regions arranged in a column-major grid layout
493pub const fn const_grid_layout_column_major<ID: WidgetId, const N: usize>(
494    id_list: &[ID; N],
495    area: &Rectangle,
496    rows: u32,
497    cols: u32,
498    gap: u32,
499) -> [Region<ID>; N] {
500    assert!(N > 0, "id_list must not be empty");
501    let mut regions = [Region::zero(id_list[0]); N];
502
503    let cols_max = if cols > 0 { cols } else { 1 };
504    let rows_max = if rows > 0 { rows } else { 1 };
505    let col_width = ((area.size.width - gap * (cols_max - 1)) / cols_max) as u16;
506    let row_height = ((area.size.height - gap * (rows_max - 1)) / rows_max) as u16;
507
508    let mut x = area.top_left.x;
509    let mut y = area.top_left.y;
510    let mut rows_count = 0;
511
512    let mut i = 0;
513    while i < N {
514        regions[i] = Region::new(id_list[i], x as i16, y as i16, col_width, row_height);
515
516        rows_count += 1;
517        if rows_count < rows {
518            y += row_height as i32 + gap as i32;
519        } else {
520            if x - area.top_left.x >= area.size.width as i32 {
521                break;
522            }
523            x += col_width as i32 + gap as i32;
524            y = area.top_left.y;
525            rows_count = 0;
526        }
527
528        i += 1;
529    }
530
531    regions
532}
533
534/// Creates a row-major grid layout at compile time.
535///
536/// This const function creates an array of regions arranged in a grid layout,
537/// ordered by rows (filling each row left to right before moving to the next row).
538///
539/// # Arguments
540///
541/// * `id_list` - An array of widget IDs to use for the regions
542/// * `area` - The bounding rectangle for the entire grid
543/// * `rows` - The number of rows in the grid
544/// * `cols` - The number of columns in the grid
545/// * `gap` - The gap between adjacent regions
546///
547/// # Returns
548///
549/// An array of regions arranged in a row-major grid layout
550pub const fn const_grid_layout_row_major<ID: WidgetId, const N: usize>(
551    id_list: &[ID; N],
552    area: &Rectangle,
553    rows: u32,
554    cols: u32,
555    gap: u32,
556) -> [Region<ID>; N] {
557    assert!(N > 0, "id_list must not be empty");
558    let mut regions = [Region::zero(id_list[0]); N];
559
560    let cols_max = if cols > 0 { cols } else { 1 };
561    let rows_max = if rows > 0 { rows } else { 1 };
562    let col_width = ((area.size.width - gap * (cols_max - 1)) / cols_max) as u16;
563    let row_height = ((area.size.height - gap * (rows_max - 1)) / rows_max) as u16;
564
565    let mut x = area.top_left.x;
566    let mut y = area.top_left.y;
567    let mut cols_count = 0;
568
569    let mut i = 0;
570    while i < N {
571        regions[i] = Region::new(id_list[i], x as i16, y as i16, col_width, row_height);
572
573        cols_count += 1;
574        if cols_count < cols {
575            x += col_width as i32 + gap as i32;
576        } else {
577            if y - area.top_left.y >= area.size.height as i32 {
578                break;
579            }
580            y += row_height as i32 + gap as i32;
581            x = area.top_left.x;
582            cols_count = 0;
583        }
584
585        i += 1;
586    }
587
588    regions
589}
590
591/// Creates constant variables for a grid layout.
592///
593/// This macro generates:
594/// - A constant for the layout area
595/// - A constant for the grid layout array
596/// - Constants for each region in the grid
597///
598/// # Arguments
599///
600/// * `name` - The base name for the generated constants and enum
601/// * `base_idx` - The base index to use for region constants
602/// * `(x, y, width, height)` - The area for the grid layout
603/// * `(rows, cols, gap)` - The grid dimensions and gap
604/// * `[first, rest...]` - The region names
605/// * `layout_fn` - The layout function to use (column or row)
606/// * `suffix` - The suffix to use for the layout array constant
607#[macro_export]
608macro_rules! grid_layout_const_var {
609    ($name:ident, $base_idx:expr, ($x:expr, $y:expr, $width:expr, $height:expr),
610    ($rows:expr, $cols:expr, $gap:expr), [$first:ident, $($rest:ident),+ $(,)?], $layout_fn:ident, $suffix:ident) => {
611        paste::paste! {
612            pub const [<$name:upper _AREA>]: Rectangle =
613                Rectangle::new(Point::new($x, $y), Size::new($width, $height));
614            pub const [<$name:upper _ $suffix>]: [Region<[<$name:camel>]>; [<$name:upper _COUNT>] ] =
615                matrix_gui::region::[<const_grid_layout_ $layout_fn _major>](&[<$name:camel>]::all(),
616                                    &[<$name:upper _AREA>], $rows, $cols, $gap);
617        }
618        paste::paste! {
619            pub const [<$first:upper>]: &Region<[<$name:camel>]> =
620                &[<$name:upper _ $suffix>][[<$name:camel>]::[<$first:camel>] as usize - $base_idx];
621        }
622        $(paste::paste! {
623            pub const [<$rest:upper>]: &Region<[<$name:camel>]> =
624                &[<$name:upper _ $suffix>][[<$name:camel>]::[<$rest:camel>] as usize - $base_idx];
625        })+
626    }
627}
628
629/// Creates a column-major grid layout with region IDs starting from 0.
630///
631/// This macro generates:
632/// - A region ID enum with variants for each region
633/// - Constants for each region in the grid
634/// - A constant for the grid layout array
635///
636/// # Usage
637///
638/// ```rust
639/// use matrix_gui::grid_layout_column_major;
640/// use matrix_gui::prelude::*;
641///
642/// grid_layout_column_major!(
643///     Button, (10, 10, 200, 100),
644///     (2, 2, 10), [Ok, Cancel, Reset, Exit]
645/// );
646/// ```
647#[macro_export]
648macro_rules! grid_layout_column_major {
649    ($name:ident, ($x:expr, $y:expr, $width:expr, $height:expr),
650        ($rows:expr, $cols:expr, $gap:expr), [$first:ident, $($rest:ident),+ $(,)?]) => {
651        matrix_gui::region_id!($name, [$first, $($rest),+]);
652        matrix_gui::grid_layout_const_var!($name, 0, ($x, $y, $width, $height),
653                                        ($rows, $cols, $gap), [$first, $($rest),+], column, GLCM);
654    }
655}
656
657/// Creates a row-major grid layout with region IDs starting from 0.
658///
659/// This macro generates:
660/// - A region ID enum with variants for each region
661/// - Constants for each region in the grid
662/// - A constant for the grid layout array
663///
664/// # Usage
665///
666/// ```rust
667/// use matrix_gui::grid_layout_row_major;
668/// use matrix_gui::prelude::*;
669///
670/// grid_layout_row_major!(
671///     Button, (10, 10, 200, 100),
672///     (2, 2, 10), [Ok, Cancel, Reset, Exit]
673/// );
674/// ```
675#[macro_export]
676macro_rules! grid_layout_row_major {
677    ($name:ident, ($x:expr, $y:expr, $width:expr, $height:expr),
678        ($rows:expr, $cols:expr, $gap:expr), [$first:ident, $($rest:ident),+ $(,)?]) => {
679        matrix_gui::region_id!($name, [$first, $($rest),+]);
680        matrix_gui::grid_layout_const_var!($name, 0, ($x, $y, $width, $height),
681                                        ($rows, $cols, $gap), [$first, $($rest),+], row, GLRM);
682    }
683}
684
685/// Creates a column-major grid layout with region IDs starting from a specified value.
686///
687/// This macro generates:
688/// - A region ID enum with variants for each region, starting from the specified value
689/// - Constants for each region in the grid
690/// - A constant for the grid layout array
691///
692/// # Usage
693///
694/// ```rust
695/// use matrix_gui::grid_layout_column_major_with_start;
696/// use matrix_gui::prelude::*;
697///
698/// grid_layout_column_major_with_start!(
699///     Button, 5, (10, 10, 200, 100),
700///     (2, 2, 10), [Ok, Cancel, Reset, Exit]
701/// );
702/// ```
703#[macro_export]
704macro_rules! grid_layout_column_major_with_start {
705    ($name:ident, $start:expr, ($x:expr, $y:expr, $width:expr, $height:expr),
706        ($rows:expr, $cols:expr, $gap:expr), [$first:ident, $($rest:ident),+ $(,)?]) => {
707        matrix_gui::region_id_with_start!($name, $start, [$first, $($rest),+]);
708        matrix_gui::grid_layout_const_var!($name, $start, ($x, $y, $width, $height),
709                                        ($rows, $cols, $gap), [$first, $($rest),+], column, GLCM);
710    }
711}
712
713/// Creates a row-major grid layout with region IDs starting from a specified value.
714///
715/// This macro generates:
716/// - A region ID enum with variants for each region, starting from the specified value
717/// - Constants for each region in the grid
718/// - A constant for the grid layout array
719///
720/// # Usage
721///
722/// ```rust
723/// use matrix_gui::grid_layout_row_major_with_start;
724/// use matrix_gui::prelude::*;
725///
726/// grid_layout_row_major_with_start!(
727///     Button, 5, (10, 10, 200, 100),
728///     (2, 2, 10), [Ok, Cancel, Reset, Exit]
729/// );
730/// ```
731#[macro_export]
732macro_rules! grid_layout_row_major_with_start {
733    ($name:ident, $start:expr, ($x:expr, $y:expr, $width:expr, $height:expr),
734        ($rows:expr, $cols:expr, $gap:expr), [$first:ident, $($rest:ident),+ $(,)?]) => {
735        matrix_gui::region_id_with_start!($name, $start, [$first, $($rest),+]);
736        matrix_gui::grid_layout_const_var!($name, $start, ($x, $y, $width, $height),
737                                        ($rows, $cols, $gap), [$first, $($rest),+], row, GLRM);
738    }
739}