Add support for align-items: space-evenly (#218)
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::SpaceEvenly => free_space / (num_lines + 1) as f32,
1315 AlignContent::SpaceBetween => {
1316 if is_first {
1317 0.0
1318 } else {
1319 free_space / (num_lines - 1) as f32
1320 }
1321 }
1322 AlignContent::SpaceAround => {
1323 if is_first {
1324 (free_space / num_lines as f32) / 2.0
1325 } else {
1326 free_space / num_lines as f32
1327 }
1328 }
1329 };
1330 };
1331
1332 if constants.is_wrap_reverse {
1333 flex_lines.iter_mut().rev().enumerate().for_each(align_line);
1334 } else {
1335 flex_lines.iter_mut().enumerate().for_each(align_line);
1336 }
1337 }
1338
1339 /// Calculates the layout for a flex-item
1340 #[allow(clippy::too_many_arguments)]
1341 fn calculate_flex_item(
1342 &mut self,
1343 node: Node,
1344 item: &mut FlexItem,
1345 total_offset_main: &mut f32,
1346 total_offset_cross: f32,
1347 line_offset_cross: f32,
1348 container_size: Size<f32>,
1349 direction: FlexDirection,
1350 ) {
1351 let preliminary_size = self.compute_preliminary(
1352 item.node,
1353 item.target_size.map(|s| s.into()),
1354 container_size.map(|s| s.into()),
1355 true,
1356 false,
1357 );
1358
1359 let offset_main = *total_offset_main
1360 + item.offset_main
1361 + item.margin.main_start(direction)
1362 + (item.position.main_start(direction).unwrap_or(0.0) - item.position.main_end(direction).unwrap_or(0.0));
1363
1364 let offset_cross = total_offset_cross
1365 + item.offset_cross
1366 + line_offset_cross
1367 + item.margin.cross_start(direction)
1368 + (item.position.cross_start(direction).unwrap_or(0.0) - item.position.cross_end(direction).unwrap_or(0.0));
1369
1370 self.nodes[item.node].layout = Layout {
1371 order: self.children[node].iter().position(|n| *n == item.node).unwrap() as u32,
1372 size: preliminary_size,
1373 location: Point {
1374 x: if direction.is_row() { offset_main } else { offset_cross },
1375 y: if direction.is_column() { offset_main } else { offset_cross },
1376 },
1377 };
1378
1379 *total_offset_main +=
1380 item.offset_main + item.margin.main_axis_sum(direction) + preliminary_size.main(direction);
1381 }
1382
1383 /// Calculates the layout line
1384 fn calculate_layout_line(
1385 &mut self,
1386 node: Node,
1387 line: &mut FlexLine,
1388 total_offset_cross: &mut f32,
1389 container_size: Size<f32>,
1390 padding_border: Rect<f32>,
1391 direction: FlexDirection,
1392 ) {
1393 let mut total_offset_main = padding_border.main_start(direction);
1394 let line_offset_cross = line.offset_cross;
1395
1396 if direction.is_reverse() {
1397 for item in line.items.iter_mut().rev() {
1398 self.calculate_flex_item(
1399 node,
1400 item,
1401 &mut total_offset_main,
1402 *total_offset_cross,
1403 line_offset_cross,
1404 container_size,
1405 direction,
1406 );
1407 }
1408 } else {
1409 for item in line.items.iter_mut() {
1410 self.calculate_flex_item(
1411 node,
1412 item,
1413 &mut total_offset_main,
1414 *total_offset_cross,
1415 line_offset_cross,
1416 container_size,
1417 direction,
1418 );
1419 }
1420 }
1421
1422 *total_offset_cross += line_offset_cross + line.cross_size;
1423 }
1424
1425 /// Do a final layout pass and collect the resulting layouts.
1426 #[inline]
1427 fn final_layout_pass(&mut self, node: Node, flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1428 let mut total_offset_cross = constants.padding_border.cross_start(constants.dir);
1429
1430 if constants.is_wrap_reverse {
1431 for line in flex_lines.iter_mut().rev() {
1432 self.calculate_layout_line(
1433 node,
1434 line,
1435 &mut total_offset_cross,
1436 constants.container_size,
1437 constants.padding_border,
1438 constants.dir,
1439 );
1440 }
1441 } else {
1442 for line in flex_lines.iter_mut() {
1443 self.calculate_layout_line(
1444 node,
1445 line,
1446 &mut total_offset_cross,
1447 constants.container_size,
1448 constants.padding_border,
1449 constants.dir,
1450 );
1451 }
1452 }
1453 }
1454
1455 /// Perform absolute layout on all absolutely positioned children.
1456 #[inline]
1457 fn perform_absolute_layout_on_absolute_children(&mut self, node: Node, constants: &AlgoConstants) {
1458 // TODO: remove number of Vec<_> generated
1459 let candidates = self.children[node]
1460 .iter()
1461 .cloned()
1462 .enumerate()
1463 .filter(|(_, child)| self.nodes[*child].style.position_type == PositionType::Absolute)
1464 .collect::<Vec<_>>();
1465
1466 for (order, child) in candidates {
1467 let container_width = constants.container_size.width.into();
1468 let container_height = constants.container_size.height.into();
1469
1470 let child_style = self.nodes[child].style;
1471
1472 // X-axis
1473 let child_position_start = child_style.position.start.maybe_resolve(container_width);
1474 let child_margin_start = child_style.margin.start.maybe_resolve(container_width);
1475 let start = child_position_start.maybe_add(child_margin_start);
1476
1477 let child_position_end = child_style.position.end.maybe_resolve(container_width);
1478 let child_margin_end = child_style.margin.end.maybe_resolve(container_width);
1479 let end = child_position_end.maybe_add(child_margin_end);
1480
1481 // Y-axis
1482 let child_position_top = child_style.position.top.maybe_resolve(container_height);
1483 let child_margin_top = child_style.margin.top.maybe_resolve(container_height);
1484 let top = child_position_top.maybe_add(child_margin_top);
1485
1486 let child_position_bottom = child_style.position.bottom.maybe_resolve(container_height);
1487 let child_margin_bottom = child_style.margin.bottom.maybe_resolve(container_height);
1488 let bottom = child_position_bottom.maybe_add(child_margin_bottom);
1489
1490 let (start_main, end_main) = if constants.is_row { (start, end) } else { (top, bottom) };
1491 let (start_cross, end_cross) = if constants.is_row { (top, bottom) } else { (start, end) };
1492
1493 let mut width = child_style
1494 .size
1495 .width
1496 .maybe_resolve(container_width)
1497 .maybe_max(child_style.min_size.width.maybe_resolve(container_width))
1498 .maybe_min(child_style.max_size.width.maybe_resolve(container_width));
1499
1500 if width.is_none() && start.is_some() && end.is_some() {
1501 width = container_width.maybe_sub(start).maybe_sub(end);
1502 }
1503
1504 let mut height: Option<f32> = child_style
1505 .size
1506 .height
1507 .maybe_resolve(container_height)
1508 .maybe_max(child_style.min_size.height.maybe_resolve(container_height))
1509 .maybe_min(child_style.max_size.height.maybe_resolve(container_height));
1510
1511 if height.is_none() && top.is_some() && bottom.is_some() {
1512 height = container_height.maybe_sub(top).maybe_sub(bottom);
1513 }
1514
1515 let preliminary_size = self.compute_preliminary(
1516 child,
1517 Size { width, height },
1518 Size { width: container_width, height: container_height },
1519 true,
1520 false,
1521 );
1522
1523 let free_main_space = constants.container_size.main(constants.dir)
1524 - preliminary_size
1525 .main(constants.dir)
1526 .maybe_max(
1527 child_style
1528 .min_main_size(constants.dir)
1529 .maybe_resolve(constants.node_inner_size.main(constants.dir)),
1530 )
1531 .maybe_min(
1532 child_style
1533 .max_main_size(constants.dir)
1534 .maybe_resolve(constants.node_inner_size.main(constants.dir)),
1535 );
1536
1537 let free_cross_space = constants.container_size.cross(constants.dir)
1538 - preliminary_size
1539 .cross(constants.dir)
1540 .maybe_max(
1541 child_style
1542 .min_cross_size(constants.dir)
1543 .maybe_resolve(constants.node_inner_size.cross(constants.dir)),
1544 )
1545 .maybe_min(
1546 child_style
1547 .max_cross_size(constants.dir)
1548 .maybe_resolve(constants.node_inner_size.cross(constants.dir)),
1549 );
1550
1551 let offset_main = if start_main.is_some() {
1552 start_main.unwrap_or(0.0) + constants.border.main_start(constants.dir)
1553 } else if end_main.is_some() {
1554 free_main_space - end_main.unwrap_or(0.0) - constants.border.main_end(constants.dir)
1555 } else {
1556 match self.nodes[node].style.justify_content {
1557 JustifyContent::SpaceBetween | JustifyContent::FlexStart => {
1558 constants.padding_border.main_start(constants.dir)
1559 }
1560 JustifyContent::FlexEnd => free_main_space - constants.padding_border.main_end(constants.dir),
1561 JustifyContent::SpaceEvenly | JustifyContent::SpaceAround | JustifyContent::Center => {
1562 free_main_space / 2.0
1563 }
1564 }
1565 };
1566
1567 let offset_cross = if start_cross.is_some() {
1568 start_cross.unwrap_or(0.0) + constants.border.cross_start(constants.dir)
1569 } else if end_cross.is_some() {
1570 free_cross_space - end_cross.unwrap_or(0.0) - constants.border.cross_end(constants.dir)
1571 } else {
1572 match child_style.align_self(&self.nodes[node].style) {
1573 AlignSelf::Auto => unreachable!(),
1574 AlignSelf::FlexStart => {
1575 if constants.is_wrap_reverse {
1576 free_cross_space - constants.padding_border.cross_end(constants.dir)
1577 } else {
1578 constants.padding_border.cross_start(constants.dir)
1579 }
1580 }
1581 AlignSelf::FlexEnd => {
1582 if constants.is_wrap_reverse {
1583 constants.padding_border.cross_start(constants.dir)
1584 } else {
1585 free_cross_space - constants.padding_border.cross_end(constants.dir)
1586 }
1587 }
1588 AlignSelf::Center => free_cross_space / 2.0,
1589 AlignSelf::Baseline => free_cross_space / 2.0, // Treat as center for now until we have baseline support
1590 AlignSelf::Stretch => {
1591 if constants.is_wrap_reverse {
1592 free_cross_space - constants.padding_border.cross_end(constants.dir)
1593 } else {
1594 constants.padding_border.cross_start(constants.dir)
1595 }
1596 }
1597 }
1598 };
1599
1600 self.nodes[child].layout = Layout {
1601 order: order as u32,
1602 size: preliminary_size,
1603 location: Point {
1604 x: if constants.is_row { offset_main } else { offset_cross },
1605 y: if constants.is_column { offset_main } else { offset_cross },
1606 },
1607 };
1608 }
1609 }
1610
1611 /// Compute a preliminary size for an item
1612 fn compute_preliminary(
1613 &mut self,
1614 node: Node,
1615 node_size: Size<Option<f32>>,
1616 parent_size: Size<Option<f32>>,
1617 perform_layout: bool,
1618 main_size: bool,
1619 ) -> Size<f32> {
1620 self.nodes[node].is_dirty = false;
1621
1622 // First we check if we have a result for the given input
1623 if let Some(cached_size) = self.compute_from_cache(node, node_size, parent_size, perform_layout, main_size) {
1624 return cached_size;
1625 }
1626
1627 // Define some general constants we will need for the remainder of the algorithm.
1628 let mut constants = Taffy::compute_constants(&self.nodes[node], node_size, parent_size);
1629
1630 // If this is a leaf node we can skip a lot of this function in some cases
1631 if self.children[node].is_empty() {
1632 if node_size.width.is_some() && node_size.height.is_some() {
1633 return node_size.map(|s| s.unwrap_or(0.0));
1634 }
1635
1636 if let Some(ref measure) = self.nodes[node].measure {
1637 let converted_size = match measure {
1638 MeasureFunc::Raw(measure) => measure(node_size),
1639
1640 #[cfg(any(feature = "std", feature = "alloc"))]
1641 MeasureFunc::Boxed(measure) => measure(node_size),
1642 };
1643 *self.cache(node, main_size) =
1644 Some(Cache { node_size, parent_size, perform_layout, size: converted_size });
1645 return converted_size;
1646 }
1647
1648 return Size {
1649 width: node_size.width.unwrap_or(0.0) + constants.padding_border.horizontal_axis_sum(),
1650 height: node_size.height.unwrap_or(0.0) + constants.padding_border.vertical_axis_sum(),
1651 };
1652 }
1653
1654 // 9. Flex Layout Algorithm
1655
1656 // 9.1. Initial Setup
1657
1658 // 1. Generate anonymous flex items as described in §4 Flex Items.
1659 let mut flex_items = self.generate_anonymous_flex_items(node, &constants);
1660
1661 // 9.2. Line Length Determination
1662
1663 // 2. Determine the available main and cross space for the flex items.
1664 let available_space = Self::determine_available_space(node_size, parent_size, &constants);
1665
1666 let has_baseline_child = flex_items
1667 .iter()
1668 .any(|child| self.nodes[child.node].style.align_self(&self.nodes[node].style) == AlignSelf::Baseline);
1669
1670 // 3. Determine the flex base size and hypothetical main size of each item.
1671 self.determine_flex_base_size(node, node_size, &constants, available_space, &mut flex_items);
1672
1673 // TODO: Add step 4 according to spec: https://www.w3.org/TR/css-flexbox-1/#algo-main-container
1674 // 9.3. Main Size Determination
1675
1676 // 5. Collect flex items into flex lines.
1677 let mut flex_lines = self.collect_flex_lines(node, &constants, available_space, &mut flex_items);
1678
1679 // 6. Resolve the flexible lengths of all the flex items to find their used main size.
1680 for line in &mut flex_lines {
1681 self.resolve_flexible_lengths(line, &constants, available_space);
1682 }
1683
1684 // TODO: Cleanup and make according to spec
1685 // Not part of the spec from what i can see but seems correct
1686 constants.container_size.set_main(
1687 constants.dir,
1688 node_size.main(constants.dir).unwrap_or({
1689 let longest_line = flex_lines.iter().fold(f32::MIN, |acc, line| {
1690 let length: f32 = line.items.iter().map(|item| item.outer_target_size.main(constants.dir)).sum();
1691 acc.max(length)
1692 });
1693
1694 let size = longest_line + constants.padding_border.main_axis_sum(constants.dir);
1695 match available_space.main(constants.dir) {
1696 Some(val) if flex_lines.len() > 1 && size < val => val,
1697 _ => size,
1698 }
1699 }),
1700 );
1701
1702 constants.inner_container_size.set_main(
1703 constants.dir,
1704 constants.container_size.main(constants.dir) - constants.padding_border.main_axis_sum(constants.dir),
1705 );
1706
1707 // 9.4. Cross Size Determination
1708
1709 // 7. Determine the hypothetical cross size of each item.
1710 for line in &mut flex_lines {
1711 self.determine_hypothetical_cross_size(line, &constants, available_space);
1712 }
1713
1714 // TODO - probably should move this somewhere else as it doesn't make a ton of sense here but we need it below
1715 // TODO - This is expensive and should only be done if we really require a baseline. aka, make it lazy
1716 if has_baseline_child {
1717 self.calculate_children_base_lines(node, node_size, &mut flex_lines, &constants);
1718 }
1719
1720 // 8. Calculate the cross size of each flex line.
1721 self.calculate_cross_size(&mut flex_lines, node, node_size, &constants);
1722
1723 // 9. Handle 'align-content: stretch'.
1724 self.handle_align_content_stretch(&mut flex_lines, node, node_size, &constants);
1725
1726 // 10. Collapse visibility:collapse items. If any flex items have visibility: collapse,
1727 // note the cross size of the line they’re in as the item’s strut size, and restart
1728 // layout from the beginning.
1729 //
1730 // In this second layout round, when collecting items into lines, treat the collapsed
1731 // items as having zero main size. For the rest of the algorithm following that step,
1732 // ignore the collapsed items entirely (as if they were display:none) except that after
1733 // calculating the cross size of the lines, if any line’s cross size is less than the
1734 // largest strut size among all the collapsed items in the line, set its cross size to
1735 // that strut size.
1736 //
1737 // Skip this step in the second layout round.
1738
1739 // TODO implement once (if ever) we support visibility:collapse
1740
1741 // 11. Determine the used cross size of each flex item.
1742 self.determine_used_cross_size(&mut flex_lines, node, &constants);
1743
1744 // 9.5. Main-Axis Alignment
1745
1746 // 12. Distribute any remaining free space.
1747 self.distribute_remaining_free_space(&mut flex_lines, node, &constants);
1748
1749 // 9.6. Cross-Axis Alignment
1750
1751 // 13. Resolve cross-axis auto margins (also includes 14).
1752 self.resolve_cross_axis_auto_margins(&mut flex_lines, node, &constants);
1753
1754 // 15. Determine the flex container’s used cross size.
1755 let total_cross_size = Self::determine_container_cross_size(&mut flex_lines, node_size, &mut constants);
1756
1757 // We have the container size.
1758 // If our caller does not care about performing layout we are done now.
1759 if !perform_layout {
1760 let container_size = constants.container_size;
1761 *self.cache(node, main_size) = Some(Cache { node_size, parent_size, perform_layout, size: container_size });
1762 return container_size;
1763 }
1764
1765 // 16. Align all flex lines per align-content.
1766 self.align_flex_lines_per_align_content(&mut flex_lines, node, &constants, total_cross_size);
1767
1768 // Do a final layout pass and gather the resulting layouts
1769 self.final_layout_pass(node, &mut flex_lines, &constants);
1770
1771 // Before returning we perform absolute layout on all absolutely positioned children
1772 self.perform_absolute_layout_on_absolute_children(node, &constants);
1773
1774 // Recursively hide all nodes set to Display::None
1775 for (order, child) in self.children[node].iter().enumerate() {
1776 if self.nodes[*child].style.display == Display::None {
1777 hidden_layout(&mut self.nodes, &self.children, *child, order as _);
1778 }
1779 }
1780
1781 let container_size = constants.container_size;
1782 *self.cache(node, main_size) = Some(Cache { node_size, parent_size, perform_layout, size: container_size });
1783
1784 container_size
1785 }
1786}
1787
1788/// Creates a zero-sized layout for this node and its children, recursively.
1789fn hidden_layout(
1790 nodes: &mut SlotMap<Node, NodeData>,
1791 children: &SlotMap<Node, ChildrenVec<Node>>,
1792 node: Node,
1793 order: u32,
1794) {
1795 nodes[node].layout = Layout::with_order(order);
1796
1797 for (order, child) in children[node].iter().enumerate() {
1798 hidden_layout(nodes, children, *child, order as _);
1799 }
1800}
1801
1802#[cfg(test)]
1803mod tests {
1804 use crate::flexbox::hidden_layout;
1805 use crate::geometry::Point;
1806 use crate::style::Display;
1807 use crate::style::Display::Flex;
1808 use crate::{
1809 math::MaybeMath,
1810 prelude::{Rect, Size},
1811 resolve::ResolveOrDefault,
1812 style::{FlexWrap, FlexboxLayout},
1813 Taffy,
1814 };
1815
1816 // Make sure we get correct constants
1817 #[test]
1818 fn correct_constants() {
1819 let mut tree = Taffy::with_capacity(16);
1820
1821 let style = FlexboxLayout::default();
1822 let node_id = tree.new_leaf(style).unwrap();
1823
1824 let node_size = Size::NONE;
1825 let parent_size = Size::NONE;
1826
1827 let constants = Taffy::compute_constants(&tree.nodes[node_id], node_size, parent_size);
1828
1829 assert!(constants.dir == style.flex_direction);
1830 assert!(constants.is_row == style.flex_direction.is_row());
1831 assert!(constants.is_column == style.flex_direction.is_column());
1832 assert!(constants.is_wrap_reverse == (style.flex_wrap == FlexWrap::WrapReverse));
1833
1834 let margin = style.margin.resolve_or_default(parent_size);
1835 assert_eq!(constants.margin, margin);
1836
1837 let border = style.border.resolve_or_default(parent_size);
1838 assert_eq!(constants.border, border);
1839
1840 let padding = style.padding.resolve_or_default(parent_size);
1841
1842 // TODO: Replace with something less hardcoded?
1843 let padding_border = Rect {
1844 start: padding.start + border.start,
1845 end: padding.end + border.end,
1846 top: padding.top + border.top,
1847 bottom: padding.bottom + border.bottom,
1848 };
1849
1850 assert_eq!(constants.padding_border, padding_border);
1851
1852 // TODO: Replace with something less hardcoded?
1853 let inner_size = Size {
1854 width: node_size.width.maybe_sub(padding_border.horizontal_axis_sum()),
1855 height: node_size.height.maybe_sub(padding_border.vertical_axis_sum()),
1856 };
1857 assert_eq!(constants.node_inner_size, inner_size);
1858
1859 assert_eq!(constants.container_size, Size::ZERO);
1860 assert_eq!(constants.inner_container_size, Size::ZERO);
1861 }
1862
1863 #[test]
1864 fn hidden_layout_should_hide_recursively() {
1865 let mut taffy = Taffy::new();
1866
1867 let style: FlexboxLayout =
1868 FlexboxLayout { display: Flex, size: Size::from_points(50.0, 50.0), ..Default::default() };
1869
1870 let grandchild_00 = taffy.new_leaf(style).unwrap();
1871
1872 let grandchild_01 = taffy.new_leaf(style).unwrap();
1873
1874 let grandchild_02 = taffy.new_leaf(style).unwrap();
1875
1876 let child_00 = taffy.new_with_children(style, &[grandchild_00, grandchild_01]).unwrap();
1877
1878 let child_01 = taffy.new_with_children(style, &[grandchild_02]).unwrap();
1879
1880 let root = taffy
1881 .new_with_children(
1882 FlexboxLayout { display: Display::None, size: Size::from_points(50.0, 50.0), ..Default::default() },
1883 &[child_00, child_01],
1884 )
1885 .unwrap();
1886
1887 hidden_layout(&mut taffy.nodes, &taffy.children, root, 0);
1888
1889 // Whatever size and display-mode the nodes had previously,
1890 // all layouts should resolve to ZERO due to the root's DISPLAY::NONE
1891 for (node, _) in &taffy.nodes {
1892 if let Ok(layout) = taffy.layout(node) {
1893 assert_eq!(layout.size, Size::ZERO);
1894 assert_eq!(layout.location, Point::ZERO);
1895 }
1896 }
1897 }
1898}