From 972a1386b41bbece7d7b1493246a0266e9ef8088 Mon Sep 17 00:00:00 2001 From: Adrian Cochrane Date: Tue, 21 Nov 2023 12:43:15 +1300 Subject: [PATCH] Test flexbox pagination! --- Graphics/Layout/Flex.hs | 20 ++- test/Test.hs | 365 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 378 insertions(+), 7 deletions(-) diff --git a/Graphics/Layout/Flex.hs b/Graphics/Layout/Flex.hs index 8f205bb..9fbf406 100644 --- a/Graphics/Layout/Flex.hs +++ b/Graphics/Layout/Flex.hs @@ -64,6 +64,7 @@ flexSumBasis :: FlexParent a Double -> Double flexSumBasis self = maximum [Prelude.sum $ intersperse (baseGap self) $ map basis row | row <- children self] +-- NOTE: shrink propery may yield negative sizes. Caller will want to enforce min-sizes. flexWrap :: CastDouble b => FlexParent a b -> Double -> FlexParent a b flexWrap self size | NoWrap <- wrap self = post self @@ -75,9 +76,15 @@ flexWrap self size } wrapRow :: CastDouble b => [FlexChild a b] -> [[FlexChild a b]] wrapRow [] = [] - wrapRow kids@(kid:_) = let (row, rest) = splitRow kids $ basis' kid + 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, splitRow' :: CastDouble b => [FlexChild a b] -> Double -> + ([FlexChild a b], [FlexChild a b]) + -- This wrapper function ensures we don't end up with empty rows, or infinite loops. + splitRow' (kid:kids) end = + let (kids', rest) = splitRow kids (end + baseGap' self + basis' kid) + in (kid:kids', rest) + splitRow' [] _ = ([], []) splitRow (kid:kids) end | end > size = ([], kid:kids) | otherwise = let (kids', rest) = splitRow kids (end + baseGap' self + basis' kid) @@ -93,16 +100,19 @@ flexWrap self size resizeRow :: CastDouble b => [FlexChild a b] -> [FlexChild a b] resizeRow row | rowSize > size = [kid { - basis = fromDouble $ basis' kid - shrink kid * sfr + basis = fromDouble $ basis' kid - shrink kid * nanguard sfr } | kid <- row] | rowSize < size = [kid { - basis = fromDouble $ basis' kid + grow kid * gfr + basis = fromDouble $ basis' kid + grow kid * nanguard 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) + nanguard x | isNaN x = 0 + | isInfinite x = 0 + | otherwise = x baseGap' :: CastDouble b => FlexParent a b -> Double baseGap' = toDouble . baseGap basis' :: CastDouble b => FlexChild a b -> Double @@ -207,7 +217,7 @@ flexSplit cb h _ self@FlexParent { direction = Row, pageWidth = w } = self' = flexWrap self w (page0, page1) = splitRows (-crossGap self) $ children self splitRows start (row:rows) - | start > h = ([row], rows) + | start >= h = ([], row:rows) | otherwise = let (rows', rest) = flip splitRows rows $ start + crossGap self + flexRowSize (inline . cb) row diff --git a/test/Test.hs b/test/Test.hs index bd49f35..0ba9120 100644 --- a/test/Test.hs +++ b/test/Test.hs @@ -10,7 +10,7 @@ import Stylist.Tree (StyleTree(..)) import Data.Maybe (fromJust) import Graphics.Layout.Box as B -import Graphics.Layout.Grid +import Graphics.Layout.Grid as Grid import Graphics.Layout.Flow import Graphics.Layout @@ -18,6 +18,7 @@ import Graphics.Layout.CSS import Graphics.Layout.Grid.Table import Graphics.Layout.Inline.CSS import Graphics.Layout.CSS.Font (placeholderFont) +import Graphics.Layout.Flex as Flex import Data.Text.ParagraphLayout.Rich (constructParagraph, defaultParagraphOptions, defaultTextOptions, @@ -360,7 +361,7 @@ spec = do let defaultPageOptions = PageOptions 0 0 2 2 let gridItem x y = GridItem { cellStart = x, cellEnd = y, - alignment = Start, + Grid.alignment = Start, minSize = 0, natSize = 0 } let track cells' = Track { @@ -410,6 +411,366 @@ spec = do LayoutFlow () lengthBox [inline "H"] ] ] + describe "Flexbox" $ do + it "wraps properly" $ do + let child l = FlexChild { + grow = 0, + shrink = 0, + basis = l, + Flex.alignment = AlStart, + flexInner = () + } + let baseFlex = FlexParent { + direction = Row, + reverseRows = False, + wrap = NoWrap, + justify = JStart, + alignLines = Just JStart, + baseGap = 2, + crossGap = 2, + Flex.children = [[(child 10) { grow = 1 }, (child 20) { shrink = 1 }, + (child 30) { grow = 2 }, (child 40) { shrink = 2 }]], + pageWidth = 0 + } :: FlexParent () Double + -- These test results don't look right... + flexWrap baseFlex 50 `shouldBe` baseFlex { + Flex.children = [[(child 10) { grow = 1 }, + (child 1.3333333333333321) { shrink = 1 }, (child 30) { grow = 2 }, + (child 2.6666666666666643) { shrink = 2}]] + } + flexWrap baseFlex 40 `shouldBe` baseFlex { + Flex.children = [[(child 10) { grow = 1 }, + (child $ -2) { shrink = 1 }, (child 30) { grow = 2 }, + (child $ -4) { shrink = 2}]] + } + flexWrap baseFlex 30 `shouldBe` baseFlex { + Flex.children = [[(child 10) { grow = 1 }, + (child $ -5.333333333333332) { shrink = 1 }, (child 30) { grow = 2 }, + (child $ -10.666666666666664) { shrink = 2}]] + } + flexWrap baseFlex 20 `shouldBe` baseFlex { + Flex.children = [[(child 10) { grow = 1 }, + (child $ -8.666666666666668) { shrink = 1 }, (child 30) { grow = 2 }, + (child $ -17.333333333333336) { shrink = 2}]] + } + flexWrap baseFlex 10 `shouldBe` baseFlex { + Flex.children = [[(child 10) { grow = 1 }, + (child $ -12) { shrink = 1 }, (child 30) { grow = 2 }, + (child $ -24) { shrink = 2}]] + } + flexWrap baseFlex { direction = Flex.Column } 50 `shouldBe` baseFlex { + direction = Flex.Column, + Flex.children = [[(child 10) { grow = 1 }, + (child 1.3333333333333321) { shrink = 1 }, (child 30) { grow = 2 }, + (child 2.6666666666666643) { shrink = 2}]] + } + flexWrap baseFlex { direction = Flex.Column } 40 `shouldBe` baseFlex { + direction = Flex.Column, + Flex.children = [[(child 10) { grow = 1 }, + (child $ -2) { shrink = 1 }, (child 30) { grow = 2 }, + (child $ -4) { shrink = 2}]] + } + flexWrap baseFlex { direction = Flex.Column } 30 `shouldBe` baseFlex { + direction = Flex.Column, + Flex.children = [[(child 10) { grow = 1 }, + (child $ -5.333333333333332) { shrink = 1 }, (child 30) { grow = 2 }, + (child $ -10.666666666666664) { shrink = 2}]] + } + flexWrap baseFlex { direction = Flex.Column } 20 `shouldBe` baseFlex { + direction = Flex.Column, + Flex.children = [[(child 10) { grow = 1 }, + (child $ -8.666666666666668) { shrink = 1 }, (child 30) { grow = 2 }, + (child $ -17.333333333333336) { shrink = 2}]] + } + flexWrap baseFlex { direction = Flex.Column } 10 `shouldBe` baseFlex { + direction = Flex.Column, + Flex.children = [[(child 10) { grow = 1 }, + (child $ -12) { shrink = 1 }, (child 30) { grow = 2 }, + (child $ -24) { shrink = 2}]] + } + flexWrap baseFlex { reverseRows = True } 50 `shouldBe` baseFlex { + reverseRows = True, + Flex.children = [[ + FlexChild { + grow = 0.0, + shrink = 2.0, + basis = 2.6666666666666643, + Flex.alignment = AlStart, + flexInner = () + }, + FlexChild { + grow = 2.0, + shrink = 0.0, + basis = 30.0, + Flex.alignment = AlStart, + flexInner = () + }, + FlexChild { + grow = 0.0, + shrink = 1.0, + basis = 1.3333333333333321, + Flex.alignment = AlStart, + flexInner = () + }, + FlexChild { + grow = 1.0, + shrink = 0.0, + basis = 10.0, + Flex.alignment = AlStart, + flexInner = () + } + ]] + } + flexWrap baseFlex { reverseRows = True } 40 `shouldBe` baseFlex { + reverseRows = True, + Flex.children = [[ + FlexChild { + grow = 0.0, + shrink = 2.0, + basis = -4, + Flex.alignment = AlStart, + flexInner = () + }, + FlexChild { + grow = 2.0, + shrink = 0.0, + basis = 30.0, + Flex.alignment = AlStart, + flexInner = () + }, + FlexChild { + grow = 0.0, + shrink = 1.0, + basis = -2, + Flex.alignment = AlStart, + flexInner = () + }, + FlexChild { + grow = 1.0, + shrink = 0.0, + basis = 10.0, + Flex.alignment = AlStart, + flexInner = () + } + ]] + } + flexWrap baseFlex { reverseRows = True } 30 `shouldBe` baseFlex { + reverseRows = True, + Flex.children = [[ + FlexChild { + grow = 0.0, + shrink = 2.0, + basis = -10.666666666666664, + Flex.alignment = AlStart, + flexInner = () + }, + FlexChild { + grow = 2.0, + shrink = 0.0, + basis = 30.0, + Flex.alignment = AlStart, + flexInner = () + }, + FlexChild { + grow = 0.0, + shrink = 1.0, + basis = -5.333333333333332, + Flex.alignment = AlStart, + flexInner = () + }, + FlexChild { + grow = 1.0, + shrink = 0.0, + basis = 10.0, + Flex.alignment = AlStart, + flexInner = () + } + ]] + } + flexWrap baseFlex { reverseRows = True } 20 `shouldBe` baseFlex { + reverseRows = True, + Flex.children = [[ + FlexChild { + grow = 0.0, + shrink = 2.0, + basis = -17.333333333333336, + Flex.alignment = AlStart, + flexInner = () + }, + FlexChild { + grow = 2.0, + shrink = 0.0, + basis = 30.0, + Flex.alignment = AlStart, + flexInner = () + }, + FlexChild { + grow = 0.0, + shrink = 1.0, + basis = -8.666666666666668, + Flex.alignment = AlStart, + flexInner = () + }, + FlexChild { + grow = 1.0, + shrink = 0.0, + basis = 10.0, + Flex.alignment = AlStart, + flexInner = () + } + ]] + } + flexWrap baseFlex { reverseRows = True } 10 `shouldBe` baseFlex { + reverseRows = True, + Flex.children = [[ + FlexChild { + grow = 0.0, + shrink = 2.0, + basis = -24, + Flex.alignment = AlStart, + flexInner = () + }, + FlexChild { + grow = 2.0, + shrink = 0.0, + basis = 30.0, + Flex.alignment = AlStart, + flexInner = () + }, + FlexChild { + grow = 0.0, + shrink = 1.0, + basis = -12, + Flex.alignment = AlStart, + flexInner = () + }, + FlexChild { + grow = 1.0, + shrink = 0.0, + basis = 10.0, + Flex.alignment = AlStart, + flexInner = () + } + ]] + } + flexWrap baseFlex { wrap = Wrap } 50 `shouldBe` baseFlex { + wrap = Wrap, + Flex.children = [[(child 10) { grow = 1 }, + (child 6) { shrink = 1 }, (child 30) { grow = 2 }], [ + (child 40) { shrink = 2}]] + } + flexWrap baseFlex { wrap = Wrap } 40 `shouldBe` baseFlex { + wrap = Wrap, + Flex.children = [[(child 18) { grow = 1 }, + (child 20) { shrink = 1 }], [(child 40) { grow = 2 }], + [(child 40) { shrink = 2}]] + } + flexWrap baseFlex { wrap = Wrap } 30 `shouldBe` baseFlex { + wrap = Wrap, + Flex.children = [[(child 10) { grow = 1 }, + (child 18) { shrink = 1 }], [(child 30) { grow = 2 }], + [(child 30) { shrink = 2}]] + } + flexWrap baseFlex { wrap = Wrap } 20 `shouldBe` baseFlex { + wrap = Wrap, + Flex.children = [[(child 20) { grow = 1 }], + [(child 20) { shrink = 1 }], [(child 30) { grow = 2 }], + [(child 20) { shrink = 2}]] + } + flexWrap baseFlex { wrap = Wrap } 10 `shouldBe` baseFlex { + wrap = Wrap, + Flex.children = [[(child 10) { grow = 1 }], [(child 10) { shrink = 1 }], + [(child 30) { grow = 2 }], [(child 10) { shrink = 2}]] + } + flexWrap baseFlex { wrap = WrapReverse } 50 `shouldBe` baseFlex { + wrap = WrapReverse, + Flex.children = [[(child 40) { shrink = 2}], [(child 10) { grow = 1 }, + (child 6) { shrink = 1 }, (child 30) { grow = 2 }]] + } + flexWrap baseFlex { wrap = WrapReverse } 40 `shouldBe` baseFlex { + wrap = WrapReverse, + Flex.children = [[(child 40) { shrink = 2}], [(child 40) { grow = 2 }], + [(child 18) { grow = 1 }, (child 20) { shrink = 1 }]] + } + flexWrap baseFlex { wrap = WrapReverse } 30 `shouldBe` baseFlex { + wrap = WrapReverse, + Flex.children = [[(child 30) { shrink = 2}], [(child 30) { grow = 2 }], + [(child 10) { grow = 1 }, (child 18) { shrink = 1 }]] + } + flexWrap baseFlex { wrap = WrapReverse } 20 `shouldBe` baseFlex { + wrap = WrapReverse, + Flex.children = [[(child 20) { shrink = 2}], [(child 30) { grow = 2 }], + [(child 20) { shrink = 1 }], [(child 20) { grow = 1 }]] + } + flexWrap baseFlex { wrap = WrapReverse } 10 `shouldBe` baseFlex { + wrap = WrapReverse, + Flex.children = [[(child 10) { shrink = 2}], [(child 30) { grow = 2 }], + [(child 10) { shrink = 1 }], [(child 10) { grow = 1 }]] + } + it "Paginates properly" $ do + let child l align = FlexChild { + grow = 0, + shrink = 0, + basis = l, + Flex.alignment = align, + flexInner = l + } + let baseFlex = FlexParent { + direction = Row, + reverseRows = False, + wrap = Wrap, + justify = JStart, + alignLines = Just JStart, + baseGap = 2, + crossGap = 2, + Flex.children = [[(child 10 AlStart) { grow = 1 }, + (child 20 AlCenter) { shrink = 1 }, + (child 30 AlEnd) { grow = 2 }, + (child 40 AlStretch) { shrink = 2 }]], + pageWidth = 80 + } :: FlexParent Double Double + let self = flexWrap baseFlex 50 + self `shouldBe` baseFlex { + Flex.children = [[(child 10 AlStart) { grow = 1 }, + (child 6 AlCenter) { shrink = 1, flexInner = 20 }, + (child 30 AlEnd) { grow = 2 }], [ + (child 40 AlStretch) { shrink = 2}]] + } + flexSplit (\x -> Size x x) 30 50 self `shouldBe` (baseFlex { + Flex.children = [[(child 10 AlStart) { grow = 1 }, + (child 6 AlCenter) { shrink = 1, flexInner = 20 }, + (child 30 AlEnd) { grow = 2 }]] + }, baseFlex { + Flex.children = [[(child 40 AlStretch) { shrink = 2}]] + }) + print self { direction = Flex.Column } + flexSplit (\x -> Size x x) 40 50 self { direction = Flex.Column } `shouldBe` (baseFlex { + direction = Flex.Column, + Flex.children = [[(child 10 AlStart) { grow = 1 }, + (child (-4) AlCenter) { shrink = 1, flexInner = 20 }, + (child 30 AlEnd) { grow = 2 }], [(child 40 AlStretch) { shrink = 2}]] + }, baseFlex { + direction = Flex.Column, + Flex.children = [] + }) + flexSplit (\x -> Size x x) 10 50 self { direction = Flex.Column } `shouldBe` (baseFlex { + direction = Flex.Column, + Flex.children = [] + }, baseFlex { + direction = Flex.Column, + Flex.children = [[(child 10 AlStart) { grow = 1 }, + (child 6 AlCenter) { shrink = 1, flexInner = 20 }, + (child 30 AlEnd) { grow = 2 }], [(child 40 AlStretch) { shrink = 2}]] + }) + flexSplit (\x -> Size x x) 10 50 self { direction = Flex.Column, pageWidth = 40 } `shouldBe` (baseFlex { + Flex.children = [[(child 10 AlStart) { grow = 1 }, + (child 6 AlCenter) { shrink = 1, flexInner = 20 }, + (child 30 AlEnd) { grow = 2 }]], + pageWidth = 40 + }, baseFlex { + Flex.children = [[(child 40 AlStretch) { shrink = 2}]], + pageWidth = 40 + }) runMath = flip evalCalc [] . mapCalc fst . flip parseCalc [] . filter (/= Whitespace) . tokenize -- 2.30.2