title |
---|
Delayed Circles |
The next example, DelayedCircles.elm, is a program displaying several colorful circles. The circles follow the mouse pointer, but not immediately. There is a time lag associated with each circle, which is larger for bigger circles. Before continuing, take a look at the working program here, to have an idea of how it works.
The DrawCircles
module defines the drawCircles
function, which
draws the circles given a list of their radiuses.
% DrawCircles.elm module DrawCircles where
import Array as A
import Color (Color, blue, brown, green, orange, purple, red, yellow)
import Graphics.Collage (Form, circle, collage, filled, move)
import Graphics.Element (Element)
import List (map)
import Maybe
color : Int -> Color
color n =
let colors =
A.fromList [ green, red, blue, yellow, brown, purple, orange ]
maybeColor = A.get (n % (A.length colors)) colors
in
Maybe.withDefault green maybeColor
circleForm : (Int, (Int, Int)) -> Form
circleForm (r, (x, y)) =
circle (toFloat r*5)
|> filled (color r)
|> move (toFloat x,toFloat y)
drawCircles : List (Int, (Int, Int)) -> (Int, Int) -> Element
drawCircles d (w, h) = collage w h <| map circleForm d
main =
drawCircles [
(3, (-200, 0)),
(4, (-100, 0)),
(5, (0, 0)),
(7, (100, 0)),
(9, (200, 0))
]
(600, 400)
The color
function takes a number and returns one of the colors from
a predefined list. The argument modulo the length of the list gives
the index of the returned color. In order to retrieve an element from
a given index, the list is transformed into an array using the
fromList
function of the Array
module. The get
function is used
to retrieve the element from the given index.
get : Int -> Array a -> Maybe a
Its first argument is the index, and the second argument is the
array. However, the result type is not a
— the type of array
elements — but Maybe a
. Let’s see what the get
function returns
depending on the value of the index.
> import Array as A
> arr = A.fromList ['a', 'b', 'c', 'd']
Array.fromList ['a','b','c','d'] : Array.Array Char
> A.get 0 arr
Just 'a' : Maybe.Maybe Char
> A.get 9 arr
Nothing : Maybe.Maybe Char
The Maybe
data type is defined in the Maybe
module and it is a so
called union type and it is a union of two distinct cases. It
represents optional values. An existing value is represented by the
Just
case, while a non-existing value by Nothing
. Array elements
are indexed starting with 0, thus 0 is a valid index and the return
value is the first element of our array “wrapped” in Just
. However,
calling get
with the index of 9 returns Nothing
. To get the value
out of the Maybe
type we can use the Maybe.withDefault
function.
> import Maybe (withDefault)
> withDefault
<function> : a -> Maybe.Maybe a -> a
> withDefault 'z' (A.get 0 arr)
'a' : Char
> withDefault 'z' (A.get 9 arr)
'z' : Char
The first argument of withDefault
is a fallback value — to be used
in case the Maybe
value is Nothing
.
The color
function in the DrawCircles
module uses withDefault
to
“unwrap” the color value from the Maybe
value returned by get
, but
the fallback value is never used, since the code always calculate a
correct index value when calling get
.
The circleForm
function returns a Form
representing a circle drawn
according to the data provided in the first argument. The first
argument is provided as a pair (tuple) of values. The first of them
represents the circle radius. The second one is another pair,
representing its position.
The drawCircles
method takes a list of values specifying the circle
radiuses and coordinates and creates an element with the drawn
circles. The second argument represent the dimensions of the element.
The main
function tests the code. You can see its result
here.
The DelayedMousePositions
module defines a signal carrying mouse
positions delayed by certain amounts of time. Each event of the signal
is a list of pairs. The first element of each pair is a number
indicating how much time the of mouse position coordinates should be
delayed (the units used are tenths of seconds). The second element of
each pair is the delayed mouse position.
% DelayedMousePositions.elm module DelayedMousePositions where
import List
import List ((::), foldr, length, repeat)
import Mouse
import Signal
import Signal (Signal, (~), (<~), constant)
import Text (asText)
import Time (delay)
import Window
combine : List (Signal a) -> Signal (List a)
combine = foldr (Signal.map2 (::)) (constant [])
delayedMousePositions : List Int -> Signal (List (Int, (Int, Int)))
delayedMousePositions rs =
let adjust (w, h) (x, y) = (x-w//2,h//2-y)
n = length rs
position = adjust <~ Window.dimensions ~ Mouse.position
positions = repeat n position -- List (Signal (Int, Int))
delayedPositions = -- List (Signal (Int, (Int, Int))
List.map2
(\\r pos ->
let delayedPosition = delay (toFloat r*100) pos
in
(\\pos -> (r,pos)) <~ delayedPosition)
rs
positions
in
combine delayedPositions
main = asText <~ delayedMousePositions [0,10,25]
The delayedMousePositions
function takes a list of values
representing the delays and returns the signal. The following figure
presents how the signal is built by combining and transforming other
signals.
The Window.dimensions
and Mouse.positions
signals are the basic
signals from the standard library, that are transformed into the
output signal.
The first transformation is performed by the position
function,
which returns a signal of the mouse positions represented in a
coordinate system suitable for drawing. The Mouse.position
values
are using the coordinate system with the origin in the top-left corner
and with coordinates increasing to the right and downwards. The
position
signal values use the center of the screen as the
origin. The coordinate values increase as mouse pointer moves to the
right and upwards.
The positions
function returns the position
signal repeated n
times, where n
is the length of the input list. Thus the positions
function returns a list of type List (Signal (Int,Int))
.
The delayedPositions
returns a list of signals, each of which
carries pairs of values — the function return type is [Signal (Int,(Int,Int))]
. The first value of the pair is one of the values
from the list provided as the input argument. The second value of the
pair is a pair of mouse pointer coordinates delayed by a given amount
of time. The delay
function from the Time
module is used to obtain
a delayed signal.
> delay
<function: delay> : Float -> Signal.Signal a -> Signal.Signal a
The returned signal is produced using the combine
function, which
turns a list of signals into a signal of a list of values.
Again, the main
function is used for testing. You can see its result
here.
The DelayedCircles.elm program combines the
delayedMousePositions
with the drawCircles
function. The outcome
is a series of circles, each of which follow the mouse pointer, but
with a time lag proportional to the size of the circle.
% DelayedCircles.elm module DelayedCircles where
import DelayedMousePositions (delayedMousePositions)
import DrawCircles (drawCircles)
import Fibonacci (fibonacci)
import List (reverse, tail)
import Signal ((~), (<~))
import Window
main =
drawCircles
<~ delayedMousePositions (fibonacci 8 |> tail |> reverse)
~ Window.dimensions
The sizes of the circles are calculated by the fibonacci
function
from the Fibonacci
module described in Chapter
2.
The next chapter presents signals used for generating random numbers.