How does one get infinite zoom out to work?
To keep the explanation of the problem as simple as possible I have created a ScrollPane with this simple FXML.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.Group?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.shape.Rectangle?>
<ScrollPane fx:id="topScrollPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" style="-fx-background-color: rgb(0,0,0);" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.SimulationSelectionController">
<content>
<AnchorPane onScroll="#handleScroll">
<children>
<Rectangle fx:id="mouseEventSafetyNetRectangle" arcHeight="5.0" arcWidth="5.0" fill="#0000003c" height="700.0" stroke="BLACK" strokeType="INSIDE" width="700.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="5.0" />
<Group fx:id="topScrollPaneGroup" onScroll="#handleScroll">
<children>
<StackPane fx:id="topStackPane" layoutX="0" layoutY="0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity">
<children>
<Group fx:id="topStackPaneGroup" onScroll="#handleScroll">
<children>
<Circle fill="DODGERBLUE" layoutX="-98.0" layoutY="-127.0" radius="350.0" stroke="BLACK" strokeType="INSIDE" />
</children>
</Group>
</children>
</StackPane>
</children>
</Group>
</children>
</AnchorPane>
</content>
</ScrollPane>
With this controller
package application;
import java.util.EventObject;
import javafx.fxml.FXML;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Box;
import javafx.scene.shape.Rectangle;
public class SimulationSelectionController {
@FXML
public Rectangle mouseEventSafetyNetRectangle;
final double SCALE_DELTA = 1.1;
@FXML
public ScrollPane topScrollPane;
@FXML
public Group topScrollPaneGroup;
@FXML
public StackPane topStackPane;
@FXML
public Group topStackPaneGroup;
protected void addMouseEventSafetyNetRectangle(ScrollPane sp) {
System.out.println("Entered addMouseEventSafetyNetRectangle()");
// double maxX = getDataModel().getBoundsInParent().getMaxX();
// double minX = getDataModel().getBoundsInParent().getMinX();
// double maxY = getDataModel().getBoundsInParent().getMaxY();
// double minY = getDataModel().getBoundsInParent().getMinY();
double width = sp.getBoundsInParent().getWidth();
double height = sp.getBoundsInParent().getHeight();
getMouseEventSafetyNetRectangle().setWidth(width);
getMouseEventSafetyNetRectangle().setHeight(height);
getMouseEventSafetyNetRectangle().setLayoutX(0);
getMouseEventSafetyNetRectangle().setLayoutY(0);
// getMouseEventSafetyNetRectangle().setOpacity(0);
}
private Point2D figureScrollOffset(Node scrollContent, ScrollPane scroller) {
double extraWidth = scrollContent.getLayoutBounds().getWidth() - scroller.getViewportBounds().getWidth();
// System.out.println("figureScrollOffset() extraWidth returned " + extraWidth);
double sHvalue = scroller.getHvalue();
double sHmin = scroller.getHmin();
double sHMax = scroller.getHmax();
double hScrollProportion = (scroller.getHvalue() - scroller.getHmin()) / (scroller.getHmax() - scroller.getHmin());
// System.out.println("sHvalue returned " + sHvalue);
// System.out.println("sHmin returned " + sHmin);
// System.out.println("sHMax returned " + sHMax);
// System.out.println("figureScrollOffset() hScrollProportion returned " + hScrollProportion);
double scrollXOffset = hScrollProportion * Math.max(0, extraWidth);
// System.out.println("figureScrollOffset() scrollXOffset returned " + scrollXOffset);
double extraHeight = scrollContent.getLayoutBounds().getHeight() - scroller.getViewportBounds().getHeight();
// System.out.println("figureScrollOffset() extraHeight returned " + extraHeight);
double vScrollProportion = (scroller.getVvalue() - scroller.getVmin()) / (scroller.getVmax() - scroller.getVmin());
// System.out.println("figureScrollOffset() vScrollProportion returned " + vScrollProportion);
double scrollYOffset = vScrollProportion * Math.max(0, extraHeight);
// System.out.println("figureScrollOffset() scrollYOffset returned " + scrollYOffset);
// System.out.println("figureScrollOffset() returned " + scrollXOffset + " for X");
// System.out.println("figureScrollOffset() returned " + scrollYOffset + " for Y");
return new Point2D(scrollXOffset, scrollYOffset);
}
private Rectangle getMouseEventSafetyNetRectangle() {
return mouseEventSafetyNetRectangle;
}
private double getSCALE_DELTA() {
return SCALE_DELTA;
}
private ScrollPane getTopScrollPane() {
return topScrollPane;
}
private Group getTopScrollPaneGroup() {
return topScrollPaneGroup;
}
private StackPane getTopStackPane() {
return topStackPane;
}
private Group getTopStackPaneGroup() {
return topStackPaneGroup;
}
@FXML
public void handleScroll(ScrollEvent se) {
double scrollDelta = 0;
double scaleFactor = 0;
Object wwEventObject = null;
System.out.println("Entered handleScroll()");
System.out.println("Event object fx:id is:" + se.getSource().getClass().getName() );
se.consume();
wwEventObject = se.getSource();
// System.out.println("event Source FX:id is: " + wwEventObject.getId());
// System.out.println("Mouse scroll wheel used.");
scrollDelta = se.getDeltaY();
// System.out.println("Mouse scroll wheel getDeltaY() is:" + scrollDelta);
if (scrollDelta == 0) {
return;
}
scaleFactor = (se.getDeltaY() > 0) ? SCALE_DELTA
: 1 / SCALE_DELTA;
// amount of scrolling in each direction in scrollContent coordinate
// units
Point2D scrollOffset = figureScrollOffset(topScrollPaneGroup, topScrollPane);
((Node)wwEventObject).setScaleX(((Node)wwEventObject).getScaleX() * scaleFactor);
((Node)wwEventObject).setScaleY(((Node)wwEventObject).getScaleY() * scaleFactor);
addMouseEventSafetyNetRectangle(getTopScrollPane());
// move viewport so that old center remains in the center after the
// scaling
repositionScroller(topScrollPaneGroup, topScrollPane, scaleFactor, scrollOffset);
}
@FXML
protected void initialize() {
System.out.println("In initialize() method");
addMouseEventSafetyNetRectangle(getTopScrollPane());
}
private void repositionScroller(Node scrollContent, ScrollPane scroller, double scaleFactor, Point2D scrollOffset) {
// System.out.println("In repositionScroller event method:");
double scrollXOffset = scrollOffset.getX();
double scrollYOffset = scrollOffset.getY();
double extraWidth = scrollContent.getLayoutBounds().getWidth() - scroller.getViewportBounds().getWidth();
if (extraWidth > 0) {
double halfWidth = scroller.getViewportBounds().getWidth() / 2 ;
double newScrollXOffset = (scaleFactor - 1) * halfWidth + scaleFactor * scrollXOffset;
scroller.setHvalue(scroller.getHmin() + newScrollXOffset * (scroller.getHmax() - scroller.getHmin()) / extraWidth);
} else {
scroller.setHvalue(scroller.getHmin());
}
double extraHeight = scrollContent.getLayoutBounds().getHeight() - scroller.getViewportBounds().getHeight();
if (extraHeight > 0) {
double halfHeight = scroller.getViewportBounds().getHeight() / 2 ;
double newScrollYOffset = (scaleFactor - 1) * halfHeight + scaleFactor * scrollYOffset;
scroller.setVvalue(scroller.getVmin() + newScrollYOffset * (scroller.getVmax() - scroller.getVmin()) / extraHeight);
} else {
scroller.setVvalue(scroller.getVmin());
}
}
private void setMouseEventSafetyNetRectangle(Rectangle mouseEventSaftyNetRectangle) {
this.mouseEventSafetyNetRectangle = mouseEventSaftyNetRectangle;
}