Search code examples
javajavafxdragscrollpanepanning

Custom Panning Handler Javafx


Hello due to issues with the built in panning tool for scrollpane (runs slowly and transitions in chunks rather than fluidly) I am trying to create a custom panning handler. Below is my attempt but the if statement does not work correctly.

    mapScroll.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
        startX = e.getX();
        startY = e.getY();
    });
    mapScroll.addEventFilter(MouseEvent.MOUSE_DRAGGED, e -> {
        endX = e.getX();
        endY = e.getY();
        Bounds viewBounds = mapScroll.getViewportBounds();
        Bounds mapBounds = mapScroll.getBoundsInParent();
        double xMin = viewBounds.getMinX();
        double xMax = viewBounds.getMaxX();
        double yMin = viewBounds.getMinY();
        double yMax = viewBounds.getMaxY();
        double startEndX = endX - startX;
        double startEndY = endY - startY;
        if(mapBounds.contains(xMin + startEndX, yMin + startEndY) && mapBounds.contains(xMin + startEndX, yMax + startEndY) && mapBounds.contains(xMax + startEndX, yMin + startEndY) && mapBounds.contains(xMax + startEndX, yMax + startEndY))
        {
            double fullWidth = mapScroll.getWidth();
            double fullHeight = mapScroll.getHeight();
            double hChange = startEndX/fullWidth;
            double vChange = startEndY/fullHeight;
            mapScroll.setHvalue(mapScroll.getHvalue() + hChange);
            mapScroll.setVvalue(mapScroll.getVvalue() + vChange);
        }
        endX = startX;
        endY = startY;
    });

Solution

  • You never use the width of the content in your calculations. This means you have to move the mouse the same distance to scroll from one side to the other to bottom regardless of the size of the content. Whether the content is just 10 pixels larger than the viewport or the content is 10 times the size of the viewport makes no difference in your code. Furthermore note that

    endX = startX;
    endY = startY;
    

    is absolutely useless, since you overwrite the values at the start of every call of the event filter anyways. You probably wanted to do the assignments the other way round.

    Also you move the scroll position in the same direction as the mouse movement. You should however move the scroll position in the opposite direction, if you want the mouse position to remain the same relative to the content.

    Here's a example (bounds tests removed for simplicity):

    private double startX;
    private double startY;
    
    @Override
    public void start(Stage primaryStage) {
        ImageView image = new ImageView("https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/687px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg");
        ScrollPane mapScroll = new ScrollPane(image);
        Scene scene = new Scene(mapScroll, 400, 400);
        mapScroll.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
            startX = e.getX();
            startY = e.getY();
        });
        mapScroll.addEventFilter(MouseEvent.MOUSE_DRAGGED, e -> {
            double endX = e.getX();
            double endY = e.getY();
            Bounds viewBounds = mapScroll.getViewportBounds();
    
            double startEndX = startX - endX;
            double startEndY = startY - endY;
    
            Bounds contentBounds = mapScroll.getContent().getLayoutBounds();
    
            double hChange = startEndX / (contentBounds.getWidth() - viewBounds.getWidth());
            double vChange = startEndY / (contentBounds.getHeight() - viewBounds.getHeight());
            mapScroll.setHvalue(mapScroll.getHvalue() + hChange);
            mapScroll.setVvalue(mapScroll.getVvalue() + vChange);
    
            startX = endX;
            startY = endY;
        });
    
        primaryStage.setScene(scene);
        primaryStage.show();
    }