JavaFX setFitHeight()/setFitWidth() for an image used within a scrollPane disables panning
Asked Answered
B

2

2

So I am creating a map in JavaFX and I would like to have the whole map visible at times. However, the issue is that after I set the imageView to fit the screen size then adding it to the scrollPane my zoom function works fine, but once I zoom in I am not allowed to pan around the image. Below is the code that I have written.

package gameaspects;

import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class SourceCodeVersion8 extends Application{

final double SCALE_DELTA = 1.1;
public double SCALE_TOTAL = 1;

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

@Override
public void start(Stage primaryStage) throws Exception {
    AnchorPane mapAnchorO = addMapAnchor();
    Scene mapScene = new Scene(mapAnchorO);
    primaryStage.setScene(mapScene);
    primaryStage.setFullScreen(true);
    primaryStage.setResizable(false);
    primaryStage.show();
}

//Creates an AnchorPane for the map
private AnchorPane addMapAnchor()
{
    AnchorPane mapAnchor = new AnchorPane();
    ScrollPane mapScrollO = addMapScroll();
    mapAnchor.getChildren().add(mapScrollO);
    AnchorPane.setLeftAnchor(mapScrollO, 0.0);
    AnchorPane.setTopAnchor(mapScrollO, 0.0);
    AnchorPane.setBottomAnchor(mapScrollO, 0.0);
    AnchorPane.setRightAnchor(mapScrollO, 0.0);
    return mapAnchor;
}

//Creates an ImageView for the map
private ImageView addMapView()
{
    Image mapImage = new Image("WorldProvincialMap-v1.01.png");
    ImageView mapView = new ImageView(mapImage);
    GraphicsDevice gd =               GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
    int width = gd.getDisplayMode().getWidth();
    int height = gd.getDisplayMode().getHeight();
    mapView.setFitHeight(height);
    mapView.setFitWidth(width);
    return mapView;
}

//Creates a scrollPane for the map
private ScrollPane addMapScroll()
{
    ScrollPane mapScroll = new ScrollPane();
    ImageView mapViewO = addMapView();
    mapScroll.setContent(mapViewO);
    mapScroll.setPannable(true);
    mapScroll.setVbarPolicy(ScrollBarPolicy.NEVER);
    mapScroll.setHbarPolicy(ScrollBarPolicy.NEVER);
    mapScroll.addEventFilter(ScrollEvent.ANY, e ->{
        e.consume();
        if(e.getDeltaY() == 0)
        {
            return;
        }
        double scaleFactor =
                  (e.getDeltaY() > 0)
                    ? SCALE_DELTA
                    : 1/SCALE_DELTA;

        if(scaleFactor * SCALE_TOTAL >= 1)
        {
            mapScroll.setScaleX(mapScroll.getScaleX() * scaleFactor);
            mapScroll.setScaleY(mapScroll.getScaleY() * scaleFactor);
            SCALE_TOTAL *= scaleFactor;
        }
    });
    return mapScroll;
    }
}
Botanist answered 16/9, 2016 at 10:59 Comment(1)
I haven't tested this, but I think I might consider not using a scroll pane and manipulating the image view's viewport in response to mouse events instead. Not clear which is easier though.Winder
S
4

You're scaling the ScrollPane instead of the content. Furthermore even if you scale the ImageView, scaleX and scaleY are not taken into account when it comes to calculating the content's size. Therefore the ImageView should also be wrapped in a Group:

private ScrollPane addMapScroll() {
    ScrollPane mapScroll = new ScrollPane();
    ImageView mapViewO = addMapView();
    mapScroll.setContent(new Group(mapViewO));
    mapScroll.setPannable(true);
    mapScroll.setVbarPolicy(ScrollBarPolicy.NEVER);
    mapScroll.setHbarPolicy(ScrollBarPolicy.NEVER);
    mapScroll.addEventFilter(ScrollEvent.ANY, e -> {
        e.consume();
        if (e.getDeltaY() == 0) {
            return;
        }
        double scaleFactor
                = (e.getDeltaY() > 0)
                        ? SCALE_DELTA
                        : 1 / SCALE_DELTA;

        if (scaleFactor * SCALE_TOTAL >= 1) {
            mapViewO.setScaleX(mapViewO.getScaleX() * scaleFactor);
            mapViewO.setScaleY(mapViewO.getScaleY() * scaleFactor);
            SCALE_TOTAL *= scaleFactor;
        }
    });
    return mapScroll;
}

To "keep the center centered" you need to adjust he scroll positions accordingly:

