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