use taffy::prelude::{Line, Size, TaffyGridLine, TaffyGridSpan, auto, fr, length, percent};
use taffy::style::{
CheapCloneStr, GridPlacement, GridTemplateComponent, MaxTrackSizingFunction,
MinTrackSizingFunction, RepetitionCount, TrackSizingFunction,
};
use taffy::style_helpers::{TaffyMaxContent, TaffyMinContent, fit_content, minmax, repeat};
use crate::style::{
AlignContent, AlignItems, Direction, Display, GridPlace, GridTemplate, JustifyContent, Length,
Overflow, Position, Repeat, Style, Track, TrackMax, TrackMin,
};
pub(crate) fn to_taffy(style: &Style, in_stack: bool) -> taffy::Style {
let mut out = taffy::Style {
display: match style.display {
Display::Flex => taffy::style::Display::Flex,
Display::Grid => taffy::style::Display::Grid,
Display::None => taffy::style::Display::None,
},
flex_direction: match style.direction {
Direction::Row => taffy::style::FlexDirection::Row,
Direction::Column => taffy::style::FlexDirection::Column,
},
flex_wrap: if style.wrap {
taffy::style::FlexWrap::Wrap
} else {
taffy::style::FlexWrap::NoWrap
},
align_items: Some(align_items(style.align_items)),
align_self: style.align_self.map(align_items),
justify_content: Some(match style.justify_content {
JustifyContent::Start => taffy::style::JustifyContent::FlexStart,
JustifyContent::Center => taffy::style::JustifyContent::Center,
JustifyContent::End => taffy::style::JustifyContent::FlexEnd,
JustifyContent::SpaceBetween => taffy::style::JustifyContent::SpaceBetween,
}),
align_content: Some(match style.align_content {
AlignContent::Start => taffy::style::AlignContent::FlexStart,
AlignContent::Center => taffy::style::AlignContent::Center,
AlignContent::End => taffy::style::AlignContent::FlexEnd,
AlignContent::Stretch => taffy::style::AlignContent::Stretch,
AlignContent::SpaceBetween => taffy::style::AlignContent::SpaceBetween,
}),
gap: Size {
width: length(finite(style.gap)),
height: length(finite(style.gap)),
},
padding: taffy::geometry::Rect {
left: length(finite(style.padding.left)),
right: length(finite(style.padding.right)),
top: length(finite(style.padding.top)),
bottom: length(finite(style.padding.bottom)),
},
margin: taffy::geometry::Rect {
left: length(finite(style.margin.left)),
right: length(finite(style.margin.right)),
top: length(finite(style.margin.top)),
bottom: length(finite(style.margin.bottom)),
},
inset: taffy::geometry::Rect {
left: inset(style.inset.left),
right: inset(style.inset.right),
top: inset(style.inset.top),
bottom: inset(style.inset.bottom),
},
position: match style.position {
Position::Relative | Position::Sticky => taffy::style::Position::Relative,
Position::Absolute => taffy::style::Position::Absolute,
},
size: Size {
width: dimension(style.width),
height: dimension(style.height),
},
min_size: Size {
width: dimension(style.min_width),
height: dimension(style.min_height),
},
max_size: Size {
width: dimension(style.max_width),
height: dimension(style.max_height),
},
flex_grow: style.flex_grow,
flex_shrink: style.flex_shrink,
flex_basis: dimension(style.flex_basis),
grid_template_columns: clamp_axis(&style.grid_template_columns),
grid_template_rows: clamp_axis(&style.grid_template_rows),
overflow: taffy::geometry::Point {
x: overflow(style.overflow_x),
y: overflow(style.overflow_y),
},
scrollbar_width: 0.0,
..Default::default()
};
if in_stack {
out.grid_row = taffy::prelude::line(1);
out.grid_column = taffy::prelude::line(1);
} else {
out.grid_row = grid_line(style.grid_row);
out.grid_column = grid_line(style.grid_column);
}
out
}
fn align_items(v: AlignItems) -> taffy::style::AlignItems {
match v {
AlignItems::Stretch => taffy::style::AlignItems::Stretch,
AlignItems::Start => taffy::style::AlignItems::FlexStart,
AlignItems::Center => taffy::style::AlignItems::Center,
AlignItems::End => taffy::style::AlignItems::FlexEnd,
AlignItems::Baseline => taffy::style::AlignItems::FlexStart,
}
}
const MAX_DIMENSION: f32 = 1.0e9;
fn finite(v: f32) -> f32 {
if v.is_finite() {
v.clamp(0.0, MAX_DIMENSION)
} else {
0.0
}
}
fn dimension(l: Length) -> taffy::style::Dimension {
match l {
Length::Px(v) if !v.is_finite() => auto(),
Length::Px(v) => length(v.clamp(0.0, MAX_DIMENSION)),
Length::Pct(v) => percent(finite(v) / 100.0),
Length::Ch(_) => auto(),
Length::Auto => auto(),
}
}
fn inset(v: Option<f32>) -> taffy::style::LengthPercentageAuto {
match v {
Some(v) if v.is_finite() => length(v.clamp(-MAX_DIMENSION, MAX_DIMENSION)),
Some(_) | None => auto(),
}
}
fn overflow(v: Overflow) -> taffy::style::Overflow {
match v {
Overflow::Visible => taffy::style::Overflow::Visible,
Overflow::Hidden => taffy::style::Overflow::Hidden,
Overflow::Scroll => taffy::style::Overflow::Scroll,
}
}
fn clamp_axis<S: CheapCloneStr>(templates: &[GridTemplate]) -> Vec<GridTemplateComponent<S>> {
let mut remaining = MAX_GRID_TRACKS;
let mut out = Vec::with_capacity(templates.len());
for g in templates {
match g {
GridTemplate::Single(t) => {
if remaining == 0 {
continue;
}
out.push(GridTemplateComponent::Single(track_sizing(*t)));
remaining -= 1;
}
GridTemplate::Repeat(Repeat::Count(n), tracks) => {
if remaining == 0 {
continue;
}
let per = u16::try_from(tracks.len())
.unwrap_or(u16::MAX)
.clamp(1, remaining);
let sized: Vec<_> = tracks
.iter()
.take(per.into())
.map(|t| track_sizing(*t))
.collect();
let max_reps = remaining / per; let realized = (*n).min(max_reps);
out.push(repeat(RepetitionCount::Count(realized), sized));
remaining -= realized * per;
}
GridTemplate::Repeat(rep, tracks) => {
let per = tracks.len().min(usize::from(MAX_GRID_TRACKS)).max(1);
let sized: Vec<_> = tracks.iter().take(per).map(|t| track_sizing(*t)).collect();
let count = match rep {
Repeat::AutoFit => RepetitionCount::AutoFit,
Repeat::AutoFill => RepetitionCount::AutoFill,
Repeat::Count(_) => unreachable!("handled above"),
};
out.push(repeat(count, sized));
}
}
}
out
}
fn track_sizing(t: Track) -> TrackSizingFunction {
match t {
Track::Px(v) => length(finite(v)),
Track::Fr(f) => fr(finite(f)),
Track::Auto => auto(),
Track::MinContent => TrackSizingFunction::MIN_CONTENT,
Track::MaxContent => TrackSizingFunction::MAX_CONTENT,
Track::FitContent(v) => fit_content(length(finite(v))),
Track::MinMax(mn, mx) => minmax(track_min(mn), track_max(mx)),
}
}
fn track_min(m: TrackMin) -> MinTrackSizingFunction {
match m {
TrackMin::Px(v) => length(finite(v)),
TrackMin::Auto => auto(),
TrackMin::MinContent => MinTrackSizingFunction::MIN_CONTENT,
TrackMin::MaxContent => MinTrackSizingFunction::MAX_CONTENT,
}
}
fn track_max(m: TrackMax) -> MaxTrackSizingFunction {
match m {
TrackMax::Px(v) => length(finite(v)),
TrackMax::Fr(f) => fr(finite(f)),
TrackMax::Auto => auto(),
TrackMax::MinContent => MaxTrackSizingFunction::MIN_CONTENT,
TrackMax::MaxContent => MaxTrackSizingFunction::MAX_CONTENT,
TrackMax::FitContent(v) => fit_content(length(finite(v))),
}
}
const MAX_GRID_TRACKS: u16 = 1024;
fn grid_line(p: GridPlace) -> Line<GridPlacement> {
match (p.start, p.span) {
(None, None) => Line {
start: GridPlacement::Auto,
end: GridPlacement::Auto,
},
(Some(s), None) => taffy::prelude::line(s),
(None, Some(n)) => taffy::prelude::span(n),
(Some(s), Some(n)) => Line {
start: GridPlacement::from_line_index(s),
end: GridPlacement::from_span(n),
},
}
}