Skip to content

Latest commit

 

History

History
204 lines (152 loc) · 7.52 KB

03__Mouse_Signals.md

File metadata and controls

204 lines (152 loc) · 7.52 KB
title
Mouse Signals

Our programs written so far were all static — they were just web pages that did nothing once displayed. We are going to change that and make pages dynamic by reacting to mouse events. Elm handles mouse events by means of so called signals. Before describing what signals are, let’s see them in action. Take a look at the MouseSignals1.elm program, presented below. A working example is available here.

% MouseSignals1.elm module MouseSignals1 where

  import Mouse
  import Signal (map)
  import Text (asText)


  main = map asText Mouse.x

Try running that program and notice what happens as you move your mouse pointer. The program shows the x coordinate of the mouse pointer. The value changes as the pointer changes its position. How does the program do that? The Mouse.x expression represents a signal of mouse pointer x coordinates. A signal is a stream of values that change over time. As the mouse cursor changes its position, the signal value representing its coordinate changes as well. A signal is always defined. It always has a value. The initial value of that particular signal is 0 — you can notice that by observing the initial value shown by the program just after it is started, but before the mouse pointer moves. Once you move the mouse pointer, the x coordinate changes as the pointer moves left or right. If you stop moving the mouse, the signal “remembers” the last value.

The type of Mouse.x is Signal Int. That type indicates that it is a signal of Int values. We cannot show it directly on the screen, that is we cannot write main = Mouse.x, because such program would not compile. All our programs so far assigned to main values of type Element, but it is not the only possible type of the main function. Another possible type is Signal Element. In other words Elm can display a dynamic signal of elements.

We can use the asText function to turn an Int into an Element. But what we need is to turn a Signal Int into a Signal Element. How can we do that? By using the map function from the Signal module! Here is its signature:

  map : (a -> b) -> Signal a -> Signal b

It takes a function and a signal, and applies the function to the values “carried” by the signal. In other words, it applies the function “inside” the signal, turning a signal of some values of type a into a signal of values of type b.

Elm allows using functions as operators, that is in the infix notation, by enclosing them in backsticks. We could thus define the main function in an alternative way as follows:

  main = asText `map` Mouse.x

However, Elm provides already the <~ operator that is equivalent to the map function. Thus, we can also write (provided we also import the <~ operator from the Signal module):

  main = asText <~ Mouse.x

The next example — MouseSignals2.elm — shows two signals combined together and displayed as a pair of mouse pointer coordinates. You can see it in action here.

% MouseSignals2.elm module MouseSignals2 where

  import Graphics.Element (Element)
  import Mouse
  import Signal (map2)
  import Text (asText)


  combine : a -> b -> Element
  combine a b = asText (a,b)


  main = map2 combine Mouse.x Mouse.y

The map function is not enough when we want to combine two signals into one. Luckily Elm provides the map2 function that let us do that. It has the following signature:

  map2 : (a -> b -> c) -> Signal a -> Signal b -> Signal c

Our program merges the Mouse.x and Mouse.y (which obviously represents the mouse pointer y coordinates) signals using the map2 function. Elm also provides other similar functions: map3, map4 and map5. However, it also provides an alternative way of combining several signals into one. The last line of our program could have been written as follows (again, importing ~ would be necessary):

  main = combine <~ Mouse.x ~ Mouse.y

What is going on here? We already know the <~ operator, which is equivalent to the map function. The ~ operator has the following signature:

  (~) : Signal (a -> b) -> Signal a -> Signal b

Given a signal of functions from a to b and a signal of a values, the operator returns a signal of b values. How is that useful, you might ask? Let’s analyze our definition of the main function. The combine function has the following signature:

  combine : a -> b -> Element

It is a two-argument function. The <~ operator “uses” its first argument, leaving the second one unaffected. Therefore, combine <~ Mouse.x expression has the type Signal (b -> Element). The ~ operator takes that value, and the signal of b values and returns a Signal Element, which can be assigned to main.

To complete our presentation of mouse-related signals, let’s take a look at yet another program — MouseSignals3.elm (the working program is available here):

% MouseSignals3.elm module MouseSignals3 where

  import Graphics.Element (down, flow)
  import List (map)
  import Mouse
  import Signal ((~), (<~), sampleOn)
  import Text (plainText)


  showsignals a b c d e f g =
      flow
          down
          <|
              map
                  plainText
                  [
                      "Mouse.position: " ++ toString a,
                      "Mouse.x: " ++ toString b,
                      "Mouse.y: " ++ toString c,
                      "Mouse.clicks: " ++ toString d,
                      "Mouse.isDown: " ++ toString e,
                      "sampleOn Mouse.clicks Mouse.position: " ++ toString f,
                      "sampleOn Mouse.isDown Mouse.position: " ++ toString g
                  ]


  main =
      showsignals
          <~ Mouse.position
          ~ Mouse.x
          ~ Mouse.y
          ~ Mouse.clicks
          ~ Mouse.isDown
          ~ sampleOn Mouse.clicks Mouse.position
          ~ sampleOn Mouse.isDown Mouse.position

The showsignals function presents a list of several values with descriptions. Each item on that list represents a signal. Signal values are “feeded” into the function using the <~ and ~ operators.

The toString function used as the argument of the map function, converts any value to a String.

  toString : a -> String

The first signal, Mouse.position, represents the mouse pointer coordinates as a pair of values. We have already seen the Mouse.x and Mouse.y signals.

The Mouse.clicks() signal is a signal of () values. The () value is both a type and the only value of that type. It is called unit.

  > ()
  () : ()

The Mouse.clicks signal generates a new event for every mouse click (to be more precise, the event corresponds to the moment of when the mouse button is released).

The Mouse.isDown signal is a signal of boolean values indicating whether the mouse button is being pressed.

The last two signals use the sampleOn function, which samples the second signal whenever the first signal changes its value.

  sampleOn : Signal a -> Signal b -> Signal b

Thus the last but one signal is a stream of mouse positions from the moments of when the mouse button was released, while the last signal represents mouse positions from the moments of both when the mouse button was pressed and relesed.

The next chapter presents signals defined in the Window module.