Getting Started
Here you will find both basic and advanced tutorials to help you get started using Piccolo2D. All tutorials provide examples in both Java and C#. This section assumes you have read Piccolo2D Patterns and have a basic understanding of the concepts presented there.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 Piccolo2D 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:
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.*;
using UMD.HCIL.Piccolo; using UMD.HCIL.Piccolo.Event; using UMD.HCIL.Piccolo.Nodes; using UMD.HCIL.Piccolo.Util; using UMD.HCIL.PiccoloX;
The first line adds the base Piccolo2D types, such as
PNode
. The second line includes the basic event types. The third line includes the default node types that Piccolo2D provides, all of which extendPNode
. The fourth line includes several utility classes, and the last line includes various "extras," such asPForm
in .NET andPFrame
in Java, which will be used below. -
Next we will extend the
PForm
class in .NET or thePFrame
class in Java. This is a convenience class that adds aPCanvas
to a window. When you extend this class, you should NOT add your Piccolo2D code to the constructor. Instead, you should override theinitialize
method and add all of your Piccolo2D code there. See the FAQ for more details.public class InteractionFrame extends PFrame { public void initialize() { //Add Piccolo2D code here. } }
public class InteractionForm : PForm { public override void Initialize() { //Add Piccolo2D code here. } }
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 Piccolo2D 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.
Piccolo2D 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 Piccolo2D that is used to register for mouse and keyboard events on a PNode. Add the following internal class beneath the
initialize
method.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()); } }
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); } }
PBasicInputEventHandler
is the top-level event handler class. It implements thePInputEventListener
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 thelastPoint
.When the mouse is dragged, we call
UpdateSquiggle()
. This method will add a new line segment from thelastPoint
to the current mouse position. Finally it will set thelastPoint
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 Piccolo2D.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.// 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);
// 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);
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 ofPInputEventFilter
. In Piccolo2D.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 theandMask
, at least one of the modifiers listed in theorMask
, and none of the modifiers listed in thenotMask
. In this case, we set theandMask
to aBUTTON1_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.// Create a green rectangle node. PNode nodeGreen = PPath.createRectangle(0, 0, 100, 100); nodeGreen.setPaint(Color.GREEN); getCanvas().getLayer().addChild(nodeGreen);
// Create a green rectangle node. PNode nodeGreen = PPath.CreateRectangle(0, 0, 100, 100); nodeGreen.Brush = Brushes.Green; Canvas.Layer.AddChild(nodeGreen);
-
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 beneathinitialize
.// 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; } } });
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; } }
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 Piccolo2D.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 Piccolo2D 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.// Attach event handler directly to the node. The Java version defines and attaches the event handler using an anonymous class. See previous code sample.
// 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);
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 extendedPBasicInputEventHandler
and added a new event handler class to the node, as the Java code does.