~alcinnz/CatTrap

b47a0b46c730af87d9231b13666ffb21c29e150f — Adrian Cochrane 1 year, 4 months ago eae2c20
Unittest & fix abstract layout.
4 files changed, 450 insertions(+), 25 deletions(-)

M Graphics/Layout.hs
M Graphics/Layout/Box.hs
M Graphics/Layout/Grid.hs
M test/Test.hs
M Graphics/Layout.hs => Graphics/Layout.hs +250 -11
@@ 1,21 1,260 @@
{-# LANGUAGE OverloadedStrings, RecordWildCards #-}
module Graphics.Layout where

import Graphics.Layout.Box
import Graphics.Layout.Grid
import Graphics.Layout.Box as B
import Graphics.Layout.Grid as G
import Graphics.Layout.Flow as F

import Data.Maybe (fromMaybe)

data LayoutItem m n x =
    LayoutFlow x (PaddedBox m n) [LayoutItem m n x]
    | LayoutGrid x (Grid m n) [(GridItem m n, LayoutItem m n x)]
-- More to come...

{-boxMinWidth :: Double -> LayoutItem y Length x -> (Double, LayoutItem y Length x)
boxNatWidth :: Double -> LayoutItem y Length x -> (Double, LayoutItem y Length x)
boxMaxWidth :: PaddedBox y Length -> LayoutItem y Length x -> (Double, LayoutItem y Length x)
boxWidth :: PaddedBox y Length -> LayoutItem y Length x -> (Double, LayoutItem y Double x)
boxNatHeight :: LayoutItem Length Double x -> (Double, LayoutItem Length Double x)
layoutGetBox (LayoutFlow _ ret _) = ret
layoutGetBox (LayoutGrid _ self _) = zero {
    B.min = containerMin self,
    B.size = containerSize self,
    B.max = containerMax self
}
setCellBox' (child, cell) = cell { gridItemBox = layoutGetBox child }

boxMinWidth :: Zero y => Maybe Double -> LayoutItem y Length x -> (Double, LayoutItem y Length x)
boxMinWidth parent (LayoutFlow val self childs) = (min', LayoutFlow val self' childs')
  where
    self' = self {B.min = Size (Pixels min') (block $ B.min self) }
    min' = flowMinWidth parent' self childs''
    childs'' = map (mapX' $ lowerLength selfWidth) $ map layoutGetBox childs'
    childs' = map snd $ map (boxMinWidth $ Just selfWidth) childs
    selfWidth = width $ mapX' (lowerLength parent') self
    parent' = fromMaybe 0 parent
boxMinWidth parent (LayoutGrid val self childs) =
    (min', LayoutGrid val self' $ zip cells' childs')
  where
    self' = self {
        containerMin = Size (Pixels min') (block $ containerMin self),
        colBounds = zip cells (map snd (colBounds self) ++ repeat 0)
      }
    (min', cells) = gridMinWidths parent' self cells''
    cells'' = [ setCellBox (mapX' (lowerLength selfWidth) $ gridItemBox cell) cell
                | cell <- cells']
    cells' = map setCellBox' $ zip childs' $ map fst childs
    childs'' = map (mapX' $ lowerLength selfWidth) $ map layoutGetBox childs'
    childs' = map snd $ map (boxMinWidth $ Just selfWidth) $ map snd childs
    selfWidth = lowerLength parent' $ inline $ containerSize self
    parent' = fromMaybe (gridEstWidth self [
        GridItem startRow endRow startCol endCol alignment zeroBox |
        (GridItem {..}, _) <- childs]) parent
    zeroBox :: PaddedBox Double Double
    zeroBox = zero
boxNatWidth :: Zero y => Maybe Double -> LayoutItem y Length x -> (Double, LayoutItem y Length x)
boxNatWidth parent (LayoutFlow val self childs) = (size', LayoutFlow val self childs')
  -- NOTE: Need to preserve auto/percentage in actual width calculation.
  -- self' doesn't preserve this. CatTrap will need a decent refactor!
  where
    self' = self {size = Size (Pixels size') (block $ size self) }
    size' = flowNatWidth parent' self childs''
    childs'' = map (mapX' $ lowerLength selfWidth) $ map layoutGetBox childs'
    childs' = map snd $ map (boxNatWidth $ Just selfWidth) childs
    selfWidth = width $ mapX' (lowerLength parent') self
    parent' = fromMaybe 0 parent
boxNatWidth parent (LayoutGrid val self childs) =
    (size', LayoutGrid val self' $ zip cells' childs')
  where
    self' = self {
        containerSize = Size (Pixels size') (block $ containerSize self),
        colBounds = zip (map fst (colBounds self) ++ repeat 0) cells
      }
    (size', cells) = gridNatWidths parent' self cells''
    cells'' = [
        cell { gridItemBox = mapX' (lowerLength selfWidth) $ gridItemBox cell }
        | cell <- cells']
    cells' = map setCellBox $ zip childs' $ map fst childs
    setCellBox (child, cell) = cell { gridItemBox = layoutGetBox child }
    childs'' = map (mapX' $ lowerLength selfWidth) $ map layoutGetBox childs'
    childs' = map snd $ map (boxNatWidth $ Just selfWidth) $ map snd childs
    selfWidth = lowerLength parent' $ inline $ containerSize self
    parent' = fromMaybe (gridEstWidth self [
        GridItem startRow endRow startCol endCol alignment zeroBox |
        (GridItem {..}, _) <- childs]) parent
    zeroBox :: PaddedBox Double Double
    zeroBox = zero
boxMaxWidth :: PaddedBox a Double -> LayoutItem y Length x -> (Double, LayoutItem y Length x)
boxMaxWidth parent (LayoutFlow val self childs) = (max', LayoutFlow val self' childs)
  where
    self' = self { B.max = Size (Pixels max') (block $ B.max self) }
    max' = flowMaxWidth parent self
boxMaxWidth parent (LayoutGrid val self childs) =
    (max', LayoutGrid val self' childs)
  where
    self' = self { containerMax = Size (Pixels max') (block $ containerMax self) }
    (max', _) = gridMaxWidths parent self $ colBounds self
boxWidth :: Zero y => PaddedBox b Double -> LayoutItem y Length x ->
        (Double, LayoutItem y Double x)
boxWidth parent (LayoutFlow val self childs) = (size', LayoutFlow val self' childs')
  where
    childs' = map (snd . boxWidth self') childs
    self' = (mapX' (lowerLength $ inline $ size parent) self) {
        size = Size size' $ block $ B.max self
      }
    size' = flowWidth parent self
