javafx scroll zoom pinchzoom panning

javafx - Escala en el punto de pivote en un nodo ya escalado



scroll zoom (3)

Cambio tu clase de SceneGestures por lo que ahora estoy trabajando.

class SceneGestures { private double oldx; private double oldy; double ttx=0; double tty=0; private static final double MAX_SCALE = 10.0d; private static final double MIN_SCALE = .1d; private DragContext sceneDragContext = new DragContext(); PannableCanvas canvas; public SceneGestures( PannableCanvas canvas) { this.canvas = canvas; } public EventHandler<MouseEvent> getOnMousePressedEventHandler() { return onMousePressedEventHandler; } public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() { return onMouseDraggedEventHandler; } public EventHandler<ScrollEvent> getOnScrollEventHandler() { return onScrollEventHandler; } private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() { public void handle(MouseEvent event) { // right mouse button => panning if( !event.isSecondaryButtonDown()) return; sceneDragContext.mouseAnchorX = event.getSceneX(); sceneDragContext.mouseAnchorY = event.getSceneY(); sceneDragContext.translateAnchorX = canvas.getTranslateX(); sceneDragContext.translateAnchorY = canvas.getTranslateY(); } }; private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() { public void handle(MouseEvent event) { // right mouse button => panning if( !event.isSecondaryButtonDown()) return; canvas.setTranslateX(sceneDragContext.translateAnchorX + event.getSceneX() - sceneDragContext.mouseAnchorX); canvas.setTranslateY(sceneDragContext.translateAnchorY + event.getSceneY() - sceneDragContext.mouseAnchorY); event.consume(); } }; /** * Mouse wheel handler: zoom to pivot point */ private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() { @Override public void handle(ScrollEvent event) { double delta = 1; double scale = canvas.getScale(); // currently we only use Y, same value is used for X double oldScale = scale; if (event.getDeltaY() < 0) scale -= delta; else scale += delta; if (scale <= MIN_SCALE) { scale = MIN_SCALE; } else if (scale >= MAX_SCALE) { scale = MAX_SCALE; } if (oldx==0){ ttx=event.getSceneX() ; tty=event.getSceneY() ; }else{ if (oldx!=event.getSceneX()){ ttx=((event.getSceneX()+oldx)/2) ; } if (oldy!=event.getSceneY()){ tty=((event.getSceneY()+oldy)/2); } } // pivot value must be untransformed, i. e. without scaling canvas.setPivot( ((ttx- canvas.getBoundsInParent().getMinX()) / oldScale), ((tty- canvas.getBoundsInParent().getMinY()) / oldScale) ); // if (oldx==0){ oldx=event.getSceneX(); oldy=event.getSceneY(); // } //try { // Robot rbt=new Robot(); // rbt.mouseMove(512, 384); //} catch (AWTException ex) { // System.out.println(ex.getMessage()); // } canvas.setScale( scale); System.out.println( "new pivot x: " + canvas.scaleTransform.getPivotX() + "/" + canvas.scaleTransform.getPivotY() + ", new scale: " + scale); System.out.println( "bounds: " + canvas.getBoundsInParent()); System.out.println( "old: " + oldx+" "+oldy); System.out.println( "tt: " + ttx+" "+tty); event.consume(); } }; }

Intento crear una aplicación con un lienzo ampliable / pannable.

Caracteristicas :

