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