boxWidth parent (LayoutGrid val self childs) = (size', LayoutGrid val self' childs')
  where
    childs' = map recurse childs
    recurse (cell, child) = (cell', child')
      where
        cell' = cell { gridItemBox = layoutGetBox child' }
        (_, child') = boxWidth parent' child
        parent' = zero {
            B.min = containerMin self',
            B.size = containerSize self',
            B.max = containerMax self'
          }
    self' = Grid {
        containerSize = Size size' $ block $ containerSize self,
        containerMin = mapSizeX (lowerLength outerwidth) $ containerMin self,
        containerMax = mapSizeX (lowerLength outerwidth) $ containerMax self,
        gap = mapSizeX (lowerLength outerwidth) $ gap self,
        columns = [("", Left width) | width <- widths],

        rows = rows self,
        rowBounds = rowBounds self,
        colBounds = colBounds self
      }
    outerwidth = inline $ size parent
    (size', widths) = gridWidths parent self $ colBounds self

boxNatHeight :: Double -> LayoutItem Length Double x -> (Double, LayoutItem Length Double x)
boxNatHeight parent (LayoutFlow val self childs) = (size', LayoutFlow val self' childs')
  where
    self' = self {
        size = Size width (Pixels size')
      }
    size' = flowNatHeight parent self childs''
    childs'' = map (mapY' (lowerLength parent)) $ map layoutGetBox childs'
    childs' = map snd $ map (boxNatHeight width) childs
    width = inline $ size self
boxNatHeight parent (LayoutGrid val self childs) =
    (size', LayoutGrid val self' $ zip cells childs')
  where
    self' = self {
        containerSize = Size width $ Pixels size'
    }
    lowerGridUnit (Left length) = Left $ lowerLength width length
    lowerGridUnit (Right x) = Right x
    (size', heights) = gridNatHeights parent self cells'
    cells' = [setCellBox (mapY' (lowerLength width) $ gridItemBox cell) cell | cell <- cells]
    cells = map setCellBox' $ zip childs' $ map fst childs
    childs' = map snd $ map (boxNatHeight width) $ map snd childs
    width = inline $ containerSize self
boxMinHeight :: Double -> LayoutItem Length Double x -> (Double, LayoutItem Length Double x)
boxMaxHeight :: Double -> LayoutItem Length Double x -> (Double, LayoutItem Length Double x)
boxHeight :: Double -> LayoutItem Length Double x -> (Double, LayoutItem Length Length x)
boxPosition :: LayoutItem Double Double x -> LayoutItem Double Double x
boxMinHeight parent (LayoutFlow val self childs) = (min', LayoutFlow val self' childs')
  where
    childs' = map snd $ map (boxMinHeight $ inline $ size self) childs
    self' = self {
        B.min = Size (inline $ B.min self) (Pixels min')
      }
    min' = flowMinHeight parent self
boxMinHeight parent (LayoutGrid val self childs) = (min', LayoutGrid val self' childs')
  where
    childs' = map recurse childs
    recurse (cell, child) = (cell', child')
      where
        cell' = setCellBox (layoutGetBox child') cell
        (_, child') = boxMinHeight width child
    self' = self {
        containerMin = Size width $ Pixels min',
        rowBounds = zip heights (map snd (rowBounds self) ++ repeat 0)
      }
    (min', heights) = gridMinHeights width self childs0
    childs0 = [ GridItem {
        gridItemBox = mapY' (lowerLength width) $ gridItemBox cell,
        startRow = startRow cell, endRow = endRow cell,
        startCol = startCol cell, endCol = endCol cell, alignment = alignment cell
      } | (cell, _) <- childs]
    width = inline $ containerSize self
boxMaxHeight :: PaddedBox Double Double -> LayoutItem Length Double x ->
        (Double, LayoutItem Length Double x)
boxMaxHeight parent (LayoutFlow val self childs) = (max', LayoutFlow val self' childs')
  where
    childs' = map snd $ map (boxMaxHeight $ mapY' (lowerLength width) self') childs
    self' = self {
        B.max = Size (inline $ B.max self) (Pixels max')
      }
    max' = flowMaxHeight (inline $ size parent) self
    width = inline $ size self
boxMaxHeight parent (LayoutGrid val self childs) = (max', LayoutGrid val self' childs')
  where
    childs' = map recurse childs
    recurse (cell, child) = (cell', child')
      where
        cell' = setCellBox (layoutGetBox child') cell
        (_, child') = boxMaxHeight parent' child
        parent' :: PaddedBox Double Double
        parent' = zero {
            B.min = mapSizeY (lowerLength width) $ containerMin self,
            B.size = mapSizeY (lowerLength width) $ containerSize self,
            B.max = Size (inline $ containerMax self') max'
        }
    self' = self {
        containerMax = Size (inline $ containerSize self) (Pixels max')
      }
    (max', heights) = gridMaxHeights parent self $ rowBounds self
    width = inline $ size parent
boxHeight :: PaddedBox Double Double -> LayoutItem Length Double x ->
        (Double, LayoutItem Double Double x)
boxHeight parent (LayoutFlow val self childs) = (size', LayoutFlow val self' childs')
  where
    childs' = map snd $ map (boxHeight self') childs
    self' = (mapY' (lowerLength $ inline $ size parent) self) {
        size = Size (inline $ size self) size'
      }
    size' = flowHeight parent self
    width = inline $ size self
boxHeight parent (LayoutGrid val self childs) = (size', LayoutGrid val self' childs')
  where
    childs' = map recurse childs
    recurse (cell, child) = (cell', child')
      where
        cell' = setCellBox (layoutGetBox child') cell
        (_, child') = boxHeight (layoutGetBox $ LayoutGrid val self' []) child
    self' = Grid {
        containerSize = Size (inline $ containerSize self) size',
        containerMin = mapSizeY (lowerLength width) $ containerMin self,
        containerMax = mapSizeY (lowerLength width) $ containerMax self,
        gap = mapSizeY (lowerLength width) $ gap self,

        rows = map lowerSize $ rows self, rowBounds = rowBounds self,
        columns = columns self, colBounds = colBounds self
      }
    (size', heights) = gridHeights parent self $ rowBounds self
    lowerSize (n, Left x) = (n, Left $ lowerLength width x)
    lowerSize (n, Right x) = (n, Right x)
    width = inline $ size parent

boxPosition :: (Double, Double) -> LayoutItem Double Double x ->
    LayoutItem Double Double ((Double, Double), x)
boxPosition pos@(x, y) (LayoutFlow val self childs) = LayoutFlow (pos, val) self childs'
  where
    childs' = map recurse $ zip pos' childs
    recurse ((Size x' y'), child) = boxPosition (x + x', y + y') child
    pos' = positionFlow $ map layoutGetBox childs
boxPosition pos@(x, y) (LayoutGrid val self childs) = LayoutGrid (pos, val) self childs'
  where
    childs' = map recurse $ zip pos' childs
    recurse ((Size x' y'), (cell, child)) = (cell, boxPosition (x + x', y + y') child)
    pos' = gridPosition self $ map fst childs
boxLayout :: PaddedBox Double Double -> LayoutItem Length Length x -> Bool ->
        LayoutItem Double Double x-}
        LayoutItem Double Double ((Double, Double), x)
boxLayout parent self paginate = self8
  where
    (_, self0) = boxMinWidth Nothing self
    (_, self1) = boxNatWidth Nothing self0
    (_, self2) = boxMaxWidth parent self1
    (_, self3) = boxWidth parent self2
    (natsize, self4) = boxNatHeight (inline $ size parent) self3
    (_, self5) = boxMinHeight natsize self4
    (_, self6) = boxMaxHeight parent self5
    (_, self7) = boxHeight parent self6
    self8 = boxPosition (0, 0) self7

M Graphics/Layout/Box.hs => Graphics/Layout/Box.hs +37 -1
@@ 10,6 10,8 @@ mapX cb self = self { left = cb $ left self, right = cb $ right self }
mapY cb self = self { top = cb $ top self, bottom = cb $ bottom self }

data Size m n = Size {inline :: n, block :: m} deriving (Eq, Show)
mapSizeY cb self = Size (inline self) (cb $ block self)
mapSizeX cb self = Size (cb $ inline self) (block self)

data PaddedBox m n = PaddedBox {
    min :: Size m n,


@@ 35,7 37,26 @@ lengthBox = PaddedBox {
    padding = Border zero zero zero zero,
    border = Border zero zero zero zero,
    margin = Border zero zero zero zero
  } where zero = Pixels 0
  }

mapX' :: (n -> nn) -> PaddedBox m n -> PaddedBox m nn
mapX' cb PaddedBox {..} = PaddedBox {
    min = Size (cb $ inline min) (block min),
    size = Size (cb $ inline size) (block size),
    max = Size (cb $ inline max) (block max),
    padding = mapX cb padding,
    border = mapX cb border,
    margin = mapX cb margin
  }
mapY' :: (m -> mm) -> PaddedBox m n -> PaddedBox mm n
mapY' cb PaddedBox {..} = PaddedBox {
    min = Size (inline min) (cb $ block min),
    size = Size (inline size) (cb $ block size),
    max = Size (inline max) (cb $ block max),
    padding = mapY cb padding,
    border = mapY cb border,
    margin = mapY cb margin
  }

width PaddedBox {..} = left margin + left border + left padding +
    inline size + right padding + right border + right margin


@@ 56,3 77,18 @@ lowerLength :: Double -> Length -> Double
lowerLength _ (Pixels x) = x
lowerLength outerwidth (Percent x) = x * outerwidth
lowerLength _ _ = 0

class Zero a where
    zero :: a

instance Zero Double where zero = 0
instance Zero Length where zero = Pixels 0
instance (Zero m, Zero n) => Zero (PaddedBox m n) where
    zero = PaddedBox {
        min = Size zero zero,
        max = Size zero zero,
        size = Size zero zero,
    padding = Border zero zero zero zero,
    border = Border zero zero zero zero,
    margin = Border zero zero zero zero
    }

M Graphics/Layout/Grid.hs => Graphics/Layout/Grid.hs +17 -12
@@ 14,7 14,9 @@ data Grid m n = Grid {
    columns :: [(Name, Either n Double)],
    colBounds :: [(Double, Double)],
    gap :: Size m n,
    containerSize :: Size m n -- wrap in a Flow box to get padding, etc.
    containerSize :: Size m n, -- wrap in a Flow box to get padding, etc.
    containerMin :: Size m n,
    containerMax :: Size m n
}
data GridItem m n = GridItem {
    startRow :: Int, endRow :: Int, startCol :: Int, endCol :: Int,


@@ 31,8 33,13 @@ buildGrid rows columns = Grid {
    columns = zip (repeat "") columns,
    colBounds = [],
    gap = Size (Pixels 0) (Pixels 0),
    containerSize = Size Auto Auto
    containerSize = Size Auto Auto,
    containerMin = Size Auto Auto,
    containerMax = Size Auto Auto
}
-- Created to address typesystem issues with record syntax.
setCellBox :: PaddedBox mm nn -> GridItem m n -> GridItem mm nn
setCellBox box' GridItem {..} = GridItem startRow endRow startCol endCol alignment box'

cellsForCol :: [GridItem y x] -> Int -> [GridItem y x]
cellsForCol cells ix =


@@ 109,11 116,6 @@ gridWidths parent self subwidths =
    colWidth' fr ((_, nat), Left Auto) = Prelude.min nat fr
    colWidth' fr (_, Right x) = x*fr

gridEstHeight :: Grid Length Double -> [GridItem Double Double] -> Double
gridEstHeight self childs = fst $ gridMaxHeights zeroBox self $ zip mins nats
  where
    mins = snd $ gridMinHeights 0 self childs
    nats = snd $ gridNatHeights 0 self childs
gridNatHeights :: Double -> Grid Length Double -> [GridItem Double Double] -> (Double, [Double])
gridNatHeights parent self childs =
    (sum $ intersperse (lowerLength parent $ block $ gap self) ret, ret)


@@ 202,21 204,24 @@ gridLayout parent self childs paginate = (self', zip positions childs)
        columns = zip (map fst $ rows self) $ map Left cols',
        colBounds = colBounds',
        gap = Size (lowerLength width' gapX) (lowerLength width' gapY),
        containerSize = Size width' height'
        containerSize = Size width' height',
        containerMin = Size width' height',
        containerMax = Size width' height'
      }
    Size gapX gapY = gap self

    (height', rows') = gridHeights parent self0 rowBounds'
    rowBounds' = zip rowMins rowNats
    (_, rowMins) = gridMinHeights estHeight self0 childs
    (_, rowNats) = gridNatHeights estHeight self0 childs
    estHeight = gridEstHeight self0 childs
    (_, rowMins) = gridMinHeights width' self0 childs
    (_, rowNats) = gridNatHeights width' self0 childs

    self0 = self {
        columns = zip (map fst $ columns self) $ map Left cols',
        colBounds = colBounds',
        gap = Size (lowerLength width' gapX) gapY,
        containerSize = let Size _ y = containerSize self in Size width' y
        containerSize = let Size _ y = containerSize self in Size width' y,
        containerMin = let Size _ y = containerSize self in Size width' y,
        containerMax = let Size _ y = containerSize self in Size width' y
      }
    (width', cols') = gridWidths parent self colBounds'
    colBounds' = zip colMins colNats

M test/Test.hs => test/Test.hs +146 -1
@@ 5,9 5,11 @@ import Test.Hspec

import Graphics.Layout.Arithmetic
import Data.CSS.Syntax.Tokens (tokenize, Token(..))

import Graphics.Layout.Box as B
import Graphics.Layout.Grid
import Graphics.Layout.Flow
import Graphics.Layout

main :: IO ()
main = hspec spec


@@ 25,7 27,7 @@ spec = do
            runMath "6 * (9 - 42)" `shouldBe` -198
            runMath "6 * calc(9 - 42)" `shouldBe` -198
            runMath "6 * abs(9 - 42)" `shouldBe` 198
    describe "Width sizing" $ do
    describe "Flow sizing" $ do
        -- Based on http://hixie.ch/tests/adhoc/css/box/block/
        it "Can overflow parent" $ do
            width (fst $ layoutFlow zeroBox {


@@ 62,6 64,13 @@ spec = do
                    margin = Border (Pixels 0) (Pixels 0) (Pixels 2) (Pixels 2),
                    size = Size Auto $ Pixels 1
                } [] False) `shouldBe` 5
        it "Fits children" $ do
            let child = mapX' (lowerLength 100) $ lengthBox {
                size = Size (Pixels 10) (Pixels 10)
              }
            height (fst $ layoutFlow zeroBox {
                size = Size 100 100
              } lengthBox [child, child] False) `shouldBe` 20
        it "Collapses margins" $ do
            let a :: PaddedBox Length Double
                a = PaddedBox {


@@ 134,5 143,141 @@ spec = do
                    }] True
            containerSize minGrid `shouldBe` Size 10 10
            fst (head minCells) `shouldBe` Size 0 0
    describe "Abstract layout" $ do
        it "Can overflow parent" $ do
            width (layoutGetBox $ boxLayout zeroBox {
                    size = Size 3 1
                } (LayoutFlow () lengthBox {
                    border = Border (Pixels 0) (Pixels 0) (Pixels 2) (Pixels 2)
                } []) False) `shouldBe` 4
            height (layoutGetBox $ boxLayout zeroBox {
                    size = Size 3 1
                } (LayoutFlow () lengthBox {
                    border = Border (Pixels 2) (Pixels 2) (Pixels 2) (Pixels 2)
                } []) False) `shouldBe` 4
            width (layoutGetBox $ boxLayout zeroBox {
                    size = Size 3 1
                } (LayoutFlow () lengthBox {
                    padding = Border (Pixels 0) (Pixels 0) (Pixels 2) (Pixels 2)
                } []) False) `shouldBe` 4
            height (layoutGetBox $ boxLayout zeroBox {
                    size = Size 3 1
                } (LayoutFlow () lengthBox {
                    padding = Border (Pixels 2) (Pixels 2) (Pixels 2) (Pixels 2)
                } []) False) `shouldBe` 4
            width (layoutGetBox $ boxLayout zeroBox {
                    size = Size 3 1
                } (LayoutFlow () lengthBox {
                    margin = Border (Pixels 0) (Pixels 0) (Pixels 2) (Pixels 2)
                } []) False) `shouldBe` 4
            height (layoutGetBox $ boxLayout zeroBox {
                    size = Size 3 1
                } (LayoutFlow () lengthBox {
                    margin = Border (Pixels 2) (Pixels 2) (Pixels 2) (Pixels 2)
                } []) False) `shouldBe` 4
        it "Fits to parent" $ do
            width (layoutGetBox $ boxLayout zeroBox {
                    size = Size 5 1
                } (LayoutFlow () lengthBox {
                    border = Border (Pixels 0) (Pixels 0) (Pixels 2) (Pixels 2),
                    size = Size Auto $ Pixels 1
                } []) False) `shouldBe` 5
            width (layoutGetBox $ boxLayout zeroBox {
                    size = Size 5 1
                } (LayoutFlow () lengthBox {
                    padding = Border (Pixels 0) (Pixels 0) (Pixels 2) (Pixels 2),
                    size = Size Auto $ Pixels 1
                } []) False) `shouldBe` 5
            width (layoutGetBox $ boxLayout zeroBox {
                    size = Size 5 1
                } (LayoutFlow () lengthBox {
                    margin = Border (Pixels 0) (Pixels 0) (Pixels 2) (Pixels 2),
                    size = Size Auto $ Pixels 1
                } []) False) `shouldBe` 5
        it "Fits children" $ do
            let child = LayoutFlow () lengthBox {
                size = Size (Pixels 10) (Pixels 10),
                B.max = Size (Pixels 10) (Pixels 10)
              } []
            height (layoutGetBox $ boxLayout zeroBox {
                size = Size 100 100
              } child False) `shouldBe` 10
            height (layoutGetBox $ boxLayout zeroBox {
                size = Size 100 100
              } (LayoutFlow () lengthBox [child, child]) False) `shouldBe` 20
        it "Collapses margins" $ do
            let a :: LayoutItem Length Length ()
                a = LayoutFlow () PaddedBox {
                    B.min = Size Auto Auto,
                    size = Size Auto Auto,
                    B.max = Size Auto Auto,
                    padding = Border (Pixels 0) (Pixels 0) (Pixels 0) (Pixels 0),
                    border = Border (Pixels 0) (Pixels 0) (Pixels 0) (Pixels 0),
                    margin = Border (Pixels 5) (Pixels 10) (Pixels 0) (Pixels 0)
                  } []
                b :: LayoutItem Length Length ()
                b = LayoutFlow () PaddedBox {
                    B.min = Size Auto Auto,
                    size = Size Auto Auto,
                    B.max = Size Auto Auto,
                    padding = Border (Pixels 0) (Pixels 0) (Pixels 0) (Pixels 0),
                    border = Border (Pixels 0) (Pixels 0) (Pixels 0) (Pixels 0),
                    margin = Border (Pixels 10) (Pixels 5) (Pixels 0) (Pixels 0)
                  } []
            height (layoutGetBox $ boxLayout zeroBox {
                    size = Size 100 100
                } (LayoutFlow () lengthBox [a, a]) False) `shouldBe` 25
            height (layoutGetBox $ boxLayout zeroBox {
                    size = Size 100 100
                } (LayoutFlow () lengthBox [b, b]) False) `shouldBe` 25
            height (layoutGetBox $ boxLayout zeroBox {
                    size = Size 100 100
                } (LayoutFlow () lengthBox [a, b]) False) `shouldBe` 20
            height (layoutGetBox $ boxLayout zeroBox {
                    size = Size 100 100
                } (LayoutFlow () lengthBox [b, a]) False) `shouldBe` 25

        it "computes single-columns widths/heights" $ do
            let zeroCell = LayoutFlow () lengthBox []
            let nonzeroCell = LayoutFlow () lengthBox {
                B.min = Size (Pixels 10) (Pixels 10),
                size = Size (Pixels 20) (Pixels 20)
              } []

            let LayoutGrid (_, _) pxGrid pxCells = boxLayout zeroBox {
                    size = Size 100 100
                  } (LayoutGrid () (buildGrid [Left $ Pixels 10] [Left $ Pixels 10])
                    [(GridItem 0 1 0 1 (Size Start Start) lengthBox, zeroCell)]) True
            let LayoutFlow (pos, _) _ _ = snd $ head pxCells
            containerSize pxGrid `shouldBe` Size 10 10
            pos `shouldBe` (0, 0)
            let LayoutGrid (_, _) pxGrid pxCells = boxLayout zeroBox {
                    size = Size 100 100
                  } (LayoutGrid () (buildGrid [Left $ Percent 0.5] [Left $ Percent 0.5])
                    [(GridItem 0 1 0 1 (Size Start Start) lengthBox, zeroCell)]) True
            let LayoutFlow (pos, _) _ _ = snd $ head pxCells
            containerSize pxGrid `shouldBe` Size 50 50
            pos `shouldBe` (0, 0)
            let LayoutGrid (_, _) pxGrid pxCells = boxLayout zeroBox {
                    size = Size 100 100
                  } (LayoutGrid () (buildGrid [Left Auto] [Left Auto])
                    [(GridItem 0 1 0 1 (Size Start Start) lengthBox, nonzeroCell)]) True
            let LayoutFlow (pos, _) _ _ = snd $ head pxCells
            containerSize pxGrid `shouldBe` Size 20 10 -- FIXME Is the 10 correct?
            pos `shouldBe` (0, 0)
            let LayoutGrid (_, _) pxGrid pxCells = boxLayout zeroBox {
                    size = Size 100 100
                  } (LayoutGrid () (buildGrid [Left Preferred] [Left Preferred])
                    [(GridItem 0 1 0 1 (Size Start Start) lengthBox, nonzeroCell)]) True
            let LayoutFlow (pos, _) _ _ = snd $ head pxCells
            containerSize pxGrid `shouldBe` Size 20 0 -- FIXME Is the 0 correct?
            pos `shouldBe` (0, 0)
            let LayoutGrid (_, _) pxGrid pxCells = boxLayout zeroBox {
                    size = Size 100 100
                  } (LayoutGrid () (buildGrid [Left Min] [Left Min])
                    [(GridItem 0 1 0 1 (Size Start Start) lengthBox, nonzeroCell)]) True
            let LayoutFlow (pos, _) _ _ = snd $ head pxCells
            containerSize pxGrid `shouldBe` Size 10 10
            pos `shouldBe` (0, 0)

runMath = flip evalCalc [] . mapCalc fst . flip parseCalc [] . filter (/= Whitespace) . tokenize