tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 583HTML 275Markdown 8TOML 41586 Definitions

fix a couple bugs and add todo markers

155d303566a24eb16641379f20f0d734d153a3e5 · by Emil Sjölander · (7 years ago) · parent bd78e4a19 · browse the tree at this commit

Semantic delta · 17

src/algo.rs

src/style.rs

tests/lib.rs

3 files · +253 −60

diff --git a/src/algo.rs b/src/algo.rs
index d5ad87481..680397a8b 100644
--- a/src/algo.rs
+++ b/src/algo.rs
@@ -9,18 +9,45 @@ struct FlexSize {
   cross: f32,
 }
 
-impl FlexSize {
-  fn max() -> FlexSize {
-    FlexSize {
-      main: f32::MAX,
-      cross: f32::MAX,
+#[derive(Debug, Copy, Clone)]
+struct SizeConstraint {
+  min: f32,
+  max: f32,
+}
+
+impl SizeConstraint {
+  fn exactly(size: f32) -> SizeConstraint {
+    SizeConstraint {
+      min: size,
+      max: size,
+    }
+  }
+
+  fn undefined() -> SizeConstraint {
+    SizeConstraint {
+      min: f32::NEG_INFINITY,
+      max: f32::INFINITY,
+    }
+  }
+
+  fn at_least(size: f32) -> SizeConstraint {
+    SizeConstraint {
+      min: size,
+      max: f32::NEG_INFINITY,
+    }
+  }
+
+  fn at_most(size: f32) -> SizeConstraint {
+    SizeConstraint {
+      min: f32::NEG_INFINITY,
+      max: size,
     }
   }
 
-  fn min() -> FlexSize {
-    FlexSize {
-      main: f32::MIN,
-      cross: f32::MIN,
+  fn between(min: f32, max: f32) -> SizeConstraint {
+    SizeConstraint {
+      min: min,
+      max: max,
     }
   }
 }
@@ -65,7 +92,7 @@ struct FlexLine<'a> {
 }
 
 pub fn compute(root: &style::Node) -> layout::Node {
-  let result = compute_internal(root, FlexSize::min(), FlexSize::max());
+  let result = compute_internal(root, SizeConstraint::undefined(), SizeConstraint::undefined());
 
   layout::Node {
     width: if root.flex_direction.is_row() { result.size.main } else { result.size.cross },
@@ -76,7 +103,7 @@ pub fn compute(root: &style::Node) -> layout::Node {
   }
 }
 
-fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> ComputeResult {
+fn compute_internal(node: &style::Node, main: SizeConstraint, cross: SizeConstraint) -> ComputeResult {
   // 9.2. Line Length Determination
 
   // 1. Generate anonymous flex items as described in §4 Flex Items.
@@ -114,24 +141,24 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
   //    margin, border, and padding from the space available to the flex container 
   //    in that dimension and use that value. This might result in an infinite value.
 
-  let avaliable_main = node.main_size().resolve(max.main, {
-    max.main 
-        - node.main_margin_start().resolve(max.main, 0.0) 
-        - node.main_margin_end().resolve(max.main, 0.0) 
-        - node.main_padding_start().resolve(max.main, 0.0) 
-        - node.main_padding_end().resolve(max.main, 0.0) 
-        - node.main_border_start().resolve(max.main, 0.0)
-        - node.main_border_end().resolve(max.main, 0.0)
+  let avaliable_main = node.main_size().resolve(main.max, {
+    main.max 
+        - node.main_margin_start().resolve(main.max, 0.0) 
+        - node.main_margin_end().resolve(main.max, 0.0) 
+        - node.main_padding_start().resolve(main.max, 0.0) 
+        - node.main_padding_end().resolve(main.max, 0.0) 
+        - node.main_border_start().resolve(main.max, 0.0)
+        - node.main_border_end().resolve(main.max, 0.0)
   });
 
-  let avaliable_cross = node.cross_size().resolve(max.cross, {
-    max.cross 
-        - node.cross_margin_start().resolve(max.cross, 0.0) 
-        - node.cross_margin_end().resolve(max.cross, 0.0) 
-        - node.cross_padding_start().resolve(max.cross, 0.0) 
-        - node.cross_padding_end().resolve(max.cross, 0.0) 
-        - node.cross_border_start().resolve(max.cross, 0.0)
-        - node.cross_border_end().resolve(max.cross, 0.0)
+  let avaliable_cross = node.cross_size().resolve(cross.max, {
+    cross.max 
+        - node.cross_margin_start().resolve(cross.max, 0.0) 
+        - node.cross_margin_end().resolve(cross.max, 0.0) 
+        - node.cross_padding_start().resolve(cross.max, 0.0) 
+        - node.cross_padding_end().resolve(cross.max, 0.0) 
+        - node.cross_border_start().resolve(cross.max, 0.0)
+        - node.cross_border_end().resolve(cross.max, 0.0)
   });
   
   // 3. Determine the flex base size and hypothetical main size of each item:
@@ -141,7 +168,7 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
     let flex_basis = child.node.flex_basis.resolve(avaliable_main, f32::INFINITY);
     if flex_basis.is_finite() {
       child.flex_basis = flex_basis;
-      break;
+      continue;
     }
 
     // B. If the flex item has an intrinsic aspect ratio,
@@ -154,7 +181,7 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
 
       if cross_size.is_finite() && child.node.flex_basis == style::Dimension::Auto {
         child.flex_basis = ratio * cross_size;
-        break;
+        continue;
       }
     }
 
@@ -164,10 +191,10 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
     //    size the item under that constraint. The flex base size is the item’s 
     //    resulting main size.
 
-    if min.main.is_finite() || max.main.is_finite() {
-      let size = compute_internal(child.node, min, max).size;
+    if main.min.is_finite() || main.max.is_finite() {
+      let size = compute_internal(child.node, main, cross).size;
       child.flex_basis = size.main;
-      break;
+      continue;
     }
 
     // D. Otherwise, if the used flex basis is content or depends on its
@@ -177,9 +204,9 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
     //    is the item’s max-content main size.
 
     if avaliable_main.is_infinite() {
-      let size = compute_internal(child.node, FlexSize::min(), FlexSize::max()).size;
+      let size = compute_internal(child.node, SizeConstraint::undefined(), SizeConstraint::undefined()).size;
       child.flex_basis = size.main;
-      break;
+      continue;
     }
 
     // E. Otherwise, size the item into the available space using its used flex basis
@@ -189,7 +216,7 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
     //    is auto and not definite, in this calculation use fit-content as the
     //    flex item’s cross size. The flex base size is the item’s resulting main size.
 
-    let size = compute_internal(child.node, FlexSize::min(), FlexSize { main: avaliable_main, cross: avaliable_cross }).size;
+    let size = compute_internal(child.node, SizeConstraint::at_most(avaliable_main), SizeConstraint::at_most(avaliable_cross)).size;
     child.flex_basis = size.main;
   }
 
@@ -203,8 +230,8 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
         .max(0.0);
 
     child.hypothetical_outer_main_size = child.hypothetical_inner_main_size
-        + node.main_margin_start().resolve(avaliable_main, 0.0) 
-        + node.main_margin_end().resolve(avaliable_main, 0.0);
+        + child.node.main_margin_start().resolve(avaliable_main, 0.0) 
+        + child.node.main_margin_end().resolve(avaliable_main, 0.0);
   }
 
   // 9.3. Main Size Determination
@@ -261,12 +288,8 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
     //    use the flex grow factor for the rest of this algorithm; otherwise, use the
     //    flex shrink factor.
 
-    let mut used_space = 0.0;
-    for child in line.items.iter() {
-      used_space += child.hypothetical_outer_main_size;
-    }
-
-    let growing = used_space < avaliable_main;
+    let used_flex_factor: f32 = line.items.iter().map(|child| child.hypothetical_outer_main_size).sum();
+    let growing = used_flex_factor < avaliable_main;
     let shrinking = !growing;
 
     // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size
@@ -277,10 +300,11 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
     //      smaller than its hypothetical main size
 
     for child in line.items.iter_mut() {
+      child.target_main_size = child.hypothetical_inner_main_size;
+      
       if (child.node.flex_grow == 0.0 && child.node.flex_shrink == 0.0) ||
          (growing && child.flex_basis > child.hypothetical_inner_main_size) ||
          (shrinking && child.flex_basis < child.hypothetical_inner_main_size) {
-        child.target_main_size = child.hypothetical_inner_main_size;
         child.frozen = true;
       }
     }
@@ -290,6 +314,8 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
     //    use their outer target main size; for other items, use their outer flex base size.
 
     let used_space: f32 = line.items.iter().map(|child| {
+      child.node.main_margin_start().resolve(avaliable_main, 0.0) +
+      child.node.main_margin_end().resolve(avaliable_main, 0.0) + 
       if child.frozen {
         child.target_main_size
       } else {
@@ -327,6 +353,8 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
       //    as the remaining free space.
 
       let used_space: f32 = Iterator::chain(frozen.iter(), unfrozen.iter()).map(|child| {
+        child.node.main_margin_start().resolve(avaliable_main, 0.0) +
+        child.node.main_margin_end().resolve(avaliable_main, 0.0) + 
         if child.frozen {
           child.target_main_size
         } else {
@@ -424,6 +452,27 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
     }
   }
 
+  // Not part of the spec from what i can see but seems correct
+  let container_main_size = if avaliable_main.is_finite() {
+    avaliable_main
+  } else {
+    let mut longest_line = f32::MIN;
+
+    for line in &flex_lines {
+      let length: f32 = line.items.iter().map(|item| {
+        item.target_main_size
+      }).sum();
+
+      longest_line = if length > longest_line {
+        length
+      } else {
+        longest_line
+      };
+    }
+
+    longest_line
+  }.min(main.max).max(main.min).max(0.0);
+
   // 9.4. Cross Size Determination
 
   // 7. Determine the hypothetical cross size of each item by performing layout with the
@@ -432,9 +481,9 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
   for line in &mut flex_lines {
     for child in &mut line.items {
       let size = compute_internal(
-        child.node, 
-        FlexSize { main: child.target_main_size, cross: min.cross }, 
-        FlexSize { main: child.target_main_size, cross: avaliable_cross },
+        child.node,
+        SizeConstraint::exactly(child.target_main_size),
+        SizeConstraint::between(cross.min, avaliable_cross),
       ).size;
 
       child.hypothetical_inner_cross_size = size.cross
@@ -467,7 +516,7 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
   //    of min/max-width/height applied more generally, this behavior would fall out automatically.
 
   if flex_lines.len() == 1 && avaliable_cross.is_finite() {
-    flex_lines[0].cross_size = avaliable_cross.min(max.cross).max(min.cross);
+    flex_lines[0].cross_size = avaliable_cross.min(cross.max).max(cross.min);
   } else {
     for line in &mut flex_lines {
       // TODO handle baseline (1)
@@ -540,8 +589,8 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
       }
 
       if is_stretch {
-        let size = FlexSize { main: child.target_main_size, cross: child.target_cross_size };
-        compute_internal(child.node, size, size);
+        // TODO use result somehow
+        compute_internal(child.node, SizeConstraint::exactly(child.target_main_size), SizeConstraint::exactly(child.target_cross_size));
       }
     }
   }
@@ -657,7 +706,7 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
     avaliable_cross
   } else {
     total_cross_size
-  }.min(max.cross).max(min.cross);;
+  }.min(cross.max).max(cross.min);;
 
   // 16. Align all flex lines per align-content.
 
@@ -679,24 +728,45 @@ fn compute_internal(node: &style::Node, min: FlexSize, max: FlexSize) -> Compute
 
   let mut children: Vec<layout::Node> = vec![];
   for line in &mut flex_lines {
+    let mut sibling_offset = 0.0;
+
     for child in &mut line.items {
-      let size = FlexSize { main: avaliable_main, cross: container_cross_size };
-      let result = compute_internal(child.node, size, size);
+      let result = compute_internal(
+        child.node, 
+        SizeConstraint::exactly(child.target_main_size), 
+        SizeConstraint::exactly(child.target_cross_size));
+
+      let offset_main = {
+        child.offset_main +
+        child.main_margin_start + 
+        sibling_offset + 
+        node.main_padding_start().resolve(container_main_size, 0.0) + 
+        node.main_border_start().resolve(container_main_size, 0.0)
+      };
+
+      let offset_cross = {
+        child.offset_cross + 
+        line.offset_cross + 
+        child.cross_margin_start +
+        node.cross_padding_start().resolve(container_cross_size, 0.0) +
+        node.cross_border_start().resolve(container_cross_size, 0.0)
+      };
 
-      // TODO incorporate margins, paddings, borders, and sublings
       children.push(layout::Node {
         width: if child.node.flex_direction.is_row() { result.size.main } else { result.size.cross },
         height: if child.node.flex_direction.is_column() { result.size.main } else { result.size.cross },
-        x: if child.node.flex_direction.is_row() { child.offset_main } else { child.offset_cross + line.offset_cross },
-        y: if child.node.flex_direction.is_column() { child.offset_main } else { child.offset_cross + line.offset_cross },
+        x: if child.node.flex_direction.is_row() { offset_main } else {offset_cross },
+        y: if child.node.flex_direction.is_column() { offset_main } else {offset_cross },
         children: result.children,
       });
+
+      sibling_offset += offset_main + result.size.main + child.main_margin_end;
     }
   }
 
   ComputeResult {
     size: FlexSize {
-      main: avaliable_main,
+      main: container_main_size,
       cross: container_cross_size,
     },
     children: children,
diff --git a/src/style.rs b/src/style.rs
index 64a6553b4..a48822566 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -143,8 +143,8 @@ impl Dimension {
   pub(crate) fn resolve(&self, parent_size: f32, auto_size: f32) -> f32 {
     match self {
       Dimension::Points(points) => *points,
-      Dimension::Percent(percent) => percent * parent_size,
-      Dimension::Auto => auto_size,
+      Dimension::Percent(percent) if parent_size.is_finite() => percent * parent_size,
+      _ => auto_size,
     }
   }
 }
diff --git a/tests/lib.rs b/tests/lib.rs
index 4f0490ff5..649965792 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -1,7 +1,7 @@
 #[cfg(test)]
 mod tests {
     #[test]
-    fn defined_width_height() {
+    fn test_1() {
         let layout = stretch::compute(&stretch::style::Node {
             width: stretch::style::Dimension::Points(100.0),
             height: stretch::style::Dimension::Points(100.0),
@@ -11,4 +11,127 @@ mod tests {
         assert_eq!(layout.width, 100.0);
         assert_eq!(layout.height, 100.0);
     }
+
+    #[test]
+    fn test_2() {
+        let layout = stretch::compute(&stretch::style::Node {
+            width: stretch::style::Dimension::Points(100.0),
+            height: stretch::style::Dimension::Points(100.0),
+
+            children: vec![
+                stretch::style::Node {
+                    width: stretch::style::Dimension::Percent(0.5),
+
+                    ..Default::default()
+                },
+            ],
+          
+            ..Default::default()
+        });
+
+        assert_eq!(layout.children[0].width, 50.0);
+        assert_eq!(layout.children[0].height, 100.0);
+    }
+
+    #[test]
+    fn test_3() {
+        let layout = stretch::compute(&stretch::style::Node {
+            width: stretch::style::Dimension::Points(100.0),
+            height: stretch::style::Dimension::Points(100.0),
+
+            children: vec![
+                stretch::style::Node {
+                    width: stretch::style::Dimension::Percent(0.5),
+
+                    ..Default::default()
+                },
+                stretch::style::Node {
+                    width: stretch::style::Dimension::Percent(0.5),
+
+                    ..Default::default()
+                },
+            ],
+          
+            ..Default::default()
+        });
+
+        assert_eq!(layout.children[0].width, 50.0);
+        assert_eq!(layout.children[0].height, 100.0);
+        assert_eq!(layout.children[0].x, 0.0);
+        assert_eq!(layout.children[0].y, 0.0);
+
+        assert_eq!(layout.children[1].width, 50.0);
+        assert_eq!(layout.children[1].height, 100.0);
+        assert_eq!(layout.children[1].x, 50.0);
+        assert_eq!(layout.children[1].y, 0.0);
+    }
+
+    #[test]
+    fn test_4() {
+        let layout = stretch::compute(&stretch::style::Node {
+            width: stretch::style::Dimension::Points(100.0),
+            height: stretch::style::Dimension::Points(100.0),
+
+            children: vec![
+                stretch::style::Node {
+                    width: stretch::style::Dimension::Points(20.0),
+                    flex_grow: 1.0,
+
+                    ..Default::default()
+                },
+                stretch::style::Node {
+                    width: stretch::style::Dimension::Points(20.0),
+
+                    ..Default::default()
+                },
+            ],
+          
+            ..Default::default()
+        });
+
+        assert_eq!(layout.children[0].width, 80.0);
+        assert_eq!(layout.children[0].height, 100.0);
+        assert_eq!(layout.children[0].x, 0.0);
+        assert_eq!(layout.children[0].y, 0.0);
+
+        assert_eq!(layout.children[1].width, 20.0);
+        assert_eq!(layout.children[1].height, 100.0);
+        assert_eq!(layout.children[1].x, 80.0);
+        assert_eq!(layout.children[1].y, 0.0);
+    }
+
+    #[test]
+    fn test_5() {
+        let layout = stretch::compute(&stretch::style::Node {
+            width: stretch::style::Dimension::Points(100.0),
+            height: stretch::style::Dimension::Points(100.0),
+
+            children: vec![
+                stretch::style::Node {
+                    width: stretch::style::Dimension::Points(100.0),
+                    flex_shrink: 1.0,
+
+                    ..Default::default()
+                },
+                stretch::style::Node {
+                    width: stretch::style::Dimension::Points(100.0),
+                    flex_shrink: 1.0,
+
+                    ..Default::default()
+                },
+            ],
+          
+            ..Default::default()
+        });
+
+        assert_eq!(layout.children[0].width, 50.0);
+        assert_eq!(layout.children[0].height, 100.0);
+        assert_eq!(layout.children[0].x, 0.0);
+        assert_eq!(layout.children[0].y, 0.0);
+
+        assert_eq!(layout.children[1].width, 50.0);
+        assert_eq!(layout.children[1].height, 100.0);
+        assert_eq!(layout.children[1].x, 50.0);
+        assert_eq!(layout.children[1].y, 0.0);
+    }
 }