1use super::types::GridTrack;
3use crate::compute::common::alignment::compute_alignment_offset;
4use crate::geometry::{InBothAbsAxis, Line, Point, Rect, Size};
5use crate::style::{AlignContent, AlignItems, AlignSelf, AvailableSpace, Overflow, Position};
6use crate::tree::{Layout, NodeId, PartialLayoutTree, PartialLayoutTreeExt, 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;
12
13pub(super) fn align_tracks(
17 grid_container_content_box_size: f32,
18 padding: Line<f32>,
19 border: Line<f32>,
20 tracks: &mut [GridTrack],
21 track_alignment_style: AlignContent,
22) {
23 let used_size: f32 = tracks.iter().map(|track| track.base_size).sum();
24 let free_space = grid_container_content_box_size - used_size;
25 let origin = padding.start + border.start;
26
27 let num_tracks = tracks.iter().skip(1).step_by(2).filter(|track| !track.is_collapsed).count();
29
30 let gap = 0.0;
33 let layout_is_reversed = false;
34
35 let mut total_offset = origin;
37 tracks.iter_mut().enumerate().for_each(|(i, track)| {
38 let is_gutter = i % 2 == 0;
40
41 let is_first = i == 1;
43
44 let offset = if is_gutter {
45 0.0
46 } else {
47 compute_alignment_offset(free_space, num_tracks, gap, track_alignment_style, layout_is_reversed, is_first)
48 };
49
50 track.offset = total_offset + offset;
51 total_offset = total_offset + offset + track.base_size;
52 });
53}
54
55pub(super) fn align_and_position_item(
57 tree: &mut impl PartialLayoutTree,
58 node: NodeId,
59 order: u32,
60 grid_area: Rect<f32>,
61 container_alignment_styles: InBothAbsAxis<Option<AlignItems>>,
62 baseline_shim: f32,
63) -> (Size<f32>, f32, f32) {
64 let grid_area_size = Size { width: grid_area.right - grid_area.left, height: grid_area.bottom - grid_area.top };
65
66 let style = tree.get_style(node);
67
68 let overflow = style.overflow;
69 let scrollbar_width = style.scrollbar_width;
70 let aspect_ratio = style.aspect_ratio;
71 let justify_self = style.justify_self;
72 let align_self = style.align_self;
73
74 let position = style.position;
75 let inset_horizontal = style.inset.horizontal_components().map(|size| size.resolve_to_option(grid_area_size.width));
76 let inset_vertical = style.inset.vertical_components().map(|size| size.resolve_to_option(grid_area_size.height));
77 let padding = style.padding.map(|p| p.resolve_or_zero(Some(grid_area_size.width)));
78 let border = style.border.map(|p| p.resolve_or_zero(Some(grid_area_size.width)));
79 let padding_border_size = (padding + border).sum_axes();
80 let inherent_size = style.size.maybe_resolve(grid_area_size).maybe_apply_aspect_ratio(aspect_ratio);
81 let min_size = style
82 .min_size
83 .maybe_resolve(grid_area_size)
84 .or(padding_border_size.map(Some))
85 .maybe_max(padding_border_size)
86 .maybe_apply_aspect_ratio(aspect_ratio);
87 let max_size = style.max_size.maybe_resolve(grid_area_size).maybe_apply_aspect_ratio(aspect_ratio);
88
89 let alignment_styles = InBothAbsAxis {
94 horizontal: justify_self.or(container_alignment_styles.horizontal).unwrap_or_else(|| {
95 if inherent_size.width.is_some() {
96 AlignSelf::Start
97 } else {
98 AlignSelf::Stretch
99 }
100 }),
101 vertical: align_self.or(container_alignment_styles.vertical).unwrap_or_else(|| {
102 if inherent_size.height.is_some() || aspect_ratio.is_some() {
103 AlignSelf::Start
104 } else {
105 AlignSelf::Stretch
106 }
107 }),
108 };
109
110 let margin = style.margin.map(|margin| margin.resolve_to_option(grid_area_size.width));
113
114 let grid_area_minus_item_margins_size = Size {
115 width: grid_area_size.width.maybe_sub(margin.left).maybe_sub(margin.right),
116 height: grid_area_size.height.maybe_sub(margin.top).maybe_sub(margin.bottom) - baseline_shim,
117 };
118
119 let width = inherent_size.width.or_else(|| {
122 if position == Position::Absolute {
125 if let (Some(left), Some(right)) = (inset_horizontal.start, inset_horizontal.end) {
126 return Some(f32_max(grid_area_minus_item_margins_size.width - left - right, 0.0));
127 }
128 }
129
130 if margin.left.is_some()
135 && margin.right.is_some()
136 && alignment_styles.horizontal == AlignSelf::Stretch
137 && position != Position::Absolute
138 {
139 return Some(grid_area_minus_item_margins_size.width);
140 }
141
142 None
143 });
144
145 let Size { width, height } = Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio);
147
148 let height = height.or_else(|| {
149 if position == Position::Absolute {
150 if let (Some(top), Some(bottom)) = (inset_vertical.start, inset_vertical.end) {
151 return Some(f32_max(grid_area_minus_item_margins_size.height - top - bottom, 0.0));
152 }
153 }
154
155 if margin.top.is_some()
160 && margin.bottom.is_some()
161 && alignment_styles.vertical == AlignSelf::Stretch
162 && position != Position::Absolute
163 {
164 return Some(grid_area_minus_item_margins_size.height);
165 }
166
167 None
168 });
169 let Size { width, height } = Size { width, height }.maybe_apply_aspect_ratio(aspect_ratio);
171
172 let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
174
175 let layout_output = tree.perform_child_layout(
177 node,
178 Size { width, height },
179 grid_area_size.map(Option::Some),
180 grid_area_minus_item_margins_size.map(AvailableSpace::Definite),
181 SizingMode::InherentSize,
182 Line::FALSE,
183 );
184
185 let Size { width, height } = Size { width, height }.unwrap_or(layout_output.size).maybe_clamp(min_size, max_size);
187
188 let x = align_item_within_area(
189 Line { start: grid_area.left, end: grid_area.right },
190 justify_self.unwrap_or(alignment_styles.horizontal),
191 width,
192 position,
193 inset_horizontal,
194 margin.horizontal_components(),
195 0.0,
196 );
197 let y = align_item_within_area(
198 Line { start: grid_area.top, end: grid_area.bottom },
199 align_self.unwrap_or(alignment_styles.vertical),
200 height,
201 position,
202 inset_vertical,
203 margin.vertical_components(),
204 baseline_shim,
205 );
206
207 let scrollbar_size = Size {
208 width: if overflow.y == Overflow::Scroll { scrollbar_width } else { 0.0 },
209 height: if overflow.x == Overflow::Scroll { scrollbar_width } else { 0.0 },
210 };
211
212 tree.set_unrounded_layout(
213 node,
214 &Layout {
215 order,
216 location: Point { x, y },
217 size: Size { width, height },
218 #[cfg(feature = "content_size")]
219 content_size: layout_output.content_size,
220 scrollbar_size,
221 padding,
222 border,
223 },
224 );
225
226 #[cfg(feature = "content_size")]
227 let contribution =
228 compute_content_size_contribution(Point { x, y }, Size { width, height }, layout_output.content_size, overflow);
229 #[cfg(not(feature = "content_size"))]
230 let contribution = Size::ZERO;
231
232 (contribution, y, height)
233}
234
235pub(super) fn align_item_within_area(
237 grid_area: Line<f32>,
238 alignment_style: AlignSelf,
239 resolved_size: f32,
240 position: Position,
241 inset: Line<Option<f32>>,
242 margin: Line<Option<f32>>,
243 baseline_shim: f32,
244) -> f32 {
245 let non_auto_margin = Line { start: margin.start.unwrap_or(0.0) + baseline_shim, end: margin.end.unwrap_or(0.0) };
247 let grid_area_size = f32_max(grid_area.end - grid_area.start, 0.0);
248 let free_space = f32_max(grid_area_size - resolved_size - non_auto_margin.sum(), 0.0);
249
250 let auto_margin_count = margin.start.is_none() as u8 + margin.end.is_none() as u8;
252 let auto_margin_size = if auto_margin_count > 0 { free_space / auto_margin_count as f32 } else { 0.0 };
253 let resolved_margin = Line {
254 start: margin.start.unwrap_or(auto_margin_size) + baseline_shim,
255 end: margin.end.unwrap_or(auto_margin_size),
256 };
257
258 let alignment_based_offset = match alignment_style {
260 AlignSelf::Start | AlignSelf::FlexStart => resolved_margin.start,
261 AlignSelf::End | AlignSelf::FlexEnd => grid_area_size - resolved_size - resolved_margin.end,
262 AlignSelf::Center => (grid_area_size - resolved_size + resolved_margin.start - resolved_margin.end) / 2.0,
263 AlignSelf::Baseline => resolved_margin.start,
265 AlignSelf::Stretch => resolved_margin.start,
266 };
267
268 let offset_within_area = if position == Position::Absolute {
269 if let Some(start) = inset.start {
270 start + non_auto_margin.start
271 } else if let Some(end) = inset.end {
272 grid_area_size - end - resolved_size - non_auto_margin.end
273 } else {
274 alignment_based_offset
275 }
276 } else {
277 alignment_based_offset
278 };
279
280 let mut start = grid_area.start + offset_within_area;
281 if position == Position::Relative {
282 start += inset.start.or(inset.end.map(|pos| -pos)).unwrap_or(0.0);
283 }
284
285 start
286}