package edu.umd.cs.piccolo.examples; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Paint; import java.util.ArrayList; import edu.umd.cs.piccolo.PCamera; import edu.umd.cs.piccolo.PCanvas; import edu.umd.cs.piccolo.PNode; import edu.umd.cs.piccolo.nodes.PPath; import edu.umd.cs.piccolo.util.PBounds; import edu.umd.cs.piccolo.util.PPaintContext; import edu.umd.cs.piccolox.PFrame; import edu.umd.cs.piccolox.event.PSelectionEventHandler; /** * An example of how to implement decorator groups. Decorator groups are nodes that base their bounds and rendering on their children. * This seems to be a common type of visual node that requires some potentially non-obvious subclassing to get right. * * Both a volatile and a non-volatile implementation are shown. The volatile implementation might be used in cases where you want to * keep a scale-independent pen width border around a group of objects. The non-volatile implementation can be used in more standard * cases where the decorator's bounds are stable during zooming. * * @author Lance Good */ public class GroupExample extends PFrame { public GroupExample() { this(null); } public GroupExample(PCanvas aCanvas) { super("GroupExample", false, aCanvas); } public void initialize() { super.initialize(); getCanvas().removeInputEventListener(getCanvas().getPanEventHandler()); // Create a decorator group that is NOT volatile DecoratorGroup dg = new DecoratorGroup(); dg.setPaint(Color.magenta); // Put some nodes under the group for it to decorate PPath p1 = PPath.createEllipse(25,25,75,75); p1.setPaint(Color.red); PPath p2 = PPath.createRectangle(125,75,50,50); p2.setPaint(Color.blue); // Add everything to the Piccolo hierarchy dg.addChild(p1); dg.addChild(p2); getCanvas().getLayer().addChild(dg); // Create a decorator group that IS volatile VolatileDecoratorGroup vdg = new VolatileDecoratorGroup(getCanvas().getCamera()); vdg.setPaint(Color.cyan); // Put some nodes under the group for it to decorate PPath p3 = PPath.createEllipse(275,175,50,50); p3.setPaint(Color.blue); PPath p4 = PPath.createRectangle(175,175,75,75); p4.setPaint(Color.green); // Add everything to the Piccolo hierarchy vdg.addChild(p3); vdg.addChild(p4); getCanvas().getLayer().addChild(vdg); // Create a selection handler so we can see that the decorator actually works ArrayList selectableParents = new ArrayList(); selectableParents.add(dg); selectableParents.add(vdg); PSelectionEventHandler ps = new PSelectionEventHandler(getCanvas().getLayer(),selectableParents); getCanvas().addInputEventListener(ps); } public static void main(String[] args) { new GroupExample(); } } /** * This is the non-volatile implementation of a decorator group that paints a background rectangle based * on the bounds of its children. */ class DecoratorGroup extends PNode { int INDENT = 10; PBounds cachedChildBounds = new PBounds(); PBounds comparisonBounds = new PBounds(); public DecoratorGroup() { super(); } /** * Change the default paint to fill an expanded bounding box based on its children's bounds */ public void paint(PPaintContext ppc) { Paint paint = getPaint(); if (paint != null) { Graphics2D g2 = ppc.getGraphics(); g2.setPaint(paint); PBounds bounds = getUnionOfChildrenBounds(null); bounds.setRect(bounds.getX()-INDENT,bounds.getY()-INDENT,bounds.getWidth()+2*INDENT,bounds.getHeight()+2*INDENT); g2.fill(bounds); } } /** * Change the full bounds computation to take into account that we are expanding the children's bounds * Do this instead of overriding getBoundsReference() since the node is not volatile */ public PBounds computeFullBounds(PBounds dstBounds) { PBounds result = getUnionOfChildrenBounds(dstBounds); cachedChildBounds.setRect(result); result.setRect(result.getX()-INDENT,result.getY()-INDENT,result.getWidth()+2*INDENT,result.getHeight()+2*INDENT); localToParent(result); return result; } /** * This is a crucial step. We have to override this method to invalidate the paint each time the bounds are changed so * we repaint the correct region */ public boolean validateFullBounds() { comparisonBounds = getUnionOfChildrenBounds(comparisonBounds); if (!cachedChildBounds.equals(comparisonBounds)) { setPaintInvalid(true); } return super.validateFullBounds(); } } /** * This is the volatile implementation of a decorator group that paints a background rectangle based * on the bounds of its children. */ class VolatileDecoratorGroup extends PNode { int INDENT = 10; PBounds cachedChildBounds = new PBounds(); PBounds comparisonBounds = new PBounds(); PCamera renderCamera; public VolatileDecoratorGroup(PCamera camera) { super(); renderCamera = camera; } /** * Indicate that the bounds are volatile for this group */ public boolean getBoundsVolatile() { return true; } /** * Since our bounds are volatile, we can override this method to indicate that we are expanding our bounds beyond our children */ public PBounds getBoundsReference() { PBounds bds = super.getBoundsReference(); getUnionOfChildrenBounds(bds); cachedChildBounds.setRect(bds); double scaledIndent = INDENT/renderCamera.getViewScale(); bds.setRect(bds.getX()-scaledIndent,bds.getY()-scaledIndent,bds.getWidth()+2*scaledIndent,bds.getHeight()+2*scaledIndent); return bds; } /** * This is a crucial step. We have to override this method to invalidate the paint each time the bounds are changed so * we repaint the correct region */ public boolean validateFullBounds() { comparisonBounds = getUnionOfChildrenBounds(comparisonBounds); if (!cachedChildBounds.equals(comparisonBounds)) { setPaintInvalid(true); } return super.validateFullBounds(); } }