Input¶
Now, let us handle users’ input. We’ll start with mouse handling.
To handle mouse input, you need to define event handlers in your
GraphicsObject
.
These are methods called on_pointer_motion
, on_pointer_press
, and so
on.
Each one is called with a pointer (cursor) identifier, coordinates, and
possibly other keyword arguments.
The pointer
identifier is currently always the string 'mouse'
.
If you hook your program up to a different input system than a traditional
mouse-based computer, such as a multi-touch screen, you can use different
pointer identifiers to differentiate between different pointers [1].
The coordinates are more interesting.
These come in three arguments, x, y, z
[2], and represent the position
of the pointer in the object’s own local coordinates.
The keyword arguments vary depending on the event type. For example, press and drag events will be called with a “button” argument. The methods need to accept any additional arguments that might be passed to them (for example, if someone connects Gillcup Graphics to a tablet, you might start getting pressure/tilt information in the events).
For motion
and press
events, the handler can return a true value to
“claim” the event.
Once claimed, events don’t propagate to any objects that might be below the
claimer.
For press
events, the claimer will also exclusively get subsequent drag
and release
events.
Enough theory! The following example is a bit less minimal than the previous ones, but it should illustrate typical Gillcup Graphics usage better:
from __future__ import division
import gillcup
import gillcup_graphics
class DemoRect(gillcup_graphics.Rectangle):
"""A rectangle that goes purple on hover and blue on click/hold/drag
"""
def __init__(self, parent, clock, **kwargs):
super(DemoRect, self).__init__(parent, **kwargs)
self.clock = clock
self.pointers_in = set()
self.pointers_pressed = set()
def on_pointer_motion(self, pointer, x, y, z, **kwargs):
if pointer not in self.pointers_in:
self.clock.schedule(gillcup.Animation(self, 'red', 0, time=0.15))
self.pointers_in.add(pointer)
return True
def on_pointer_leave(self, pointer, x, y, z, **kwargs):
self.pointers_in.remove(pointer)
if not self.pointers_in:
self.clock.schedule(gillcup.Animation(self, 'red', 1, time=0.15))
return True
def on_pointer_press(self, pointer, x, y, z, button, **kwargs):
if not self.pointers_pressed:
self.clock.schedule(gillcup.Animation(self, 'green', 0, time=0.15))
self.pointers_pressed.add((pointer, button))
return True
def on_pointer_release(self, pointer, x, y, z, button, **kwargs):
self.pointers_pressed.remove((pointer, button))
if not self.pointers_pressed:
self.clock.schedule(gillcup.Animation(self, 'green', 1, time=0.15))
return True
class DraggableRect(gillcup_graphics.Rectangle):
"""A rectangle that can be dragged around, but is "transparent" to hover
N.B.: This simple mechanism will not work with objects that are scaled or
rotated.
"""
def __init__(self, parent, **kwargs):
super(DraggableRect, self).__init__(parent, **kwargs)
self.drag_starts = {}
def on_pointer_press(self, pointer, x, y, z, button, **kwargs):
self.drag_starts[pointer, button] = x, y
return True
def on_pointer_drag(self, pointer, x, y, z, button, **kwargs):
start_x, start_y = self.drag_starts[pointer, button]
self.x += x - start_x
self.y += y - start_y
return True
def on_pointer_release(self, pointer, x, y, z, button, **kwargs):
del self.drag_starts[pointer, button]
return True
class DemoLayer(gillcup_graphics.Layer):
def __init__(self, clock, *args, **kwargs):
super(DemoLayer, self).__init__(*args, **kwargs)
n = 10
for x in range(n):
for y in range(n):
rct = DemoRect(self, clock, scale=(1 / n, 1 / n),
position=(x / n, y / n))
if (x, y) == (5, 5):
rct.rotation = 30
rct.blue = 0.7
DraggableRect(self, size=(0.05, 0.05), color=(0, 1, 0),
position=(1 / 3, 1 / 3))
if __name__ == '__main__':
clock = gillcup_graphics.RealtimeClock()
layer = DemoLayer(clock)
window = gillcup_graphics.Window(layer, width=500, height=500)
gillcup_graphics.run()
Let’s take it one class at a time, this time.
The DemoRect
class defines a rectangle that changes color depending on
mouse input.
In the on_pointer_motion
and on_pointer_leave
, we keep track of which
pointers are “hovering” over.
If there are any, it changes color to cyan (or more precisely, becomes “less
red”).
When the last pointer leaves, it switches back to white (or, “full red”).
The on_pointer_press
and on_pointer_leave
do a similar thing with
button-press-based events, except for each pointer they also remember which
buttons are pressed.
From the time a button is pressed over the rectangle, to the time the last such
button is released, the square is “less green”.
This nicely illustrates the dragging concept: even if you drag your mouse out
of a rectangle, it still counts as dragging that rectangle, and the “release”
events will fire even if the mouse is outside at that time.
The DraggableRect
uses press
, drag
, and release
events to
implement an object that can be dragged around: it remembers where, relative
to itself, the drag started, and when dragged, moves so that the pointer is
at the same place (again, relative to the DraggableRect).
Finally, the DemoLayer
is just a container for a hundred instances of
DemoRect
and a DraggableRect
. One DemoRect
is rotated and
discolored to show what happens with transformations and overlapping.
[1] | Of course, you’d need to do such hooking up manually, by calling
pointer_event() of your root layer. |
[2] | The z coordinate will generally be zero. |