summaryrefslogtreecommitdiff
path: root/book/src/main/scalatex/book/handson/CanvasApp.scalatex
blob: ae97dee0b630a4b871d67879032cd1be8398698d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
@import BookData._
@val canvasapp = wd/'examples/'demos/'src/'main/'scala/'canvasapp
@p
  By this point, you've already cloned and got your hands dirty fiddling around with the toy @lnk("workbench-example-app", "https://github.com/lihaoyi/workbench-example-app"). You have your editor set up, SBT installed, and have published the example application in a way you can host online for other people to see. Maybe you've even made some changes to the application to see what happens. Hopefully you're curious, and want to learn more.

@p
  In this section of the book, we will walk through making a small canvas application. This will expose you to important concepts like:

@ul
  @li
    Taking input with Javascript event handlers
  @li
    Writing your application logic in Scala
  @li
    Using a timer to drive periodic actions

@p
  In general, while the previous chapter was mostly set-up and exploring the Scala.js project, this chapter will walk you through actually writing a non-trivial, self-contained Scala.js application. Throughout this chapter, we will only be making modifications to @code{ScalaJSExample.scala}; the rest of the project will remain unchanged.

@sect{Making a Sketchpad using Mouse Input}

  @p
    To begin with, lets remove all the existing stuff in our @code{.scala} file and leave only the @hl.scala{object} and the @hl.scala{main} method. Let's start off with some necessary boilerplate:

  @hl.ref(canvasapp/"ScratchPad.scala", "/*setup*/", end = "/*code*/")

  @p
    As described earlier, this code uses the @lnk.dom.getElementById function to fish out the @code{canvas} element that we interested in from the DOM. It then gets a rendering context from that @code{canvas}, and sets the height and width of the canvas to completely fill its containing element. Lastly, it fills out the canvas light-gray, so that we can see it on the page.

  @p
    Next, let's set some event handlers on the canvas:

  @split
    @more
      @hl.ref(canvasapp/"ScratchPad.scala", "/*code*/")

    @less
      @BookData.example(canvas, "canvasapp.ScratchPad().main")

  @p
    This code sets up the @lnk.dom.mousedown and @lnk.dom.mouseup events to keep track of whether or not the mouse has currently been clicked. It then draws black squares any time you move the mouse while the button is down. This lets you basically click-and-drag to draw pictures on the canvas. Try it out!

  @p
    In general, you have access to all the DOM APIs through the @hl.scala{dom} package as well as through Javascript objects such as the @lnk.dom.HTMLCanvasElement. Setting the @code{onmouseXXX} callbacks is just one way of interacting with the DOM. With Scala.js, you also get a very handy autocomplete in the editor, which you can use to browse the various other APIs that are available for use:

  @img(src:="images/Dropdown.png", maxWidth:="100%")

  @p
    Apart from mouse events, keyboard events, scroll events, input events, etc. are all usable from Scala.js as you'd expect. If you have problems getting this to work, feel free to click on the link @i(cls:="fa fa-link ") icon below the code snippet to see what the full code for the example looks like

@sect{Making a Clock using setInterval}

  @p
    You've already seen this in the previous example, but @lnk.dom.setInterval can be used to schedule recurring, periodic events in your program. Common use cases include running the @lnk("event loop for a game", "http://gameprogrammingpatterns.com/game-loop.html"), making smooth animations, and other tasks of that sort which require some work to happen over a period of time.

  @p
    Again, we need roughly the same boilerplate as just now to set up the canvas:

  @hl.ref(canvasapp/"Clock.scala", "/*setup*/", "/*code*/")

  @p
    The only thing unusual here is that I'm going to create a @hl.scala{linearGradient} in order to make the stopwatch look pretty. This is by no means necessary, and you could simply make the @hl.scala{fillStyle} @hl.scala{"black"} if you want to keep things simple.

  @p
    Once that's done, it's only a few lines of code to set up a nice, live clock:

  @split
    @more
      @hl.ref(canvasapp/"Clock.scala", "/*code*/")

    @less
      @BookData.example(canvas, "canvasapp.Clock().main")

  @p
    As you can see, we're using more @lnk("Canvas APIs", "https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D"), in this case dealing with rendering text on the canvas. Another thing we're using is the Javascript @lnk("Date", "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date") class, in Scala.js under the full name @hl.scala{scala.scalajs.js.Date}, here imported as @hl.scala{js.Date}. Again, click on the link @i(cls:="fa fa-link ") icon to view the full-code if you're having trouble here.

