Move flex-item and flex-line layout passes to methods instead of closures (#209)
1//! Computes the [flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) layout algorithm on [`Taffy`](crate::Taffy) according to the [spec](https://www.w3.org/TR/css-flexbox-1/)
2//!
3//! Note that some minor steps appear to be missing: see https://github.com/DioxusLabs/taffy/issues for more information.
4use core::f32;
5
6use slotmap::SlotMap;
7
8use crate::data::NodeData;
9use crate::geometry::{Point, Rect, Size};
10use crate::layout::{Cache, Layout};
11use crate::math::MaybeMath;
12use crate::node::{MeasureFunc, Node};
13use crate::resolve::{MaybeResolve, ResolveOrDefault};
14use crate::style::{AlignContent, AlignSelf, Dimension, Display, FlexWrap, JustifyContent, PositionType};
15use crate::style::{FlexDirection, FlexboxLayout};
16use crate::sys::{abs, round, ChildrenVec, Vec};
17use crate::Taffy;
18
19/// The intermediate results of a flexbox calculation for a single item
20struct FlexItem {
21 /// The identifier for the associated [`Node`](crate::node::Node)
22 node: Node,
23
24 /// The base size of this item
25 size: Size<Option<f32>>,
26 /// The minimum allowable size of this item
27 min_size: Size<Option<f32>>,
28 /// The maximum allowable size of this item
29 max_size: Size<Option<f32>>,
30
31 /// The final offset of this item
32 position: Rect<Option<f32>>,
33 /// The margin of this item
34 margin: Rect<f32>,
35 /// The padding of this item
36 padding: Rect<f32>,
37 /// The border of this item
38 border: Rect<f32>,
39
40 /// The default size of this item
41 flex_basis: f32,
42 /// The default size of this item, minus padding and border
43 inner_flex_basis: f32,
44 /// The amount by which this item has deviated from its target size
45 violation: f32,
46 /// Is the size of this item locked
47 frozen: bool,
48
49 /// The proposed inner size of this item
50 hypothetical_inner_size: Size<f32>,
51 /// The proposed outer size of this item
52 hypothetical_outer_size: Size<f32>,
53 /// The size that this item wants to be
54 target_size: Size<f32>,
55 /// The size that this item wants to be, plus any padding and border
56 outer_target_size: Size<f32>,
57
58 /// The position of the bottom edge of this item
59 baseline: f32,
60
61 /// A temporary value for the main offset
62 ///
63 /// Offset is the relative position from the item's natural flow position based on
64 /// relative position values, alignment, and justification. Does not include margin/padding/border.
65 offset_main: f32,
66 /// A temporary value for the cross offset
67 ///
68 /// Offset is the relative position from the item's natural flow position based on
69 /// relative position values, alignment, and justification. Does not include margin/padding/border.
70 offset_cross: f32,
71}
72
73/// A line of [`FlexItem`] used for intermediate computation
74struct FlexLine<'a> {
75 /// The slice of items to iterate over during computation of this line
76 items: &'a mut [FlexItem],
77 /// The dimensions of the cross-axis
78 cross_size: f32,
79 /// The relative offset of the cross-axis
80 offset_cross: f32,
81}
82
83/// Values that can be cached during the flexbox algorithm
84struct AlgoConstants {
85 /// The direction of the current segment being laid out
86 dir: FlexDirection,
87 /// Is this segment a row
88 is_row: bool,
89 /// Is this segment a column
90 is_column: bool,
91 /// Is the wrap direction inverted
92 is_wrap_reverse: bool,
93
94 /// The margin of this section
95 margin: Rect<f32>,
96 /// The border of this section
97 border: Rect<f32>,
98 /// The padding of this section
99 padding_border: Rect<f32>,
100
101 /// The size of the internal node
102 node_inner_size: Size<Option<f32>>,
103 /// The size of the surrounding container
104 container_size: Size<f32>,
105 /// The size of the internal container
106 inner_container_size: Size<f32>,
107}
108
109impl Taffy {
110 /// Computes the layout of [`Taffy`] according to the flexbox algorithm
111 pub(crate) fn compute(&mut self, root: Node, size: Size<Option<f32>>) {
112 let style = self.nodes[root].style;
113 let has_root_min_max = style.min_size.width.is_defined()
114 || style.min_size.height.is_defined()
115 || style.max_size.width.is_defined()
116 || style.max_size.height.is_defined();
117
118 let preliminary_size = if has_root_min_max {
119 let first_pass = self.compute_preliminary(root, style.size.maybe_resolve(size), size, false, true);
120
121 self.compute_preliminary(
122 root,
123 Size {
124 width: first_pass
125 .width
126 .maybe_max(style.min_size.width.maybe_resolve(size.width))
127 .maybe_min(style.max_size.width.maybe_resolve(size.width))
128 .into(),
129 height: first_pass
130 .height
131 .maybe_max(style.min_size.height.maybe_resolve(size.height))
132 .maybe_min(style.max_size.height.maybe_resolve(size.height))
133 .into(),
134 },
135 size,
136 true,
137 true,
138 )
139 } else {
140 self.compute_preliminary(root, style.size.maybe_resolve(size), size, true, true)
141 };
142
143 self.nodes[root].layout = Layout { order: 0, size: preliminary_size, location: Point::ZERO };
144
145 Self::round_layout(&mut self.nodes, &self.children, root, 0.0, 0.0);
146 }
147
148 /// Rounds the calculated [`NodeData`] according to the spec
149 fn round_layout(
150 nodes: &mut SlotMap<Node, NodeData>,
151 children: &SlotMap<Node, ChildrenVec<Node>>,
152 root: Node,
153 abs_x: f32,
154 abs_y: f32,
155 ) {
156 let layout = &mut nodes[root].layout;
157 let abs_x = abs_x + layout.location.x;
158 let abs_y = abs_y + layout.location.y;
159
160 layout.location.x = round(layout.location.x);
161 layout.location.y = round(layout.location.y);
162
163 layout.size.width = round(layout.size.width);
164 layout.size.height = round(layout.size.height);
165
166 for child in &children[root] {
167 Self::round_layout(nodes, children, *child, abs_x, abs_y);
168 }
169 }
170
171 /// Saves intermediate results to a [`Cache`]
172 fn cache(&mut self, node: Node, main_size: bool) -> &mut Option<Cache> {
173 if main_size {
174 &mut self.nodes[node].main_size_layout_cache
175 } else {
176 &mut self.nodes[node].other_layout_cache
177 }
178 }
179
180 /// Try to get the computation result from the cache.
181 #[inline]
182 fn compute_from_cache(
183 &mut self,
184 node: Node,
185 node_size: Size<Option<f32>>,
186 parent_size: Size<Option<f32>>,
187 perform_layout: bool,
188 main_size: bool,
189 ) -> Option<Size<f32>> {
190 if let Some(ref cache) = self.cache(node, main_size) {
191 if cache.perform_layout || !perform_layout {
192 let width_compatible = if let Some(width) = node_size.width {
193 abs(width - cache.size.width) < f32::EPSILON
194 } else {
195 cache.node_size.width.is_none()
196 };
197
198 let height_compatible = if let Some(height) = node_size.height {
199 abs(height - cache.size.height) < f32::EPSILON
200 } else {
201 cache.node_size.height.is_none()
202 };
203
204 if width_compatible && height_compatible {
205 return Some(cache.size);
206 }
207
208 if cache.node_size == node_size && cache.parent_size == parent_size {
209 return Some(cache.size);
210 }
211 }
212 }
213
214 None
215 }
216
217 /// Compute constants that can be reused during the flexbox algorithm.
218 #[inline]
219 fn compute_constants(
220 node: &NodeData,
221 node_size: Size<Option<f32>>,
222 parent_size: Size<Option<f32>>,
223 ) -> AlgoConstants {
224 let dir = node.style.flex_direction;
225 let is_row = dir.is_row();
226 let is_column = dir.is_column();
227 let is_wrap_reverse = node.style.flex_wrap == FlexWrap::WrapReverse;
228
229 let margin = node.style.margin.resolve_or_default(parent_size.width);
230 let padding = node.style.padding.resolve_or_default(parent_size.width);
231 let border = node.style.border.resolve_or_default(parent_size.width);
232
233 let padding_border = Rect {
234 start: padding.start + border.start,
235 end: padding.end + border.end,
236 top: padding.top + border.top,
237 bottom: padding.bottom + border.bottom,
238 };
239
240 let node_inner_size = Size {
241 width: node_size.width.maybe_sub(padding_border.horizontal_axis_sum()),
242 height: node_size.height.maybe_sub(padding_border.vertical_axis_sum()),
243 };
244
245 let container_size = Size::ZERO;
246 let inner_container_size = Size::ZERO;
247
248 AlgoConstants {
249 dir,
250 is_row,
251 is_column,
252 is_wrap_reverse,
253 margin,
254 border,
255 padding_border,
256 node_inner_size,
257 container_size,
258 inner_container_size,
259 }
260 }
261
262 /// Generate anonymous flex items.
263 ///
264 /// # [9.1. Initial Setup](https://www.w3.org/TR/css-flexbox-1/#box-manip)
265 ///
266 /// - [**Generate anonymous flex items**](https://www.w3.org/TR/css-flexbox-1/#algo-anon-box) as described in [§4 Flex Items](https://www.w3.org/TR/css-flexbox-1/#flex-items).
267 #[inline]
268 fn generate_anonymous_flex_items(&self, node: Node, constants: &AlgoConstants) -> Vec<FlexItem> {
269 self.children[node]
270 .iter()
271 .map(|child| (child, &self.nodes[*child].style))
272 .filter(|(_, style)| style.position_type != PositionType::Absolute)
273 .filter(|(_, style)| style.display != Display::None)
274 .map(|(child, child_style)| FlexItem {
275 node: *child,
276 size: child_style.size.maybe_resolve(constants.node_inner_size),
277 min_size: child_style.min_size.maybe_resolve(constants.node_inner_size),
278 max_size: child_style.max_size.maybe_resolve(constants.node_inner_size),
279
280 position: child_style.position.zip_size(constants.node_inner_size, |p, s| p.maybe_resolve(s)),
281 margin: child_style.margin.resolve_or_default(constants.node_inner_size.width),
282 padding: child_style.padding.resolve_or_default(constants.node_inner_size.width),
283 border: child_style.border.resolve_or_default(constants.node_inner_size.width),
284 flex_basis: 0.0,
285 inner_flex_basis: 0.0,
286 violation: 0.0,
287 frozen: false,
288
289 hypothetical_inner_size: Size::ZERO,
290 hypothetical_outer_size: Size::ZERO,
291 target_size: Size::ZERO,
292 outer_target_size: Size::ZERO,
293
294 baseline: 0.0,
295
296 offset_main: 0.0,
297 offset_cross: 0.0,
298 })
299 .collect()
300 }
301
302 /// Determine the available main and cross space for the flex items.
303 ///
304 /// # [9.2. Line Length Determination](https://www.w3.org/TR/css-flexbox-1/#line-sizing)
305 ///
306 /// - [**Determine the available main and cross space for the flex items**](https://www.w3.org/TR/css-flexbox-1/#algo-available).
307 /// For each dimension, if that dimension of the flex container’s content box is a definite size, use that;
308 /// if that dimension of the flex container is being sized under a min or max-content constraint, the available space in that dimension is that constraint;
309 /// otherwise, subtract the flex container’s margin, border, and padding from the space available to the flex container in that dimension and use that value.
310 /// **This might result in an infinite value**.
311 #[inline]
312 #[must_use]
313 fn determine_available_space(
314 node_size: Size<Option<f32>>,
315 parent_size: Size<Option<f32>>,
316 constants: &AlgoConstants,
317 ) -> Size<Option<f32>> {
318 let width = match node_size.width {
319 Some(node_width) => Some(node_width),
320 None => parent_size
321 .width
322 .maybe_sub(constants.margin.horizontal_axis_sum())
323 .maybe_sub(constants.padding_border.horizontal_axis_sum()),
324 };
325
326 let height = match node_size.height {
327 Some(node_height) => Some(node_height),
328 None => parent_size
329 .height
330 .maybe_sub(constants.margin.vertical_axis_sum())
331 .maybe_sub(constants.padding_border.vertical_axis_sum()),
332 };
333
334 Size { width, height }
335 }
336
337 /// Determine the flex base size and hypothetical main size of each item.
338 ///
339 /// # [9.2. Line Length Determination](https://www.w3.org/TR/css-flexbox-1/#line-sizing)
340 ///
341 /// - [**Determine the flex base size and hypothetical main size of each item:**](https://www.w3.org/TR/css-flexbox-1/#algo-main-item)
342 ///
343 /// - A. If the item has a definite used flex basis, that’s the flex base size.
344 ///
345 /// - B. If the flex item has ...
346 ///
347 /// - an intrinsic aspect ratio,
348 /// - a used flex basis of content, and
349 /// - a definite cross size,
350 ///
351 /// then the flex base size is calculated from its inner cross size and the flex item’s intrinsic aspect ratio.
352 ///
353 /// - C. If the used flex basis is content or depends on its available space, and the flex container is being sized under a min-content
354 /// or max-content constraint (e.g. when performing automatic table layout \[CSS21\]), size the item under that constraint.
355 /// The flex base size is the item’s resulting main size.
356 ///
357 /// - E. Otherwise, size the item into the available space using its used flex basis in place of its main size, treating a value of content as max-content.
358 /// If a cross size is needed to determine the main size (e.g. when the flex item’s main size is in its block axis) and the flex item’s cross size is auto and not definite,
359 /// in this calculation use fit-content as the flex item’s cross size. The flex base size is the item’s resulting main size.
360 ///
361 /// When determining the flex base size, the item’s min and max main sizes are ignored (no clamping occurs).
362 /// Furthermore, the sizing calculations that floor the content box size at zero when applying box-sizing are also ignored.
363 /// (For example, an item with a specified size of zero, positive padding, and box-sizing: border-box will have an outer flex base size of zero—and hence a negative inner flex base size.)
364 #[inline]
365 fn determine_flex_base_size(
366 &mut self,
367 node: Node,
368 node_size: Size<Option<f32>>,
369 constants: &AlgoConstants,
370 available_space: Size<Option<f32>>,
371 flex_items: &mut Vec<FlexItem>,
372 ) {
373 // TODO - this does not follow spec. See the TODOs below
374 for child in flex_items.iter_mut() {
375 let child_style = self.nodes[child.node].style;
376
377 // A. If the item has a definite used flex basis, that’s the flex base size.
378
379 let flex_basis = child_style.flex_basis.maybe_resolve(constants.node_inner_size.main(constants.dir));
380 if flex_basis.is_some() {
381 child.flex_basis = flex_basis.unwrap_or(0.0);
382 continue;
383 };
384
385 // B. If the flex item has an intrinsic aspect ratio,
386 // a used flex basis of content, and a definite cross size,
387 // then the flex base size is calculated from its inner
388 // cross size and the flex item’s intrinsic aspect ratio.
389
390 if let Some(ratio) = child_style.aspect_ratio {
391 if let Some(cross) = node_size.cross(constants.dir) {
392 if child_style.flex_basis == Dimension::Auto {
393 child.flex_basis = cross * ratio;
394 continue;
395 }
396 }
397 }
398
399 // C. If the used flex basis is content or depends on its available space,
400 // and the flex container is being sized under a min-content or max-content
401 // constraint (e.g. when performing automatic table layout [CSS21]),
402 // size the item under that constraint. The flex base size is the item’s
403 // resulting main size.
404
405 // TODO - Probably need to cover this case in future
406
407 // D. Otherwise, if the used flex basis is content or depends on its
408 // available space, the available main size is infinite, and the flex item’s
409 // inline axis is parallel to the main axis, lay the item out using the rules
410 // for a box in an orthogonal flow [CSS3-WRITING-MODES]. The flex base size
411 // is the item’s max-content main size.
412
413 // TODO - Probably need to cover this case in future
414
415 // E. Otherwise, size the item into the available space using its used flex basis
416 // in place of its main size, treating a value of content as max-content.
417 // If a cross size is needed to determine the main size (e.g. when the
418 // flex item’s main size is in its block axis) and the flex item’s cross size
419 // is auto and not definite, in this calculation use fit-content as the
420 // flex item’s cross size. The flex base size is the item’s resulting main size.
421
422 let width: Option<f32> = if child.size.width.is_none()
423 && child_style.align_self(&self.nodes[node].style) == AlignSelf::Stretch
424 && constants.is_column
425 {
426 available_space.width
427 } else {
428 child.size.width
429 };
430
431 let height: Option<f32> = if child.size.height.is_none()
432 && child_style.align_self(&self.nodes[node].style) == AlignSelf::Stretch
433 && constants.is_row
434 {
435 available_space.height
436 } else {
437 child.size.height
438 };
439
440 child.flex_basis = self
441 .compute_preliminary(
442 child.node,
443 Size {
444 width: width.maybe_min(child.max_size.width),
445 height: height.maybe_min(child.max_size.height),
446 },
447 available_space,
448 false,
449 true,
450 )
451 .main(constants.dir)
452 .maybe_min(child.max_size.main(constants.dir));
453 }
454
455 // The hypothetical main size is the item’s flex base size clamped according to its
456 // used min and max main sizes (and flooring the content box size at zero).
457
458 for child in flex_items {
459 child.inner_flex_basis = child.flex_basis
460 - child.padding.main_axis_sum(constants.dir)
461 - child.border.main_axis_sum(constants.dir);
462
463 // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
464 // The following logic was developed not from the spec but by trail and error looking into how
465 // webkit handled various scenarios. Can probably be solved better by passing in
466 // min-content max-content constraints from the top
467 let min_main = self
468 .compute_preliminary(child.node, Size::NONE, available_space, false, false)
469 .main(constants.dir)
470 .maybe_max(child.min_size.main(constants.dir))
471 .maybe_min(child.size.main(constants.dir))
472 .into();
473
474 child.hypothetical_inner_size.set_main(
475 constants.dir,
476 child.flex_basis.maybe_max(min_main).maybe_min(child.max_size.main(constants.dir)),
477 );
478
479 child.hypothetical_outer_size.set_main(
480 constants.dir,
481 child.hypothetical_inner_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
482 );
483 }
484 }
485
486 /// Collect flex items into flex lines.
487 ///
488 /// # [9.3. Main Size Determination](https://www.w3.org/TR/css-flexbox-1/#main-sizing)
489 ///
490 /// - [**Collect flex items into flex lines**](https://www.w3.org/TR/css-flexbox-1/#algo-line-break):
491 ///
492 /// - If the flex container is single-line, collect all the flex items into a single flex line.
493 ///
494 /// - Otherwise, starting from the first uncollected item, collect consecutive items one by one until the first time that the next collected item would not fit into the flex container’s inner main size
495 /// (or until a forced break is encountered, see [§10 Fragmenting Flex Layout](https://www.w3.org/TR/css-flexbox-1/#pagination)).
496 /// If the very first uncollected item wouldn't fit, collect just it into the line.
497 ///
498 /// For this step, the size of a flex item is its outer hypothetical main size. (**Note: This can be negative**.)
499 ///
500 /// Repeat until all flex items have been collected into flex lines.
501 ///
502 /// **Note that the "collect as many" line will collect zero-sized flex items onto the end of the previous line even if the last non-zero item exactly "filled up" the line**.
503 #[inline]
504 fn collect_flex_lines<'a>(
505 &self,
506 node: Node,
507 constants: &AlgoConstants,
508 available_space: Size<Option<f32>>,
509 flex_items: &'a mut Vec<FlexItem>,
510 ) -> Vec<FlexLine<'a>> {
511 let mut lines = crate::sys::new_vec_with_capacity(1);
512
513 if self.nodes[node].style.flex_wrap == FlexWrap::NoWrap {
514 lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
515 } else {
516 let mut flex_items = &mut flex_items[..];
517
518 while !flex_items.is_empty() {
519 let mut line_length = 0.0;
520 let index = flex_items
521 .iter()
522 .enumerate()
523 .find(|&(idx, child)| {
524 line_length += child.hypothetical_outer_size.main(constants.dir);
525 if let Some(main) = available_space.main(constants.dir) {
526 line_length > main && idx != 0
527 } else {
528 false
529 }
530 })
531 .map(|(idx, _)| idx)
532 .unwrap_or(flex_items.len());
533
534 let (items, rest) = flex_items.split_at_mut(index);
535 lines.push(FlexLine { items, cross_size: 0.0, offset_cross: 0.0 });
536 flex_items = rest;
537 }
538 }
539
540 lines
541 }
542
543 /// Resolve the flexible lengths of the items within a flex line.
544 ///
545 /// # [9.7. Resolving Flexible Lengths](https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths)
546 #[inline]
547 fn resolve_flexible_lengths(
548 &mut self,
549 line: &mut FlexLine,
550 constants: &AlgoConstants,
551 available_space: Size<Option<f32>>,
552 ) {
553 // 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all
554 // items on the line. If the sum is less than the flex container’s inner main size,
555 // use the flex grow factor for the rest of this algorithm; otherwise, use the
556 // flex shrink factor.
557
558 let used_flex_factor: f32 =
559 line.items.iter().map(|child| child.hypothetical_outer_size.main(constants.dir)).sum();
560 let growing = used_flex_factor < constants.node_inner_size.main(constants.dir).unwrap_or(0.0);
561 let shrinking = !growing;
562
563 // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size
564 // - Any item that has a flex factor of zero
565 // - If using the flex grow factor: any item that has a flex base size
566 // greater than its hypothetical main size
567 // - If using the flex shrink factor: any item that has a flex base size
568 // smaller than its hypothetical main size
569
570 for child in line.items.iter_mut() {
571 // TODO - This is not found by reading the spec. Maybe this can be done in some other place
572 // instead. This was found by trail and error fixing tests to align with webkit output.
573 if constants.node_inner_size.main(constants.dir).is_none() && constants.is_row {
574 child.target_size.set_main(
575 constants.dir,
576 self.compute_preliminary(
577 child.node,
578 Size {
579 width: child.size.width.maybe_max(child.min_size.width).maybe_min(child.max_size.width),
580 height: child.size.height.maybe_max(child.min_size.height).maybe_min(child.max_size.height),
581 },
582 available_space,
583 false,
584 false,
585 )
586 .main(constants.dir)
587 .maybe_max(child.min_size.main(constants.dir))
588 .maybe_min(child.max_size.main(constants.dir)),
589 );
590 } else {
591 child.target_size.set_main(constants.dir, child.hypothetical_inner_size.main(constants.dir));
592 }
593
594 // TODO this should really only be set inside the if-statement below but
595 // that causes the target_main_size to never be set for some items
596
597 child.outer_target_size.set_main(
598 constants.dir,
599 child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
600 );
601
602 let child_style = &self.nodes[child.node].style;
603 if (child_style.flex_grow == 0.0 && child_style.flex_shrink == 0.0)
604 || (growing && child.flex_basis > child.hypothetical_inner_size.main(constants.dir))
605 || (shrinking && child.flex_basis < child.hypothetical_inner_size.main(constants.dir))
606 {
607 child.frozen = true;
608 }
609 }
610
611 // 3. Calculate initial free space. Sum the outer sizes of all items on the line,
612 // and subtract this from the flex container’s inner main size. For frozen items,
613 // use their outer target main size; for other items, use their outer flex base size.
614
615 let used_space: f32 = line
616 .items
617 .iter()
618 .map(|child| {
619 child.margin.main_axis_sum(constants.dir)
620 + if child.frozen { child.target_size.main(constants.dir) } else { child.flex_basis }
621 })
622 .sum();
623
624 let initial_free_space = constants.node_inner_size.main(constants.dir).maybe_sub(used_space).unwrap_or(0.0);
625
626 // 4. Loop
627
628 loop {
629 // a. Check for flexible items. If all the flex items on the line are frozen,
630 // free space has been distributed; exit this loop.
631
632 if line.items.iter().all(|child| child.frozen) {
633 break;
634 }
635
636 // b. Calculate the remaining free space as for initial free space, above.
637 // If the sum of the unfrozen flex items’ flex factors is less than one,
638 // multiply the initial free space by this sum. If the magnitude of this
639 // value is less than the magnitude of the remaining free space, use this
640 // as the remaining free space.
641
642 let used_space: f32 = line
643 .items
644 .iter()
645 .map(|child| {
646 child.margin.main_axis_sum(constants.dir)
647 + if child.frozen { child.target_size.main(constants.dir) } else { child.flex_basis }
648 })
649 .sum();
650
651 let mut unfrozen: Vec<&mut FlexItem> = line.items.iter_mut().filter(|child| !child.frozen).collect();
652
653 let (sum_flex_grow, sum_flex_shrink): (f32, f32) =
654 unfrozen.iter().fold((0.0, 0.0), |(flex_grow, flex_shrink), item| {
655 let style = &self.nodes[item.node].style;
656 (flex_grow + style.flex_grow, flex_shrink + style.flex_shrink)
657 });
658
659 let free_space = if growing && sum_flex_grow < 1.0 {
660 (initial_free_space * sum_flex_grow)
661 .maybe_min(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
662 } else if shrinking && sum_flex_shrink < 1.0 {
663 (initial_free_space * sum_flex_shrink)
664 .maybe_max(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
665 } else {
666 (constants.node_inner_size.main(constants.dir).maybe_sub(used_space)).unwrap_or(0.0)
667 };
668
669 // c. Distribute free space proportional to the flex factors.
670 // - If the remaining free space is zero
671 // Do Nothing
672 // - If using the flex grow factor
673 // Find the ratio of the item’s flex grow factor to the sum of the
674 // flex grow factors of all unfrozen items on the line. Set the item’s
675 // target main size to its flex base size plus a fraction of the remaining
676 // free space proportional to the ratio.
677 // - If using the flex shrink factor
678 // For every unfrozen item on the line, multiply its flex shrink factor by
679 // its inner flex base size, and note this as its scaled flex shrink factor.
680 // Find the ratio of the item’s scaled flex shrink factor to the sum of the
681 // scaled flex shrink factors of all unfrozen items on the line. Set the item’s
682 // target main size to its flex base size minus a fraction of the absolute value
683 // of the remaining free space proportional to the ratio. Note this may result
684 // in a negative inner main size; it will be corrected in the next step.
685 // - Otherwise
686 // Do Nothing
687
688 if free_space.is_normal() {
689 if growing && sum_flex_grow > 0.0 {
690 for child in &mut unfrozen {
691 child.target_size.set_main(
692 constants.dir,
693 child.flex_basis + free_space * (self.nodes[child.node].style.flex_grow / sum_flex_grow),
694 );
695 }
696 } else if shrinking && sum_flex_shrink > 0.0 {
697 let sum_scaled_shrink_factor: f32 = unfrozen
698 .iter()
699 .map(|child| child.inner_flex_basis * self.nodes[child.node].style.flex_shrink)
700 .sum();
701
702 if sum_scaled_shrink_factor > 0.0 {
703 for child in &mut unfrozen {
704 let scaled_shrink_factor =
705 child.inner_flex_basis * self.nodes[child.node].style.flex_shrink;
706 child.target_size.set_main(
707 constants.dir,
708 child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor),
709 )
710 }
711 }
712 }
713 }
714
715 // d. Fix min/max violations. Clamp each non-frozen item’s target main size by its
716 // used min and max main sizes and floor its content-box size at zero. If the
717 // item’s target main size was made smaller by this, it’s a max violation.
718 // If the item’s target main size was made larger by this, it’s a min violation.
719
720 let total_violation = unfrozen.iter_mut().fold(0.0, |acc, child| -> f32 {
721 // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
722 // The following logic was developed not from the spec but by trial and error looking into how
723 // webkit handled various scenarios. Can probably be solved better by passing in
724 // min-content max-content constraints from the top. Need to figure out correct thing to do here as
725 // just piling on more conditionals.
726 let min_main = if constants.is_row && self.nodes[child.node].measure.is_none() {
727 self.compute_preliminary(child.node, Size::NONE, available_space, false, false)
728 .width
729 .maybe_min(child.size.width)
730 .maybe_max(child.min_size.width)
731 .into()
732 } else {
733 child.min_size.main(constants.dir)
734 };
735
736 let max_main = child.max_size.main(constants.dir);
737 let clamped = child.target_size.main(constants.dir).maybe_min(max_main).maybe_max(min_main).max(0.0);
738 child.violation = clamped - child.target_size.main(constants.dir);
739 child.target_size.set_main(constants.dir, clamped);
740 child.outer_target_size.set_main(
741 constants.dir,
742 child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
743 );
744
745 acc + child.violation
746 });
747
748 // e. Freeze over-flexed items. The total violation is the sum of the adjustments
749 // from the previous step ∑(clamped size - unclamped size). If the total violation is:
750 // - Zero
751 // Freeze all items.
752 // - Positive
753 // Freeze all the items with min violations.
754 // - Negative
755 // Freeze all the items with max violations.
756
757 for child in &mut unfrozen {
758 match total_violation {
759 v if v > 0.0 => child.frozen = child.violation > 0.0,
760 v if v < 0.0 => child.frozen = child.violation < 0.0,
761 _ => child.frozen = true,
762 }
763 }
764
765 // f. Return to the start of this loop.
766 }
767 }
768
769 /// Determine the hypothetical cross size of each item.
770 ///
771 /// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
772 ///
773 /// - [**Determine the hypothetical cross size of each item**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-item)
774 /// by performing layout with the used main size and the available space, treating auto as fit-content.
775 #[inline]
776 fn determine_hypothetical_cross_size(
777 &mut self,
778 line: &mut FlexLine,
779 constants: &AlgoConstants,
780 available_space: Size<Option<f32>>,
781 ) {
782 for child in line.items.iter_mut() {
783 let child_cross = child
784 .size
785 .cross(constants.dir)
786 .maybe_max(child.min_size.cross(constants.dir))
787 .maybe_min(child.max_size.cross(constants.dir));
788
789 child.hypothetical_inner_size.set_cross(
790 constants.dir,
791 self.compute_preliminary(
792 child.node,
793 Size {
794 width: if constants.is_row { child.target_size.width.into() } else { child_cross },
795 height: if constants.is_row { child_cross } else { child.target_size.height.into() },
796 },
797 Size {
798 width: if constants.is_row {
799 constants.container_size.main(constants.dir).into()
800 } else {
801 available_space.width
802 },
803 height: if constants.is_row {
804 available_space.height
805 } else {
806 constants.container_size.main(constants.dir).into()
807 },
808 },
809 false,
810 false,
811 )
812 .cross(constants.dir)
813 .maybe_max(child.min_size.cross(constants.dir))
814 .maybe_min(child.max_size.cross(constants.dir)),
815 );
816
817 child.hypothetical_outer_size.set_cross(
818 constants.dir,
819 child.hypothetical_inner_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
820 );
821 }
822 }
823
824 /// Calculate the base lines of the children.
825 #[inline]
826 fn calculate_children_base_lines(
827 &mut self,
828 node: Node,
829 node_size: Size<Option<f32>>,
830 flex_lines: &mut [FlexLine],
831 constants: &AlgoConstants,
832 ) {
833 /// Recursively calculates the baseline for children
834 fn calc_baseline(db: &Taffy, node: Node, layout: &Layout) -> f32 {
835 if db.children[node].is_empty() {
836 layout.size.height
837 } else {
838 let child = db.children[node][0];
839 calc_baseline(db, child, &db.nodes[child].layout)
840 }
841 }
842
843 for line in flex_lines {
844 for child in line.items.iter_mut() {
845 let preliminary_size = self.compute_preliminary(
846 child.node,
847 Size {
848 width: if constants.is_row {
849 child.target_size.width.into()
850 } else {
851 child.hypothetical_inner_size.width.into()
852 },
853 height: if constants.is_row {
854 child.hypothetical_inner_size.height.into()
855 } else {
856 child.target_size.height.into()
857 },
858 },
859 Size {
860 width: if constants.is_row { constants.container_size.width.into() } else { node_size.width },
861 height: if constants.is_row {
862 node_size.height
863 } else {
864 constants.container_size.height.into()
865 },
866 },
867 true,
868 false,
869 );
870
871 child.baseline = calc_baseline(
872 self,
873 child.node,
874 &Layout {
875 order: self.children[node].iter().position(|n| *n == child.node).unwrap() as u32,
876 size: preliminary_size,
877 location: Point::ZERO,
878 },
879 );
880 }
881 }
882 }
883
884 /// Calculate the cross size of each flex line.
885 ///
886 /// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
887 ///
888 /// - [**Calculate the cross size of each flex line**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-line).
889 ///
890 /// If the flex container is single-line and has a definite cross size, the cross size of the flex line is the flex container’s inner cross size.
891 ///
892 /// Otherwise, for each flex line:
893 ///
894 /// 1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose align-self is baseline, and whose cross-axis margins are both non-auto.
895 /// Find the largest of the distances between each item’s baseline and its hypothetical outer cross-start edge,
896 /// and the largest of the distances between each item’s baseline and its hypothetical outer cross-end edge, and sum these two values.
897 ///
898 /// 2. Among all the items not collected by the previous step, find the largest outer hypothetical cross size.
899 ///
900 /// 3. The used cross-size of the flex line is the largest of the numbers found in the previous two steps and zero.
901 ///
902 /// If the flex container is single-line, then clamp the line’s cross-size to be within the container’s computed min and max cross sizes.
903 /// **Note that if CSS 2.1’s definition of min/max-width/height applied more generally, this behavior would fall out automatically**.
904 #[inline]
905 fn calculate_cross_size(
906 &mut self,
907 flex_lines: &mut [FlexLine],
908 node: Node,
909 node_size: Size<Option<f32>>,
910 constants: &AlgoConstants,
911 ) {
912 if flex_lines.len() == 1 && node_size.cross(constants.dir).is_some() {
913 flex_lines[0].cross_size =
914 (node_size.cross(constants.dir).maybe_sub(constants.padding_border.cross_axis_sum(constants.dir)))
915 .unwrap_or(0.0);
916 } else {
917 for line in flex_lines.iter_mut() {
918 // 1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose
919 // align-self is baseline, and whose cross-axis margins are both non-auto. Find the
920 // largest of the distances between each item’s baseline and its hypothetical outer
921 // cross-start edge, and the largest of the distances between each item’s baseline
922 // and its hypothetical outer cross-end edge, and sum these two values.
923
924 // 2. Among all the items not collected by the previous step, find the largest
925 // outer hypothetical cross size.
926
927 // 3. The used cross-size of the flex line is the largest of the numbers found in the
928 // previous two steps and zero.
929
930 let max_baseline: f32 = line.items.iter().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
931 line.cross_size = line
932 .items
933 .iter()
934 .map(|child| {
935 let child_style = &self.nodes[child.node].style;
936 if child_style.align_self(&self.nodes[node].style) == AlignSelf::Baseline
937 && child_style.cross_margin_start(constants.dir) != Dimension::Auto
938 && child_style.cross_margin_end(constants.dir) != Dimension::Auto
939 && child_style.cross_size(constants.dir) == Dimension::Auto
940 {
941 max_baseline - child.baseline + child.hypothetical_outer_size.cross(constants.dir)
942 } else {
943 child.hypothetical_outer_size.cross(constants.dir)
944 }
945 })
946 .fold(0.0, |acc, x| acc.max(x));
947 }
948 }
949 }
950
951 /// Handle 'align-content: stretch'.
952 ///
953 /// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
954 ///
955 /// - [**Handle 'align-content: stretch'**](https://www.w3.org/TR/css-flexbox-1/#algo-line-stretch). If the flex container has a definite cross size, align-content is stretch,
956 /// and the sum of the flex lines' cross sizes is less than the flex container’s inner cross size,
957 /// increase the cross size of each flex line by equal amounts such that the sum of their cross sizes exactly equals the flex container’s inner cross size.
958 #[inline]
959 fn handle_align_content_stretch(
960 &mut self,
961 flex_lines: &mut [FlexLine],
962 node: Node,
963 node_size: Size<Option<f32>>,
964 constants: &AlgoConstants,
965 ) {
966 if self.nodes[node].style.align_content == AlignContent::Stretch && node_size.cross(constants.dir).is_some() {
967 let total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
968 let inner_cross =
969 (node_size.cross(constants.dir).maybe_sub(constants.padding_border.cross_axis_sum(constants.dir)))
970 .unwrap_or(0.0);
971
972 if total_cross < inner_cross {
973 let remaining = inner_cross - total_cross;
974 let addition = remaining / flex_lines.len() as f32;
975 flex_lines.iter_mut().for_each(|line| line.cross_size += addition);
976 }
977 }
978 }
979
980 /// Determine the used cross size of each flex item.
981 ///
982 /// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
983 ///
984 /// - [**Determine the used cross size of each flex item**](https://www.w3.org/TR/css-flexbox-1/#algo-stretch). If a flex item has align-self: stretch, its computed cross size property is auto,
985 /// and neither of its cross-axis margins are auto, the used outer cross size is the used cross size of its flex line, clamped according to the item’s used min and max cross sizes.
986 /// Otherwise, the used cross size is the item’s hypothetical cross size.
987 ///
988 /// If the flex item has align-self: stretch, redo layout for its contents, treating this used size as its definite cross size so that percentage-sized children can be resolved.
989 ///
990 /// **Note that this step does not affect the main size of the flex item, even if it has an intrinsic aspect ratio**.
991 #[inline]
992 fn determine_used_cross_size(&mut self, flex_lines: &mut [FlexLine], node: Node, constants: &AlgoConstants) {
993 for line in flex_lines {
994 let line_cross_size = line.cross_size;
995
996 for child in line.items.iter_mut() {
997 let child_style = &self.nodes[child.node].style;
998 child.target_size.set_cross(
999 constants.dir,
1000 if child_style.align_self(&self.nodes[node].style) == AlignSelf::Stretch
1001 && child_style.cross_margin_start(constants.dir) != Dimension::Auto
1002 && child_style.cross_margin_end(constants.dir) != Dimension::Auto
1003 && child_style.cross_size(constants.dir) == Dimension::Auto
1004 {
1005 (line_cross_size - child.margin.cross_axis_sum(constants.dir))
1006 .maybe_max(child.min_size.cross(constants.dir))
1007 .maybe_min(child.max_size.cross(constants.dir))
1008 } else {
1009 child.hypothetical_inner_size.cross(constants.dir)
1010 },
1011 );
1012
1013 child.outer_target_size.set_cross(
1014 constants.dir,
1015 child.target_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
1016 );
1017 }
1018 }
1019 }
1020
1021 /// Distribute any remaining free space.
1022 ///
1023 /// # [9.5. Main-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#main-alignment)
1024 ///
1025 /// - [**Distribute any remaining free space**](https://www.w3.org/TR/css-flexbox-1/#algo-main-align). For each flex line:
1026 ///
1027 /// 1. If the remaining free space is positive and at least one main-axis margin on this line is `auto`, distribute the free space equally among these margins.
1028 /// Otherwise, set all `auto` margins to zero.
1029 ///
1030 /// 2. Align the items along the main-axis per `justify-content`.
1031 #[inline]
1032 fn distribute_remaining_free_space(&mut self, flex_lines: &mut [FlexLine], node: Node, constants: &AlgoConstants) {
1033 for line in flex_lines {
1034 let used_space: f32 = line.items.iter().map(|child| child.outer_target_size.main(constants.dir)).sum();
1035 let free_space = constants.inner_container_size.main(constants.dir) - used_space;
1036 let mut num_auto_margins = 0;
1037
1038 for child in line.items.iter_mut() {
1039 let child_style = &self.nodes[child.node].style;
1040 if child_style.main_margin_start(constants.dir) == Dimension::Auto {
1041 num_auto_margins += 1;
1042 }
1043 if child_style.main_margin_end(constants.dir) == Dimension::Auto {
1044 num_auto_margins += 1;
1045 }
1046 }
1047
1048 if free_space > 0.0 && num_auto_margins > 0 {
1049 let margin = free_space / num_auto_margins as f32;
1050
1051 for child in line.items.iter_mut() {
1052 let child_style = &self.nodes[child.node].style;
1053 if child_style.main_margin_start(constants.dir) == Dimension::Auto {
1054 if constants.is_row {
1055 child.margin.start = margin;
1056 } else {
1057 child.margin.top = margin;
1058 }
1059 }
1060 if child_style.main_margin_end(constants.dir) == Dimension::Auto {
1061 if constants.is_row {
1062 child.margin.end = margin;
1063 } else {
1064 child.margin.bottom = margin;
1065 }
1066 }
1067 }
1068 } else {
1069 let num_items = line.items.len();
1070 let layout_reverse = constants.dir.is_reverse();
1071
1072 let justify_item = |(i, child): (usize, &mut FlexItem)| {
1073 let is_first = i == 0;
1074
1075 child.offset_main = match self.nodes[node].style.justify_content {
1076 JustifyContent::FlexStart => {
1077 if layout_reverse && is_first {
1078 free_space
1079 } else {
1080 0.0
1081 }
1082 }
1083 JustifyContent::Center => {
1084 if is_first {
1085 free_space / 2.0
1086 } else {
1087 0.0
1088 }
1089 }
1090 JustifyContent::FlexEnd => {
1091 if is_first && !layout_reverse {
1092 free_space
1093 } else {
1094 0.0
1095 }
1096 }
1097 JustifyContent::SpaceBetween => {
1098 if is_first {
1099 0.0
1100 } else {
1101 free_space / (num_items - 1) as f32
1102 }
1103 }
1104 JustifyContent::SpaceAround => {
1105 if is_first {
1106 (free_space / num_items as f32) / 2.0
1107 } else {
1108 free_space / num_items as f32
1109 }
1110 }
1111 JustifyContent::SpaceEvenly => free_space / (num_items + 1) as f32,
1112 };
1113 };
1114
1115 if layout_reverse {
1116 line.items.iter_mut().rev().enumerate().for_each(justify_item);
1117 } else {
1118 line.items.iter_mut().enumerate().for_each(justify_item);
1119 }
1120 }
1121 }
1122 }
1123
1124 /// Resolve cross-axis `auto` margins.
1125 ///
1126 /// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1127 ///
1128 /// - [**Resolve cross-axis `auto` margins**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-margins).
1129 /// If a flex item has auto cross-axis margins:
1130 ///
1131 /// - If its outer cross size (treating those auto margins as zero) is less than the cross size of its flex line,
1132 /// distribute the difference in those sizes equally to the auto margins.
1133 ///
1134 /// - Otherwise, if the block-start or inline-start margin (whichever is in the cross axis) is auto, set it to zero.
1135 /// Set the opposite margin so that the outer cross size of the item equals the cross size of its flex line.
1136 #[inline]
1137 fn resolve_cross_axis_auto_margins(&mut self, flex_lines: &mut [FlexLine], node: Node, constants: &AlgoConstants) {
1138 for line in flex_lines {
1139 let line_cross_size = line.cross_size;
1140 let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1141
1142 for child in line.items.iter_mut() {
1143 let free_space = line_cross_size - child.outer_target_size.cross(constants.dir);
1144 let child_style = &self.nodes[child.node].style;
1145
1146 if child_style.cross_margin_start(constants.dir) == Dimension::Auto
1147 && child_style.cross_margin_end(constants.dir) == Dimension::Auto
1148 {
1149 if constants.is_row {
1150 child.margin.top = free_space / 2.0;
1151 child.margin.bottom = free_space / 2.0;
1152 } else {
1153 child.margin.start = free_space / 2.0;
1154 child.margin.end = free_space / 2.0;
1155 }
1156 } else if child_style.cross_margin_start(constants.dir) == Dimension::Auto {
1157 if constants.is_row {
1158 child.margin.top = free_space;
1159 } else {
1160 child.margin.start = free_space;
1161 }
1162 } else if child_style.cross_margin_end(constants.dir) == Dimension::Auto {
1163 if constants.is_row {
1164 child.margin.bottom = free_space;
1165 } else {
1166 child.margin.end = free_space;
1167 }
1168 } else {
1169 // 14. Align all flex items along the cross-axis.
1170 child.offset_cross = self.align_flex_items_along_cross_axis(
1171 node,
1172 child,
1173 child_style,
1174 free_space,
1175 max_baseline,
1176 constants,
1177 );
1178 }
1179 }
1180 }
1181 }
1182
1183 /// Align all flex items along the cross-axis.
1184 ///
1185 /// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1186 ///
1187 /// - [**Align all flex items along the cross-axis**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-align) per `align-self`,
1188 /// if neither of the item's cross-axis margins are `auto`.
1189 #[inline]
1190 fn align_flex_items_along_cross_axis(
1191 &self,
1192 node: Node,
1193 child: &mut FlexItem,
1194 child_style: &FlexboxLayout,
1195 free_space: f32,
1196 max_baseline: f32,
1197 constants: &AlgoConstants,
1198 ) -> f32 {
1199 match child_style.align_self(&self.nodes[node].style) {
1200 AlignSelf::Auto => unreachable!(),
1201 AlignSelf::FlexStart => {
1202 if constants.is_wrap_reverse {
1203 free_space
1204 } else {
1205 0.0
1206 }
1207 }
1208 AlignSelf::FlexEnd => {
1209 if constants.is_wrap_reverse {
1210 0.0
1211 } else {
1212 free_space
1213 }
1214 }
1215 AlignSelf::Center => free_space / 2.0,
1216 AlignSelf::Baseline => {
1217 if constants.is_row {
1218 max_baseline - child.baseline
1219 } else {
1220 // baseline alignment only makes sense if the constants.direction is row
1221 // we treat it as flex-start alignment in columns.
1222 if constants.is_wrap_reverse {
1223 free_space
1224 } else {
1225 0.0
1226 }
1227 }
1228 }
1229 AlignSelf::Stretch => {
1230 if constants.is_wrap_reverse {
1231 free_space
1232 } else {
1233 0.0
1234 }
1235 }
1236 }
1237 }
1238
1239 /// Determine the flex container’s used cross size.
1240 ///
1241 /// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1242 ///
1243 /// - [**Determine the flex container’s used cross size**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-container):
1244 ///
1245 /// - If the cross size property is a definite size, use that, clamped by the used min and max cross sizes of the flex container.
1246 ///
1247 /// - Otherwise, use the sum of the flex lines' cross sizes, clamped by the used min and max cross sizes of the flex container.
1248 #[inline]
1249 #[must_use]
1250 fn determine_container_cross_size(
1251 flex_lines: &mut [FlexLine],
1252 node_size: Size<Option<f32>>,
1253 constants: &mut AlgoConstants,
1254 ) -> f32 {
1255 let total_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
1256
1257 constants.container_size.set_cross(
1258 constants.dir,
1259 node_size
1260 .cross(constants.dir)
1261 .unwrap_or(total_cross_size + constants.padding_border.cross_axis_sum(constants.dir)),
1262 );
1263
1264 constants.inner_container_size.set_cross(
1265 constants.dir,
1266 constants.container_size.cross(constants.dir) - constants.padding_border.cross_axis_sum(constants.dir),
1267 );
1268
1269 total_cross_size
1270 }
1271
1272 /// Align all flex lines per `align-content`.
1273 ///
1274 /// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1275 ///
1276 /// - [**Align all flex lines**](https://www.w3.org/TR/css-flexbox-1/#algo-line-align) per `align-content`.
1277 #[inline]
1278 fn align_flex_lines_per_align_content(
1279 &self,
1280 flex_lines: &mut [FlexLine],
1281 node: Node,
1282 constants: &AlgoConstants,
1283 total_cross_size: f32,
1284 ) {
1285 let free_space = constants.inner_container_size.cross(constants.dir) - total_cross_size;
1286 let num_lines = flex_lines.len();
1287
1288 let align_line = |(i, line): (usize, &mut FlexLine)| {
1289 let is_first = i == 0;
1290
1291 line.offset_cross = match self.nodes[node].style.align_content {
1292 AlignContent::FlexStart => {
1293 if is_first && constants.is_wrap_reverse {
1294 free_space
1295 } else {
1296 0.0
1297 }
1298 }
1299 AlignContent::FlexEnd => {
1300 if is_first && !constants.is_wrap_reverse {
1301 free_space
1302 } else {
1303 0.0
1304 }
1305 }
1306 AlignContent::Center => {
1307 if is_first {
1308 free_space / 2.0
1309 } else {
1310 0.0
1311 }
1312 }
1313 AlignContent::Stretch => 0.0,
1314 AlignContent::SpaceBetween => {
1315 if is_first {
1316 0.0
1317 } else {
1318 free_space / (num_lines - 1) as f32
1319 }
1320 }
1321 AlignContent::SpaceAround => {
1322 if is_first {
1323 (free_space / num_lines as f32) / 2.0
1324 } else {
1325 free_space / num_lines as f32
1326 }
1327 }
1328 };
1329 };
1330
1331 if constants.is_wrap_reverse {
1332 flex_lines.iter_mut().rev().enumerate().for_each(align_line);
1333 } else {
1334 flex_lines.iter_mut().enumerate().for_each(align_line);
1335 }
1336 }
1337
1338 /// Calculates the layout for a flex-item
1339 #[allow(clippy::too_many_arguments)]
1340 fn calculate_flex_item(
1341 &mut self,
1342 node: Node,
1343 item: &mut FlexItem,
1344 total_offset_main: &mut f32,
1345 total_offset_cross: f32,
1346 line_offset_cross: f32,
1347 container_size: Size<f32>,
1348 direction: FlexDirection,
1349 ) {
1350 let preliminary_size = self.compute_preliminary(
1351 item.node,
1352 item.target_size.map(|s| s.into()),
1353 container_size.map(|s| s.into()),
1354 true,
1355 false,
1356 );
1357
1358 let offset_main = *total_offset_main
1359 + item.offset_main
1360 + item.margin.main_start(direction)
1361 + (item.position.main_start(direction).unwrap_or(0.0) - item.position.main_end(direction).unwrap_or(0.0));
1362
1363 let offset_cross = total_offset_cross
1364 + item.offset_cross
1365 + line_offset_cross
1366 + item.margin.cross_start(direction)
1367 + (item.position.cross_start(direction).unwrap_or(0.0) - item.position.cross_end(direction).unwrap_or(0.0));
1368
1369 self.nodes[item.node].layout = Layout {
1370 order: self.children[node].iter().position(|n| *n == item.node).unwrap() as u32,
1371 size: preliminary_size,
1372 location: Point {
1373 x: if direction.is_row() { offset_main } else { offset_cross },
1374 y: if direction.is_column() { offset_main } else { offset_cross },
1375 },
1376 };
1377
1378 *total_offset_main +=
1379 item.offset_main + item.margin.main_axis_sum(direction) + preliminary_size.main(direction);
1380 }
1381
1382 /// Calculates the layout line
1383 fn calculate_layout_line(
1384 &mut self,
1385 node: Node,
1386 line: &mut FlexLine,
1387 total_offset_cross: &mut f32,
1388 container_size: Size<f32>,
1389 padding_border: Rect<f32>,
1390 direction: FlexDirection,
1391 ) {
1392 let mut total_offset_main = padding_border.main_start(direction);
1393 let line_offset_cross = line.offset_cross;
1394
1395 if direction.is_reverse() {
1396 for item in line.items.iter_mut().rev() {
1397 self.calculate_flex_item(
1398 node,
1399 item,
1400 &mut total_offset_main,
1401 *total_offset_cross,
1402 line_offset_cross,
1403 container_size,
1404 direction,
1405 );
1406 }
1407 } else {
1408 for item in line.items.iter_mut() {
1409 self.calculate_flex_item(
1410 node,
1411 item,
1412 &mut total_offset_main,
1413 *total_offset_cross,
1414 line_offset_cross,
1415 container_size,
1416 direction,
1417 );
1418 }
1419 }
1420
1421 *total_offset_cross += line_offset_cross + line.cross_size;
1422 }
1423
1424 /// Do a final layout pass and collect the resulting layouts.
1425 #[inline]
1426 fn final_layout_pass(&mut self, node: Node, flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1427 let mut total_offset_cross = constants.padding_border.cross_start(constants.dir);
1428
1429 if constants.is_wrap_reverse {
1430 for line in flex_lines.iter_mut().rev() {
1431 self.calculate_layout_line(
1432 node,
1433 line,
1434 &mut total_offset_cross,
1435 constants.container_size,
1436 constants.padding_border,
1437 constants.dir,
1438 );
1439 }
1440 } else {
1441 for line in flex_lines.iter_mut() {
1442 self.calculate_layout_line(
1443 node,
1444 line,
1445 &mut total_offset_cross,
1446 constants.container_size,
1447 constants.padding_border,
1448 constants.dir,
1449 );
1450 }
1451 }
1452 }
1453
1454 /// Perform absolute layout on all absolutely positioned children.
1455 #[inline]
1456 fn perform_absolute_layout_on_absolute_children(&mut self, node: Node, constants: &AlgoConstants) {
1457 // TODO: remove number of Vec<_> generated
1458 let candidates = self.children[node]
1459 .iter()
1460 .cloned()
1461 .enumerate()
1462 .filter(|(_, child)| self.nodes[*child].style.position_type == PositionType::Absolute)
1463 .collect::<Vec<_>>();
1464
1465 for (order, child) in candidates {
1466 let container_width = constants.container_size.width.into();
1467 let container_height = constants.container_size.height.into();
1468
1469 let child_style = self.nodes[child].style;
1470
1471 // X-axis
1472 let child_position_start = child_style.position.start.maybe_resolve(container_width);
1473 let child_margin_start = child_style.margin.start.maybe_resolve(container_width);
1474 let start = child_position_start.maybe_add(child_margin_start);
1475
1476 let child_position_end = child_style.position.end.maybe_resolve(container_width);
1477 let child_margin_end = child_style.margin.end.maybe_resolve(container_width);
1478 let end = child_position_end.maybe_add(child_margin_end);
1479
1480 // Y-axis
1481 let child_position_top = child_style.position.top.maybe_resolve(container_height);
1482 let child_margin_top = child_style.margin.top.maybe_resolve(container_height);
1483 let top = child_position_top.maybe_add(child_margin_top);
1484
1485 let child_position_bottom = child_style.position.bottom.maybe_resolve(container_height);
1486 let child_margin_bottom = child_style.margin.bottom.maybe_resolve(container_height);
1487 let bottom = child_position_bottom.maybe_add(child_margin_bottom);
1488
1489 let (start_main, end_main) = if constants.is_row { (start, end) } else { (top, bottom) };
1490 let (start_cross, end_cross) = if constants.is_row { (top, bottom) } else { (start, end) };
1491
1492 let mut width = child_style
1493 .size
1494 .width
1495 .maybe_resolve(container_width)
1496 .maybe_max(child_style.min_size.width.maybe_resolve(container_width))
1497 .maybe_min(child_style.max_size.width.maybe_resolve(container_width));
1498
1499 if width.is_none() && start.is_some() && end.is_some() {
1500 width = container_width.maybe_sub(start).maybe_sub(end);
1501 }
1502
1503 let mut height: Option<f32> = child_style
1504 .size
1505 .height
1506 .maybe_resolve(container_height)
1507 .maybe_max(child_style.min_size.height.maybe_resolve(container_height))
1508 .maybe_min(child_style.max_size.height.maybe_resolve(container_height));
1509
1510 if height.is_none() && top.is_some() && bottom.is_some() {
1511 height = container_height.maybe_sub(top).maybe_sub(bottom);
1512 }
1513
1514 let preliminary_size = self.compute_preliminary(
1515 child,
1516 Size { width, height },
1517 Size { width: container_width, height: container_height },
1518 true,
1519 false,
1520 );
1521
1522 let free_main_space = constants.container_size.main(constants.dir)
1523 - preliminary_size
1524 .main(constants.dir)
1525 .maybe_max(
1526 child_style
1527 .min_main_size(constants.dir)
1528 .maybe_resolve(constants.node_inner_size.main(constants.dir)),
1529 )
1530 .maybe_min(
1531 child_style
1532 .max_main_size(constants.dir)
1533 .maybe_resolve(constants.node_inner_size.main(constants.dir)),
1534 );
1535
1536 let free_cross_space = constants.container_size.cross(constants.dir)
1537 - preliminary_size
1538 .cross(constants.dir)
1539 .maybe_max(
1540 child_style
1541 .min_cross_size(constants.dir)
1542 .maybe_resolve(constants.node_inner_size.cross(constants.dir)),
1543 )
1544 .maybe_min(
1545 child_style
1546 .max_cross_size(constants.dir)
1547 .maybe_resolve(constants.node_inner_size.cross(constants.dir)),
1548 );
1549
1550 let offset_main = if start_main.is_some() {
1551 start_main.unwrap_or(0.0) + constants.border.main_start(constants.dir)
1552 } else if end_main.is_some() {
1553 free_main_space - end_main.unwrap_or(0.0) - constants.border.main_end(constants.dir)
1554 } else {
1555 match self.nodes[node].style.justify_content {
1556 JustifyContent::SpaceBetween | JustifyContent::FlexStart => {
1557 constants.padding_border.main_start(constants.dir)
1558 }
1559 JustifyContent::FlexEnd => free_main_space - constants.padding_border.main_end(constants.dir),
1560 JustifyContent::SpaceEvenly | JustifyContent::SpaceAround | JustifyContent::Center => {
1561 free_main_space / 2.0
1562 }
1563 }
1564 };
1565
1566 let offset_cross = if start_cross.is_some() {
1567 start_cross.unwrap_or(0.0) + constants.border.cross_start(constants.dir)
1568 } else if end_cross.is_some() {
1569 free_cross_space - end_cross.unwrap_or(0.0) - constants.border.cross_end(constants.dir)
1570 } else {
1571 match child_style.align_self(&self.nodes[node].style) {
1572 AlignSelf::Auto => unreachable!(),
1573 AlignSelf::FlexStart => {
1574 if constants.is_wrap_reverse {
1575 free_cross_space - constants.padding_border.cross_end(constants.dir)
1576 } else {
1577 constants.padding_border.cross_start(constants.dir)
1578 }
1579 }
1580 AlignSelf::FlexEnd => {
1581 if constants.is_wrap_reverse {
1582 constants.padding_border.cross_start(constants.dir)
1583 } else {
1584 free_cross_space - constants.padding_border.cross_end(constants.dir)
1585 }
1586 }
1587 AlignSelf::Center => free_cross_space / 2.0,
1588 AlignSelf::Baseline => free_cross_space / 2.0, // Treat as center for now until we have baseline support
1589 AlignSelf::Stretch => {
1590 if constants.is_wrap_reverse {
1591 free_cross_space - constants.padding_border.cross_end(constants.dir)
1592 } else {
1593 constants.padding_border.cross_start(constants.dir)
1594 }
1595 }
1596 }
1597 };
1598
1599 self.nodes[child].layout = Layout {
1600 order: order as u32,
1601 size: preliminary_size,
1602 location: Point {
1603 x: if constants.is_row { offset_main } else { offset_cross },
1604 y: if constants.is_column { offset_main } else { offset_cross },
1605 },
1606 };
1607 }
1608 }
1609
1610 /// Compute a preliminary size for an item
1611 fn compute_preliminary(
1612 &mut self,
1613 node: Node,
1614 node_size: Size<Option<f32>>,
1615 parent_size: Size<Option<f32>>,
1616 perform_layout: bool,
1617 main_size: bool,
1618 ) -> Size<f32> {
1619 self.nodes[node].is_dirty = false;
1620
1621 // First we check if we have a result for the given input
1622 if let Some(cached_size) = self.compute_from_cache(node, node_size, parent_size, perform_layout, main_size) {
1623 return cached_size;
1624 }
1625
1626 // Define some general constants we will need for the remainder of the algorithm.
1627 let mut constants = Taffy::compute_constants(&self.nodes[node], node_size, parent_size);
1628
1629 // If this is a leaf node we can skip a lot of this function in some cases
1630 if self.children[node].is_empty() {
1631 if node_size.width.is_some() && node_size.height.is_some() {
1632 return node_size.map(|s| s.unwrap_or(0.0));
1633 }
1634
1635 if let Some(ref measure) = self.nodes[node].measure {
1636 let converted_size = match measure {
1637 MeasureFunc::Raw(measure) => measure(node_size),
1638
1639 #[cfg(any(feature = "std", feature = "alloc"))]
1640 MeasureFunc::Boxed(measure) => measure(node_size),
1641 };
1642 *self.cache(node, main_size) =
1643 Some(Cache { node_size, parent_size, perform_layout, size: converted_size });
1644 return converted_size;
1645 }
1646
1647 return Size {
1648 width: node_size.width.unwrap_or(0.0) + constants.padding_border.horizontal_axis_sum(),
1649 height: node_size.height.unwrap_or(0.0) + constants.padding_border.vertical_axis_sum(),
1650 };
1651 }
1652
1653 // 9. Flex Layout Algorithm
1654
1655 // 9.1. Initial Setup
1656
1657 // 1. Generate anonymous flex items as described in §4 Flex Items.
1658 let mut flex_items = self.generate_anonymous_flex_items(node, &constants);
1659
1660 // 9.2. Line Length Determination
1661
1662 // 2. Determine the available main and cross space for the flex items.
1663 let available_space = Self::determine_available_space(node_size, parent_size, &constants);
1664
1665 let has_baseline_child = flex_items
1666 .iter()
1667 .any(|child| self.nodes[child.node].style.align_self(&self.nodes[node].style) == AlignSelf::Baseline);
1668
1669 // 3. Determine the flex base size and hypothetical main size of each item.
1670 self.determine_flex_base_size(node, node_size, &constants, available_space, &mut flex_items);
1671
1672 // TODO: Add step 4 according to spec: https://www.w3.org/TR/css-flexbox-1/#algo-main-container
1673 // 9.3. Main Size Determination
1674
1675 // 5. Collect flex items into flex lines.
1676 let mut flex_lines = self.collect_flex_lines(node, &constants, available_space, &mut flex_items);
1677
1678 // 6. Resolve the flexible lengths of all the flex items to find their used main size.
1679 for line in &mut flex_lines {
1680 self.resolve_flexible_lengths(line, &constants, available_space);
1681 }
1682
1683 // TODO: Cleanup and make according to spec
1684 // Not part of the spec from what i can see but seems correct
1685 constants.container_size.set_main(
1686 constants.dir,
1687 node_size.main(constants.dir).unwrap_or({
1688 let longest_line = flex_lines.iter().fold(f32::MIN, |acc, line| {
1689 let length: f32 = line.items.iter().map(|item| item.outer_target_size.main(constants.dir)).sum();
1690 acc.max(length)
1691 });
1692
1693 let size = longest_line + constants.padding_border.main_axis_sum(constants.dir);
1694 match available_space.main(constants.dir) {
1695 Some(val) if flex_lines.len() > 1 && size < val => val,
1696 _ => size,
1697 }
1698 }),
1699 );
1700
1701 constants.inner_container_size.set_main(
1702 constants.dir,
1703 constants.container_size.main(constants.dir) - constants.padding_border.main_axis_sum(constants.dir),
1704 );
1705
1706 // 9.4. Cross Size Determination
1707
1708 // 7. Determine the hypothetical cross size of each item.
1709 for line in &mut flex_lines {
1710 self.determine_hypothetical_cross_size(line, &constants, available_space);
1711 }
1712
1713 // TODO - probably should move this somewhere else as it doesn't make a ton of sense here but we need it below
1714 // TODO - This is expensive and should only be done if we really require a baseline. aka, make it lazy
1715 if has_baseline_child {
1716 self.calculate_children_base_lines(node, node_size, &mut flex_lines, &constants);
1717 }
1718
1719 // 8. Calculate the cross size of each flex line.
1720 self.calculate_cross_size(&mut flex_lines, node, node_size, &constants);
1721
1722 // 9. Handle 'align-content: stretch'.
1723 self.handle_align_content_stretch(&mut flex_lines, node, node_size, &constants);
1724
1725 // 10. Collapse visibility:collapse items. If any flex items have visibility: collapse,
1726 // note the cross size of the line they’re in as the item’s strut size, and restart
1727 // layout from the beginning.
1728 //
1729 // In this second layout round, when collecting items into lines, treat the collapsed
1730 // items as having zero main size. For the rest of the algorithm following that step,
1731 // ignore the collapsed items entirely (as if they were display:none) except that after
1732 // calculating the cross size of the lines, if any line’s cross size is less than the
1733 // largest strut size among all the collapsed items in the line, set its cross size to
1734 // that strut size.
1735 //
1736 // Skip this step in the second layout round.
1737
1738 // TODO implement once (if ever) we support visibility:collapse
1739
1740 // 11. Determine the used cross size of each flex item.
1741 self.determine_used_cross_size(&mut flex_lines, node, &constants);
1742
1743 // 9.5. Main-Axis Alignment
1744
1745 // 12. Distribute any remaining free space.
1746 self.distribute_remaining_free_space(&mut flex_lines, node, &constants);
1747
1748 // 9.6. Cross-Axis Alignment
1749
1750 // 13. Resolve cross-axis auto margins (also includes 14).
1751 self.resolve_cross_axis_auto_margins(&mut flex_lines, node, &constants);
1752
1753 // 15. Determine the flex container’s used cross size.
1754 let total_cross_size = Self::determine_container_cross_size(&mut flex_lines, node_size, &mut constants);
1755
1756 // We have the container size.
1757 // If our caller does not care about performing layout we are done now.
1758 if !perform_layout {
1759 let container_size = constants.container_size;
1760 *self.cache(node, main_size) = Some(Cache { node_size, parent_size, perform_layout, size: container_size });
1761 return container_size;
1762 }
1763
1764 // 16. Align all flex lines per align-content.
1765 self.align_flex_lines_per_align_content(&mut flex_lines, node, &constants, total_cross_size);
1766
1767 // Do a final layout pass and gather the resulting layouts
1768 self.final_layout_pass(node, &mut flex_lines, &constants);
1769
1770 // Before returning we perform absolute layout on all absolutely positioned children
1771 self.perform_absolute_layout_on_absolute_children(node, &constants);
1772
1773 // Recursively hide all nodes set to Display::None
1774 for (order, child) in self.children[node].iter().enumerate() {
1775 if self.nodes[*child].style.display == Display::None {
1776 hidden_layout(&mut self.nodes, &self.children, *child, order as _);
1777 }
1778 }
1779
1780 let container_size = constants.container_size;
1781 *self.cache(node, main_size) = Some(Cache { node_size, parent_size, perform_layout, size: container_size });
1782
1783 container_size
1784 }
1785}
1786
1787/// Creates a zero-sized layout for this node and its children, recursively.
1788fn hidden_layout(
1789 nodes: &mut SlotMap<Node, NodeData>,
1790 children: &SlotMap<Node, ChildrenVec<Node>>,
1791 node: Node,
1792 order: u32,
1793) {
1794 nodes[node].layout = Layout::with_order(order);
1795
1796 for (order, child) in children[node].iter().enumerate() {
1797 hidden_layout(nodes, children, *child, order as _);
1798 }
1799}
1800
1801#[cfg(test)]
1802mod tests {
1803 use crate::flexbox::hidden_layout;
1804 use crate::geometry::Point;
1805 use crate::style::Display;
1806 use crate::style::Display::Flex;
1807 use crate::{
1808 math::MaybeMath,
1809 prelude::{Rect, Size},
1810 resolve::ResolveOrDefault,
1811 style::{FlexWrap, FlexboxLayout},
1812 Taffy,
1813 };
1814
1815 // Make sure we get correct constants
1816 #[test]
1817 fn correct_constants() {
1818 let mut tree = Taffy::with_capacity(16);
1819
1820 let style = FlexboxLayout::default();
1821 let node_id = tree.new_leaf(style).unwrap();
1822
1823 let node_size = Size::NONE;
1824 let parent_size = Size::NONE;
1825
1826 let constants = Taffy::compute_constants(&tree.nodes[node_id], node_size, parent_size);
1827
1828 assert!(constants.dir == style.flex_direction);
1829 assert!(constants.is_row == style.flex_direction.is_row());
1830 assert!(constants.is_column == style.flex_direction.is_column());
1831 assert!(constants.is_wrap_reverse == (style.flex_wrap == FlexWrap::WrapReverse));
1832
1833 let margin = style.margin.resolve_or_default(parent_size);
1834 assert_eq!(constants.margin, margin);
1835
1836 let border = style.border.resolve_or_default(parent_size);
1837 assert_eq!(constants.border, border);
1838
1839 let padding = style.padding.resolve_or_default(parent_size);
1840
1841 // TODO: Replace with something less hardcoded?
1842 let padding_border = Rect {
1843 start: padding.start + border.start,
1844 end: padding.end + border.end,
1845 top: padding.top + border.top,
1846 bottom: padding.bottom + border.bottom,
1847 };
1848
1849 assert_eq!(constants.padding_border, padding_border);
1850
1851 // TODO: Replace with something less hardcoded?
1852 let inner_size = Size {
1853 width: node_size.width.maybe_sub(padding_border.horizontal_axis_sum()),
1854 height: node_size.height.maybe_sub(padding_border.vertical_axis_sum()),
1855 };
1856 assert_eq!(constants.node_inner_size, inner_size);
1857
1858 assert_eq!(constants.container_size, Size::ZERO);
1859 assert_eq!(constants.inner_container_size, Size::ZERO);
1860 }
1861
1862 #[test]
1863 fn hidden_layout_should_hide_recursively() {
1864 let mut taffy = Taffy::new();
1865
1866 let style: FlexboxLayout =
1867 FlexboxLayout { display: Flex, size: Size::from_points(50.0, 50.0), ..Default::default() };
1868
1869 let grandchild_00 = taffy.new_leaf(style).unwrap();
1870
1871 let grandchild_01 = taffy.new_leaf(style).unwrap();
1872
1873 let grandchild_02 = taffy.new_leaf(style).unwrap();
1874
1875 let child_00 = taffy.new_with_children(style, &[grandchild_00, grandchild_01]).unwrap();
1876
1877 let child_01 = taffy.new_with_children(style, &[grandchild_02]).unwrap();
1878
1879 let root = taffy
1880 .new_with_children(
1881 FlexboxLayout { display: Display::None, size: Size::from_points(50.0, 50.0), ..Default::default() },
1882 &[child_00, child_01],
1883 )
1884 .unwrap();
1885
1886 hidden_layout(&mut taffy.nodes, &taffy.children, root, 0);
1887
1888 // Whatever size and display-mode the nodes had previously,
1889 // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
1890 for (node, _) in &taffy.nodes {
1891 if let Ok(layout) = taffy.layout(node) {
1892 assert_eq!(layout.size, Size::ZERO);
1893 assert_eq!(layout.location, Point::ZERO);
1894 }
1895 }
1896 }
1897}