I'm trying to implement a ScrollPane with similar functionality as CorelDraw/InDesign: A piece of paper within a ScrollPane that can be moved around (Changing ViewPort) and that can be enlarged/ zoomed in out.
My problem is that I don't know how to get the coordinates of the Page within the ScrollPane. I read many articles about how to implement a ScrollPane with zoomable content and learned that a Group and an additional Node was necessary to make the entire Page visible within a ScrollPane.
What I want:
If the Page is zoomed in so that the upper part and lower part is outside the viewport but the background (in my case a VBox) is visible left and right, I want to know how many pixels of the VBox are visible in the ScrollPanes viewport left of the page content (And same right).
With the current code I know how far the upper left corner of the page is "away" from the left edge of the ScrollPanes viewport but if the entire page is visible it always says 0,0.
Any ideas how to achieve that with my arrangements of components?
My code so far:
package demo;
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class MVP extends Application {
private Stage stage = null;
private Group zoomGroup = null;
private VBox centeredVBox = null;
private ScrollPane scrollPane = null;
private StackPane page = null;
@Override
public void start(Stage primaryStage) throws Exception {
this.stage = primaryStage;
this.page = new StackPane();
this.page.setPrefWidth(42840);
this.page.setPrefHeight(60624);
this.page.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY)));
this.zoomGroup = new Group(page);
this.centeredVBox = new VBox(zoomGroup);
this.centeredVBox.setAlignment(Pos.CENTER);
this.centeredVBox
.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
this.scrollPane = new ScrollPane(centeredVBox);
this.scrollPane.setPannable(true);
this.scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
this.scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
this.scrollPane.setFitToHeight(true); // center
this.scrollPane.setFitToWidth(true); // center
this.scrollPane.setPrefSize(800, 600);
this.centeredVBox.setOnScroll(evt -> {
if (evt.isControlDown()) {
this.onScroll(evt.getTextDeltaY(), new Point2D(evt.getX(), evt.getY()));
}
this.scrollPane.layout();
});
this.scrollPane.setOnMouseReleased(evt -> {
// HOW TO GET THE POSITION OF THE PAGE WITHIN VIEWPORT OF SCROLLPANE IN PIXEL COORDINATES?
System.out.println(
"Upper left: " + (this.scrollPane.getViewportBounds().getMinX() * (1 / this.page.getScaleX())) + "/"
+ (this.scrollPane.getViewportBounds().getMinY() * (1 / this.page.getScaleY())));
System.out.println(
"Lower Right: " + (this.scrollPane.getViewportBounds().getMaxX() * (1 / this.page.getScaleX()))
+ "/" + (this.scrollPane.getViewportBounds().getMaxY() * (1 / this.page.getScaleY())));
});
this.page.setOnMouseMoved(evt -> {
this.stage.setTitle(String.valueOf(evt.getX() + "/" + evt.getY()));
});
Scene scene = new Scene(this.scrollPane);
primaryStage.setTitle("Demo");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private void onScroll(double wheelDelta, Point2D mousePoint) {
// this.pageRepresentation.setLastClickedPoint(mousePoint);
double zoomFactor = Math.exp(wheelDelta * 0.02);
Bounds innerBounds = this.zoomGroup.getLayoutBounds();
Bounds viewportBounds = this.scrollPane.getViewportBounds();
// calculate pixel offsets from [0, 1] range
double valX = this.scrollPane.getHvalue() * (innerBounds.getWidth() - viewportBounds.getWidth());
double valY = this.scrollPane.getVvalue() * (innerBounds.getHeight() - viewportBounds.getHeight());
// convert target coordinates to zoomTarget coordinates
Point2D posInZoomTarget = this.page.parentToLocal(this.zoomGroup.parentToLocal(mousePoint));
// this.pageRepresentation.setLastClickedPoint(posInZoomTarget);
// calculate adjustment of scroll position (pixels)
Point2D adjustment = this.page.getLocalToParentTransform()
.deltaTransform(posInZoomTarget.multiply(zoomFactor - 1));
// this.pageRepresentation.setLastClickedPoint(adjustment);
// convert back to [0, 1] range
// (too large/small values are automatically corrected by ScrollPane)
Bounds updatedInnerBounds = zoomGroup.getBoundsInLocal();
this.scrollPane
.setHvalue((valX + adjustment.getX()) / (updatedInnerBounds.getWidth() - viewportBounds.getWidth()));
this.scrollPane
.setVvalue((valY + adjustment.getY()) / (updatedInnerBounds.getHeight() - viewportBounds.getHeight()));
double zoomValue = this.page.getScaleX() * zoomFactor;
this.page.setScaleX(zoomValue);
this.page.setScaleY(zoomValue);
this.page.layout();
this.scrollPane.layout();
}
}
Solution
Convert coordinates to a common coordinate system, e.g. scene coordinates, then you can directly compare their positions relative to each other.
Sample Application Discussion
I lifted the code from here:
And modified it to report coordinates. Perhaps it reports the values you are looking for.
I didn't really understand your app code, so I did not use that.
There is a lot of code in this answer, but most of it is just copied from the linked answer.
The only new bit is the stuff that reports coordinates for the scene root, ScrollPane viewport, and individual nodes in the viewport.
To use it, run the app and either use the menu or press the
rkey to generate an alert and sysout log reporting the current coordinates of interesting things (in scene coordinates).Scene coordinates are used so that all coordinates are in the same coordinate system. You can use them to compare absolute positions, which, if I understood your question, is something you are trying to do.
The ScrollPane in the example is pannable and zoomable and that is taken into account when calculating the coordinates of items in the viewpoint. So scroll and pan to move the content around, then press the
rkey to report the current scene coordinates of everything.Transformation on node bounds to the scene coordinate system is done using this code:
If the node you want coordinates of is also transformed (then you may need to use
getBoundsInParent()rather thangetBoundsInLocal()). But in this case, it wasn't necessary. The root node in the scroll pane was transformed and not, directly, the nodes inside it. So each child node of the root has boundsInLocal and boundsInParent that are equivalent to each other.Sample Application