  • acercar / alejar con la rueda del mouse en los puntos de pivote
  • arrastre los nodos por el lienzo con el botón izquierdo del mouse
  • arrastre todo el lienzo con el botón derecho del mouse

El zoom en el punto de pivote funciona siempre que inicie el zoom a escala 1. Coloque el mouse sobre un punto de la cuadrícula y desplace la rueda del mouse. El punto de pivote se mantendrá donde comenzó a hacer zoom.

Problema :

Cuando acerque la imagen, mueva el mouse a otro punto y vuelva a enfocar, luego el punto de pivote se desplazará y el zoom ya no se realizará en la posición inicial del mouse.

Ejemplo :

Aquí está el código:

import javafx.application.Application; import javafx.event.EventHandler; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.control.Label; import javafx.scene.input.MouseEvent; import javafx.scene.input.ScrollEvent; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Rectangle; import javafx.scene.transform.Scale; import javafx.stage.Stage; /** * The canvas which holds all of the nodes of the application. */ class PannableCanvas extends Pane { Scale scaleTransform; public PannableCanvas() { setPrefSize(600, 600); setStyle("-fx-background-color: lightgrey; -fx-border-color: blue;"); // add scale transform scaleTransform = new Scale( 1.0, 1.0); getTransforms().add( scaleTransform); // logging addEventFilter(MouseEvent.MOUSE_PRESSED, event -> { System.out.println( "canvas event: " + ( ((event.getSceneX() - getBoundsInParent().getMinX()) / getScale()) + ", scale: " + getScale()) ); System.out.println( "canvas bounds: " + getBoundsInParent()); }); } /** * Add a grid to the canvas, send it to back */ public void addGrid() { double w = getBoundsInLocal().getWidth(); double h = getBoundsInLocal().getHeight(); // add grid Canvas grid = new Canvas(w, h); // don''t catch mouse events grid.setMouseTransparent(true); GraphicsContext gc = grid.getGraphicsContext2D(); gc.setStroke(Color.GRAY); gc.setLineWidth(1); // draw grid lines double offset = 50; for( double i=offset; i < w; i+=offset) { // vertical gc.strokeLine( i, 0, i, h); // horizontal gc.strokeLine( 0, i, w, i); } getChildren().add( grid); grid.toBack(); } public Scale getScaleTransform() { return scaleTransform; } public double getScale() { return scaleTransform.getY(); } /** * Set x/y scale * @param scale */ public void setScale( double scale) { scaleTransform.setX(scale); scaleTransform.setY(scale); } /** * Set x/y pivot points * @param x * @param y */ public void setPivot( double x, double y) { scaleTransform.setPivotX(x); scaleTransform.setPivotY(y); } } /** * Mouse drag context used for scene and nodes. */ class DragContext { double mouseAnchorX; double mouseAnchorY; double translateAnchorX; double translateAnchorY; } /** * Listeners for making the nodes draggable via left mouse button. Considers if parent is zoomed. */ class NodeGestures { private DragContext nodeDragContext = new DragContext(); PannableCanvas canvas; public NodeGestures( PannableCanvas canvas) { this.canvas = canvas; } public EventHandler<MouseEvent> getOnMousePressedEventHandler() { return onMousePressedEventHandler; } public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() { return onMouseDraggedEventHandler; } private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() { public void handle(MouseEvent event) { // left mouse button => dragging if( !event.isPrimaryButtonDown()) return; nodeDragContext.mouseAnchorX = event.getSceneX(); nodeDragContext.mouseAnchorY = event.getSceneY(); Node node = (Node) event.getSource(); nodeDragContext.translateAnchorX = node.getTranslateX(); nodeDragContext.translateAnchorY = node.getTranslateY(); } }; private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() { public void handle(MouseEvent event) { // left mouse button => dragging if( !event.isPrimaryButtonDown()) return; double scale = canvas.getScale(); Node node = (Node) event.getSource(); node.setTranslateX(nodeDragContext.translateAnchorX + (( event.getSceneX() - nodeDragContext.mouseAnchorX) / scale)); node.setTranslateY(nodeDragContext.translateAnchorY + (( event.getSceneY() - nodeDragContext.mouseAnchorY) / scale)); event.consume(); } }; } /** * Listeners for making the scene''s canvas draggable and zoomable */ class SceneGestures { private static final double MAX_SCALE = 10.0d; private static final double MIN_SCALE = .1d; private DragContext sceneDragContext = new DragContext(); PannableCanvas canvas; public SceneGestures( PannableCanvas canvas) { this.canvas = canvas; } public EventHandler<MouseEvent> getOnMousePressedEventHandler() { return onMousePressedEventHandler; } public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() { return onMouseDraggedEventHandler; } public EventHandler<ScrollEvent> getOnScrollEventHandler() { return onScrollEventHandler; } private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() { public void handle(MouseEvent event) { // right mouse button => panning if( !event.isSecondaryButtonDown()) return; sceneDragContext.mouseAnchorX = event.getSceneX(); sceneDragContext.mouseAnchorY = event.getSceneY(); sceneDragContext.translateAnchorX = canvas.getTranslateX(); sceneDragContext.translateAnchorY = canvas.getTranslateY(); } }; private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() { public void handle(MouseEvent event) { // right mouse button => panning if( !event.isSecondaryButtonDown()) return; canvas.setTranslateX(sceneDragContext.translateAnchorX + event.getSceneX() - sceneDragContext.mouseAnchorX); canvas.setTranslateY(sceneDragContext.translateAnchorY + event.getSceneY() - sceneDragContext.mouseAnchorY); event.consume(); } }; /** * Mouse wheel handler: zoom to pivot point */ private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() { @Override public void handle(ScrollEvent event) { double delta = 1; double scale = canvas.getScale(); // currently we only use Y, same value is used for X double oldScale = scale; if (event.getDeltaY() < 0) scale -= delta; else scale += delta; if (scale <= MIN_SCALE) { scale = MIN_SCALE; } else if (scale >= MAX_SCALE) { scale = MAX_SCALE; } // pivot value must be untransformed, i. e. without scaling canvas.setPivot( ((event.getSceneX() - canvas.getBoundsInParent().getMinX()) / oldScale), ((event.getSceneY() - canvas.getBoundsInParent().getMinY()) / oldScale) ); canvas.setScale( scale); System.out.println( "new pivot x: " + canvas.scaleTransform.getPivotX() + "/" + canvas.scaleTransform.getPivotY() + ", new scale: " + scale); System.out.println( "bounds: " + canvas.getBoundsInParent()); event.consume(); } }; } /** * An application with a zoomable and pannable canvas. */ public class ScrollApplication extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { Group group = new Group(); // create canvas PannableCanvas canvas = new PannableCanvas(); // we don''t want the canvas on the top/left in this example => just // translate it a bit canvas.setTranslateX(100); canvas.setTranslateY(100); // create sample nodes which can be dragged NodeGestures nodeGestures = new NodeGestures( canvas); Label label1 = new Label("Draggable node 1"); label1.setTranslateX(10); label1.setTranslateY(10); label1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler()); label1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler()); Label label2 = new Label("Draggable node 2"); label2.setTranslateX(100); label2.setTranslateY(100); label2.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler()); label2.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler()); Label label3 = new Label("Draggable node 3"); label3.setTranslateX(200); label3.setTranslateY(200); label3.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler()); label3.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler()); Circle circle1 = new Circle( 300, 300, 50); circle1.setStroke(Color.ORANGE); circle1.setFill(Color.ORANGE.deriveColor(1, 1, 1, 0.5)); circle1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler()); circle1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler()); Rectangle rect1 = new Rectangle(100,100); rect1.setTranslateX(450); rect1.setTranslateY(450); rect1.setStroke(Color.BLUE); rect1.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.5)); rect1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler()); rect1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler()); canvas.getChildren().addAll(label1, label2, label3, circle1, rect1); group.getChildren().add(canvas); // create scene which can be dragged and zoomed Scene scene = new Scene(group, 1024, 768); SceneGestures sceneGestures = new SceneGestures(canvas); scene.addEventFilter( MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler()); scene.addEventFilter( MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler()); scene.addEventFilter( ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler()); stage.setScene(scene); stage.show(); canvas.addGrid(); } }

Obviamente, hay algo mal con el cálculo del punto de pivote, pero no puedo descubrir qué es y cómo solucionarlo.

¡Muchas gracias!


En FX8 puedes hacer

final Affine accumulatedScales = new Affine(); chart.getTransforms().add(accumulatedScales); chart.setOnScroll(new EventHandler<ScrollEvent>() { @Override public void handle(ScrollEvent event) { accumulatedScales.appendScale(scaleFactor, scaleFactor, event.getX(), event.getY()); } });

y has terminado


Primero, recomendaría no escalar en pasos lineales, sino por factores para suavizar la escala:

double delta = 1.2; if (event.getDeltaY() < 0) scale /= delta; else scale *= delta;

... y para ser de algún modo mandón, recomiendo los corchetes como un buen estilo ;-):

double delta = 1.2; if (event.getDeltaY() < 0) { scale /= delta; } else { scale *= delta; }

... y para utilizar el valor de desplazamiento del mouse para una calidad aún mejor:

double delta = 1.2; if (event.getDeltaY() < 0) { scale /= Math.pow(delta, -event.getDeltaY()/20); } else { scale *= Math.pow(delta, event.getDeltaY()/20); }

... que finalmente es lo mismo que:

scale *= Math.pow(1.01, event.getDeltaY());

En segundo lugar, recomiendo utilizar las propiedades de escala y trama en lugar de una Transformación:

public class ZoomApplication extends Application { static public class PannableCanvas extends Pane { DoubleProperty myScale = new SimpleDoubleProperty(1.0); public PannableCanvas() { setPrefSize(600, 600); setStyle("-fx-background-color: lightgrey; -fx-border-color: blue;"); // add scale transform scaleXProperty().bind(myScale); scaleYProperty().bind(myScale); // logging addEventFilter(MouseEvent.MOUSE_PRESSED, event -> { System.out.println( "canvas event: " + ( ((event.getSceneX() - getBoundsInParent().getMinX()) / getScale()) + ", scale: " + getScale()) ); System.out.println( "canvas bounds: " + getBoundsInParent()); }); } /** * Add a grid to the canvas, send it to back */ public void addGrid() { double w = getBoundsInLocal().getWidth(); double h = getBoundsInLocal().getHeight(); // add grid Canvas grid = new Canvas(w, h); // don''t catch mouse events grid.setMouseTransparent(true); GraphicsContext gc = grid.getGraphicsContext2D(); gc.setStroke(Color.GRAY); gc.setLineWidth(1); // draw grid lines double offset = 50; for( double i=offset; i < w; i+=offset) { // vertical gc.strokeLine( i, 0, i, h); // horizontal gc.strokeLine( 0, i, w, i); } getChildren().add( grid); grid.toBack(); } public double getScale() { return myScale.get(); } /** * Set x/y scale * @param myScale */ public void setScale( double scale) { myScale.set(scale); } /** * Set x/y pivot points * @param x * @param y */ public void setPivot( double x, double y) { setTranslateX(getTranslateX()-x); setTranslateY(getTranslateY()-y); } } /** * Mouse drag context used for scene and nodes. */ class DragContext { double mouseAnchorX; double mouseAnchorY; double translateAnchorX; double translateAnchorY; } /** * Listeners for making the nodes draggable via left mouse button. Considers if parent is zoomed. */ class NodeGestures { private DragContext nodeDragContext = new DragContext(); PannableCanvas canvas; public NodeGestures( PannableCanvas canvas) { this.canvas = canvas; } public EventHandler<MouseEvent> getOnMousePressedEventHandler() { return onMousePressedEventHandler; } public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() { return onMouseDraggedEventHandler; } private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() { public void handle(MouseEvent event) { // left mouse button => dragging if( !event.isPrimaryButtonDown()) return; nodeDragContext.mouseAnchorX = event.getSceneX(); nodeDragContext.mouseAnchorY = event.getSceneY(); Node node = (Node) event.getSource(); nodeDragContext.translateAnchorX = node.getTranslateX(); nodeDragContext.translateAnchorY = node.getTranslateY(); } }; private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() { public void handle(MouseEvent event) { // left mouse button => dragging if( !event.isPrimaryButtonDown()) return; double scale = canvas.getScale(); Node node = (Node) event.getSource(); node.setTranslateX(nodeDragContext.translateAnchorX + (( event.getSceneX() - nodeDragContext.mouseAnchorX) / scale)); node.setTranslateY(nodeDragContext.translateAnchorY + (( event.getSceneY() - nodeDragContext.mouseAnchorY) / scale)); event.consume(); } }; } /** * Listeners for making the scene''s canvas draggable and zoomable */ class SceneGestures { private static final double MAX_SCALE = 10.0d; private static final double MIN_SCALE = .1d; private DragContext sceneDragContext = new DragContext(); PannableCanvas canvas; public SceneGestures( PannableCanvas canvas) { this.canvas = canvas; } public EventHandler<MouseEvent> getOnMousePressedEventHandler() { return onMousePressedEventHandler; } public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() { return onMouseDraggedEventHandler; } public EventHandler<ScrollEvent> getOnScrollEventHandler() { return onScrollEventHandler; } private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() { public void handle(MouseEvent event) { // right mouse button => panning if( !event.isSecondaryButtonDown()) return; sceneDragContext.mouseAnchorX = event.getSceneX(); sceneDragContext.mouseAnchorY = event.getSceneY(); sceneDragContext.translateAnchorX = canvas.getTranslateX(); sceneDragContext.translateAnchorY = canvas.getTranslateY(); } }; private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() { public void handle(MouseEvent event) { // right mouse button => panning if( !event.isSecondaryButtonDown()) return; canvas.setTranslateX(sceneDragContext.translateAnchorX + event.getSceneX() - sceneDragContext.mouseAnchorX); canvas.setTranslateY(sceneDragContext.translateAnchorY + event.getSceneY() - sceneDragContext.mouseAnchorY); event.consume(); } }; /** * Mouse wheel handler: zoom to pivot point */ private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() { @Override public void handle(ScrollEvent event) { double scale = canvas.getScale(); // currently we only use Y, same value is used for X double oldScale = scale; scale *= Math.pow(1.01, event.getDeltaY()); if (scale <= MIN_SCALE) { scale = MIN_SCALE; } else if (scale >= MAX_SCALE) { scale = MAX_SCALE; } double f = (scale / oldScale)-1; double dx = (event.getSceneX() - (canvas.getBoundsInParent().getWidth()/2 + canvas.getBoundsInParent().getMinX())); double dy = (event.getSceneY() - (canvas.getBoundsInParent().getHeight()/2 + canvas.getBoundsInParent().getMinY())); canvas.setScale( scale); canvas.setPivot(f*dx, f*dy); event.consume(); } }; } public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) { Group group = new Group(); // create canvas PannableCanvas canvas = new PannableCanvas(); // we don''t want the canvas on the top/left in this example => just // translate it a bit canvas.setTranslateX(100); canvas.setTranslateY(100); // create sample nodes which can be dragged NodeGestures nodeGestures = new NodeGestures( canvas); Label label1 = new Label("Draggable node 1"); label1.setTranslateX(10); label1.setTranslateY(10); label1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler()); label1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler()); Label label2 = new Label("Draggable node 2"); label2.setTranslateX(100); label2.setTranslateY(100); label2.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler()); label2.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler()); Label label3 = new Label("Draggable node 3"); label3.setTranslateX(200); label3.setTranslateY(200); label3.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler()); label3.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler()); Circle circle1 = new Circle( 300, 300, 50); circle1.setStroke(Color.ORANGE); circle1.setFill(Color.ORANGE.deriveColor(1, 1, 1, 0.5)); circle1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler()); circle1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler()); Rectangle rect1 = new Rectangle(100,100); rect1.setTranslateX(450); rect1.setTranslateY(450); rect1.setStroke(Color.BLUE); rect1.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.5)); rect1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler()); rect1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler()); canvas.getChildren().addAll(label1, label2, label3, circle1, rect1); group.getChildren().add(canvas); // create scene which can be dragged and zoomed Scene scene = new Scene(group, 1024, 768); SceneGestures sceneGestures = new SceneGestures(canvas); scene.addEventFilter( MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler()); scene.addEventFilter( MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler()); scene.addEventFilter( ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler()); stage.setScene(scene); stage.show(); canvas.addGrid(); } }

Después de algunas reflexiones sobre el zoom, llegué a la conclusión de que sería una buena idea

  1. Escribir un método de ayuda de zoom independiente para facilitar la función de zoom
  2. Para apoyar también el gesto de pellizcar para hacer zoom con el mismo método

Así que escribí el siguiente método de ayuda:

/** Allow to zoom/scale any node with pivot at scene (x,y) coordinates. * * @param node * @param delta * @param x * @param y */ public static void zoom(Node node, double factor, double x, double y) { double oldScale = node.getScaleX(); double scale = oldScale * factor; if (scale < 0.05) scale = 0.05; if (scale > 50) scale = 50; node.setScaleX(scale); node.setScaleY(scale); double f = (scale / oldScale)-1; Bounds bounds = node.localToScene(node.getBoundsInLocal()); double dx = (x - (bounds.getWidth()/2 + bounds.getMinX())); double dy = (y - (bounds.getHeight()/2 + bounds.getMinY())); node.setTranslateX(node.getTranslateX()-f*dx); node.setTranslateY(node.getTranslateY()-f*dy); } public static void zoom(Node node, ScrollEvent event) { zoom(node, Math.pow(1.01, event.getDeltaY()), event.getSceneX(), event.getSceneY()); } public static void zoom(Node node, ZoomEvent event) { zoom(node, event.getZoomFactor(), event.getSceneX(), event.getSceneY()); }

permitiéndome registrar la función de zoom en cualquier nodo tan fácil como:

myView.setOnScroll(event -> GUITools.zoom(myView, event)); // mouse scroll wheel zoom myView.setOnZoom(event -> GUITools.zoom(myView, event)); // pinch to zoom

y hecho...