Piccolo is a framework for building zoomable user interfaces. The elements of these interfaces can be scaled or animated, and react to user input. The user viewpoint may also be scaled and panned. Interfaces built with Piccolo can define your entire application interface, or they can be a small part of a large system.
Figure 1 shows a sequence of frames from a simple zooming interface created using Piccolo. This example shows the fundamental aspects of a Piccolo application. In this sequence the user is zooming in on a "Hello world!" interface object.
The objects on a piccolo canvas are nodes (instances of PNode
),
in this case a PText
node. Nodes are used to represent the discrete
components of an interface. The framework comes with some commonly
required nodes (shapes, images, and text) and the developer has the
opportunity to define new ones for their own interfaces.
Figure 1’s zooming interaction was created with the help of an
event listener (instances of PInputEventListener
). Event listeners
define how the interface reacts to user input. In this zooming
interaction the event listener reacted to mouse input by
manipulating the camera node's view transform to create the zooming
effect. Piccolo comes with ready-made event listeners for zooming
and panning camera nodes, and dragging nodes on the canvas.
All Piccolo interfaces need to be placed in a PCanvas
so that
they may be viewed and interacted with by the user. PCanvas
extends
the javax.swing.JComponent
class in Piccolo.Java and the equivalent
System.Windows.Forms.Control
class in Piccolo.NET.
As a result, piccolo can easily be integrated with both Java
Swing and Windows applications. In addition to hosting Piccolo in
the application interface, the canvas also maintains a camera and layer node. Scaling
and translating the camera's view transform over time is how zooming
and panning is accomplished. New nodes are
normally added to the canvas's layer node.
The interface depicted in figure 1 can be created with the following code:
|import edu.umd.cs.piccolo.PCanvas; import edu.umd.cs.piccolo.nodes.PText; import edu.umd.cs.piccolox.PFrame; // The PFrame class is a utility class that creates a new java window // and adds a PCanvas to it. You can override the initialize method and // start building your interface there. public class PiccoloExample extends PFrame { public void initialize() { PNode aNode = new PText("Hello World!"); // Add the node to the canvas layer so that it // will be displayed on the screen. getCanvas().getLayer().addChild(aNode); } public static void main(String[] args) { new PiccoloExample(); } }
using UMD.HCIL.Piccolo; using UMD.HCIL.Piccolo.Nodes; using UMD.HCIL.PiccoloX; // The PForm class is a utility class that creates a new window // and adds a PCanvas to it. You can override the Initialize method and // start building your interface there. public class PiccoloExample : PForm { public override void Initialize() { PText text = new PText("Hello World"); // Add the node to the canvas layer so that it // will be displayed on the screen. Canvas.Layer.AddChild(text); } [STAThread] static void Main() { Application.Run(new PiccoloExample()); } }
This creates the default zooming interface. A new text node is
added to the surface, and event handlers for zooming and panning are
automatically installed by the PCanvas
.
The Piccolo usage patterns are designed to get you up and running with Piccolo as quickly as possible. They provide quick "cookbook" explanations of the major things that can be done with Piccolo. To gain a better understanding of the Piccolo implementation and of its runtime behavior is see Implementation Patterns for Piccolo.
To use Piccolo.Java, you will need to add the appropriate .jar files to your classpath, and to do that you need to know what each .jar file contains, and how to build the framework.
Piccolo.Java comes with 4 .jar files:
If you downloaded the compiled distribution of Piccolo.Java these .jar files are located for you in "piccolo/build/". If you didn't then you will need to build them yourself. This is done by typing 'build all' from within the piccolo/ folder.
Piccolo.Java also comes with project files for the Eclipse IDE (www.eclipse.org), our favorite development environment for Piccolo.Java, and Java in general.
To use Piccolo.NET, you will need to add references to the appropriate .dll files in your Visual Studio Project.
Piccolo.NET comes with 2 .dll files and 2 .xml files:
You do not need to build either distribution of Piccolo.NET. The DLL distribution contains all four of these files at the top level. And, the source distribution contains these files in "Piccolo.NET/Bin/".
However, if you modify the source code, then you will need to rebuild the framework. To do this, open "Piccolo.NET/Source/Piccolo.Net.sln" in Visual Studio.NET. Then, from the "Build" menu select "Rebuild Solution." The new .dll files will be located in "Piccolo.NET/Source/Piccolo/bin/Debug" and "Piccolo.NET/Source/PiccoloX/bin/Debug".
Note: For more detailed instructions on setting up
Piccolo, see the README file that comes with each distribution
and the
Getting Started
tutorial.
There are an infinite variety of nodes that can be included in a zoomable user interface. And, there are several ways to make new nodes for each application.
PText
,
PPath
, and PImage
. These may be used ‘as is’ in a new
application, which saves developers the time and effort required
to create such elements themselves, but reduces the knowledge
they have about how that figure is implemented.
PNodes
are ‘almost but not quite’ what is
required for an interface. In such circumstances, it makes sense
to customize the existing node to fit the needs of the interface. This
saves time compared with starting from scratch and provides
insight into the organization of that node but subclassing will
increase the number of classes in the system and carries the
responsibility of ensuring that the original intent of that
inheritance hierarchy is maintained.
PNode
directly. PNode
already provides
default behavior for all operations (it is a concrete class). So,
it is fairly easy to subclass. Many subclasses will want
to override a few key methods: setBounds()
, intersects()
, and paint()
in Piccolo.Java and SetBounds()
, Intersects()
and Paint()
in
Piccolo.NET.Each visual interface element in Piccolo is a subclass of PNode
.
Nodes can be used directly or customized by sub classing or
composition.
The following code defines a new simple (without a border) ellipse node.
|public class SimpleEllipseNode extends PNode { private Ellipse2D ellipse; // This nodes uses an internal Ellipse2D to define its shape. public Ellipse2D getEllipse() { if (ellipse == null) ellipse = new Ellipse2D.Double(); return ellipse; } // This method is important to override so that the geometry of // the ellipse stays consistent with the bounds geometry. public boolean setBounds(double x, double y, double width, double height) { if(super.setBounds(x, y, width, height)) { ellipse.setFrame(x, y, width, height); return true; } return false; } // Non rectangular subclasses need to override this method so // that they will be picked correctly and will receive the // correct mouse events. public boolean intersects(Rectangle2D aBounds) { return getEllipse().intersects(aBounds); } // Nodes that override the visual representation of their super // class need to override a paint method. public void paint(PPaintContext aPaintContext) { Graphics2D g2 = aPaintContext.getGraphics(); g2.setPaint(getPaint()); g2.fill(getEllipse()); } }
public class SimpleEllipseNode : PNode { private GraphicsPath ellipse; // This node uses an internal GraphicsPath to define its shape. public GraphicsPath Ellipse { get { if (ellipse == null) ellipse = new GraphicsPath(); return ellipse; } } // This method is important to override so that the geometry of // the ellipse stays consistent with the bounds geometry. public override bool SetBounds(float x, float y, float width, float height) { if(base.SetBounds (x, y, width, height)) { Ellipse.Reset(); Ellipse.AddEllipse(x, y, width, height); return true; } return false; } // Non rectangular subclasses need to override this method so // that they will be picked correctly and will receive the // correct mouse events. public override bool Intersects(RectangleF bounds) { Region ellipseRegion = new Region(Ellipse); return ellipseRegion.IsVisible(bounds); } // Nodes that override the visual representation of their super // class need to override a paint method. protected override void Paint(PPaintContext paintContext) { Graphics g = paintContext.Graphics; if (Brush != null) { g.FillPath(Brush, Ellipse); } } }
Event listeners represent the modes of interaction between the user and the interface. Piccolo comes with event listeners that let the user zoom and pan their viewpoint, and drag nodes in the interface. An important part of designing an interface using Piccolo is to design the set of event listeners that will define the user experience.
Once created an event listener must be registered with a node so that it can receive events. Many event handlers register with the camera node so that they get all events that come from the canvas associated with that camera.
This example class creates an event listener that will create rectangle nodes on the canvas's layer when the user presses, drags, and then releases the mouse.
|// This event listener works by keeping track of the mouse press // location and the current mouse drag location. It then sizes the new // rectangle around those points. Note: The implementation of this event // handler could be simplified by sub classing PDragSequenceEventHandler. public class RectangleCreationEventHandler extends PBasicInputEventHandler { // The rectangle that is currently getting created. protected PPath rectangle; // The mouse press location for the current pressed, drag, release // sequence. protected Point2D pressPoint; // The current drag location. protected Point2D dragPoint; public void mousePressed(PInputEvent e) { super.mousePressed(e); PLayer layer = ((PCanvas)(e.getComponent())).getLayer(); // Initialize the locations. pressPoint = e.getPosition(); dragPoint = pressPoint; // create a new rectangle and add it to the canvas layer so // that we can see it. rectangle = new PPath(); rectangle.setStroke(new BasicStroke( (float)(1/ e.getCamera().getViewScale()))); layer.addChild(rectangle); // update the rectangle shape. updateRectangle(); } public void mouseDragged(PInputEvent e) { super.mouseDragged(e); // update the drag point location. dragPoint = e.getPosition(); // update the rectangle shape. updateRectangle(); } public void mouseReleased(PInputEvent e) { super.mouseReleased(e); // update the rectangle shape. updateRectangle(); rectangle = null; } public void updateRectangle() { // create a new bounds that contains both the press and // current drag point. PBounds b = new PBounds(); b.add(pressPoint); b.add(dragPoint); // Set the rectangles bounds. rectangle.setPathTo(b); } }
// This event listener works by keeping track of the mouse press // location and the current mouse drag location. It then sizes the new // rectangle around those points. Note: The implementation of this event // handler could be simplified by sub classing PDragSequenceEventHandler. public class RectangleCreationEventHandler : PBasicInputEventHandler { // The rectangle that is currently getting created. protected PPath rectangle; // The mouse press location for the current pressed, drag, release // sequence. protected PointF pressPoint; // The current drag location. protected PointF dragPoint; public override void OnMouseDown(object sender, PInputEventArgs e) { base.OnMouseDown (sender, e); PLayer layer = e.Canvas.Layer; // Initialize the locations. pressPoint = e.Position; dragPoint = pressPoint; // Create a new rectangle and add it to the canvas layer so // that we can see it. rectangle = new PPath(); rectangle.Pen = new Pen(Brushes.Black, (float)(1/ e.Camera.ViewScale)); layer.AddChild(rectangle); // update the rectangle shape. UpdateRectangle(); } public override void OnMouseDrag(object sender, PInputEventArgs e) { base.OnMouseDrag (sender, e); // Update the drag point location. dragPoint = e.Position; // Update the rectangle shape. UpdateRectangle(); } public override void OnMouseUp(object sender, PInputEventArgs e) { base.OnMouseUp (sender, e); // Update the rectangle shape. UpdateRectangle(); rectangle = null; } public void UpdateRectangle() { // Create a new bounds that contains both the press and // current drag point. RectangleF r = RectangleF.Empty; r = PUtil.AddPointToRect(r, pressPoint); r = PUtil.AddPointToRect(r, dragPoint); // Set the rectangles bounds. rectangle.PathReference.Reset(); rectangle.AddRectangle(r.X, r.Y, r.Width, r.Height); } }
The code to register this event listener with the canvas would look like this:
|getCanvas().addInputEventListener(new RectangleCreationEventHandler());
Canvas.AddInputEventListener(new RectangleCreationEventHandler());
Note that this event handler reacts to the same events that the
zoom and pan event handlers react to. If they are all active on the
canvas at the same time undesired behavior would occur. Often you will want to remove the default pan and zoom
event handlers associated with the PCanvas
, this can be done as
follows:
getCanvas().removeInputEventListener(getCanvas().getZoomEventHandler()); getCanvas().removeInputEventListener(getCanvas().getPanEventHandler());
Canvas.RemoveInputEventListener(Canvas.ZoomEventHandler); Canvas.RemoveInputEventListener(Canvas.PanEventHandler);
In Piccolo.NET you can also use the following shortcut:
C#Canvas.ZoomEventHandler = null; Canvas.PanEventHandler = null;
A general problem when defining global event handlers is making
sure that they do not conflict. The Java PInputEventFilter
class and the C#
PInputEventListener.DoesAcceptEvent()
method can help in this
regard.
In Piccolo.NET there is another way to handle basic input events. You can connect an event handler method directly to a node in the same way that .NET event handlers can be connected to a control. This allows you to define your event handlers in style consistent with .NET events. For example, the following code prints a message every time the user clicks the green node:
C#public override void Initialize() { PNode aNode = new PNode(); aNode.SetBounds(0,0, 100, 100); aNode.Brush = Brushes.Green; Canvas.Layer.AddChild(aNode); aNode.MouseDown += new PInputEventHandler(aNode_MouseDown); } protected void aNode_MouseDown(object sender, PInputEventArgs e) { System.Console.WriteLine("Clicked aNode!"); }
For more information about the tradeoffs of each of these approaches see the Defining User Interaction tutorial on the Getting Started page.
Often an interface has constraints that must be maintained between a node and its children. For example a node may want to always make its children line up in a row or a node may wish to expand its base size to always fully contain the bounds of its children.
The PNode
class does no automatic layout of its own, but it
provides methods that subclasses can override to perform layout in
the appropriate place during the layout process.. The following is a
simple layout node that overrides and lays its children out in a
horizontal row.
PNode layoutNode extends PNode { public void layoutChildren() { double xOffset = 0; double yOffset = 0; Iterator i = getChildrenIterator(); while (i.hasNext()) { PNode each = (PNode) i.next(); each.setOffset(xOffset - each.getX(), yOffset); xOffset += each.getFullBoundsReference().getWidth(); } } }
public class LayoutNode : PNode { public override void LayoutChildren() { float xOffset = 0; float yOffset = 0; PNodeList children = this.ChildrenReference; foreach (PNode each in children) { each.Offset = new PointF(xOffset - each.X, yOffset); xOffset += each.FullBounds.Width; } } }
Event handlers let an interface react to a user. Actives are used to give the interface a life of its own through the use of animation and other "scheduled" behaviors.
Activities control some time-dependent aspect of the Piccolo system, usually some part of a node. This behavior may be of fixed duration or may continue until some termination condition is met (or perhaps forever). Activities of fixed duration may be defined to consume a fixed amount of time, independent of the frame rate.
This method sets up a flash activity that flashes the given node's color from red to green for 5 seconds.
|public void flashNode(final PNode aNode) { PActivity flash = new PActivity(5000) { boolean fRed = true; protected void step(long time) { super.step(time); if (fRed) { aNode.setPaint(Color.red); } else { aNode.setPaint(Color.green); } fRed = !fRed; } }; // Must schedule the activity with the root for it to run. aNode.getRoot().addActivity(flash); }
public void flashNode(PNode aNode) { PActivity flash = new PActivity(5000); flash.ActivityStepped = new ActivitySteppedDelegate(ActivityStepped); // Must schedule the activity with the root for it to run. Canvas.Root.AddActivity(flash); } protected void ActivityStepped(PActivity activity) { if (fRed) { aNode.Brush = Brushes.Red; } else { aNode.Brush = Brushes.Green; } fRed = !fRed; }
Activities are scheduled by the PRoot
until they have completed.
Note that for animation activities you can also use the convenience
methods in PNode
:
public PTransformActivity animateToPositionScaleRotation(double x, double y, double scale, double theta, long duration); public PTransformActivity animateToTransform(AffineTransform aDestination, long duration);
public virtual PTransformActivity AnimateToPositionScaleRotation(float x, float y, float scale, float theta, long duration); public virtual PTransformActivity AnimateToMatrix(PMatrix destMatrix, long duration);
Each activity has a start time and a duration, that together
determine when an activity starts stepping and how long it continues
to step. The Java PActivity.startAfter()
and the C# PActivity.StartAfter()
methods may be used to sequence an activity so that it starts right
after another has stopped.
SWT is a new Java toolkit (similar to and replacing Swing) that comes out of the Eclipse IDE project. Piccolo.Java was originally designed to work with swing, but now has preliminary SWT support as well. You will most likely use SWT if you are writing Piccolo extensions to the Eclipse IDE. See www.eclipse.org for more info on the Eclipse project.
Again SWT support is not complete. We are interested in your
feedback and hope it is of use, but we can not promise future
updates. SWT support is provided in the extras package in
edu.umd.cs.piccolox.swt
, and you will find SWT examples in the
examples package. Here is the code for SWT hello world:
public class SWTHelloWorld { public SWTHelloWorld() { super(); } public static void main(String[] args) { Display display = new Display (); Shell shell = open(display); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } public static Shell open(Display display) { final Shell shell = new Shell(display); shell.setLayout(new FillLayout()); PSWTCanvas canvas = new PSWTCanvas(shell,0); PSWTText text = new PSWTText("Hello World"); canvas.getLayer().addChild(text); shell.open(); return shell; } }
The Piccolo implementation patterns are designed to give a general understanding of how Piccolo is implemented and what its runtime behaviors are. This is opposed to the patterns in Usage Patterns For Piccolo that are designed to get you using the framework as quickly as possible.
Pattern 8: Piccolo Framework DesignYou should be familiar with the basic concepts of the Piccolo framework design and how they relate to each other to effectively use Piccolo.
Piccolo is a direct-manipulation graphics framework that supports constructing zoomable interfaces. The framework's design borrows heavily from the designs of both the Jazz and Morphic interface frameworks.
Piccolo Class Hierarchy
There are four main classes that define the framework's core:
PNode
(anything that is visible and gets events)
PCamera
(a node that looks at other layer nodes, and applies
a view transform)
PLayer
(a node that can be looked at by a camera)
PRoot
(the root of the Piccolo display tree)
PCanvas
(the host component that lets PNodes
exist in a Java
Swing or .NET Windows application. Each PCanvas
is associated with a PCamera
.
But all cameras are not necessarily directly associated with a
PCanvas
, internal cameras for example are not.) Piccolo Runtime Structure
At runtime these classes form a tree-like structure
with the PRoot
situated at the top. Each PCamera
is normally linked
with at least one PLayer
that it looks at through it's view
transform. If a camera is associated with a canvas then that
camera's view is displayed on the canvas, and input events from the
canvas enter the Piccolo scene graph at that camera's point in the
hierarchy.
Nodes are the central design concept in Piccolo. Any object that wants to paint itself on the screen should inherit from the node class. In addition to painting on the screen all nodes may have other "child" nodes added to them. Visual structures are build up by grouping and sub grouping collections of nodes.
Each node also has its own affine transform that is applied before the node is drawn to the screen. This transform can be modified to scale and translate the node. The transform exits directly above the node, but below the node's parent. Thus, translating it will translate the node (and all its descendents) but will not translate the node's parent.
Cameras are nodes that have an additional view transform and a collection of layers in addition to the
collection of children that they inherit from PNode
. The view
transform is applied before drawing or picking the layers, but not
when drawing or picking the camera's children. Cameras may (an
internal camera might not) also reference a PCanvas
, and forward
repaint events to that canvas. The canvas would then later ask the
camera to draw the damaged region on its surface.
Layer nodes are nodes that can be viewed by a one or more cameras. They maintain a list of the cameras that are viewing them, and notify these cameras when they are repainted.
The PRoot
serves as the topmost node in the Piccolo runtime
structure; all other nodes are its direct children or descendents of
its children. The PCanvas
communicates with the root node to manage
screen updates and to dispatch events to its children.
The PCanvas
is a JComponent
in Piccolo.Java and
a Control
in Piccolo.NET. Thus, it is used to view a Piccolo scene
graph in Java Swing and .NET Windows applications respectively. The PCanvas
views the scene graph
through a PCamera
. It forwards input events to that camera,
and uses that camera to draw itself. Translating and scaling that
camera's view transform is how panning and zooming are accomplished.
Many of PNode's methods work on the composite structure (the node plus all its descendents) of the node. It is helpful to be able to easily distinguish these methods from ones that only work directly on the node and not on its children.
Piccolo uses the term "full" to mean a node and its descendants with the node's transform applied. This helps distinguish between methods that work on a single node and those that work on a node together with all of its descendants with the node's transform applied. When traversing the node scene graph to paint or calculate bounds parent nodes generally call the "full" methods of their direct child nodes. For example:
|// Returns the bounding box of the given node without the node's // transform applied. aNode.getBounds(); // Returns the bounds of the given node combined with the bounds of // all of the descendants of that node after applying the node's // transform. Descendants need not be contained in the bounds of // their parent, so this full bounds value may be larger than the // bounds, but it will never be smaller. aNode.getFullBounds(); // This will paint just "aNode" and not any of its children. Normally this // method is automatically called from the fullPaint method. aNode.paint(aPaintContext); // This will paint "aNode" together will all of that node's descendents. aNode.fullPaint(aPaintContext);
// Returns the bounding box of the given node without the node's // transform applied. aNode.Bounds; // Returns the bounds of the given node combined with the bounds of // all of the descendants of that node after applying the node's // transform. Descendants need not be contained in the bounds of // their parent, so this full bounds value may be larger than the // bounds, but it will never be smaller. aNode.FullBounds; // This will paint just "aNode" and not any of its children. Normally this // method is automatically called from the FullPaint method. aNode.Paint(aPaintContext); // This will paint "aNode" together will all of that node's descendents. aNode.FullPaint(aPaintContext);
Subclasses should generally not override the "full" methods, since they are implemented in terms of other methods that can be overridden.
Pattern 10: Coordinate SystemsEach node in Piccolo has its own transform that it uses to define its own coordinate system. It is essential that you understand coordinate systems, and how to convert from one coordinate system to another when designing a zoomable user interface.
There may be thousands of different coordinate systems in a Piccolo interface (a different one for each node), but they can all be organized into three categories:
PCanvas
. Piccolo provides methods that let you easily convert between
different coordinate systems. The PNode
class defines the methods:
Here is a typical example of how coordinate systems are used:
|// This method connects the centers of two // rectangle nodes with a line node. If you know that two nodes // exist in the same coordinate system then you don't need to make // all these conversions. This example assumes the most general case where // they all exist in different coordinate systems. public void connectRectsWithLine(PPath rect1, PPath rect2, PPath line) { // First get the center of each rectangle in the // local coordinate system of each rectangle. Point2D r1c = rect1.getBounds().getCenter2D(); Point2D r2c = rect2.getBounds().getCenter2D(); // Next convert that center point for each rectangle // into global coordinate system. rect1.localToGlobal(r1c); rect2.localToGlobal(r2c); // Now that the centers are in global coordinates they // can be converted into the local coordinate system // of the line node. line.globalToLocal(r1c); line.globalToLocal(r2c); // Finish by setting the endpoints of the line to // the center points of the rectangles, now that those // center points are in the local coordinate system of the line. line.setPathTo(new Line2D.Double(r1c, r2c)); }
// This method connects the centers of two // rectangle nodes with a line node. If you know that two nodes // exist in the same coordinate system then you don't need to make // all these conversions. This example assumes the most general case where // they all exist in different coordinate systems. public void ConnectRectsWithLine(PPath rect1, PPath rect2, PPath line) { // First get the center of each rectangle in the // local coordinate system of each rectangle. PointF r1c = PUtil.CenterOfRectangle(rect1.Bounds); PointF r2c = PUtil.CenterOfRectangle(rect2.Bounds); // Next convert that center point for each rectangle // into global coordinate system. r1c = rect1.LocalToGlobal(r1c); r2c = rect2.LocalToGlobal(r2c); // Now that the centers are in global coordinates they // can be converted into the local coordinate system // of the line node. r1c = line.GlobalToLocal(r1c); r2c = line.GlobalToLocal(r2c); // Finish by setting the endpoints of the line to // the center points of the rectangles, now that those // center points are in the local coordinate system of the line. line.Reset(); line.AddLine(r1c.X, r1c.Y, r2c.X, r2c.Y); }
The root node's UI Cycle is at the center of Piccolo's runtime behavior where it drives the processes for processing user input, activities, bounds management, and scheduling repaints.
The PRoot
class is responsible for running the UI Cycle. During
each cycle it performs four actions:
PInputEventListeners
.The Piccolo UI loop is always driven from the event dispatch thread. A UI cycle is done for each new input event received by the canvas, and for each time the activity timer fires (to support animation).
Piccolo is not thread safe and should only be used by a single thread at a time, and that thread will almost always be the event dispatch thread.
If you need to run a computation in another thread you will need
to call special methods to connect the results of your
computation back up to the event dispatch thread. In Piccolo.Java,
use SwingUtilities.invokeLater()
or
SwingUtilities.invokeAndWait()
. In Piccolo.NET, use
Control.BeginInvoke()
and
Control.Invoke()
.
Event dispatch is the process through which Piccolo directs new events coming from the user to event handlers in the interface.
All event dispatch is managed by the input manager
(PInputManager
). Events get to the dispatch manager by
first coming off the Java or .NET event queue, next they are sent to the canvas
(PCanvas
) and the canvas forwards them to the input manager.
The input manager then converts the incoming Java or .NET
event to a Piccolo event. in Piccolo.Java, a java.awt.InputEvent
is converted into a PInputEvent
. In Piccolo.NET, a System.EventArgs
is
converted into a PInputEventArgs
. Next the input manager
sends the Piccolo event to the event listeners of the appropriate
nodes. The dispatch manager maintains the following focus nodes:
Events are dispatched up the PPickPath
associated with a given
focus node, so they percolate up the pick path until they are
consumed or they reach the originating camera node.
TIP: You can always get a reference to the dispatch manager from
a PInputEvent
, and ask it for the current focus nodes.
All scheduled activities are given a chance to run during the UI Cycle.
Activities are used to control some time dependent aspect of the Piccolo framework, for example they can be used to animate a node across the screen, or animate the camera's view transform to perform a zoom.
Pattern 15: Validating BoundsWhen the geometry of a node changes its bounds caches need to be recomputed and the geometry of other related nodes may also be effected.
Maintaining bounds caches and updating layouts can become very expensive when manipulating a large number of nodes. Because of this Piccolo uses a two stage incremental approach the layout management. The two stages consist of; a damage stage where damage is recorded in bit flags for each damaged node, and an incremental repair stage where the damage is repaired as is needed.
The damage stage begins when some node geometry changes. When this happens the type of damage that occurred is recorded for each node. The are three kinds of damage that can occur:
Damage is repaired by PNode's
validateFullBounds method at the
end of the UI cycle. That method does the following things
PNode
does nothing, but layout manager nodes would override it).
Piccolo should paint the screen only when needed, and it should be smart about only painting the portions of the screen that need to be painted.
Display update in Piccolo is driven from the UI Cycle, and uses the same damage/repair design that Piccolo uses to validate bounds. When a node changes such that it needs to be repainted it invalidates its paint, and invalidates the child paint of all its ancestors. Later (at the end of the UI Cycle) screen damage is collected for all nodes with invalid paint.
This section contains a few basic patterns that occur frequently in ZUIs, and describes how they can be implemented with Piccolo. To learn more about using Piccolo see Usage Patterns For Piccolo, and to learn more about the Piccolo implementation see Implementation Patterns for Piccolo.
Pattern 17: Semantic ZoomingIt is useful for an object to change its visual representation based on the scale that it is being viewed at. For example when a document is viewed from far away (at a small scale) in a ZUI it might be best to just show that documents title, but when the view is zoomed in all the documents content should become visible.
To do semantic zooming in Piccolo you should override the appropriate paint method, and then choose how the node renders itself based on the scale stored in the paint context parameter. This example creates a new node that will paint its based bounds filed with a blue color when viewed at a scale that is less then one, and with an orange color when the scale is greater then one.
|public class SemanticNode extends PNode { public void paint(PPaintContext aPaintContext) { double s = aPaintContext.getScale(); Graphics2D g2 = aPaintContext.getGraphics(); if (s < 1) { g2.setPaint(Color.blue); } else { g2.setPaint(Color.orange); } g2.fill(getBoundsReference()); } }
public class SemanticNode : PNode { protected override void Paint(PPaintContext paintContext) { float s = paintContext.Scale; Graphics g = paintContext.Graphics; if (s < 1) { g.FillRectangle(Brushes.Blue, this.Bounds); } else { g.FillRectangle(Brushes.Orange, this.Bounds); } } }
It is useful for an object to "stick" to a camera, so that its position does not change even when the camera is zoomed and panned.
To do this in Piccolo you should add the "sticky node" as a child of the camera. The camera's view transform is only applied to the layer nodes that it is viewing, not to its children. The children are drawn after (on top of) the layer nodes.
This example creates a yellow rectangle with bounds handles and adds it to the camera as a sticky node. A standard rectangle is then added to the main layer. Zooming in and out will change the scale of the standard rectangle but not the sticky one.
|public void initialize() { PPath sticky = PPath.createRectangle(0, 0, 50, 50);; sticky.setPaint(Color.YELLOW); sticky.setStroke(null); PBoundsHandle.addBoundsHandlesTo(sticky); getCanvas().getLayer().addChild(PPath.createRectangle(0, 0, 100, 80)); getCanvas().getCamera().addChild(sticky); }
public override void Initialize() { PPath sticky = PPath.CreateRectangle(0, 0, 50, 50);; sticky.Brush = Brushes.Yellow; sticky.Pen = null; PBoundsHandle.AddBoundsHandlesTo(sticky); Canvas.Layer.AddChild(PPath.CreateRectangle(0, 0, 100, 80)); Canvas.Camera.AddChild(sticky); }