1use super::types::GridTrack;
3use crate::compute::common::alignment::{apply_alignment_fallback, compute_alignment_offset};
4use crate::geometry::{InBothAbsAxis, Line, Point, Rect, Size};
5use crate::style::{
6 AlignContent, AlignItems, AlignItemsKeyword, AlignSelf, AvailableSpace, CoreStyle, GridItemStyle, Overflow,
7 Position,
8};
9use crate::tree::{Layout, LayoutPartialTreeExt, NodeId, SizingMode};
10use crate::util::sys::f32_max;
11use crate::util::{MaybeMath, MaybeResolve, ResolveOrZero};
12
13#[cfg(feature = "content_size")]
14use crate::compute::common::content_size::compute_content_size_contribution;
15use crate::{BoxSizing, Direction, LayoutGridContainer};
16
17pub(super) fn align_tracks(
21 grid_container_content_box_size: f32,
22 padding: Line<f32>,
23 border: Line<f32>,
24 tracks: &mut [GridTrack],
25 track_alignment_style: AlignContent,
26 axis_is_reversed: bool,
27) {
28 let used_size: f32 = tracks.iter().map(|track| track.base_size).sum();
29 let free_space = grid_container_content_box_size - used_size;
30 let origin = padding.start + border.start;
31
32 let num_tracks = tracks.iter().skip(1).step_by(2).filter(|track| !track.is_collapsed).count();
34
35 let gap = 0.0;
38 let layout_is_reversed = false;
39 let track_alignment = apply_alignment_fallback(free_space, num_tracks, track_alignment_style);
40 let track_alignment = if axis_is_reversed { track_alignment.reversed() } else { track_alignment };
41
42 let mut total_offset = origin;
44 let mut seen_non_collapsed_track = false;
45 tracks.iter_mut().enumerate().for_each(|(i, track)| {
46 let is_gutter = i % 2 == 0;
48 let is_non_collapsed_track = !is_gutter && !track.is_collapsed;
49
50 let is_first = is_non_collapsed_track && !seen_non_collapsed_track;
52
53 let offset = if is_non_collapsed_track {
54 compute_alignment_offset(free_space, num_tracks, gap, track_alignment, layout_is_reversed, is_first)
55 } else {
56 0.0
57 };
58
59 track.offset = total_offset + offset;
60 total_offset = total_offset + offset + track.base_size;
61 if is_non_collapsed_track {
62 seen_non_collapsed_track = true;
63 }
64 });
65}
66
67pub(super) fn align_and_position_item(
69 tree: &mut impl LayoutGridContainer,
70 node: NodeId,
71 order: u32,
72 grid_area: Rect<f32>,
73 container_alignment_styles: InBothAbsAxis<Option<AlignItems>>,
74 baseline_shim: f32,
75 direction: Direction,
76) -> (Size<f32>, f32, f32) {
77 let grid_area_size = Size { width: grid_area.right - grid_area.left, height: grid_area.bottom - grid_area.top };
78
79 let style = tree.get_grid_child_style(node);
80
81 let overflow = style.overflow();
82 let scrollbar_width = style.scrollbar_width();
83 let aspect_ratio = style.aspect_ratio();
84 let justify_self = style.justify_self();
85 let align_self = style.align_self();
86
87 let position = style.position();
88 let inset_horizontal = style
89 .inset()
90 .horizontal_components()
91 .map(|size| size.resolve_to_option(grid_area_size.width, |val, basis| tree.calc(val, basis)));
92 let inset_vertical = style
93 .inset()
94 .vertical_components()
95 .map(|size| size.resolve_to_option(grid_area_size.height, |val, basis| tree.calc(val, basis)));
96 let padding =
97 style.padding().map(|p| p.resolve_or_zero(Some(grid_area_size.width), |val, basis| tree.calc(val, basis)));
98 let border =
99 style.border().map(|p| p.resolve_or_zero(Some(grid_area_size.width), |val, basis| tree.calc(val, basis)));
100 let padding_border_size = (padding + border).sum_axes();
101
102 let box_sizing_adjustment =
103 if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
104
105 let inherent_size = style
106 .size()
107 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
108 .maybe_apply_aspect_ratio(aspect_ratio)
109 .maybe_add(box_sizing_adjustment);
110 let min_size = style
111 .min_size()
112 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
113 .maybe_add(box_sizing_adjustment)
114 .or(padding_border_size.map(Some))
115 .maybe_max(padding_border_size)
116 .maybe_apply_aspect_ratio(aspect_ratio);
117 let max_size = style
118 .max_size()
119 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
120 .maybe_apply_aspect_ratio(aspect_ratio)
121 .maybe_add(box_sizing_adjustment);
122
123 let alignment_styles = InBothAbsAxis {
128 horizontal: justify_self.or(container_alignment_styles.horizontal).unwrap_or_else(|| {
129 if inherent_size.width.is_some() {
130 AlignSelf::START
131 } else {
132 AlignSelf::STRETCH
133 }
134 }),
135 vertical: align_self.or(container_alignment_styles.vertical).unwrap_or_else(|| {
136 if inherent_size.height.is_some() || aspect_ratio.is_some() {
137 AlignSelf::START
138 } else {
139 AlignSelf::STRETCH
140 }
141 }),
142 };
143
144 let margin =
147 style.margin().map(|margin| margin.resolve_to_option(grid_area_size.width, |val, basis| tree.calc(val, basis)));
148
149 let grid_area_minus_item_margins_size = Size {
150 width: grid_area_size.width.maybe_sub(margin.left).maybe_sub(margin.right),
151 height: grid_area_size.height.maybe_sub(margin.top).maybe_sub(margin.bottom) - baseline_shim,
152 };
153
154 let width = inherent_size.width.or_else(|| {
157 if position == Position::Absolute {
160 if let (Some(left), Some(right)) = (inset_horizontal.start, inset_horizontal.end) {
161 return Some(f32_max(grid_area_minus_item_margins_size.width - left - right, 0.0));
162 }
163 }
164
165 if margin.left.is_some()
170 && margin.right.is_some()
171 && alignment_styles.horizontal == AlignSelf::STRETCH
172 && position != Position::Absolute
173 {
174 return Some(grid_area_minus_item_margins_size.width);
175 }
176
177 None
178 });
179
180 let Size { width, height } = Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio);
182
183 let height = height.or_else(|| {
184 if position == Position::Absolute {
185 if let (Some(top), Some(bottom)) = (inset_vertical.start, inset_vertical.end) {
186 return Some(f32_max(grid_area_minus_item_margins_size.height - top - bottom, 0.0));
187 }
188 }
189
190 if margin.top.is_some()
195 && margin.bottom.is_some()
196 && alignment_styles.vertical == AlignSelf::STRETCH
197 && position != Position::Absolute
198 {
199 return Some(grid_area_minus_item_margins_size.height);
200 }
201
202 None
203 });
204 let Size { width, height } = Size { width, height }.maybe_apply_aspect_ratio(aspect_ratio);
206
207 let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
209
210 drop(style);
212
213 let size = if position == Position::Absolute && (width.is_none() || height.is_none()) {
214 tree.measure_child_size_both(
215 node,
216 Size { width, height },
217 grid_area_size.map(Option::Some),
218 grid_area_minus_item_margins_size.map(AvailableSpace::Definite),
219 SizingMode::InherentSize,
220 Line::FALSE,
221 )
222 .map(Some)
223 } else {
224 Size { width, height }
225 };
226
227 let layout_output = tree.perform_child_layout(
228 node,
229 size,
230 grid_area_size.map(Option::Some),
231 grid_area_minus_item_margins_size.map(AvailableSpace::Definite),
232 SizingMode::InherentSize,
233 Line::FALSE,
234 );
235
236 let Size { width, height } = size.unwrap_or(layout_output.size).maybe_clamp(min_size, max_size);
238
239 let (x, x_margin) = align_item_within_area(
240 Line { start: grid_area.left, end: grid_area.right },
241 justify_self.unwrap_or(alignment_styles.horizontal),
242 width,
243 position,
244 inset_horizontal,
245 margin.horizontal_components(),
246 0.0,
247 direction,
248 );
249 let (y, y_margin) = align_item_within_area(
250 Line { start: grid_area.top, end: grid_area.bottom },
251 align_self.unwrap_or(alignment_styles.vertical),
252 height,
253 position,
254 inset_vertical,
255 margin.vertical_components(),
256 baseline_shim,
257 Direction::Ltr,
258 );
259
260 let scrollbar_size = Size {
261 width: if overflow.y == Overflow::Scroll { scrollbar_width } else { 0.0 },
262 height: if overflow.x == Overflow::Scroll { scrollbar_width } else { 0.0 },
263 };
264
265 let resolved_margin = Rect { left: x_margin.start, right: x_margin.end, top: y_margin.start, bottom: y_margin.end };
266
267 tree.set_unrounded_layout(
268 node,
269 &Layout {
270 order,
271 location: Point { x, y },
272 size: Size { width, height },
273 #[cfg(feature = "content_size")]
274 content_size: layout_output.content_size,
275 scrollbar_size,
276 padding,
277 border,
278 margin: resolved_margin,
279 },
280 );
281
282 #[cfg(feature = "content_size")]
283 let contribution = compute_content_size_contribution(
284 Point { x: x - grid_area.left, y: y - grid_area.top },
285 Size { width, height },
286 layout_output.content_size,
287 overflow,
288 );
289 #[cfg(not(feature = "content_size"))]
290 let contribution = Size::ZERO;
291
292 (contribution, y, height)
293}
294
295#[allow(clippy::too_many_arguments)]
297pub(super) fn align_item_within_area(
298 grid_area: Line<f32>,
299 alignment_style: AlignSelf,
300 resolved_size: f32,
301 position: Position,
302 inset: Line<Option<f32>>,
303 margin: Line<Option<f32>>,
304 baseline_shim: f32,
305 direction: Direction,
306) -> (f32, Line<f32>) {
307 let non_auto_margin = Line { start: margin.start.unwrap_or(0.0) + baseline_shim, end: margin.end.unwrap_or(0.0) };
309 let grid_area_size = f32_max(grid_area.end - grid_area.start, 0.0);
310 let free_space = f32_max(grid_area_size - resolved_size - non_auto_margin.sum(), 0.0);
311
312 let auto_margin_count = margin.start.is_none() as u8 + margin.end.is_none() as u8;
314 let auto_margin_size = if auto_margin_count > 0 { free_space / auto_margin_count as f32 } else { 0.0 };
315 let resolved_margin = Line {
316 start: margin.start.unwrap_or(auto_margin_size) + baseline_shim,
317 end: margin.end.unwrap_or(auto_margin_size),
318 };
319
320 let overflows = resolved_size + non_auto_margin.sum() > grid_area_size;
325 let alignment_keyword =
326 if alignment_style.is_safe() && overflows { AlignItemsKeyword::Start } else { alignment_style.keyword() };
327
328 let alignment_based_offset = match alignment_keyword {
330 AlignItemsKeyword::Start
332 | AlignItemsKeyword::FlexStart
333 | AlignItemsKeyword::Baseline
334 | AlignItemsKeyword::Stretch => {
335 if direction.is_rtl() {
336 grid_area_size - resolved_size - resolved_margin.end
337 } else {
338 resolved_margin.start
339 }
340 }
341 AlignItemsKeyword::End | AlignItemsKeyword::FlexEnd => {
342 if direction.is_rtl() {
343 resolved_margin.start
344 } else {
345 grid_area_size - resolved_size - resolved_margin.end
346 }
347 }
348 AlignItemsKeyword::Center => {
349 (grid_area_size - resolved_size + resolved_margin.start - resolved_margin.end) / 2.0
350 }
351 };
352
353 let offset_within_area = if position == Position::Absolute {
354 match (inset.start, inset.end) {
355 (Some(start), Some(end)) => {
356 if direction.is_rtl() {
357 grid_area_size - end - resolved_size - non_auto_margin.end
358 } else {
359 start + non_auto_margin.start
360 }
361 }
362 (Some(start), None) => start + non_auto_margin.start,
363 (None, Some(end)) => grid_area_size - end - resolved_size - non_auto_margin.end,
364 (None, None) => alignment_based_offset,
365 }
366 } else {
367 alignment_based_offset
368 };
369
370 let mut start = grid_area.start + offset_within_area;
371 if position == Position::Relative {
372 let relative_inset = if direction.is_rtl() {
373 inset.end.map(|pos| -pos).or(inset.start)
374 } else {
375 inset.start.or(inset.end.map(|pos| -pos))
376 };
377 start += relative_inset.unwrap_or(0.0);
378 }
379
380 (start, resolved_margin)
381}