@sect{Tying it together: Flappy Box}

  @p
    You've just seen two examples of how to use Scala.js, together with the Javascript DOM APIs, to make simple applications. However, we've only used the "Scala" in Scala.js in the most rudimentary fashion: setting a few primitives here and there, defining some methods, mainly just gluing together a few Javascript APIs

  @p
    In this example we will make a spiritual clone of the popular @lnk("Flappy Bird", "http://en.wikipedia.org/wiki/Flappy_Bird") video game. This game involves a few simple rules

  @ul
    @li
      Your character starts in the middle of the screen
    @li
      Gravity pulls your character down
    @li
      Clicking/tapping the screen makes your character jump up
    @li
      There are obstacles that approach your character from the right side of the screen, and you have to make sure you go through the hole in each obstacle to avoid hitting it
    @li
      Don't go out of bounds!

  @p
    It's a relatively simple game, but there should be enough "business logic" in here that we won't be simply gluing together APIs. Let's start!

  @sect{Setting Up the Canvas}
    @hl.ref(canvasapp/"FlappyLine.scala", "/*setup*/", end="/*variables*/")

    @p
      This section of the code is peripherally necessary, but not core to the implementation or logic of Flappy Box. We see the same @hl.scala{canvas}/@hl.scala{renderer} logic we've seen in all our examples, along with some logic to make the canvas a reasonable size, and some configuration of how we will render text to the canvas.

    @p
      In general, code like this will usually end up being necessary in a Scala.js program: the Javascript APIs that the browser provides to do things often ends up being somewhat roundabout and verbose. It's somewhat annoying to have to do for a small program such as this one, but in a larger application, the cost is both spread out over thousands of lines of code and also typically hidden away in helper functions, so the verbosity and non-idiomatic-scala-ness doesn't bother you much.

  @sect{Defining our State}
    @hl.ref(canvasapp/"FlappyLine.scala", "/*variables*/", end="def runLive")

    @p
      This is where we start defining things that are relevant to Flappy Box. There are roughly two groups of values here: immutable constants in the top group, and mutable variables in the bottom. The rough meaning of each variable is documented in the comments, and we'll see exactly how we use them later.

    @p
      One notable thing is that we're using a @lnk("collection.mutable.Queue", "http://docs.scala-lang.org/overviews/collections/concrete-mutable-collection-classes.html") to store the list of obstacles. This is defined in the Scala standard library; in general, all the collections in the Scala standard library can be used without issue in Scala.js.

  @sect{Game Logic}
    @hl.ref(canvasapp/"FlappyLine.scala", "def runLive", "def runDead")

    @p
      The @hl.scala{runLive} function is the meat of Flappy Box. In it, we

    @ul
      @li
        Clear the canvas
      @li
        Generate new obstacles
      @li
        Apply velocity and acceleration to the player
      @li
        Check for collisions or out-of-bounds, killing the player if it happens
      @li
        Rendering everything, including the player as the namesake box

    @p
      This function basically contains all the game logic, from motion, to collision-detection, to rendering, so it's pretty large. Not that large though! And entirely understandable, even if it takes a few moments to read through.

    @hl.ref(canvasapp/"FlappyLine.scala", "def runDead", "def run()")

    @p
      This is the function that handles what happens when you're dead. Essentially, we reset all the mutable variables to their initial state, and just count down the @hl.scala{dead} counter until it reaches zero and we're considered alive again.

  @sect{A Working Product}
    @hl.ref(canvasapp/"FlappyLine.scala", "def run()")

    @p
      And finally, this is the code that kicks everything off: we define the @hl.scala{run} function to swap between @hl.scala{runLive} and @hl.scala{runDead}, register an @lnk.dom.onclick handler to make the player jump by tweaking his velocity, and we call @lnk.dom.setInterval to run the @hl.scala{run} function every 20 milliseconds.

    @p
      At almost 100 lines of code, this is quite a meaty example! Nonetheless, when all is said and done, you will find that the example actually works! Try it out!

    @div
      @BookData.example(canvas, "canvasapp.FlappyLine().main")

