elm/libraries/Graphics/Collage.elm
2013-06-13 12:00:17 -07:00

213 lines
6.4 KiB
Elm

module Graphics.Collage where
import List
import Native.Utils (toFloat)
import Either
import Native.Matrix2D as Matrix
import Native.Graphics.Collage as N
import Graphics.Element
import Color
type Form = {
theta : Float,
scale : Float,
x : Float,
y : Float,
alpha : Float,
form : BasicForm
}
data FillStyle
= Solid Color
| Texture String
| Gradient Gradient
-- The shape of the ends of a line.
data LineCap = Flat | Round | Padded
-- The shape of the “joints” of a line, where each line segment
-- meets. `Sharp` takes an argument to limit the length of the joint. This
-- defaults to 10.
data LineJoin = Smooth | Sharp Float | Clipped
-- All of the attributes of a line style. This lets you build up a line style
-- however you want. You can also update existing line styles with record updates.
type LineStyle = {
color : Color,
width : Float,
cap : LineCap,
join : LineJoin,
dashing : [Int],
dashOffset : Int
}
-- The default line style, which is solid black with flat caps and sharp joints.
-- You can use record updates to build the line style you
-- want. For example, to make a thicker line, you could say:
--
-- { defaultLine | width <- 10 }
defaultLine : LineStyle
defaultLine = {
color = Color.black,
width = 1,
cap = Flat,
join = Sharp 10,
dashing = [],
dashOffset = 0
}
-- Create a solid line style with a given color.
solid : Color -> LineStyle
solid clr = { defaultLine | color <- clr }
-- Create a dashed line style with a given color. Dashing equals `[8,4]`.
dashed : Color -> LineStyle
dashed clr = { defaultLine | color <- clr, dashing <- [8,4] }
-- Create a dotted line style with a given color. Dashing equals `[3,3]`.
dotted : Color -> LineStyle
dotted clr = { defaultLine | color <- clr, dashing <- [3,3] }
data BasicForm
= FPath LineStyle Path
| FShape (Either LineStyle FillStyle) Shape
| FImage Int Int (Int,Int) String
| FElement Element
| FGroup Matrix2D [Form]
form : BasicForm -> Form
form f = { theta=0, scale=1, x=0, y=0, alpha=1, form=f }
fill style shape = form (FShape (Either.Right style) shape)
-- Create a filled in shape.
filled : Color -> Shape -> Form
filled color shape = fill (Solid color) shape
-- Create a textured shape. The texture is described by some url and is
-- tiled to fill the entire shape.
textured : String -> Shape -> Form
textured src shape = fill (Texture src) shape
-- Fill a shape with a [gradient](/docs/Color.elm#linear).
gradient : Gradient -> Shape -> Form
gradient grad shape = fill (Gradient grad) shape
-- Outline a shape with a given line style.
outlined : LineStyle -> Shape -> Form
outlined style shape = form (FShape (Either.Left style) shape)
-- Trace a path with a given line style.
traced : LineStyle -> Path -> Form
traced style path = form (FPath style path)
-- Create a sprite from a sprite sheet. It cuts out a rectangle
-- at a given position.
sprite : Int -> Int -> (Int,Int) -> String -> Form
sprite w h pos src = form (FImage w h pos src)
-- Turn any `Element` into a `Form`. This lets you use text, gifs, and video
-- in your collage. This means you can move, rotate, and scale
-- an `Element` however you want.
toForm : Element -> Form
toForm e = form (FElement e)
-- Flatten many forms into a single `Form`. This lets you move and rotate them
-- as a single unit, making it possible to build small, modular components.
group : [Form] -> Form
group fs = form (FGroup Matrix.identity fs)
-- Flatten many forms into a single `Form` and then apply a matrix
-- transformation.
groupTransform : Matrix2D -> [Form] -> Form
groupTransform matrix fs = form (FGroup matrix fs)
-- Rotate a form by a given angle. Rotate takes standard Elm angles (radians)
-- and turns things counterclockwise. So to turn `form` 30&deg; to the left
-- you would say, `(rotate (degrees 30) form)`.
rotate : Float -> Form -> Form
rotate t f = { f | theta <- f.theta + t }
-- Scale a form by a given factor. Scaling by 2 doubles the size.
scale : Float -> Form -> Form
scale s f = { f | scale <- f.scale * s }
-- Move a form by the given amount. This is a relative translation so
-- `(move (10,10) form)` would move `form` ten pixels up and ten pixels to the
-- right.
move : (Float,Float) -> Form -> Form
move (x,y) f = { f | x <- f.x + x, y <- f.y + y }
-- Move a shape in the x direction. This is relative so `(moveX 10 form)` moves
-- `form` 10 pixels to the right.
moveX : Float -> Form -> Form
moveX x f = { f | x <- f.x + x }
-- Move a shape in the y direction. This is relative so `(moveY 10 form)` moves
-- `form` upwards by 10 pixels.
moveY : Float -> Form -> Form
moveY y f = { f | y <- f.y + y }
-- Set the opacity of a `Form`. The default is 1, and 0 is totally transparent.
opacity : Float -> Form -> Form
opacity a f = { f | alpha <- a }
-- A collage is a collection of 2D forms. There are no strict positioning
-- relationships between forms, so you are free to do all kinds of 2D graphics.
collage : Int -> Int -> [Form] -> Element
type Path = [(Float,Float)]
-- Create a path that follows a sequence of points.
path : [(Number a,Number a)] -> Path
path ps = ps
-- Create a path along a given line segment.
segment : (Number a,Number a) -> (Number a,Number a) -> Path
segment p1 p2 = [p1,p2]
type Shape = [(Float,Float)]
-- Create an arbitrary polygon by specifying its corners in order.
-- `polygon` will automatically close all shapes, so the given list
-- of points does not need to start and end with the same position.
polygon : [(Number a,Number a)] -> Shape
polygon points = points
-- A rectangle with a given width and height.
rect : Number a -> Number a -> Shape
rect w h = let hw = w/2
hh = h/2
in [ (0-hw,0-hh), (0-hw,hh), (hw,hh), (hw,0-hh) ]
-- A square with a given edge length.
square : Number a -> Shape
square n = rect n n
-- An oval with a given width and height.
oval : Number a -> Number a -> Shape
oval w h =
let n = 50
t = 2 * Math.PI / n
hw = w/2
hh = h/2
f i = (hw * Math.cos (t*i), hh * Math.sin (t*i))
in List.map f [0..n-1]
-- A circle with a given radius.
circle : Number a -> Shape
circle r = oval (2*r) (2*r)
-- A regular polygon with N sides. The first argument specifies the number
-- of sides and the second is the radius. So to create a pentagon with radius
-- 30 you would say:
--
-- ngon 5 30
ngon : Int -> Number a -> Shape
ngon n r =
let m = toFloat n
t = 2 * Math.PI / m
f i = ( r * Math.cos (t*i), r * Math.sin (t*i) )
in List.map f [0..n-1]