Difference between revisions of "W1521 Moving Along"

From Coder Merlin
m (Editorial review and minor corrections)
 
(34 intermediate revisions by 2 users not shown)
Line 1: Line 1:
[[File:Animhorse.gif|thumb|Animated Horse]]
[[File:Animhorse.gif|thumb|Animated Horse]]
== Prerequisites ==
== Prerequisites ==
* [[Project-1505|1505 Images]]
* [[W1505 Images]]


== Research ==
== Research ==
* [https://en.wikipedia.org/wiki/Event_(computing) Events]


== Background ==
== Background ==
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.
An '''event''' is an asynchronous occurrence to which our software can react by implementing 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 can 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 might, for example, miss a keypress because we were busy doing something else, or we might respond much more slowly and not meet the user's expectations. In this lab, we'll be using the '''render''' event handler to refresh the objects drawn on the canvas and the '''onEntityMouseClick''' event to respond to a mouse click. The '''render''' event handler is invoked by the system periodically to refresh the canvas, while the '''onEntityMouseClick''' 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 that persist throughout our Scenes session, enabling us to modify the properties of these objects to create simple animations.
== Prepare ==
{{ScenesShellPrepare|W1521}}
== Experiment ==
== Experiment ==
=== First Steps ===
{{Hint|
* Remember to execute '''dylibEmacs''' to generate the files required by emacs to access dynamic libraries
* Remember to import any required libraries at the top of your file
* When creating a new file, emacs might not yet recognize the associated libraries. Simply exit emacs and re-enter the file.
}}
Let's start by creating a ball. Create a new file {{Pathname|Ball.swift}} in emacs. Begin by defining the following properties on a new {{SwiftClass|Ball}} class:
<syntaxhighlight lang="swift">
class Ball: RenderableEntity {
    let ellipse = Ellipse(center:Point(x:0, y:0), radiusX:30, radiusY:30, fillMode:.fillAndStroke)
    let strokeStyle = StrokeStyle(color:Color(.orange))
    let fillStyle = FillStyle(color:Color(.red))
    let lineWidth = LineWidth(width:5)
}
</syntaxhighlight>
This defines a new class with properties defining the visual aspects required to render a ball.


=== Getting Started ===
We'll need to initialize the ball in our {{SwiftIdentifier|init}} constructor. Create the constructor as follows:
[[File:Breathe-document-new.svg|left|50px|link=|Breathe-document-new]]  Begin a '''new''' project
<syntaxhighlight lang="swift" highlight="2">
    init() {
        // Using a meaningful name can be helpful for debugging
        super.init(name:"Ball")
    }
</syntaxhighlight>


The {{SwiftIdentifier|setup}} method is invoked once, when our canvas is first set up. We'll use this opportunity to set the initial position of the ellipse. Create a new method, {{SwiftIdentifier|setup}}, as follows:
<syntaxhighlight lang="swift">
    override func setup(canvasSize: Size, canvas: Canvas) {
        // Position the ellipse at the center of the canvas
        ellipse.center = canvasSize.center
    }
</syntaxhighlight>




Create an Igis shell project within your "project" directory.  
Finally, we'll need to render the ball when requested. We'll do that in the {{SwiftIdentifier|render}} method. The {{SwiftIdentifier|render}} method is invoked frequently for as long as our application is running. Add a {{SwiftIdentifier|render}} method as follows:
<syntaxhighlight lang="bash">
 
cd ~/projects
<syntaxhighlight lang="swift">
git clone https://github.com/TangoGolfDigital/IgisShell IgisShell-MovingAlong
    override func render(canvas:Canvas) {
        canvas.render(strokeStyle, fillStyle, lineWidth, ellipse)
    }
</syntaxhighlight>
</syntaxhighlight>


Enter into the Sources directory of the new project.
<syntaxhighlight lang="bash">
cd IgisShell-MovingAlong/Sources/IgisShell/
</syntaxhighlight>


Build the project. (This may take some time.)
At this point, we've created a {{SwiftClass|Ball}} class but we haven't '''instantiated''' an instance of it. For it to participate in our {{SwiftClass|Scene}} we'll need to add it to a {{SwiftClass|Layer}}. Because we'll later be interacting with the ball, let's add it to our {{SwiftClass|InteractionLayer}}. Edit the code in {{Pathname|InteractionLayer.swift}} as follows:
<syntaxhighlight lang="bash">
swift build
</syntaxhighlight>


[[File:Start button green arrow.svg|left|link=|Start button green arrow]] Run the project.
<syntaxhighlight lang="swift" highlight="8">
<br>
class InteractionLayer : Layer {
<syntaxhighlight lang="bash">
swift run
    let ball = Ball()
</syntaxhighlight>


Open a browser (or use a new tab on an already-open browser).
    init() {
Go to the URL:  http://www.codermerlin.com/users/user-name/dyn/index.html
        // Using a meaningful name can be helpful for debugging
        super.init(name:"Interaction")


NOTE: You MUST change '''user-name''' to your actual user name.  For example, http://www.codermerlin.com/users/john-williams/dyn/index.html
        // We insert our RenderableEntities in the constructor
        insert(entity: ball, at: .front)
    }
}
</syntaxhighlight>


You'll know your 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.)
{{RunProgram|Run the program and view in a browser before continuing. Be sure that you understand the role of each method.}}


{{notice|[[File:Oxygen480-actions-help-hint.svg|frameless|30px]]|Helpful hint:  It's useful to bookmark this page in your browser.}}
=== Mouse Click Events ===
{{Hint|You might want to review [[W1513 Patterns of Patterns]] to refresh your memory on event handling.}}


=== First Steps ===
Continue editing {{Pathname|Ball.swift}}:
Let's set start by creating a ball.  Edit file "main.swift":
<syntaxhighlight lang="bash">
emacs main.swift
</syntaxhighlight>


Edit the file by finding the definition of the '''Painter''' class.  Before the '''init''' constructor, add the following:
==== Declare Conformance to Protocol ====
<syntaxhighlight lang="swift">
<syntaxhighlight lang="swift" highlight="1">
    let ball : Ellipse
class Ball : RenderableEntity, EntityMouseClickHandler {
    var coordinatesSet = false
  ...
}
</syntaxhighlight>
</syntaxhighlight>
This defines a property of type Ellipse, named "ball".  It also defines a [https://en.wikipedia.org/wiki/Boolean_flag boolean flag] used to determine if we've set the initial coordinates.


We'll need to initialize the ball in our '''init''' constructor.  Edit the constructor as follows:
==== Notify Dispatcher ====
<syntaxhighlight lang="swift">
<syntaxhighlight lang="swift" highlight="3,6-8">
     required init() {
     override func setup(canvasSize:Size, canvas:Canvas) {
         ball = Ellipse(center:Point(x:0, y:0), radiusX:30, radiusY:30, fillMode:.fillAndStroke)
         // ... other code is here ...
        dispatcher.registerEntityMouseClickHandler(handler:self)
     }
     }
</syntaxhighlight>


The '''setup''' method is invoked once, when our canvas is first setup.  We'll use this opportunity to set the style of our ellipse.  Edit '''setup''' as follows:
     override func teardown() {
<syntaxhighlight lang="swift">
         dispatcher.unregisterEntityMouseClickHandler(handler:self)
     override func setup(canvas:Canvas) {
         let strokeStyle = StrokeStyle(color:Color(.orange))
        let fillStyle = FillStyle(color:Color(.red))
        let lineWidth = LineWidth(width:5)
        canvas.paint(strokeStyle, fillStyle, lineWidth)
     }
     }
</syntaxhighlight>
</syntaxhighlight>


==== Implement Event Handler ====
Let's add another method, {{SwiftIdentifier| 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.


We've prepared the attributes for our canvas but we haven't yet drawn the ball.  We'll do that in the '''update''' method.  The '''update''' method is invoked frequently for as long as our application is running.  We'll use it to set the initial coordinates (if they haven't yet been set) and draw the ball in the center of the screen.  Add an '''update''' method as follows, immediately after the '''setup''' method.
<syntaxhighlight lang="swift">
<syntaxhighlight lang="swift">
     override func update(canvas:Canvas) {
     func onEntityMouseClick(globalLocation: Point) {
         if let canvasSize = canvas.canvasSize {
         ellipse.center = globalLocation
            // Set the coordinates to the center of the screen if they've not yet been set
            if !coordinatesSet {
                let canvasCenter = Point(x:canvasSize.width/2, y:canvasSize.height/2)
                ball.center = canvasCenter
                coordinatesSet = true
            }
            canvas.paint(ball)
        }
     }
     }
</syntaxhighlight>
</syntaxhighlight>
Note that we can't set the coordinates of the ball earlier than the '''update''' method because the size of the canvas isn't known prior to the invocation of this method.
[[File:Start button green arrow.svg|left|link=|Start button green arrow]] Run the project. <br/>
View the results in the browser.  Be sure that you understand the role of each method.<br/>


=== Mouse Clicks ===
Note the manner in which we took advantage of objects by assigning an instance of {{SwiftClass|Point}} directly to the ball's center, which is also a property of type {{SwiftClass|Point}}.
Let's add another method, '''onClick''', 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.


Add the following:
==== Define the BoundingRect ====
We want to respond from anywhere on the canvas, so we'll use the largest Rect possible:
<syntaxhighlight lang="swift">
<syntaxhighlight lang="swift">
     override func onClick(location:Point) {
     override func boundingRect() -> Rect {
         ball.center = location
         return Rect(size: Size(width: Int.max, height: Int.max))   
     }
     }
</syntaxhighlight>
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.
[[File:Start button green arrow.svg|left|link=|Start button green arrow]] Run the project. <br/>
View the results in the browser.  Did the ball move to the new location?  If not, what happened? Why?<br/>
=== Clear the Canvas ===
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"):
<syntaxhighlight lang="swift">
    var needToDraw = true
</syntaxhighlight>
</syntaxhighlight>


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:
<syntaxhighlight lang="swift">
        if needToDraw {
                canvas.paint(ball)
                needToDraw = false
        }
</syntaxhighlight>


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).
{{RunProgram|Run the program and view in a browser before continuing. <br/> Click the mouse in several different locations. Did the ball '''move''' to the new location? If not, what happened? Why?}}
<syntaxhighlight lang="swift">
    override func onClick(location:Point) {
        ball.center = location
        needToDraw = true
    }
</syntaxhighlight>


== Exercises ==
== Exercises ==
{{W1521-Exercises}}
* {{MMMAssignment|M1521-28}}


== Key Concepts ==
== Key Concepts ==
{{KeyConcepts|
* An '''event''' is an asynchronous occurrence to which our software can react by implementing an '''event handler'''
** Events can be triggered by the system
*** Timers
*** Screen refresh
** Events can 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
}}
[[Category:IGIS]]

Latest revision as of 14:37, 7 May 2022

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 can react by implementing 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 can 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 might, for example, miss a keypress because we were busy doing something else, or we might respond much more slowly and not meet the user's expectations. In this lab, we'll be using the render event handler to refresh the objects drawn on the canvas and the onEntityMouseClick event to respond to a mouse click. The render event handler is invoked by the system periodically to refresh the canvas, while the onEntityMouseClick 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 that persist throughout our Scenes 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]

Hint.pngHelpful Hint
  • Remember to execute dylibEmacs to generate the files required by emacs to access dynamic libraries
  • Remember to import any required libraries at the top of your file
  • When creating a new file, emacs might not yet recognize the associated libraries. Simply exit emacs and re-enter the file.

Let's start by creating a ball. Create a new file Ball.swift in emacs. Begin by defining the following properties on a new Ball class:

class Ball: RenderableEntity {
    let ellipse = Ellipse(center:Point(x:0, y:0), radiusX:30, radiusY:30, fillMode:.fillAndStroke)
    let strokeStyle = StrokeStyle(color:Color(.orange))
    let fillStyle = FillStyle(color:Color(.red))
    let lineWidth = LineWidth(width:5) 
}

This defines a new class with properties defining the visual aspects required to render a ball.

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

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

The setup method is invoked once, when our canvas is first set up. We'll use this opportunity to set the initial position of the ellipse. Create a new method, setup, as follows:

    override func setup(canvasSize: Size, canvas: Canvas) {
        // Position the ellipse at the center of the canvas
        ellipse.center = canvasSize.center
    }


Finally, we'll need to render the ball when requested. 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(strokeStyle, fillStyle, lineWidth, ellipse)
    }


At this point, we've created a Ball class but we haven't instantiated an instance of it. For it to participate in our Scene we'll need to add it to a Layer. Because we'll later be interacting with the ball, let's add it to our InteractionLayer. Edit the code in InteractionLayer.swift as follows:

class InteractionLayer : Layer {
 
    let ball = Ball()

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

        // We insert our RenderableEntities in the constructor
        insert(entity: ball, at: .front)
     }
 }
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 on event handling.

Continue editing Ball.swift:

Declare Conformance to Protocol[edit]

class Ball : 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) {
        ellipse.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]

We want to respond from anywhere on the canvas, so we'll use the largest Rect possible:

    override func boundingRect() -> Rect {
        return Rect(size: Size(width: Int.max, height: Int.max))    
    }


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

Exercises[edit]

ExercisesExercisesIcon.png
  • Alter the Background so that the screen is cleared during each render cycle. Then click on the canvas again and demonstrate that the ball appears to move as the mouse is clicked.
  • Alter the Background so that the top half of the screen appears to be a sky of blue and the bottom half appears to be grass of green
  • Add at least three additional outdoor shapes to the Background using at least three additional colors. This is your creation, so feel free to use your imagination rather than going for the straight-forward sun, clouds, and trees.
  • Alter the Ball so that rather than responding to onEntityMouseClick events, it responds to onMouseMove events. To do so, conform to protocol MouseMoveHandler, and register and unregister with the dispatcher (registerMouseMoveHandler, unregisterMouseMoveHandler). The signature for onMouseMove is:
    func onMouseMove(globalLocation: Point, movement: Point)
  •  M1521-28  Complete  Merlin Mission Manager  Mission M1521-28.

Key Concepts[edit]

Key ConceptsKeyConceptsIcon.png
  • An event is an asynchronous occurrence to which our software can react by implementing an event handler
    • Events can be triggered by the system
      • Timers
      • Screen refresh
    • Events can 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