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. } deriving (Eq, Show, Read) data FlexChild a b = FlexChild { grow :: Double, shrink :: Double, basis :: b, alignment :: Alignment, flexInner :: a } deriving (Eq, Show, Read) data Direction = Row | Column deriving (Eq, Show, Read) data FlexWrapping = NoWrap | Wrap | WrapReverse deriving (Eq, Show, Read) data Justification = JStart | JEnd | JCenter | JSpaceBetween | JSpaceAround | JSpaceEvenly deriving (Eq, Show, Read) data Alignment = AlStretch | AlStart | AlEnd | AlCenter | AlBaseline deriving (Eq, Show, Read) 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