Newer
Older
piccolo2d.java / examples / src / main / java / edu / umd / cs / piccolo / examples / GroupExample.java
@Marcus Rohrmoser Marcus Rohrmoser on 22 Jul 2008 5 KB issue#6
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();	
	}	
}