gloss-relative-0.1.1.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 Graphics.Gloss.Relative.Internal.Raster
import Graphics.Gloss.Relative.Internal.Data
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 Control.Monad.Trans
import Control.Monad.IO.Class
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 -> IO (Set String)
data WindowOutput = WindowOutput { wPic :: Picture, wHandler :: RegionHandler }
instance Semigroup WindowOutput where
(WindowOutput p1 r1) <> (WindowOutput p2 r2) = WindowOutput (joinPicture p1 p2) (mappend r1 r2)
instance Monoid WindowOutput where
mempty = WindowOutput emptyPicture mempty
newtype Window m a = Window { unWindow :: ReaderT Dimension (WriterT WindowOutput m) a }
deriving (Monad,Applicative,Functor)
instance MonadTrans Window where
lift m = Window $ lift $ lift m
instance MonadIO m => MonadIO (Window m) where
liftIO = lift . liftIO
mkWindow :: Monad m => (Dimension -> WindowOutput) -> Window m ()
mkWindow f = Window $ do
screen <- Reader.ask
Writer.tell $ f screen
runWindow :: Monad m => Dimension -> Window m a -> m (a,WindowOutput)
runWindow screen (Window w) = Writer.runWriterT (Reader.runReaderT w screen)
execWindow :: Monad m => Dimension -> Window m a -> m WindowOutput
execWindow screen (Window w) = Writer.execWriterT (Reader.runReaderT w screen)
askDimension :: Monad m => Window m Dimension
askDimension = Window $ Reader.ask
withDimension :: Monad m => (Dimension -> Dimension) -> Window m a -> Window m a
withDimension f (Window w) = Window $ Reader.local f w
mapWindowOutput :: Monad m => (WindowOutput -> WindowOutput) -> Window m a -> Window m a
mapWindowOutput f (Window w) = Window $ Reader.mapReaderT (Writer.mapWriterT (\m -> m >>= \(x,y) -> return (x,f y))) w
emptyWindow :: Monad m => Window m ()
emptyWindow = tellWindowOutput mempty
tellWindowOutput :: Monad m => WindowOutput -> Window m ()
tellWindowOutput p = Window $ Writer.tell p
-- * Window transformations
-- | Builds a grid of windows, evenly splitting the screen both horiontally and vertically.
grid :: Monad m => [[Window m a]] -> Window m [[a]]
grid = rows . map columns
-- | Evenly splits the screen vertically into a list.
rows :: Monad m => [Window m a] -> Window m [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 :: Monad m => [Window m a] -> Window m [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 :: Monad m => Float -> Window m a -> Window m b -> Window m (a,b)
top sy1 w1 w2 = Window $ do
dim@(sx,sy) <- Reader.ask
let sy2 = sy - sy1
(a,WindowOutput p1 r1) <- lift $ lift $ runWindow (sx,sy1) w1
(b,WindowOutput p2 r2) <- lift $ lift $ runWindow (sx,sy2) w2
let p12 = Pictures [translateY (realToFrac sy2/2) p1,translateY (-realToFrac sy1/2) p2]
let r12 pos = do
m1 <- r1 $ translatePointY (-realToFrac sy2/2) pos
m2 <- r2 $ translatePointY (realToFrac sy1/2) pos
return $ Set.unions [m1,m2]
Writer.tell (WindowOutput p12 r12)
return (a,b)
-- | Bottom-biased vertical composition of two windows. Receives height of bottom row.
bottom :: Monad m => Float -> Window m a -> Window m b -> Window m (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 :: Monad m => Float -> Window m a -> Window m b -> Window m (a,b)
left sx1 w1 w2 = Window $ do
(sx,sy) <- Reader.ask
let sx2 = sx - sx1
(a,WindowOutput p1 r1) <- lift $ lift $ runWindow (sx1,sy) w1
(b,WindowOutput p2 r2) <- lift $ lift $ runWindow (sx2,sy) w2
let p12 = Pictures [translateX (-realToFrac sx2/2) p1,translateX (realToFrac sx1/2) p2]
let r12 pos = do
m1 <- r1 $ translatePointX (realToFrac sx2/2) pos
m2 <- r2 $ translatePointX (-realToFrac sx1/2) pos
return $ Set.unions [m1,m2]
Writer.tell (WindowOutput p12 r12)
return (a,b)
-- Right-biased horizontal composition of two windows. Receives width of right column.
right :: Monad m => Float -> Window m a -> Window m b -> Window m (a,b)
right sx2 w1 w2 = do
(w,h) <- askDimension
left (w-sx2) w1 w2
-- | Stretches a picture to fit the window
stretchWith :: Monad m => Dimension -> Picture -> Window m ()
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 (WindowOutput (Scale scalex scaley pic) mempty)
return ()
stretch :: Monad m => Picture -> Window m ()
stretch pic = stretchWith (pictureDimension pic) pic
alignWith :: Monad m => Dimension -> Alignment -> WindowOutput -> Window m ()
alignWith dim a (WindowOutput pic region) = Window $ do
screen <- Reader.ask
let (ax,ay) = alignCoords a dim screen
let pic' = translateX ax $ translateY ay pic
let region' pos = region $ translatePointX (-ax) $ translatePointY (-ay) pos
Writer.tell (WindowOutput pic' region')
align :: Monad m => Alignment -> WindowOutput -> Window m ()
align a (WindowOutput pic region) = alignWith (pictureDimension pic) a (WindowOutput pic region)
fitWith :: Monad m => Dimension -> WindowOutput -> Window m ()
fitWith (cx,cy) (WindowOutput 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 (WindowOutput pic' region')
fit :: Monad m => WindowOutput -> Window m ()
fit (WindowOutput pic region) = fitWith (pictureDimension pic) (WindowOutput pic region)
text :: Monad m => String -> Window m ()
text str = fitWith dim (WindowOutput 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, as a rectangle.
addRegionRectangle :: Monad m => String -> Window m ()
addRegionRectangle name = do
dim <- askDimension
let reg = dimensionToRegion dim
let react p = if pointInsideRegion p reg
then return $ Set.singleton name
else return Set.empty
tellWindowOutput $ WindowOutput emptyPicture react
-- | Registers a new region for the current window, pixel-wise.
addRegionTransparent :: Monad m => String -> RasteredPicture -> Window m ()
addRegionTransparent name pic = do
dim <- askDimension
let reg = dimensionToRegion dim
let react p = case pointWithinRegion p reg of
Nothing -> return Set.empty
Just p' -> getPixelAlpha pic p' >>= \alpha -> if alpha > 0
then return $ Set.singleton name
else return $ Set.empty
tellWindowOutput $ WindowOutput emptyPicture react
-- * Alignments
alignCoords :: Alignment -> Dimension -> Dimension -> (Float,Float)
alignCoords (RelativeAlignment ha va) c s = halignCoords ha c s Gloss.+ valignCoords va c s
alignCoords (AbsoluteAlignment (x,y)) (cx,cy) (sx,sy) = (ax,ay)
where
hborder = sx / 2 - cx / 2
vborder = sy / 2 - cy / 2
ax = min hborder $ max (-hborder) x
ay = min vborder $ max (-vborder) y
halignCoords :: HorizontalAlignment -> Dimension -> Dimension -> (Float,Float)
halignCoords a (cx,cy) (sx,sy) = case a of
AlignLeft -> (-((sx-cx)/2),0)
AlignRight -> (((sx-cx)/2),0)
AlignCenter -> (0,0)
valignCoords :: VerticalAlignment -> Dimension -> Dimension -> (Float,Float)
valignCoords a (cx,cy) (sx,sy) = case a of
AlignTop -> (0,((sy-cy)/2))
AlignBottom -> (0,-((sy-cy)/2))
AlignMiddle -> (0,0)
largestAspectFitDim :: Float -> Float -> Dimension -> Dimension
largestAspectFitDim a b (screenW,screenH)
| screenW / screenH >= a / b = (screenH * (a / b), screenH)
| otherwise = (screenW, screenW * (b / a))