Friday 15 February 2013

JavaFX Pan & Zoom

These notes are based around putting objects into a normal Pane object and not, for example, a ScrollPane where the rules for finding the centre point to pivot a scale around are a bit odd!

JavaFX gives amazing controls over panning and zooming.  For example to pan a pane you can simply use

    pane.setTranslateX(10);
    pane.setTranslateY(10);

This will set the translate.  If you want to move an object more from its currently panned position use

    pane.setTranslateX(pane.getTranslateX() + ...);
    pane.setTranslateY(pane.getTranslateY() + ...);

Zooming is very similar, to double the size around the centre point of the pane,

    pane.setScaleX(2);
    pane.setScaleY(2);

Putting these together is fine and works well.  However, you would normally want to zoom around the centre point of the screen rather than the centre point of the pane, particularly if the pane has been panned first. To do a zoom around a particular point you can use the Scale transformation provided as standard.

    Scale scale = new Scale();
    scale.setPivotX(pivotX);
    scale.setPivotY(pivotY);
    scale.setX(zoomFactorX);
    scale.setY(zoomFactorY);
    pane.getTransforms().add(scale);

The pivotX, pivotY point here is the point around which the scale takes place.  How, the problem comes to calculate the pivot point.  You can do this fairly easily using the position of the pane in the parent.

    Bounds bip = pane.boundsInParent().get();
    double pivotX = (screenWidth / 2 - bip.getMinX()) / scaleX;
    double pivotY = (screenHeight / 2 - bip.getMinY()) / scaleY;

    Scale scale = new Scale()
    ...

    scaleX *= zoomFactorX;
    scaleY *= zoomFactorY;

Where scaleX and scaleY are the current scaling.  You can keep track of the current scale easily enough by adjusting it after each zoom (as above) or you can get the current width / height from the bounds object and calculate the zoom from the original size.

For consistency, you can now use a transform to do the translation as well.  This makes no difference to the scaling and pivot calculation as the boundsInParent will still be the same whichever way the translation is done.

    Translate translate = new Translate(panX, panY);
    pane.getTransforms().add(translate);

JavaFX Layout Sizes

If you are using JavaFX then it is worth knowing that the definitive way to get size and position information for a Pane is to use

    Bounds bounds = pane.boundsInParent().get();

The Bounds object obtained gives width, height and translated position (minX, minY) for this pane in its parent.  Even if the width of a pane is set it may get overridden by the layout if there is insufficient room.  Therefore, you cannot rely on what you think the size is.  Instead use the bounds above to get the actual answer.