{-# LANGUAGE RecordWildCards, OverloadedStrings #-}
module Graphics.Layout.Grid(Grid(..), GridItem(..), Alignment(..), Name,
buildGrid, setCellBox, enumerate,
gridEstWidth, gridNatWidths, gridMinWidths, gridMaxWidths, gridWidths,
gridNatHeights, gridMinHeights, gridMaxHeights, gridHeights,
gridPosition, gridLayout) 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