« Previous - Version 2/8 (diff) - Next » - Current version
admin, 20/03/2012 15:13

Event Handling Details

Event Handling is a method of attaching python code to actions from the user of the application. Events can originate from a mouse or keyboard, or from other devices such as multitouch tables. For mouses and touch devices, there are down, up, over, out and move events.

This page describes traditional event handling geared mostly to WIMP (Windows, Icon, Menu, Pointer) interfaces. Touch- and gesture-based interfaces can use the powerful Touch Input interface.

Event Bubbling

Determining which node an event is sent to follows a process called bubbling. Initially, libavg looks for the target node by recursively going down the node tree, always choosing the first child node that contains the cursor, until it reaches the lowest node that contains the cursor. Then, libavg examines the nodes from leaf to root (starting from the last node found) and delivers the event to the nodes that have appropriate event handlers in that order. The first step goes down the hierarchy in a geometrical fashion, and the second one goes up the node hierarchy by following the chain of parent nodes.

Note that DivNodes react to events directly as well as by bubbling - if the cursor of an event is inside a DivNode, that node will get the event.

Sound complicated? It really isn't and corresponds to the javascript event bubbling model. Usually, the effect is just what you would expect: Put an event handler in a leaf node and a surrounding div, and both will get called. The leaf node handler gets called first. But: if a node is covered by another node - even if it's something transparent or a DivNode -, it will never get events. Here is an example:


 1#!/usr/bin/env python
 2# -*- coding: utf-8 -*-
 4from libavg import avg
 6def onDiv(event):
 7    words.color = "FF8000" 
 9def onWords(event):
10    words.color = "00FF00" 
12player = avg.Player.get()
14canvas = player.createMainCanvas(size=(640,480))
15rootNode = canvas.getRootNode()
16words = avg.WordsNode(pos=(10,10), font="arial", 
17        text="Should I stay or should I go?", parent=rootNode)
18div = avg.DivNode(pos=(100,0), size=(80,200), parent=rootNode)
19words.setEventHandler(avg.CURSORMOTION, avg.MOUSE, onWords)
20div.setEventHandler(avg.CURSORMOTION, avg.MOUSE, onDiv)

Now, whenever the cursor is above the left part of the text, the text will turn green. When the cursor is over the right part - which is covered by the div - the text will turn orange. If you switch the order of the div and words nodes in the avg file, this won't happen anymore: The text will always turn green whenever the cursor is over it (since there's no cursorout handler, it will never turn white again, though).

Any node can be completely cut off from event handling by setting it's sensitive attribute. If this attribute is False, the node is ignored by the event handling code. Try this with the DivNode in the example and see what happens. The sensitive attribute extends to the children of a node: if you set it to False in a DivNode, all it's children will stop receiving events as well.

DivNodes and Event Handling

A DivNode with an explicit size attribute reacts to events as described above: Any events inside of its area are routed to the div node. A DivNode without an explicit size attribute, however, does not react to events directly. Bubbling still works: if a child of the DivNode is underneath a click, the event handlers of the parent will be called as well as described above.

Event Capture

When implementing things like dragging, scrollbars or even simple buttons, it helps a lot to be able to route all events from a pointer to one specific node. This can be done using the event capture mechanisms of libavg. Event capture allows you to route all events from a specific cursor to a single node temporarily. (As an aside, this is something completely different than what javascript calls event capture. It's the same thing that MFC calls event capture, though.)

Capture is set up by calling Node.setEventCapture() and released using Node.releaseEventCapture(). Here's an example drag-and-drop:


 1#!/usr/bin/env python
 2# -*- coding: utf-8 -*-
 4from libavg import avg
 6offset = None
 8def onMouseDown(event):
 9    global offset
10    node = event.node
11    offset = node.getRelPos((event.x, event.y))
12    node.setEventCapture()
14def onMouseMove(event):
15    global offset
16    node = event.node
17    if offset != None:
18        node.x = event.x-offset[0]
19        node.y = event.y-offset[1]
21def onMouseUp(event):
22    global offset
23    node = event.node
24    if offset != None:
25        node.releaseEventCapture()
26        offset = None;
28player = avg.Player.get()
30canvas = player.createMainCanvas(size=(640,480))
31rootNode = canvas.getRootNode()
32node = avg.WordsNode(pos=(10,10), font="arial", 
33        text="Hello World", parent=rootNode)
34node.setEventHandler(avg.CURSORDOWN, avg.MOUSE, onMouseDown)
35node.setEventHandler(avg.CURSORMOTION, avg.MOUSE, onMouseMove)
36node.setEventHandler(avg.CURSORUP, avg.MOUSE, onMouseUp)

If you remove the set/releaseEventCapture() calls, dragging will only work as long as you move slowly and don't leave the area of the node with the cursor. The code to duplicate this functionality without event capture is messy, especially if you need to support multitouch and several elements can be dragged at once. For multitouch devices, set/releaseEventCapture() take a cursor id as parameter so you can specify which finger corresponds to which user interface element.