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 empty_grid_offset = if num_tracks == 0 {
48 compute_alignment_offset(free_space, num_tracks, gap, track_alignment, layout_is_reversed, true)
49 } else {
50 0.0
51 };
52
53 let mut total_offset = origin + empty_grid_offset;
55 let mut seen_non_collapsed_track = false;
56 tracks.iter_mut().enumerate().for_each(|(i, track)| {
57 let is_gutter = i % 2 == 0;
59 let is_non_collapsed_track = !is_gutter && !track.is_collapsed;
60
61 let is_first = is_non_collapsed_track && !seen_non_collapsed_track;
63
64 let offset = if is_non_collapsed_track {
65 compute_alignment_offset(free_space, num_tracks, gap, track_alignment, layout_is_reversed, is_first)
66 } else {
67 0.0
68 };
69
70 track.offset = total_offset + offset;
71 total_offset = total_offset + offset + track.base_size;
72 if is_non_collapsed_track {
73 seen_non_collapsed_track = true;
74 }
75 });
76}
77
78#[allow(clippy::too_many_arguments)]
80pub(super) fn align_and_position_item(
81 tree: &mut impl LayoutGridContainer,
82 node: NodeId,
83 order: u32,
84 grid_area: Rect<f32>,
85 container_alignment_styles: InBothAbsAxis<Option<AlignItems>>,
86 baseline_shim: f32,
87 direction: Direction,
88 container_border_box_width: f32,
89 container_border: Rect<f32>,
90) -> (Size<f32>, f32, f32) {
91 let grid_area_size = Size { width: grid_area.right - grid_area.left, height: grid_area.bottom - grid_area.top };
92
93 let style = tree.get_grid_child_style(node);
94
95 let overflow = style.overflow();
96 let scrollbar_width = style.scrollbar_width();
97 let aspect_ratio = style.aspect_ratio();
98 let item_direction = style.direction();
102 let justify_self = style.justify_self().map(|align| align.resolve_self_relative(item_direction, direction, true));
103 let align_self = style.align_self().map(|align| align.resolve_self_relative(item_direction, direction, false));
104 let container_alignment_styles = InBothAbsAxis {
105 horizontal: container_alignment_styles
106 .horizontal
107 .map(|align| align.resolve_self_relative(item_direction, direction, true)),
108 vertical: container_alignment_styles
109 .vertical
110 .map(|align| align.resolve_self_relative(item_direction, direction, false)),
111 };
112
113 let position = style.position();
114 let inset_horizontal = style
115 .inset()
116 .horizontal_components()
117 .map(|size| size.resolve_to_option(grid_area_size.width, |val, basis| tree.calc(val, basis)));
118 let inset_vertical = style
119 .inset()
120 .vertical_components()
121 .map(|size| size.resolve_to_option(grid_area_size.height, |val, basis| tree.calc(val, basis)));
122 let padding =
123 style.padding().map(|p| p.resolve_or_zero(Some(grid_area_size.width), |val, basis| tree.calc(val, basis)));
124 let border =
125 style.border().map(|p| p.resolve_or_zero(Some(grid_area_size.width), |val, basis| tree.calc(val, basis)));
126 let padding_border_size = (padding + border).sum_axes();
127
128 let box_sizing_adjustment =
129 if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
130
131 let inherent_size = style
132 .size()
133 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
134 .maybe_apply_aspect_ratio(aspect_ratio)
135 .maybe_add(box_sizing_adjustment);
136 let min_size = style
137 .min_size()
138 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
139 .maybe_add(box_sizing_adjustment)
140 .or(padding_border_size.map(Some))
141 .maybe_max(padding_border_size)
142 .maybe_apply_aspect_ratio(aspect_ratio);
143 let max_size = style
144 .max_size()
145 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
146 .maybe_apply_aspect_ratio(aspect_ratio)
147 .maybe_add(box_sizing_adjustment);
148
149 let alignment_styles = InBothAbsAxis {
154 horizontal: justify_self.or(container_alignment_styles.horizontal).unwrap_or_else(|| {
155 if inherent_size.width.is_some() {
156 AlignSelf::START
157 } else {
158 AlignSelf::STRETCH
159 }
160 }),
161 vertical: align_self.or(container_alignment_styles.vertical).unwrap_or_else(|| {
162 if inherent_size.height.is_some() || aspect_ratio.is_some() {
163 AlignSelf::START
164 } else {
165 AlignSelf::STRETCH
166 }
167 }),
168 };
169
170 let margin =
173 style.margin().map(|margin| margin.resolve_to_option(grid_area_size.width, |val, basis| tree.calc(val, basis)));
174
175 let grid_area_minus_item_margins_size = Size {
176 width: grid_area_size.width.maybe_sub(margin.left).maybe_sub(margin.right),
177 height: grid_area_size.height.maybe_sub(margin.top).maybe_sub(margin.bottom) - baseline_shim,
178 };
179
180 let width = inherent_size.width.or_else(|| {
183 if position == Position::Absolute {
186 if let (Some(left), Some(right)) = (inset_horizontal.start, inset_horizontal.end) {
187 return Some(f32_max(grid_area_minus_item_margins_size.width - left - right, 0.0));
188 }
189 }
190
191 if margin.left.is_some()
196 && margin.right.is_some()
197 && alignment_styles.horizontal == AlignSelf::STRETCH
198 && position != Position::Absolute
199 {
200 return Some(grid_area_minus_item_margins_size.width);
201 }
202
203 None
204 });
205
206 let Size { width, height } = Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio);
208
209 let height = height.or_else(|| {
210 if position == Position::Absolute {
211 if let (Some(top), Some(bottom)) = (inset_vertical.start, inset_vertical.end) {
212 return Some(f32_max(grid_area_minus_item_margins_size.height - top - bottom, 0.0));
213 }
214 }
215
216 if margin.top.is_some()
221 && margin.bottom.is_some()
222 && alignment_styles.vertical == AlignSelf::STRETCH
223 && position != Position::Absolute
224 {
225 return Some(grid_area_minus_item_margins_size.height);
226 }
227
228 None
229 });
230 let Size { width, height } = Size { width, height }.maybe_apply_aspect_ratio(aspect_ratio);
232
233 let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
235
236 drop(style);
238
239 let size = if position == Position::Absolute && (width.is_none() || height.is_none()) {
240 tree.measure_child_size_both(
241 node,
242 Size { width, height },
243 grid_area_size.map(Option::Some),
244 grid_area_minus_item_margins_size.map(AvailableSpace::Definite),
245 SizingMode::InherentSize,
246 Line::FALSE,
247 )
248 .map(Some)
249 } else {
250 Size { width, height }
251 };
252
253 let layout_output = tree.perform_child_layout(
254 node,
255 size,
256 grid_area_size.map(Option::Some),
257 grid_area_minus_item_margins_size.map(AvailableSpace::Definite),
258 SizingMode::InherentSize,
259 Line::FALSE,
260 );
261
262 let Size { width, height } = size.unwrap_or(layout_output.size).maybe_clamp(min_size, max_size);
264
265 let (x, x_margin) = align_item_within_area(
266 Line { start: grid_area.left, end: grid_area.right },
267 justify_self.unwrap_or(alignment_styles.horizontal),
268 width,
269 position,
270 inset_horizontal,
271 margin.horizontal_components(),
272 0.0,
273 direction,
274 );
275 let (y, y_margin) = align_item_within_area(
276 Line { start: grid_area.top, end: grid_area.bottom },
277 align_self.unwrap_or(alignment_styles.vertical),
278 height,
279 position,
280 inset_vertical,
281 margin.vertical_components(),
282 baseline_shim,
283 Direction::Ltr,
284 );
285
286 let scrollbar_size = Size {
287 width: if overflow.y == Overflow::Scroll { scrollbar_width } else { 0.0 },
288 height: if overflow.x == Overflow::Scroll { scrollbar_width } else { 0.0 },
289 };
290
291 let resolved_margin = Rect { left: x_margin.start, right: x_margin.end, top: y_margin.start, bottom: y_margin.end };
292
293 tree.set_unrounded_layout(
294 node,
295 &Layout {
296 order,
297 location: Point { x, y },
298 size: Size { width, height },
299 #[cfg(feature = "content_size")]
300 content_size: layout_output.content_size,
301 scrollbar_size,
302 padding,
303 border,
304 margin: resolved_margin,
305 },
306 );
307
308 #[cfg(feature = "content_size")]
309 let contribution = {
310 let contribution_location = if direction.is_rtl() {
313 Point { x: container_border_box_width - (x + width) - container_border.right, y: y - container_border.top }
314 } else {
315 Point { x: x - container_border.left, y: y - container_border.top }
316 };
317 compute_content_size_contribution(
318 contribution_location,
319 Size { width, height },
320 layout_output.content_size,
321 overflow,
322 )
323 };
324 #[cfg(not(feature = "content_size"))]
325 let contribution = Size::ZERO;
326
327 (contribution, y, height)
328}
329
330#[allow(clippy::too_many_arguments)]
332pub(super) fn align_item_within_area(
333 grid_area: Line<f32>,
334 alignment_style: AlignSelf,
335 resolved_size: f32,
336 position: Position,
337 inset: Line<Option<f32>>,
338 margin: Line<Option<f32>>,
339 baseline_shim: f32,
340 direction: Direction,
341) -> (f32, Line<f32>) {
342 let non_auto_margin = Line { start: margin.start.unwrap_or(0.0) + baseline_shim, end: margin.end.unwrap_or(0.0) };
344 let grid_area_size = f32_max(grid_area.end - grid_area.start, 0.0);
345 let free_space = f32_max(grid_area_size - resolved_size - non_auto_margin.sum(), 0.0);
346
347 let auto_margin_count = margin.start.is_none() as u8 + margin.end.is_none() as u8;
349 let auto_margin_size = if auto_margin_count > 0 { free_space / auto_margin_count as f32 } else { 0.0 };
350 let resolved_margin = Line {
351 start: margin.start.unwrap_or(auto_margin_size) + baseline_shim,
352 end: margin.end.unwrap_or(auto_margin_size),
353 };
354
355 let overflows = resolved_size + non_auto_margin.sum() > grid_area_size;
356 let alignment_keyword = resolve_self_alignment_safety(alignment_style, overflows);
357
358 let alignment_based_offset = match alignment_keyword {
360 AlignItemsKeyword::Start
362 | AlignItemsKeyword::FlexStart
363 | AlignItemsKeyword::Baseline
364 | AlignItemsKeyword::Stretch => {
365 if direction.is_rtl() {
366 grid_area_size - resolved_size - resolved_margin.end
367 } else {
368 resolved_margin.start
369 }
370 }
371 AlignItemsKeyword::End | AlignItemsKeyword::FlexEnd => {
372 if direction.is_rtl() {
373 resolved_margin.start
374 } else {
375 grid_area_size - resolved_size - resolved_margin.end
376 }
377 }
378 AlignItemsKeyword::Center => {
379 (grid_area_size - resolved_size + resolved_margin.start - resolved_margin.end) / 2.0
380 }
381 AlignItemsKeyword::SelfStart | AlignItemsKeyword::SelfEnd => unreachable!(),
384 };
385
386 let offset_within_area = if position == Position::Absolute {
387 match (inset.start, inset.end) {
388 (Some(start), Some(end)) => {
389 if direction.is_rtl() {
390 grid_area_size - end - resolved_size - non_auto_margin.end
391 } else {
392 start + non_auto_margin.start
393 }
394 }
395 (Some(start), None) => start + non_auto_margin.start,
396 (None, Some(end)) => grid_area_size - end - resolved_size - non_auto_margin.end,
397 (None, None) => alignment_based_offset,
398 }
399 } else {
400 alignment_based_offset
401 };
402
403 let mut start = grid_area.start + offset_within_area;
404 if position == Position::Relative {
405 let relative_inset = if direction.is_rtl() {
406 inset.end.map(|pos| -pos).or(inset.start)
407 } else {
408 inset.start.or(inset.end.map(|pos| -pos))
409 };
410 start += relative_inset.unwrap_or(0.0);
411 }
412
413 (start, resolved_margin)
414}