INTRODUCTION |
So far, your understanding of the computer is that it executes instruction after instruction. It can also change the course of a program due to certain conditions or run through loops. The corresponding program structures are called sequence, selection, and iteration. As early as 1966 Böhm and Jacopini proved in a famous article that any calculation procedures (algorithms) can be realized using these three programming structures. This is, however, only true as long as you do not incorporate any external influences. For example, you can cancel a program at any moment by clicking with the mouse on "Close" (close button). Such processes need a new programming concept: event control (event handling). You have already learned the basic principles in the chapter Turtle Graphics/Event Control. It consists in procedures of the type: "Whenever the event e occurs, the function f is executed". The implementation is simple and known since the early days of computer technology in the fifties of the last century. We define a function f (then called interrupt routine) which is never even called by our own program. It sleeps, so to speak, until a certain event E occurs, an external influence, upon which it is then automatically called by the system. Today we call such a function callback and we say that the callback f is “fired” by the event E. Often, callbacks are called with parameter values that contain important information about the event, for example, which mouse button was pressed or where the mouse is located. |
REACTING TO A MOUSE EVENT |
You can also use mouse events in the GPanel, just like in turtle graphics. In the first example, a green circle is drawn at the current mouse position when the left or right mouse button is pressed. Do the following:
To register your callback you will need a named parameter of makeGPanel() that is called mousePressed. from gpanel import * def onMousePressed(x, y): move(x, y) fillCircle(0.02) makeGPanel(mousePressed = onMousePressed) setColor("green") |
MEMO |
A callback is not called by your own program, but rather automatically when the event is triggered. The registration of the callback is performed through a named parameter. You can detect the pressing of a mouse button with two different callbacks: a click event or a press event. The click event will not be triggered until after the key is released, but the press event triggers immediately once you press the button. |
DETECTING MOUSE MOVEMENT |
from gpanel import * def onMouseMoved(x, y): move(x, y) setColor("red") fillCircle(.04) setColor("black") circle(.04) makeGPanel(mouseMoved = onMouseMoved) |
MEMO |
The onMouseMoved(x, y) callback is registered through a named parameter mouseMoved. |
FREE HAND DRAWING WITH A PRESSED MOUSE BUTTON |
from gpanel import * def onMousePressed(x, y): move(x, y) def onMouseDragged(x, y): draw(x, y) makeGPanel(mousePressed = onMousePressed, mouseDragged = onMouseDragged) |
MEMO |
You can register multiple callback with named parameters simultaneously. The order of the parameters does not matter. |
THE LEFT AND RIGHT MOUSE BUTTON |
from gpanel import * def onMousePressed(x, y): if isLeftMouseButton(): pixColor = getPixelColor(x, y) if pixColor == makeColor("white"): return clear() setColor(pixColor) move(5, 5) fillCircle(2) if isRightMouseButton(): for i in range(5): move(9, 2 * i + 1) if i == 0: setColor("deep pink") if i == 1: setColor("green") if i == 2: setColor("yellow") if i == 3: setColor("deep sky blue") if i == 4: setColor("dark violet") fillRectangle(2, 2) makeGPanel(0, 10, 0, 10, mousePressed = onMousePressed) move(5, 5) fillCircle(2) |
MEMO |
The registered mouse callbacks are triggered with the left and the right mouse buttons. You can find out which button was used by calling isLeftMouseButton() or isRightMouseButton(). |
RUBBER BAND LINES |
But there is a particular problem: to move the rubber band over the drawing area it must be repeatedly erased from its old location and drawn again to the new location, without changing the already existing drawing. If you deleted the lines by overwriting them with the background color, gaps would result in the existing drawing at the intersection points. To solve this problem, you must save the existing drawing in the press callback (one also calls this "rescue"). The deletion of the temporary rubber band then happens by restoring this “old” drawing. You can save the drawing with storeGraphics() and restore it with recallGraphics(). from gpanel import * def onMousePressed(x, y): global x1, y1, x2, y2 storeGraphics() x1 = x y1 = y x2 = x1 y2 = y1 setColor("red") def onMouseDragged(x, y): global x2, y2 recallGraphics() x2 = x y2 = y line(x1, y1, x2, y2) def onMouseReleased(x, y): setColor("white") if not (x1 == x2 and y1 == y2): line(x1, y1, x2, y2) else: recalGraphics() x1 = 0 y1 = 0 x2 = 0 y2 = 0 makeGPanel(mousePressed = onMousePressed, mouseDragged = onMouseDragged, mouseReleased = onMouseReleased) title("Press And Drag To Draw Lines") bgColor("blue") setColor("white") lineWidth(2) |
MEMO |
Remember the principles of drawing rubber band lines: In a press event the end point of the line is initialized and the graphic is saved.In a drag event the saved/stored graphic is restored, the temporary line with the new end point is saved, and the new end point is saved. In a release event the line is definitely drawn, but only if the mouse was really moved. |
EXERCISES |
|
ADDITIONAL MATERIAL |
REGISTERING CALLBACKS WITH DECORATORS |
Instead of using named parameters of makeGPanel() to register a callback, an arbitrary named function with two parameters x and y can be "decorated" by a preceding line, so that TigerJython automatically registers the function as callback that is called when the event happens. The additional line has to be prefixed by the "at" sign @. The following decorators are available:
So the program shown above which draws a circle when the mouse button is pressed, can be written using a decorator: from gpanel import * @onMousePressed def doIt(x, y): move(x, y) fillCircle(0.02) makeGPanel() setColor("green") |