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::scrollable_overflow::compute_scrollable_overflow_contribution;
17use crate::compute::common::sizing_keyword::{resolve_sizing_keyword, SizingKeywordResolution};
18use crate::{AbsoluteAxis, BoxSizing, Direction, LayoutGridContainer};
19
20pub(super) fn align_tracks(
24 grid_container_content_box_size: f32,
25 padding: Line<f32>,
26 border: Line<f32>,
27 tracks: &mut [GridTrack],
28 track_alignment_style: AlignContent,
29 axis_is_reversed: bool,
30) {
31 let used_size: f32 = tracks.iter().map(|track| track.base_size).sum();
32 let free_space = grid_container_content_box_size - used_size;
33 let origin = padding.start + border.start;
34
35 let num_tracks = tracks.iter().skip(1).step_by(2).filter(|track| !track.is_collapsed).count();
37
38 let gap = 0.0;
41 let layout_is_reversed = false;
42 let track_alignment = apply_alignment_fallback(free_space, num_tracks, track_alignment_style);
43 let track_alignment = if axis_is_reversed { track_alignment.reversed() } else { track_alignment };
44
45 let empty_grid_offset = if num_tracks == 0 {
49 compute_alignment_offset(free_space, num_tracks, gap, track_alignment, layout_is_reversed, true)
50 } else {
51 0.0
52 };
53
54 let mut total_offset = origin + empty_grid_offset;
57 let mut seen_non_collapsed_track = false;
58 let mut position_track = |i: usize, track: &mut GridTrack| {
59 let is_gutter = i % 2 == 0;
61 let is_non_collapsed_track = !is_gutter && !track.is_collapsed;
62
63 let is_first = is_non_collapsed_track && !seen_non_collapsed_track;
65
66 let offset = if is_non_collapsed_track {
67 compute_alignment_offset(free_space, num_tracks, gap, track_alignment, layout_is_reversed, is_first)
68 } else {
69 0.0
70 };
71
72 track.offset = total_offset + offset;
73 total_offset = total_offset + offset + track.base_size;
74 if is_non_collapsed_track {
75 seen_non_collapsed_track = true;
76 }
77 };
78 if axis_is_reversed {
79 tracks.iter_mut().rev().enumerate().for_each(|(i, track)| position_track(i, track));
80 } else {
81 tracks.iter_mut().enumerate().for_each(|(i, track)| position_track(i, track));
82 }
83}
84
85#[allow(clippy::too_many_arguments)]
87pub(super) fn align_and_position_item(
88 tree: &mut impl LayoutGridContainer,
89 node: NodeId,
90 order: u32,
91 grid_area: Rect<f32>,
92 container_alignment_styles: InBothAbsAxis<Option<AlignItems>>,
93 baseline_shim: f32,
94 direction: Direction,
95 container_border_box_width: f32,
96 container_border: Rect<f32>,
97 #[cfg(feature = "content_size")] container_is_scroll_container: bool,
98) -> (Rect<f32>, f32, f32) {
99 let grid_area_size = Size { width: grid_area.right - grid_area.left, height: grid_area.bottom - grid_area.top };
100
101 let style = tree.get_grid_child_style(node);
102
103 let overflow = style.overflow();
104 #[cfg(feature = "content_size")]
105 let contain = style.contain();
106 let scrollbar_width = style.scrollbar_width();
107 let aspect_ratio = style.aspect_ratio();
108 let item_direction = style.direction();
112 let justify_self = style.justify_self().map(|align| align.resolve_self_relative(item_direction, direction, true));
113 let align_self = style.align_self().map(|align| align.resolve_self_relative(item_direction, direction, false));
114 let container_alignment_styles = InBothAbsAxis {
115 horizontal: container_alignment_styles
116 .horizontal
117 .map(|align| align.resolve_self_relative(item_direction, direction, true)),
118 vertical: container_alignment_styles
119 .vertical
120 .map(|align| align.resolve_self_relative(item_direction, direction, false)),
121 };
122
123 let position = style.position();
124 let inset_horizontal = style
125 .inset()
126 .horizontal_components()
127 .map(|size| size.resolve_to_option(grid_area_size.width, |val, basis| tree.calc(val, basis)));
128 let inset_vertical = style
129 .inset()
130 .vertical_components()
131 .map(|size| size.resolve_to_option(grid_area_size.height, |val, basis| tree.calc(val, basis)));
132 let padding =
133 style.padding().map(|p| p.resolve_or_zero(Some(grid_area_size.width), |val, basis| tree.calc(val, basis)));
134 let border =
135 style.border().map(|p| p.resolve_or_zero(Some(grid_area_size.width), |val, basis| tree.calc(val, basis)));
136 let padding_border_size = (padding + border).sum_axes();
137
138 let box_sizing_adjustment =
139 if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
140
141 let size_style = style.size();
142 let inherent_size = size_style
143 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
144 .maybe_apply_aspect_ratio(aspect_ratio)
145 .maybe_add(box_sizing_adjustment);
146 let min_size = style
147 .min_size()
148 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
149 .maybe_add(box_sizing_adjustment)
150 .or(padding_border_size.map(Some))
151 .maybe_max(padding_border_size)
152 .maybe_apply_aspect_ratio(aspect_ratio);
153 let max_size = style
154 .max_size()
155 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
156 .maybe_apply_aspect_ratio(aspect_ratio)
157 .maybe_add(box_sizing_adjustment);
158
159 let alignment_styles = InBothAbsAxis {
164 horizontal: justify_self.or(container_alignment_styles.horizontal).unwrap_or_else(|| {
165 if inherent_size.width.is_some() || size_style.width.is_sizing_keyword() {
166 AlignSelf::START
167 } else {
168 AlignSelf::STRETCH
169 }
170 }),
171 vertical: align_self.or(container_alignment_styles.vertical).unwrap_or_else(|| {
172 if inherent_size.height.is_some() || size_style.height.is_sizing_keyword() || aspect_ratio.is_some() {
173 AlignSelf::START
174 } else {
175 AlignSelf::STRETCH
176 }
177 }),
178 };
179
180 let margin =
183 style.margin().map(|margin| margin.resolve_to_option(grid_area_size.width, |val, basis| tree.calc(val, basis)));
184
185 drop(style);
186
187 let grid_area_minus_item_margins_size = Size {
188 width: grid_area_size.width.maybe_sub(margin.left).maybe_sub(margin.right),
189 height: grid_area_size.height.maybe_sub(margin.top).maybe_sub(margin.bottom) - baseline_shim,
190 };
191
192 let keyword_width = inherent_size.width.is_none().then(|| {
196 resolve_sizing_keyword(
197 size_style.width,
198 Some(grid_area_minus_item_margins_size.width),
199 Some(grid_area_size.width),
200 )
201 });
202 let keyword_height = inherent_size.height.is_none().then(|| {
203 resolve_sizing_keyword(
204 size_style.height,
205 Some(grid_area_minus_item_margins_size.height),
206 Some(grid_area_size.height),
207 )
208 });
209
210 let keyword_measured_size: Size<Option<f32>> = match (&keyword_width, &keyword_height) {
212 (
213 Some(Some(SizingKeywordResolution::Measure(available_width))),
214 Some(Some(SizingKeywordResolution::Measure(available_height))),
215 ) if position != Position::Absolute => tree
216 .measure_child_size_both(
217 node,
218 Size::NONE,
219 grid_area_size.map(Option::Some),
220 Size { width: *available_width, height: *available_height },
221 SizingMode::InherentSize,
222 Line::FALSE,
223 )
224 .map(Option::Some),
225 _ => Size::NONE,
226 };
227
228 let width = inherent_size.width.or_else(|| {
231 if position == Position::Absolute {
234 if let (Some(left), Some(right)) = (inset_horizontal.start, inset_horizontal.end) {
235 return Some(f32_max(grid_area_minus_item_margins_size.width - left - right, 0.0));
236 }
237 }
238
239 if let Some(Some(resolution)) = keyword_width {
240 return Some(match resolution {
241 SizingKeywordResolution::Exact(width) => width,
242 SizingKeywordResolution::Measure(available_width) => keyword_measured_size.width.unwrap_or_else(|| {
243 tree.measure_child_size(
244 node,
245 Size::NONE,
246 grid_area_size.map(Option::Some),
247 Size {
248 width: available_width,
249 height: AvailableSpace::Definite(grid_area_minus_item_margins_size.height),
250 },
251 SizingMode::InherentSize,
252 AbsoluteAxis::Horizontal,
253 Line::FALSE,
254 )
255 }),
256 });
257 }
258
259 if margin.left.is_some()
264 && margin.right.is_some()
265 && alignment_styles.horizontal == AlignSelf::STRETCH
266 && position != Position::Absolute
267 {
268 return Some(grid_area_minus_item_margins_size.width);
269 }
270
271 None
272 });
273
274 let Size { width, height } = Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio);
276
277 let height = height.or_else(|| {
278 if position == Position::Absolute {
279 if let (Some(top), Some(bottom)) = (inset_vertical.start, inset_vertical.end) {
280 return Some(f32_max(grid_area_minus_item_margins_size.height - top - bottom, 0.0));
281 }
282 }
283
284 if let Some(Some(resolution)) = keyword_height {
285 return Some(match resolution {
286 SizingKeywordResolution::Exact(height) => height,
287 SizingKeywordResolution::Measure(available_height) => {
288 keyword_measured_size.height.unwrap_or_else(|| {
289 tree.measure_child_size(
290 node,
291 Size { width, height: None },
292 grid_area_size.map(Option::Some),
293 Size {
294 width: width
295 .map(AvailableSpace::Definite)
296 .unwrap_or(AvailableSpace::Definite(grid_area_minus_item_margins_size.width)),
297 height: available_height,
298 },
299 SizingMode::InherentSize,
300 AbsoluteAxis::Vertical,
301 Line::FALSE,
302 )
303 })
304 }
305 });
306 }
307
308 if margin.top.is_some()
313 && margin.bottom.is_some()
314 && alignment_styles.vertical == AlignSelf::STRETCH
315 && position != Position::Absolute
316 {
317 return Some(grid_area_minus_item_margins_size.height);
318 }
319
320 None
321 });
322 let Size { width, height } = Size { width, height }.maybe_apply_aspect_ratio(aspect_ratio);
324
325 let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
327
328 let size = if position == Position::Absolute && (width.is_none() || height.is_none()) {
330 tree.measure_child_size_both(
331 node,
332 Size { width, height },
333 grid_area_size.map(Option::Some),
334 grid_area_minus_item_margins_size.map(AvailableSpace::Definite),
335 SizingMode::InherentSize,
336 Line::FALSE,
337 )
338 .map(Some)
339 } else {
340 Size { width, height }
341 };
342
343 let layout_output = tree.perform_child_layout(
344 node,
345 size,
346 grid_area_size.map(Option::Some),
347 grid_area_minus_item_margins_size.map(AvailableSpace::Definite),
348 SizingMode::InherentSize,
349 Line::FALSE,
350 );
351
352 let Size { width, height } = size.unwrap_or(layout_output.size).maybe_clamp(min_size, max_size);
354
355 let (x, x_margin) = align_item_within_area(
356 Line { start: grid_area.left, end: grid_area.right },
357 justify_self.unwrap_or(alignment_styles.horizontal),
358 width,
359 position,
360 inset_horizontal,
361 margin.horizontal_components(),
362 0.0,
363 direction,
364 );
365 let (y, y_margin) = align_item_within_area(
366 Line { start: grid_area.top, end: grid_area.bottom },
367 align_self.unwrap_or(alignment_styles.vertical),
368 height,
369 position,
370 inset_vertical,
371 margin.vertical_components(),
372 baseline_shim,
373 Direction::Ltr,
374 );
375
376 let scrollbar_size = Size {
377 width: if overflow.y == Overflow::Scroll { scrollbar_width } else { 0.0 },
378 height: if overflow.x == Overflow::Scroll { scrollbar_width } else { 0.0 },
379 };
380
381 let resolved_margin = Rect { left: x_margin.start, right: x_margin.end, top: y_margin.start, bottom: y_margin.end };
382
383 tree.set_unrounded_layout(
384 node,
385 &Layout {
386 order,
387 location: Point { x, y },
388 size: Size { width, height },
389 #[cfg(feature = "content_size")]
390 scrollable_overflow_rect: layout_output.scrollable_overflow_rect,
391 scrollbar_size,
392 padding,
393 border,
394 margin: resolved_margin,
395 },
396 );
397
398 #[cfg(feature = "content_size")]
399 let contribution = {
400 let contribution_location = if direction.is_rtl() {
403 Point { x: container_border_box_width - (x + width) - container_border.right, y: y - container_border.top }
404 } else {
405 Point { x: x - container_border.left, y: y - container_border.top }
406 };
407 compute_scrollable_overflow_contribution(
408 contribution_location,
409 Size { width, height },
410 layout_output.scrollable_overflow_rect,
411 overflow,
412 contain,
413 container_is_scroll_container,
414 )
415 };
416 #[cfg(not(feature = "content_size"))]
417 let contribution = Rect::ZERO;
418
419 (contribution, y, height)
420}
421
422#[allow(clippy::too_many_arguments)]
424pub(super) fn align_item_within_area(
425 grid_area: Line<f32>,
426 alignment_style: AlignSelf,
427 resolved_size: f32,
428 position: Position,
429 inset: Line<Option<f32>>,
430 margin: Line<Option<f32>>,
431 baseline_shim: f32,
432 direction: Direction,
433) -> (f32, Line<f32>) {
434 let non_auto_margin = Line { start: margin.start.unwrap_or(0.0) + baseline_shim, end: margin.end.unwrap_or(0.0) };
436 let grid_area_size = f32_max(grid_area.end - grid_area.start, 0.0);
437 let free_space = f32_max(grid_area_size - resolved_size - non_auto_margin.sum(), 0.0);
438
439 let auto_margin_count = margin.start.is_none() as u8 + margin.end.is_none() as u8;
441 let auto_margin_size = if auto_margin_count > 0 { free_space / auto_margin_count as f32 } else { 0.0 };
442 let resolved_margin = Line {
443 start: margin.start.unwrap_or(auto_margin_size) + baseline_shim,
444 end: margin.end.unwrap_or(auto_margin_size),
445 };
446
447 let overflows = resolved_size + non_auto_margin.sum() > grid_area_size;
448 let alignment_keyword = resolve_self_alignment_safety(alignment_style, overflows);
449
450 let alignment_based_offset = match alignment_keyword {
452 AlignItemsKeyword::Start
454 | AlignItemsKeyword::FlexStart
455 | AlignItemsKeyword::Baseline
456 | AlignItemsKeyword::Stretch => {
457 if direction.is_rtl() {
458 grid_area_size - resolved_size - resolved_margin.end
459 } else {
460 resolved_margin.start
461 }
462 }
463 AlignItemsKeyword::End | AlignItemsKeyword::FlexEnd => {
464 if direction.is_rtl() {
465 resolved_margin.start
466 } else {
467 grid_area_size - resolved_size - resolved_margin.end
468 }
469 }
470 AlignItemsKeyword::Center => {
471 (grid_area_size - resolved_size + resolved_margin.start - resolved_margin.end) / 2.0
472 }
473 AlignItemsKeyword::SelfStart | AlignItemsKeyword::SelfEnd => unreachable!(),
476 };
477
478 let offset_within_area = if position == Position::Absolute {
479 match (inset.start, inset.end) {
480 (Some(start), Some(end)) => {
481 if direction.is_rtl() {
482 grid_area_size - end - resolved_size - non_auto_margin.end
483 } else {
484 start + non_auto_margin.start
485 }
486 }
487 (Some(start), None) => start + non_auto_margin.start,
488 (None, Some(end)) => grid_area_size - end - resolved_size - non_auto_margin.end,
489 (None, None) => alignment_based_offset,
490 }
491 } else {
492 alignment_based_offset
493 };
494
495 let mut start = grid_area.start + offset_within_area;
496 if position == Position::Relative {
497 let relative_inset = if direction.is_rtl() {
498 inset.end.map(|pos| -pos).or(inset.start)
499 } else {
500 inset.start.or(inset.end.map(|pos| -pos))
501 };
502 start += relative_inset.unwrap_or(0.0);
503 }
504
505 (start, resolved_margin)
506}