@sect{Canvas Recap}
  @p
    We've now gone through the workings of building a handful of toy applications using Scala.js. What have we learnt in the process?

  @sect{Development Speed}
    @p
      We've by now written a good chunk of Scala.js code, and perhaps debugged some mysterious errors, and tried some new things. One thing you've probably noticed is the efficiency of the process: you make a change in your editor, the browser reloads itself, and life goes on. There is a compile cycle, but after a few runs the compiler warms up and the compilation cycle drops to less than a second.
    @p
      Apart from the compilation/reload speed, you've probably noticed the benefit of tooling around Scala.js. Unlike Javascript editors, your existing Scala IDEs like @lnk.misc.IntelliJ or @lnk.misc.Eclipse can give very useful help when you're working with Scala.js. Autocomplete, error-highlighting, jump-to-definition, and a myriad other modern conveniences that are missing when working in dynamically-typed languages are present when working in Scala.js. This makes the code much less mysterious: you're no longer trying to guess what methods a value has, or what a method returns: it's all laid out in front of you in plain sight.

  @sect{Full Scala}
    @p
      All of the examples so far have been very self-contained: they do not touch the HTML DOM, they do not make Ajax calls, or try to access web services. They don't push the limits of the browser's API.

    @p
      Nevertheless, these examples have exercised a good amount of the Scala language. List comprehensions, collections, the math library, and more. In general, most of the Scala standard library works under Scala.js, as well as a large number of third-party libraries. Unlike many other compile-to-Javascript languages out there, this isn't a language-that-looks-like-Scala: it is Scala through and through, with a tiny number of semantic differences.

  @sect{Seamless Javascript Interop}
    @p
      Even if we take some time to read through the code we've written, it is not immediately obvious which bits of code are Scala and which bits are Javascript! It all kind of meshes together, for example if we take the Flappy Box source code:

    @ul
      @li
        @hl.scala{obstacles} is a Scala @lnk("mutable.Queue", "http://docs.scala-lang.org/overviews/collections/concrete-mutable-collection-classes.html"), as we defined it earlier, and all the methods on it are Scala method calls
      @li
        @hl.scala{renderer} is a Javascript @lnk.dom.CanvasRenderingContext2D, and all the methods on it are Javascript method calls directly on the Javascript object
      @li
        @hl.scala{frame} is a Scala @hl.scala{Int}, and obeys Scala semantics, though it is implemented as a Javascript @hl.javascript{Number} under the hood.
      @li
        @hl.scala{playerY} and @hl.scala{playerV} are Scala @hl.scala{Double}s, implemented directly as Javascript @hl.javascript{Number}s

    @p
      This reveals something pretty interesting about Scala.js: even though Scala at-first-glance is a very different language from Javascript, the interoperation with Javascript is so seamless that you can't even tell from the code which values/methods are defined in Scala and which values/methods come from Javascript!
    @p
      These two classes of values/methods are treated very differently by the compiler when it comes to emitting the executable Javascript blob, but the compiler does not need extra syntax telling it which things belong to Scala and which to Javascript: the types are sufficient. @hl.scala{renderer}, for example is of type @lnk.dom.CanvasRenderingContext2D which is a subtype of @hl.scala{scalajs.js.Object}, indicating to the compiler that it needs special treatment. Primitives like @hl.scala{Double}s and @hl.scala{Int}s have similar treatment

    @p
      Overall, this seamless mix of Scala and Javascript values/methods/functions is a common theme in Scala.js applications, so you should expect to see more of it in later chapters of the book.

  @hr

  @p
    You've now had some experience building small canvas applications in Scala.js. Why not try exercising your new-found skills? Here are some possibilities:

  @ul
    @li
      Make more video games! I have a set of @lnk("retro-games ported to Scala.js", "http://lihaoyi.github.io/scala-js-games/"). Maybe re-make one of them without looking at the source, or maybe port some other game you're familiar with and enjoy playing. Even just drawing things on canvas, games can get @lnk("pretty elaborate", "http://lihaoyi.github.io/roll/").

    @li
      Explore the APIs! We've touched on a small number of Javascript APIs here, mainly for dealign with the canvas, but modern browsers offer a whole @lnk("zoo of functionality", "https://developer.mozilla.org/en-US/docs/Web/API"). Try making an application that uses @lnk("localStorage", "https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage") to save state even when you close the tab, or an application that works with the HTML DOM.

    @li
      Draw something pretty! We have a working canvas, a nice programming language, and a way to easily publish the results online. @lnk("Various", "http://www.scala-js-fiddle.com/gist/77a3840678d154257ca1/KochSnowflake.scala") @lnk("fractals", "http://www.scala-js-fiddle.com/gist/77a3840678d154257ca1/KochCurve.scala"), or @lnk("colorful visualizations", "http://www.scala-js-fiddle.com/gist/9443f8e0ecc68d1058ad/RayTracer.scala") are all possibilities.

  @p
    By this point you've some experience building stand-alone, single-canvas Scala.js applications, which has hopefully given you a feel for how Scala.js works. The problem is that few web applications satisfy the criteria of being stand-alone single-page canvas applications! Most web applications need to deal with the DOM of the HTML page, need to fetch data from web services, and generally need to do a lot of other messy things. We'll go into that in the next chapter