Skip to content

Latest commit

 

History

History
256 lines (202 loc) · 9.29 KB

07__Delayed_Circles.md

File metadata and controls

256 lines (202 loc) · 9.29 KB
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.