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