if (scaleFactor * SCALE_TOTAL >= 1) {
    Bounds viewPort = mapScroll.getViewportBounds();
    Bounds contentSize = mapViewO.getBoundsInParent();

    double centerPosX = (contentSize.getWidth() - viewPort.getWidth()) * mapScroll.getHvalue() + viewPort.getWidth() / 2;
    double centerPosY = (contentSize.getHeight() - viewPort.getHeight()) * mapScroll.getVvalue() + viewPort.getHeight() / 2;

    mapViewO.setScaleX(mapViewO.getScaleX() * scaleFactor);
    mapViewO.setScaleY(mapViewO.getScaleY() * scaleFactor);
    SCALE_TOTAL *= scaleFactor;

    double newCenterX = centerPosX * scaleFactor;
    double newCenterY = centerPosY * scaleFactor;

    mapScroll.setHvalue((newCenterX - viewPort.getWidth()/2) / (contentSize.getWidth() * scaleFactor - viewPort.getWidth()));
    mapScroll.setVvalue((newCenterY - viewPort.getHeight()/2) / (contentSize.getHeight() * scaleFactor  -viewPort.getHeight()));
}
Surcingle answered 16/9, 2016 at 12:15 Comment(2)
I used this solution. However, now when I zoom in it moves to the top left corner. Is there any way to just have it zoom in on the center of the screen?Botanist
@AdamRen Indeed there is, but you need to recalculate the scroll positions. See my edit.Surcingle
A
1

I improved your version with a mouseDragHandler.

This should be the right solution for your problem!

package test;

import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class test extends Application {

final double SCALE_DELTA = 1.1;
public double SCALE_TOTAL = 1;

public double dragDeltay;
public double dragDeltax;


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

@Override
public void start(Stage primaryStage) throws Exception {
    AnchorPane mapAnchorO = addMapAnchor();
    Scene mapScene = new Scene(mapAnchorO);
    primaryStage.setScene(mapScene);
    primaryStage.setFullScreen(true);
    primaryStage.setResizable(false);
    primaryStage.show();
}

// Creates an AnchorPane for the map
private AnchorPane addMapAnchor() {
    AnchorPane mapAnchor = new AnchorPane();
    ScrollPane mapScrollO = addMapScroll();
    mapAnchor.getChildren().add(mapScrollO);
    AnchorPane.setLeftAnchor(mapScrollO, 0.0);
    AnchorPane.setTopAnchor(mapScrollO, 0.0);
    AnchorPane.setBottomAnchor(mapScrollO, 0.0);
    AnchorPane.setRightAnchor(mapScrollO, 0.0);
    return mapAnchor;
}

// Creates an ImageView for the map
private ImageView addMapView() {
    Image mapImage = new Image("WorldProvincialMap-v1.01.png");
    ImageView mapView = new ImageView(mapImage);
    GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
    int width = gd.getDisplayMode().getWidth();
    int height = gd.getDisplayMode().getHeight();
    mapView.setFitHeight(height);
    mapView.setFitWidth(width);
    return mapView;
}

// Creates a scrollPane for the map
private ScrollPane addMapScroll() {
    ScrollPane mapScroll = new ScrollPane();
    ImageView mapViewO = addMapView();
    mapScroll.setContent(mapViewO);

    mapViewO.setOnMousePressed(new EventHandler<MouseEvent>() {
        @Override
        public void handle(MouseEvent mouseEvent) {
            dragDeltax = mapViewO.getX() - mouseEvent.getScreenX();
            dragDeltay = mapViewO.getY() - mouseEvent.getScreenY();
        }
    });

    mapViewO.setOnMouseDragged(new EventHandler<MouseEvent>() {
        @Override
        public void handle(MouseEvent mouseEvent) {
            mapScroll.setTranslateX(mouseEvent.getScreenX() + dragDeltax);
            mapScroll.setTranslateY(mouseEvent.getScreenY() + dragDeltay);
        }
    });

    mapScroll.setVbarPolicy(ScrollBarPolicy.NEVER);
    mapScroll.setHbarPolicy(ScrollBarPolicy.NEVER);
    mapScroll.addEventFilter(ScrollEvent.ANY, e -> {
        e.consume();
        if (e.getDeltaY() == 0) {
            return;
        }
        double scaleFactor = (e.getDeltaY() > 0) ? SCALE_DELTA : 1 / SCALE_DELTA;

        if (scaleFactor * SCALE_TOTAL >= 1) {
            mapScroll.setScaleX(mapScroll.getScaleX() * scaleFactor);
            mapScroll.setScaleY(mapScroll.getScaleY() * scaleFactor);
            SCALE_TOTAL *= scaleFactor;
        }
    });

    return mapScroll;
}

}
Airlie answered 16/9, 2016 at 12:13 Comment(1)
This fixes the problem with panning, but introduces the problem that I can see outside of the map. Do you have a solution to that?Botanist

© 2022 - 2024 — McMap. All rights reserved.