sdl2-gfx-0.3.0.0: src/SDL/Primitive.hs
{-# LANGUAGE OverloadedStrings #-}
-- |
--
-- Module : SDL.Primitive
-- Copyright : (c) 2015 Siniša Biđin, 2021 Daniel Firth
-- License : MIT
-- Maintainer : dan.firth@homotopic.tech
-- Stability : experimental
--
-- Bindings to @SDL2_gfx@'s primitives drawing functionality. These functions
-- should allow you to render various simple shapes such as lines, ellipses or
-- polygons.
--
-- All of the monadic functions within this module are capable of throwing an
-- 'SDL.Exception.SDLException' if they encounter an error.
module SDL.Primitive
( -- * Pixels
Pos,
Color,
pixel,
-- * Lines
line,
Length,
horizontalLine,
verticalLine,
smoothLine,
Width,
thickLine,
-- * Triangles
triangle,
smoothTriangle,
fillTriangle,
-- * Rectangles
rectangle,
Radius,
roundRectangle,
fillRectangle,
fillRoundRectangle,
-- * Curves
Start,
End,
arc,
circle,
smoothCircle,
fillCircle,
ellipse,
smoothEllipse,
fillEllipse,
pie,
fillPie,
Steps,
bezier,
-- * Polygons
polygon,
smoothPolygon,
fillPolygon,
)
where
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Int (Int16)
import Data.Vector.Storable (Vector, length, unsafeWith)
import Data.Word (Word8)
import Foreign.C.Types (CInt)
import SDL.Internal.Exception (throwIfNeg_)
import SDL.Internal.Types (Renderer (..))
import qualified SDL.Raw.Primitive
import SDL.Vect (V2 (..), V4 (..))
import Prelude hiding (length)
-- | A position as a two-dimensional vector.
type Pos = V2 CInt
-- | A color as an RGBA byte-vector.
type Color = V4 Word8
-- The SDL2_gfx API expects Int16, while SDL2 uses CInt. We could force Int16,
-- but that would cause issues for the end user always having to convert
-- between vector types in order to use both SDL2 and SDL2_gfx. I'm therefore
-- currently opting to accept CInt and convert to Int16, overflows be damned.
cint :: CInt -> Int16
cint = fromIntegral
-- | Renders a single pixel at a given position.
pixel :: MonadIO m => Renderer -> Pos -> Color -> m ()
pixel (Renderer p) (V2 x y) (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.pixel" "pixelRGBA" $
SDL.Raw.Primitive.pixel
p
(cint x)
(cint y)
r
g
b
a
-- | Renders a line between two points.
line :: MonadIO m => Renderer -> Pos -> Pos -> Color -> m ()
line (Renderer p) (V2 x y) (V2 u v) (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.line" "lineRGBA" $
SDL.Raw.Primitive.line
p
(cint x)
(cint y)
(cint u)
(cint v)
r
g
b
a
-- | A width in pixels.
type Width = CInt
-- | Same as 'line', but the rendered line is of a given 'Width'.
thickLine :: MonadIO m => Renderer -> Pos -> Pos -> Width -> Color -> m ()
thickLine (Renderer p) (V2 x y) (V2 u v) w (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.thickLine" "thickLineRGBA" $
SDL.Raw.Primitive.thickLine
p
(cint x)
(cint y)
(cint u)
(cint v)
(cint w)
r
g
b
a
-- | Renders an anti-aliased line between two points.
smoothLine :: MonadIO m => Renderer -> Pos -> Pos -> Color -> m ()
smoothLine (Renderer p) (V2 x y) (V2 u v) (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.smoothLine" "aalineRGBA" $
SDL.Raw.Primitive.aaLine
p
(cint x)
(cint y)
(cint u)
(cint v)
r
g
b
a
-- | A length in pixels.
type Length = CInt
-- | Renders a horizontal line of a certain 'Length', its left and starting
-- point corresponding to a given 'Pos'.
horizontalLine :: MonadIO m => Renderer -> Pos -> Length -> Color -> m ()
horizontalLine (Renderer p) (V2 x y) w (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.horizontalLine" "hlineRGBA" $
SDL.Raw.Primitive.hline
p
(cint x)
(cint $ x + w)
(cint y)
r
g
b
a
-- | Renders a vertical line of a certain 'Length', its top and starting point
-- corresponding to a given 'Pos'.
verticalLine :: MonadIO m => Renderer -> Pos -> Length -> Color -> m ()
verticalLine (Renderer p) (V2 x y) h (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.verticalLine" "vlineRGBA" $
SDL.Raw.Primitive.vline
p
(cint x)
(cint y)
(cint $ y + h)
r
g
b
a
-- | Renders a transparent rectangle spanning two points, bordered by a line of
-- a given 'Color'.
rectangle :: MonadIO m => Renderer -> Pos -> Pos -> Color -> m ()
rectangle (Renderer p) (V2 x y) (V2 u v) (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.rectangle" "rectangleRGBA" $
SDL.Raw.Primitive.rectangle
p
(cint x)
(cint y)
(cint u)
(cint v)
r
g
b
a
-- | A radius in pixels.
type Radius = CInt
-- | Same as 'rectangle', but the rectangle's corners are rounded.
--
-- Control the roundness using the 'Radius' argument, defining the radius of
-- the corner arcs.
roundRectangle :: MonadIO m => Renderer -> Pos -> Pos -> Radius -> Color -> m ()
roundRectangle (Renderer p) (V2 x y) (V2 u v) rad (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.roundRectangle" "roundedRectangleRGBA" $
SDL.Raw.Primitive.roundedRectangle
p
(cint x)
(cint y)
(cint u)
(cint v)
(cint rad)
r
g
b
a
-- | Same as 'rectangle', but the rectangle is filled by the given 'Color'.
fillRectangle :: MonadIO m => Renderer -> Pos -> Pos -> Color -> m ()
fillRectangle (Renderer p) (V2 x y) (V2 u v) (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.fillRectangle" "boxRGBA" $
SDL.Raw.Primitive.box
p
(cint x)
(cint y)
(cint u)
(cint v)
r
g
b
a
-- | Same as 'roundRectangle', but the rectangle is filled by the given 'Color'.
fillRoundRectangle :: MonadIO m => Renderer -> Pos -> Pos -> Radius -> Color -> m ()
fillRoundRectangle (Renderer p) (V2 x y) (V2 u v) rad (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.fillRoundRectangle" "roundedBoxRGBA" $
SDL.Raw.Primitive.roundedBox
p
(cint x)
(cint y)
(cint u)
(cint v)
(cint rad)
r
g
b
a
-- | A starting position in degrees.
type Start = CInt
-- | An ending position in degrees.
type End = CInt
-- | Render an arc, its 'Pos' being its center.
--
-- The 'Start' and 'End' arguments define the starting and ending points of the
-- arc in degrees, zero degrees being south and increasing counterclockwise.
arc :: MonadIO m => Renderer -> Pos -> Radius -> Start -> End -> Color -> m ()
arc (Renderer p) (V2 x y) rad start end (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.arc" "arcRGBA" $
SDL.Raw.Primitive.arc
p
(cint x)
(cint y)
(cint rad)
(cint start)
(cint end)
r
g
b
a
-- | Renders a transparent circle, bordered by a line of a given 'Color'.
circle :: MonadIO m => Renderer -> Pos -> Radius -> Color -> m ()
circle (Renderer p) (V2 x y) rad (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.circle" "circleRGBA" $
SDL.Raw.Primitive.circle
p
(cint x)
(cint y)
(cint rad)
r
g
b
a
-- | Same as 'circle', but fills it with the given 'Color' instead.
fillCircle :: MonadIO m => Renderer -> Pos -> Radius -> Color -> m ()
fillCircle (Renderer p) (V2 x y) rad (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.filledCircle" "filledCircleRGBA" $
SDL.Raw.Primitive.filledCircle
p
(cint x)
(cint y)
(cint rad)
r
g
b
a
-- | Same as 'circle', but the border is anti-aliased.
smoothCircle :: MonadIO m => Renderer -> Pos -> Radius -> Color -> m ()
smoothCircle (Renderer p) (V2 x y) rad (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.aaCircle" "aacircleRGBA" $
SDL.Raw.Primitive.aaCircle
p
(cint x)
(cint y)
(cint rad)
r
g
b
a
-- | Renders a transparent ellipse, bordered by a line of a given 'Color'.
--
-- The 'Radius' arguments are the horizontal and vertical radius of the ellipse
-- respectively, in pixels.
ellipse :: MonadIO m => Renderer -> Pos -> Radius -> Radius -> Color -> m ()
ellipse (Renderer p) (V2 x y) rx ry (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.ellipse" "ellipseRGBA" $
SDL.Raw.Primitive.ellipse
p
(cint x)
(cint y)
(cint rx)
(cint ry)
r
g
b
a
-- | Same as 'ellipse', but makes the border anti-aliased.
smoothEllipse :: MonadIO m => Renderer -> Pos -> Radius -> Radius -> Color -> m ()
smoothEllipse (Renderer p) (V2 x y) rx ry (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.smoothEllipse" "aaellipseRGBA" $
SDL.Raw.Primitive.aaEllipse
p
(cint x)
(cint y)
(cint rx)
(cint ry)
r
g
b
a
-- | Same as 'ellipse', but fills it with the given 'Color' instead.
fillEllipse :: MonadIO m => Renderer -> Pos -> Radius -> Radius -> Color -> m ()
fillEllipse (Renderer p) (V2 x y) rx ry (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.fillEllipse" "filledEllipseRGBA" $
SDL.Raw.Primitive.filledEllipse
p
(cint x)
(cint y)
(cint rx)
(cint ry)
r
g
b
a
-- | Render a pie outline, its 'Pos' being its center.
--
-- The 'Start' and 'End' arguments define the starting and ending points of the
-- pie in degrees, zero degrees being east and increasing counterclockwise.
pie :: MonadIO m => Renderer -> Pos -> Radius -> Start -> End -> Color -> m ()
pie (Renderer p) (V2 x y) rad start end (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.pie" "pieRGBA" $
SDL.Raw.Primitive.pie
p
(cint x)
(cint y)
(cint rad)
(cint start)
(cint end)
r
g
b
a
-- | Same as 'pie', but fills it with the given 'Color' instead.
fillPie :: MonadIO m => Renderer -> Pos -> Radius -> Start -> End -> Color -> m ()
fillPie (Renderer p) (V2 x y) rad start end (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.fillPie" "filledPieRGBA" $
SDL.Raw.Primitive.filledPie
p
(cint x)
(cint y)
(cint rad)
(cint start)
(cint end)
r
g
b
a
-- | How many interpolation steps when rendering a bezier curve?
--
-- The higher this is, the smoother the curve and more resource-intensive the
-- render.
type Steps = CInt
-- | Renders a bezier curve of a given 'Color'.
--
-- The input vectors contain the bezier curve's point locations on the x and
-- y-axis, respectively. The input vectors need to be the same length, and
-- those lengths must be at least 3, otherwise 'bezier' might raise an
-- 'SDL.Exception.SDLException'. The same applies for the number of
-- interpolation 'Steps': it must be at least 2.
bezier :: MonadIO m => Renderer -> Vector Int16 -> Vector Int16 -> Steps -> Color -> m ()
bezier (Renderer p) xs ys steps (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.bezier" "bezierRGBA" $
liftIO
. unsafeWith xs
$ \xs' ->
unsafeWith ys $ \ys' ->
SDL.Raw.Primitive.bezier
p
xs'
ys'
(fromIntegral $ length xs)
steps
r
g
b
a
-- | Render a transparent triangle, its edges being of a given 'Color'.
triangle :: MonadIO m => Renderer -> Pos -> Pos -> Pos -> Color -> m ()
triangle (Renderer p) (V2 x y) (V2 u v) (V2 t z) (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.triangle" "trigonRGBA" $
SDL.Raw.Primitive.trigon
p
(cint x)
(cint y)
(cint u)
(cint v)
(cint t)
(cint z)
r
g
b
a
-- | Same as 'triangle', but the edges are anti-aliased.
smoothTriangle :: MonadIO m => Renderer -> Pos -> Pos -> Pos -> Color -> m ()
smoothTriangle (Renderer p) (V2 x y) (V2 u v) (V2 t z) (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.smoothTriangle" "aatrigonRGBA" $
SDL.Raw.Primitive.aaTrigon
p
(cint x)
(cint y)
(cint u)
(cint v)
(cint t)
(cint z)
r
g
b
a
-- | Same as 'triangle', but the triangle is filled with the given 'Color'
-- instead.
fillTriangle :: MonadIO m => Renderer -> Pos -> Pos -> Pos -> Color -> m ()
fillTriangle (Renderer p) (V2 x y) (V2 u v) (V2 t z) (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.fillTriangle" "filledTrigonRGBA" $
SDL.Raw.Primitive.filledTrigon
p
(cint x)
(cint y)
(cint u)
(cint v)
(cint t)
(cint z)
r
g
b
a
-- | Render a transparent polygon, its edges of a given 'Color'.
--
-- The input vectors contain the points' locations on the x and y-axis,
-- respectively. The input vectors need to be the of the same length, and the
-- lengths must be at least 3, otherwise 'polygon' might raise an
-- 'SDL.Exception.SDLException'.
polygon :: MonadIO m => Renderer -> Vector Int16 -> Vector Int16 -> Color -> m ()
polygon (Renderer p) xs ys (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.polygon" "polygonRGBA" $
liftIO
. unsafeWith xs
$ \xs' ->
unsafeWith ys $ \ys' ->
SDL.Raw.Primitive.polygon
p
xs'
ys'
(fromIntegral $ length xs)
r
g
b
a
-- | Same as 'polygon', but the edges are drawn anti-aliased.
smoothPolygon :: MonadIO m => Renderer -> Vector Int16 -> Vector Int16 -> Color -> m ()
smoothPolygon (Renderer p) xs ys (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.smoothPolygon" "aapolygonRGBA" $
liftIO
. unsafeWith xs
$ \xs' ->
unsafeWith ys $ \ys' ->
SDL.Raw.Primitive.aaPolygon
p
xs'
ys'
(fromIntegral $ length xs)
r
g
b
a
-- | Same as 'polygon', but the polygon is filled with the given 'Color'.
fillPolygon :: MonadIO m => Renderer -> Vector Int16 -> Vector Int16 -> Color -> m ()
fillPolygon (Renderer p) xs ys (V4 r g b a) =
throwIfNeg_ "SDL.Primitive.fillPolygon" "filledPolygonRGBA" $
liftIO
. unsafeWith xs
$ \xs' ->
unsafeWith ys $ \ys' ->
SDL.Raw.Primitive.filledPolygon
p
xs'
ys'
(fromIntegral $ length xs)
r
g
b
a