use super::types::GridTrack;
use crate::compute::common::alignment::{apply_alignment_fallback, compute_alignment_offset};
use crate::geometry::{InBothAbsAxis, Line, Point, Rect, Size};
use crate::style::{AlignContent, AlignItems, AlignSelf, AvailableSpace, CoreStyle, GridItemStyle, Overflow, Position};
use crate::tree::{Layout, LayoutPartialTreeExt, NodeId, SizingMode};
use crate::util::sys::f32_max;
use crate::util::{MaybeMath, MaybeResolve, ResolveOrZero};
#[cfg(feature = "content_size")]
use crate::compute::common::content_size::compute_content_size_contribution;
use crate::{BoxSizing, Direction, LayoutGridContainer};
pub(super) fn align_tracks(
grid_container_content_box_size: f32,
padding: Line<f32>,
border: Line<f32>,
tracks: &mut [GridTrack],
track_alignment_style: AlignContent,
axis_is_reversed: bool,
) {
let used_size: f32 = tracks.iter().map(|track| track.base_size).sum();
let free_space = grid_container_content_box_size - used_size;
let origin = padding.start + border.start;
let num_tracks = tracks.iter().skip(1).step_by(2).filter(|track| !track.is_collapsed).count();
let gap = 0.0;
let layout_is_reversed = false;
let is_safe = false; let track_alignment = apply_alignment_fallback(free_space, num_tracks, track_alignment_style, is_safe);
let track_alignment = if axis_is_reversed { track_alignment.reversed() } else { track_alignment };
let mut total_offset = origin;
let mut seen_non_collapsed_track = false;
tracks.iter_mut().enumerate().for_each(|(i, track)| {
let is_gutter = i % 2 == 0;
let is_non_collapsed_track = !is_gutter && !track.is_collapsed;
let is_first = is_non_collapsed_track && !seen_non_collapsed_track;
let offset = if is_non_collapsed_track {
compute_alignment_offset(free_space, num_tracks, gap, track_alignment, layout_is_reversed, is_first)
} else {
0.0
};
track.offset = total_offset + offset;
total_offset = total_offset + offset + track.base_size;
if is_non_collapsed_track {
seen_non_collapsed_track = true;
}
});
}
pub(super) fn align_and_position_item(
tree: &mut impl LayoutGridContainer,
node: NodeId,
order: u32,
grid_area: Rect<f32>,
container_alignment_styles: InBothAbsAxis<Option<AlignItems>>,
baseline_shim: f32,
direction: Direction,
) -> (Size<f32>, f32, f32) {
let grid_area_size = Size { width: grid_area.right - grid_area.left, height: grid_area.bottom - grid_area.top };
let style = tree.get_grid_child_style(node);
let overflow = style.overflow();
let scrollbar_width = style.scrollbar_width();
let aspect_ratio = style.aspect_ratio();
let justify_self = style.justify_self();
let align_self = style.align_self();
let position = style.position();
let inset_horizontal = style
.inset()
.horizontal_components()
.map(|size| size.resolve_to_option(grid_area_size.width, |val, basis| tree.calc(val, basis)));
let inset_vertical = style
.inset()
.vertical_components()
.map(|size| size.resolve_to_option(grid_area_size.height, |val, basis| tree.calc(val, basis)));
let padding =
style.padding().map(|p| p.resolve_or_zero(Some(grid_area_size.width), |val, basis| tree.calc(val, basis)));
let border =
style.border().map(|p| p.resolve_or_zero(Some(grid_area_size.width), |val, basis| tree.calc(val, basis)));
let padding_border_size = (padding + border).sum_axes();
let box_sizing_adjustment =
if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
let inherent_size = style
.size()
.maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let min_size = style
.min_size()
.maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
.maybe_add(box_sizing_adjustment)
.or(padding_border_size.map(Some))
.maybe_max(padding_border_size)
.maybe_apply_aspect_ratio(aspect_ratio);
let max_size = style
.max_size()
.maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let alignment_styles = InBothAbsAxis {
horizontal: justify_self.or(container_alignment_styles.horizontal).unwrap_or_else(|| {
if inherent_size.width.is_some() {
AlignSelf::Start
} else {
AlignSelf::Stretch
}
}),
vertical: align_self.or(container_alignment_styles.vertical).unwrap_or_else(|| {
if inherent_size.height.is_some() || aspect_ratio.is_some() {
AlignSelf::Start
} else {
AlignSelf::Stretch
}
}),
};
let margin =
style.margin().map(|margin| margin.resolve_to_option(grid_area_size.width, |val, basis| tree.calc(val, basis)));
let grid_area_minus_item_margins_size = Size {
width: grid_area_size.width.maybe_sub(margin.left).maybe_sub(margin.right),
height: grid_area_size.height.maybe_sub(margin.top).maybe_sub(margin.bottom) - baseline_shim,
};
let width = inherent_size.width.or_else(|| {
if position == Position::Absolute {
if let (Some(left), Some(right)) = (inset_horizontal.start, inset_horizontal.end) {
return Some(f32_max(grid_area_minus_item_margins_size.width - left - right, 0.0));
}
}
if margin.left.is_some()
&& margin.right.is_some()
&& alignment_styles.horizontal == AlignSelf::Stretch
&& position != Position::Absolute
{
return Some(grid_area_minus_item_margins_size.width);
}
None
});
let Size { width, height } = Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio);
let height = height.or_else(|| {
if position == Position::Absolute {
if let (Some(top), Some(bottom)) = (inset_vertical.start, inset_vertical.end) {
return Some(f32_max(grid_area_minus_item_margins_size.height - top - bottom, 0.0));
}
}
if margin.top.is_some()
&& margin.bottom.is_some()
&& alignment_styles.vertical == AlignSelf::Stretch
&& position != Position::Absolute
{
return Some(grid_area_minus_item_margins_size.height);
}
None
});
let Size { width, height } = Size { width, height }.maybe_apply_aspect_ratio(aspect_ratio);
let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
drop(style);
let size = if position == Position::Absolute && (width.is_none() || height.is_none()) {
tree.measure_child_size_both(
node,
Size { width, height },
grid_area_size.map(Option::Some),
grid_area_minus_item_margins_size.map(AvailableSpace::Definite),
SizingMode::InherentSize,
Line::FALSE,
)
.map(Some)
} else {
Size { width, height }
};
let layout_output = tree.perform_child_layout(
node,
size,
grid_area_size.map(Option::Some),
grid_area_minus_item_margins_size.map(AvailableSpace::Definite),
SizingMode::InherentSize,
Line::FALSE,
);
let Size { width, height } = size.unwrap_or(layout_output.size).maybe_clamp(min_size, max_size);
let (x, x_margin) = align_item_within_area(
Line { start: grid_area.left, end: grid_area.right },
justify_self.unwrap_or(alignment_styles.horizontal),
width,
position,
inset_horizontal,
margin.horizontal_components(),
0.0,
direction,
);
let (y, y_margin) = align_item_within_area(
Line { start: grid_area.top, end: grid_area.bottom },
align_self.unwrap_or(alignment_styles.vertical),
height,
position,
inset_vertical,
margin.vertical_components(),
baseline_shim,
Direction::Ltr,
);
let scrollbar_size = Size {
width: if overflow.y == Overflow::Scroll { scrollbar_width } else { 0.0 },
height: if overflow.x == Overflow::Scroll { scrollbar_width } else { 0.0 },
};
let resolved_margin = Rect { left: x_margin.start, right: x_margin.end, top: y_margin.start, bottom: y_margin.end };
tree.set_unrounded_layout(
node,
&Layout {
order,
location: Point { x, y },
size: Size { width, height },
#[cfg(feature = "content_size")]
content_size: layout_output.content_size,
scrollbar_size,
padding,
border,
margin: resolved_margin,
},
);
#[cfg(feature = "content_size")]
let contribution = compute_content_size_contribution(
Point { x: x - grid_area.left, y: y - grid_area.top },
Size { width, height },
layout_output.content_size,
overflow,
);
#[cfg(not(feature = "content_size"))]
let contribution = Size::ZERO;
(contribution, y, height)
}
#[allow(clippy::too_many_arguments)]
pub(super) fn align_item_within_area(
grid_area: Line<f32>,
alignment_style: AlignSelf,
resolved_size: f32,
position: Position,
inset: Line<Option<f32>>,
margin: Line<Option<f32>>,
baseline_shim: f32,
direction: Direction,
) -> (f32, Line<f32>) {
let non_auto_margin = Line { start: margin.start.unwrap_or(0.0) + baseline_shim, end: margin.end.unwrap_or(0.0) };
let grid_area_size = f32_max(grid_area.end - grid_area.start, 0.0);
let free_space = f32_max(grid_area_size - resolved_size - non_auto_margin.sum(), 0.0);
let auto_margin_count = margin.start.is_none() as u8 + margin.end.is_none() as u8;
let auto_margin_size = if auto_margin_count > 0 { free_space / auto_margin_count as f32 } else { 0.0 };
let resolved_margin = Line {
start: margin.start.unwrap_or(auto_margin_size) + baseline_shim,
end: margin.end.unwrap_or(auto_margin_size),
};
let alignment_based_offset = match alignment_style {
AlignSelf::Start | AlignSelf::FlexStart | AlignSelf::Baseline | AlignSelf::Stretch => {
if direction.is_rtl() {
grid_area_size - resolved_size - resolved_margin.end
} else {
resolved_margin.start
}
}
AlignSelf::End | AlignSelf::FlexEnd => {
if direction.is_rtl() {
resolved_margin.start
} else {
grid_area_size - resolved_size - resolved_margin.end
}
}
AlignSelf::Center => (grid_area_size - resolved_size + resolved_margin.start - resolved_margin.end) / 2.0,
};
let offset_within_area = if position == Position::Absolute {
match (inset.start, inset.end) {
(Some(start), Some(end)) => {
if direction.is_rtl() {
grid_area_size - end - resolved_size - non_auto_margin.end
} else {
start + non_auto_margin.start
}
}
(Some(start), None) => start + non_auto_margin.start,
(None, Some(end)) => grid_area_size - end - resolved_size - non_auto_margin.end,
(None, None) => alignment_based_offset,
}
} else {
alignment_based_offset
};
let mut start = grid_area.start + offset_within_area;
if position == Position::Relative {
let relative_inset = if direction.is_rtl() {
inset.end.map(|pos| -pos).or(inset.start)
} else {
inset.start.or(inset.end.map(|pos| -pos))
};
start += relative_inset.unwrap_or(0.0);
}
(start, resolved_margin)
}