So we've seen how to make various shapes. Next, let's build an application that lets us stamp shapes onto the canvas. Let's get started.

Project

We're going to be collecting a sequence of mouse clicks and placing stamps in those positions. Those will be 2-tuples of Integers ((Int, Int)), so let's start off with a basic application that takes a static list of positions and places stamps in each position.

vim Stamps.elm

We'll add some imports to the top and define our module name:

module Stamps exposing (..)

import Color exposing (..)
import Collage exposing (..)
import Element exposing (..)
import Html exposing (..)

Next, let's define the shape we'll use for our stamp:

-- drawStamp takes a position and return a graphics form
drawStamp : (Int, Int) -> Form
drawStamp (x, y) =
  -- Let's draw a polygon with a radius of 50
  ngon 5 50
  -- Fill it red
  |> filled red
  -- And move it to the appropriate position
  |> move (toFloat(x), toFloat(-1 * y))

OK, so that's what our stamp will look like. Let's make our view, which will be a canvas containing a list of these stamps:

-- We pass a list of positions to the view function, and it returns a collage with
-- stamps in those positions
view : List (Int, Int) -> Element
view positions =
  let
    theGroup =
      -- Map a list of positions through our drawstamp function to get a list
      -- of forms, and put them in a group
      group (List.map drawStamp positions)

    -- We'll move the group to the origin like we did in the previous examples
    originGroup =
      move (-400, 400) theGroup

  in
    -- Now make a collage containing the group
    collage 800 800
      [ originGroup ]

Next, let's make a main function that uses this view:

main : Html msg
main =
  -- We'll just hardcode a list of positions
  view [(0, 0), (100, 100), (200, 100)]
    |> Element.toHtml

That should produce something for us, we'll open it up in elm-reactor.

Alright, let's quickly refactor our list of positions to its own function. We ultimately want to have an item in this list for each click, so we'll call it clicks:

clicks : List (Int, Int)
clicks =
  -- We'll just hardcode a list of positions
  [(0, 0), (100, 100), (200, 100)]


main : Html msg
main =
  view clicks
    |> Element.toHtml

Alright, that reads pretty clearly in terms of what we're setting out to accomplish. Sadly, it can't stay this way, because our main has to become a program and we need to introduce a model. First, we'll turn this into a Program:

-- We need Html.App
import Html.App as Html
-- We also know we're going to use the Mouse
import Mouse


main : Program Never
main =
  Html.program
    { init = (model, Cmd.none)
    , update = update
    , view = view
    , subscriptions = subscriptions
    }


subscriptions : Model -> Sub Msg
subscriptions model =
  Sub.none

Next, we'll add a Model, and a Msg for AddClick Position.

type alias Position =
  (Int, Int)


type alias Model =
  { clicks : List Position
  }


type Msg
  = AddClick Position


model : Model
model =
  { clicks = clicks
  }


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    AddClick pos ->
      { model | clicks = pos :: model.clicks } ! []


-- We'll update the view to use our model
view : Model -> Html Msg
view model =
  let
    theGroup =
      -- Map a list of positions through our drawstamp function to get a list
      -- of forms, and put them in a group
      group (List.map drawStamp model.clicks)

    -- We'll move the group to the origin like we did in the previous examples
    originGroup =
      move (-400, 400) theGroup
  in
    -- Now make a collage containing the group
    collage 800 800
      [ originGroup ]
      |> Element.toHtml

OK, so now we know that we want clicking to add a click to the model. Let's wire up the subscription:

subscriptions : Model -> Sub Msg
subscriptions model =
  Mouse.clicks (\{x, y} -> AddClick (x, y))

We get a record with x and y fields, and we want to turn it into a Msg with a 2-tuple for the x and y coordinates.

Now if you refresh the page, we've built a working stamp application. And we can draw a slightly terrifying face with it.

Summary

In today's episode we saw how to use an Html.App.program to build a Graphics-based application. I hope you enjoyed it. See you soon!

Resources