lipstick-0.1.0.0: src/Lipstick/Place.hs
-- | Placing a block in a larger area filled with whitespace.
--
-- == Examples
--
-- >>> let hi = render (border roundedBorder) "hi"
-- >>> putBlock (place (width 16 <> height 5 <> align center <> valign center <> whitespaceChars "·") hi)
-- ················
-- ······╭──╮······
-- ······│hi│······
-- ······╰──╯······
-- ················
-- >>> putBlock (place (width 16 <> height 3 <> align right <> valign bottom <> whitespaceChars "·") hi)
-- ············╭──╮
-- ············│hi│
-- ············╰──╯
--
-- Without a 'Lipstick.Mod.width', the area is as wide as the space it is
-- laid out in:
--
-- >>> putBlock (fromGrid (layout (Just 20) (place (align center <> whitespaceChars "-") " centred ")))
-- ----- centred ------
module Lipstick.Place
( Place
, place
, whitespaceChars
) where
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Data.Text qualified as T
import Lipstick.Internal.Grid
import Lipstick.Internal.Layout (Assign (..), Block (..), Measured (..), Request (..), cropLayoutWidth, measureIn)
import Lipstick.Internal.Types
import Lipstick.Internal.Mod
-- $setup
-- >>> import Lipstick
-- | The surrounding area: its size, where the block sits in it, and the
-- characters of the whitespace. Its text attributes colour the whitespace
-- and are the base for the placed block.
data Place = Place
{ placeSize :: !Size
, placeAlign :: !Align
, placeChars :: !Text
}
deriving (Eq, Show)
instance HasSize Place where
overSize f p = p {placeSize = f (placeSize p)}
instance HasAlign Place where
overAlign f p = p {placeAlign = f (placeAlign p)}
instance HasCrop Place
defPlace :: Place
defPlace = Place defSize defAlign (T.singleton ' ')
-- | Characters repeated to fill the whitespace (default a space).
whitespaceChars :: Text -> Mod Place
whitespaceChars t = fieldMod (\p -> p {placeChars = t})
-- | Place a block in an area, at the position set by 'align' and
-- 'valign'. The area is as wide as the space it is laid out in, or 'width'
-- columns if set and there is room for them, and at least 'height' lines
-- if set. A block wider than the area is laid out in its width, so its
-- text wraps; a block taller than the area makes it taller.
--
-- >>> putBlock (place (width 7 <> height 3 <> align center <> valign center <> whitespaceChars ".") "b")
-- .......
-- ...b...
-- .......
place :: Mod Place -> Block -> Block
place m b = Block $ \ctx -> placeNode m (measureIn ctx b)
placeNode :: Mod Place -> Measured -> Measured
placeNode m nb = Measured req run
where
(Place (Size wSet hSet weight stretch) (Align ah av) chars, props) = runMod m defPlace
r = blockRequest nb
capW x = maybe x (min x . max 0) (propMaxWidth props)
capH x = maybe x (min x . max 0) (propMaxHeight props)
req =
Request
{ reqMin = capW (fromMaybe (reqMin r) wSet)
, reqMax = capW (fromMaybe (reqMax r) wSet)
, -- Without a width, the area is all the space there is.
reqGrow = maybe (max 1 weight) (const weight) wSet
, reqLimit = max 0 <$> propMaxWidth props
, reqStretch = stretch
, reqFill = False
}
run (Assign w h) = (fitWidth w (withProps props ls), natural)
where
outerW = cropLayoutWidth (propMaxWidth props) (reqGrow req) (fromMaybe (reqMax r) wSet) w
(g, n) = blockLayout nb (Assign (if reqGrow r > 0 then outerW else min outerW (reqMax r)) outerH)
natural = capH (max (fromMaybe 0 hSet) n)
-- From the child's natural height, not its grid's: the grid is
-- laid out with this height, for a child that stretches.
outerH = maximum [n, fromMaybe 0 hSet, if stretch then h else 0]
ws k = fillCells mempty chars k
(left', right') = splitExtra ah (outerW - gridWidth g)
(above, below) = splitExtra av (outerH - gridHeight g)
body = [ws left' ++ l ++ ws right' | l <- gridCells g]
ls = replicate above (ws outerW) ++ body ++ replicate below (ws outerW)