-- | Sizes a block element & positions their children.
-- Taking into account size bounds.
module Graphics.Layout.Flow(flowMinWidth, flowNatWidth, flowMaxWidth, flowWidth,
flowNatHeight, flowMinHeight, flowMaxHeight, flowHeight,
positionFlow, layoutFlow) where
import Graphics.Layout.Box as B
-- | Compute the minimum width of a block element with children of the given sizes.
flowMinWidth :: Double -> PaddedBox a Length -> [PaddedBox b Double] -> Double
flowMinWidth _ PaddedBox {B.min = Size (Pixels x) _} _ = x
flowMinWidth parent PaddedBox {B.min = Size (Percent x) _} _ = x * parent
flowMinWidth parent self@PaddedBox {B.min = Size Preferred _} childs =
flowNatWidth parent self childs
flowMinWidth _ _ childs = maximum $ (0:) $ map minWidth childs
-- | Compute the natural width of a block element with children of the given sizes.
flowNatWidth :: Double -> PaddedBox a Length -> [PaddedBox b Double] -> Double
flowNatWidth _ PaddedBox {size = Size (Pixels x) _} _ = x
flowNatWidth parent PaddedBox {size = Size (Percent x) _} _ = x * parent
flowNatWidth parent self@PaddedBox {size = Size Min _, B.min = Size x _} childs
-- Avoid infinite loops!
| x /= Preferred = flowMinWidth parent self childs
flowNatWidth parent _ childs = maximum $ (0:) $ map maxWidth childs
-- | Compute the maximum width of a block element inside the given parent size.
flowMaxWidth :: PaddedBox a Double -> PaddedBox b Length -> Double
flowMaxWidth _ PaddedBox {B.max = Size (Pixels x) _} = x
flowMaxWidth parent PaddedBox {B.max = Size (Percent x) _} = x * (inline $ size parent)
flowMaxWidth parent self@PaddedBox {B.max = Size Auto _} = inline (size parent) - ws
where
ws = l2d (left $ margin self) + l2d (left $ border self) + l2d (left $ padding self) +
l2d (right $ padding self) + l2d (right $ border self) + l2d (right $ margin self)
l2d = lowerLength $ inline $ size parent
flowMaxWidth parent self@PaddedBox {B.max = Size Preferred _} =
flowNatWidth (inline $ size parent) self []
flowMaxWidth parent self@PaddedBox {B.max = Size Min _} =
flowMinWidth (inline $ B.min parent) self []
-- | Compute final block element width based on cached width computations &
-- parent size.
flowWidth :: PaddedBox a Double -> PaddedBox b Length -> Double
flowWidth parent self
| small > large = small
| natural > large = large
| inline (size self) == Auto = large -- specialcase
| natural >= small = natural
| otherwise = small
where
small = flowMinWidth (inline $ B.min parent) self []
natural = flowNatWidth (inline $ size parent) self []
large = flowMaxWidth parent self
-- | Compute natural block element height at cached width.
flowNatHeight :: Double -> PaddedBox Length Double -> [PaddedBox Double Double] -> Double
flowNatHeight _ PaddedBox {size = Size _ (Pixels y)} _ = y
flowNatHeight parent PaddedBox {size = Size _ (Percent y)} _ = y * parent
flowNatHeight _ PaddedBox {size = Size _ Min} childs =
sum $ map minHeight $ marginCollapse childs
flowNatHeight _ PaddedBox {size = Size owidth _} childs =
sum $ map height $ marginCollapse childs
-- | Compute minimum block height at cached width.
flowMinHeight :: Double -> PaddedBox Length Double -> Double
flowMinHeight _ PaddedBox {B.min = Size _ (Pixels y)} = y
flowMinHeight parent PaddedBox {B.min = Size _ (Percent y)} = y * parent
flowMinHeight parent self = flowNatHeight parent self []
-- | Compute maximum block height at cached width.
flowMaxHeight :: Double -> PaddedBox Length Double -> Double
flowMaxHeight _ PaddedBox {B.max = Size _ (Pixels y)} = y
flowMaxHeight parent PaddedBox {B.max = Size _ (Percent y)} = y * parent
flowMaxHeight parent PaddedBox {B.max = Size _ Auto} = parent
flowMaxHeight parent self@PaddedBox {B.max = Size _ Preferred} = flowNatHeight parent self []
flowMaxHeight parent self@PaddedBox {B.max = Size _ Min} = flowMinHeight parent self
-- | Compute final block height at cached width.
flowHeight :: PaddedBox Double Double -> PaddedBox Length Double -> Double
flowHeight parent self
| small > large = small
| natural > large = large
| natural >= small = natural
| otherwise = small
where
small = flowMinHeight (block $ B.min parent) self
natural = flowNatHeight (block $ B.nat parent) self []
large = flowMaxHeight (block $ B.max parent) self
-- | Compute position of all children relative to this block element.
positionFlow :: [PaddedBox Double Double] -> [Size Double Double]
positionFlow childs = scanl inner (Size 0 0) $ marginCollapse childs
where inner (Size x y) self = Size x $ height self
-- | Compute size given block element in given parent,
-- & position of given children.
layoutFlow :: PaddedBox Double Double -> PaddedBox Length Length ->
[PaddedBox Length Double] ->
(PaddedBox Double Double, [(Size Double Double, PaddedBox Double Double)])
layoutFlow parent self childs = (self', zip positions' childs')
where
positions' = positionFlow childs'
childs' = map layoutZooko childs
self' = self0 {
B.min = (B.min self0) { block = flowMinHeight (block $ B.min parent) self0 },
size = (size self0) { block = flowHeight parent self0 },
B.max = (B.max self0) { block = flowMaxHeight (block $ B.max parent) self0 },
padding = mapY (lowerLength owidth) $ padding self0,
border = mapY (lowerLength owidth) $ border self0,
margin = mapY (lowerLength owidth) $ margin self0
}
self0 = self1 {
size = (size self1) { block = Pixels $ flowNatHeight oheight self1 childs'}
}
self1 = self2 {
size = (size self2) { inline = width' },
B.max = (B.max self2) { inline = flowMaxWidth parent self2 },
B.min = (B.min self2) { inline = flowMinWidth owidth self2 [] },
padding = mapX (lowerLength owidth) $ padding self2,
border = mapX (lowerLength owidth) $ border self2,
margin = lowerMargin owidth (owidth - width') $ margin self2
}
width' = flowWidth parent self
self2 = self {
size = (size self) { inline = Pixels $ flowNatWidth owidth self childs },
B.min = (B.min self) { inline = Pixels $ flowMinWidth owidth self childs }
}
owidth = inline $ size parent
oheight = block $ size parent
layoutZooko child = child {
B.min = Size (inline $ B.min child) (flowMinHeight (block $ B.min self') child),
size = Size (inline $ size child) (flowHeight self' child),
B.max = Size (inline $ B.max child) (flowMaxHeight (block $ size self') child),
padding = mapY (lowerLength owidth) $ padding child,
border = mapY (lowerLength owidth) $ border child,
margin = mapY (lowerLength owidth) $ margin child
}
-- | Removes overlapping margins.
marginCollapse :: [PaddedBox Double n] -> [PaddedBox Double n]
marginCollapse (x'@PaddedBox {margin = xm@Border { bottom = x }}:
y'@PaddedBox {margin = ym@Border { top = y}}:rest)
| x > y = x':marginCollapse (y' {margin = ym { top = 0 }}:rest)
| otherwise = x' { margin = xm { bottom = 0 }}:marginCollapse (y':rest)
marginCollapse rest = rest
-- | Resolves auto paddings or margins to fill given width.
lowerMargin :: Double -> Double -> Border m Length -> Border m Double
lowerMargin _ available (Border top' bottom' Auto Auto) =
Border top' bottom' (available/2) (available/2)
lowerMargin outerwidth available (Border top' bottom' Auto right') =
Border top' bottom' available $ lowerLength outerwidth right'
lowerMargin outerwidth available (Border top' bottom' left' Auto) =
Border top' bottom' (lowerLength outerwidth left') available
lowerMargin outerwidth _ (Border top' bottom' left' right') =
Border top' bottom' (lowerLength outerwidth left') (lowerLength outerwidth right')