Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JAVAFX zoom, scroll in ScrollPane

I have JAVAFX application with zoom and scale as described here: Scale at pivot point in an already scaled node What I need to achieve is to place the image to right pane and keep the image on the left as depicted in the figure below. enter image description here

Question How can I embed this application into SPlitPane, where on left will be another panel.

    SplitPane splitPane = new SplitPane();
    splitPane.getItems().add(new Label("Left Panel"));
    splitPane.getItems().add(group);

    Scene scene = new Scene(splitPane, 1024, 768);

Unfortunately the code results in wrong coordinates,

like image 962
Radim Burget Avatar asked Jul 27 '16 05:07

Radim Burget


1 Answers

For zooming inside a ScrollPane, you need to adjust the scroll positions. This is a bit more complex than just appending a transformation , which could be done, if no ScrollPane was used.

Basically you need to check the current scroll position before scaling the node and after scaling modify the scroll positions to prevent the pivot point from changing it's position because of the changed scale factor.

The following example lets you move a circle around "in" a grid and move around the grid when not clicking on a circle (pannable ScrollPane) in addition to allowing you to adjust the zoom when moving the mouse wheel while holding down the CTRL key.

import java.net.MalformedURLException;
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
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.control.ScrollPane;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class PivotZoom extends Application {

    public static Region createContent() {
        double width = 1000;
        double height = 1000;

        Canvas canvas = new Canvas(width, height);
        GraphicsContext gc = canvas.getGraphicsContext2D();

        gc.setFill(Color.LIGHTGREY);
        gc.fillRect(0, 0, width, height);

        gc.setStroke(Color.BLUE);
        gc.beginPath();

        for (int i = 50; i < width; i += 50) {
            gc.moveTo(i, 0);
            gc.lineTo(i, height);
        }

        for (int i = 50; i < height; i += 50) {
            gc.moveTo(0, i);
            gc.lineTo(width, i);
        }
        gc.stroke();

        Pane content = new Pane(
                new Circle(50, 50, 20),
                new Circle(120, 90, 20, Color.RED),
                new Circle(200, 70, 20, Color.GREEN)
        );

        StackPane result = new StackPane(canvas, content);
        result.setAlignment(Pos.TOP_LEFT);

        class DragData {

            double startX;
            double startY;
            double startLayoutX;
            double startLayoutY;
            Node dragTarget;
        }

        DragData dragData = new DragData();

        content.setOnMousePressed(evt -> {
            if (evt.getTarget() != content) {
                // initiate drag gesture, if a child of content receives the
                // event to prevent ScrollPane from panning.
                evt.consume();
                evt.setDragDetect(true);
            }
        });

        content.setOnDragDetected(evt -> {
            Node n = (Node) evt.getTarget();
            if (n != content) {
                // set start paremeters
                while (n.getParent() != content) {
                    n = n.getParent();
                }
                dragData.startX = evt.getX();
                dragData.startY = evt.getY();
                dragData.startLayoutX = n.getLayoutX();
                dragData.startLayoutY = n.getLayoutY();
                dragData.dragTarget = n;
                n.startFullDrag();
                evt.consume();
            }
        });

        // stop dragging when mouse is released
        content.setOnMouseReleased(evt -> dragData.dragTarget = null);

        content.setOnMouseDragged(evt -> {
            if (dragData.dragTarget != null) {
                // move dragged node
                dragData.dragTarget.setLayoutX(evt.getX() + dragData.startLayoutX - dragData.startX);
                dragData.dragTarget.setLayoutY(evt.getY() + dragData.startLayoutY - dragData.startY);
                Point2D p = new Point2D(evt.getX(), evt.getY());
                evt.consume();
            }
        });

        return result;
    }

    @Override
    public void start(Stage primaryStage) throws MalformedURLException {
        Region zoomTarget = createContent();
        zoomTarget.setPrefSize(1000, 1000);
        zoomTarget.setOnDragDetected(evt -> {
            Node target = (Node) evt.getTarget();
            while (target != zoomTarget && target != null) {
                target = target.getParent();
            }
            if (target != null) {
                target.startFullDrag();
            }
        });

        Group group = new Group(zoomTarget);

        // stackpane for centering the content, in case the ScrollPane viewport
        // is larget than zoomTarget
        StackPane content = new StackPane(group);
        group.layoutBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
            // keep it at least as large as the content
            content.setMinWidth(newBounds.getWidth());
            content.setMinHeight(newBounds.getHeight());
        });

        ScrollPane scrollPane = new ScrollPane(content);
        scrollPane.setPannable(true);
        scrollPane.viewportBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
            // use vieport size, if not too small for zoomTarget
            content.setPrefSize(newBounds.getWidth(), newBounds.getHeight());
        });

        content.setOnScroll(evt -> {
            if (evt.isControlDown()) {
                evt.consume();

                final double zoomFactor = evt.getDeltaY() > 0 ? 1.2 : 1 / 1.2;

                Bounds groupBounds = group.getLayoutBounds();
                final Bounds viewportBounds = scrollPane.getViewportBounds();

                // calculate pixel offsets from [0, 1] range
                double valX = scrollPane.getHvalue() * (groupBounds.getWidth() - viewportBounds.getWidth());
                double valY = scrollPane.getVvalue() * (groupBounds.getHeight() - viewportBounds.getHeight());

                // convert content coordinates to zoomTarget coordinates
                Point2D posInZoomTarget = zoomTarget.parentToLocal(group.parentToLocal(new Point2D(evt.getX(), evt.getY())));

                // calculate adjustment of scroll position (pixels)
                Point2D adjustment = zoomTarget.getLocalToParentTransform().deltaTransform(posInZoomTarget.multiply(zoomFactor - 1));

                // do the resizing
                zoomTarget.setScaleX(zoomFactor * zoomTarget.getScaleX());
                zoomTarget.setScaleY(zoomFactor * zoomTarget.getScaleY());

                // refresh ScrollPane scroll positions & content bounds
                scrollPane.layout();

                // convert back to [0, 1] range
                // (too large/small values are automatically corrected by ScrollPane)
                groupBounds = group.getLayoutBounds();
                scrollPane.setHvalue((valX + adjustment.getX()) / (groupBounds.getWidth() - viewportBounds.getWidth()));
                scrollPane.setVvalue((valY + adjustment.getY()) / (groupBounds.getHeight() - viewportBounds.getHeight()));
            }
        });

        StackPane left = new StackPane(new Label("Left Menu"));
        SplitPane root = new SplitPane(left, scrollPane);

        Scene scene = new Scene(root, 800, 600);

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

}
like image 162
fabian Avatar answered Nov 08 '22 20:11

fabian