Building the Interface This tutorial will show you
how to build a Piccolo interface. It will cover how to
use the default nodes, how to create new nodes through
composition and how to create new nodes using inheritance. |
|
 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.Event;
using UMD.HCIL.Piccolo.Nodes;
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 basic event types. The third line
includes the default node types that Piccolo provides, all of
which extend PNode . 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 InterfaceFrame extends PFrame {
public void initialize() {
//Add Piccolo code here.
}
}
Web Accessibility
public class InterfaceForm : PForm {
public override void Initialize() {
//Add Piccolo code here.
}
}
Web Accessibility
2.
Add a Drag Event Handler.
Now lets define some interaction for the nodes that we
will add below.
By default, the pan and zoom event handlers are installed. We
will remove the pan event handler and add a drag event handler
instead so that the individual nodes can be dragged around.
Add the following lines of code to the initialize method.
Java |
C#
// Remove the Default pan event handler and add a drag event handler
// so that we can drag the nodes around individually.
getCanvas().setPanEventHandler(null);
getCanvas().addInputEventListener(new PDragEventHandler());
Web Accessibility
// Remove the default pan event handler and add a drag event handler
// so that we can drag the nodes around individually.
Canvas.PanEventHandler = null;
Canvas.AddInputEventListener(new PDragEventHandler());
Web Accessibility
3.
Add Some Default Nodes
Now we will add a few nodes to the interface.
In addition to PNode, Piccolo comes with three basic types of nodes (PPath, PText
and PImage), plus a few in the extras package. We will
add each of the basic types below.
- First we will use PNode directly. PNode is a
concrete class and can be added to the scene. By default,
a PNode will just fill its bounds with its brush. Add the
following lines of code to the
initialize method.
Java |
C#
// Create a node.
PNode aNode = new PNode();
// A node will not be visible until its bounds and brush are set.
aNode.setBounds(0, 0, 100, 80);
aNode.setPaint(Color.RED);
// A node needs to be a descendent of the root to be displayed.
PLayer layer = getCanvas().getLayer();
layer.addChild(aNode);
// A node can have child nodes added to it.
PNode anotherNode = new PNode();
anotherNode.setBounds(0, 0, 100, 80);
anotherNode.setPaint(Color.YELLOW);
aNode.addChild(anotherNode);
// The base bounds of a node are easy to change. Changing the bounds
// of a node will not affect its children.
aNode.setBounds(-10, -10, 200, 110);
// Each node has a transform that can be used to modify the position,
// scale or rotation of a node. Changing a node's transform, will
// transform all of its children as well.
aNode.translate(100, 100);
aNode.scale(1.5f);
aNode.rotate(45);
Web Accessibility
// Create a node.
PNode aNode = new PNode();
// A node will not be visible until its bounds and brush are set.
aNode.SetBounds(0, 0, 100, 80);
aNode.Brush = Brushes.Red;
// A node needs to be a descendent of the root to be displayed.
PLayer layer = Canvas.Layer;
layer.AddChild(aNode);
// A node can have child nodes added to it.
PNode anotherNode = new PNode();
anotherNode.SetBounds(0, 0, 100, 80);
anotherNode.Brush = Brushes.Yellow;
aNode.AddChild(anotherNode);
// The base bounds of a node are easy to change. Changing the bounds
// of a node will not affect it's children.
aNode.SetBounds(-10, -10, 200, 110);
// Each node has a transform that can be used to modify the position,
// scale or rotation of a node. Changing a node's transform, will
// transform all of its children as well.
aNode.TranslateBy(100, 100);
aNode.ScaleBy(1.5f);
aNode.RotateBy(45);
Web Accessibility
This snippet illustrates how to create a node and add it to
the scene. Typically you will add nodes to the main layer.
It also shows that there are two ways to affect the position and
size of a node, by changing its bounds or its transform.
The first approach does not affect the node's children, while
the second does. You can think of modifying a node's
transform as defining a new coordinate system for that node and
all of its children.
- PNode itself is not very
interesting. So, lets try some more advanced nodes.
Add the following lines of code to the
initialize method.
Java |
C#
// Add a couple of PPath nodes and a PText node.
layer.addChild(PPath.createEllipse(0, 0, 100, 100));
layer.addChild(PPath.createRectangle(0, 100, 100, 100));
layer.addChild(new PText("Hello World"));
// Here we create a PImage node that displays a thumbnail image
// of the root node. Then we add the new PImage to the main layer.
PImage image = new PImage(layer.toImage(300, 300, null));
layer.addChild(image);
Web Accessibility
// Add a couple of PPath nodes and a PText node.
layer.AddChild(PPath.CreateEllipse(0, 0, 100, 100));
layer.AddChild(PPath.CreateRectangle(0, 100, 100, 100));
layer.AddChild(new PText("Hello World"));
// Here we create a PImage node that displays a thumbnail image
// of the root node. Then we add the new PImage to the main layer.
PImage image = new PImage(layer.ToImage(300, 300));
layer.AddChild(image);
Web Accessibility
In the first two lines, we add a couple of path nodes.
PPath
represents a general path. You can create arbitrary paths by adding
line segments and arcs or you can use one of the static CreateXXX
methods to create a common shape. The Pen property in .NET
and the setStroke() and setStrokePaint() methods in java
define how the edge of a path is drawn, while the
Brush property in .NET and the setPaint() method in Java will define how the path is filled.
Next, we add a text
node. PText is a multi-line text node that will wrap its
text based on the width of the node's bounds.
Finally, we add an image node. PImage wraps an image so
that it can be added to the Piccolo hierarchy. Above, we
take advantage of the PNode.ToImage() method, which can be used to
get a thumbnail of any node.
4. Create a New Node using
Composition
The real power of Piccolo is the ability to make new
kinds of interface components. There are various way
to make new types of nodes. Here we will examine how
to create new nodes by combining several pre-existing nodes
together.
We will create a face node by adding the eyes and mouth as
the children of another node. Add the following lines of
code to the initialize method.
Java |
C#
PNode myCompositeFace = PPath.createRectangle(0, 0, 100, 80);
// Create parts for the face.
PNode eye1 = PPath.createEllipse(0, 0, 20, 20);
eye1.setPaint(Color.YELLOW);
PNode eye2 = (PNode) eye1.clone();
PNode mouth = PPath.createRectangle(0, 0, 40, 20);
mouth.setPaint(Color.BLACK);
// Add the face parts.
myCompositeFace.addChild(eye1);
myCompositeFace.addChild(eye2);
myCompositeFace.addChild(mouth);
// Don't want anyone grabbing out our eye's.
myCompositeFace.setChildrenPickable(false);
// Position the face parts.
eye2.translate(25, 0);
mouth.translate(0, 30);
// Set the face bounds so that it neatly contains the face parts.
PBounds b = myCompositeFace.getUnionOfChildrenBounds(null);
b.inset(-5, -5);
myCompositeFace.setBounds(b);
// Opps its to small, so scale it up.
myCompositeFace.scale(1.5);
layer.addChild(myCompositeFace);
Web Accessibility
PNode myCompositeFace = PPath.CreateRectangle(0, 0, 100, 80);
// Create parts for the face.
PNode eye1 = PPath.CreateEllipse(0, 0, 20, 20);
eye1.Brush = Brushes.Yellow;
PNode eye2 = (PNode) eye1.Clone();
PNode mouth = PPath.CreateRectangle(0, 0, 40, 20);
mouth.Brush = Brushes.Black;
// Add the face parts
myCompositeFace.AddChild(eye1);
myCompositeFace.AddChild(eye2);
myCompositeFace.AddChild(mouth);
// Don't want anyone grabbing out our eye's.
myCompositeFace.ChildrenPickable = false;
// Position the face parts.
eye2.TranslateBy(25, 0);
mouth.TranslateBy(0, 30);
// Set the face bounds so that it neatly contains the face parts.
RectangleF b = myCompositeFace.UnionOfChildrenBounds;
b.Inflate(5, 5);
myCompositeFace.Bounds = b;
// Opps its too small, so scale it up.
myCompositeFace.ScaleBy(1.5f);
layer.AddChild(myCompositeFace);
Web Accessibility
This works because of the hierarchical nature of Piccolo.
However, by default any node is pickable. "Picking" refers
to the process that determines which node is under the mouse
cursor. Events will be dispatched to the picked node
and passed to each ancestor that also intersects that point (see
the Defining User Interaction
tutorial). Typically event handlers will modify the picked
node in some way. For example the drag event handler, will
move the picked node, which means that the children can be
dragged around without moving the parent. In this case, we
only want to be able to drag the face as whole. So we make
the face's children not pickable. This means if you click
on an eye, the face will be picked instead. The drag event
handler moves the picked node by modifying its transform so the
eyes and the mouth will also be moved.
5. Create a New Node using
Inheritance
Of course, the ultimate creative control comes from
extending the existing node types to create new kinds of
nodes. Here we will use inheritance to build a new
kind of node.
- We will create a new node that will draw itself as an
ellipse by default and as a rectangle when the mouse is pressed
over the node. We will implement this class by extending
PPath so we don't have to create the ellipse code from scratch.
For an example of how you could create an ellipse node from
scratch see the
Creating Nodes Pattern. Add the following internal class beneath
the
initialize method.
Java |
C#
class ToggleShape extends PPath {
private boolean fIsPressed = false;
public ToggleShape() {
setPathToEllipse(0, 0, 100, 80);
addInputEventListener(new PBasicInputEventHandler() {
public void mousePressed(PInputEvent event) {
super.mousePressed(event);
fIsPressed = true;
repaint();
}
public void mouseReleased(PInputEvent event) {
super.mouseReleased(event);
fIsPressed = false;
repaint();
}
});
}
protected void paint(PPaintContext paintContext) {
if (fIsPressed) {
Graphics2D g2 = paintContext.getGraphics();
g2.setPaint(getPaint());
g2.fill(getBoundsReference());
} else {
super.paint(paintContext);
}
}
}
Web Accessibility
class ToggleShape : PPath {
private bool fIsPressed = false;
public bool FIsPressed {
set {
fIsPressed = value;
InvalidatePaint();
}
get { return fIsPressed; }
}
public ToggleShape() {
this.AddEllipse(0, 0, 100, 80);
}
public override void OnMouseDown(PInputEventArgs e) {
base.OnMouseDown (e);
FIsPressed = true;
}
public override void OnMouseUp(PInputEventArgs e) {
base.OnMouseUp (e);
FIsPressed = false;
}
protected override void Paint(PPaintContext paintContext) {
if (fIsPressed) {
Graphics g = paintContext.Graphics;
g.FillRectangle(this.Brush, this.Bounds);
}
else {
base.Paint(paintContext);
}
}
}
Web Accessibility
In the constructor we use the PPath to create
the ellipse. We store the fIsPressed
field to indicate whether or not the mouse is currently down
over the node. Notice that the set accessor for fIsPressed
sets the value and then calls InvalidatePaint() .
This method notifies the framework that the node needs to be
repainted. Piccolo will then invalidate the child
paint of all the ancestors of this node. Later the screen
damage will be collected for all the nodes with invalid paint
and eventually the Paint() method of those nodes will get
called. Notice that we override the Paint() method to
modify how the node will be drawn when it is repainted. When fIsPressed is
true, we will fill the bounds of the node. Otherwise, we
will just use the base implementation, which will only fill the
ellipse.
Finally, we also use event handlers to set fIsPressed to true
when the mouse is down over the node, and back to false, when
the mouse is released. For more about events, see the Defining User Interaction
tutorial.
- Now that we've created the new type, we have to actually add
the node to the interface. Add the following lines of code
to the
initialize method.
Java |
C#
ToggleShape ts = new ToggleShape();
ts.setPaint(Color.ORANGE);
layer.addChild(ts);
Web Accessibility
ToggleShape ts = new ToggleShape();
ts.Brush = Brushes.Orange;
layer.AddChild(ts);
Web Accessibility
|