use embedded_graphics::{
geometry::AnchorPoint,
prelude::{Point, Size},
primitives::Rectangle,
};
use crate::{
prelude::{DeltaResize, LwPoint, LwRectangle, LwSize},
widget_state::WidgetId,
};
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
pub struct Region<ID> {
id: ID,
area: LwRectangle<i16, u16>,
}
impl<ID: WidgetId> Region<ID> {
pub const fn new(id: ID, x: i16, y: i16, w: u16, h: u16) -> Self {
let area = LwRectangle::new(LwPoint::new(x, y), LwSize::new(w, h));
Region { id, area }
}
pub const fn new_with_area(id: ID, area: LwRectangle<i16, u16>) -> Self {
Region { id, area }
}
pub const fn zero(id: ID) -> Self {
Self::new(id, 0, 0, 0, 0)
}
pub const fn id(&self) -> ID {
self.id
}
pub const fn replace_id(&self, id: ID) -> Self {
Self {
id,
area: self.area,
}
}
pub fn top_left(&self) -> Point {
self.area.top_left.into()
}
pub fn size(&self) -> Size {
self.area.size.into()
}
pub fn rectangle(&self) -> Rectangle {
self.area.into()
}
pub const fn x(&self) -> i32 {
self.area.top_left.x as i32
}
pub const fn y(&self) -> i32 {
self.area.top_left.y as i32
}
pub const fn width(&self) -> u32 {
self.area.size.width as u32
}
pub const fn height(&self) -> u32 {
self.area.size.height as u32
}
pub fn delta_resize(&self, delta: DeltaResize) -> Region<ID> {
let area = self.area.delta_resize(delta);
Region { id: self.id, area }
}
pub fn move_by(&self, dx: i16, dy: i16) -> Self {
Self {
id: self.id,
area: self.area.move_by(dx, dy),
}
}
pub fn resized(&self, width: u16, height: u16, anchor_point: AnchorPoint) -> Self {
Self {
id: self.id,
area: self.area.resized(width, height, anchor_point),
}
}
}
#[macro_export]
macro_rules! free_form_region {
(
$enum_name:ident,
$(($name:ident, $x:expr, $y:expr, $width:expr, $height:expr)),+
$(,)?
) => {
paste::paste! {
pub const [<$enum_name:upper _COUNT>]: usize = enum_iterator::cardinality::<[<$enum_name:camel>]>();
#[derive(Debug, PartialEq, Clone, Copy, Default, enum_iterator::Sequence)]
#[repr(u16)]
pub enum [<$enum_name:camel>] {
#[default]
$([<$name:camel>]),+
}
impl $crate::widget_state::WidgetId for [<$enum_name:camel>] {
fn id(&self) -> usize {
*self as usize
}
}
}
$(paste::paste! {
pub const [<$name:upper>]: &$crate::region::Region<[<$enum_name:camel>]> = &$crate::region::Region::new(
[<$enum_name:camel>]::[<$name:camel>],
$x, $y, $width, $height
);
})+
};
}
#[macro_export]
macro_rules! region_id {
($name:ident, [$first:ident, $($rest:ident),+ $(,)?]) => {
matrix_gui::region_id_with_start!($name, 0, [$first, $($rest),+]);
};
}
#[macro_export]
macro_rules! region_id_with_start {
($name:ident, $start:expr, [$first:ident, $($rest:ident),+ $(,)?]) => {
paste::paste! {
pub const [<$name:upper _COUNT>] : usize = enum_iterator::cardinality::<[<$name:camel>]>();
#[derive(Debug, PartialEq, Clone, Copy, Default, enum_iterator::Sequence)]
#[repr(u16)]
pub enum [<$name:camel>] {
#[default]
[<$first:camel>] = $start as u16,
$([<$rest:camel>]),+
}
impl $crate::widget_state::WidgetId for [<$name:camel>] {
fn id(&self) -> usize {
*self as usize
}
}
impl [<$name:camel>] {
pub const fn all() -> [[<$name:camel>]; [<$name:upper _COUNT>]] {
[
[<$name:camel>]::[<$first:camel>],
$([<$name:camel>]::[<$rest:camel>]),+
]
}
}
}
};
($name:ident, $start:expr, [$only:ident]) => {
paste::paste! {
#[repr(u16)]
pub enum [<$name:camel>] {
[<$only:camel>] = $start
}
}
};
}
pub fn grid_layout_column_major_mut<ID: WidgetId>(
start_id: ID,
area: &Rectangle,
rows: u32,
cols: u32,
gap: u32,
regions: &mut [Region<ID>],
) {
let col_width = (area.size.width - gap * (cols.max(1) - 1)) / cols.max(1);
let row_height = (area.size.height - gap * (rows.max(1) - 1)) / rows.max(1);
let region_size = Size::new(col_width, row_height);
let mut region_top_left = area.top_left;
let mut rows_count = 0;
let mut curr_id = start_id;
for region in regions {
region.id = curr_id;
region.area = Rectangle::new(region_top_left, region_size).into();
rows_count += 1;
if rows_count < rows {
region_top_left.y += row_height as i32 + gap as i32;
} else {
if region_top_left.x - area.top_left.x >= area.size.width as i32 {
break;
}
region_top_left.x += col_width as i32 + gap as i32;
region_top_left.y = area.top_left.y;
rows_count = 0;
}
if let Some(id) = curr_id.next() {
curr_id = id;
}
}
}
pub fn grid_layout_column_major<ID: WidgetId, const N: usize>(
start_id: ID,
area: &Rectangle,
rows: u32,
cols: u32,
gap: u32,
) -> [Region<ID>; N] {
let mut regions = [Region::zero(start_id); N];
grid_layout_column_major_mut(start_id, area, rows, cols, gap, &mut regions);
regions
}
pub fn grid_layout_row_major_mut<ID: WidgetId>(
start_id: ID,
area: &Rectangle,
rows: u32,
cols: u32,
gap: u32,
regions: &mut [Region<ID>],
) {
let col_width = (area.size.width - gap * (cols.max(1) - 1)) / cols.max(1);
let row_height = (area.size.height - gap * (rows.max(1) - 1)) / rows.max(1);
let region_size = Size::new(col_width, row_height);
let mut region_top_left = area.top_left;
let mut cols_count = 0;
let mut curr_id = start_id;
for region in regions {
region.id = curr_id;
region.area = Rectangle::new(region_top_left, region_size).into();
cols_count += 1;
if cols_count < cols {
region_top_left.x += col_width as i32 + gap as i32;
} else {
if region_top_left.y - area.top_left.y >= area.size.height as i32 {
break;
}
region_top_left.y += row_height as i32 + gap as i32;
region_top_left.x = area.top_left.x;
cols_count = 0;
}
if let Some(id) = curr_id.next() {
curr_id = id;
}
}
}
pub fn grid_layout_row_major<ID: WidgetId, const N: usize>(
start_id: ID,
area: &Rectangle,
rows: u32,
cols: u32,
gap: u32,
) -> [Region<ID>; N] {
let mut regions = [Region::zero(start_id); N];
grid_layout_row_major_mut(start_id, area, rows, cols, gap, &mut regions);
regions
}
pub const fn const_grid_layout_column_major<ID: WidgetId, const N: usize>(
id_list: &[ID; N],
area: &Rectangle,
rows: u32,
cols: u32,
gap: u32,
) -> [Region<ID>; N] {
assert!(N > 0, "id_list must not be empty");
let mut regions = [Region::zero(id_list[0]); N];
let cols_max = if cols > 0 { cols } else { 1 };
let rows_max = if rows > 0 { rows } else { 1 };
let col_width = ((area.size.width - gap * (cols_max - 1)) / cols_max) as u16;
let row_height = ((area.size.height - gap * (rows_max - 1)) / rows_max) as u16;
let mut x = area.top_left.x;
let mut y = area.top_left.y;
let mut rows_count = 0;
let mut i = 0;
while i < N {
regions[i] = Region::new(id_list[i], x as i16, y as i16, col_width, row_height);
rows_count += 1;
if rows_count < rows {
y += row_height as i32 + gap as i32;
} else {
if x - area.top_left.x >= area.size.width as i32 {
break;
}
x += col_width as i32 + gap as i32;
y = area.top_left.y;
rows_count = 0;
}
i += 1;
}
regions
}
pub const fn const_grid_layout_row_major<ID: WidgetId, const N: usize>(
id_list: &[ID; N],
area: &Rectangle,
rows: u32,
cols: u32,
gap: u32,
) -> [Region<ID>; N] {
assert!(N > 0, "id_list must not be empty");
let mut regions = [Region::zero(id_list[0]); N];
let cols_max = if cols > 0 { cols } else { 1 };
let rows_max = if rows > 0 { rows } else { 1 };
let col_width = ((area.size.width - gap * (cols_max - 1)) / cols_max) as u16;
let row_height = ((area.size.height - gap * (rows_max - 1)) / rows_max) as u16;
let mut x = area.top_left.x;
let mut y = area.top_left.y;
let mut cols_count = 0;
let mut i = 0;
while i < N {
regions[i] = Region::new(id_list[i], x as i16, y as i16, col_width, row_height);
cols_count += 1;
if cols_count < cols {
x += col_width as i32 + gap as i32;
} else {
if y - area.top_left.y >= area.size.height as i32 {
break;
}
y += row_height as i32 + gap as i32;
x = area.top_left.x;
cols_count = 0;
}
i += 1;
}
regions
}
#[macro_export]
macro_rules! grid_layout_const_var {
($name:ident, $base_idx:expr, ($x:expr, $y:expr, $width:expr, $height:expr),
($rows:expr, $cols:expr, $gap:expr), [$first:ident, $($rest:ident),+ $(,)?], $layout_fn:ident, $suffix:ident) => {
paste::paste! {
pub const [<$name:upper _AREA>]: Rectangle =
Rectangle::new(Point::new($x, $y), Size::new($width, $height));
pub const [<$name:upper _ $suffix>]: [Region<[<$name:camel>]>; [<$name:upper _COUNT>] ] =
matrix_gui::region::[<const_grid_layout_ $layout_fn _major>](&[<$name:camel>]::all(),
&[<$name:upper _AREA>], $rows, $cols, $gap);
}
paste::paste! {
pub const [<$first:upper>]: &Region<[<$name:camel>]> =
&[<$name:upper _ $suffix>][[<$name:camel>]::[<$first:camel>] as usize - $base_idx];
}
$(paste::paste! {
pub const [<$rest:upper>]: &Region<[<$name:camel>]> =
&[<$name:upper _ $suffix>][[<$name:camel>]::[<$rest:camel>] as usize - $base_idx];
})+
}
}
#[macro_export]
macro_rules! grid_layout_column_major {
($name:ident, ($x:expr, $y:expr, $width:expr, $height:expr),
($rows:expr, $cols:expr, $gap:expr), [$first:ident, $($rest:ident),+ $(,)?]) => {
matrix_gui::region_id!($name, [$first, $($rest),+]);
matrix_gui::grid_layout_const_var!($name, 0, ($x, $y, $width, $height),
($rows, $cols, $gap), [$first, $($rest),+], column, GLCM);
}
}
#[macro_export]
macro_rules! grid_layout_row_major {
($name:ident, ($x:expr, $y:expr, $width:expr, $height:expr),
($rows:expr, $cols:expr, $gap:expr), [$first:ident, $($rest:ident),+ $(,)?]) => {
matrix_gui::region_id!($name, [$first, $($rest),+]);
matrix_gui::grid_layout_const_var!($name, 0, ($x, $y, $width, $height),
($rows, $cols, $gap), [$first, $($rest),+], row, GLRM);
}
}
#[macro_export]
macro_rules! grid_layout_column_major_with_start {
($name:ident, $start:expr, ($x:expr, $y:expr, $width:expr, $height:expr),
($rows:expr, $cols:expr, $gap:expr), [$first:ident, $($rest:ident),+ $(,)?]) => {
matrix_gui::region_id_with_start!($name, $start, [$first, $($rest),+]);
matrix_gui::grid_layout_const_var!($name, $start, ($x, $y, $width, $height),
($rows, $cols, $gap), [$first, $($rest),+], column, GLCM);
}
}
#[macro_export]
macro_rules! grid_layout_row_major_with_start {
($name:ident, $start:expr, ($x:expr, $y:expr, $width:expr, $height:expr),
($rows:expr, $cols:expr, $gap:expr), [$first:ident, $($rest:ident),+ $(,)?]) => {
matrix_gui::region_id_with_start!($name, $start, [$first, $($rest),+]);
matrix_gui::grid_layout_const_var!($name, $start, ($x, $y, $width, $height),
($rows, $cols, $gap), [$first, $($rest),+], row, GLRM);
}
}