use alloc::vec::Vec;
use float_pigment_css::length_num::LengthNum;
use crate::{
algo::grid::track::GridTracks, types::MinMax, DefLength, LayoutGridAuto, LayoutTrackListItem,
LayoutTrackSize, LayoutTreeNode, OptionNum, OptionSize, Size,
};
use super::matrix::GridLayoutMatrix;
fn resolve_fr_track_sizes<L: LengthNum + Copy>(
tracks: &mut [TrackInfo<L>],
available_space: OptionNum<L>,
) {
let total_fr: f32 = tracks
.iter()
.filter(|t| t.track_type == IntrinsicTrackType::Fr)
.map(|t| t.fr_value)
.sum();
if total_fr <= 0.0 {
return;
}
match available_space.val() {
Some(available) => {
resolve_fr_definite(tracks, available);
}
None => {
resolve_fr_indefinite(tracks);
}
}
}
fn resolve_fr_definite<L: LengthNum + Copy>(tracks: &mut [TrackInfo<L>], available: L) {
let total_non_fr_size: L = tracks
.iter()
.filter(|t| t.track_type != IntrinsicTrackType::Fr)
.fold(L::zero(), |acc, t| acc + t.base_size.unwrap_or(L::zero()));
let initial_remaining = if available > total_non_fr_size {
available - total_non_fr_size
} else {
L::zero()
};
let mut remaining_space = initial_remaining;
let mut active_flex: f32 = tracks
.iter()
.filter(|t| t.track_type == IntrinsicTrackType::Fr)
.map(|t| t.fr_value)
.sum();
let mut flexible_indices: Vec<usize> = (0..tracks.len())
.filter(|&i| tracks[i].track_type == IntrinsicTrackType::Fr)
.collect();
if flexible_indices.is_empty() {
return;
}
loop {
if active_flex <= 0.0 {
break;
}
let hypothetical_fr_size = remaining_space.div_f32(active_flex);
let mut any_frozen = false;
flexible_indices.retain(|&i| {
let hypothetical_size = hypothetical_fr_size.mul_f32(tracks[i].fr_value);
if hypothetical_size < tracks[i].min_content {
tracks[i].base_size = Some(tracks[i].min_content);
remaining_space -= tracks[i].min_content;
active_flex -= tracks[i].fr_value;
any_frozen = true;
false
} else {
true
}
});
if !any_frozen {
for &i in &flexible_indices {
let fr_size = hypothetical_fr_size.mul_f32(tracks[i].fr_value);
tracks[i].base_size = Some(fr_size);
}
break;
}
}
}
fn resolve_fr_indefinite<L: LengthNum + Copy>(tracks: &mut [TrackInfo<L>]) {
let mut hypothetical_1fr = L::zero();
for track in tracks.iter() {
if track.track_type == IntrinsicTrackType::Fr && track.fr_value > 0.0 {
let candidate = track.max_content.div_f32(track.fr_value);
if candidate > hypothetical_1fr {
hypothetical_1fr = candidate;
}
}
}
for track in tracks.iter_mut() {
if track.track_type == IntrinsicTrackType::Fr {
let fr_size = hypothetical_1fr.mul_f32(track.fr_value);
track.base_size = Some(fr_size.max(track.min_content));
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum IntrinsicTrackType {
Fixed,
Auto,
MinContent,
MaxContent,
Fr,
}
pub(crate) struct TrackInfo<L: LengthNum> {
pub(crate) base_size: Option<L>,
pub(crate) growth_limit: Option<L>,
pub(crate) fr_value: f32,
pub(crate) min_content: L,
pub(crate) max_content: L,
pub(crate) track_type: IntrinsicTrackType,
}
impl<L: LengthNum + Copy> TrackInfo<L> {
fn new() -> Self {
Self {
base_size: None,
growth_limit: None,
fr_value: 0.0,
min_content: L::zero(),
max_content: L::zero(),
track_type: IntrinsicTrackType::Auto,
}
}
}
pub(crate) fn classify_track_type<L: LengthNum, C: PartialEq + Clone>(
track_size: &LayoutTrackSize<L, C>,
) -> IntrinsicTrackType {
match track_size {
LayoutTrackSize::Length(DefLength::Points(_) | DefLength::Percent(_)) => {
IntrinsicTrackType::Fixed
}
LayoutTrackSize::Length(_) => IntrinsicTrackType::Auto,
LayoutTrackSize::Fr(_) => IntrinsicTrackType::Fr,
LayoutTrackSize::MinContent => IntrinsicTrackType::MinContent,
LayoutTrackSize::MaxContent => IntrinsicTrackType::MaxContent,
}
}
impl IntrinsicTrackType {
#[inline]
pub(crate) fn needs_min_content(self) -> bool {
self != IntrinsicTrackType::Fixed
}
#[inline]
pub(crate) fn needs_max_content(self) -> bool {
matches!(
self,
IntrinsicTrackType::Auto | IntrinsicTrackType::MaxContent | IntrinsicTrackType::Fr
)
}
}
pub(crate) fn classify_track_at_index<L: LengthNum, C: PartialEq + Clone>(
index: usize,
explicit_track_list: &[&LayoutTrackListItem<L, C>],
grid_auto_tracks: &LayoutGridAuto<L, C>,
) -> IntrinsicTrackType {
if let Some(track_item) = explicit_track_list.get(index) {
match track_item {
LayoutTrackListItem::TrackSize(track_size) => classify_track_type(track_size),
_ => IntrinsicTrackType::Fixed,
}
} else {
let implicit_index = index - explicit_track_list.len();
classify_track_type(&grid_auto_tracks.get(implicit_index))
}
}
fn init_track_infos<L: LengthNum + Copy, C: PartialEq + Clone>(
track_count: usize,
explicit_track_list: &[&LayoutTrackListItem<L, C>],
grid_auto_tracks: &LayoutGridAuto<L, C>,
) -> Vec<TrackInfo<L>> {
let explicit_count = explicit_track_list.len();
(0..track_count)
.map(|i| {
let mut info = TrackInfo::new();
if let Some(LayoutTrackListItem::TrackSize(track_size)) = explicit_track_list.get(i) {
info.track_type = classify_track_type(track_size);
if let LayoutTrackSize::Fr(fr_value) = track_size {
info.fr_value = *fr_value;
}
} else if i >= explicit_count {
let implicit_index = i - explicit_count;
let implicit_track_size = grid_auto_tracks.get(implicit_index);
info.track_type = classify_track_type(&implicit_track_size);
if let LayoutTrackSize::Fr(fr_value) = implicit_track_size {
info.fr_value = fr_value;
}
}
info
})
.collect()
}
fn update_track_intrinsic_sizes<L: LengthNum + Copy>(
track: &mut TrackInfo<L>,
outer_min_content: L,
effective_min_content: L,
outer_max_content: L,
fixed_track_size: OptionNum<L>,
) {
track.min_content = track.min_content.max(outer_min_content);
track.max_content = track.max_content.max(outer_max_content);
if track.track_type == IntrinsicTrackType::Fr {
return;
}
match track.track_type {
IntrinsicTrackType::Fixed => {
if fixed_track_size.is_some() {
let size = fixed_track_size.val().unwrap();
track.base_size = Some(track.base_size.map(|s| s.max(size)).unwrap_or(size));
track.growth_limit = track.base_size;
}
}
IntrinsicTrackType::Auto => {
track.base_size = Some(
track
.base_size
.map_or(effective_min_content, |s| s.max(effective_min_content)),
);
track.growth_limit = None;
}
IntrinsicTrackType::MinContent => {
track.base_size = Some(
track
.base_size
.map(|s| s.max(effective_min_content))
.unwrap_or(effective_min_content),
);
track.growth_limit = Some(
track
.growth_limit
.map(|s| s.max(effective_min_content))
.unwrap_or(effective_min_content),
);
}
IntrinsicTrackType::MaxContent => {
track.base_size = Some(
track
.base_size
.map(|s| s.max(effective_min_content))
.unwrap_or(effective_min_content),
);
track.growth_limit = Some(
track
.growth_limit
.map(|s| s.max(outer_max_content))
.unwrap_or(outer_max_content),
);
}
IntrinsicTrackType::Fr => {}
}
}
fn resolve_fixed_track_size<T: LayoutTreeNode>(
track_index: usize,
explicit_count: usize,
item_track_size: OptionNum<T::Length>,
grid_auto_tracks: &LayoutGridAuto<T::Length, T::LengthCustom>,
available_space: OptionNum<T::Length>,
node: &T,
) -> OptionNum<T::Length> {
if track_index < explicit_count {
item_track_size
} else {
let implicit_index = track_index - explicit_count;
match grid_auto_tracks.get(implicit_index) {
LayoutTrackSize::Length(def_len) => def_len.resolve(available_space, node),
_ => OptionNum::none(),
}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute_track_sizes<T: LayoutTreeNode>(
grid_layout_matrix: &GridLayoutMatrix<T>,
column_track_list: &[&LayoutTrackListItem<T::Length, T::LengthCustom>],
row_track_list: &[&LayoutTrackListItem<T::Length, T::LengthCustom>],
available_grid_space: OptionSize<T::Length>,
grid_auto_columns: &LayoutGridAuto<T::Length, T::LengthCustom>,
grid_auto_rows: &LayoutGridAuto<T::Length, T::LengthCustom>,
) -> (GridTracks<T>, GridTracks<T>) {
let explicit_column_count = column_track_list.len();
let explicit_row_count = row_track_list.len();
let mut columns = init_track_infos(
grid_layout_matrix.column_count(),
column_track_list,
grid_auto_columns,
);
let mut rows = init_track_infos(
grid_layout_matrix.row_count(),
row_track_list,
grid_auto_rows,
);
for item in grid_layout_matrix.items() {
let row = item.row();
let column = item.column();
let css_width = item.css_size.width;
let css_height = item.css_size.height;
let min_content_size = item.min_content_size().copied().unwrap_or(Size::zero());
let min_content_width = if css_width.is_some() {
css_width.val().unwrap().max(min_content_size.width)
} else {
min_content_size.width
};
let outer_min_content_width = min_content_width + item.margin.horizontal();
let min_content_height = if css_height.is_some() {
css_height.val().unwrap().max(min_content_size.height)
} else {
min_content_size.height
};
let outer_min_content_height = min_content_height + item.margin.vertical();
let max_content_size = item.max_content_size().copied();
let max_content_width = if css_width.is_some() {
css_width.val().unwrap()
} else {
max_content_size.map_or(item.computed_size().width, |s| s.width)
};
let outer_max_content_width = max_content_width + item.margin.horizontal();
let max_content_height = if css_height.is_some() {
css_height.val().unwrap()
} else {
item.computed_size().height
};
let outer_max_content_height = max_content_height + item.margin.vertical();
let effective_min_content_width = outer_min_content_width.min(outer_max_content_width);
let effective_min_content_height = outer_min_content_height.min(outer_max_content_height);
let col_fixed_size = resolve_fixed_track_size::<T>(
column,
explicit_column_count,
item.track_inline_size(),
grid_auto_columns,
available_grid_space.width,
item.node,
);
let row_fixed_size = resolve_fixed_track_size::<T>(
row,
explicit_row_count,
item.track_block_size(),
grid_auto_rows,
available_grid_space.height,
item.node,
);
update_track_intrinsic_sizes(
&mut columns[column],
outer_min_content_width,
effective_min_content_width,
outer_max_content_width,
col_fixed_size,
);
update_track_intrinsic_sizes(
&mut rows[row],
outer_min_content_height,
effective_min_content_height,
outer_max_content_height,
row_fixed_size,
);
}
resolve_fr_track_sizes(&mut columns, available_grid_space.width);
resolve_fr_track_sizes(&mut rows, available_grid_space.height);
(
GridTracks::from_track_info(&columns),
GridTracks::from_track_info(&rows),
)
}
#[cfg(test)]
mod tests {
use super::*;
fn fixed_track(base: f32) -> TrackInfo<f32> {
TrackInfo {
base_size: Some(base),
growth_limit: Some(base),
fr_value: 0.0,
min_content: 0.0,
max_content: 0.0,
track_type: IntrinsicTrackType::Fixed,
}
}
fn fr_track(fr: f32, min_content: f32) -> TrackInfo<f32> {
TrackInfo {
base_size: None,
growth_limit: None,
fr_value: fr,
min_content,
max_content: 0.0,
track_type: IntrinsicTrackType::Fr,
}
}
fn fr_track_with_max(fr: f32, min_content: f32, max_content: f32) -> TrackInfo<f32> {
TrackInfo {
base_size: None,
growth_limit: None,
fr_value: fr,
min_content,
max_content,
track_type: IntrinsicTrackType::Fr,
}
}
#[test]
fn fr_single_track_takes_all_remaining_space() {
let mut tracks = vec![fixed_track(100.0), fr_track(1.0, 0.0)];
resolve_fr_track_sizes(&mut tracks, OptionNum::some(400.0));
assert_eq!(tracks[0].base_size, Some(100.0));
assert_eq!(tracks[1].base_size, Some(300.0));
}
#[test]
fn fr_multiple_tracks_distribute_proportionally() {
let mut tracks = vec![fr_track(1.0, 0.0), fr_track(2.0, 0.0), fr_track(1.0, 0.0)];
resolve_fr_track_sizes(&mut tracks, OptionNum::some(400.0));
assert_eq!(tracks[0].base_size, Some(100.0));
assert_eq!(tracks[1].base_size, Some(200.0));
assert_eq!(tracks[2].base_size, Some(100.0));
}
#[test]
fn fr_with_fixed_tracks_subtracts_fixed_first() {
let mut tracks = vec![
fixed_track(50.0),
fr_track(1.0, 0.0),
fixed_track(50.0),
fr_track(2.0, 0.0),
];
resolve_fr_track_sizes(&mut tracks, OptionNum::some(350.0));
assert_eq!(tracks[0].base_size, Some(50.0)); let fr_unit = 250.0 / 3.0;
assert!((tracks[1].base_size.unwrap() - fr_unit).abs() < 0.01);
assert_eq!(tracks[2].base_size, Some(50.0)); assert!((tracks[3].base_size.unwrap() - fr_unit * 2.0).abs() < 0.01);
}
#[test]
fn fr_freeze_at_min_content() {
let mut tracks = vec![fr_track(1.0, 80.0), fr_track(1.0, 0.0)];
resolve_fr_track_sizes(&mut tracks, OptionNum::some(100.0));
assert_eq!(tracks[0].base_size, Some(80.0));
assert_eq!(tracks[1].base_size, Some(20.0));
}
#[test]
fn fr_all_frozen_at_min_content() {
let mut tracks = vec![fr_track(1.0, 60.0), fr_track(1.0, 60.0)];
resolve_fr_track_sizes(&mut tracks, OptionNum::some(100.0));
assert_eq!(tracks[0].base_size, Some(60.0));
assert_eq!(tracks[1].base_size, Some(60.0));
}
#[test]
fn fr_no_available_space_returns_zero() {
let mut tracks = vec![fr_track(1.0, 0.0), fr_track(2.0, 0.0)];
resolve_fr_track_sizes(&mut tracks, OptionNum::some(0.0));
assert_eq!(tracks[0].base_size, Some(0.0));
assert_eq!(tracks[1].base_size, Some(0.0));
}
#[test]
fn fr_indefinite_container_uses_max_content() {
let mut tracks = vec![
fr_track_with_max(1.0, 0.0, 100.0),
fr_track_with_max(2.0, 0.0, 200.0),
];
resolve_fr_track_sizes(&mut tracks, OptionNum::none());
assert_eq!(tracks[0].base_size, Some(100.0));
assert_eq!(tracks[1].base_size, Some(200.0));
}
#[test]
fn fr_indefinite_picks_largest_hypothetical() {
let mut tracks = vec![
fr_track_with_max(1.0, 0.0, 150.0),
fr_track_with_max(2.0, 0.0, 200.0),
];
resolve_fr_track_sizes(&mut tracks, OptionNum::none());
assert_eq!(tracks[0].base_size, Some(150.0));
assert_eq!(tracks[1].base_size, Some(300.0));
}
#[test]
fn fr_indefinite_clamps_to_min_content() {
let mut tracks = vec![
fr_track_with_max(1.0, 80.0, 50.0),
fr_track_with_max(1.0, 0.0, 50.0),
];
resolve_fr_track_sizes(&mut tracks, OptionNum::none());
assert_eq!(tracks[0].base_size, Some(80.0));
assert_eq!(tracks[1].base_size, Some(50.0));
}
#[test]
fn fr_indefinite_zero_max_content() {
let mut tracks = vec![
fr_track_with_max(1.0, 0.0, 0.0),
fr_track_with_max(2.0, 0.0, 0.0),
];
resolve_fr_track_sizes(&mut tracks, OptionNum::none());
assert_eq!(tracks[0].base_size, Some(0.0));
assert_eq!(tracks[1].base_size, Some(0.0));
}
#[test]
fn fr_zero_total_fr_is_noop() {
let mut tracks = vec![fixed_track(100.0)];
resolve_fr_track_sizes(&mut tracks, OptionNum::some(400.0));
assert_eq!(tracks[0].base_size, Some(100.0)); }
#[test]
fn fr_fixed_consumes_all_space_fr_gets_zero() {
let mut tracks = vec![fixed_track(300.0), fr_track(1.0, 0.0)];
resolve_fr_track_sizes(&mut tracks, OptionNum::some(300.0));
assert_eq!(tracks[0].base_size, Some(300.0));
assert_eq!(tracks[1].base_size, Some(0.0));
}
#[test]
fn classify_track_type_fixed_points() {
let ts: LayoutTrackSize<f32, i32> = LayoutTrackSize::Length(DefLength::Points(100.0));
assert_eq!(
classify_track_type::<f32, i32>(&ts),
IntrinsicTrackType::Fixed
);
}
#[test]
fn classify_track_type_fixed_percent() {
let ts: LayoutTrackSize<f32, i32> = LayoutTrackSize::Length(DefLength::Percent(0.5));
assert_eq!(
classify_track_type::<f32, i32>(&ts),
IntrinsicTrackType::Fixed
);
}
#[test]
fn classify_track_type_auto() {
let ts: LayoutTrackSize<f32, i32> = LayoutTrackSize::Length(DefLength::Auto);
assert_eq!(
classify_track_type::<f32, i32>(&ts),
IntrinsicTrackType::Auto
);
}
#[test]
fn classify_track_type_fr() {
let ts: LayoutTrackSize<f32, i32> = LayoutTrackSize::Fr(1.0);
assert_eq!(classify_track_type::<f32, i32>(&ts), IntrinsicTrackType::Fr);
}
#[test]
fn classify_track_type_min_content() {
let ts: LayoutTrackSize<f32, i32> = LayoutTrackSize::MinContent;
assert_eq!(
classify_track_type::<f32, i32>(&ts),
IntrinsicTrackType::MinContent
);
}
#[test]
fn classify_track_type_max_content() {
let ts: LayoutTrackSize<f32, i32> = LayoutTrackSize::MaxContent;
assert_eq!(
classify_track_type::<f32, i32>(&ts),
IntrinsicTrackType::MaxContent
);
}
}