JavaFx 缩放到鼠标作为枢轴 [英] JavaFx zooming to mouse as pivot

查看:20
本文介绍了JavaFx 缩放到鼠标作为枢轴的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经尝试了

import javafx.application.Application;导入 javafx.beans.property.DoubleProperty;导入 javafx.beans.property.SimpleDoubleProperty;导入 javafx.event.EventHandler;导入javafx.scene.Group;导入 javafx.scene.Node;导入javafx.scene.Scene;导入 javafx.scene.canvas.Canvas;导入 javafx.scene.canvas.GraphicsContext;导入 javafx.scene.control.Label;导入 javafx.scene.input.MouseEvent;导入 javafx.scene.input.ScrollEvent;导入 javafx.scene.layout.Pane;导入 javafx.scene.paint.Color;导入 javafx.scene.shape.Circle;导入 javafx.scene.shape.Rectangle;导入javafx.stage.Stage;类 PannableCanvas 扩展窗格 {DoubleProperty myScale = new SimpleDoubleProperty(1.0);公共 PannableCanvas() {setPrefSize(600, 600);setStyle("-fx-背景色:浅灰色;-fx-边框色:蓝色;");//添加比例变换scaleXProperty().bind(myScale);scaleYProperty().bind(myScale);}/*** 在画布上添加一个网格,将其发送到后面*/公共无效addGrid(){双 w = getBoundsInLocal().getWidth();双 h = getBoundsInLocal().getHeight();//添加网格画布网格 = 新画布(w,h);//不捕获鼠标事件grid.setMouseTransparent(true);GraphicsContext gc = grid.getGraphicsContext2D();gc.setStroke(Color.GRAY);gc.setLineWidth(1);//绘制网格线双偏移 = 50;for(double i=offset; i () {公共无效句柄(鼠标事件事件){//鼠标左键 =>拖动if(!event.isPrimaryButtonDown())返回;nodeDragContext.mouseAnchorX = event.getSceneX();nodeDragContext.mouseAnchorY = event.getSceneY();节点 node = (Node) event.getSource();nodeDragContext.translateAnchorX = node.getTranslateX();nodeDragContext.translateAnchorY = node.getTranslateY();}};私有事件处理程序<鼠标事件>onMouseDraggedEventHandler = new EventHandler<鼠标事件>() {公共无效句柄(鼠标事件事件){//鼠标左键 =>拖动if(!event.isPrimaryButtonDown())返回;双比例 = canvas.getScale();节点 node = (Node) event.getSource();node.setTranslateX(nodeDragContext.translateAnchorX + (( event.getSceneX() - nodeDragContext.mouseAnchorX)/scale));node.setTranslateY(nodeDragContext.translateAnchorY + (( event.getSceneY() - nodeDragContext.mouseAnchorY)/scale));event.consume();}};}/*** 使场景的画布可拖动和缩放的监听器*/类场景手势{私有静态最终双 MAX_SCALE = 10.0d;私有静态最终双 MIN_SCALE = .1d;私有 DragContext 场景DragContext = new DragContext();PannableCanvas 画布;公共场景手势( PannableCanvas 画布){this.canvas = 画布;}公共事件处理程序<鼠标事件>getOnMousePressedEventHandler() {返回 onMousePressedEventHandler;}公共事件处理程序<鼠标事件>getOnMouseDraggedEventHandler() {返回 onMouseDraggedEventHandler;}公共事件处理程序<滚动事件>getOnScrollEventHandler() {返回 onScrollEventHandler;}私有事件处理程序<鼠标事件>onMousePressedEventHandler = new EventHandler<鼠标事件>() {公共无效句柄(鼠标事件事件){//鼠标右键 =>平移if(!event.isSecondaryButtonDown())返回;sceneDragContext.mouseAnchorX = event.getSceneX();sceneDragContext.mouseAnchorY = event.getSceneY();sceneDragContext.translateAnchorX = canvas.getTranslateX();sceneDragContext.translateAnchorY = canvas.getTranslateY();}};私有事件处理程序<鼠标事件>onMouseDraggedEventHandler = new EventHandler<鼠标事件>() {公共无效句柄(鼠标事件事件){//鼠标右键 =>平移if(!event.isSecondaryButtonDown())返回;canvas.setTranslateX(sceneDragContext.translateAnchorX + event.getSceneX() - sceneDragContext.mouseAnchorX);canvas.setTranslateY(sceneDragContext.translateAnchorY + event.getSceneY() - sceneDragContext.mouseAnchorY);event.consume();}};/***鼠标滚轮处理程序:缩放到枢轴点*/私有事件处理程序<滚动事件>onScrollEventHandler = new EventHandler() {@覆盖公共无效句柄(滚动事件事件){双增量 = 1.2;双比例 = canvas.getScale();//目前我们只使用 Y,X 使用相同的值双oldScale =规模;if (event.getDeltaY() <0)比例/=增量;别的比例 *= 增量;规模=钳(规模,MIN_SCALE,MAX_SCALE);双 f = (比例/oldScale)-1;双 dx = (event.getSceneX() - (canvas.getBoundsInParent().getWidth()/2 + canvas.getBoundsInParent().getMinX()));double dy = (event.getSceneY() - (canvas.getBoundsInParent().getHeight()/2 + canvas.getBoundsInParent().getMinY()));canvas.setScale(比例);//注意:pivot 值必须是未转换的,即.e.无缩放canvas.setPivot(f*dx, f*dy);event.consume();}};公共静态双钳(双值,双最小值,双最大值){if( Double.compare(value, min) < 0)返回最小值;if(Double.compare(value, max) > 0)返回最大值;返回值;}}/*** 具有可缩放和可平移画布的应用程序.*/公共类 ZoomAndScrollApplication 扩展应用程序 {公共静态无效主要(字符串[]参数){启动(参数);}@覆盖公共无效开始(阶段阶段){组组 = 新组();//创建画布PannableCanvas 画布 = new PannableCanvas();//在本例中,我们不希望画布位于顶部/左侧 =>只是//稍微翻译一下canvas.setTranslateX(100);canvas.setTranslateY(100);//创建可以拖动的示例节点NodeGestures nodeGestures = new NodeGestures(canvas);标签 label1 = new Label("可拖动节点 1");label1.setTranslateX(10);标签1.setTranslateY(10);label1.addEventFilter(MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());label1.addEventFilter(MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());标签 label2 = new Label("可拖动节点 2");label2.setTranslateX(100);label2.setTranslateY(100);label2.addEventFilter(MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());label2.addEventFilter(MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());标签 label3 = new Label("可拖动节点 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());矩形 rect1 = 新矩形(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);//创建可以拖动和缩放的场景场景场景=新场景(组,1024,768);场景手势场景手势 = 新场景手势(画布);scene.addEventFilter(MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler());scene.addEventFilter(MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler());scene.addEventFilter(ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler());stage.setScene(场景);舞台.show();canvas.addGrid();}}

解决方案

由于到目前为止没有人回答这个问题并且我偶然发现了同样的问题,我将发布我的解决方案,其中添加了一个简单的左/上/节点的下端和右端.

如果你用下面附加的部分替换缩放代码的部分,你应该很好.

//maxX = 右悬垂,maxY = 下悬垂double maxX = canvas.getBoundsInParent().getMaxX() - canvas.localToParent(canvas.getPrefWidth(), canvas.getPrefHeight()).getX();double maxY = canvas.getBoundsInParent().getMaxY() - canvas.localToParent(canvas.getPrefWidth(), canvas.getPrefHeight()).getY();//minX = 左悬垂,minY = 上悬垂双 minX = canvas.localToParent(0,0).getX() - canvas.getBoundsInParent().getMinX();double minY = canvas.localToParent(0,0).getY() - canvas.getBoundsInParent().getMinY();//将悬垂加在一起,因为我们只考虑画布本身的宽度双子X = maxX + minX;双子Y = maxY + minY;//从宽度中减去整体悬垂,仅从左上角减去左悬垂和上悬垂双 dx = (event.getSceneX() - ((canvas.getBoundsInParent().getWidth()-subX)/2 + (canvas.getBoundsInParent().getMinX()+minX)));double dy = (event.getSceneY() - ((canvas.getBoundsInParent().getHeight()-subY)/2 + (canvas.getBoundsInParent().getMinY()+minY)));

警告:总是会正确计算左和上悬垂,但我没有找到任何可行的方法来计算节点的右悬垂和下悬垂,而不使用首选的高度和宽度属性.所以请记住,您需要这些.

此外,您可以通过只计算一次 canvas.getBoundsInParent() 以及多次计算的其他计算来提高性能.

希望对某人有所帮助.

I have tried the this example given in another post to learn about zooming and panning relative to the mouse pointer. When everything is on the grid, zooming works as expected:

When zooming into the mouse pointer location on the top left image, it is zoomed into the exact location as seen in the top right image.

If something is dragged off the grid, e.g. the pivot starts to 'misbehave':

When zooming into the mouse pointer location on the bottom left image, it is zoomed into a location other than the one intended, seen in the bottom right image.

The bounds of the canvas inside the parent changes from 600x600 (without scale) to something like 600x700… Which affects the outcomes dx, dy of the following function.

double dx = (event.getSceneX() - (canvas.getBoundsInParent().getWidth()/2 + canvas.getBoundsInParent().getMinX()));
double dy = (event.getSceneY() - (canvas.getBoundsInParent().getHeight()/2 + canvas.getBoundsInParent().getMinY()));

When editing this function by changing .getWidth() to .getHeight() and then again move the rectangle out right… the zoom works correctly. However, if the rectangle is moved out vertically (to the bottom or top) and to the left the problem again is reproduced again.

Is the above function correct, what is it trying to do? Why does the zoom not work the same, as when everything was on the grid?

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();

    }
}

