packages feed

gloss-relative-0.1.1.0: src/Graphics/Gloss/Relative/Internal/Frame.hs

{-# LANGUAGE ViewPatterns #-}

module Graphics.Gloss.Relative.Internal.Frame where

import Graphics.Gloss
import qualified Graphics.Gloss.Rendering as Gloss
import Graphics.Gloss.Relative.Internal.Dimension
import Graphics.Gloss.Relative.Internal.Data
import qualified Graphics.Gloss.Relative.Internal.Picture as Picture
import qualified Graphics.Gloss.Relative.Internal.Window as Window
import qualified Graphics.Gloss.Relative.Internal.Raster as Raster
import qualified Graphics.Gloss.Relative.Internal.Cache as Cache

import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans
import Control.Monad.Identity (Identity(..))
import qualified Control.Monad.Identity as Identity
import Data.Maybe
import Data.IORef

-- | Renders a frame into a picture. 
renderStaticFrame
    :: Frame -- ^ The frame to render. __Note:__ This function ignores frame labels. Use only if you don't need mouse hover events in this frame.
    -> Dimension -- ^ The dimension of the screen in which to render the frame.
    -> Picture -- ^ The resulting picture.
renderStaticFrame f screen = Window.wPic $ Identity.runIdentity $ Window.execWindow screen (renderStaticFrameAsWindow f)
    where
    renderStaticFrameAsWindow :: Monad m => Frame -> Window.Window m ()
    renderStaticFrameAsWindow (Aspect (aw,ah) a f) = do
        dim <- Window.askDimension
        let adim = Window.largestAspectFitDim aw ah dim
        wo <- lift $ Window.execWindow adim (renderStaticFrameAsWindow f)
        wo' <- lift $ Window.execWindow adim $ Window.fitWith adim wo
        Window.alignWith adim a wo'
    renderStaticFrameAsWindow (Zoom x y a f) = do
        (w,h) <- Window.askDimension
        let dim' = (w*x,h*y)
        wo <- lift $ Window.execWindow dim' (renderStaticFrameAsWindow f)
        Window.alignWith dim' a wo
    renderStaticFrameAsWindow (Grid xs) = do
        Window.grid (map (map renderStaticFrameAsWindow) xs)
        return ()
    renderStaticFrameAsWindow (Frames xs) = mapM_ renderStaticFrameAsWindow xs
    renderStaticFrameAsWindow (Stretch Nothing pic) = Window.stretch pic
    renderStaticFrameAsWindow (Stretch (Just dim) pic) = Window.stretchWith dim pic
    renderStaticFrameAsWindow (Sized f) = do
        dim <- Window.askDimension
        renderStaticFrameAsWindow (f dim)
    renderStaticFrameAsWindow (Label name isTransparent f) = renderStaticFrameAsWindow f -- ignores labels

renderDynamicFrame :: Int -> Cache.CacheTable -> Gloss.State -> IORef (IO Raster.Offscreen) -> Frame -> Dimension -> IO Window.WindowOutput
renderDynamicFrame i cache state off f screen = Window.execWindow screen (renderDynamicFrameAsWindow f)
    where
    renderDynamicFrameAsWindow :: Frame -> Window.Window IO ()
    renderDynamicFrameAsWindow (Aspect (aw,ah) a f) = do
        dim <- Window.askDimension
        let adim = Window.largestAspectFitDim aw ah dim
        wo <- lift $ Window.execWindow adim (renderDynamicFrameAsWindow f)
        wo' <- lift $ Window.execWindow adim $ Window.fitWith adim wo
        Window.alignWith adim a wo'
    renderDynamicFrameAsWindow (Zoom x y a f) = do
        (w,h) <- Window.askDimension
        let dim' = (w*x,h*y)
        wo <- lift $ Window.execWindow dim' (renderDynamicFrameAsWindow f)
        Window.alignWith dim' a wo
    renderDynamicFrameAsWindow (Grid xs) = do
        Window.grid (map (map renderDynamicFrameAsWindow) xs)
        return ()
    renderDynamicFrameAsWindow (Frames xs) = mapM_ renderDynamicFrameAsWindow xs
    renderDynamicFrameAsWindow (Stretch Nothing pic) = Window.stretch pic
    renderDynamicFrameAsWindow (Stretch (Just dim) pic) = Window.stretchWith dim pic
    renderDynamicFrameAsWindow (Sized f) = do
        dim <- Window.askDimension
        renderDynamicFrameAsWindow (f dim)
    renderDynamicFrameAsWindow (Label name False f) = Window.addRegionRectangle name >> renderDynamicFrameAsWindow f
    renderDynamicFrameAsWindow (Label name True f) = Cache.cachedRenderFrame i cache renderLabel name f
        where
        renderLabel :: String -> Frame -> Window.Window IO ()
        renderLabel name f = do
            dim <- Window.askDimension
            let screen = dimensionToScreenSize dim
            o <- liftIO $ Raster.getResizeOffscreen off screen
            (Window.WindowOutput pic region) <- lift $ Window.execWindow dim (renderDynamicFrameAsWindow f)
            pic' <- liftIO $ Raster.rasterPicture state o screen pic
            Window.addRegionTransparent name pic'
            Window.tellWindowOutput (Window.WindowOutput (Raster.fromRasteredPicture pic') region)

-- | Creates each cell in a grid depending on the row and column indexes.
grid :: Int -> Int -> (Int -> Int -> Frame) -> Frame
grid ncols nrows mk = Grid $ map (\row -> map (\col -> mk row col) [0..ncols-1]) [0..nrows-1] 

-- | Alignment to the top-left of the frame.
alignTopLeft :: Alignment
alignTopLeft = RelativeAlignment AlignLeft AlignTop

-- | Alignment to the top (and center) of the frame.
alignTop :: Alignment
alignTop = RelativeAlignment AlignCenter AlignTop

-- | Alignment to the top-right of the frame.
alignTopRight :: Alignment
alignTopRight = RelativeAlignment AlignRight AlignTop

-- | Alignment to the left (and middle) the frame.
alignLeft :: Alignment
alignLeft = RelativeAlignment AlignLeft AlignMiddle

-- | Alignment to the center (and middle) the frame.
alignCenter :: Alignment
alignCenter = RelativeAlignment AlignCenter AlignMiddle

-- | Alignment to the right (and middle) the frame.
alignRight :: Alignment
alignRight = RelativeAlignment AlignRight AlignMiddle

-- | Alignment to the bottom-left the frame.
alignBottomLeft :: Alignment
alignBottomLeft = RelativeAlignment AlignLeft AlignBottom

-- | Alignment to the bottom (and middle) the frame.
alignBottom :: Alignment
alignBottom = RelativeAlignment AlignCenter AlignBottom

-- | Alignment to the bottom-right the frame.
alignBottomRight :: Alignment
alignBottomRight = RelativeAlignment AlignRight AlignBottom

-- | Fit picture to the frame, preserving the picture's aspect ratio. Receives a picture alignment inside the frame.
fit
    :: Maybe Dimension -- ^ The explicit dimension of the picture, or inferred if 'Nothing'.
    -> Alignment -- ^ The alignment of the fitted picture to the current frame.
    -> Picture -- ^ The picture to stretch to the frame's dimension.
    -> Frame -- ^ The resulting frame.
fit mbscreen a pic = Aspect screen a (Stretch (Just picdim) pic)
    where
    picdim = Picture.pictureDimension pic
    screen = fromMaybe picdim mbscreen

-- | Draws a picture inside a frame using the original picture dimensions, with no scaling, stretching or fitting.
-- __Warning:__ May naturally lead to misaligned pictures if not used with care.
absolute :: Picture -> Frame
absolute pic = Sized $ \dim -> Stretch (Just dim) pic

-- | Draws a picture inside a frame using the original picture dimensions, with no scaling, stretching or fitting.
-- Receives a function so that the picture can be created depending on the current frame's size.
-- __Warning:__ May naturally lead to misaligned pictures if not used with care.
absoluteSized :: (Dimension -> Picture) -> Frame
absoluteSized fpic = Sized $ \dim -> Stretch (Just dim) (fpic dim)

-- | Paints the borders of the frame with a color.
wire :: Color -> Frame
wire c = Stretch Nothing $ Color c $ rectangleWire 10 10 -- any dimension would work here

-- | Paints the frame with a solid color.
solid :: Color -> Frame
solid c = Stretch Nothing $ Color c $ rectangleSolid 10 10 -- any dimension would work here

-- | Draws a banner, that is, a piece of text fitted inside the frame, with a color.
banner :: String -> Color -> Frame
banner txt c = Sized $ \dim -> 
    let pic = Window.wPic $ Identity.runIdentity $ Window.execWindow dim (Window.text txt)
    in Stretch (Just dim) $ Color c pic

-- | A convex polygon filled with a solid color.
shape
    :: [Point] -- ^ A sequence of points that form the polygon. Differently from Gloss 'polygon', each coordinate of a point @(relx,rely)@ is defined as relative screen width / height percentages between @-0.5@ and @0.5@.
    -> Color -- ^ The fill color.
    -> Frame -- ^ The resulting frame
shape ps c = absoluteSized $ \dim -> Color c $ Polygon $ map (Picture.mulPointwise dim) ps

-- | embedding of Gloss 'polygon' with absolute sizes.
absoluteShape
    :: [Point] 
    -> Color 
    -> Frame 
absoluteShape ps c = absoluteSized $ \dim -> Color c $ Polygon ps

-- | A line connecting a sequence of points, drawn with a color.
stroke
    :: [Point] -- ^ A sequence of points that form the polygon. Differently from Gloss 'polygon', each coordinate of a point @(relx,rely)@ is defined as relative screen width / height percentages between @-0.5@ and @0.5@.
    -> Color -- ^ The fill color.
    -> Frame -- ^ The resulting frame
stroke ps c = absoluteSized $ \dim -> Color c $ Line $ map (Picture.mulPointwise dim) ps

-- | Draws a border around a smaller frame.
bordered
    :: Float -- ^ A fixed thickness in pixels.
    -> Color -- ^ The color for the border.
    -> Frame -- ~ The inner frame, whose screen dimension is smaller by the defined thickness.
    -> Frame -- ^ The resulting frame.
bordered thick c frame = Sized $ \dim@(w,h) ->
    let w' = max 0 (w-thick)
        h' = max 0 (h-thick)
        borderleft = absoluteShape [(-w/2,-h/2),(-w/2,h/2),(-w'/2,h/2),(-w'/2,-h/2)] c
        borderright = absoluteShape [(w/2,-h/2),(w/2,h/2),(w'/2,h/2),(w'/2,-h/2)] c
        bordertop = absoluteShape [(-w/2,h/2),(w/2,h/2),(w/2,h'/2),(-w/2,h'/2)] c
        borderbottom = absoluteShape [(-w/2,-h/2),(w/2,-h/2),(w/2,-h'/2),(-w/2,-h'/2)] c
        borders = [borderleft,borderright,bordertop,borderbottom]
        inner = Zoom (w' / w) (h' / h) alignCenter frame
    in Frames $ borders ++ [inner]

-- | Draws an ellipsis fitting the current frame.
ellipsis :: Color -> Frame
ellipsis c = absoluteSized $ \dim@(w,h) ->
    let (diameter,sx,sy) = if w < h then (w,1,h/w) else (h,w/h,1)
    in Scale sx sy $ Color c $ Circle (diameter/2)

-- | Draws an ellipsis fitting the current frame, with a given thickness.
thickEllipsis :: Color -> Float -> Frame
thickEllipsis c thick = absoluteSized $ \dim@(w,h) ->
    let (diameter,sx,sy) = if w < h then (w,1,h/w) else (h,w/h,1)
    in Scale sx sy $ Color c $ ThickCircle (diameter/2 - thick/2) thick

-- | Draws an ellipsis fitting the current frame, filled with a given color.
solidEllipsis :: Color -> Frame
solidEllipsis c = absoluteSized $ \dim@(w,h) ->
    let (diameter,sx,sy) = if w < h then (w,1,h/w) else (h,w/h,1)
        radius = diameter / 2
    in Scale sx sy $ Color c $ ThickCircle (radius/2) radius

-- | Zoom from the current frame to a smaller frame with a fixed dimension.
fixedZoom :: Dimension -> Alignment -> Frame -> Frame
fixedZoom (fw,fh) a f = Sized $ \(w,h) ->
    let zx = min 1 (fw / w)
        zy = min 1 (fh / h)
    in Zoom zx zy a f