1 #[cfg(not(feature = "std"))] 2 use alloc::{vec, vec::Vec}; 3 #[cfg(not(feature = "std"))] 4 use libm::F32Ext; 5 6 use core::any::Any; 7 use core::f32; 8 9 use crate::forest::{Forest, NodeData}; 10 use crate::id::NodeId; 11 use crate::result; 12 use crate::style::*; 13 14 use crate::number::Number::*; 15 use crate::number::*; 16 17 use crate::geometry::{Point, Rect, Size}; 18 19 #[derive(Debug, Clone)] 20 pub struct ComputeResult { 21 pub size: Size<f32>, 22 } 23 24 struct FlexItem { 25 node: NodeId, 26 27 size: Size<Number>, 28 min_size: Size<Number>, 29 max_size: Size<Number>, 30 31 position: Rect<Number>, 32 margin: Rect<f32>, 33 padding: Rect<f32>, 34 border: Rect<f32>, 35 36 flex_basis: f32, 37 inner_flex_basis: f32, 38 violation: f32, 39 frozen: bool, 40 41 hypothetical_inner_size: Size<f32>, 42 hypothetical_outer_size: Size<f32>, 43 target_size: Size<f32>, 44 outer_target_size: Size<f32>, 45 46 baseline: f32, 47 48 // temporary values for holding offset in the main / cross direction. 49 // offset is the relative position from the item's natural flow position based on 50 // relative position values, alignment, and justification. Does not include margin/padding/border. 51 offset_main: f32, 52 offset_cross: f32, 53 } 54 55 struct FlexLine { 56 items: Vec<FlexItem>, 57 cross_size: f32, 58 offset_cross: f32, 59 } 60 61 impl Forest { compute(&mut self, root: NodeId, size: Size<Number>) -> Result<(), Box<Any>>62 pub(crate) fn compute(&mut self, root: NodeId, size: Size<Number>) -> Result<(), Box<Any>> { 63 let style = self.nodes[root].style; 64 let has_root_min_max = style.min_size.width.is_defined() 65 || style.min_size.height.is_defined() 66 || style.max_size.width.is_defined() 67 || style.max_size.height.is_defined(); 68 69 let result = if has_root_min_max { 70 let first_pass = self.compute_internal( 71 root, 72 Size { width: style.size.width.resolve(size.width), height: style.size.height.resolve(size.height) }, 73 size, 74 false, 75 )?; 76 77 self.compute_internal( 78 root, 79 Size { 80 width: first_pass 81 .size 82 .width 83 .maybe_max(style.min_size.width.resolve(size.width)) 84 .maybe_min(style.max_size.width.resolve(size.width)) 85 .to_number(), 86 height: first_pass 87 .size 88 .height 89 .maybe_max(style.min_size.height.resolve(size.height)) 90 .maybe_min(style.max_size.height.resolve(size.height)) 91 .to_number(), 92 }, 93 size, 94 true, 95 )? 96 } else { 97 self.compute_internal( 98 root, 99 Size { width: style.size.width.resolve(size.width), height: style.size.height.resolve(size.height) }, 100 size, 101 true, 102 )? 103 }; 104 105 self.nodes[root].layout = result::Layout { 106 order: 0, 107 size: Size { width: result.size.width, height: result.size.height }, 108 location: Point { x: 0.0, y: 0.0 }, 109 }; 110 111 Self::round_layout(&mut self.nodes, &self.children, root, 0.0, 0.0); 112 Ok(()) 113 } 114 round_layout(nodes: &mut Vec<NodeData>, children: &Vec<Vec<NodeId>>, root: NodeId, abs_x: f32, abs_y: f32)115 fn round_layout(nodes: &mut Vec<NodeData>, children: &Vec<Vec<NodeId>>, root: NodeId, abs_x: f32, abs_y: f32) { 116 let layout = &mut nodes[root].layout; 117 let abs_x = abs_x + layout.location.x; 118 let abs_y = abs_y + layout.location.y; 119 120 layout.location.x = layout.location.x.round(); 121 layout.location.y = layout.location.y.round(); 122 layout.size.width = (abs_x + layout.size.width).round() - abs_x.round(); 123 layout.size.height = (abs_y + layout.size.height).round() - abs_y.round(); 124 for child in &children[root] { 125 Self::round_layout(nodes, children, *child, abs_x, abs_y); 126 } 127 } 128 compute_internal( &mut self, node: NodeId, node_size: Size<Number>, parent_size: Size<Number>, perform_layout: bool, ) -> Result<ComputeResult, Box<Any>>129 fn compute_internal( 130 &mut self, 131 node: NodeId, 132 node_size: Size<Number>, 133 parent_size: Size<Number>, 134 perform_layout: bool, 135 ) -> Result<ComputeResult, Box<Any>> { 136 self.nodes[node].is_dirty = false; 137 138 // First we check if we have a result for the given input 139 if let Some(ref cache) = self.nodes[node].layout_cache { 140 if cache.perform_layout || !perform_layout { 141 let width_compatible = if let Number::Defined(width) = node_size.width { 142 (width - cache.result.size.width).abs() < f32::EPSILON 143 } else { 144 cache.node_size.width.is_undefined() 145 }; 146 147 let height_compatible = if let Number::Defined(height) = node_size.height { 148 (height - cache.result.size.height).abs() < f32::EPSILON 149 } else { 150 cache.node_size.height.is_undefined() 151 }; 152 153 if width_compatible && height_compatible { 154 return Ok(cache.result.clone()); 155 } 156 157 if cache.node_size == node_size && cache.parent_size == parent_size { 158 return Ok(cache.result.clone()); 159 } 160 } 161 } 162 163 // Define some general constants we will need for the remainder 164 // of the algorithm. 165 166 let dir = self.nodes[node].style.flex_direction; 167 let is_row = dir.is_row(); 168 let is_column = dir.is_column(); 169 let is_wrap_reverse = self.nodes[node].style.flex_wrap == FlexWrap::WrapReverse; 170 171 let margin = self.nodes[node].style.margin.map(|n| n.resolve(parent_size.width).or_else(0.0)); 172 let padding = self.nodes[node].style.padding.map(|n| n.resolve(parent_size.width).or_else(0.0)); 173 let border = self.nodes[node].style.border.map(|n| n.resolve(parent_size.width).or_else(0.0)); 174 175 let padding_border = Rect { 176 start: padding.start + border.start, 177 end: padding.end + border.end, 178 top: padding.top + border.top, 179 bottom: padding.bottom + border.bottom, 180 }; 181 182 let node_inner_size = Size { 183 width: node_size.width - padding_border.horizontal(), 184 height: node_size.height - padding_border.vertical(), 185 }; 186 187 let mut container_size = Size { width: 0.0, height: 0.0 }; 188 let mut inner_container_size = Size { width: 0.0, height: 0.0 }; 189 190 // If this is a leaf node we can skip a lot of this function in some cases 191 if self.children[node].is_empty() { 192 if node_size.width.is_defined() && node_size.height.is_defined() { 193 return Ok(ComputeResult { size: node_size.map(|s| s.or_else(0.0)) }); 194 } 195 196 if let Some(ref measure) = self.nodes[node].measure { 197 let result = ComputeResult { size: measure(node_size)? }; 198 self.nodes[node].layout_cache = 199 Some(result::Cache { node_size, parent_size, perform_layout, result: result.clone() }); 200 return Ok(result); 201 } 202 203 return Ok(ComputeResult { 204 size: Size { 205 width: node_size.width.or_else(0.0) + padding_border.horizontal(), 206 height: node_size.height.or_else(0.0) + padding_border.vertical(), 207 }, 208 }); 209 } 210 211 // 9.2. Line Length Determination 212 213 // 1. Generate anonymous flex items as described in §4 Flex Items. 214 215 // 2. Determine the available main and cross space for the flex items. 216 // For each dimension, if that dimension of the flex container’s content box 217 // is a definite size, use that; if that dimension of the flex container is 218 // being sized under a min or max-content constraint, the available space in 219 // that dimension is that constraint; otherwise, subtract the flex container’s 220 // margin, border, and padding from the space available to the flex container 221 // in that dimension and use that value. This might result in an infinite value. 222 223 let available_space = Size { 224 width: node_size.width.or_else(parent_size.width - margin.horizontal()) - padding_border.horizontal(), 225 height: node_size.height.or_else(parent_size.height - margin.vertical()) - padding_border.vertical(), 226 }; 227 228 let mut flex_items: Vec<FlexItem> = self.children[node] 229 .iter() 230 .map(|child| (child, &self.nodes[*child].style)) 231 .filter(|(_, style)| style.position_type != PositionType::Absolute) 232 .filter(|(_, style)| style.display != Display::None) 233 .map(|(child, child_style)| FlexItem { 234 node: *child, 235 236 size: Size { 237 width: child_style.size.width.resolve(node_inner_size.width), 238 height: child_style.size.height.resolve(node_inner_size.height), 239 }, 240 241 min_size: Size { 242 width: child_style.min_size.width.resolve(node_inner_size.width), 243 height: child_style.min_size.height.resolve(node_inner_size.height), 244 }, 245 246 max_size: Size { 247 width: child_style.max_size.width.resolve(node_inner_size.width), 248 height: child_style.max_size.height.resolve(node_inner_size.height), 249 }, 250 251 position: child_style.position.map(|p| p.resolve(node_inner_size.width)), 252 margin: child_style.margin.map(|m| m.resolve(node_inner_size.width).or_else(0.0)), 253 padding: child_style.padding.map(|p| p.resolve(node_inner_size.width).or_else(0.0)), 254 border: child_style.border.map(|b| b.resolve(node_inner_size.width).or_else(0.0)), 255 256 flex_basis: 0.0, 257 inner_flex_basis: 0.0, 258 violation: 0.0, 259 frozen: false, 260 261 hypothetical_inner_size: Size { width: 0.0, height: 0.0 }, 262 hypothetical_outer_size: Size { width: 0.0, height: 0.0 }, 263 target_size: Size { width: 0.0, height: 0.0 }, 264 outer_target_size: Size { width: 0.0, height: 0.0 }, 265 266 baseline: 0.0, 267 268 offset_main: 0.0, 269 offset_cross: 0.0, 270 }) 271 .collect(); 272 273 let has_baseline_child = flex_items.iter().fold(false, |result, child| { 274 result || self.nodes[child.node].style.align_self(&self.nodes[node].style) == AlignSelf::Baseline 275 }); 276 277 // TODO - this does not follow spec. See commented out code below 278 // 3. Determine the flex base size and hypothetical main size of each item: 279 flex_items.iter_mut().try_for_each(|child| -> Result<(), Box<Any>> { 280 let child_style = self.nodes[child.node].style; 281 282 // A. If the item has a definite used flex basis, that’s the flex base size. 283 284 let flex_basis = child_style.flex_basis.resolve(node_inner_size.main(dir)); 285 if flex_basis.is_defined() { 286 child.flex_basis = flex_basis.or_else(0.0); 287 return Ok(()); 288 }; 289 290 // B. If the flex item has an intrinsic aspect ratio, 291 // a used flex basis of content, and a definite cross size, 292 // then the flex base size is calculated from its inner 293 // cross size and the flex item’s intrinsic aspect ratio. 294 295 if let Defined(ratio) = child_style.aspect_ratio { 296 if let Defined(cross) = node_size.cross(dir) { 297 if child_style.flex_basis == Dimension::Auto { 298 child.flex_basis = cross * ratio; 299 return Ok(()); 300 } 301 } 302 } 303 304 // C. If the used flex basis is content or depends on its available space, 305 // and the flex container is being sized under a min-content or max-content 306 // constraint (e.g. when performing automatic table layout [CSS21]), 307 // size the item under that constraint. The flex base size is the item’s 308 // resulting main size. 309 310 // TODO - Probably need to cover this case in future 311 312 // D. Otherwise, if the used flex basis is content or depends on its 313 // available space, the available main size is infinite, and the flex item’s 314 // inline axis is parallel to the main axis, lay the item out using the rules 315 // for a box in an orthogonal flow [CSS3-WRITING-MODES]. The flex base size 316 // is the item’s max-content main size. 317 318 // TODO - Probably need to cover this case in future 319 320 // E. Otherwise, size the item into the available space using its used flex basis 321 // in place of its main size, treating a value of content as max-content. 322 // If a cross size is needed to determine the main size (e.g. when the 323 // flex item’s main size is in its block axis) and the flex item’s cross size 324 // is auto and not definite, in this calculation use fit-content as the 325 // flex item’s cross size. The flex base size is the item’s resulting main size. 326 327 let width: Number = if !child.size.width.is_defined() 328 && child_style.align_self(&self.nodes[node].style) == AlignSelf::Stretch 329 && is_column 330 { 331 available_space.width 332 } else { 333 child.size.width 334 }; 335 336 let height: Number = if !child.size.height.is_defined() 337 && child_style.align_self(&self.nodes[node].style) == AlignSelf::Stretch 338 && is_row 339 { 340 available_space.height 341 } else { 342 child.size.height 343 }; 344 345 child.flex_basis = self 346 .compute_internal( 347 child.node, 348 Size { 349 width: width.maybe_max(child.min_size.width).maybe_min(child.max_size.width), 350 height: height.maybe_max(child.min_size.height).maybe_min(child.max_size.height), 351 }, 352 available_space, 353 false, 354 )? 355 .size 356 .main(dir) 357 .maybe_max(child.min_size.main(dir)) 358 .maybe_min(child.max_size.main(dir)); 359 360 Ok(()) 361 })?; 362 363 // The hypothetical main size is the item’s flex base size clamped according to its 364 // used min and max main sizes (and flooring the content box size at zero). 365 366 flex_items.iter_mut().try_for_each(|child| -> Result<(), Box<Any>> { 367 child.inner_flex_basis = child.flex_basis - child.padding.main(dir) - child.border.main(dir); 368 369 // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though. 370 // The following logic was developed not from the spec but by trail and error looking into how 371 // webkit handled various scenarios. Can probably be solved better by passing in 372 // min-content max-content constraints from the top 373 let min_main = self 374 .compute_internal(child.node, Size { width: Undefined, height: Undefined }, available_space, false)? 375 .size 376 .main(dir) 377 .maybe_max(child.min_size.main(dir)) 378 .maybe_min(child.size.main(dir)) 379 .to_number(); 380 381 child 382 .hypothetical_inner_size 383 .set_main(dir, child.flex_basis.maybe_max(min_main).maybe_min(child.max_size.main(dir))); 384 385 child 386 .hypothetical_outer_size 387 .set_main(dir, child.hypothetical_inner_size.main(dir) + child.margin.main(dir)); 388 389 Ok(()) 390 })?; 391 392 // 9.3. Main Size Determination 393 394 // 5. Collect flex items into flex lines: 395 // - If the flex container is single-line, collect all the flex items into 396 // a single flex line. 397 // - Otherwise, starting from the first uncollected item, collect consecutive 398 // items one by one until the first time that the next collected item would 399 // not fit into the flex container’s inner main size (or until a forced break 400 // is encountered, see §10 Fragmenting Flex Layout). If the very first 401 // uncollected item wouldn’t fit, collect just it into the line. 402 // 403 // For this step, the size of a flex item is its outer hypothetical main size. (Note: This can be negative.) 404 // Repeat until all flex items have been collected into flex lines 405 // 406 // Note that the "collect as many" line will collect zero-sized flex items onto 407 // the end of the previous line even if the last non-zero item exactly "filled up" the line. 408 409 let mut flex_lines = { 410 let mut lines: Vec<FlexLine> = vec![]; 411 let mut line_length = 0.0; 412 413 if self.nodes[node].style.flex_wrap == FlexWrap::NoWrap { 414 lines.push(FlexLine { items: flex_items, cross_size: 0.0, offset_cross: 0.0 }); 415 } else { 416 let mut line = FlexLine { items: vec![], cross_size: 0.0, offset_cross: 0.0 }; 417 418 for child in flex_items { 419 line_length += child.hypothetical_outer_size.main(dir); 420 421 if let Defined(main) = available_space.main(dir) { 422 if line_length > main && !line.items.is_empty() { 423 line_length = child.hypothetical_outer_size.main(dir); 424 lines.push(line); 425 line = FlexLine { items: vec![], cross_size: 0.0, offset_cross: 0.0 }; 426 } 427 } 428 429 line.items.push(child); 430 } 431 432 lines.push(line); 433 } 434 435 lines 436 }; 437 438 // 6. Resolve the flexible lengths of all the flex items to find their used main size. 439 // See §9.7 Resolving Flexible Lengths. 440 // 441 // 9.7. Resolving Flexible Lengths 442 443 flex_lines.iter_mut().try_for_each(|line| -> Result<(), Box<Any>> { 444 // 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all 445 // items on the line. If the sum is less than the flex container’s inner main size, 446 // use the flex grow factor for the rest of this algorithm; otherwise, use the 447 // flex shrink factor. 448 449 let used_flex_factor: f32 = line.items.iter().map(|child| child.hypothetical_outer_size.main(dir)).sum(); 450 let growing = used_flex_factor < node_inner_size.main(dir).or_else(0.0); 451 let shrinking = !growing; 452 453 // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size 454 // - Any item that has a flex factor of zero 455 // - If using the flex grow factor: any item that has a flex base size 456 // greater than its hypothetical main size 457 // - If using the flex shrink factor: any item that has a flex base size 458 // smaller than its hypothetical main size 459 460 line.items.iter_mut().try_for_each(|child| -> Result<(), Box<Any>> { 461 // TODO - This is not found by reading the spec. Maybe this can be done in some other place 462 // instead. This was found by trail and error fixing tests to align with webkit output. 463 if node_inner_size.main(dir).is_undefined() && is_row { 464 child.target_size.set_main( 465 dir, 466 self.compute_internal( 467 child.node, 468 Size { 469 width: child.size.width.maybe_max(child.min_size.width).maybe_min(child.max_size.width), 470 height: child 471 .size 472 .height 473 .maybe_max(child.min_size.height) 474 .maybe_min(child.max_size.height), 475 }, 476 available_space, 477 false, 478 )? 479 .size 480 .main(dir) 481 .maybe_max(child.min_size.main(dir)) 482 .maybe_min(child.max_size.main(dir)), 483 ); 484 } else { 485 child.target_size.set_main(dir, child.hypothetical_inner_size.main(dir)); 486 } 487 488 // TODO this should really only be set inside the if-statement below but 489 // that causes the target_main_size to never be set for some items 490 491 child.outer_target_size.set_main(dir, child.target_size.main(dir) + child.margin.main(dir)); 492 493 let child_style = &self.nodes[child.node].style; 494 if (child_style.flex_grow == 0.0 && child_style.flex_shrink == 0.0) 495 || (growing && child.flex_basis > child.hypothetical_inner_size.main(dir)) 496 || (shrinking && child.flex_basis < child.hypothetical_inner_size.main(dir)) 497 { 498 child.frozen = true; 499 } 500 501 Ok(()) 502 })?; 503 504 // 3. Calculate initial free space. Sum the outer sizes of all items on the line, 505 // and subtract this from the flex container’s inner main size. For frozen items, 506 // use their outer target main size; for other items, use their outer flex base size. 507 508 let used_space: f32 = line 509 .items 510 .iter() 511 .map(|child| { 512 child.margin.main(dir) + if child.frozen { child.target_size.main(dir) } else { child.flex_basis } 513 }) 514 .sum(); 515 516 let initial_free_space = (node_inner_size.main(dir) - used_space).or_else(0.0); 517 518 // 4. Loop 519 520 loop { 521 // a. Check for flexible items. If all the flex items on the line are frozen, 522 // free space has been distributed; exit this loop. 523 524 let mut frozen: Vec<&mut FlexItem> = vec![]; 525 let mut unfrozen: Vec<&mut FlexItem> = vec![]; 526 527 line.items.iter_mut().for_each(|child| { 528 if child.frozen { 529 frozen.push(child); 530 } else { 531 unfrozen.push(child); 532 } 533 }); 534 535 if unfrozen.is_empty() { 536 break; 537 } 538 539 // b. Calculate the remaining free space as for initial free space, above. 540 // If the sum of the unfrozen flex items’ flex factors is less than one, 541 // multiply the initial free space by this sum. If the magnitude of this 542 // value is less than the magnitude of the remaining free space, use this 543 // as the remaining free space. 544 545 let used_space: f32 = Iterator::chain(frozen.iter(), unfrozen.iter()) 546 .map(|child| { 547 child.margin.main(dir) 548 + if child.frozen { child.target_size.main(dir) } else { child.flex_basis } 549 }) 550 .sum(); 551 552 let (sum_flex_grow, sum_flex_shrink): (f32, f32) = 553 unfrozen.iter().fold((0.0, 0.0), |(flex_grow, flex_shrink), item| { 554 let style = &self.nodes[item.node].style; 555 (flex_grow + style.flex_grow, flex_shrink + style.flex_shrink) 556 }); 557 558 let free_space = if growing && sum_flex_grow < 1.0 { 559 (initial_free_space * sum_flex_grow).maybe_min(node_inner_size.main(dir) - used_space) 560 } else if shrinking && sum_flex_shrink < 1.0 { 561 (initial_free_space * sum_flex_shrink).maybe_max(node_inner_size.main(dir) - used_space) 562 } else { 563 (node_inner_size.main(dir) - used_space).or_else(0.0) 564 }; 565 566 // c. Distribute free space proportional to the flex factors. 567 // - If the remaining free space is zero 568 // Do Nothing 569 // - If using the flex grow factor 570 // Find the ratio of the item’s flex grow factor to the sum of the 571 // flex grow factors of all unfrozen items on the line. Set the item’s 572 // target main size to its flex base size plus a fraction of the remaining 573 // free space proportional to the ratio. 574 // - If using the flex shrink factor 575 // For every unfrozen item on the line, multiply its flex shrink factor by 576 // its inner flex base size, and note this as its scaled flex shrink factor. 577 // Find the ratio of the item’s scaled flex shrink factor to the sum of the 578 // scaled flex shrink factors of all unfrozen items on the line. Set the item’s 579 // target main size to its flex base size minus a fraction of the absolute value 580 // of the remaining free space proportional to the ratio. Note this may result 581 // in a negative inner main size; it will be corrected in the next step. 582 // - Otherwise 583 // Do Nothing 584 585 if free_space.is_normal() { 586 if growing && sum_flex_grow > 0.0 { 587 unfrozen.iter_mut().for_each(|child| { 588 child.target_size.set_main( 589 dir, 590 child.flex_basis 591 + free_space * (self.nodes[child.node].style.flex_grow / sum_flex_grow), 592 ); 593 }); 594 } else if shrinking && sum_flex_shrink > 0.0 { 595 let sum_scaled_shrink_factor: f32 = unfrozen 596 .iter() 597 .map(|child| child.inner_flex_basis * self.nodes[child.node].style.flex_shrink) 598 .sum(); 599 600 if sum_scaled_shrink_factor > 0.0 { 601 unfrozen.iter_mut().for_each(|child| { 602 let scaled_shrink_factor = 603 child.inner_flex_basis * self.nodes[child.node].style.flex_shrink; 604 child.target_size.set_main( 605 dir, 606 child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor), 607 ) 608 }); 609 } 610 } 611 } 612 613 // d. Fix min/max violations. Clamp each non-frozen item’s target main size by its 614 // used min and max main sizes and floor its content-box size at zero. If the 615 // item’s target main size was made smaller by this, it’s a max violation. 616 // If the item’s target main size was made larger by this, it’s a min violation. 617 618 let total_violation = unfrozen.iter_mut().try_fold(0.0, |acc, child| -> Result<f32, Box<Any>> { 619 // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though. 620 // The following logic was developed not from the spec but by trail and error looking into how 621 // webkit handled various scenarios. Can probably be solved better by passing in 622 // min-content max-content constraints from the top. Need to figure out correct thing to do here as 623 // just piling on more conditionals. 624 let min_main = if is_row && self.nodes[child.node].measure.is_none() { 625 self.compute_internal( 626 child.node, 627 Size { width: Undefined, height: Undefined }, 628 available_space, 629 false, 630 )? 631 .size 632 .width 633 .maybe_min(child.size.width) 634 .maybe_max(child.min_size.width) 635 .to_number() 636 } else { 637 child.min_size.main(dir) 638 }; 639 640 let max_main = child.max_size.main(dir); 641 let clamped = child.target_size.main(dir).maybe_min(max_main).maybe_max(min_main).max(0.0); 642 child.violation = clamped - child.target_size.main(dir); 643 child.target_size.set_main(dir, clamped); 644 child.outer_target_size.set_main(dir, child.target_size.main(dir) + child.margin.main(dir)); 645 646 Ok(acc + child.violation) 647 })?; 648 649 // e. Freeze over-flexed items. The total violation is the sum of the adjustments 650 // from the previous step ∑(clamped size - unclamped size). If the total violation is: 651 // - Zero 652 // Freeze all items. 653 // - Positive 654 // Freeze all the items with min violations. 655 // - Negative 656 // Freeze all the items with max violations. 657 658 unfrozen.iter_mut().for_each(|child| match total_violation { 659 v if v > 0.0 => child.frozen = child.violation > 0.0, 660 v if v < 0.0 => child.frozen = child.violation < 0.0, 661 _ => child.frozen = true, 662 }) 663 664 // f. Return to the start of this loop. 665 } 666 667 Ok(()) 668 })?; 669 670 // Not part of the spec from what i can see but seems correct 671 container_size.set_main( 672 dir, 673 node_size.main(dir).or_else({ 674 let longest_line = flex_lines.iter().fold(f32::MIN, |acc, line| { 675 let length: f32 = line.items.iter().map(|item| item.outer_target_size.main(dir)).sum(); 676 acc.max(length) 677 }); 678 679 let size = longest_line + padding_border.main(dir); 680 match available_space.main(dir) { 681 Defined(val) if flex_lines.len() > 1 && size < val => val, 682 _ => size, 683 } 684 }), 685 ); 686 687 inner_container_size.set_main(dir, container_size.main(dir) - padding_border.main(dir)); 688 689 // 9.4. Cross Size Determination 690 691 // 7. Determine the hypothetical cross size of each item by performing layout with the 692 // used main size and the available space, treating auto as fit-content. 693 694 flex_lines.iter_mut().try_for_each(|line| { 695 line.items.iter_mut().try_for_each(|child| -> Result<(), Box<Any>> { 696 let child_cross = 697 child.size.cross(dir).maybe_max(child.min_size.cross(dir)).maybe_min(child.max_size.cross(dir)); 698 699 child.hypothetical_inner_size.set_cross( 700 dir, 701 self.compute_internal( 702 child.node, 703 Size { 704 width: if is_row { child.target_size.width.to_number() } else { child_cross }, 705 height: if is_row { child_cross } else { child.target_size.height.to_number() }, 706 }, 707 Size { 708 width: if is_row { container_size.main(dir).to_number() } else { available_space.width }, 709 height: if is_row { available_space.height } else { container_size.main(dir).to_number() }, 710 }, 711 false, 712 )? 713 .size 714 .cross(dir) 715 .maybe_max(child.min_size.cross(dir)) 716 .maybe_min(child.max_size.cross(dir)), 717 ); 718 719 child 720 .hypothetical_outer_size 721 .set_cross(dir, child.hypothetical_inner_size.cross(dir) + child.margin.cross(dir)); 722 723 Ok(()) 724 }) 725 })?; 726 727 // TODO - probably should move this somewhere else as it doesn't make a ton of sense here but we need it below 728 // TODO - This is expensive and should only be done if we really require a baseline. aka, make it lazy 729 730 fn calc_baseline(db: &Forest, node: NodeId, layout: &result::Layout) -> f32 { 731 if db.children[node].is_empty() { 732 layout.size.height 733 } else { 734 let child = db.children[node][0]; 735 calc_baseline(db, child, &db.nodes[child].layout) 736 } 737 }; 738 739 if has_baseline_child { 740 flex_lines.iter_mut().try_for_each(|line| { 741 line.items.iter_mut().try_for_each(|child| -> Result<(), Box<Any>> { 742 let result = self.compute_internal( 743 child.node, 744 Size { 745 width: if is_row { 746 child.target_size.width.to_number() 747 } else { 748 child.hypothetical_inner_size.width.to_number() 749 }, 750 height: if is_row { 751 child.hypothetical_inner_size.height.to_number() 752 } else { 753 child.target_size.height.to_number() 754 }, 755 }, 756 Size { 757 width: if is_row { container_size.width.to_number() } else { node_size.width }, 758 height: if is_row { node_size.height } else { container_size.height.to_number() }, 759 }, 760 true, 761 )?; 762 763 child.baseline = calc_baseline( 764 self, 765 child.node, 766 &result::Layout { 767 order: self.children[node].iter().position(|n| *n == child.node).unwrap() as u32, 768 size: result.size, 769 location: Point { x: 0.0, y: 0.0 }, 770 }, 771 ); 772 773 Ok(()) 774 }) 775 })?; 776 } 777 778 // 8. Calculate the cross size of each flex line. 779 // If the flex container is single-line and has a definite cross size, the cross size 780 // of the flex line is the flex container’s inner cross size. Otherwise, for each flex line: 781 // 782 // If the flex container is single-line, then clamp the line’s cross-size to be within 783 // the container’s computed min and max cross sizes. Note that if CSS 2.1’s definition 784 // of min/max-width/height applied more generally, this behavior would fall out automatically. 785 786 if flex_lines.len() == 1 && node_size.cross(dir).is_defined() { 787 flex_lines[0].cross_size = (node_size.cross(dir) - padding_border.cross(dir)).or_else(0.0); 788 } else { 789 flex_lines.iter_mut().for_each(|line| { 790 // 1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose 791 // align-self is baseline, and whose cross-axis margins are both non-auto. Find the 792 // largest of the distances between each item’s baseline and its hypothetical outer 793 // cross-start edge, and the largest of the distances between each item’s baseline 794 // and its hypothetical outer cross-end edge, and sum these two values. 795 796 // 2. Among all the items not collected by the previous step, find the largest 797 // outer hypothetical cross size. 798 799 // 3. The used cross-size of the flex line is the largest of the numbers found in the 800 // previous two steps and zero. 801 802 let max_baseline: f32 = line.items.iter().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x)); 803 line.cross_size = line 804 .items 805 .iter() 806 .map(|child| { 807 let child_style = &self.nodes[child.node].style; 808 if child_style.align_self(&self.nodes[node].style) == AlignSelf::Baseline 809 && child_style.cross_margin_start(dir) != Dimension::Auto 810 && child_style.cross_margin_end(dir) != Dimension::Auto 811 && child_style.cross_size(dir) == Dimension::Auto 812 { 813 max_baseline - child.baseline + child.hypothetical_outer_size.cross(dir) 814 } else { 815 child.hypothetical_outer_size.cross(dir) 816 } 817 }) 818 .fold(0.0, |acc, x| acc.max(x)); 819 }); 820 } 821 822 // 9. Handle 'align-content: stretch'. If the flex container has a definite cross size, 823 // align-content is stretch, and the sum of the flex lines' cross sizes is less than 824 // the flex container’s inner cross size, increase the cross size of each flex line 825 // by equal amounts such that the sum of their cross sizes exactly equals the 826 // flex container’s inner cross size. 827 828 if self.nodes[node].style.align_content == AlignContent::Stretch && node_size.cross(dir).is_defined() { 829 let total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum(); 830 let inner_cross = (node_size.cross(dir) - padding_border.cross(dir)).or_else(0.0); 831 832 if total_cross < inner_cross { 833 let remaining = inner_cross - total_cross; 834 let addition = remaining / flex_lines.len() as f32; 835 flex_lines.iter_mut().for_each(|line| line.cross_size += addition); 836 } 837 } 838 839 // 10. Collapse visibility:collapse items. If any flex items have visibility: collapse, 840 // note the cross size of the line they’re in as the item’s strut size, and restart 841 // layout from the beginning. 842 // 843 // In this second layout round, when collecting items into lines, treat the collapsed 844 // items as having zero main size. For the rest of the algorithm following that step, 845 // ignore the collapsed items entirely (as if they were display:none) except that after 846 // calculating the cross size of the lines, if any line’s cross size is less than the 847 // largest strut size among all the collapsed items in the line, set its cross size to 848 // that strut size. 849 // 850 // Skip this step in the second layout round. 851 852 // TODO implement once (if ever) we support visibility:collapse 853 854 // 11. Determine the used cross size of each flex item. If a flex item has align-self: stretch, 855 // its computed cross size property is auto, and neither of its cross-axis margins are auto, 856 // the used outer cross size is the used cross size of its flex line, clamped according to 857 // the item’s used min and max cross sizes. Otherwise, the used cross size is the item’s 858 // hypothetical cross size. 859 // 860 // If the flex item has align-self: stretch, redo layout for its contents, treating this 861 // used size as its definite cross size so that percentage-sized children can be resolved. 862 // 863 // Note that this step does not affect the main size of the flex item, even if it has an 864 // intrinsic aspect ratio. 865 866 flex_lines.iter_mut().for_each(|line| { 867 let line_cross_size = line.cross_size; 868 869 line.items.iter_mut().for_each(|child| { 870 let child_style = &self.nodes[child.node].style; 871 child.target_size.set_cross( 872 dir, 873 if child_style.align_self(&self.nodes[node].style) == AlignSelf::Stretch 874 && child_style.cross_margin_start(dir) != Dimension::Auto 875 && child_style.cross_margin_end(dir) != Dimension::Auto 876 && child_style.cross_size(dir) == Dimension::Auto 877 { 878 (line_cross_size - child.margin.cross(dir)) 879 .maybe_max(child.min_size.cross(dir)) 880 .maybe_min(child.max_size.cross(dir)) 881 } else { 882 child.hypothetical_inner_size.cross(dir) 883 }, 884 ); 885 886 child.outer_target_size.set_cross(dir, child.target_size.cross(dir) + child.margin.cross(dir)); 887 }); 888 }); 889 890 // 9.5. Main-Axis Alignment 891 892 // 12. Distribute any remaining free space. For each flex line: 893 // 1. If the remaining free space is positive and at least one main-axis margin on this 894 // line is auto, distribute the free space equally among these margins. Otherwise, 895 // set all auto margins to zero. 896 // 2. Align the items along the main-axis per justify-content. 897 898 flex_lines.iter_mut().for_each(|line| { 899 let used_space: f32 = line.items.iter().map(|child| child.outer_target_size.main(dir)).sum(); 900 let free_space = inner_container_size.main(dir) - used_space; 901 let mut num_auto_margins = 0; 902 903 line.items.iter_mut().for_each(|child| { 904 let child_style = &self.nodes[child.node].style; 905 if child_style.main_margin_start(dir) == Dimension::Auto { 906 num_auto_margins += 1; 907 } 908 if child_style.main_margin_end(dir) == Dimension::Auto { 909 num_auto_margins += 1; 910 } 911 }); 912 913 if free_space > 0.0 && num_auto_margins > 0 { 914 let margin = free_space / num_auto_margins as f32; 915 916 line.items.iter_mut().for_each(|child| { 917 let child_style = &self.nodes[child.node].style; 918 if child_style.main_margin_start(dir) == Dimension::Auto { 919 if is_row { 920 child.margin.start = margin; 921 } else { 922 child.margin.top = margin; 923 } 924 } 925 if child_style.main_margin_end(dir) == Dimension::Auto { 926 if is_row { 927 child.margin.end = margin; 928 } else { 929 child.margin.bottom = margin; 930 } 931 } 932 }); 933 } else { 934 let num_items = line.items.len(); 935 let layout_reverse = dir.is_reverse(); 936 937 let justify_item = |(i, child): (usize, &mut FlexItem)| { 938 let is_first = i == 0; 939 940 child.offset_main = match self.nodes[node].style.justify_content { 941 JustifyContent::FlexStart => { 942 if layout_reverse && is_first { 943 free_space 944 } else { 945 0.0 946 } 947 } 948 JustifyContent::Center => { 949 if is_first { 950 free_space / 2.0 951 } else { 952 0.0 953 } 954 } 955 JustifyContent::FlexEnd => { 956 if is_first && !layout_reverse { 957 free_space 958 } else { 959 0.0 960 } 961 } 962 JustifyContent::SpaceBetween => { 963 if is_first { 964 0.0 965 } else { 966 free_space / (num_items - 1) as f32 967 } 968 } 969 JustifyContent::SpaceAround => { 970 if is_first { 971 (free_space / num_items as f32) / 2.0 972 } else { 973 free_space / num_items as f32 974 } 975 } 976 JustifyContent::SpaceEvenly => free_space / (num_items + 1) as f32, 977 }; 978 }; 979 980 if layout_reverse { 981 line.items.iter_mut().rev().enumerate().for_each(justify_item); 982 } else { 983 line.items.iter_mut().enumerate().for_each(justify_item); 984 } 985 } 986 }); 987 988 // 9.6. Cross-Axis Alignment 989 990 // 13. Resolve cross-axis auto margins. If a flex item has auto cross-axis margins: 991 // - If its outer cross size (treating those auto margins as zero) is less than the 992 // cross size of its flex line, distribute the difference in those sizes equally 993 // to the auto margins. 994 // - Otherwise, if the block-start or inline-start margin (whichever is in the cross axis) 995 // is auto, set it to zero. Set the opposite margin so that the outer cross size of the 996 // item equals the cross size of its flex line. 997 998 flex_lines.iter_mut().for_each(|line| { 999 let line_cross_size = line.cross_size; 1000 let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x)); 1001 1002 line.items.iter_mut().for_each(|child| { 1003 let free_space = line_cross_size - child.outer_target_size.cross(dir); 1004 let child_style = &self.nodes[child.node].style; 1005 1006 if child_style.cross_margin_start(dir) == Dimension::Auto 1007 && child_style.cross_margin_end(dir) == Dimension::Auto 1008 { 1009 if is_row { 1010 child.margin.top = free_space / 2.0; 1011 child.margin.bottom = free_space / 2.0; 1012 } else { 1013 child.margin.start = free_space / 2.0; 1014 child.margin.end = free_space / 2.0; 1015 } 1016 } else if child_style.cross_margin_start(dir) == Dimension::Auto { 1017 if is_row { 1018 child.margin.top = free_space; 1019 } else { 1020 child.margin.start = free_space; 1021 } 1022 } else if child_style.cross_margin_end(dir) == Dimension::Auto { 1023 if is_row { 1024 child.margin.bottom = free_space; 1025 } else { 1026 child.margin.end = free_space; 1027 } 1028 } else { 1029 // 14. Align all flex items along the cross-axis per align-self, if neither of the item’s 1030 // cross-axis margins are auto. 1031 1032 child.offset_cross = match child_style.align_self(&self.nodes[node].style) { 1033 AlignSelf::Auto => 0.0, // Should never happen 1034 AlignSelf::FlexStart => { 1035 if is_wrap_reverse { 1036 free_space 1037 } else { 1038 0.0 1039 } 1040 } 1041 AlignSelf::FlexEnd => { 1042 if is_wrap_reverse { 1043 0.0 1044 } else { 1045 free_space 1046 } 1047 } 1048 AlignSelf::Center => free_space / 2.0, 1049 AlignSelf::Baseline => { 1050 if is_row { 1051 max_baseline - child.baseline 1052 } else { 1053 // baseline alignment only makes sense if the direction is row 1054 // we treat it as flex-start alignment in columns. 1055 if is_wrap_reverse { 1056 free_space 1057 } else { 1058 0.0 1059 } 1060 } 1061 } 1062 AlignSelf::Stretch => { 1063 if is_wrap_reverse { 1064 free_space 1065 } else { 1066 0.0 1067 } 1068 } 1069 }; 1070 } 1071 }); 1072 }); 1073 1074 // 15. Determine the flex container’s used cross size: 1075 // - If the cross size property is a definite size, use that, clamped by the used 1076 // min and max cross sizes of the flex container. 1077 // - Otherwise, use the sum of the flex lines' cross sizes, clamped by the used 1078 // min and max cross sizes of the flex container. 1079 1080 let total_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum(); 1081 container_size.set_cross(dir, node_size.cross(dir).or_else(total_cross_size + padding_border.cross(dir))); 1082 inner_container_size.set_cross(dir, container_size.cross(dir) - padding_border.cross(dir)); 1083 1084 // We have the container size. If our caller does not care about performing 1085 // layout we are done now. 1086 if !perform_layout { 1087 let result = ComputeResult { size: container_size }; 1088 self.nodes[node].layout_cache = 1089 Some(result::Cache { node_size, parent_size, perform_layout, result: result.clone() }); 1090 return Ok(result); 1091 } 1092 1093 // 16. Align all flex lines per align-content. 1094 1095 let free_space = inner_container_size.cross(dir) - total_cross_size; 1096 let num_lines = flex_lines.len(); 1097 1098 let align_line = |(i, line): (usize, &mut FlexLine)| { 1099 let is_first = i == 0; 1100 1101 line.offset_cross = match self.nodes[node].style.align_content { 1102 AlignContent::FlexStart => { 1103 if is_first && is_wrap_reverse { 1104 free_space 1105 } else { 1106 0.0 1107 } 1108 } 1109 AlignContent::FlexEnd => { 1110 if is_first && !is_wrap_reverse { 1111 free_space 1112 } else { 1113 0.0 1114 } 1115 } 1116 AlignContent::Center => { 1117 if is_first { 1118 free_space / 2.0 1119 } else { 1120 0.0 1121 } 1122 } 1123 AlignContent::Stretch => 0.0, 1124 AlignContent::SpaceBetween => { 1125 if is_first { 1126 0.0 1127 } else { 1128 free_space / (num_lines - 1) as f32 1129 } 1130 } 1131 AlignContent::SpaceAround => { 1132 if is_first { 1133 (free_space / num_lines as f32) / 2.0 1134 } else { 1135 free_space / num_lines as f32 1136 } 1137 } 1138 }; 1139 }; 1140 1141 if is_wrap_reverse { 1142 flex_lines.iter_mut().rev().enumerate().for_each(align_line); 1143 } else { 1144 flex_lines.iter_mut().enumerate().for_each(align_line); 1145 } 1146 1147 // Do a final layout pass and gather the resulting layouts 1148 { 1149 let mut lines: Vec<Vec<result::Layout>> = vec![]; 1150 let mut total_offset_cross = padding_border.cross_start(dir); 1151 1152 let layout_line = |line: &mut FlexLine| -> Result<(), Box<Any>> { 1153 let mut children: Vec<result::Layout> = vec![]; 1154 let mut total_offset_main = padding_border.main_start(dir); 1155 let line_offset_cross = line.offset_cross; 1156 1157 let layout_item = |child: &mut FlexItem| -> Result<(), Box<Any>> { 1158 let result = self.compute_internal( 1159 child.node, 1160 child.target_size.map(|s| s.to_number()), 1161 container_size.map(|s| s.to_number()), 1162 true, 1163 )?; 1164 1165 let offset_main = total_offset_main 1166 + child.offset_main 1167 + child.margin.main_start(dir) 1168 + (child.position.main_start(dir).or_else(0.0) - child.position.main_end(dir).or_else(0.0)); 1169 1170 let offset_cross = total_offset_cross 1171 + child.offset_cross 1172 + line_offset_cross 1173 + child.margin.cross_start(dir) 1174 + (child.position.cross_start(dir).or_else(0.0) - child.position.cross_end(dir).or_else(0.0)); 1175 1176 self.nodes[child.node].layout = result::Layout { 1177 order: self.children[node].iter().position(|n| *n == child.node).unwrap() as u32, 1178 size: result.size, 1179 location: Point { 1180 x: if is_row { offset_main } else { offset_cross }, 1181 y: if is_column { offset_main } else { offset_cross }, 1182 }, 1183 }; 1184 1185 total_offset_main += child.offset_main + child.margin.main(dir) + result.size.main(dir); 1186 1187 Ok(()) 1188 }; 1189 1190 if dir.is_reverse() { 1191 line.items.iter_mut().rev().try_for_each(layout_item)?; 1192 } else { 1193 line.items.iter_mut().try_for_each(layout_item)?; 1194 } 1195 1196 total_offset_cross += line_offset_cross + line.cross_size; 1197 1198 if dir.is_reverse() { 1199 children.reverse(); 1200 } 1201 1202 lines.push(children); 1203 1204 Ok(()) 1205 }; 1206 1207 if is_wrap_reverse { 1208 flex_lines.iter_mut().rev().try_for_each(layout_line)?; 1209 } else { 1210 flex_lines.iter_mut().try_for_each(layout_line)?; 1211 } 1212 } 1213 1214 // Before returning we perform absolute layout on all absolutely positioned children 1215 { 1216 // TODO: remove number of Vec<_> generated 1217 let candidates = self.children[node] 1218 .iter() 1219 .cloned() 1220 .enumerate() 1221 .filter(|(_, child)| self.nodes[*child].style.position_type == PositionType::Absolute) 1222 .collect::<Vec<_>>(); 1223 1224 for (order, child) in candidates { 1225 let container_width = container_size.width.to_number(); 1226 let container_height = container_size.height.to_number(); 1227 1228 let child_style = self.nodes[child].style; 1229 1230 let start = child_style.position.start.resolve(container_width) 1231 + child_style.margin.start.resolve(container_width); 1232 let end = 1233 child_style.position.end.resolve(container_width) + child_style.margin.end.resolve(container_width); 1234 let top = child_style.position.top.resolve(container_height) 1235 + child_style.margin.top.resolve(container_height); 1236 let bottom = child_style.position.bottom.resolve(container_height) 1237 + child_style.margin.bottom.resolve(container_height); 1238 1239 let (start_main, end_main) = if is_row { (start, end) } else { (top, bottom) }; 1240 let (start_cross, end_cross) = if is_row { (top, bottom) } else { (start, end) }; 1241 1242 let width = child_style 1243 .size 1244 .width 1245 .resolve(container_width) 1246 .maybe_max(child_style.min_size.width.resolve(container_width)) 1247 .maybe_min(child_style.max_size.width.resolve(container_width)) 1248 .or_else(if start.is_defined() && end.is_defined() { 1249 container_width - start - end 1250 } else { 1251 Undefined 1252 }); 1253 1254 let height = child_style 1255 .size 1256 .height 1257 .resolve(container_height) 1258 .maybe_max(child_style.min_size.height.resolve(container_height)) 1259 .maybe_min(child_style.max_size.height.resolve(container_height)) 1260 .or_else(if top.is_defined() && bottom.is_defined() { 1261 container_height - top - bottom 1262 } else { 1263 Undefined 1264 }); 1265 1266 let result = self.compute_internal( 1267 child, 1268 Size { width, height }, 1269 Size { width: container_width, height: container_height }, 1270 true, 1271 )?; 1272 1273 let free_main_space = container_size.main(dir) 1274 - result 1275 .size 1276 .main(dir) 1277 .maybe_max(child_style.min_main_size(dir).resolve(node_inner_size.main(dir))) 1278 .maybe_min(child_style.max_main_size(dir).resolve(node_inner_size.main(dir))); 1279 1280 let free_cross_space = container_size.cross(dir) 1281 - result 1282 .size 1283 .cross(dir) 1284 .maybe_max(child_style.min_cross_size(dir).resolve(node_inner_size.cross(dir))) 1285 .maybe_min(child_style.max_cross_size(dir).resolve(node_inner_size.cross(dir))); 1286 1287 let offset_main = if start_main.is_defined() { 1288 start_main.or_else(0.0) + border.main_start(dir) 1289 } else if end_main.is_defined() { 1290 free_main_space - end_main.or_else(0.0) - border.main_end(dir) 1291 } else { 1292 match self.nodes[node].style.justify_content { 1293 JustifyContent::SpaceBetween | JustifyContent::FlexStart => padding_border.main_start(dir), 1294 JustifyContent::FlexEnd => free_main_space - padding_border.main_end(dir), 1295 JustifyContent::SpaceEvenly | JustifyContent::SpaceAround | JustifyContent::Center => { 1296 free_main_space / 2.0 1297 } 1298 } 1299 }; 1300 1301 let offset_cross = if start_cross.is_defined() { 1302 start_cross.or_else(0.0) + border.cross_start(dir) 1303 } else if end_cross.is_defined() { 1304 free_cross_space - end_cross.or_else(0.0) - border.cross_end(dir) 1305 } else { 1306 match child_style.align_self(&self.nodes[node].style) { 1307 AlignSelf::Auto => 0.0, // Should never happen 1308 AlignSelf::FlexStart => { 1309 if is_wrap_reverse { 1310 free_cross_space - padding_border.cross_end(dir) 1311 } else { 1312 padding_border.cross_start(dir) 1313 } 1314 } 1315 AlignSelf::FlexEnd => { 1316 if is_wrap_reverse { 1317 padding_border.cross_start(dir) 1318 } else { 1319 free_cross_space - padding_border.cross_end(dir) 1320 } 1321 } 1322 AlignSelf::Center => free_cross_space / 2.0, 1323 AlignSelf::Baseline => free_cross_space / 2.0, // Treat as center for now until we have baseline support 1324 AlignSelf::Stretch => { 1325 if is_wrap_reverse { 1326 free_cross_space - padding_border.cross_end(dir) 1327 } else { 1328 padding_border.cross_start(dir) 1329 } 1330 } 1331 } 1332 }; 1333 1334 self.nodes[child].layout = result::Layout { 1335 order: order as u32, 1336 size: result.size, 1337 location: Point { 1338 x: if is_row { offset_main } else { offset_cross }, 1339 y: if is_column { offset_main } else { offset_cross }, 1340 }, 1341 }; 1342 } 1343 } 1344 1345 fn hidden_layout(nodes: &mut Vec<NodeData>, children: &Vec<Vec<NodeId>>, node: NodeId, order: u32) { 1346 nodes[node].layout = 1347 result::Layout { order, size: Size { width: 0.0, height: 0.0 }, location: Point { x: 0.0, y: 0.0 } }; 1348 1349 for (order, child) in children[node].iter().enumerate() { 1350 hidden_layout(nodes, children, *child, order as _); 1351 } 1352 } 1353 1354 for (order, child) in self.children[node].iter().enumerate() { 1355 if self.nodes[*child].style.display == Display::None { 1356 hidden_layout(&mut self.nodes, &self.children, *child, order as _); 1357 } 1358 } 1359 1360 let result = ComputeResult { size: container_size }; 1361 self.nodes[node].layout_cache = 1362 Some(result::Cache { node_size, parent_size, perform_layout, result: result.clone() }); 1363 Ok(result) 1364 } 1365 } 1366