monomer-1.5.1.0: src/Monomer/Widgets/Containers/Grid.hs
{-|
Module : Monomer.Widgets.Containers.Grid
Copyright : (c) 2018 Francisco Vallarino
License : BSD-3-Clause (see the LICENSE file)
Maintainer : fjvallarino@gmail.com
Stability : experimental
Portability : non-portable
Layout container which distributes space evenly along the main axis. For the
secondary axis children will receive as much space as available for the grid
widget itself.
In the same way as with hstack and vstack, 'hgrid' and 'vgrid' can be combined
to create more complex layouts.
The hgrid widget requests maxWidth * elements as its width, and the max height
as its height. The inverse happens for vgrid.
@
hgrid [
label "Third 1",
label "Third 2",
label "Third 3"
]
@
-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE Strict #-}
module Monomer.Widgets.Containers.Grid (
-- * Configuration
GridCfg,
-- * Constructors
hgrid,
hgrid_,
vgrid,
vgrid_
) where
import Control.Applicative ((<|>))
import Control.Lens ((&), (^.), (.~), (%~))
import Data.Default
import Data.List (foldl')
import Data.Maybe
import Data.Sequence (Seq(..), (|>))
import qualified Data.Sequence as Seq
import Monomer.Helper (applyFnList)
import Monomer.Widgets.Container
import qualified Monomer.Lens as L
{-|
Configuration options for grid:
- 'childSpacing': spacing between the child widgets.
- 'sizeReqUpdater': allows modifying the 'SizeReq' generated by the grid.
-}
data GridCfg = GridCfg {
_grcChildSpacing :: Maybe Double,
_grcSizeReqUpdater :: [SizeReqUpdater]
}
instance Default GridCfg where
def = GridCfg {
_grcChildSpacing = Nothing,
_grcSizeReqUpdater = []
}
instance Semigroup GridCfg where
(<>) s1 s2 = GridCfg {
_grcChildSpacing = _grcChildSpacing s2 <|> _grcChildSpacing s1,
_grcSizeReqUpdater = _grcSizeReqUpdater s1 <> _grcSizeReqUpdater s2
}
instance Monoid GridCfg where
mempty = def
instance CmbChildSpacing GridCfg where
childSpacing_ spacing = def {
_grcChildSpacing = Just spacing
}
instance CmbSizeReqUpdater GridCfg where
sizeReqUpdater updater = def {
_grcSizeReqUpdater = [updater]
}
-- | Creates a grid of items with the same width.
hgrid
:: Traversable t
=> t (WidgetNode s e) -- ^ The list of items.
-> WidgetNode s e -- ^ The created grid.
hgrid children = hgrid_ def children
-- | Creates a grid of items with the same width. Accepts config.
hgrid_
:: Traversable t
=> [GridCfg] -- ^ The config options.
-> t (WidgetNode s e) -- ^ The list of items.
-> WidgetNode s e -- ^ The created grid.
hgrid_ configs children = newNode where
config = mconcat configs
newNode = defaultWidgetNode "hgrid" (makeFixedGrid True config)
& L.children .~ foldl' (|>) Empty children
-- | Creates a grid of items with the same height.
vgrid
:: Traversable t
=> t (WidgetNode s e) -- ^ The list of items.
-> WidgetNode s e -- ^ The created grid.
vgrid children = vgrid_ def children
-- | Creates a grid of items with the same height. Accepts config.
vgrid_
:: Traversable t
=> [GridCfg] -- ^ The config options.
-> t (WidgetNode s e) -- ^ The list of items.
-> WidgetNode s e -- ^ The created grid.
vgrid_ configs children = newNode where
config = mconcat configs
newNode = defaultWidgetNode "vgrid" (makeFixedGrid False config)
& L.children .~ foldl' (|>) Empty children
makeFixedGrid :: Bool -> GridCfg -> Widget s e
makeFixedGrid isHorizontal config = widget where
widget = createContainer () def {
containerLayoutDirection = getLayoutDirection isHorizontal,
containerGetSizeReq = getSizeReq,
containerResize = resize
}
isVertical = not isHorizontal
childSpacing = fromMaybe 0 (_grcChildSpacing config)
getSizeReq wenv node children = newSizeReq where
sizeReqFns = _grcSizeReqUpdater config
vchildren = Seq.filter (_wniVisible . _wnInfo) children
newSizeReqW = getDimSizeReq isHorizontal (_wniSizeReqW . _wnInfo) vchildren
newSizeReqH = getDimSizeReq isVertical (_wniSizeReqH . _wnInfo) vchildren
newSizeReq = applyFnList sizeReqFns (newSizeReqW, newSizeReqH)
getDimSizeReq mainAxis accessor vchildren
| Seq.null vreqs = fixedSize 0
| mainAxis = foldl1 sizeReqMergeSum (Seq.replicate nreqs maxSize) & L.fixed %~ (+ totalSpacing)
| otherwise = maxSize
where
vreqs = accessor <$> vchildren
nreqs = Seq.length vreqs
~maxSize = foldl1 sizeReqMergeMax vreqs
totalSpacing = fromIntegral (nreqs - 1) * childSpacing
resize wenv node viewport children = resized where
style = currentStyle wenv node
contentArea = fromMaybe def (removeOuterBounds style viewport)
Rect l t w h = contentArea
vchildren = Seq.filter (_wniVisible . _wnInfo) children
totalSpacingW = fromIntegral (max 0 (cols - 1)) * childSpacing
totalSpacingH = fromIntegral (max 0 (rows - 1)) * childSpacing
cols = if isHorizontal then length vchildren else 1
rows = if isHorizontal then 1 else length vchildren
cw = if cols > 0 then (w - totalSpacingW) / fromIntegral cols else 0
ch = if rows > 0 then (h - totalSpacingH) / fromIntegral rows else 0
cx i
| rows == 0 = 0
| isHorizontal = l + fromIntegral i * cw + spacingOffset i
| otherwise = l
cy i
| cols == 0 = 0
| isVertical = t + fromIntegral i * ch + spacingOffset i
| otherwise = t
spacingOffset i =
fromIntegral i * childSpacing
foldHelper (currAreas, index) child = (newAreas, newIndex) where
(newIndex, newViewport)
| child ^. L.info . L.visible = (index + 1, calcViewport index)
| otherwise = (index, def)
newArea = newViewport
newAreas = currAreas |> newArea
calcViewport i = Rect (cx i) (cy i) cw ch
assignedAreas = fst $ foldl' foldHelper (Seq.empty, 0) children
resized = (resultNode node, assignedAreas)