All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.piccolo2d.util.PPickPath Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008-2019, Piccolo2D project, http://piccolo2d.org
 * Copyright (c) 1998-2008, University of Maryland
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
 * and the following disclaimer in the documentation and/or other materials provided with the
 * distribution.
 *
 * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its
 * contributors may be used to endorse or promote products derived from this software without specific
 * prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.piccolo2d.util;

import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;

import javax.swing.event.EventListenerList;

import org.piccolo2d.PCamera;
import org.piccolo2d.PNode;
import org.piccolo2d.event.PInputEvent;
import org.piccolo2d.event.PInputEventListener;


/**
 * PPickPath represents a ordered list of nodes that have been picked.
 * The topmost ancestor node is the first node in the list (and should be a
 * camera), the bottommost child node is at the end of the list. It is this
 * bottom node that is given first chance to handle events, and that any active
 * event handlers usually manipulate.
 * 

* Note that because of layers (which can be picked by multiple camera's) the * ordered list of nodes in a pick path do not all share a parent child * relationship with the nodes in the list next to them. This means that the * normal localToGlobal methods don't work when trying to transform geometry up * and down the pick path, instead you should use the pick paths canvasToLocal * methods to get the mouse event points into your local coord system. *

* Note that PInputEvent wraps most of the useful PPickPath methods, so often * you can use a PInputEvent directly instead of having to access its pick path. *

* * @see org.piccolo2d.event.PInputEvent * @version 1.0 * @author Jesse Grosjean */ public class PPickPath implements PInputEventListener { /** Global pick path. */ public static PPickPath CURRENT_PICK_PATH; /** Used when calculating the scale. */ private static final double[] PTS = new double[4]; /** Stack of nodes representing all picked nodes. */ private PStack nodeStack; private final PCamera topCamera; private PStack transformStack; private PStack pickBoundsStack; private PCamera bottomCamera; private HashMap excludedNodes; /** * Creates a pick pack originating from the provided camera and with the * given screen pick bounds. * * @param camera camera from which the pickpath originates * @param aScreenPickBounds bounds of pick area */ public PPickPath(final PCamera camera, final PBounds aScreenPickBounds) { super(); pickBoundsStack = new PStack(); topCamera = camera; nodeStack = new PStack(); transformStack = new PStack(); pickBoundsStack.push(aScreenPickBounds); CURRENT_PICK_PATH = this; } /** * Returns the bounds of the entire PickPath taken as a whole. * * @return bounds of the entire PickPath */ public PBounds getPickBounds() { return (PBounds) pickBoundsStack.peek(); } /** * Determines if the passed node has been excluded from being a member of * the pickpath. * * @param node node being tested * @return true if node is acceptable to the path */ public boolean acceptsNode(final PNode node) { return excludedNodes == null || !excludedNodes.containsKey(node); } // **************************************************************** // Picked Nodes // **************************************************************** /** * Pushes the provided node to the top of the pick path. * * @param node node to be added to the pick path */ public void pushNode(final PNode node) { nodeStack.push(node); } /** * Removes the topmost node from the node stack. * * @param node completely unused in this method, but is passed in so that * subclasses may be informed of it. */ public void popNode(final PNode node) { nodeStack.pop(); } /** * Get the bottom node on the pick path node stack. That is the last node to * be picked. * * @return the bottom node on the pick path */ public PNode getPickedNode() { return (PNode) nodeStack.peek(); } // **************************************************************** // Iterating over picked nodes. // **************************************************************** /** * Return the next node that will be picked after the current picked node. * For instance of you have two overlapping children nodes then the topmost * child will always be picked first, use this method to find the covered * child. Return the camera when no more visual will be picked. * * @return next node to picked after the picked node */ public PNode nextPickedNode() { final PNode picked = getPickedNode(); if (picked == topCamera) { return null; } if (excludedNodes == null) { excludedNodes = new HashMap(); } // exclude current picked node excludedNodes.put(picked, picked); final Object screenPickBounds = pickBoundsStack.get(0); // reset path state pickBoundsStack = new PStack(); nodeStack = new PStack(); transformStack = new PStack(); pickBoundsStack = new PStack(); pickBoundsStack.push(screenPickBounds); // pick again topCamera.fullPick(this); // make sure top camera is pushed. if (getNodeStackReference().size() == 0) { pushNode(topCamera); pushTransform(topCamera.getTransformReference(false)); } return getPickedNode(); } /** * Get the top camera on the pick path. This is the camera that originated * the pick action. * * @return the topmost camera of this pick pack */ public PCamera getTopCamera() { return topCamera; } /** * Get the bottom camera on the pick path. This may be different then the * top camera if internal cameras are in use. * * @return the camera closest to the picked node */ public PCamera getBottomCamera() { if (bottomCamera == null) { bottomCamera = calculateBottomCamera(); } return bottomCamera; } private PCamera calculateBottomCamera() { for (int i = nodeStack.size() - 1; i >= 0; i--) { final PNode each = (PNode) nodeStack.get(i); if (each instanceof PCamera) { return (PCamera) each; } } return null; } /** * Returns a reference to the node stack. Be Careful! * * @return the node stack */ public PStack getNodeStackReference() { return nodeStack; } // **************************************************************** // Path Transform // **************************************************************** /** * Returns the resulting scale of applying the transforms of the entire pick * path. In essence it gives you the scale at which interaction is * occurring. * * @return scale at which interaction is occurring. */ public double getScale() { // x1, y1, x2, y3 PTS[0] = 0; PTS[1] = 0; PTS[2] = 1; PTS[3] = 0; final int count = transformStack.size(); for (int i = 0; i < count; i++) { final PAffineTransform each = ((PTuple) transformStack.get(i)).transform; if (each != null) { each.transform(PTS, 0, PTS, 0, 2); } } return Point2D.distance(PTS[0], PTS[1], PTS[2], PTS[3]); } /** * Adds the transform to the pick path's transform. This is used when * determining the context of the current interaction. * * @param transform transform to be added to applied to the pickpath. */ public void pushTransform(final PAffineTransform transform) { transformStack.push(new PTuple(getPickedNode(), transform)); if (transform != null) { final Rectangle2D newPickBounds = (Rectangle2D) getPickBounds().clone(); transform.inverseTransform(newPickBounds, newPickBounds); pickBoundsStack.push(newPickBounds); } } /** * Pops the top most transform from the pick path. * * @param transform unused in this method */ public void popTransform(final PAffineTransform transform) { transformStack.pop(); if (transform != null) { pickBoundsStack.pop(); } } /** * Calculates the context at which the given node is being interacted with. * * @param nodeOnPath a node currently on the pick path. An exception will be * thrown if the node cannot be found. * * @return Transform at which the given node is being interacted with. */ public PAffineTransform getPathTransformTo(final PNode nodeOnPath) { final PAffineTransform aTransform = new PAffineTransform(); final int count = transformStack.size(); for (int i = 0; i < count; i++) { final PTuple each = (PTuple) transformStack.get(i); if (each.transform != null) { aTransform.concatenate(each.transform); } if (nodeOnPath == each.node) { return aTransform; } } throw new RuntimeException("Node could not be found on pick path"); } /** * Process Events - Give each node in the pick path, starting at the bottom * most one, a chance to handle the event. * * @param event event to be processed * @param eventType the type of event being processed */ public void processEvent(final PInputEvent event, final int eventType) { event.setPath(this); for (int i = nodeStack.size() - 1; i >= 0; i--) { final PNode each = (PNode) nodeStack.get(i); final EventListenerList list = each.getListenerList(); if (list != null) { final Object[] listeners = list.getListeners(PInputEventListener.class); for (int j = 0; j < listeners.length; j++) { final PInputEventListener listener = (PInputEventListener) listeners[j]; listener.processEvent(event, eventType); if (event.isHandled()) { return; } } } } } // **************************************************************** // Transforming Geometry - Methods to transform geometry through // this path. //

// Note that this is different that just using the // PNode.localToGlobal (an other coord system transform methods). // The PNode coord system transform methods always go directly up // through their parents. The PPickPath coord system transform // methods go up through the list of picked nodes instead. And since // cameras can pick their layers in addition to their children these // two paths may be different. // **************************************************************** /** * Convert the given point from the canvas coordinates, down through the * pick path (and through any camera view transforms applied to the path) to * the local coordinates of the given node. * * @param canvasPoint point to be transformed * @param nodeOnPath node into which the point is to be transformed * iteratively through the pick path * * @return transformed canvasPoint in local coordinates of the picked node */ public Point2D canvasToLocal(final Point2D canvasPoint, final PNode nodeOnPath) { return getPathTransformTo(nodeOnPath).inverseTransform(canvasPoint, canvasPoint); } /** * Convert the given dimension from the canvas coordinates, down through the * pick path (and through any camera view transforms applied to the path) to * the local coordinates of the given node. * * @param canvasDimension dimension to be transformed * @param nodeOnPath node into which the dimension is to be transformed * iteratively through the stack * * @return transformed canvasDimension in local coordinates of the picked * node */ public Dimension2D canvasToLocal(final Dimension2D canvasDimension, final PNode nodeOnPath) { return getPathTransformTo(nodeOnPath).inverseTransform(canvasDimension, canvasDimension); } /** * Convert the given rectangle from the canvas coordinates, down through the * pick path (and through any camera view transforms applied to the path) to * the local coordinates of the given node. * * @param canvasRectangle rectangle to be transformed * @param nodeOnPath node into which the rectangle is to be transformed * iteratively through the stack * @return transformed canvasRectangle in local coordinates of the picked * node */ public Rectangle2D canvasToLocal(final Rectangle2D canvasRectangle, final PNode nodeOnPath) { return getPathTransformTo(nodeOnPath).inverseTransform(canvasRectangle, canvasRectangle); } /** * Used to associated nodes with their transforms on the transform stack. */ private static class PTuple { public PNode node; public PAffineTransform transform; public PTuple(final PNode n, final PAffineTransform t) { node = n; transform = t; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy