gloss-relative-0.1.0.0: src/Graphics/Gloss/Relative/Internal/Window.hs
module Graphics.Gloss.Relative.Internal.Window where
import Graphics.Gloss
import qualified Graphics.Gloss.Data.Point.Arithmetic as Gloss
import Graphics.Gloss.Relative.Internal.Dimension
import Graphics.Gloss.Relative.Internal.Picture
import Control.Monad.Reader (Reader(..),ReaderT(..))
import qualified Control.Monad.Reader as Reader
import Control.Monad.Writer (Writer(..),WriterT(..))
import qualified Control.Monad.Writer as Writer
import Control.Monad
import Data.Set (Set(..))
import qualified Data.Set as Set
-- * Window data type
-- | A function that returns which regions a position belongs to.
type RegionHandler = Point -> Set String
type WindowOutput = (Picture,RegionHandler)
newtype Window a = Window { unWindow :: ReaderT Dimension (Writer WindowOutput) a }
deriving (Monad,Applicative,Functor)
mkWindow :: (Dimension -> WindowOutput) -> Window ()
mkWindow f = Window $ do
screen <- Reader.ask
Writer.tell $ f screen
runWindow :: Dimension -> Window a -> (a,WindowOutput)
runWindow screen (Window w) = Writer.runWriter (Reader.runReaderT w screen)
execWindow :: Dimension -> Window a -> WindowOutput
execWindow screen w = snd $ runWindow screen w
askDimension :: Window Dimension
askDimension = Window $ Reader.ask
withDimension :: (Dimension -> Dimension) -> Window a -> Window a
withDimension f (Window w) = Window $ Reader.local f w
mapWindowOutput :: (WindowOutput -> WindowOutput) -> Window a -> Window a
mapWindowOutput f (Window w) = Window $ Reader.mapReaderT (Writer.mapWriter (\(x,y) -> (x,f y))) w
emptyWindow :: Window ()
emptyWindow = Window $ Writer.tell (Blank,mempty)
tellWindowOutput :: WindowOutput -> Window ()
tellWindowOutput p = Window $ Writer.tell p
-- * Window transformations
-- | Builds a grid of windows, evenly splitting the screen both horiontally and vertically.
grid :: [[Window a]] -> Window [[a]]
grid = rows . map columns
-- | Evenly splits the screen vertically into a list.
rows :: [Window a] -> Window [a]
rows ws = Window $ do
dim@(dimx,dimy) <- Reader.ask
let dimyn = (realToFrac dimy) / (realToFrac $ length ws)
let go [] = fmap (Prelude.const []) emptyWindow
go (x:xs) = fmap (uncurry (:)) (top dimyn x (go xs))
unWindow $ go ws
-- | Evenly splits the screen horizontally into a list.
columns :: [Window a] -> Window [a]
columns ws = Window $ do
dim@(dimx,dimy) <- Reader.ask
let dimxn = (realToFrac dimx) / (realToFrac $ length ws)
let go [] = fmap (Prelude.const []) emptyWindow
go (x:xs) = fmap (uncurry (:)) (left dimxn x (go xs))
unWindow $ go ws
-- | Top-biased vertical composition of two windows. Receives height of top row.
top :: Float -> Window a -> Window b -> Window (a,b)
top sy1 w1 w2 = Window $ do
dim@(sx,sy) <- Reader.ask
let sy2 = sy - sy1
let (a,(p1,r1)) = runWindow (sx,sy1) w1
let (b,(p2,r2)) = runWindow (sx,sy2) w2
let p12 = Pictures [translateY (realToFrac sy2/2) p1,translateY (-realToFrac sy1/2) p2]
let r12 pos = Set.unions [r1 $ translatePointY (-realToFrac sy2/2) pos, r2 $ translatePointY (realToFrac sy1/2) pos]
Writer.tell (p12,r12)
return (a,b)
-- | Bottom-biased vertical composition of two windows. Receives height of bottom row.
bottom :: Float -> Window a -> Window b -> Window (a,b)
bottom sy2 w1 w2 = do
(w,h) <- askDimension
top (h-sy2) w1 w2
-- Left-biased horizontal composition of two windows. Receives width of left column.
left :: Float -> Window a -> Window b -> Window (a,b)
left sx1 w1 w2 = Window $ do
(sx,sy) <- Reader.ask
let sx2 = sx - sx1
let (a,(p1,r1)) = runWindow (sx1,sy) w1
let (b,(p2,r2)) = runWindow (sx2,sy) w2
let p12 = Pictures [translateX (-realToFrac sx2/2) p1,translateX (realToFrac sx1/2) p2]
let r12 pos = Set.unions [r1 $ translatePointX (realToFrac sx2/2) pos,r2 $ translatePointX (-realToFrac sx1/2) pos]
Writer.tell (p12,r12)
return (a,b)
-- Right-biased horizontal composition of two windows. Receives width of right column.
right :: Float -> Window a -> Window b -> Window (a,b)
right sx2 w1 w2 = do
(w,h) <- askDimension
left (w-sx2) w1 w2
-- | Stretches a picture to fit the window
stretchWith :: Dimension -> Picture -> Window ()
stretchWith (cx,cy) pic = Window $ do
screen@(sx,sy) <- Reader.ask
let scalex = max 0 (realToFrac sx / realToFrac cx)
scaley = max 0 (realToFrac sy / realToFrac cy)
Writer.tell (Scale scalex scaley pic,mempty)
return ()
stretch :: Picture -> Window ()
stretch pic = stretchWith (pictureDimension pic) pic
align :: Alignment -> Dimension -> Dimension -> (Float,Float)
align (Alignment ha va) c s = halign ha c s Gloss.+ valign va c s
halign :: HorizontalAlignment -> Dimension -> Dimension -> (Float,Float)
halign a (cx,cy) (sx,sy) = case a of
AlignLeft -> (-((sx-cx)/2),0)
AlignRight -> (((sx-cx)/2),0)
AlignCenter -> (0,0)
valign :: VerticalAlignment -> Dimension -> Dimension -> (Float,Float)
valign a (cx,cy) (sx,sy) = case a of
AlignTop -> (0,((sy-cy)/2))
AlignBottom -> (0,-((sy-cy)/2))
AlignMiddle -> (0,0)
alignWith :: Dimension -> Alignment -> WindowOutput -> Window ()
alignWith dim a (pic,region) = Window $ do
screen <- Reader.ask
let (ax,ay) = align a dim screen
let pic' = translateX ax $ translateY ay pic
let region' pos = region $ translatePointX (-ax) $ translatePointY (-ay) pos
Writer.tell (pic',region')
fitWith :: Dimension -> WindowOutput -> Window ()
fitWith (cx,cy) (pic,region) = Window $ do
(sx,sy) <- Reader.ask
let scalex = sx / cx
let scaley = sy / cy
let scalexy = max 0 (min scalex scaley)
let dim' = (cx*scalexy,cy*scalexy)
let pic' = Scale scalexy scalexy pic
let region' pos = region $ scalePoint (1/scalexy) (1/scalexy) pos
Writer.tell (pic',region')
fit :: WindowOutput -> Window ()
fit (pic,region) = fitWith (pictureDimension pic) (pic,region)
largestAspectFit :: Float -> Float -> Dimension -> Dimension
largestAspectFit a b (screenW,screenH)
| screenW / screenH >= a / b = (screenH * (a / b), screenH)
| otherwise = (screenW, screenW * (b / a))
text :: String -> Window ()
text str = fitWith dim (pic,mempty)
where
textWidth = realToFrac (length str) * (realToFrac charWidth)
pic = Translate (-realToFrac textWidth/2) (-realToFrac charHeight/2) $ Text str
dim = (textWidth,realToFrac charHeight)
-- * Regions
-- | Registers a new region for the current window
addRegion :: String -> Window ()
addRegion name = do
dim <- askDimension
let reg = dimensionToRegion dim
tellWindowOutput (Blank,\p -> if pointInsideRegion p reg then Set.singleton name else Set.empty)
-- * Alignment types
-- | Alignment options for a picture or frame inside a larger frame.
data Alignment = Alignment
{ alignH :: HorizontalAlignment -- ^ Horizontal alignment.
, alignV :: VerticalAlignment -- ^ Vertical alignment.
} deriving (Eq,Ord,Show)
-- | Horizontal alignment options for a picture or frame inside a larger frame.
data HorizontalAlignment
= AlignLeft
| AlignCenter
| AlignRight
deriving (Eq,Ord,Show,Enum,Bounded)
-- | Vertical alignment options for a picture or frame inside a larger frame.
data VerticalAlignment
= AlignTop
| AlignMiddle
| AlignBottom
deriving (Eq,Ord,Show,Enum,Bounded)