W1521 Moving Along

From Coder Merlin
Within these castle walls be forged Mavens of Computer Science ...
— Merlin, The Coder
Animated Horse

Prerequisites[edit]

Research[edit]

Background[edit]

An event is an asynchronous occurrence to which our software may react through the implementation of an event handler. Events are often triggered by either the user (e.g. moving the mouse or pressing a key) or by the system (e.g. timers, screen refresh). Handling user-generated events in this manner enables our system to be more responsive, because our program has the opportunity to react to the event as it occurs. An alternative method involved polling, in which we periodically check to see if the state of a system has changed. For example, we can periodically check to see if a key has been pressed on the keyboard. Generally, polling leads to a less than ideal experience for the user. We may, for example, miss a keypress because we were busy doing something else, or we may respond much more slowly and not meet the user's expectations. In this lab, we'll be using the update event handler to refresh the objects drawn on the canvas and the onClick event to respond to a mouse click. The update event handler is invoked by the system periodically to refresh the canvas, while the onClick event is generated as a result of the user clicking the mouse.

Using the tools that we've learned to date, we're able to produce many types of still images. In this lab, we'll focus on defining objects which persist throughout our Igis session, enabling us to modify the properties of these objects to create simple animations.

Prepare[edit]

Create a new Scenes shell project within your Experiences directory:

ty-cam@codermerlin:~$  cd ~/Experiences

ty-cam@codermerlin:~/Experiences$  git clone https://github.com/TheCoderMerlin/ScenesShellBasic W1521


Enter the Sources/ScenesShell directory of the new project:

ty-cam@codermerlin:~/Experiences$  cd W1521/Sources/ScenesShell/


Start button green arrow
Run the program.

ty-cam@codermerlin:~/Experiences/W1521/Sources/ScenesShell$  run


Ensure that you are logged on to the wiki. Then, click on the Tools menu followed by right-clicking on IGIS and selecting the menu item Open in New Window or Open in New Tab.

You'll know you're successful if you see the title bar change to "Coder Merlin: IGIS". (The browser window will be blank because we haven't added any graphics yet.)

Hint.pngHelpful Hint
It's useful to bookmark this page in your browser.

Experiment[edit]

First Steps[edit]

Open the file Background.swift in emacs.

Let's set start by creating a ball. Add the following properties to the Background class:

class Background : RenderableEntity {
    let ball : Ellipse
    var rect: Rect? = nil

    init() {
        // Using a meaningful name can be helpful for debugging
        super.init(name:"Background")
    }

This defines a property of type Ellipse, named "ball".

We'll need to initialize the ball in our init constructor. Edit the constructor as follows:

    init() {
        ball = Ellipse(center:Point(x:0, y:0), radiusX:30, radiusY:30, fillMode:.fillAndStroke)

        // Using a meaningful name can be helpful for debugging
        super.init(name:"Background")
    }

The setup method is invoked once, when our canvas is first setup. We'll use this opportunity to both style our ellipse and to set its initial position. We'll also remember the size of the canvas. Create a new method, setup, as follows:

    override func setup(canvasSize: Size, canvas: Canvas) {
        // Style the ball
        let strokeStyle = StrokeStyle(color:Color(.orange))
        let fillStyle = FillStyle(color:Color(.red))
        let lineWidth = LineWidth(width:5)
        canvas.render(strokeStyle, fillStyle, lineWidth)

        // Preserve the dimensions of the canvas for later
        rect = Rect(size: canvasSize)

        // Position the ball at the center of the canvas
        ball.center = canvasSize.center
    }


We've prepared the attributes for our canvas but we haven't yet rendered the ball. We'll do that in the render method. The render method is invoked frequently for as long as our application is running. Add a render method as follows:

    override func render(canvas:Canvas) {
        canvas.render(ball)
    }
Start button green arrow
Run the program and view in a browser before continuing. Be sure that you understand the role of each method.

Mouse Click Events[edit]

Hint.pngHelpful Hint
You might want to review W1513 Patterns of Patterns to refresh your memory with regard to event handling.

Declare Conformance to Protocol[edit]

class Background : RenderableEntity, EntityMouseClickHandler {
   ...
}

Notify Dispatcher[edit]

    override func setup(canvasSize:Size, canvas:Canvas) {
        // ... other code is here ...
        dispatcher.registerEntityMouseClickHandler(handler:self)
    }

    override func teardown() {
        dispatcher.unregisterEntityMouseClickHandler(handler:self)
    }

Implement Event Handler[edit]

Let's add another method, onEntityMouseClick, which is invoked when the mouse button is clicked. The method provides the location on the canvas at the position that the mouse was clicked. We can use the location to move the ball from its original, center position to the location that the mouse was clicked.

    func onEntityMouseClick(globalLocation: Point) {
        ball.center = globalLocation
    }

Note the manner in which we took advantage of objects by assigning an instance of Point directly to the ball's center, which is also a property of type Point.

Define the BoundingRect[edit]

    override func boundingRect() -> Rect {
        if let rect = rect {
            return rect
        } else {
            return Rect()
        }
    }


Start button green arrow
Run the program and view in a browser before continuing.
Did the ball move to the new location? If not, what happened? Why?

Clear the Canvas[edit]

Let's add another flag to track whether or not we need to draw the ball. We only need to draw it if it's moved. If it hasn't moved, then there's no reason to redraw it. Add the following property (below "var coordinateSet = false"):

    var needToDraw = true

We initialize this property to true because at this point, we haven't yet drawn the ball. Each time we do draw the ball, we'll set this property to false. We won't change the flag to true unless something has changed. Modify the update method so that we check the flag before painting the ball and subsequently clear the flag:

        if needToDraw {
                canvas.paint(ball)
                needToDraw = false
        }

We'll also need to modify the onClick method to set this flag to true, indicating that we'll need to draw the ball (because we've changed the center).

    override func onClick(location:Point) {
        ball.center = location
        needToDraw = true
    }


Finally, we'll need to clear the canvas. We do this by painting a rectangle and specifying the .clear fill mode. Let's define a helper function for this purpose:

    func clearScreen(canvas:Canvas, canvasSize:Size) {
        let rect = Rect(topLeft:Point(x:0, y:0), size:canvasSize)
        let rectangle = Rectangle(rect:rect, fillMode:.clear)
        canvas.paint(rectangle)
    }

Of course, we'll need to invoke the function. The best place to do this is immediately before we paint the ball. Add the following line immediately before the ball is painted:

                clearScreen(canvas:canvas, canvasSize:canvasSize)
Start button green arrow

Run the project.

View the results in the browser. Did the ball move to the new location? If not, what happened? Why?

Tracking the Mouse[edit]

Clicking the mouse can move the ball to that location, but we can also have the ball track the mouse. We'll simply use a different method, named onMouseMove. This method is invoked whenever the mouse is moved on the canvas. Change the name of the onClick method to onMouseMove. No other changes are necessary.

Start button green arrow

Run the project.

View the results in the browser. Does the ball track the mouse? How is this behavior different from the situation when we were using onClick?

Exercises[edit]

  1. Change the background of the animation from white to a sky of blue and grass of green

Key Concepts[edit]

  • An event is an asynchronous occurrence to which our software may react through the implementation of an event handler
    • Events may be triggered by the system
      • Timers
      • Screen refresh
    • Events may be triggered by a user
      • Mouse movement
      • Mouse clicks
      • Keypresses
  • Flags are Boolean values that indicate the state of a binary situation
    • clearing a flag means setting it to false
    • setting a flag means setting it to true