module Graphics.Layout.Flex(FlexParent(..), FlexChild(..),
Direction(..), FlexWrapping(..), Justification(..), Alignment(..),
flexMaxBasis, flexSumBasis, flexWrap) where
import Graphics.Layout.Box as B (Length, lowerLength, Size(..), PaddedBox(..),
maxWidth, width, minWidth, maxHeight, height, minHeight)
import Data.List (intersperse)
data FlexParent a b = FlexParent {
direction :: Direction,
reverseRows :: Bool,
wrap :: FlexWrapping,
justify :: Justification,
alignLines :: Maybe Justification, -- `Nothing` is "stretch"
baseGap :: b,
crossGap :: b,
children :: [[FlexChild a b]] -- 2D list to store lines once split.
}
data FlexChild a b = FlexChild {
grow :: Double,
shrink :: Double,
basis :: b,
alignment :: Alignment,
flexInner :: a
}
data Direction = Row | Column
data FlexWrapping = NoWrap | Wrap | WrapReverse
data Justification = JStart | JEnd | JCenter | JSpaceBetween | JSpaceAround | JSpaceEvenly
data Alignment = AlStretch | AlStart | AlEnd | AlCenter | AlBaseline
flexMaxBasis :: FlexParent a Length -> Double -> Double
flexMaxBasis self outersize = maximum [lowerLength outersize $ basis child |
row <- children self, child <- row]
flexSumBasis :: FlexParent a Length -> Double -> Double
flexSumBasis self size = maximum [Prelude.sum $ map (lowerLength size) $
intersperse (baseGap self) $ map basis row | row <- children self]
flexWrap :: FlexParent a Length -> Double -> FlexParent a Double
flexWrap self size
| NoWrap <- wrap self = post self'
| Wrap <- wrap self = post wrapped
| WrapReverse <- wrap self = post wrapped {children=reverse$children wrapped}
where
self' = FlexParent {
direction = direction self,
reverseRows = reverseRows self,
wrap = wrap self,
justify = justify self,
alignLines = alignLines self,
baseGap = lowerLength size $ baseGap self,
crossGap = lowerLength size $ crossGap self,
children = map (map $ child' size) $ children self
}
child' size x = FlexChild {
grow = grow x, shrink = shrink x,
basis = lowerLength size $ basis x,
alignment = alignment x, flexInner = flexInner x
}
wrapped = self' {
children = concatMap wrapRow $ children self'
}
wrapRow :: [FlexChild a Double] -> [[FlexChild a Double]]
wrapRow [] = []
wrapRow kids@(kid:_) = let (row, rest) = splitRow kids $ basis kid
in (row):wrapRow rest
splitRow (kid:kids) end
| end > size = ([], kid:kids)
| otherwise = let (kids', rest) = splitRow kids (end + baseGap self' + basis kid)
in (kid:kids', rest)
splitRow [] _ = ([], [])
post flex
| reverseRows self = post' flex { children = map reverse $ children flex }
| otherwise = post' flex
post' flex = flex { children = map resizeRow $ children flex }
resizeRow row
| rowSize > size = [kid { basis = basis kid - shrink kid * sfr } | kid <- row]
| rowSize < size = [kid { basis = basis kid + grow kid * gfr } | kid <- row]
| otherwise = row
where
rowSize = Prelude.sum $ intersperse (baseGap self') $ map basis row
sfr = (rowSize - size)/(Prelude.sum $ map shrink row)
gfr = (size - rowSize)/(Prelude.sum $ map grow row)
flexRowSize :: (a -> Double) -> [FlexChild a b] -> Double
flexRowSize cb row = maximum $ map (cb . flexInner) row
flexRowsSize :: (a -> Double) -> FlexParent a Double -> Double
flexRowsSize cb FlexParent { crossGap = gap, children = kids } =
sum $ intersperse gap $ flexRowSize cb `map` kids
justifyOffset, justifySpacing :: Double -> [Double] -> Double -> Justification -> Double
justifyOffset _ _ _ JStart = 0
justifyOffset outersize ks g JEnd = outersize - innersize g ks
justifyOffset outersize ks g JCenter = half $ outersize - innersize g ks
justifyOffset _ _ _ JSpaceBetween = 0
justifyOffset outersize ks g JSpaceAround =
half $ (outersize - innersize g ks)/length' ks
justifyOffset size ks g JSpaceEvenly = (size - innersize g ks)/(length' ks + 1)
justifySpacing size ks g JSpaceBetween = (size - innersize g ks)/(length' ks - 1)
justifySpacing size ks g JSpaceAround = (size - innersize g ks)/length' ks
justifySpacing size ks g JSpaceEvenly = (size - innersize g ks)/(length' ks + 1)
justifySpacing _ _ _ _ = 0
alignOffset :: Double -> Double -> Alignment -> Double
alignOffset _ _ AlStretch = 0 -- Needs special handling elsewhere
alignOffset _ _ AlStart = 0
alignOffset outer inner AlEnd = outer - inner
alignOffset outer inner AlCenter = half $ outer - inner
alignOffset outer inner AlBaseline = half $ outer - inner -- FIXME: Implement properly!
innersize gap = sum . intersperse gap
half = (/2)
length' :: [a] -> Double
length' = toEnum . length
------
--- Mapping Box Model axes <-> Flex Box axes
------
outerMinMain, outerMain, outerMaxMain :: Num m => PaddedBox m m -> Direction -> m
outerMinMain box Row = minWidth box
outerMinMain box Column = minHeight box
outerMain box Row = width box
outerMain box Column = height box
outerMaxMain box Row = maxWidth box
outerMaxMain box Column = maxHeight box
outerMinCross, outerCross, outerMaxCross :: Num m => PaddedBox m m -> Direction -> m
outerMinCross box Row = minHeight box
outerMinCross box Column = minWidth box
outerCross box Row = height box
outerCross box Column = width box
outerMaxCross box Row = maxHeight box
outerMaxCross box Column = maxWidth box
innerMinMain, innerMain, innerMaxMain :: Num m => PaddedBox m m -> Direction -> m
innerMinMain box = innerMain' $ B.min box
innerMain box = innerMain' $ B.size box
innerMaxMain box = innerMain' $ B.max box
innerMinCross, innerCross, innerMaxCross :: Num m => PaddedBox m m -> Direction -> m
innerMinCross box = innerCross' $ B.min box
innerCross box = innerCross' $ B.size box
innerMaxCross box = innerCross' $ B.max box
innerMain', innerCross' :: Num m => Size m m -> Direction -> m
innerMain' self Row = inline self
innerMain' self Column = block self
innerCross' self Row = block self
innerCross' self Column = inline self