use std::ops::RangeInclusive;
use cranpose_core::NodeId;
use smallvec::SmallVec;
use super::layout::{LayoutEnvironment, compose_layout};
use crate::{
composable,
layout::{core::Alignment, grid::GridMeasurePolicy},
modifier::Modifier,
};
pub const GRID_INDEX_UNSPECIFIED: i32 = 0;
pub const MAX_GRID_INDEX: i32 = 1000;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Fr(pub f32);
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum GridTrackSize {
Fixed(f32),
Percentage(f32),
Flex(Fr),
MinMax(f32, Fr),
MinContent,
MaxContent,
Auto,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum GridFlow {
#[default]
Row,
Column,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct GridAreaDef {
pub(crate) name: &'static str,
pub(crate) row: i32,
pub(crate) column: i32,
pub(crate) row_span: u32,
pub(crate) column_span: u32,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct GridConfig {
pub(crate) columns: SmallVec<[GridTrackSize; 4]>,
pub(crate) rows: SmallVec<[GridTrackSize; 4]>,
pub(crate) row_gap: f32,
pub(crate) column_gap: f32,
pub(crate) flow: GridFlow,
pub(crate) areas: SmallVec<[GridAreaDef; 2]>,
}
impl GridConfig {
pub fn new() -> Self {
Self::default()
}
pub fn column(mut self, size: GridTrackSize) -> Self {
self.columns.push(size);
self
}
pub fn columns(mut self, sizes: impl IntoIterator<Item = GridTrackSize>) -> Self {
self.columns.extend(sizes);
self
}
pub fn row(mut self, size: GridTrackSize) -> Self {
self.rows.push(size);
self
}
pub fn rows(mut self, sizes: impl IntoIterator<Item = GridTrackSize>) -> Self {
self.rows.extend(sizes);
self
}
pub fn gap(self, all: f32) -> Self {
self.row_gap(all).column_gap(all)
}
pub fn row_gap(mut self, gap: f32) -> Self {
self.row_gap = gap.max(0.0);
self
}
pub fn column_gap(mut self, gap: f32) -> Self {
self.column_gap = gap.max(0.0);
self
}
pub fn flow(mut self, flow: GridFlow) -> Self {
self.flow = flow;
self
}
pub fn area(
mut self,
name: &'static str,
row: i32,
column: i32,
row_span: u32,
column_span: u32,
) -> Self {
let area = GridAreaDef {
name,
row: clamp_index(row),
column: clamp_index(column),
row_span: clamp_span(row_span),
column_span: clamp_span(column_span),
};
match self.areas.iter_mut().find(|known| known.name == name) {
Some(known) => *known = area,
None => self.areas.push(area),
}
self
}
pub fn area_ranges(
self,
name: &'static str,
rows: RangeInclusive<i32>,
columns: RangeInclusive<i32>,
) -> Self {
let (row, row_span) = start_and_span(rows);
let (column, column_span) = start_and_span(columns);
self.area(name, row, column, row_span, column_span)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GridItem {
pub(crate) row: i32,
pub(crate) column: i32,
pub(crate) row_span: u32,
pub(crate) column_span: u32,
pub(crate) area: Option<&'static str>,
pub(crate) alignment: Alignment,
}
impl GridItem {
pub const fn auto() -> Self {
Self {
row: GRID_INDEX_UNSPECIFIED,
column: GRID_INDEX_UNSPECIFIED,
row_span: 1,
column_span: 1,
area: None,
alignment: Alignment::TOP_START,
}
}
pub const fn at(row: i32, column: i32) -> Self {
Self {
row: clamp_index(row),
column: clamp_index(column),
..Self::auto()
}
}
pub fn ranges(rows: RangeInclusive<i32>, columns: RangeInclusive<i32>) -> Self {
let (row, row_span) = start_and_span(rows);
let (column, column_span) = start_and_span(columns);
Self::at(row, column).span(row_span, column_span)
}
pub const fn area(name: &'static str) -> Self {
Self {
area: Some(name),
..Self::auto()
}
}
pub const fn span(mut self, rows: u32, columns: u32) -> Self {
self.row_span = clamp_span(rows);
self.column_span = clamp_span(columns);
self
}
pub const fn alignment(mut self, alignment: Alignment) -> Self {
self.alignment = alignment;
self
}
}
impl Default for GridItem {
fn default() -> Self {
Self::auto()
}
}
impl Modifier {
pub fn grid_item(self, item: GridItem) -> Self {
self.layout_data(item)
}
}
const fn clamp_index(index: i32) -> i32 {
if index > MAX_GRID_INDEX {
MAX_GRID_INDEX
} else if index < -MAX_GRID_INDEX {
-MAX_GRID_INDEX
} else {
index
}
}
const fn clamp_span(span: u32) -> u32 {
if span == 0 {
1
} else if span > MAX_GRID_INDEX as u32 {
MAX_GRID_INDEX as u32
} else {
span
}
}
fn start_and_span(range: RangeInclusive<i32>) -> (i32, u32) {
let (start, end) = range.into_inner();
(
start,
end.saturating_sub(start).saturating_add(1).max(1) as u32,
)
}
#[composable]
pub fn Grid<F>(config: GridConfig, modifier: Modifier, content: F) -> NodeId
where
F: FnMut() + 'static,
{
let environment = LayoutEnvironment::current();
let policy = GridMeasurePolicy {
config,
layout_direction: environment.direction,
density: environment.density.density(),
};
compose_layout(modifier, policy, environment, content)
}