0

I have a moveable group inside a Stackpane (So I can use the mouse for zooming and panning)

I am struggling with translating my group so that it moves to a given position on klick.

I am using TranslateTransition like so:

public void goTo(double x, double y) {
    TranslateTransition tt = new TranslateTransition();
    tt.setNode(group);
    tt.setDuration(Duration.millis(TRANSITION_TIME));
    tt.setInterpolator(Interpolator.EASE_BOTH);
    tt.setToX(x);
    tt.setToY(y);
    tt.playFromStart();
}

Look at the given picture that demonstrates the problem:

  • Black is my group
  • Red is the currently visible area of my group
  • Green is a circle within the visible area on screen
  • Purple is a circle somewhere else in the group (can be in the non-visible area)
  • Light purple is the intented target for the movement of the group after clicking the green circle

So my goal is that after clicking on the green circle the purple circle (and the complete group) will move so that the purple circle is in the center of the visible area (light purple in the screenshot - This also implies that i.e the green circle will move to the non visible area)

The issue

So from my understanding the groups translateX and translateY position are the origin of the move. But I do not know who to determine the correct x and y distances to perform the move I also do not know how to get the boundries of the currently visible red area.

I tried something like but I think I am not sure how to use getBoundsInParent properly:

goTo(purpleCircle.getCenterX() - group.getBoundsInParent().getWidth() / 2.0, -purpleCircle.getCenterY() + group.getBoundsInParent().getHeight() / 2.0);

MWE
Question: How to move the purple circle into the middle of the screen when clicking on the green circle (no matter how far I am zoomed in on the green circle)

package com.example.sandbox;

import javafx.animation.Interpolator;
import javafx.animation.TranslateTransition;
import javafx.application.Application;

import javafx.scene.Group;
import javafx.scene.Scene;

import javafx.scene.layout.*;

import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.util.concurrent.atomic.AtomicReference;

public class TranslateTest extends Application {
    Group group;

    @Override
    public void start(Stage primaryStage) {
        StackPane sp = new StackPane();
        group = new Group();
        sp.getChildren().add(group);
        Scene primaryScene = new Scene(sp, 1024, 600);

        AtomicReference<Double> scrollX = new AtomicReference<>((double) 0);
        AtomicReference<Double> scrollY = new AtomicReference<>((double) 0);

        // Add scrolling
        sp.setOnMouseDragged(
                event -> {
                    double newX = event.getX();
                    double newY = event.getY();
                    double diffX = newX - scrollX.get();
                    double diffY = newY - scrollY.get();
                    group.setTranslateX(group.getTranslateX() + diffX);
                    group.setTranslateY(group.getTranslateY() + diffY);
                    scrollX.set(newX);
                    scrollY.set(newY);
                }
        );
        // Record mouse position for correct scrolling
        sp.setOnMouseMoved(
                event -> {
                    scrollX.set(event.getX());
                    scrollY.set(event.getY());
                }
        );
        // Zooming
        sp.setOnScroll(
                event -> {
                    double oldScale = sp.getScaleX();
                    double newScale = oldScale + event.getDeltaY() * 0.01;
                    // Limit scale
                    newScale = Math.max(0.2, Math.min(32, newScale));
                    sp.setScaleX(newScale);
                    sp.setScaleY(newScale);

                    scrollX.set(event.getX());
                    scrollY.set(event.getY());
                    // Zoom to mouse cursor
                    group.setTranslateX(group.getTranslateX() + (scrollX.get() - sp.getWidth() / 2) * (oldScale - newScale) / newScale /* * newScale / sp.getWidth()*/);
                    group.setTranslateY(group.getTranslateY() + (scrollY.get() - sp.getHeight() / 2) * (oldScale - newScale) / newScale /* * newScale / sp.getHeight()*/);
                }
        );

        primaryStage.setScene(primaryScene);
        sp.setPrefWidth(1024);
        sp.setPrefHeight(600);


        System.out.println(group.getBoundsInParent());
        // Display data
        Circle purple = new Circle(-50, 0, 3);
        purple.setFill(Color.PURPLE);

        Circle green = new Circle(0, 0, 3);
        green.setFill(Color.GREEN);

        green.setOnMouseClicked(event -> {
            goTo(-purple.getCenterX(), purple.getCenterY());
        });


        sp.setScaleX(15);
        sp.setScaleY(15);

        group.getChildren().add(purple);
        group.getChildren().add(green);

        primaryStage.show();

    }

    public void goTo(double x, double y) {
        TranslateTransition tt = new TranslateTransition();
        tt.setNode(group);
        tt.setDuration(Duration.millis(2000));
        tt.setInterpolator(Interpolator.EASE_BOTH);
        tt.setToX(x);
        tt.setToY(y);
        tt.playFromStart();
    }

    public static void main(String[] args) {
        launch();
    }
}
  • 3
    Please create and post a [mre] – James_D Feb 21 '23 at 11:38
  • Edited the question with MWE – Dennis Höhl Feb 21 '23 at 12:42
  • 2
    Don't use `StackPane` to hold the `Group`. Try `Pane`. `StackPane` will place nodes based on a limited few options. I think `Center` is the default. – SedJ601 Feb 21 '23 at 15:06
  • 2
    @SedJ601 is correct, as this [example](https://stackoverflow.com/a/64852023/230513) illustrates. – trashgod Feb 21 '23 at 15:47
  • 1
    There is no need to use `AtomicReference`, there is no multi-threading here due to the [JavaFX threading model](https://docs.oracle.com/javase/8/javafx/get-started-tutorial/jfx-architecture.htm#A1107438). Though you may wish to use a `SimpleDoubleProperty` value instead, for the functionality that provides. – jewelsea Feb 21 '23 at 19:42

0 Answers0