/* * Copyright (c) 2008, 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 edu.umd.cs.piccolox.event; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import edu.umd.cs.piccolo.PCamera; import edu.umd.cs.piccolo.PNode; import edu.umd.cs.piccolo.activities.PActivity; import edu.umd.cs.piccolo.activities.PTransformActivity; import edu.umd.cs.piccolo.event.PBasicInputEventHandler; import edu.umd.cs.piccolo.event.PInputEvent; import edu.umd.cs.piccolo.event.PInputEventFilter; import edu.umd.cs.piccolo.util.PBounds; import edu.umd.cs.piccolo.util.PDimension; /** * <b>PNavigationEventHandler</b> implements simple focus based navigation. Uses * mouse button one or the arrow keys to set a new focus. Animates the canvas * view to keep the focus node on the screen and at 100 percent scale with * minimal view movement. * * @version 1.0 * @author Jesse Grosjean */ public class PNavigationEventHandler extends PBasicInputEventHandler { public static final int NORTH = 0; public static final int SOUTH = 1; public static final int EAST = 2; public static final int WEST = 3; public static final int IN = 4; public static final int OUT = 5; private static Hashtable NODE_TO_GLOBAL_NODE_CENTER_MAPPING = new Hashtable(); private PNode focusNode; private PActivity navigationActivity; public PNavigationEventHandler() { super(); setEventFilter(new PInputEventFilter(InputEvent.BUTTON1_MASK)); } // **************************************************************** // Focus Change Events. // **************************************************************** public void keyPressed(PInputEvent e) { PNode oldLocation = focusNode; switch (e.getKeyCode()) { case KeyEvent.VK_LEFT: moveFocusLeft(e); break; case KeyEvent.VK_RIGHT: moveFocusRight(e); break; case KeyEvent.VK_UP: case KeyEvent.VK_PAGE_UP: if (e.isAltDown()) { moveFocusOut(e); } else { moveFocusUp(e); } break; case KeyEvent.VK_DOWN: case KeyEvent.VK_PAGE_DOWN: if (e.isAltDown()) { moveFocusIn(e); } else { moveFocusDown(e); } break; } if (focusNode != null && oldLocation != focusNode) { directCameraViewToFocus(e.getCamera(), focusNode, 500); } } public void mousePressed(PInputEvent aEvent) { moveFocusToMouseOver(aEvent); if (focusNode != null) { directCameraViewToFocus(aEvent.getCamera(), focusNode, 500); aEvent.getInputManager().setKeyboardFocus(aEvent.getPath()); } } // **************************************************************** // Focus Movement - Moves the focus the specified direction. Left, // right, up, down mean move the focus to the closest sibling of the // current focus node that exists in that direction. Move in means // move the focus to a child of the current focus, move out means // move the focus to the parent of the current focus. // **************************************************************** public void moveFocusDown(PInputEvent e) { PNode n = getNeighborInDirection(SOUTH); if (n != null) { focusNode = n; } } public void moveFocusIn(PInputEvent e) { PNode n = getNeighborInDirection(IN); if (n != null) { focusNode = n; } } public void moveFocusLeft(PInputEvent e) { PNode n = getNeighborInDirection(WEST); if (n != null) { focusNode = n; } } public void moveFocusOut(PInputEvent e) { PNode n = getNeighborInDirection(OUT); if (n != null) { focusNode = n; } } public void moveFocusRight(PInputEvent e) { PNode n = getNeighborInDirection(EAST); if (n != null) { focusNode = n; } } public void moveFocusUp(PInputEvent e) { PNode n = getNeighborInDirection(NORTH); if (n != null) { focusNode = n; } } public void moveFocusToMouseOver(PInputEvent e) { PNode focus = e.getPickedNode(); if (!(focus instanceof PCamera)) { focusNode = focus; } } public PNode getNeighborInDirection(int aDirection) { if (focusNode == null) return null; NODE_TO_GLOBAL_NODE_CENTER_MAPPING.clear(); Point2D highlightCenter = focusNode.getGlobalFullBounds().getCenter2D(); NODE_TO_GLOBAL_NODE_CENTER_MAPPING.put(focusNode, highlightCenter); List l = getNeighbors(); sortNodesByDistanceFromPoint(l, highlightCenter); Iterator i = l.iterator(); while (i.hasNext()) { PNode each = (PNode) i.next(); if (nodeIsNeighborInDirection(each, aDirection)) { return each; } } return null; } public List getNeighbors() { ArrayList result = new ArrayList(); if (focusNode == null) return result; if (focusNode.getParent() == null) return result; PNode focusParent = focusNode.getParent(); Iterator i = focusParent.getChildrenIterator(); while (i.hasNext()) { PNode each = (PNode) i.next(); if (each != focusNode && each.getPickable()) { result.add(each); } } result.add(focusParent); i = focusNode.getChildrenIterator(); while (i.hasNext()) { result.add(i.next()); } return result; } public boolean nodeIsNeighborInDirection(PNode aNode, int aDirection) { switch (aDirection) { case IN: { return aNode.isDescendentOf(focusNode); } case OUT: { return aNode.isAncestorOf(focusNode); } default: { if (aNode.isAncestorOf(focusNode) || aNode.isDescendentOf(focusNode)) { return false; } } } Point2D highlightCenter = (Point2D) NODE_TO_GLOBAL_NODE_CENTER_MAPPING.get(focusNode); Point2D nodeCenter = (Point2D) NODE_TO_GLOBAL_NODE_CENTER_MAPPING.get(aNode); double ytest1 = nodeCenter.getX() - highlightCenter.getX() + highlightCenter.getY(); double ytest2 = -nodeCenter.getX() + highlightCenter.getX() + highlightCenter.getY(); switch (aDirection) { case NORTH: { if (nodeCenter.getY() < highlightCenter.getY()) { if (nodeCenter.getY() < ytest1 && nodeCenter.getY() < ytest2) { return true; } } break; } case EAST: { if (nodeCenter.getX() > highlightCenter.getX()) { if (nodeCenter.getY() < ytest1 && nodeCenter.getY() > ytest2) { return true; } } break; } case SOUTH: { if (nodeCenter.getY() > highlightCenter.getY()) { if (nodeCenter.getY() > ytest1 && nodeCenter.getY() > ytest2) { return true; } } break; } case WEST: { if (nodeCenter.getX() < highlightCenter.getX()) { if (nodeCenter.getY() > ytest1 && nodeCenter.getY() < ytest2) { return true; } } break; } } return false; } public void sortNodesByDistanceFromPoint(List aNodesList, final Point2D aPoint) { Collections.sort(aNodesList, new Comparator() { public int compare(Object o1, Object o2) { PNode each1 = (PNode) o1; PNode each2 = (PNode) o2; Point2D each1Center = each1.getGlobalFullBounds().getCenter2D(); Point2D each2Center = each2.getGlobalFullBounds().getCenter2D(); NODE_TO_GLOBAL_NODE_CENTER_MAPPING.put(each1, each1Center); NODE_TO_GLOBAL_NODE_CENTER_MAPPING.put(each2, each2Center); double distance1 = aPoint.distance(each1Center); double distance2 = aPoint.distance(each2Center); if (distance1 < distance2) { return -1; } else if (distance1 == distance2) { return 0; } else { return 1; } } }); } // **************************************************************** // Canvas Movement - The canvas view is updated so that the current // focus remains visible on the screen at 100 percent scale. // **************************************************************** protected PActivity animateCameraViewTransformTo(final PCamera aCamera, AffineTransform aTransform, int duration) { boolean wasOldAnimation = false; // first stop any old animations. if (navigationActivity != null) { navigationActivity.terminate(); wasOldAnimation = true; } if (duration == 0) { aCamera.setViewTransform(aTransform); return null; } AffineTransform source = aCamera.getViewTransformReference(); if (!source.equals(aTransform)) { navigationActivity = aCamera.animateViewToTransform(aTransform, duration); ((PTransformActivity) navigationActivity).setSlowInSlowOut(!wasOldAnimation); return navigationActivity; } return null; } public PActivity directCameraViewToFocus(PCamera aCamera, PNode aFocusNode, int duration) { focusNode = aFocusNode; AffineTransform originalViewTransform = aCamera.getViewTransform(); // Scale the canvas to include PDimension d = new PDimension(1, 0); focusNode.globalToLocal(d); double scaleFactor = d.getWidth() / aCamera.getViewScale(); Point2D scalePoint = focusNode.getGlobalFullBounds().getCenter2D(); if (scaleFactor != 1) { aCamera.scaleViewAboutPoint(scaleFactor, scalePoint.getX(), scalePoint.getY()); } // Pan the canvas to include the view bounds with minimal canvas // movement. aCamera.animateViewToPanToBounds(focusNode.getGlobalFullBounds(), 0); // Get rid of any white space. The canvas may be panned and // zoomed in to do this. But make sure not stay constrained by max // magnification. // fillViewWhiteSpace(aCamera); AffineTransform resultingTransform = aCamera.getViewTransform(); aCamera.setViewTransform(originalViewTransform); // Animate the canvas so that it ends up with the given // view transform. return animateCameraViewTransformTo(aCamera, resultingTransform, duration); } protected void fillViewWhiteSpace(PCamera aCamera) { PBounds rootBounds = aCamera.getRoot().getFullBoundsReference(); PBounds viewBounds = aCamera.getViewBounds(); if (!rootBounds.contains(aCamera.getViewBounds())) { aCamera.animateViewToPanToBounds(rootBounds, 0); aCamera.animateViewToPanToBounds(focusNode.getGlobalFullBounds(), 0); // center content. double dx = 0; double dy = 0; viewBounds = aCamera.getViewBounds(); if (viewBounds.getWidth() > rootBounds.getWidth()) { // then center along x axis. double boundsCenterX = rootBounds.getMinX() + (rootBounds.getWidth() / 2); double viewBoundsCenterX = viewBounds.getMinX() + (viewBounds.getWidth() / 2); dx = viewBoundsCenterX - boundsCenterX; } if (viewBounds.getHeight() > rootBounds.getHeight()) { // then center along y axis. double boundsCenterY = rootBounds.getMinY() + (rootBounds.getHeight() / 2); double viewBoundsCenterY = viewBounds.getMinY() + (viewBounds.getHeight() / 2); dy = viewBoundsCenterY - boundsCenterY; } aCamera.translateView(dx, dy); } } }