~alcinnz/CatTrap

ref: d52668823f8d0ecdc3de52f103a6e5081fb6dd93 CatTrap/Graphics/Layout/Grid.hs -rw-r--r-- 6.7 KiB
d5266882 — Adrian Cochrane Refactor grid layout code to deduplicate logic along each axis. 1 year, 7 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
{-# LANGUAGE RecordWildCards, OverloadedStrings #-}
module Graphics.Layout.Grid(Grid(..), Track(..), GridItem(..), GridItem'(..), Alignment(..),
        buildTrack, buildGrid, setCellBox, enumerate, gridItemBox, cellSize,
        trackMin, trackNat, gridEstWidth, sizeTrackMins, sizeTrackNats, sizeTrackMaxs,
        trackPosition, gridPosition, trackLayout, gridLayout) where

import Data.Either (fromRight)
import Data.Text (Text)
import Data.List (intersperse)
import Graphics.Layout.Box as B

import Debug.Trace (trace)

type Grid m n = Size (Track m) (Track n)
data Track x = Track {
    cells :: [Either x Double],
    trackMins :: [Double],
    trackNats :: [Double],
    gap :: x
}
type GridItem = Size GridItem' GridItem'
data GridItem' = GridItem {
    cellStart :: Int, cellEnd :: Int, alignment :: Alignment,
    minSize :: Double, natSize :: Double
}
data Alignment = Start | Mid | End

buildTrack :: CastDouble x => [Either x Double] -> Track x
buildTrack cells = Track cells [] [] $ fromDouble 0
buildGrid :: (CastDouble m, CastDouble n) =>
        [Either m Double] -> [Either n Double] -> Grid m n
buildGrid rows cols = Size (buildTrack cols) (buildTrack rows)

