Mouse Interactions

The simplest mouse interaction is to wait for the user to click before proceeding in the program. Suppose the 3D display is in scene, the default window created by VPython. Here is a way to wait for a mouse click, which is defined as the mouse button being pressed and released without moving the mouse (the event occurs when the mouse button is released):

ev = scene.mouse.getclick()

You can use the package of information contained in the variable "ev":

sphere(pos=ev.pos, radius=0.1)

Starting with VPython 6, an alternative for waiting for a mouse click is to wait for various kinds of mouse or keyboard events:

The first statement, scene.waitfor('click'), makes the display be the focus of keyboard input.

The object scene.mouse contains lots of information about the current state of the mouse, which you can interrogate at any time:

pos The current 3D position
of the mouse cursor; scene.mouse.pos. VPython
always chooses a point in the plane parallel to the screen and passing through scene.center. (See Projecting
mouse information onto a given plane for other options.)

pick The nearest object
in the scene which falls under the cursor, or None. At present curve, label, helix, extrusion, and faces cannot be picked. The picked object is scene.mouse.pick.

pickpos The 3D point on
the surface of the picked object which falls under the cursor, or None; scene.mouse.pickpos.

camera The read-only current
position of the camera as positioned by the user, scene.mouse.camera.
For example, mag(scene.mouse.camera-scene.center) is the distance from the center of the scene to the current position of the
camera. If you want to set the camera position and direction by program, use scene.forward and scene.center,
described in Controlling Windows.

ray A unit vector pointing
from camera in the direction of the mouse cursor. The points under the mouse
cursor are exactly { camera + t*ray for t>0}.

The camera and ray attributes together define all of the
3D points under the mouse cursor.

The mouse routines can handle a three-button mouse,
with "left",
"right", and "middle" buttons. For systems with
a two-button mouse, the "middle" button consists of the
left and right buttons pressed together. For the Macintosh one-button
mouse, the right button is invoked by holding down the Command key (normally used for rotating the camera view),
and the middle button is invoked by holding down the Option key (normally used for zooming the camera view).

Design for left-button events if possible

VPython continues to provide the basic mouse event functionality for handling events from right and middle buttons when userspin or userzoom is disabled, out of concern for supporting old programs. However, it has become evident that there are limitations to this approach which could preclude some kinds of mouse handling that people might want to do in the future. For example, you might want to allow userspin with right drags yet also pick up right clicks. For that reason it is conceivable that future developments in this area might break existing programs, and therefore for maximum forward compatibility it is prudent to use only left-button interactions in new programs.

Polling and callback

There are two different ways to get a mouse event, "polling" and "callback". In polling, you continually check scene.mouse.events to see whether any events are waiting to be processed, and you use scene.mouse.getevent() to get the next event to process. Prior to VPython 6, this was the only way you could handle mouse or keyboard events.

If you use the callback method, you specify a function to be executed when a specific type of event occurs, and the function is sent the event information when the specified type of event occurs. For many purposes this is a better way to handle mouse and keyboard events, and we will discuss it first. Programs that use polling will continue to work, but you cannot mix polling and callback approaches: you must use one or the other in a program.

Handling events with callbacks

Here is a simple example of how to use callbacks to process click events:

We define a "function" named "change". Then we "bind" this function to click events occurring in the display named "scene". Whenever VPython detects that a click event has occurred, VPython calls the bound function, which in this case toggles the sphere's color between cyan and red.

This operation is called a "callback" because with scene.bind you register with VPython that you want to be called back any time there is a click event. Here are the built-in events that you can specify in a bind operation:

The event 'mousedown' or 'mouseup' occurs when you press or release the left button on the mouse, and the 'mousemove' event occurs whenever the mouse moves, whether or not a button is depressed. The events 'keydown' and 'keyup' are discussed in the keyboard section. A 'redraw' event occurs just before the 3D scene is redrawn on the screen, and a 'draw_complete' event occurs just after the redrawing (these event have rather technical uses such as timing how often redrawings occur, or how much time they take).

You can bind more than one event to a function. The following will cause the callback function to be executed whether you click with the mouse or press a key on the keyboard:

scene.bind('click keydown', change)

The example program eventHandlers.py illustrates the callback method for handling many kinds of events.

Details of the event

You can get detailed information about the event by writing the callback function like this (note the variable 'evt' in parentheses):

def info(evt):
print(evt.event, evt.pos, evt.button)

Here we specify an argument in the definition of the callback function ('evt' in this case). When the function is called due to a specified event happening, VPython sends the function the information contained in scene.mouse, plus 'event', which is the name of the event that triggered the callback, such as 'mousedown' or 'click'. The name of the argument need not be 'evt'; use whatever name you like. In addition to evt.event and evt.button, there is further event information in the form of evt.press, evt.click, evt.drag, evt.drop, and evt.release (see details in the section on polling), but this information is more relevant when using polling rather than callbacks to get events.

You can optionally have VPython send the callback function an additional argument. Here is a revised version of the color-change example, in which in the bind operation we specify an additional argument to be sent, in this case a list of objects whose colors should toggle:

Normally, only the left mouse button will trigger an event, but if you specify scene.userspin = False, so the right button is no longer bound to camera rotation, clicking with the right mouse button will cause a callback. Similarly, if you specify scene.userzoom = False, you can click with the middle button (or left+right buttons).

Unbinding

Suppose you executed scene.bind('mousedown mousemove', Drag), but now you no longer want to send mousemove events to that function. Do this:

