{-# LANGUAGE RecordWildCards, OverloadedStrings #-} module Graphics.Layout.Grid where import Data.Either (fromRight) import Data.Text (Text) import Data.List (intersperse) import Graphics.Layout.Box as B import Debug.Trace (trace) -- TODO implement subgrid support... data Grid m n = Grid { rows :: [Either m Double], rowBounds :: [(Double, Double)], subgridRows :: Int, columns :: [Either n Double], colBounds :: [(Double, Double)], subgridColumns :: Int, gap :: Size m n, 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, alignment :: Size Alignment Alignment, gridItemBox :: PaddedBox m n } data Alignment = Start | Mid | End type Name = Text buildGrid rows columns = Grid { rows = rows, rowBounds = [], subgridRows = 0, -- disables columns = columns, colBounds = [], subgridColumns = 0, -- disables gap = Size (Pixels 0) (Pixels 0), 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 = [cell | cell <- cells, startCol cell == ix, startCol cell == pred (endCol cell)] cellsForRow :: [GridItem y x] -> Int -> [GridItem y x] cellsForRow cells ix = [cell | cell <- cells, startRow cell == ix, startRow cell == pred (endRow cell)] verifyGrid self childs = and [ startRow < width && startRow >= 0 && endRow < width && endRow >= 0 && endCol > startCol && endRow > startRow && startCol < height && startCol >= 0 && endCol < height && endCol >= 0 | GridItem {..} <- childs] where width = length $ columns self height = length $ rows self gridEstWidth :: Grid b Length -> [GridItem y Double] -> Double gridEstWidth self childs = fst $ gridMaxWidths zeroBox self $ zip mins nats where mins = snd $ gridMinWidths 0 self childs nats = snd $ gridNatWidths 0 self childs gridMinWidths :: Double -> Grid b Length -> [GridItem y Double] -> (Double, [Double]) gridMinWidths parent self childs = (sum $ intersperse (lowerLength parent $ inline $ gap self) ret, ret) where ret = map colMinWidth $ enumerate $ columns self colMinWidth (_, Left (Pixels x)) = x colMinWidth (_, Left (Percent x)) = x * parent colMinWidth arg@(ix, Left Preferred) = maximum $ (0:) $ map (inline . size . gridItemBox) $ cellsForCol childs ix colMinWidth (ix, _) = maximum $ (0:) $ map (inline . B.min . gridItemBox) $ cellsForCol childs ix gridNatWidths :: Double -> Grid b Length -> [GridItem y Double] -> (Double, [Double]) gridNatWidths parent self childs = (sum $ intersperse (lowerLength parent $ inline $ gap self) ret, ret) where ret = map colNatWidth $ enumerate $ columns self colNatWidth (_, Left (Pixels x)) = x colNatWidth (_, Left (Percent x)) = x * parent colNatWidth arg@(ix, Left Min) = maximum $ (0:) $ map (inline . B.min . gridItemBox) $ cellsForCol childs ix colNatWidth (ix, _) = maximum $ (0:) $ map (inline . size . gridItemBox) $ cellsForCol childs ix gridMaxWidths :: PaddedBox b Double -> Grid y Length -> [(Double, Double)] -> (Double, [Double]) gridMaxWidths parent self subwidths = (sum $ intersperse (lowerLength outerwidth $ inline $ gap self) ret, ret) where ret = map (colMaxWidth fr) $ zip subwidths $ columns self fr = Prelude.max 0 fr' fr' = (outerwidth - estimate)/(countFRs $ columns self) outerwidth = inline $ size parent estimate = sum $ intersperse (lowerLength outerwidth $ inline $ gap self) $ map (colMaxWidth 0) $ zip subwidths $ columns self colMaxWidth _ (_, Left (Pixels x)) = x colMaxWidth _ (_, Left (Percent x)) = x*(inline $ size parent) colMaxWidth _ ((_, nat), Left Preferred) = nat colMaxWidth _ ((min, _), Left Min) = min colMaxWidth fr (_, Left Auto) = fr colMaxWidth fr (_, Right x) = x*fr gridWidths :: PaddedBox b Double -> Grid y Length -> [(Double, Double)] -> (Double, [Double]) gridWidths parent self subwidths = (sum $ intersperse (lowerLength outerwidth $ inline $ gap self) ret, ret) where ret = map (colWidth fr) $ zip subwidths $ columns self fr = (outerwidth - estimate)/(countFRs $ columns self) outerwidth = inline $ size parent estimate = sum $ intersperse (lowerLength outerwidth $ inline $ gap self) $ map (colWidth 0) $ zip subwidths $ columns self colWidth fr ((min, nat), size) = Prelude.max min $ colWidth' fr ((min, nat), size) colWidth' _ (_, Left (Pixels x)) = x colWidth' _ (_, Left (Percent x)) = x*(inline $ size parent) colWidth' _ ((_, nat), Left Preferred) = nat colWidth' _ ((min, _), Left Min) = min colWidth' fr ((_, nat), Left Auto) = Prelude.min nat fr colWidth' fr (_, Right x) = x*fr gridNatHeights :: Double -> Grid Length Double -> [GridItem Double Double] -> (Double, [Double]) gridNatHeights parent self childs = (sum $ intersperse (lowerLength parent $ block $ gap self) ret, ret) where ret = map rowNatHeight $ enumerate $ rows self rowNatHeight (_, Left (Pixels x)) = x rowNatHeight (_, Left (Percent x)) = x * parent rowNatHeight arg@(ix, Left Min) = maximum $ (0:) $ map (block . B.min . gridItemBox) $ cellsForCol childs ix rowNatHeight (ix, _) = maximum $ (0:) $ map (block . size . gridItemBox) $ cellsForCol childs ix gridMinHeights :: Double -> Grid Length Double -> [GridItem Double Double] -> (Double, [Double]) gridMinHeights parent self childs = (sum $ intersperse (lowerLength parent $ block $ gap self) ret, ret) where ret = map rowMinHeight $ enumerate $ rows self rowMinHeight (_, Left (Pixels x)) = x rowMinHeight (_, Left (Percent x)) = x * parent rowMinHeight arg@(ix, Left Preferred) = maximum $ (0:) $ map (block . size . gridItemBox) $ cellsForCol childs ix rowMinHeight (ix, _) = maximum $ (0:) $ map (block . B.min . gridItemBox) $ cellsForCol childs ix gridMaxHeights :: PaddedBox Double Double -> Grid Length Double -> [(Double, Double)] -> (Double, [Double]) gridMaxHeights parent self subheights = (sum $ intersperse (inline $ gap self) ret, ret) where ret = map (colMaxHeight fr) $ zip subheights $ rows self fr = (outerheight - estimate)/(countFRs $ rows self) outerwidth = inline $ size parent outerheight = block $ size parent estimate = sum $ intersperse (inline $ gap self) $ map (colMaxHeight 0) $ zip subheights $ rows self colMaxHeight _ (_, Left (Pixels x)) = x colMaxHeight _ (_, Left (Percent x)) = x*outerwidth colMaxHeight _ ((_, nat), Left Preferred) = nat colMaxHeight _ ((min, _), Left Min) = min colMaxHeight fr (_, Left Auto) = fr colMaxHeight fr (_, Right x) = x*fr gridHeights :: PaddedBox Double Double -> Grid Length Double -> [(Double, Double)] -> (Double, [Double]) gridHeights parent self subheights = (sum $ intersperse (inline $ gap self) ret, ret) where ret = map (colHeight fr) $ zip subheights $ rows self fr = (outerheight - estimate)/(countFRs $ rows self) outerwidth = inline $ size parent outerheight = block $ size parent estimate = sum $ intersperse (inline $ gap self) $ map (colHeight 0) $ zip subheights $ rows self colHeight fr ((min, nat), size) = Prelude.max min $ colHeight' fr ((min, nat), size) colHeight' _ (_, Left (Pixels x)) = x colHeight' _ (_, Left (Percent x)) = x*outerwidth colHeight' _ ((_, nat), Left Preferred) = nat colHeight' _ ((min, _), Left Min) = min colHeight' fr ((min, nat), Left Auto) = Prelude.min fr nat colHeight' fr (_, Right x) = x*fr gridPosition :: Grid Double Double -> [GridItem Double Double] -> [Size Double Double] gridPosition self childs = map gridCellPosition childs where gridCellPosition child = Size (x + align extraWidth alignX) (y + align extraHeight alignY) where Size x y = gridCellPosition' child Size alignX alignY = alignment child width = track (endCol child) (columns self) - track (startCol child) (columns self) height = track (endRow child) (rows self) - track (startRow child) (columns self) extraWidth = width - inline (size $ gridItemBox child) extraHeight = height - block (size $ gridItemBox child) gridCellPosition' child = Size (startCol child `track` columns self) (startRow child `track` rows self) track ix (size:sizes) = fromRight 0 size + track (pred ix) sizes track 0 _ = 0 track ix [] = trace "WARNING! Malformed input table!" 0 align _ Start = 0 align excess Mid = excess/2 align excess End = excess gridLayout :: PaddedBox Double Double -> Grid Length Length -> [GridItem Double Double] -> Bool -> (Grid Double Double, [(Size Double Double, GridItem Double Double)]) gridLayout parent self childs paginate = (self', zip positions childs) where positions = gridPosition self' childs self' = self { rows = map Left rows', rowBounds = rowBounds', columns = map Left cols', colBounds = colBounds', gap = Size (lowerLength width' gapX) (lowerLength width' gapY), 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 width' self0 childs (_, rowNats) = gridNatHeights width' self0 childs self0 = self { columns = map Left cols', colBounds = colBounds', gap = Size (lowerLength width' gapX) gapY, 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 (_, colMins) = gridMinWidths estWidth self childs (_, colNats) = gridNatWidths estWidth self childs estWidth = gridEstWidth self childs enumerate = zip [0..] countFRs (Left Auto:rest) = succ $ countFRs rest countFRs (Right x:rest) = x + countFRs rest countFRs (_:rest) = countFRs rest countFRs [] = 0