Defining User Interaction This tutorial will show
you how to design the set of event handlers that will define
the user experience. It will cover how to add mouse
and keyboard event handlers to the main camera
and other nodes, as well as how to filter and consume events. |
|
 Download the complete code sample in
Java or
C#.
1.
Setup
We need to create a window with a Piccolo canvas, so that
we can add the interface components to the canvas.
- First you will need to reference the appropriate
packages/namespaces. Add the following lines to the top of
your code file:
Java |
C#
import edu.umd.cs.piccolo.*;
import edu.umd.cs.piccolo.event.*;
import edu.umd.cs.piccolo.nodes.*;
import edu.umd.cs.piccolo.util.*;
import edu.umd.cs.piccolox.*;
Web Accessibility
using UMD.HCIL.Piccolo;
using UMD.HCIL.Piccolo.Nodes;
using UMD.HCIL.Piccolo.Event;
using UMD.HCIL.Piccolo.Util;
using UMD.HCIL.PiccoloX;
Web Accessibility
The first line adds the base Piccolo types, such as PNode .
The second line includes the default node types that Piccolo
provides, all of which extend PNode . The
third line includes the basic event types. The fourth line
includes several utility classes. And the last line
includes various "extras," such as PForm in .NET and
PFrame in Java, which will be used below.
- Next we will extend the
PForm class in .NET or the PFrame
class in Java. This is a convenience class that adds a PCanvas
to a window. When you extend this class, you should NOT add your Piccolo code
to the constructor. Instead, you should
override the
initialize method and add all of your Piccolo code
there. See the
FAQ for more details.
Java |
C#
public class InteractionFrame extends PFrame {
public void initialize() {
//Add Piccolo code here.
}
}
Web Accessibility
public class InteractionForm : PForm {
public override void Initialize() {
//Add Piccolo code here.
}
}
Web Accessibility
2.
Create a Camera Event Listener
Event listeners can be attached to any node in the hierarchy.
The events that you get depend on the node that you have registered
with. For example you will only get mouse moved events when
the mouse is over the node that you have registered with, not when
the mouse is over some other node.
As explained in
Piccolo Patterns, events are dispatched to the PPickPath
associated with the appropriate focus node (MouseOver, MouseFocus or
KeyboardFocus). They percolate up the pick path, giving each
node along the way a chance to handle the event if that node has a
registered event handler. The events will keep
percolating up the pick path until they are consumed by a node's
event handler or they reach the originating camera node. Often
you will attach event handlers directly to the main camera, in order
to receive all events that come from the canvas. Piccolo
comes with event listeners that let the user zoom and pan the
viewpoint and drag nodes in the interface. You can use the
default event listeners directly or you can define your own.
Here, we will create a new kind of event listener and add it to the
camera.
- We will create a squiggle event handler, that draws
squiggles as the mouse is dragged along the canvas. To do
this, we will extend PBasicInputEventHandler, the standard class
in Piccolo that is used to register for mouse and keyboard
events on a PNode. Add the following internal class
beneath the
initialize method.
Java |
C#
public class SquiggleHandler extends PBasicInputEventHandler {
protected PCanvas canvas;
// The squiggle that is currently getting created.
protected PPath squiggle;
public SquiggleHandler(PCanvas aCanvas) {
canvas = aCanvas;
setEventFilter(new PInputEventFilter(InputEvent.BUTTON1_MASK));
}
public void mousePressed(PInputEvent e) {
super.mousePressed(e);
Point2D p = e.getPosition();
// Create a new squiggle and add it to the canvas.
squiggle = new PPath();
squiggle.moveTo((float) p.getX(), (float) p.getY());
squiggle.setStroke(new BasicStroke(
(float) (1 / e.getCamera().getViewScale())));
canvas.getLayer().addChild(0, squiggle);
// Reset the keydboard focus.
e.getInputManager().setKeyboardFocus(null);
}
public void mouseDragged(PInputEvent e) {
super.mouseDragged(e);
// Update the squiggle while dragging.
updateSquiggle(e);
}
public void mouseReleased(PInputEvent e) {
super.mouseReleased(e);
// Update the squiggle one last time.
updateSquiggle(e);
squiggle = null;
}
public void updateSquiggle(PInputEvent aEvent) {
// Add a new segment to the squiggle
// from the last mouse position to
// the current mouse position.
Point2D p = aEvent.getPosition();
squiggle.lineTo((float) p.getX(), (float) p.getY());
}
}
Web Accessibility
class SquiggleHandler : PBasicInputEventHandler {
protected PCanvas canvas;
// The squiggle that is currently getting created.
protected PPath squiggle;
// The last mouse position.
protected PointF lastPoint;
public SquiggleHandler(PCanvas canvas) {
this.canvas = canvas;
}
public override void OnMouseDown(object sender, PInputEventArgs e) {
base.OnMouseDown (sender, e);
// Create a new squiggle and add it to the canvas.
squiggle = new PPath();
squiggle.Pen = new Pen(Brushes.Black,
(float)(1/ e.Camera.ViewScale));
canvas.Layer.AddChild(0, squiggle);
// Save the current mouse position.
lastPoint = e.Position;
// Reset the keyboard focus.
e.InputManager.KeyboardFocus = null;
}
public override void OnMouseDrag(object sender, PInputEventArgs e) {
base.OnMouseDrag (sender, e);
// Update the squiggle while dragging.
UpdateSquiggle(e);
}
public override void OnMouseUp(object sender, PInputEventArgs e) {
base.OnMouseUp (sender, e);
// Update the squiggle one last time.
UpdateSquiggle(e);
squiggle = null;
}
protected void UpdateSquiggle(PInputEventArgs e) {
// Add a new segment to the squiggle
// from the last mouse position to
// the current mouse position.
PointF p = e.Position;
if (p.X != lastPoint.X || p.Y != lastPoint.Y) {
squiggle.AddLine(lastPoint.X, lastPoint.Y, p.X, p.Y);
}
lastPoint = p;
}
public override bool DoesAcceptEvent(PInputEventArgs e) {
// Filter out everything but left mouse button events.
return (base.DoesAcceptEvent(e)
&& e.IsMouseEvent && e.Button == MouseButtons.Left);
}
}
Web Accessibility
PBasicInputEventHandler is
the top-level event handler class. It implements the
PInputEventListener interface. All event listeners must
implement this interface.
Typically, you will not implement it directly.
Instead, you will usually create event handlers by extending the
existing event handler classes. PBasicInputEventListener provides empty
methods for all of the basic input event types. You simply
override the methods for the events you are interested in
handling. In the above example we override the appropriate
methods for handling mouse pressed, dragged and released events.
When the mouse is pressed, we create a new PPath
called squiggle and add it to the canvas's main layer.
Notice that we set the path's pen width to 1 / ViewScale, so
that we will always draw with a uniform width. We also
save the current mouse position as the lastPoint .
When the mouse is dragged, we call UpdateSquiggle() .
This method will add a new line segment from the lastPoint to
the current mouse position. Finally it will set the
lastPoint to be the current mouse position.
When the mouse is released, we will call updateSquiggle one
more time and then set our current squiggle to null.
Finally, you might notice that the C# version of this code
snippet, overrides the DoesAcceptEvent method. This method
is part of the PInputEventListener interface and is used to
implement event filtering in Piccolo.NET. Any event handler class can
override this method and return true if it wants to accept an
event, or false otherwise. Filtered events will never be
dispatched to the event handler. In this case, we only
accept left mouse button events, so that the right mouse button
cannot be used to draw squiggles.
- Now that we have created our squiggle event handler, we need
to register it with a node so that it can receive events.
We will add the squiggle event handler to the main camera node.
Add the following lines of code to the
initialize
method.
Java |
C#
// Remove the pan event handler that is installed by default so that it
// does not conflict with our new squiggle handler.
getCanvas().setPanEventHandler(null);
// Create a squiggle handler and register it with the Canvas.
PBasicInputEventHandler squiggleHandler = new SquiggleHandler(getCanvas());
getCanvas().addInputEventListener(squiggleHandler);
Web Accessibility
// Remove the pan event handler that is installed by default so that it
// does not conflict with our new squiggle handler.
Canvas.PanEventHandler = null;
// Create a squiggle handler and register it with the Canvas.
PBasicInputEventHandler squiggleHandler = new SquiggleHandler(Canvas);
Canvas.AddInputEventListener(squiggleHandler);
Web Accessibility
First we remove the default pan event handler because we
don't want it to conflict with our squiggle event handler.
Since we are not using our right mouse button, we do not have to
remove the default zoom event handler.Next we create our
squiggle event handler. Notice that the Java version of
this code snippet calls the setEventFilter() method
and passes it a new instance of PInputEventFilter . In
Piccolo.Java, event filtering is accomplished by creating a new
event filter object and setting various masks on that object.
In particular, an event will be accepted if it contains all the
modifiers listed in the andMask , at least one of the modifiers
listed in the orMask , and none of the modifiers listed in the
notMask . In this case, we set the andMask to a
BUTTON1_MASK , so that it will only accept left mouse button
events. Finally, we register our new squiggle event handler to
receive events from the camera. Notice that we do this by
calling the canvas's AddInputEventListener() method. This is
just a convenience method that adds the given event listener to
the main camera associated with the canvas. We could have
also gotten the camera from the canvas and added the event
listener directly.
3. Create a Node Event
Listener
Now that we have added an event handler to the camera,
lets try adding an event handler to another node.
- First, we will create green rectangle node and add it to the
main layer. Add the following lines of code to the
initialize
method.
Java |
C#
// Create a green rectangle node.
PNode nodeGreen = PPath.createRectangle(0, 0, 100, 100);
nodeGreen.setPaint(Color.GREEN);
getCanvas().getLayer().addChild(nodeGreen);
Web Accessibility
// Create a green rectangle node.
PNode nodeGreen = PPath.CreateRectangle(0, 0, 100, 100);
nodeGreen.Brush = Brushes.Green;
Canvas.Layer.AddChild(nodeGreen);
Web Accessibility
- Next, we will create a new event handler that will change
our node's
color to orange when the mouse is pressed over the node and back to green, when it is released. Additionally, the event
handler will move the node when the mouse is dragged. We
will be careful to consume these events, since we don't want
dragging the node around to draw squiggles underneath of it. Note,
this is a simple example, but most of the time the best way to
implement dragging is to use a PDragEventHandler. Add the following lines of
code to the project. The java snippet should be added
directly to the
initialize method. The .NET
snippet should be added beneath initialize .
Java |
C#
// Attach event handler directly to the node.
nodeGreen.addInputEventListener(new PBasicInputEventHandler() {
public void mousePressed(PInputEvent event) {
event.getPickedNode().setPaint(Color.ORANGE);
event.getInputManager().setKeyboardFocus(event.getPath());
event.setHandled(true);
}
public void mouseDragged(PInputEvent event) {
PNode aNode = event.getPickedNode();
PDimension delta = event.getDeltaRelativeTo(aNode);
aNode.translate(delta.width, delta.height);
event.setHandled(true);
}
public void mouseReleased(PInputEvent event) {
event.getPickedNode().setPaint(Color.GREEN);
event.setHandled(true);
}
public void keyPressed(PInputEvent event) {
PNode node = event.getPickedNode();
switch (event.getKeyCode()) {
case KeyEvent.VK_UP:
node.translate(0, -10f);
break;
case KeyEvent.VK_DOWN:
node.translate(0, 10f);
break;
case KeyEvent.VK_LEFT:
node.translate(-10f, 0);
break;
case KeyEvent.VK_RIGHT:
node.translate(10f, 0);
break;
}
}
});
Web Accessibility
protected void nodeGreen_MouseDown(object sender, PInputEventArgs e) {
PNode aNode = (PNode)sender;
aNode.Brush = new SolidBrush(Color.Orange);
e.InputManager.KeyboardFocus = e.Path;
e.Handled = true;
}
protected void nodeGreen_MouseDrag(object sender, PInputEventArgs e) {
PNode aNode = (PNode)sender;
SizeF delta = e.GetDeltaRelativeTo(aNode);
aNode.TranslateBy(delta.Width, delta.Height);
e.Handled = true;
}
protected void nodeGreen_MouseUp(object sender, PInputEventArgs e) {
PNode aNode = (PNode)sender;
aNode.Brush = new SolidBrush(Color.Green);
e.Handled = true;
}
protected void nodeGreen_KeyDown(object sender, PInputEventArgs e) {
PNode node = (PNode)sender;
switch (e.KeyCode) {
case Keys.Up:
node.TranslateBy(0, -10);
break;
case Keys.Down:
node.TranslateBy(0, 10);
break;
case Keys.Left:
node.TranslateBy(-10, 0);
break;
case Keys.Right:
node.TranslateBy(10, 0);
break;
}
}
Web Accessibility
The
first thing you might notice is that the C# version of this code
snippet does not extend PBasicInputEventHandler . Instead, it
consists of several distinct methods. In Piccolo.NET there
are actually two ways to create event handlers. The first
is to use the standard approach of extending the event handler
classes. But, you can also add event handler methods directly to a
node, in the same way that you can add event handler methods
directly to Control. PNode defines Events for
each of the basic input events. The benefit of doing
things this way is that it takes fewer lines of code. The
benefit of using classes is that your event handlers will be
reusable and capable of maintaining state.When the mouse is
pressed, we get the pressed node and set it's fill color to be
orange. Next, we get the InputManager from the event and
set it's keyboard focus node to be the current pick path
generated from this mouse pressed event. This ensures that
all future keyboard events will be dispatched to that pick path.
Otherwise, our key pressed event handler would never get called.
For more information about focus nodes, picking and event
dispatch, see Piccolo Patterns. Finally, we consume the event by
marking it as handled. If we did not do this, the event
would percolate up to the camera and get dispatched to our
squiggle event handler.
When the mouse is dragged, we get the distance that the mouse
has moved from it's last position and we translate the node by
that amount. This is how dragging is implemented.
Notice that we get the distance by calling
GetDeltaRelativeToNode , ensuring that the value returned is in
the local coordinate system of this node. We also mark
this event as handled.
When the mouse is released, we set the node's fill back to
green and mark the event as handled.
When an arrow key is pressed, we will translate the node by
10 in the direction of the arrow. Note the key pressed
event handler will only get called when our node has the
keyboard focus. So, once we click on the node, we will be
able to move it with the arrow keys . But, after we draw a
squiggle, the arrow keys will no longer move the node, since the
squiggle event handler resets the keyboard focus to null.
We would then have to click on the node again to give it back
the keyboard focus.
- Finally, we will register the node event handler with our node. Add
the following lines of code to the
initialize
method.
Java |
C#
// Attach event handler directly to the node.
The Java version defines and attaches the event handler using an anonymous
class. See previous code sample.
Web Accessibility
// Attach event delegates directly to the node.
nodeGreen.MouseDown += new PInputEventHandler(nodeGreen_MouseDown);
nodeGreen.MouseDrag += new PInputEventHandler(nodeGreen_MouseDrag);
nodeGreen.MouseUp += new PInputEventHandler(nodeGreen_MouseUp);
nodeGreen.KeyDown += new PInputEventHandler(nodeGreen_KeyDown);
Web Accessibility
Notice that the C# version of this code snippet adds each of
the event handler methods individually, using the
PInputEventHandler delegate. We could have also extended
PBasicInputEventHandler and added a new event handler class to
the node, as the Java code does.
|