解决方案

As nobody answered the question up until now and I stumbled over the same problem, I will post my solution, which adds a simple calculation of the left/up/lower and right overhang of nodes.

If you replace the part of the zooming-code with the part attached below, you should be good to got.

//maxX = right overhang, maxY = lower overhang
double maxX = canvas.getBoundsInParent().getMaxX() - canvas.localToParent(canvas.getPrefWidth(), canvas.getPrefHeight()).getX();
double maxY = canvas.getBoundsInParent().getMaxY() - canvas.localToParent(canvas.getPrefWidth(), canvas.getPrefHeight()).getY();

// minX = left overhang, minY = upper overhang
double minX = canvas.localToParent(0,0).getX() - canvas.getBoundsInParent().getMinX();
double minY = canvas.localToParent(0,0).getY() - canvas.getBoundsInParent().getMinY();

// adding the overhangs together, as we only consider the width of canvas itself
double subX = maxX + minX;
double subY = maxY + minY;

// subtracting the overall overhang from the width and only the left and upper overhang from the upper left point
double dx = (event.getSceneX() - ((canvas.getBoundsInParent().getWidth()-subX)/2 + (canvas.getBoundsInParent().getMinX()+minX)));
double dy = (event.getSceneY() - ((canvas.getBoundsInParent().getHeight()-subY)/2 + (canvas.getBoundsInParent().getMinY()+minY)));

WARNING: The left and up overhang will always be computed correctly, but I did not find any working way, to compute the right and lower overhang of nodes without the use of the preferred height and width attributes. So keep in mind, that you need these.

Also, you can improve the performance by only computing the canvas.getBoundsInParent() thing only once before as well as the the other calculations that are computed multiple times.

Hope it helps someone.

这篇关于JavaFx 缩放到鼠标作为枢轴的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