packages feed

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

{-# LANGUAGE ViewPatterns #-}

module Graphics.Gloss.Relative.Internal.Frame where

import Graphics.Gloss
import Graphics.Gloss.Relative.Internal.Dimension
import qualified Graphics.Gloss.Relative.Internal.Picture as Relative
import qualified Graphics.Gloss.Relative.Internal.Window as Relative
import Graphics.Gloss.Relative.Internal.Window (Alignment(..), HorizontalAlignment(..), VerticalAlignment(..))

import Control.Monad
import Data.Maybe

-- | A picture frame. Much like the original 'Picture' data type, but the purpose is to place and adjust pictures inside a frame with general dimensions.
data Frame
    -- | Create the largest possible frame with a desired aspect ratio within the current frame.
    = Aspect
        { aspectRatio :: Dimension -- ^ Aspect ratio dimensions.
        , aspectAlign :: Relative.Alignment -- ^ Alignment inside the parent frame.
        , aspectChild :: Frame -- ^ Child frame.
        } 
    -- | Zoom the current frame by given factors, producing a smaller frame.
    | Zoom
        { zoomX :: Float -- ^ Horizontal scale (percentage between 0 and 1).
        , zoomY :: Float -- ^ Vertical scale (percentage between 0 and 1).
        , zoomAlignment :: Relative.Alignment -- ^ Alignment inside the parent frame.
        , zoomChild :: Frame -- ^ Child frame.
        }
    -- | Split the current frame into a grid with the given numbers of rows and columns. Receives a matrix of frames, represented as a list of rows.
    | Grid [[Frame]]
    -- | Labels a frame region, to use in mouse events.
    | Label
        { labelName :: String -- ^ The label for the frame region. Does not need to be unique.
        , labelChild :: Frame -- ^ Current frame.
        }
    -- | Overlay a sequence of frames.
    | Frames [Frame]
    -- | Stretch picture to fill the frame, not preserving the picture's aspect ratio. If you want to preserve the aspect ratio, consider using 'fit' instead.
    | Stretch
        { stretchDimension :: Maybe Dimension -- ^ The explicit dimension of the picture, or inferred if 'Nothing'.
        , stretchPicture :: Picture -- ^ The picture to stretch to the frame's dimension.
        }
    -- | Advanced contructor. In case you need to know the exact screen size for a frame.
    | Sized (Dimension -> Frame)

-- | Renders a frame into a picture. 
renderStaticFrame
    :: Frame -- ^ The frame to render. __Note:__ This function ignores frame labels.
    -> Dimension -- ^ The dimension of the screen in which to render the frame.
    -> Picture -- ^ The resulting picture.
renderStaticFrame f screen = fst $ Relative.execWindow screen (renderFrameAsWindow f)

renderDynamicFrame :: Frame -> Dimension -> Relative.WindowOutput
renderDynamicFrame f screen = Relative.execWindow screen (renderFrameAsWindow f)

renderFrameAsWindow :: Frame -> Relative.Window ()
renderFrameAsWindow (Aspect (aw,ah) a f) = do
    dim <- Relative.askDimension
    let adim = Relative.largestAspectFit aw ah dim
    let wo = Relative.execWindow adim (renderFrameAsWindow f)
    let wo' = Relative.execWindow adim $ Relative.fitWith adim wo
    Relative.alignWith adim a wo'
renderFrameAsWindow (Zoom x y a f) = do
    (w,h) <- Relative.askDimension
    let dim' = (w*x,h*y)
    let wo = Relative.execWindow dim' (renderFrameAsWindow f)
    Relative.alignWith dim' a wo
renderFrameAsWindow (Grid xs) = do
    Relative.grid (map (map renderFrameAsWindow) xs)
    return ()
renderFrameAsWindow (Frames xs) = mapM_ renderFrameAsWindow xs
renderFrameAsWindow (Stretch Nothing pic) = Relative.stretch pic
renderFrameAsWindow (Stretch (Just dim) pic) = Relative.stretchWith dim pic
renderFrameAsWindow (Sized f) = do
    dim <- Relative.askDimension
    renderFrameAsWindow (f dim)
renderFrameAsWindow (Label name f) = do
    Relative.addRegion name
    renderFrameAsWindow f

-- | 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 = Alignment AlignLeft AlignTop

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

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

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

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

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

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

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

-- | Alignment to the bottom-right the frame.
alignBottomRight :: Alignment
alignBottomRight = Alignment 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'.
    -> Relative.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 = Relative.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 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 -> fit Nothing alignCenter $ Color c $ fst $ Relative.execWindow dim (Relative.text txt)

-- | 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 (Relative.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 (Relative.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]