JavaFX 8 - Zooming Relative to Mouse Pointer

后端 未结 3 1324
孤独总比滥情好
孤独总比滥情好 2020-12-03 03:55

I have been trying to use this example to learn about zooming and scaling. It does a great job of demonstrating scaling.

I\'ve been trying to figure out how to chan

相关标签:
3条回答
  • 2020-12-03 04:31

    Try this if you want to zoom a pane in a window. Call method on an event.

    private void zoomIn(Pane pane) {
        Scale newScale = new Scale();
        newScale.setX(pane.getScaleX() + YOUR_ZOOM_FACTOR_VALUE);
        newScale.setY(pane.getScaleY() + YOUR_ZOOM_FACTOR_VALUE);
        newScale.setPivotX(pane.getScaleX());
        newScale.setPivotY(pane.getScaleY());
        pane.getTransforms().add(newScale);
    }
    
    0 讨论(0)
  • 2020-12-03 04:42

    I had the same question here on StackOverflow. The following code is a modified version of the question/answer there.

    You can use

    • right mouse button = panning
    • left mouse button = dragging of the nodes
    • scroll wheel = zoom in on mouse cursor location

    The relevant part for you is the onScrollEventHandler.

    import javafx.application.Application;
    import javafx.beans.property.DoubleProperty;
    import javafx.beans.property.SimpleDoubleProperty;
    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.stage.Stage;
    
    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);
        }
    
        /**
         * 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) {
                gc.strokeLine( i, 0, i, h);
                gc.strokeLine( 0, i, w, i);
            }
    
            getChildren().add( grid);
    
            grid.toBack();
        }
    
        public double getScale() {
            return myScale.get();
        }
    
        public void setScale( double scale) {
            myScale.set(scale);
        }
    
        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 delta = 1.2;
    
                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;
    
                scale = clamp( scale, MIN_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);
    
                // note: pivot value must be untransformed, i. e. without scaling
                canvas.setPivot(f*dx, f*dy);
    
                event.consume();
    
            }
    
        };
    
    
        public static double clamp( double value, double min, double max) {
    
            if( Double.compare(value, min) < 0)
                return min;
    
            if( Double.compare(value, max) > 0)
                return max;
    
            return value;
        }
    }
    
    
    
    /**
     * An application with a zoomable and pannable canvas.
     */
    public class ZoomAndScrollApplication 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();
    
        }
    }
    
    0 讨论(0)
  • 2020-12-03 04:58

    I've had this exact problem, I'll show my (heavily condensed) solution. First create a class that is in charge of the zooming:

    public class AnimatedZoomOperator {
    
        private Timeline timeline;
    
        public AnimatedZoomOperator() {         
             this.timeline = new Timeline(60);
        }
    
        public void zoom(Node node, double factor, double x, double y) {    
            // determine scale
            double oldScale = node.getScaleX();
            double scale = oldScale * factor;
            double f = (scale / oldScale) - 1;
    
            // determine offset that we will have to move the node
            Bounds bounds = node.localToScene(node.getBoundsInLocal());
            double dx = (x - (bounds.getWidth() / 2 + bounds.getMinX()));
            double dy = (y - (bounds.getHeight() / 2 + bounds.getMinY()));
    
            // timeline that scales and moves the node
            timeline.getKeyFrames().clear();
            timeline.getKeyFrames().addAll(
                new KeyFrame(Duration.millis(200), new KeyValue(node.translateXProperty(), node.getTranslateX() - f * dx)),
                new KeyFrame(Duration.millis(200), new KeyValue(node.translateYProperty(), node.getTranslateY() - f * dy)),
                new KeyFrame(Duration.millis(200), new KeyValue(node.scaleXProperty(), scale)),
                new KeyFrame(Duration.millis(200), new KeyValue(node.scaleYProperty(), scale))
            );
            timeline.play();
        }
    }
    

    Note that this uses a timeline to animate the zoom. If you don't want this, you can call node.setTranslateX(...), node.setScaleX(...), etc. directly.

    Now to use this operator you can do something like this:

    // Create panel
    StackPane zoomPane = new StackPane();
    zoomPane.getChildren().add(new Circle(100, 100, 10));
    zoomPane.getChildren().add(new Circle(200, 200, 20));
    
    // Create operator
    AnimatedZoomOperator zoomOperator = new AnimatedZoomOperator();
    
    // Listen to scroll events (similarly you could listen to a button click, slider, ...)
    zoomPane.setOnScroll(new EventHandler<ScrollEvent>() {
        @Override
        public void handle(ScrollEvent event) {
            double zoomFactor = 1.5;
            if (event.getDeltaY() <= 0) {
                // zoom out
                zoomFactor = 1 / zoomFactor;
            }
            zoomOperator.zoom(zoomPane, zoomFactor, event.getSceneX(), event.getSceneY());
        }
    });
    
    0 讨论(0)
提交回复
热议问题