packages feed

ansi-terminal-game-1.2.1.0: src/Terminal/Game/Draw.hs

-------------------------------------------------------------------------------
-- Print convenience functions
-- 2017 Francesco Ariis GPLv3
-------------------------------------------------------------------------------

-- Drawing primitives. If not stated otherwise (textbox, etc.), ' ' are
-- assumed to be opaque

module Terminal.Game.Draw (module Terminal.Game.Draw,
                           (F.&)
                          ) where

import Terminal.Game.Plane

import Text.LineBreak

import qualified Data.Function       as F ( (&) )
import qualified Data.List           as L
import qualified System.Console.ANSI as CA


-----------
-- TYPES --
-----------

-- | A drawing function, usually executed with the help of '%'.
type Draw = Plane -> Plane


-----------------
-- COMBINATORS --
-----------------

-- | Pastes one 'Plane' onto another. To be used along with 'F.&'
-- like this:
--
-- @
--  d :: Plane
--  d =          blankPlane 100 100  &
--      (3, 4) % box '_' 3 5         &
--      (a, b) % cell \'A\' '#' bold
-- @
(%) :: Coords -> Plane -> Draw
cds % p1 = \p2 -> pastePlane p1 p2 cds
infixl 4 %

-- | Apply style to plane, e.g.
--
-- > cell 'w' # bold
(#) :: Plane -> Draw -> Plane
p # sf = sf p
infixl 8 #

-- | Shorthand for sequencing 'Plane's, e.g.
--
-- @
--           firstPlane  &
--  (3, 4) '%' secondPlane &
--  (1, 9) '%' thirdPlane
-- @
--
-- is equal to
--
-- @
--  mergePlanes firstPlane [((3,4), secondPlane),
--                          ((1,9), thirdPlane)]
-- @
mergePlanes :: Plane -> [(Coords, Plane)] -> Plane
mergePlanes p cps = L.foldl' addPlane p cps
    where
          addPlane :: Plane -> (Coords, Plane) -> Plane
          addPlane bp (cs, tp) = bp F.& cs % tp

-- | Place two 'Plane's side-by-side, horizontally.
(|||) :: Plane -> Plane -> Plane
(|||) a b = let (wa, ha) = planeSize a
                (wb, hb) = planeSize b
            in mergePlanes (blankPlane (wa + wb) (max ha hb))
                           [((1,1),    a),
                            ((1,wa+1), b)]

-- | Place two 'Plane's side-by-side, vertically.
(===) :: Plane -> Plane -> Plane
(===) a b = let (wa, ha) = planeSize a
                (wb, hb) = planeSize b
            in mergePlanes (blankPlane (max wa wb) (ha + hb))
                           [((1,1),    a),
                            ((ha+1,1), b)]

-- | @a *** b@ blits @b@ in the centre of @a@.
(***) :: Plane -> Plane -> Plane
(***) a b = let (aw, ah) = planeSize a
                (bw, bh) = planeSize b
                r = quot (ah - bh) 2 + 1
                c = quot (aw - bw) 2 + 1
            in           a F.&
                (r, c) % b


-- | Place a list of 'Plane's side-by-side, horizontally.
hcat :: [Plane] -> Plane
hcat ps = L.foldl' (|||) (blankPlane 0 0) ps

-- | Place a list of 'Plane's side-by-side, vertically.
vcat :: [Plane] -> Plane
vcat ps = L.foldl' (===) (blankPlane 0 0) ps

infixl 6 |||, ===, ***


------------
-- STYLES --
------------

-- | Set foreground color.
color :: CA.Color -> CA.ColorIntensity -> Plane -> Plane
color c i p = mapPlane (colorCell c i) p

-- | Apply bold style to 'Plane'.
bold :: Plane -> Plane
bold p = mapPlane boldCell p

-- | Swap foreground and background colours of 'Plane'.
invert :: Plane -> Plane
invert p = mapPlane reverseCell p



-------------
-- DRAWING --
-------------

-- | A box of dimensions @w h@.
box :: Width -> Height -> Char -> Plane
box w h chr = seqCellsDim w h cells
    where
          cells = [((r, c), chr) | r <- [1..h], c <- [1..w]]

-- | A 1×1 @Plane@.
cell :: Char -> Plane
cell ch = box 1 1 ch

-- | @1xn@ 'Plane' with a word in it. If you need to import multiline
-- ASCII art, check 'stringPlane' and 'stringPlaneTrans'.
word :: String -> Plane
word w = seqCellsDim (L.genericLength w) 1 cells
    where
          cells = zip (zip (repeat 1) [1..]) w

-- opaque :: Plane -> Plane
-- opaque p = pastePlane p (box ' ' White w h) (1, 1)
--     where
--           (w, h) = pSize p

-- | A text-box. Assumes @' '@s are transparent.
textBox :: Width -> Height -> String -> Plane
textBox w h cs = frameTrans w h (textBoxLiquid w cs)

-- | Like 'textBox', but tall enough to fit @String@.
textBoxLiquid :: Width -> String -> Plane
textBoxLiquid w cs = textBoxGeneralLiquid Nothing w cs

-- | As 'textBox', but hypenated. Example:
--
-- @
-- Rimasi un po’ a meditare nel buio       Rimasi un po’ a meditare nel buio
-- velato appena dal barlume azzurrino     velato appena dal barlume azzurrino
-- del fornello a gas, su cui sobbol-      del fornello a gas, su cui
-- liva quieta la pentola.                 sobbolliva quieta la pentola.
-- @
--
-- Notice how in the left box «sobbolliva» is broken in two. This
-- can be useful and aesthetically pleasing when textboxes are narrow.
textBoxHyphen :: Hyphenator -> Width -> Height -> String -> Plane
textBoxHyphen hp w h cs = frameTrans w h (textBoxHyphenLiquid hp w cs)

-- | As 'textBoxLiquid', but hypenated.
textBoxHyphenLiquid :: Hyphenator -> Width -> String -> Plane
textBoxHyphenLiquid h w cs = textBoxGeneralLiquid (Just h) w cs

textBoxGeneralLiquid :: Maybe Hyphenator -> Width -> String -> Plane
textBoxGeneralLiquid mh w cs = transparent
    where
          -- hypenathion
          bf  = BreakFormat (fromIntegral w) 4 '-' mh
          hcs = breakStringLn bf cs
          h   = L.genericLength hcs

          f :: [String] -> [(Coords, Char)]
          f css = concatMap (uncurry rf) (zip [1..] css)
              where rf :: Int -> String -> [(Coords, Char)]
                    rf cr ln = zip (zip (repeat cr) [1..]) ln

          out         = seqCellsDim w h (f hcs)
          transparent = makeTransparent ' ' out

-----------------
-- ANCILLARIES --
-----------------

seqCellsDim :: Width -> Height -> [(Coords, Char)] -> Plane
seqCellsDim w h cells = seqCells (blankPlane w h) cells

seqCells :: Plane -> [(Coords, Char)] -> Plane
seqCells p cells = updatePlane p (map f cells)
    where
          f (cds, chr) = (cds, creaCell chr)

-- paste plane on a blank one, and make ' ' transparent
frameTrans :: Width -> Height -> Plane -> Plane
frameTrans w h p = let bt = makeTransparent ' ' (blankPlane w h)
                   in bt F.& (1, 1) % p