scene.unbind('mousemove', Drag)

You can also leave a function bound but start and stop having events sent to it:

We bind click events to the function clickFunc, and we bind our own special event type 'color_the_ball' to the function ballFunc. The function clickFunc is executed when the user clicks the mouse. This function creates a small sphere at the location of the mouse click, then triggers an event 'color_the_ball', with the effect of passing to the function ballFunc the sphere object. Finally ballFunc applies a color to the sphere. (Obviously one could color the sphere in clickFunc; the example is just for illustration of the basic concept.)

Handling events with polling

The following information on how to handle events using polling is still valid, but you are encouraged to consider using the more powerful callback approach when writing new programs. Remember that you cannot mix the two schemes. You can use either callback or polling in a program, but not both.

The simplest polling mouse interaction is to wait for a mouse click:

scene.mouse.getclick() Wait for a mouse click. If you say m = scene.mouse.getclick(), the variable m gives information about the event. For example, m.pos is the location of the mouse at the time of the click event.

It is a useful debugging technique to insert scene.mouse.getclick() into your program at a point where you would like to stop temporarily to examine
the scene. Then just click to proceed.

In the Drag
example you will see how to use event-handling functions to process mouse events continuously.

events The number of events
(press, click, drag, or drop) which have been queued; e.g. scene.mouse.events.scene.mouse.events = 0 may be used to discard
all input. No value other than zero can be assigned.

getevent() Obtains the
earliest mouse event and removes it from the input queue. If no events are
waiting in the queue (that is, if scene.mouse.events is zero), getevent() waits until the user enters
a mouse event (press, click, drag, or drop). getevent() returns an object with attributes similar to a mouse object: pos, button, pick, pickpos, camera, ray, project(), alt, ctrl, and shift.
These attributes correspond to the state of the mouse when the event took
place. For example, after executing mm = scene.mouse.getevent() you can look at the various properties of this event, such as mm.pos, mm.pick, mm.drag (see below), etc.

The getevent() function provides additional information, in addition to the usual information such as pos or pick:

press = 'left'
for a press event, or 'right'
or 'middle', or None. That is, if you execute mm = scene.mouse.getevent(), mm.press will be 'left', 'right', 'middle', or None. A press event occurs when a mouse button
is depressed.

click = 'left' for
a click event, or 'right' or 'middle', or None. A click event occurs when all mouse buttons
are released with no movement of the mouse. (This is also a release event.) Note that a click event happens when the mouse button is released. See Click example.

drag = 'left' for
a drag event, or 'right' or 'middle', or None;
in this case posand other attributes correspond to the state
of the mouse at the time of the original press event, so as not to lose initial
position information. A drag event occurs when the mouse is moved
slightly after a press event, with mouse buttons still down. This can be used to signal the beginning of dragging an
object. See Drag example.

drop = 'left' for
a drop event, or 'right' or 'middle', or None. A drop event occurs when the mouse buttons
are released after a drag event. (This is also a release event.)

release = 'left'
following click and drop events, indicating which button was released,
or 'right' or 'middle', or None. A release event occurs when the mouse buttons
are released after a click or drag event.

button = 'left', 'right', or 'middle'.

If you are interested in every type of event (press, click,
drag, and drop), you must use events and getevent().
If you are only interested in left click events (left button down and up without
significant mouse movement), you can use clicked and getclick():

clicked The number of left
clicks which have been queued; e.g. scene.mouse.clicked.
This does not include a count of nonclick events (press, drag, or drop).

getclick() Obtains the
earliest mouse left click event (pressing the left button and releasing it
in nearly the same position) and removes it from the input queue, discarding
any earlier press, drag, or drop events. If no clicks are waiting in the queue
(that is, if scene.mouse.clicked is zero), getclick() waits until the user clicks. Otherwise getclick() is just like getevent().

It is a useful debugging technique to insert scene.mouse.getclick() into your program at a point where you would like to stop temporarily to examine
the scene. Then just click to proceed.

Between a drag event (start of dragging) and a drop event
(end of dragging), there are no mouse events but you can examine the continuously
updated position of the mouse indicated by scene.mouse.pos.

Normally, dragging with right or middle button represents
spin or zoom, and is handled automatically by VPython, so you can check for
left-button drag or drop events simply by checking whether drag or drop is true (in Python, a nonempty string
such as 'left' is true, None is false). Unless you disable user zoom (scene.userzoom
= False), press, click, drag, drop,
and release with the middle button are invisible
to your program. Unless you disable user spin (scene.userspin
= False), press, click, drag, drop,
and release with the right button are invisible
to your program.

Projecting
mouse position onto a given plane

Here is a way to get the mouse position relative to a particular
plane in space:

Here a plane is specified by its normal and a point in the plane, and if point is not
specified, the plane passes through the origin. You obtain the 3D location in the plane where the user sees the mouse cursor. If the projection of the mouse misses the plane, as would happen if the plane is seen edge-on, the result is the special Python value None.

In the example shown above, the user of your program will
be able to use the mouse to place balls in a plane parallel to the xy plane,
a height of 3 above the xy plane, no matter how the user has rotated the point
of view.

You can instead specify a perpendicular distance d from the origin to the plane that is perpendicular to the specified normal.
The example above is equivalent to

temp = scene.mouse.project(normal=(0,1,0), d=3)

Pausing for mouse or keyboard input

Often you want to pause for either mouse or keyboard input. You can copy the following function into your program, and then insert pause() wherever you want to pause.