@@ 58,7 58,7 @@ data LayoutItem m n x =
| LayoutConst x (PaddedBox m n) [LayoutItem m n x]
-- | Children of a `LayoutInline` or `LayoutInline'`.
| LayoutSpan (FragmentTree (UserData m n x))
- | LayoutFlex x (PaddedBox m n) (FlexParent (LayoutItem m n x) m)
+ | LayoutFlex x (FlexParent (LayoutItem m n x) m)
deriving (Show, Eq)
-- | An empty box.
nullLayout :: (PropertyParser x, Zero m, Zero n) => LayoutItem m n x
@@ 88,6 88,10 @@ layoutGetBox (LayoutInline' _ self _) = zero {
}
layoutGetBox (LayoutSpan self) = treeBox self
layoutGetBox (LayoutConst _ ret _) = ret
+layoutGetBox (LayoutFlex _ self) = flexGetBox layoutGetBox' self
+layoutGetBox' :: (Zero m, Zero n, CastDouble m, CastDouble n) =>
+ LayoutItem m n x -> PaddedBox Double Double
+layoutGetBox' = mapX' toDouble . mapY' toDouble . layoutGetBox
-- | Retrieve the subtree under a node.
layoutGetChilds (LayoutFlow _ _ ret) = ret
layoutGetChilds (LayoutGrid _ _ _ ret) = ret
@@ 95,6 99,7 @@ layoutGetChilds (LayoutSpan _) = []
layoutGetChilds (LayoutInline _ self _) = map LayoutSpan $ inlineChildren self
layoutGetChilds (LayoutInline' _ self _) = map LayoutSpan $ layoutChildren self
layoutGetChilds (LayoutConst _ _ childs) = childs
+layoutGetChilds (LayoutFlex _ x) = map Fl.flexInner $ concat $ Fl.children x
-- | Retrieve the caller-specified data attached to a layout node.
layoutGetInner (LayoutFlow ret _ _) = ret
layoutGetInner (LayoutGrid ret _ _ _) = ret
@@ 102,6 107,7 @@ layoutGetInner (LayoutInline ret _ _) = ret
layoutGetInner (LayoutInline' ret _ _) = ret
layoutGetInner (LayoutConst ret _ _) = ret
layoutGetInner (LayoutSpan x) = treeInner x
+layoutGetInner (LayoutFlex ret _ ) = ret
-- | Retrieve the font associated with inline layout.
fragmentFont x = let (ret, _, _) = treeInner' x in ret
@@ 136,6 142,7 @@ boxMinWidth _ self@(LayoutInline' _ _ _) = self
boxMinWidth _ (LayoutConst val self' childs) =
LayoutConst val self' $ map (boxMinWidth Nothing) childs
boxMinWidth _ self@(LayoutSpan _) = self
+boxMinWidth size self@(LayoutFlex a b) = LayoutFlex a $ flexMap (boxMinWidth size) b
-- | Update a (sub)tree to compute & cache ideal width.
boxNatWidth :: (Zero y, CastDouble y, NFData y) =>
Maybe Double -> LayoutItem y Length x -> LayoutItem y Length x
@@ 163,6 170,7 @@ boxNatWidth _ self@(LayoutInline' _ _ _) = self
boxNatWidth _ (LayoutConst val self' childs) =
LayoutConst val self' $ map (boxNatWidth Nothing) childs
boxNatWidth _ self@(LayoutSpan _) = self
+boxNatWidth size (LayoutFlex a b ) = LayoutFlex a $ flexMap (boxNatWidth size) b
-- | Update a (sub)tree to compute & cache maximum legible width.
boxMaxWidth :: (CastDouble y, Zero y, NFData y) =>
PaddedBox a Double -> LayoutItem y Length x -> LayoutItem y Length x
@@ 183,6 191,9 @@ boxMaxWidth parent self@(LayoutInline' _ _ _) = self
boxMaxWidth _ (LayoutConst val self' childs) = LayoutConst val self' $
map (boxMaxWidth $ mapY' toDouble $ mapX' toDouble self') childs
boxMaxWidth parent self@(LayoutSpan _) = self
+boxMaxWidth parent (LayoutFlex a b) = LayoutFlex a $ (case Fl.direction b of
+ Fl.Row -> flip flexWrap (inline $ B.size parent)
+ Fl.Column -> id) $ flexMap (boxMaxWidth parent) b
-- | Update a (sub)tree to compute & cache final width.
boxWidth :: (Zero y, CastDouble y, NFData y) =>
PaddedBox b Double -> LayoutItem y Length x -> LayoutItem y Double x
@@ 218,6 229,7 @@ boxWidth p (LayoutConst val self childs) = LayoutConst val (mapX' cb self) $
where cb = lowerLength $ width p
boxWidth parent (LayoutSpan self') =
LayoutSpan $ treeMap (mapX' $ lowerLength $ width parent) self'
+boxWidth parent (LayoutFlex a b) = LayoutFlex a $ flexMap (boxWidth parent) b
-- | Update a (sub)tree to compute & cache ideal legible height.
boxNatHeight :: Double -> LayoutItem Length Double x -> LayoutItem Length Double x
@@ 239,6 251,7 @@ boxNatHeight parent self@(LayoutInline' _ _ _) = self
boxNatHeight p (LayoutConst val self' childs) = LayoutConst val self' $
map (boxNatHeight $ width $ mapY' (lowerLength p) self') childs
boxNatHeight parent self@(LayoutSpan _) = self
+boxNatHeight parent (LayoutFlex a b) = LayoutFlex a $ flexMap (boxNatHeight parent) b
-- | Update a (sub)tree to compute & cache minimum legible height.
boxMinHeight :: Double -> LayoutItem Length Double x -> LayoutItem Length Double x
boxMinHeight parent (LayoutFlow val self childs) = LayoutFlow val self' childs'
@@ 261,6 274,7 @@ boxMinHeight _ self@(LayoutInline' _ _ _) = self
boxMinHeight p (LayoutConst val self' childs) = LayoutConst val self' $
map (boxMinHeight $ width $ mapY' (lowerLength p) self') childs
boxMinHeight parent self@(LayoutSpan _) = self
+boxMinHeight parent self@(LayoutFlex a b) = LayoutFlex a $ flexMap (boxMinHeight parent) b
-- | Update a subtree to compute & cache maximum legible height.
boxMaxHeight :: PaddedBox Double Double -> LayoutItem Length Double x ->
LayoutItem Length Double x
@@ 284,6 298,7 @@ boxMaxHeight _ (LayoutInline' val self' paging) = LayoutInline' val self' paging
boxMaxHeight p (LayoutConst val self' childs) = LayoutConst val self' $
map (boxMaxHeight $ mapY' (lowerLength $ width p) self') childs
boxMaxHeight parent (LayoutSpan self') = LayoutSpan self'
+boxMaxHeight parent (LayoutFlex a b) = LayoutFlex a $ flexMap (boxMaxHeight parent) b
-- | Update a (sub)tree to compute & cache final height.
boxHeight :: PaddedBox Double Double -> LayoutItem Length Double x -> LayoutItem Double Double x
boxHeight parent (LayoutFlow val self childs) = LayoutFlow val self' childs'
@@ 319,6 334,8 @@ boxHeight p (LayoutConst val self childs) =
in LayoutConst val self' $ map (boxHeight self') childs
boxHeight p (LayoutSpan self') =
LayoutSpan $ treeMap (mapY' $ lowerLength $ width p) self'
+boxHeight p (LayoutFlex a b) = LayoutFlex a $
+ flexResolve (innerMain . layoutGetBox) (width p) $ flexMap (boxHeight p) b
-- | Split a (sub)tree to fit within max-height.
-- May take full page height into account.
@@ 359,6 376,12 @@ boxSplit maxheight pageheight (LayoutInline' a self paging) =
where
wrap self' = LayoutInline' a self' paging
boxSplit _ _ self@(LayoutSpan _) = (self, Nothing) -- Can't split!
+boxSplit maxheight pageheight (LayoutFlex a self) =
+ -- FIXME: What if any children are too big for the page?
+ let (p0, p1) = flexSplit ( B.size . layoutGetBox ) maxheight pageheight self
+ in if null $ Fl.children p1
+ then (LayoutFlex a p0, Nothing)
+ else (LayoutFlex a p0, Just $ LayoutFlex a p1)
-- | Generate a list of pages from a node, splitting subtrees where necessary.
boxPaginate pageheight node
| (page, Just overflow) <- boxSplit pageheight pageheight node =
@@ 368,6 391,19 @@ boxPaginate pageheight node
-- | Compute position of all nodes in the (sub)tree relative to a base coordinate.
boxPosition :: (PropertyParser x, Eq x) => (Double, Double) ->
LayoutItem Double Double x -> LayoutItem Double Double ((Double, Double), x)
+boxPosition (x,y) (LayoutFlow val box [LayoutFlex val' self]) =
+ LayoutFlow ((x,y), val) box [ -- Obtaining size from parent
+ LayoutFlex (pos', val') $
+ flexPosition boxPosition boxSize pos' (B.size box) self
+ ]
+ where
+ boxSize box' = let b = layoutGetBox box' in B.width b `Size` B.height b
+ pos' = (x + B.leftSpace box, y + B.rightSpace box)
+boxPosition pos self@(LayoutFlex val self') =
+ LayoutFlex (pos, val) $ flexPosition boxPosition boxSize pos size self'
+ where
+ boxSize box' = let b = layoutGetBox box' in B.width b `Size` B.height b
+ size = B.size $ layoutGetBox self
boxPosition pos@(x, y) (LayoutFlow val self childs) = LayoutFlow (pos, val) self childs'
where
childs' = parMap' recurse $ zip pos' childs
@@ 1,10 1,10 @@
-module Graphics.Layout.Flex(FlexParent(..), FlexChild(..),
- Direction(..), FlexWrapping(..), Justification(..), Alignment(..),
- flexMaxBasis, flexSumBasis, flexWrap) where
+module Graphics.Layout.Flex where
-import Graphics.Layout.Box as B (Length, lowerLength, Size(..), PaddedBox(..),
- maxWidth, width, minWidth, maxHeight, height, minHeight)
+import Graphics.Layout.Box as B (Length(..), lowerLength, Size(..), PaddedBox(..),
+ maxWidth, width, minWidth, maxHeight, height, minHeight, CastDouble(..), Zero(..))
import Data.List (intersperse)
+import GHC.Real (infinity)
+import Data.Maybe (fromMaybe)
data FlexParent a b = FlexParent {
direction :: Direction,
@@ 14,7 14,8 @@ data FlexParent a b = FlexParent {
alignLines :: Maybe Justification, -- `Nothing` is "stretch"
baseGap :: b,
crossGap :: b,
- children :: [[FlexChild a b]] -- 2D list to store lines once split.
+ children :: [[FlexChild a b]], -- 2D list to store lines once split.
+ pageWidth :: Double -- Pagination argument
} deriving (Eq, Show, Read)
data FlexChild a b = FlexChild {
grow :: Double,
@@ 31,59 32,81 @@ data Justification = JStart | JEnd | JCenter | JSpaceBetween | JSpaceAround | JS
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) $
+flexMap :: (a -> b) -> FlexParent a c -> FlexParent b c
+flexMap cb self = FlexParent {
+ direction = direction self, reverseRows = reverseRows self, wrap = wrap self,
+ justify = justify self, alignLines = alignLines self,
+ baseGap = baseGap self, crossGap = crossGap self, pageWidth = pageWidth self,
+ children = [[FlexChild {
+ grow = grow kid, shrink = shrink kid, basis = basis kid,
+ alignment = alignment kid,
+ flexInner = cb $ flexInner kid -- The important line!
+ } | kid <- row] | row <- children self]
+ }
+flexResolve :: CastDouble b => (a -> Direction -> Double) -> Double ->
+ FlexParent a b -> FlexParent a Double
+flexResolve cb size self = FlexParent {
+ direction = direction self, reverseRows = reverseRows self, wrap = wrap self,
+ justify = justify self, alignLines = alignLines self,
+ baseGap = toDoubleWithin size $ baseGap self,
+ crossGap = toDoubleWithin size $ crossGap self,
+ pageWidth = pageWidth self,
+ children = [[FlexChild {
+ grow = grow kid, shrink = shrink kid,
+ basis = toDoubleWithinAuto (flexInner kid `cb` direction self) size $ basis kid,
+ alignment = alignment kid, flexInner = flexInner kid
+ } | kid <- row] | row <- children self]
+ }
+
+flexMaxBasis :: FlexParent a Double -> Double
+flexMaxBasis self = maximum [basis child | row <- children self, child <- row]
+flexSumBasis :: FlexParent a Double -> Double
+flexSumBasis self = maximum [Prelude.sum $
intersperse (baseGap self) $ map basis row | row <- children self]
-flexWrap :: FlexParent a Length -> Double -> FlexParent a Double
+flexWrap :: CastDouble b => FlexParent a b -> Double -> FlexParent a b
flexWrap self size
- | NoWrap <- wrap self = post self'
- | Wrap <- wrap self = post wrapped
- | WrapReverse <- wrap self = post wrapped {children=reverse$children wrapped}
+ | NoWrap <- wrap self = post self
+ | Wrap <- wrap self = post self'
+ | WrapReverse <- wrap self = post self' { children=reverse $ children self' }
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'
+ self' = self {
+ children = concatMap wrapRow $ children self
}
- wrapRow :: [FlexChild a Double] -> [[FlexChild a Double]]
+ wrapRow :: CastDouble b => [FlexChild a b] -> [[FlexChild a b]]
wrapRow [] = []
- wrapRow kids@(kid:_) = let (row, rest) = splitRow kids $ basis kid
- in (row):wrapRow rest
+ wrapRow kids@(kid:_) = let (row, rest) = splitRow kids $ basis' kid
+ in row:wrapRow rest
+ splitRow :: CastDouble b => [FlexChild a b] -> Double -> ([FlexChild a b], [FlexChild a b])
splitRow (kid:kids) end
| end > size = ([], kid:kids)
- | otherwise = let (kids', rest) = splitRow kids (end + baseGap self' + basis kid)
+ | otherwise = let (kids', rest) = splitRow kids (end + baseGap' self + basis' kid)
in (kid:kids', rest)
splitRow [] _ = ([], [])
+ post :: CastDouble b => FlexParent a b -> FlexParent a b
post flex
| reverseRows self = post' flex { children = map reverse $ children flex }
| otherwise = post' flex
+ post' :: CastDouble b => FlexParent a b -> FlexParent a b
post' flex = flex { children = map resizeRow $ children flex }
+ resizeRow :: CastDouble b => [FlexChild a b] -> [FlexChild a b]
resizeRow row
- | rowSize > size = [kid { basis = basis kid - shrink kid * sfr } | kid <- row]
- | rowSize < size = [kid { basis = basis kid + grow kid * gfr } | kid <- row]
+ | rowSize > size = [kid {
+ basis = fromDouble $ basis' kid - shrink kid * sfr
+ } | kid <- row]
+ | rowSize < size = [kid {
+ basis = fromDouble $ basis' kid + grow kid * gfr
+ } | kid <- row]
| otherwise = row
where
- rowSize = Prelude.sum $ intersperse (baseGap self') $ map basis row
+ 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)
+ baseGap' :: CastDouble b => FlexParent a b -> Double
+ baseGap' = toDouble . baseGap
+ basis' :: CastDouble b => FlexChild a b -> Double
+ basis' = toDouble . basis
flexRowSize :: (a -> Double) -> [FlexChild a b] -> Double
flexRowSize cb row = maximum $ map (cb . flexInner) row
@@ 98,12 121,22 @@ 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 _ ks _ _ | length ks <= 1 = 0 -- No gaps to space, avoid numeric errors.
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
+flexJustify :: (a -> Double) -> Double -> [a] -> Double -> Justification -> [(Double, a)]
+flexJustify cb size kids gap just = inner kids offs
+ where
+ offs = justifyOffset size kids' gap just
+ spacing = justifySpacing size kids' gap just
+ kids' = map cb kids
+ inner (k:ks) start = (start, k):inner ks (start + cb k + gap)
+ inner [] _ = []
+
alignOffset :: Double -> Double -> Alignment -> Double
alignOffset _ _ AlStretch = 0 -- Needs special handling elsewhere
alignOffset _ _ AlStart = 0
@@ 137,17 170,75 @@ 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
+innerMinMain box = sizeMain $ B.min box
+innerMain box = sizeMain $ B.size box
+innerMaxMain box = sizeMain $ 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
+innerMinCross box = sizeCross $ B.min box
+innerCross box = sizeCross $ B.size box
+innerMaxCross box = sizeCross $ B.max box
+
+sizeMain, sizeCross :: Num m => Size m m -> Direction -> m
+sizeMain self Row = inline self
+sizeMain self Column = block self
+sizeCross self Row = block self
+sizeCross self Column = inline self
+
+flexGetBox :: (Zero m, CastDouble m, Zero n, CastDouble n) =>
+ (a -> PaddedBox Double Double) -> FlexParent a m -> PaddedBox m n
+flexGetBox cb self = zero {
+ B.min = flexMaxBasis self' `size` flexRowsSize (cb' innerMinCross) self',
+ B.max = fromRational infinity `size` fromRational infinity,
+ B.nat = flexSumBasis self' `size` flexRowsSize (cb' innerCross) self',
+ B.size = flexSumBasis self' `size` flexRowsSize (cb' innerCross) self'
+ } where
+ size main cross
+ | Row <- direction self = fromDouble main `Size` fromDouble cross
+ | otherwise = fromDouble cross `Size` fromDouble main
+ cb' cb_ = flip cb_ (direction self) . cb
+ self' = flexResolve (innerMain . cb) 0 self
+
+flexSplit :: (a -> Size Double Double) -> Double -> Double -> FlexParent a Double ->
+ (FlexParent a Double, FlexParent a Double)
+flexSplit cb h _ self@FlexParent { direction = Row, pageWidth = w } =
+ (self' { children = page0 }, self' { children = page1 })
+ where
+ self' = flexWrap self w
+ (page0, page1) = splitRows (-crossGap self) $ children self
+ splitRows start (row:rows)
+ | start > h = ([row], rows)
+ | otherwise =
+ let (rows', rest) = flip splitRows rows $
+ start + crossGap self + flexRowSize (inline . cb) row
+ in (row:rows', rest)
+ splitRows _ [] = ([], [])
+flexSplit cb h h' self@FlexParent { direction = Column, pageWidth = w }
+ | measure h = (flexWrap self h, self { children = [] })
+ -- If it fits on neither page... Row-direction is more versatile!
+ | not $ measure h' = flexSplit cb h h' self { direction = Row }
+ | otherwise = (self { children = [] }, flexWrap self h')
+ where
+ measure space = (block . cb) `flexRowsSize` flexWrap self space <= w
+
+flexPosition :: ((Double, Double) -> a -> b) -> (a -> Size Double Double) ->
+ (Double, Double) -> Size Double Double ->
+ FlexParent a Double -> FlexParent b Double
+flexPosition cb cb' (x,y) size self@FlexParent { direction = dir } = self {
+ children = map rowPosition $ flexJustify rowsize (sizeCross size dir)
+ (children self) (crossGap self) (justify self)
+ } where
+ rowsize = flexRowSize $ flip sizeCross dir . cb'
+ -- TODO: Handle stretch properly
+ rowPosition (rpos, row) =
+ let rsize = flexRowSize (flip sizeCross dir . cb') row
+ in map (alignChild rsize rpos) $ flexJustify basis rsize row
+ (baseGap self) (fromMaybe JSpaceAround $ alignLines self)
+ alignChild rsize rpos (kpos, kid@FlexChild {
+ flexInner = kid', alignment = align'
+ }) = kid {
+ flexInner = flip cb kid' $ sz kpos $
+ rpos + alignOffset rsize (flip sizeCross dir $ cb' kid') align'
+ }
+ sz m c | Row <- direction self = (x + m, y + c)
+ | otherwise = (x + c, y + m)