Events

Besides building and modifying the scene graph, python scripts can react when a user interacts with the system, some time has elapsed or any other input has arrived. More specifically, apps can tell libavg objects - so-called Publishers - that they would like to be notified in the case of an event. This is called subscribing; the notification takes the form of a function call from libavg to the app.

Example

Here's an example of reacting to a mouse press:

ondown.py:

 1#!/usr/bin/env python
 2# -*- coding: utf-8 -*-
 3
 4from libavg import app, avg
 5
 6class MainDiv(app.MainDiv):
 7    def onInit(self):
 8        self.toggleTouchVisualization()
 9        self.node = avg.WordsNode(pos=(10,10), text="Hello World", parent=self)
10        self.node.subscribe(avg.Node.CURSOR_DOWN, self.onDown)
11
12    def onDown(self, event):
13        self.node.x = 200
14
15app.App().run(MainDiv())

The app displays a "Hello World" text that is moved to a new position when the mouse button is pressed over the item. In this case, the WordsNode is the Publisher. The app registers for the event using node.subscribe(), and when the click happens, MainDiv.onDown is called by libavg.

General Mechanism

The simple example above illustrates the general mechanism: To register for an event, call Publisher.subscribe() with the MessageID (in the example, Node.CURSOR_DOWN) and a Python callable (in the example, self.onClick) as parameters. Events you can subscribe to include all user input (e.g., key presses, mouse clicks and movements, touches on a touchscreen,...), but the principle is much broader: This is libavg's general notification mechanism. There are events that are triggered on each frame (player.ON_FRAME), when the end of a media file has been reached (player.END_OF_FILE), etc.. Any number of clients can subscribe to a message, and all of them will be invoked when the event occurs. In most cases, you don’t have to deregister messages to clean up either. Subscriptions are based on weak references wherever possible, so when the object they refer to disappears, the subscription will just disappear as well. In cases where you would like to explicitly stop receiving a certain event, call Publisher.unsubscribe().

Like most user interface libraries, libavg is based on an event-driven programming model. This means that the flow of the program is determined by external events such as user actions or timers. Internally, libavg runs a main loop that looks approximately like this:

1while not(stopping):
2    handleTimers()
3    handleEvents()
4    render()

All python code is executed in handleTimers() and handleEvents(). As a consequence, the display is only updated after the python code returns, when render() runs. It also means that an event handler that takes a significant amout of time (such as waiting for network transmission) will cause screen updates to hang during this time.

Timers

Timers that fire after a set interval are an exception to the 'all notifications use a publish-subscribe mechanism' principle declared above: There is a separate Player.setTimeout() method that handles this event type.

As an example, consider moving a node after one second of playback:

timer.py:

 1#!/usr/bin/env python
 2# -*- coding: utf-8 -*-
 3
 4from libavg import avg, player
 5
 6class MainDiv(app.MainDiv):
 7    def onInit(self):
 8        self.toggleTouchVisualization()
 9        self.node = avg.WordsNode(pos=(10,10), text="Hello World", parent=self)
10
11        player.setTimeout(1000, moveText)
12
13    def moveText(self):
14        self.node.x = 200
15
16app.App().run(MainDiv())

The setTimeout() call is what makes this happen. It tells libavg to call the routine specified (in this case, moveText()) after a certain time has elapsed (in this case, 1000 milliseconds). In addition to setTimeout(), there is a setInterval() method that lets you register a routine to call regularly.

For more info on timing, have a look at Timing.

More on the Invoked Functions

Any Python callable can be registered as a callback, including standalone functions, class methods, lambdas and even class constructors. For simple callbacks - standalone functions or class methods -, this simply means passing the function name without parentheses to subscribe():

1node.subscribe(avg.Node.CURSOR_DOWN, self.onDown)

In this case, onDown is called with an additional parameter, event, so you need to declare it as follows:

1def onDown(self, event):

In some cases however, it becomes necessary to pass additional parameters to the callback when subscribe is called. To do this, you can use lambdas. An in-depth explanation of how lambda works is out of scope for this article (http://www.diveintopython.net/power_of_introspection/lambda_functions.html has a good explanation), but as an example for the power of this construct, have a look at this extremely compact number pad implementation that creates ten buttons which act differently:

numberpad.py:

 1#!/usr/bin/env python
 2# -*- coding: utf-8 -*-
 3
 4from libavg import app, avg, widget
 5
 6class MainDiv(app.MainDiv):
 7    def onInit(self):
 8        self.toggleTouchVisualization()
 9        self.textNode = avg.WordsNode(pos=(10,10), text="", parent=self) 
10        for i in range(0,10):
11            pos = avg.Point2D(10,40) + ((i%3)*40, (i//3)*40)
12            node = widget.TextButton(pos=pos, size=(30,30), text=str(i), parent=self)
13            node.subscribe(widget.Button.CLICKED, lambda i=i: self.onDown(i))
14
15    def onDown(self, i):
16        self.textNode.text += str(i)
17
18app.App().run(MainDiv())

Creating Publishers in Python

You can write your own publishers in Python by simply deriving from the Publisher class (Nodes are derived from Publisher already). You need just two lines of code to register a message:

1class Button(avg.DivNode):
2    CLICKED = avg.Publisher.genMessageID()
3    [...]
4    def __init__(...):
5        self.publish(self.CLICKED)
6    [...]

and this line invokes all registered subscribers:

1self.notifySubscribers(self.CLICKED, [])

The second parameter to notifySubscribers is a list of parameters to pass to the subscribers. For example code, have a look at the widget classes under src/python/widget.