use alloc::{
boxed::Box,
string::{String, ToString},
vec::Vec,
};
use crate::{
corety::AzString,
codegen::format::FormatAsRustCode,
impl_vec, impl_vec_clone, impl_vec_debug, impl_vec_eq, impl_vec_hash, impl_vec_mut,
impl_vec_ord, impl_vec_partialeq, impl_vec_partialord,
props::{basic::pixel::PixelValue, formatter::PrintAsCssValue},
};
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct GridMinMax {
pub min: Box<GridTrackSizing>,
pub max: Box<GridTrackSizing>,
}
impl core::fmt::Debug for GridMinMax {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"minmax({}, {})",
self.min.print_as_css_value(),
self.max.print_as_css_value()
)
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C, u8)]
#[derive(Default)]
pub enum GridTrackSizing {
Fixed(PixelValue),
Fr(i32),
MinContent,
MaxContent,
#[default]
Auto,
MinMax(GridMinMax),
FitContent(PixelValue),
}
impl_option!(
GridTrackSizing,
OptionGridTrackSizing,
copy = false,
[Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl core::fmt::Debug for GridTrackSizing {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.print_as_css_value())
}
}
impl PrintAsCssValue for GridTrackSizing {
fn print_as_css_value(&self) -> String {
match self {
Self::Fixed(px) => px.print_as_css_value(),
Self::Fr(f) => format!("{f}fr"),
Self::MinContent => "min-content".to_string(),
Self::MaxContent => "max-content".to_string(),
Self::Auto => "auto".to_string(),
Self::MinMax(minmax) => {
format!(
"minmax({}, {})",
minmax.min.print_as_css_value(),
minmax.max.print_as_css_value()
)
}
Self::FitContent(size) => {
format!("fit-content({})", size.print_as_css_value())
}
}
}
}
impl_vec!(GridTrackSizing, GridTrackSizingVec, GridTrackSizingVecDestructor, GridTrackSizingVecDestructorType, GridTrackSizingVecSlice, OptionGridTrackSizing);
impl_vec_clone!(
GridTrackSizing,
GridTrackSizingVec,
GridTrackSizingVecDestructor
);
impl_vec_debug!(GridTrackSizing, GridTrackSizingVec);
impl_vec_partialeq!(GridTrackSizing, GridTrackSizingVec);
impl_vec_eq!(GridTrackSizing, GridTrackSizingVec);
impl_vec_partialord!(GridTrackSizing, GridTrackSizingVec);
impl_vec_ord!(GridTrackSizing, GridTrackSizingVec);
impl_vec_hash!(GridTrackSizing, GridTrackSizingVec);
impl_vec_mut!(GridTrackSizing, GridTrackSizingVec);
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct GridTemplate {
pub tracks: GridTrackSizingVec,
}
impl core::fmt::Debug for GridTemplate {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.print_as_css_value())
}
}
impl Default for GridTemplate {
fn default() -> Self {
Self {
tracks: GridTrackSizingVec::from_vec(Vec::new()),
}
}
}
impl PrintAsCssValue for GridTemplate {
fn print_as_css_value(&self) -> String {
let tracks_slice = self.tracks.as_ref();
if tracks_slice.is_empty() {
"none".to_string()
} else {
tracks_slice
.iter()
.map(PrintAsCssValue::print_as_css_value)
.collect::<Vec<_>>()
.join(" ")
}
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct GridAutoTracks {
pub tracks: GridTrackSizingVec,
}
impl core::fmt::Debug for GridAutoTracks {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.print_as_css_value())
}
}
impl Default for GridAutoTracks {
fn default() -> Self {
Self {
tracks: GridTrackSizingVec::from_vec(Vec::new()),
}
}
}
impl PrintAsCssValue for GridAutoTracks {
fn print_as_css_value(&self) -> String {
let tracks_slice = self.tracks.as_ref();
if tracks_slice.is_empty() {
"auto".to_string()
} else {
tracks_slice
.iter()
.map(PrintAsCssValue::print_as_css_value)
.collect::<Vec<_>>()
.join(" ")
}
}
}
impl From<GridTemplate> for GridAutoTracks {
fn from(template: GridTemplate) -> Self {
Self {
tracks: template.tracks,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct NamedGridLine {
pub grid_line_name: AzString,
pub span_count: i32,
}
impl NamedGridLine {
#[must_use] pub fn create(name: AzString, span: Option<i32>) -> Self {
Self {
grid_line_name: name,
span_count: span.unwrap_or(0),
}
}
#[must_use] pub const fn span(&self) -> Option<i32> {
if self.span_count == 0 {
None
} else {
Some(self.span_count)
}
}
}
#[allow(variant_size_differences)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C, u8)]
#[derive(Default)]
pub enum GridLine {
#[default]
Auto,
Line(i32),
Named(NamedGridLine),
Span(i32),
}
impl core::fmt::Debug for GridLine {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.print_as_css_value())
}
}
impl PrintAsCssValue for GridLine {
fn print_as_css_value(&self) -> String {
match self {
Self::Auto => "auto".to_string(),
Self::Line(n) => n.to_string(),
Self::Named(named) => {
if named.span_count == 0 {
named.grid_line_name.as_str().to_string()
} else {
format!("{} {}", named.grid_line_name.as_str(), named.span_count)
}
}
Self::Span(n) => format!("span {n}"),
}
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct GridPlacement {
pub grid_start: GridLine,
pub grid_end: GridLine,
}
impl core::fmt::Debug for GridPlacement {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.print_as_css_value())
}
}
impl Default for GridPlacement {
fn default() -> Self {
Self {
grid_start: GridLine::Auto,
grid_end: GridLine::Auto,
}
}
}
impl PrintAsCssValue for GridPlacement {
fn print_as_css_value(&self) -> String {
if self.grid_end == GridLine::Auto {
self.grid_start.print_as_css_value()
} else {
format!(
"{} / {}",
self.grid_start.print_as_css_value(),
self.grid_end.print_as_css_value()
)
}
}
}
#[cfg(feature = "parser")]
#[derive(Clone, PartialEq, Eq)]
pub enum GridParseError<'a> {
InvalidValue(&'a str),
}
#[cfg(feature = "parser")]
impl_debug_as_display!(GridParseError<'a>);
#[cfg(feature = "parser")]
impl_display! { GridParseError<'a>, {
InvalidValue(e) => format!("Invalid grid value: \"{}\"", e),
}}
#[cfg(feature = "parser")]
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C, u8)]
pub enum GridParseErrorOwned {
InvalidValue(AzString),
}
#[cfg(feature = "parser")]
impl GridParseError<'_> {
#[must_use] pub fn to_contained(&self) -> GridParseErrorOwned {
match self {
GridParseError::InvalidValue(s) => GridParseErrorOwned::InvalidValue((*s).to_string().into()),
}
}
}
#[cfg(feature = "parser")]
impl GridParseErrorOwned {
#[must_use] pub fn to_shared(&self) -> GridParseError<'_> {
match self {
Self::InvalidValue(s) => GridParseError::InvalidValue(s.as_str()),
}
}
}
#[cfg(feature = "parser")]
fn split_respecting_parens(input: &str) -> Result<Vec<String>, ()> {
let mut parts = Vec::new();
let mut current = String::new();
let mut paren_depth: i32 = 0;
for ch in input.chars() {
match ch {
'(' => { paren_depth += 1; current.push(ch); }
')' => { paren_depth -= 1; if paren_depth < 0 { return Err(()); } current.push(ch); }
' ' if paren_depth == 0 => {
if !current.trim().is_empty() {
parts.push(current.trim().to_string());
current.clear();
}
}
_ => current.push(ch),
}
}
if !current.trim().is_empty() {
parts.push(current.trim().to_string());
}
Ok(parts)
}
#[cfg(feature = "parser")]
pub fn parse_grid_template(input: &str) -> Result<GridTemplate, GridParseError<'_>> {
use crate::props::basic::pixel::parse_pixel_value;
let input = input.trim();
if input == "none" {
return Ok(GridTemplate::default());
}
let parts = split_respecting_parens(input)
.map_err(|()| GridParseError::InvalidValue(input))?;
let mut tracks = Vec::new();
for part in &parts {
parse_grid_track_or_repeat(part, &mut tracks)
.map_err(|()| GridParseError::InvalidValue(input))?;
}
Ok(GridTemplate {
tracks: GridTrackSizingVec::from_vec(tracks),
})
}
#[cfg(feature = "parser")]
fn parse_grid_track_or_repeat(input: &str, tracks: &mut Vec<GridTrackSizing>) -> Result<(), ()> {
const MAX_GRID_REPEAT_COUNT: usize = 10_000;
let input = input.trim();
if input.starts_with("repeat(") && input.ends_with(')') {
let content = &input[7..input.len() - 1];
let comma_pos = content.find(',').ok_or(())?;
let count_str = content[..comma_pos].trim();
let track_list_str = content[comma_pos + 1..].trim();
let count: usize = count_str.parse().map_err(|_| ())?;
if count == 0 || count > MAX_GRID_REPEAT_COUNT {
return Err(());
}
let parts = split_respecting_parens(track_list_str)?;
let repeat_tracks: Vec<GridTrackSizing> = parts
.iter()
.map(|p| parse_grid_track_owned(p))
.collect::<Result<Vec<_>, _>>()?;
for _ in 0..count {
tracks.extend(repeat_tracks.iter().cloned());
}
return Ok(());
}
tracks.push(parse_grid_track_owned(input)?);
Ok(())
}
#[cfg(feature = "parser")]
fn parse_grid_track_owned(input: &str) -> Result<GridTrackSizing, ()> {
use crate::props::basic::pixel::parse_pixel_value;
let input = input.trim();
if input == "auto" {
return Ok(GridTrackSizing::Auto);
}
if input == "min-content" {
return Ok(GridTrackSizing::MinContent);
}
if input == "max-content" {
return Ok(GridTrackSizing::MaxContent);
}
if let Some(num_str) = input.strip_suffix("fr") {
const FR_SCALING_FACTOR: f32 = 100.0;
let num_str = num_str.trim();
if let Ok(num) = num_str.parse::<f32>() {
let scaled = num * FR_SCALING_FACTOR;
if scaled.is_nan() || scaled < crate::cast::i32_to_f32(i32::MIN) || scaled > crate::cast::i32_to_f32(i32::MAX) {
return Err(());
}
return Ok(GridTrackSizing::Fr(crate::cast::f32_to_i32(scaled)));
}
return Err(());
}
if input.starts_with("minmax(") && input.ends_with(')') {
let content = &input[7..input.len() - 1];
let parts: Vec<&str> = content.split(',').collect();
if parts.len() == 2 {
let min = parse_grid_track_owned(parts[0].trim())?;
let max = parse_grid_track_owned(parts[1].trim())?;
return Ok(GridTrackSizing::MinMax(GridMinMax {
min: Box::new(min),
max: Box::new(max),
}));
}
return Err(());
}
if input.starts_with("fit-content(") && input.ends_with(')') {
let size_str = &input[12..input.len() - 1].trim();
if let Ok(size) = parse_pixel_value(size_str) {
return Ok(GridTrackSizing::FitContent(size));
}
return Err(());
}
if let Ok(px) = parse_pixel_value(input) {
return Ok(GridTrackSizing::Fixed(px));
}
Err(())
}
#[cfg(feature = "parser")]
pub fn parse_grid_placement(input: &str) -> Result<GridPlacement, GridParseError<'_>> {
let input = input.trim();
if input == "auto" {
return Ok(GridPlacement::default());
}
let parts: Vec<&str> = input.split('/').map(str::trim).collect();
let grid_start =
parse_grid_line_owned(parts[0]).map_err(|()| GridParseError::InvalidValue(input))?;
let grid_end = if parts.len() > 1 {
parse_grid_line_owned(parts[1]).map_err(|()| GridParseError::InvalidValue(input))?
} else {
GridLine::Auto
};
Ok(GridPlacement {
grid_start,
grid_end,
})
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
#[derive(Default)]
pub enum LayoutGridAutoFlow {
#[default]
Row,
Column,
RowDense,
ColumnDense,
}
impl PrintAsCssValue for LayoutGridAutoFlow {
fn print_as_css_value(&self) -> alloc::string::String {
match self {
Self::Row => "row".to_string(),
Self::Column => "column".to_string(),
Self::RowDense => "row dense".to_string(),
Self::ColumnDense => "column dense".to_string(),
}
}
}
#[cfg(feature = "parser")]
#[derive(Clone, PartialEq, Eq)]
pub enum GridAutoFlowParseError<'a> {
InvalidValue(&'a str),
}
#[cfg(feature = "parser")]
impl_debug_as_display!(GridAutoFlowParseError<'a>);
#[cfg(feature = "parser")]
impl_display! { GridAutoFlowParseError<'a>, {
InvalidValue(e) => format!("Invalid grid-auto-flow value: \"{}\"", e),
}}
#[cfg(feature = "parser")]
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C, u8)]
pub enum GridAutoFlowParseErrorOwned {
InvalidValue(AzString),
}
#[cfg(feature = "parser")]
impl GridAutoFlowParseError<'_> {
#[must_use] pub fn to_contained(&self) -> GridAutoFlowParseErrorOwned {
match self {
GridAutoFlowParseError::InvalidValue(s) => {
GridAutoFlowParseErrorOwned::InvalidValue((*s).to_string().into())
}
}
}
}
#[cfg(feature = "parser")]
impl GridAutoFlowParseErrorOwned {
#[must_use] pub fn to_shared(&self) -> GridAutoFlowParseError<'_> {
match self {
Self::InvalidValue(s) => {
GridAutoFlowParseError::InvalidValue(s.as_str())
}
}
}
}
#[cfg(feature = "parser")]
pub fn parse_layout_grid_auto_flow(
input: &str,
) -> Result<LayoutGridAutoFlow, GridAutoFlowParseError<'_>> {
match input.trim() {
"row" => Ok(LayoutGridAutoFlow::Row),
"column" => Ok(LayoutGridAutoFlow::Column),
"row dense" | "dense" => Ok(LayoutGridAutoFlow::RowDense),
"column dense" => Ok(LayoutGridAutoFlow::ColumnDense),
_ => Err(GridAutoFlowParseError::InvalidValue(input)),
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
#[derive(Default)]
pub enum LayoutJustifySelf {
#[default]
Auto,
Start,
End,
Center,
Stretch,
}
impl PrintAsCssValue for LayoutJustifySelf {
fn print_as_css_value(&self) -> alloc::string::String {
match self {
Self::Auto => "auto".to_string(),
Self::Start => "start".to_string(),
Self::End => "end".to_string(),
Self::Center => "center".to_string(),
Self::Stretch => "stretch".to_string(),
}
}
}
#[cfg(feature = "parser")]
#[derive(Clone, PartialEq, Eq)]
pub enum JustifySelfParseError<'a> {
InvalidValue(&'a str),
}
#[cfg(feature = "parser")]
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C, u8)]
pub enum JustifySelfParseErrorOwned {
InvalidValue(AzString),
}
#[cfg(feature = "parser")]
impl JustifySelfParseError<'_> {
#[must_use] pub fn to_contained(&self) -> JustifySelfParseErrorOwned {
match self {
JustifySelfParseError::InvalidValue(s) => {
JustifySelfParseErrorOwned::InvalidValue((*s).to_string().into())
}
}
}
}
#[cfg(feature = "parser")]
impl JustifySelfParseErrorOwned {
#[must_use] pub fn to_shared(&self) -> JustifySelfParseError<'_> {
match self {
Self::InvalidValue(s) => {
JustifySelfParseError::InvalidValue(s.as_str())
}
}
}
}
#[cfg(feature = "parser")]
impl_debug_as_display!(JustifySelfParseError<'a>);
#[cfg(feature = "parser")]
impl_display! { JustifySelfParseError<'a>, {
InvalidValue(e) => format!("Invalid justify-self value: \"{}\"", e),
}}
#[cfg(feature = "parser")]
pub fn parse_layout_justify_self(
input: &str,
) -> Result<LayoutJustifySelf, JustifySelfParseError<'_>> {
match input.trim() {
"auto" => Ok(LayoutJustifySelf::Auto),
"start" | "flex-start" => Ok(LayoutJustifySelf::Start),
"end" | "flex-end" => Ok(LayoutJustifySelf::End),
"center" => Ok(LayoutJustifySelf::Center),
"stretch" => Ok(LayoutJustifySelf::Stretch),
_ => Err(JustifySelfParseError::InvalidValue(input)),
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
#[derive(Default)]
pub enum LayoutJustifyItems {
Start,
End,
Center,
#[default]
Stretch,
}
impl PrintAsCssValue for LayoutJustifyItems {
fn print_as_css_value(&self) -> alloc::string::String {
match self {
Self::Start => "start".to_string(),
Self::End => "end".to_string(),
Self::Center => "center".to_string(),
Self::Stretch => "stretch".to_string(),
}
}
}
#[cfg(feature = "parser")]
#[derive(Clone, PartialEq, Eq)]
pub enum JustifyItemsParseError<'a> {
InvalidValue(&'a str),
}
#[cfg(feature = "parser")]
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C, u8)]
pub enum JustifyItemsParseErrorOwned {
InvalidValue(AzString),
}
#[cfg(feature = "parser")]
impl JustifyItemsParseError<'_> {
#[must_use] pub fn to_contained(&self) -> JustifyItemsParseErrorOwned {
match self {
JustifyItemsParseError::InvalidValue(s) => {
JustifyItemsParseErrorOwned::InvalidValue((*s).to_string().into())
}
}
}
}
#[cfg(feature = "parser")]
impl JustifyItemsParseErrorOwned {
#[must_use] pub fn to_shared(&self) -> JustifyItemsParseError<'_> {
match self {
Self::InvalidValue(s) => {
JustifyItemsParseError::InvalidValue(s.as_str())
}
}
}
}
#[cfg(feature = "parser")]
impl_debug_as_display!(JustifyItemsParseError<'a>);
#[cfg(feature = "parser")]
impl_display! { JustifyItemsParseError<'a>, {
InvalidValue(e) => format!("Invalid justify-items value: \"{}\"", e),
}}
#[cfg(feature = "parser")]
pub fn parse_layout_justify_items(
input: &str,
) -> Result<LayoutJustifyItems, JustifyItemsParseError<'_>> {
match input.trim() {
"start" => Ok(LayoutJustifyItems::Start),
"end" => Ok(LayoutJustifyItems::End),
"center" => Ok(LayoutJustifyItems::Center),
"stretch" => Ok(LayoutJustifyItems::Stretch),
_ => Err(JustifyItemsParseError::InvalidValue(input)),
}
}
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct LayoutGap {
pub inner: PixelValue,
}
impl core::fmt::Debug for LayoutGap {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.inner)
}
}
impl PrintAsCssValue for LayoutGap {
fn print_as_css_value(&self) -> alloc::string::String {
self.inner.print_as_css_value()
}
}
impl FormatAsRustCode for LayoutGridAutoFlow {
fn format_as_rust_code(&self, _tabs: usize) -> String {
format!(
"LayoutGridAutoFlow::{}",
match self {
Self::Row => "Row",
Self::Column => "Column",
Self::RowDense => "RowDense",
Self::ColumnDense => "ColumnDense",
}
)
}
}
impl FormatAsRustCode for LayoutJustifySelf {
fn format_as_rust_code(&self, _tabs: usize) -> String {
format!(
"LayoutJustifySelf::{}",
match self {
Self::Auto => "Auto",
Self::Start => "Start",
Self::End => "End",
Self::Center => "Center",
Self::Stretch => "Stretch",
}
)
}
}
impl FormatAsRustCode for LayoutJustifyItems {
fn format_as_rust_code(&self, _tabs: usize) -> String {
format!(
"LayoutJustifyItems::{}",
match self {
Self::Start => "Start",
Self::End => "End",
Self::Center => "Center",
Self::Stretch => "Stretch",
}
)
}
}
impl FormatAsRustCode for LayoutGap {
fn format_as_rust_code(&self, _tabs: usize) -> String {
use crate::codegen::format::format_pixel_value;
format!("LayoutGap {{ inner: {} }}", format_pixel_value(&self.inner))
}
}
impl FormatAsRustCode for GridTrackSizing {
#[allow(clippy::only_used_in_recursion)]
fn format_as_rust_code(&self, tabs: usize) -> String {
use crate::codegen::format::format_pixel_value;
match self {
Self::Fixed(pv) => {
format!("GridTrackSizing::Fixed({})", format_pixel_value(pv))
}
Self::Fr(f) => format!("GridTrackSizing::Fr({f})"),
Self::MinContent => "GridTrackSizing::MinContent".to_string(),
Self::MaxContent => "GridTrackSizing::MaxContent".to_string(),
Self::Auto => "GridTrackSizing::Auto".to_string(),
Self::MinMax(minmax) => {
format!(
"GridTrackSizing::MinMax(GridMinMax {{ min: Box::new({}), max: Box::new({}) }})",
minmax.min.format_as_rust_code(tabs),
minmax.max.format_as_rust_code(tabs)
)
}
Self::FitContent(pv) => {
format!("GridTrackSizing::FitContent({})", format_pixel_value(pv))
}
}
}
}
impl FormatAsRustCode for GridAutoTracks {
fn format_as_rust_code(&self, tabs: usize) -> String {
let tracks: Vec<String> = self
.tracks
.as_ref()
.iter()
.map(|t| t.format_as_rust_code(tabs))
.collect();
format!(
"GridAutoTracks {{ tracks: GridTrackSizingVec::from_vec(vec![{}]) }}",
tracks.join(", ")
)
}
}
impl FormatAsRustCode for GridTemplateAreas {
fn format_as_rust_code(&self, _tabs: usize) -> String {
format!("GridTemplateAreas {{ areas: GridAreaDefinitionVec::from_vec(vec!{:?}) }}", self.areas.as_ref())
}
}
#[cfg(feature = "parser")]
pub fn parse_layout_gap(
input: &str,
) -> Result<LayoutGap, crate::props::basic::pixel::CssPixelValueParseError<'_>> {
crate::props::basic::pixel::parse_pixel_value(input).map(|p| LayoutGap { inner: p })
}
#[cfg(feature = "parser")]
pub fn parse_grid_line_owned(input: &str) -> Result<GridLine, ()> {
let input = input.trim();
if input == "auto" {
return Ok(GridLine::Auto);
}
if let Some(num_str) = input.strip_prefix("span ") {
let num_str = num_str.trim();
if let Ok(num) = num_str.parse::<i32>() {
return Ok(GridLine::Span(num));
}
return Err(());
}
if let Ok(num) = input.parse::<i32>() {
return Ok(GridLine::Line(num));
}
Ok(GridLine::Named(NamedGridLine::create(
input.to_string().into(),
None,
)))
}
#[cfg(all(test, feature = "parser"))]
mod tests {
use super::*;
#[test]
fn test_parse_grid_template_none() {
let result = parse_grid_template("none").unwrap();
assert_eq!(result.tracks.len(), 0);
}
#[test]
fn test_parse_grid_template_single_px() {
let result = parse_grid_template("100px").unwrap();
assert_eq!(result.tracks.len(), 1);
assert!(matches!(
result.tracks.as_ref()[0],
GridTrackSizing::Fixed(_)
));
}
#[test]
fn test_parse_grid_template_multiple_tracks() {
let result = parse_grid_template("100px 200px 1fr").unwrap();
assert_eq!(result.tracks.len(), 3);
}
#[test]
fn test_parse_grid_template_fr_units() {
let result = parse_grid_template("1fr 2fr 1fr").unwrap();
assert_eq!(result.tracks.len(), 3);
assert!(matches!(
result.tracks.as_ref()[0],
GridTrackSizing::Fr(100)
));
assert!(matches!(
result.tracks.as_ref()[1],
GridTrackSizing::Fr(200)
));
}
#[test]
fn test_parse_grid_template_fractional_fr() {
let result = parse_grid_template("0.5fr 1.5fr").unwrap();
assert_eq!(result.tracks.len(), 2);
assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(50)));
assert!(matches!(
result.tracks.as_ref()[1],
GridTrackSizing::Fr(150)
));
}
#[test]
fn test_parse_grid_template_auto() {
let result = parse_grid_template("auto 100px auto").unwrap();
assert_eq!(result.tracks.len(), 3);
assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Auto));
assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Auto));
}
#[test]
fn test_parse_grid_template_min_max_content() {
let result = parse_grid_template("min-content max-content auto").unwrap();
assert_eq!(result.tracks.len(), 3);
assert!(matches!(
result.tracks.as_ref()[0],
GridTrackSizing::MinContent
));
assert!(matches!(
result.tracks.as_ref()[1],
GridTrackSizing::MaxContent
));
}
#[test]
fn test_parse_grid_template_minmax() {
let result = parse_grid_template("minmax(100px, 1fr)").unwrap();
assert_eq!(result.tracks.len(), 1);
assert!(matches!(
result.tracks.as_ref()[0],
GridTrackSizing::MinMax(_)
));
}
#[test]
fn test_parse_grid_template_minmax_complex() {
let result = parse_grid_template("minmax(min-content, max-content)").unwrap();
assert_eq!(result.tracks.len(), 1);
}
#[test]
fn test_parse_grid_template_fit_content() {
let result = parse_grid_template("fit-content(200px)").unwrap();
assert_eq!(result.tracks.len(), 1);
assert!(matches!(
result.tracks.as_ref()[0],
GridTrackSizing::FitContent(_)
));
}
#[test]
fn test_parse_grid_template_mixed() {
let result = parse_grid_template("100px minmax(100px, 1fr) auto 2fr").unwrap();
assert_eq!(result.tracks.len(), 4);
}
#[test]
fn test_parse_grid_template_percent() {
let result = parse_grid_template("25% 50% 25%").unwrap();
assert_eq!(result.tracks.len(), 3);
}
#[test]
fn test_parse_grid_template_em_units() {
let result = parse_grid_template("10em 20em 1fr").unwrap();
assert_eq!(result.tracks.len(), 3);
}
#[test]
fn test_parse_grid_placement_auto() {
let result = parse_grid_placement("auto").unwrap();
assert!(matches!(result.grid_start, GridLine::Auto));
assert!(matches!(result.grid_end, GridLine::Auto));
}
#[test]
fn test_parse_grid_placement_line_number() {
let result = parse_grid_placement("1").unwrap();
assert!(matches!(result.grid_start, GridLine::Line(1)));
assert!(matches!(result.grid_end, GridLine::Auto));
}
#[test]
fn test_parse_grid_placement_negative_line() {
let result = parse_grid_placement("-1").unwrap();
assert!(matches!(result.grid_start, GridLine::Line(-1)));
}
#[test]
fn test_parse_grid_placement_span() {
let result = parse_grid_placement("span 2").unwrap();
assert!(matches!(result.grid_start, GridLine::Span(2)));
}
#[test]
fn test_parse_grid_placement_start_end() {
let result = parse_grid_placement("1 / 3").unwrap();
assert!(matches!(result.grid_start, GridLine::Line(1)));
assert!(matches!(result.grid_end, GridLine::Line(3)));
}
#[test]
fn test_parse_grid_placement_span_end() {
let result = parse_grid_placement("1 / span 2").unwrap();
assert!(matches!(result.grid_start, GridLine::Line(1)));
assert!(matches!(result.grid_end, GridLine::Span(2)));
}
#[test]
fn test_parse_grid_placement_named_line() {
let result = parse_grid_placement("header-start").unwrap();
assert!(matches!(result.grid_start, GridLine::Named(_)));
}
#[test]
fn test_parse_grid_placement_named_start_end() {
let result = parse_grid_placement("header-start / header-end").unwrap();
assert!(matches!(result.grid_start, GridLine::Named(_)));
assert!(matches!(result.grid_end, GridLine::Named(_)));
}
#[test]
fn test_parse_grid_template_whitespace() {
let result = parse_grid_template(" 100px 200px ").unwrap();
assert_eq!(result.tracks.len(), 2);
}
#[test]
fn test_parse_grid_placement_whitespace() {
let result = parse_grid_placement(" 1 / 3 ").unwrap();
assert!(matches!(result.grid_start, GridLine::Line(1)));
assert!(matches!(result.grid_end, GridLine::Line(3)));
}
#[test]
fn test_parse_grid_template_zero_fr() {
let result = parse_grid_template("0fr").unwrap();
assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(0)));
}
#[test]
fn test_parse_grid_placement_zero_line() {
let result = parse_grid_placement("0").unwrap();
assert!(matches!(result.grid_start, GridLine::Line(0)));
}
#[test]
fn test_parse_grid_template_repeat_fr() {
let result = parse_grid_template("repeat(3, 1fr)").unwrap();
assert_eq!(result.tracks.len(), 3);
assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(100)));
assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fr(100)));
}
#[test]
fn test_parse_grid_template_repeat_px() {
let result = parse_grid_template("repeat(2, 100px)").unwrap();
assert_eq!(result.tracks.len(), 2);
assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fixed(_)));
}
#[test]
fn test_parse_grid_template_repeat_multiple_tracks() {
let result = parse_grid_template("repeat(2, 100px 1fr)").unwrap();
assert_eq!(result.tracks.len(), 4);
assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fixed(_)));
assert!(matches!(result.tracks.as_ref()[3], GridTrackSizing::Fr(100)));
}
#[test]
fn test_parse_grid_template_repeat_with_other_tracks() {
let result = parse_grid_template("100px repeat(2, 1fr) auto").unwrap();
assert_eq!(result.tracks.len(), 4);
assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fr(100)));
assert!(matches!(result.tracks.as_ref()[3], GridTrackSizing::Auto));
}
#[test]
fn test_parse_grid_template_repeat_minmax() {
let result = parse_grid_template("repeat(3, minmax(100px, 1fr))").unwrap();
assert_eq!(result.tracks.len(), 3);
assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::MinMax(_)));
assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::MinMax(_)));
assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::MinMax(_)));
}
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GridAreaDefinition {
pub name: AzString,
pub row_start: u16,
pub row_end: u16,
pub column_start: u16,
pub column_end: u16,
}
impl_option!(
GridAreaDefinition,
OptionGridAreaDefinition,
copy = false,
[Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl_vec!(GridAreaDefinition, GridAreaDefinitionVec, GridAreaDefinitionVecDestructor, GridAreaDefinitionVecDestructorType, GridAreaDefinitionVecSlice, OptionGridAreaDefinition);
impl_vec_clone!(
GridAreaDefinition,
GridAreaDefinitionVec,
GridAreaDefinitionVecDestructor
);
impl_vec_debug!(GridAreaDefinition, GridAreaDefinitionVec);
impl_vec_partialeq!(GridAreaDefinition, GridAreaDefinitionVec);
impl_vec_eq!(GridAreaDefinition, GridAreaDefinitionVec);
impl_vec_partialord!(GridAreaDefinition, GridAreaDefinitionVec);
impl_vec_ord!(GridAreaDefinition, GridAreaDefinitionVec);
impl_vec_hash!(GridAreaDefinition, GridAreaDefinitionVec);
impl_vec_mut!(GridAreaDefinition, GridAreaDefinitionVec);
#[repr(C)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GridTemplateAreas {
pub areas: GridAreaDefinitionVec,
}
impl Default for GridTemplateAreas {
fn default() -> Self {
Self { areas: GridAreaDefinitionVec::from_vec(Vec::new()) }
}
}
impl PrintAsCssValue for GridTemplateAreas {
fn print_as_css_value(&self) -> String {
let areas_slice = self.areas.as_ref();
if areas_slice.is_empty() {
return "none".to_string();
}
let max_row = areas_slice.iter().map(|a| a.row_end).max().unwrap_or(1);
let max_col = areas_slice.iter().map(|a| a.column_end).max().unwrap_or(1);
let num_rows = (max_row - 1) as usize;
let num_cols = (max_col - 1) as usize;
let mut grid: Vec<Vec<String>> = vec![vec![".".to_string(); num_cols]; num_rows];
for area in areas_slice {
let row_start = area.row_start as usize - 1;
let row_end = area.row_end as usize - 1;
let col_start = area.column_start as usize - 1;
let col_end = area.column_end as usize - 1;
for row in grid.iter_mut().take(row_end).skip(row_start) {
for cell in row.iter_mut().take(col_end).skip(col_start) {
*cell = area.name.as_str().to_string();
}
}
}
grid.iter()
.map(|row| format!("\"{}\"", row.join(" ")))
.collect::<Vec<_>>()
.join(" ")
}
}
#[cfg(feature = "parser")]
pub fn parse_grid_template_areas(input: &str) -> Result<GridTemplateAreas, ()> {
use alloc::collections::BTreeMap;
let input = input.trim();
if input == "none" {
return Ok(GridTemplateAreas::default());
}
let mut rows: Vec<Vec<String>> = Vec::new();
let mut i = 0;
let bytes = input.as_bytes();
while i < bytes.len() {
if bytes[i] == b'"' || bytes[i] == b'\'' {
let quote = bytes[i];
i += 1;
let start = i;
while i < bytes.len() && bytes[i] != quote {
i += 1;
}
if i >= bytes.len() {
return Err(());
}
let row_str = &input[start..i];
let cells: Vec<String> = row_str.split_whitespace().map(std::string::ToString::to_string).collect();
if cells.is_empty() {
return Err(());
}
rows.push(cells);
}
i += 1;
}
if rows.is_empty() {
return Err(());
}
let num_cols = rows[0].len();
for row in &rows {
if row.len() != num_cols {
return Err(());
}
}
let mut area_map: BTreeMap<String, (usize, usize, usize, usize)> = BTreeMap::new();
for (row_idx, row) in rows.iter().enumerate() {
for (col_idx, cell) in row.iter().enumerate() {
if cell == "." {
continue; }
let entry = area_map.entry(cell.clone()).or_insert((row_idx, row_idx, col_idx, col_idx));
entry.0 = entry.0.min(row_idx);
entry.1 = entry.1.max(row_idx);
entry.2 = entry.2.min(col_idx);
entry.3 = entry.3.max(col_idx);
}
}
let mut areas = Vec::new();
for (name, (min_row, max_row, min_col, max_col)) in area_map {
areas.push(GridAreaDefinition {
name: name.into(),
row_start: u16::try_from(min_row + 1).unwrap_or(u16::MAX),
row_end: u16::try_from(max_row + 2).unwrap_or(u16::MAX), column_start: u16::try_from(min_col + 1).unwrap_or(u16::MAX),
column_end: u16::try_from(max_col + 2).unwrap_or(u16::MAX),
});
}
Ok(GridTemplateAreas { areas: GridAreaDefinitionVec::from_vec(areas) })
}