verifyTrack :: Track x -> [GridItem'] -> Bool
verifyTrack track cells' = and [
    cellStart cell < length (cells track) && cellStart cell >= 0 &&
    cellEnd cell < length (cells track) && cellEnd cell > cellStart cell
  | cell <- cells']
verifyGrid :: Grid m n -> [GridItem] -> Bool
verifyGrid grid cells =
    verifyTrack (inline grid) (map inline cells) && verifyTrack (block grid) (map block cells)

trackMin :: (n -> Double) -> Track n -> Double
trackMin cb self@Track { trackMins = [] } =
    sum $ intersperse (cb $ gap self) [cb x | Left x <- cells self]
trackMin cb self = sum $ intersperse (cb $ gap self) $ trackMins self
trackNat :: (n -> Double) -> Track n -> Double
trackNat cb self@Track { trackNats = [] } =
    sum $ intersperse (cb $ gap self) [cb x | Left x <- cells self]
trackNat cb self = sum $ intersperse (cb $ gap self) $ trackNats self

cellsForIndex :: [GridItem'] -> Int -> [GridItem']
cellsForIndex cells ix =
    [cell | cell <- cells, cellStart cell == ix, cellStart cell == pred (cellEnd cell)]
setCellBox :: (CastDouble m, CastDouble n) => GridItem -> PaddedBox m n -> GridItem
setCellBox (Size x y) box = Size x {
    minSize = toDouble $ inline $ B.min box,
    natSize = toDouble $ inline $ B.size box
  } y {
    minSize = toDouble $ block $ B.min box,
    natSize = toDouble $ inline $ B.size box
  }

gridEstWidth :: Grid y Length -> [GridItem] -> Double
gridEstWidth (Size cols _) childs = trackNat toDouble cols {
    trackMins = sizeTrackMins 0 cols $ map inline childs,
    trackNats = sizeTrackNats 0 cols $ map inline childs
  }
sizeTrackMins :: Double -> Track Length -> [GridItem'] -> [Double]
sizeTrackMins parent track childs = map inner $ enumerate $ cells track
  where
    inner (_, Left (Pixels x)) = x
    inner (_, Left (Percent x)) = x * parent
    inner arg@(ix, Left Preferred) =
        maximum $ (0:) $ map natSize $ cellsForIndex childs ix
    inner (ix, _) =
        maximum $ (0:) $ map minSize $ cellsForIndex childs ix
sizeTrackNats :: Double -> Track Length -> [GridItem'] -> [Double]
sizeTrackNats parent track childs = map inner $ enumerate $ cells track
  where
    inner (_, Left (Pixels x)) = x
    inner (_, Left (Percent x)) = x * parent
    inner arg@(ix, Left Min) =
        maximum $ (0:) $ map minSize $ cellsForIndex childs ix
    inner (ix, _) =
        maximum $ (0:) $ map natSize $ cellsForIndex childs ix
sizeTrackMaxs :: Double -> Track Length -> [Double]
sizeTrackMaxs parent track = map (inner fr) $ zip subsizes $ cells track
  where
    subsizes = zip (trackMins track) (trackNats track)
    fr = Prelude.max 0 fr'
    fr' = (parent - estimate)/(countFRs $ cells track)
    estimate = sum $ intersperse (lowerLength parent $ gap track) $
            map (inner 0) $ zip subsizes $ cells track
    inner _ (_, Left (Pixels x)) = x
    inner _ (_, Left (Percent x)) = x*parent
    inner _ ((_, nat), Left Preferred) = nat
    inner _ ((min, _), Left Min) = min
    inner fr ((_, nat), Left Auto) = Prelude.min nat fr
    inner fr (_, Right x) = x*fr

trackPosition :: Track Double -> [GridItem'] -> [Double]
trackPosition self childs = map gridCellPosition childs
  where
    gridCellPosition child = track (cellStart child) + align whitespace (alignment child)
      where
        whitespace = track (cellEnd child) - track (cellStart child) - natSize child
    track = flip track' $ cells 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
cellSize :: CastDouble x => Track x -> GridItem' -> Double
cellSize self child = track (cellEnd child) - track (cellStart child)
  where
    track = flip track' $ cells self
    track' ix (size:sizes) =
        (toDouble $ fromRight (fromDouble 0) size) + track' (pred ix) sizes
    track' 0 _ = 0
    track' ix [] = trace "WARNING! Malformed input table!" 0
gridItemBox :: (CastDouble x, CastDouble y) => Grid y x -> GridItem -> PaddedBox Double Double
gridItemBox (Size cols rows) cell =
    size2box (cellSize cols (inline cell) `Size` cellSize rows (block cell))
  where
    size2box size = zero { B.min = size, B.max = size, B.size = size }
gridPosition :: Grid Double Double -> [GridItem] -> [(Double, Double)]
gridPosition (Size cols rows) childs =
    trackPosition rows (map inline childs) `zip` trackPosition cols (map block childs)
trackLayout :: Double -> Double -> Track Length -> [GridItem'] ->
        (Track Double, [(Double, GridItem')])
trackLayout parent width self childs = (self', zip positions childs)
  where
    positions = trackPosition self' childs
    self' = self {
        cells = map Left sizes,
        trackMins = mins, trackNats = nats,
        gap = lowerLength width $ gap self
      }
    sizes = sizeTrackMaxs parent self { trackMins = mins, trackNats = nats }
    mins = sizeTrackMins parent self childs
    nats = sizeTrackNats parent self childs
gridLayout :: Size Double Double -> Grid Length Length -> [GridItem] ->
        (Grid Double Double, [((Double, Double), GridItem)])
gridLayout parent (Size cols rows) childs = (self', zip positions childs)
  where
    positions = gridPosition self' childs
    self' = Size cols' { gap = lowerLength width $ gap cols } rows'
    (rows', _) = trackLayout (block parent) width rows $ map block childs
    width = trackNat id cols'
    (cols', _) = trackLayout (inline parent) 0 cols $ map inline childs

enumerate = zip [0..]

countFRs (Left Auto:rest) = succ $ countFRs rest
countFRs (Right x:rest) = x + countFRs rest
countFRs (_:rest) = countFRs rest
countFRs [] = 0