Chapter 7. 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 (DelayedCircles.html), 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.
File DrawCircles.elm: module DrawCircles where import Array as A import Color exposing (Color, blue, brown, green, orange, purple, red, yellow) import Graphics.Collage exposing (Form, circle, collage, filled, move) import Graphics.Element exposing (Element) import List exposing (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. The result type is not a — the type of array elements — but Maybe a, because not all index values are always valid.
> 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
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.
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 one 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: DrawCircles.html.
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 mouse position coordinates should be delayed (the units used are tenths of seconds). The second element of each pair is the delayed mouse position.
File DelayedMousePositions.elm: module DelayedMousePositions where import Graphics.Element exposing (show) import List import List exposing ((::), foldr, length, repeat) import Mouse import Signal import Signal exposing (Signal, (~), (<~), constant) import Time exposing (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 = show <~ 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 for obtaining a delayed signal.
delay : Float -> Signal a -> 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: DelayedMousePositions.html.
The DelayedCircles.elm program combines the delayedMousePositions function 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.
File DelayedCircles.elm: module DelayedCircles where import DelayedMousePositions exposing (delayedMousePositions) import DrawCircles exposing (drawCircles) import Fibonacci exposing (fibonacci) import List exposing (drop, reverse) import Signal exposing ((~), (<~)) import Window main = drawCircles <~ delayedMousePositions (fibonacci 8 |> drop 1 |> 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 an example which maintains state and uses random numbers.