/** * $Id: mxMorphing.java,v 1.1 2012/11/15 13:26:46 gaudenz Exp $ * Copyright (c) 2007, Gaudenz Alder */ package com.mxgraph.swing.util; import java.awt.Graphics; import java.util.HashMap; import java.util.Map; import com.mxgraph.model.mxGeometry; import com.mxgraph.swing.mxGraphComponent; import com.mxgraph.swing.view.mxCellStatePreview; import com.mxgraph.util.mxEvent; import com.mxgraph.util.mxEventObject; import com.mxgraph.util.mxPoint; import com.mxgraph.util.mxRectangle; import com.mxgraph.view.mxCellState; import com.mxgraph.view.mxGraph; /** * Provides animation effects. */ public class mxMorphing extends mxAnimation { /** * Reference to the enclosing graph instance. */ protected mxGraphComponent graphComponent; /** * Specifies the maximum number of steps for the morphing. Default is * 6. */ protected int steps; /** * Counts the current number of steps of the animation. */ protected int step; /** * Ease-off for movement towards the given vector. Larger values are * slower and smoother. Default is 1.5. */ protected double ease; /** * Maps from cells to origins. */ protected Map<Object, mxPoint> origins = new HashMap<Object, mxPoint>(); /** * Optional array of cells to limit the animation to. */ protected Object[] cells; /** * */ protected transient mxRectangle dirty; /** * */ protected transient mxCellStatePreview preview; /** * Constructs a new morphing instance for the given graph. */ public mxMorphing(mxGraphComponent graphComponent) { this(graphComponent, 6, 1.5, DEFAULT_DELAY); // Installs the paint handler graphComponent.addListener(mxEvent.AFTER_PAINT, new mxIEventListener() { public void invoke(Object sender, mxEventObject evt) { Graphics g = (Graphics) evt.getProperty("g"); paint(g); } }); } /** * Constructs a new morphing instance for the given graph. */ public mxMorphing(mxGraphComponent graphComponent, int steps, double ease, int delay) { super(delay); this.graphComponent = graphComponent; this.steps = steps; this.ease = ease; } /** * Returns the number of steps for the animation. */ public int getSteps() { return steps; } /** * Sets the number of steps for the animation. */ public void setSteps(int value) { steps = value; } /** * Returns the easing for the movements. */ public double getEase() { return ease; } /** * Sets the easing for the movements. */ public void setEase(double value) { ease = value; } /** * Optional array of cells to be animated. If this is not specified * then all cells are checked and animated if they have been moved * in the current transaction. */ public void setCells(Object[] value) { cells = value; } /** * Animation step. */ public void updateAnimation() { preview = new mxCellStatePreview(graphComponent, false); if (cells != null) { // Animates the given cells individually without recursion for (Object cell : cells) { animateCell(cell, preview, false); } } else { // Animates all changed cells by using recursion to find // the changed cells but not for the animation itself Object root = graphComponent.getGraph().getModel().getRoot(); animateCell(root, preview, true); } show(preview); if (preview.isEmpty() || step++ >= steps) { stopAnimation(); } }; /** * */ public void stopAnimation() { graphComponent.getGraph().getView().revalidate(); super.stopAnimation(); preview = null; if (dirty != null) { graphComponent.getGraphControl().repaint(dirty.getRectangle()); } } /** * Shows the changes in the given mxCellStatePreview. */ protected void show(mxCellStatePreview preview) { if (dirty != null) { graphComponent.getGraphControl().repaint(dirty.getRectangle()); } else { graphComponent.getGraphControl().repaint(); } dirty = preview.show(); if (dirty != null) { graphComponent.getGraphControl().repaint(dirty.getRectangle()); } } /** * Animates the given cell state using moveState. */ protected void animateCell(Object cell, mxCellStatePreview move, boolean recurse) { mxGraph graph = graphComponent.getGraph(); mxCellState state = graph.getView().getState(cell); mxPoint delta = null; if (state != null) { // Moves the animated state from where it will be after the model // change by subtracting the given delta vector from that location delta = getDelta(state); if (graph.getModel().isVertex(cell) && (delta.getX() != 0 || delta.getY() != 0)) { mxPoint translate = graph.getView().getTranslate(); double scale = graph.getView().getScale(); // FIXME: Something wrong with the scale delta.setX(delta.getX() + translate.getX() * scale); delta.setY(delta.getY() + translate.getY() * scale); move.moveState(state, -delta.getX() / ease, -delta.getY() / ease); } } if (recurse && !stopRecursion(state, delta)) { int childCount = graph.getModel().getChildCount(cell); for (int i = 0; i < childCount; i++) { animateCell(graph.getModel().getChildAt(cell, i), move, recurse); } } } /** * Returns true if the animation should not recursively find more * deltas for children if the given parent state has been animated. */ protected boolean stopRecursion(mxCellState state, mxPoint delta) { return delta != null && (delta.getX() != 0 || delta.getY() != 0); } /** * Returns the vector between the current rendered state and the future * location of the state after the display will be updated. */ protected mxPoint getDelta(mxCellState state) { mxGraph graph = graphComponent.getGraph(); mxPoint origin = getOriginForCell(state.getCell()); mxPoint translate = graph.getView().getTranslate(); double scale = graph.getView().getScale(); mxPoint current = new mxPoint(state.getX() / scale - translate.getX(), state.getY() / scale - translate.getY()); return new mxPoint((origin.getX() - current.getX()) * scale, (origin .getY() - current.getY()) * scale); } /** * Returns the top, left corner of the given cell. */ protected mxPoint getOriginForCell(Object cell) { mxPoint result = origins.get(cell); if (result == null) { mxGraph graph = graphComponent.getGraph(); if (cell != null) { result = new mxPoint(getOriginForCell(graph.getModel() .getParent(cell))); mxGeometry geo = graph.getCellGeometry(cell); // TODO: Handle offset, relative geometries etc if (geo != null) { result.setX(result.getX() + geo.getX()); result.setY(result.getY() + geo.getY()); } } if (result == null) { mxPoint t = graph.getView().getTranslate(); result = new mxPoint(-t.getX(), -t.getY()); } origins.put(cell, result); } return result; } /** * */ public void paint(Graphics g) { if (preview != null) { preview.paint(g); } } }