helm-1.0.0: src/Helm/Graphics2D.hs
-- | Contains all the types and functions for composing
-- and rendering 2D graphics.
module Helm.Graphics2D
(
-- * Types
Collage(..)
, Form(..)
, FormStyle(..)
, FillStyle(..)
, LineCap(..)
, LineJoin(..)
, LineStyle(..)
, Path(..)
, Shape(..)
, ShapeStyle(..)
, Transform(..)
, Text(..)
-- * Collages
, collage
, clip
, center
, toForm
-- * Styles & Forms
, defaultLine
, solid
, dashed
, dotted
, filled
, textured
, gradient
, outlined
, traced
, image
, fittedImage
, croppedImage
, blank
, alpha
, text
-- * Grouping
, group
, groupTransform
-- * Transforming
, rotate
, scale
, move
-- * Paths
, path
-- * Shapes
, polygon
, rect
, square
, oval
, circle
, ngon
) where
import Linear.V2 (V2(V2))
import Helm.Asset (Image)
import Helm.Color (Color, rgb, Gradient)
import Helm.Graphics2D.Text (Text(..))
import Helm.Graphics2D.Transform (Transform(..), identity)
-- | Represents a collection of forms, which in turn are rendereable
-- shapes and lines. In Helm, the collage is the main structure
-- representing 2D graphics and is passed directly to the engine
-- to be rendered by your view function. It's best to think of a collage
-- as a fancy version of a game screen, with the difference being that the
-- collage itself knows nothing about the window state. It only knows
-- what will be rendered to the screen (which in this case, is a series of forms)
-- and the order in which they will be rendered.
data Collage e = Collage
{ collageDims :: Maybe (V2 Double) -- ^ The optional dimensions of the collage. It will be clipped to these dims.
, collageForms :: [Form e] -- ^ The collection of forms under the collage.
, collageCenter :: Maybe (V2 Double) -- ^ The optional center of the collage.
}
-- | Create a collage from a list of forms.
-- By default, the collage will not be clipped
-- and will not be centered. The origin point of the contained
-- forms will be the top-left of the collage (which in the case of rendering
-- a collage to the screen, is coincidently the top-left of the game window).
-- See 'center' and 'clip'.
collage :: [Form e] -> Collage e
collage forms = Collage
{ collageDims = Nothing
, collageForms = forms
, collageCenter = Nothing
}
-- | Center a collage around a fixed point. This is useful to implement
-- 2D game cameras - usually, you have the center of the screen
-- at the position of the game camera (which in a 2D platformer,
-- is usually your game character). Note that this will center
-- the forms themselves, i.e. their original point will change from being
-- the top left of the collage to
center ::
V2 Double -- ^ The position to center the collage at.
-> Collage e -- ^ The source collage.
-> Collage e -- ^ The centered collage.
center pos col = col { collageCenter = Just pos }
-- | Clip a collage by provided dimensions. Note that by default,
-- a collage will not be clipped and anything beyond the window dimensions
-- will still technically be rendered (although obviously it will not appear
-- on the game screen). By composing a collage with this function,
-- when the collage is rendered its contents will be clipped by these dimensions.
-- Not only will this generally speed up performance, it can be used for certain
-- cases where you don't want the forms in the collage to spill over
-- to other collages near it (but that's a very rare use-case).
--
-- Something to note, that this is absolutely not an ensurance that your 2D graphics
-- will be rendered quickly if you're doing a lot of graphics work. The clip merely
-- prevents things being drawn outside the dimensions, which in most cases will
-- indeed speed up the performance, but it is down to the engine implementation for how much
-- this actually helps.
--
-- In that sense, it's up to the library user to make sure they're not rendering huge amounts
-- of forms that aren't even in the screen's bounds.
clip ::
V2 Double -- ^ The dimensions to clip the collage with.
-> Collage e -- ^ The source collage.
-> Collage e -- ^ The clipped collage.
clip dims col = col { collageDims = Just dims }
-- | Create a form from a collage. This might seem a little strange (as
-- a collage is generally what you provide to the engine to render the 2D graphics)
-- but by allowing this functionality, you can compose collages from other collages.
toForm :: Collage e -> Form e
toForm = defaultForm . CollageForm
-- | Represents the styles of forms available. The form style holds data specific
-- to a variation of form, and the 'Form' is instead a general version of this
-- with positioning information, rotation, scale, etc.
data FormStyle e
= PathForm LineStyle Path -- ^ A form composed of a path
| ShapeForm (ShapeStyle e) Shape -- ^ A form composed of a shape.
| TextForm Text -- ^ A form composed of a piece of text, including string and style info.
| ImageForm (Image e) (V2 Double) (V2 Double) Bool -- ^ A form composed of an image
| GroupForm Transform [Form e] -- ^ A form composed of a group of forms, with a transformation.
| CollageForm (Collage e) -- ^ A form composed of a collage (which in turn is a collection of forms).
-- | Represents something that can be rendered to the screen (
-- contained under a collage). There are many different types of forms, which can be composed
-- below but are generally represented by the 'FormStyle' type.
--
-- A form might be an image, or a rectangle, or a circle, or even a collection
-- of forms (which in turn can be those same things).
data Form e = Form
{ formTheta :: Double -- ^ The rotation of the form (in radians).
, formScale :: Double -- ^ The scale factor of the form.
, formPos :: V2 Double -- ^ The position of the form. This will be rendered relative to the collage origin.
, formAlpha :: Double -- ^ The alpha channel of the form.
, formStyle :: FormStyle e -- ^ The style of form.
}
-- | Represents the style of shape filling available.
data FillStyle e
= Solid Color -- ^ The shape will be filled with a solid color.
| Texture (Image e) -- ^ The shape will be filled with a texture (a.k.a. image).
| Gradient Gradient -- ^ The shape will be filled with a gradient (which can be linear or radial).
-- | Represents the shape of the ends of a line.
data LineCap
= FlatCap
| RoundCap
| PaddedCap
deriving (Show, Eq, Ord, Read)
-- | Represents the shape of the joints between line segments.
data LineJoin
= SmoothJoin
| SharpJoin Double
| ClippedJoin
deriving (Show, Eq, Ord, Read)
-- | Represents the style used for drawing lines. It's best
-- to use 'defaultLine' and then only change the fields
-- you need to.
data LineStyle = LineStyle
{ lineColor :: Color
, lineWidth :: Double
, lineCap :: LineCap
, lineJoin :: LineJoin
, lineDashing :: [Double]
, lineDashOffset :: Double
} deriving (Show, Eq)
-- | Represents a series of 2D points which will be drawn in sequence.
-- Like a 'Shape', a path on its own holds no styling information.
data Path = Path [V2 Double] deriving (Show, Eq, Ord, Read)
-- | Create a path from a sequence of points (represented as 2D vectors).
path :: [V2 Double] -> Path
path = Path
-- | Represents a collection of points that when drawn in order
-- will result in a closed polygon. They have no style information -
-- rather, you compose shapes into a form with fill or line style
-- and that affects their appearance.
--
-- Note that realistically, a shape could be represented as
-- a path only. However, we add extra variants here as drawing backends
-- usually provide optimized forms of drawing circles (and perhaps rectangles,
-- although that is less likely) hence it's better to fall to those
-- if our shape is circular.
data Shape
= PolygonShape Path
| RectangleShape (V2 Double)
| ArcShape (V2 Double) Double Double Double (V2 Double)
deriving (Show, Eq, Ord, Read)
-- | Create an arbitary-sided polygon from a path.
-- The points provided should refer to each corner of the -gon,
-- however the points do not need to loop around (i.e. the final point
-- will automatically connect to the first point).
polygon :: Path -> Shape
polygon = PolygonShape
-- | Create a rectangular shape from a 2D vector, with
-- x and y representing width and height, respectively.
rect :: V2 Double -> Shape
rect = RectangleShape
-- | Create a square shape with a side length.
square :: Double -> Shape
square n = rect (V2 n n)
-- | Create an oval shape with a width and height.
oval :: Double -> Double -> Shape
oval w h = ArcShape (V2 0 0) 0 (2 * pi) 1 (V2 (w / 2) (h / 2))
-- | Create a circle shape with a radius.
circle :: Double -> Shape
circle r = ArcShape (V2 0 0) 0 (2 * pi) r (V2 1 1)
-- | Create a generic n-sided polygon (e.g. octagon, pentagon, etc) with
-- a side count and radius.
ngon :: Int -> Double -> Shape
ngon n r = polygon $ path $ map point series
where
point i = V2 (r * cos (t * i)) (r * sin (t * i))
series = [0 .. fromIntegral (n - 1)]
m = fromIntegral n
t = 2 * pi / m
-- | Create the default line style. By default, the line is black with a width of 1,
-- flat caps and regular sharp joints.
defaultLine :: LineStyle
defaultLine = LineStyle
{ lineColor = rgb 0 0 0
, lineWidth = 1
, lineCap = FlatCap
, lineJoin = SharpJoin 10
, lineDashing = []
, lineDashOffset = 0
}
-- | Create a initial form from a specific form style.
-- The form will be at the origin point (0, 0).
defaultForm :: FormStyle e -> Form e
defaultForm style = Form
{ formTheta = 0
, formScale = 1
, formPos = V2 0 0
, formAlpha = 1
, formStyle = style
}
-- | Represents the style used for drawing a shape.
data ShapeStyle e
= OutlinedShape LineStyle -- ^ Stroke/outline the shape, with a specific line style.
| FilledShape (FillStyle e) -- ^ Fill the shape, with a specific fill style.
-- | Create a solid line style with a color.
solid :: Color -> LineStyle
solid color = defaultLine { lineColor = color }
-- | Create a dashed line style with a color.
dashed :: Color -> LineStyle
dashed color = defaultLine { lineColor = color, lineDashing = [8, 4] }
-- | Create a dotted line style with a color.
dotted :: Color -> LineStyle
dotted color = defaultLine { lineColor = color, lineDashing = [3, 3] }
-- | Fill a shape with a specific fill style.
fill :: FillStyle e -> Shape -> Form e
fill style shape = defaultForm (ShapeForm (FilledShape style) shape)
-- | Fill a shape with a color.
filled :: Color -> Shape -> Form e
filled color = fill (Solid color)
-- | Fill a shape with a texture. The texture should
-- be an image loaded by the engine.
textured :: Image e -> Shape -> Form e
textured img = fill (Texture img)
-- | Fill a shape with a gradient (either 'linear' or 'radial').
gradient :: Gradient -> Shape -> Form e
gradient grad = fill (Gradient grad)
-- | Create a form from a shape by outlining it with a specific line style.
outlined :: LineStyle -> Shape -> Form e
outlined style shape = defaultForm (ShapeForm (OutlinedShape style) shape)
-- | Create a form from a path by tracing it with a specific line style.
traced :: LineStyle -> Path -> Form e
traced style p = defaultForm (PathForm style p)
-- | Create an empty form, useful for having forms rendered only at some state.
blank :: Form e
blank = group []
-- | Create a form from an image. If the image dimensions are not the
-- same as provided, then it will stretch/shrink to fit.
image :: V2 Double -> Image e -> Form e
image dims img = defaultForm $ ImageForm img (V2 0 0) dims True
-- | Create a form from an image with a 2D vector describing its dimensions.
-- If the image dimensions are not the same as given, then it will only use the relevant pixels
-- (i.e. cut out the given dimensions instead of scaling). If the given dimensions are bigger than
-- the actual image, than irrelevant pixels are ignored.
fittedImage :: V2 Double -> Image e -> Form e
fittedImage dims img = defaultForm $ ImageForm img (V2 0 0) dims False
-- | Create a form from an image by cropping it with a certain position, width, height
-- and image file path. This can be used to divide a single image up into smaller ones (
-- for example, drawing a single sprite from a sprite sheet).
croppedImage :: V2 Double -> V2 Double -> Image e -> Form e
croppedImage pos dims img = defaultForm $ ImageForm img pos dims False
-- | Group a list of forms into one. They will be drawn in their
-- sequential order within the list.
group :: [Form e] -> Form e
group forms = defaultForm (GroupForm identity forms)
-- | Group a list of forms into one, while also applying a matrix
-- transformation.
groupTransform :: Transform -> [Form e] -> Form e
groupTransform matrix forms = defaultForm (GroupForm matrix forms)
-- | Move a form by a given 2D vector. The movement is relative,
-- i.e. the translation vector provided will be added to the form's
-- current position.
move :: V2 Double -> Form e -> Form e
move trans form = form { formPos = formPos form + trans }
-- | Scale a form by a scalar factor. Scaling by 2 will double the size
-- of the form, and scaling by 0.5 will half the size. Note that like
-- 'move', the scale function is relative - i.e. if you scaled by 0.5
-- and then scaled by 0.5 a gain, the final scale would be 0.25 or
-- a quarter of the form's initial scale.
scale :: Double -> Form e -> Form e
scale factor form = form { formScale = factor * formScale form }
-- | Rotate a form by a given angle (in radians).
-- Like 'move' and 'scale', the rotation is relative.
rotate :: Double -> Form e -> Form e
rotate theta form = form { formTheta = formTheta form + theta }
-- | Change the alpha value of a form (i.e. its transparency).
-- By default, forms will have an alpha value of 1, in other words,
-- they are fully opaque. Alternatively, a value of 0 will mean the
-- form is completely hidden.
alpha :: Double -> Form e -> Form e
alpha x form = form { formAlpha = x }
-- | Create a form from a `Text` structure, which in turn
-- contains all of the text values and styling. This allows
-- you to render a the text graphically (and in turn it's a regular old
-- form, so it can be translated, rotated, etc.).
text :: Text -> Form e
text = defaultForm . TextForm