After translating node The layout x and y stays the same - javafx

I currently have some stack panes on a pane and when dragged with a mouse they move around the pane. I do this by getting the coordinate of the mouse
and the translate x and y of the stack pane when I press the the stack pane. Then when I start to drag the stack pane I set the the translation x and y of the stack pane to the mouse coordinates when I pressed the stack pane + the difference of the new mouse coordinates and the old mouse coordinates.
My problem is after dragging the StackPane the layout x and y stays the same I want to update this as I used this else where.
My event handler when you press the StackPane:
EventHandler<MouseEvent> circleOnMousePressedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
currentStackPane = ((StackPane)(t.getSource()));
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
layoutX = currentStackPane.getLayoutX();
layoutY = currentStackPane.getLayoutY();
}
};
My event handler when i drag the StackPane:
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
currentStackPane.setTranslateX(offsetX);
currentStackPane.setTranslateY(offsetY);
}
};
I tried make a event handler after the drag is finished:
EventHandler<MouseEvent> circleOnMouseReleasedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
currentStackPane.setLayoutX(layoutX + ((StackPane)(t.getSource())).getTranslateX());
currentStackPane.setLayoutY(layoutY + ((StackPane)(t.getSource())).getTranslateY());
currentStackPane.setTranslateX(0);
currentStackPane.setTranslateY(0);
}
};
But this doesn't seem to work. Any help would be appreciated thanks!
EDIT:
I have changed my event handlers. It seems to be updating the layout x and y correctly for the first time I drag the stack pane but when i first drag the stack pane and then release the mouse the stackpane moves to a different position then every time i drag after it messes up completely. Not sure why, any help appreciated!
EDIT2: I realised I set translate x to 0 but didnt set translate y to 0 in the mouse released event. It all works now!

To understand the problem, I would first recommend to have a look at the documentation of layoutX/layoutY properties of a Node.
public final DoubleProperty layoutXProperty
Defines the x coordinate of the translation that is added to this
Node's transform for the purpose of layout. The value should be
computed as the offset required to adjust the position of the node
from its current layoutBounds minX position (which might not be 0) to
the desired location.
For example, if textnode should be positioned at finalX
textnode.setLayoutX(finalX - textnode.getLayoutBounds().getMinX());
Failure to subtract layoutBounds minX may result in misplacement of
the node. The relocate(x, y) method will automatically do the correct
computation and should generally be used over setting layoutX
directly.
The node's final translation will be computed as layoutX + translateX,
where layoutX establishes the node's stable position and translateX
optionally makes dynamic adjustments to that position.
If the node is managed and has a Region as its parent, then the layout
region will set layoutX according to its own layout policy. If the
node is unmanaged or parented by a Group, then the application may set
layoutX directly to position it.
In short,for every node that is rendered in the scene, its position is actually a sum of its layoutX/Y and translateX/Y values in relative to its parent node. The layoutX/Y are initially updated as per its parents layout policy. For that reason, there is no point in updating/relying on layoutX/Y values of node, IF its parent (eg,.StackPane,HBox,VBox,..etc) manages it position.
Pane will not manage/decide its children layout. For that reason the default layoutX/Y values of its children is always 0.
From the above info,if we now look into your code, you are updating the translate values and setting the layout values wrongly. Instead what you have to actually do is:
Take intial values of layoutX/Y.
Update the translateX/Y while dragging.
And on mouse released recompute layoutX/Y values and reset
translateX/Y values.
Below is a quick demo of what I have described.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class PaneLayoutDemo extends Application {
double sceneX, sceneY, layoutX, layoutY;
#Override
public void start(Stage stage) throws Exception {
Pane root = new Pane();
Scene sc = new Scene(root, 600, 600);
stage.setScene(sc);
stage.show();
root.getChildren().addAll(getBox("green"), getBox("red"), getBox("yellow"));
}
private StackPane getBox(String color) {
StackPane box = new StackPane();
box.getChildren().add(new Label("Drag me !!"));
box.setStyle("-fx-background-color:" + color + ";-fx-border-width:2px;-fx-border-color:black;");
box.setPrefSize(150, 150);
box.setMaxSize(150, 150);
box.setMinSize(150, 150);
box.setOnMousePressed(e -> {
sceneX = e.getSceneX();
sceneY = e.getSceneY();
layoutX = box.getLayoutX();
layoutY = box.getLayoutY();
System.out.println(color.toUpperCase() + " Box onStart :: layoutX ::" + layoutX + ", layoutY::" + layoutY);
});
box.setOnMouseDragged(e -> {
double offsetX = e.getSceneX() - sceneX;
double offsetY = e.getSceneY() - sceneY;
box.setTranslateX(offsetX);
box.setTranslateY(offsetY);
});
box.setOnMouseReleased(e -> {
// Updating the new layout positions
box.setLayoutX(layoutX + box.getTranslateX());
box.setLayoutY(layoutY + box.getTranslateY());
// Resetting the translate positions
box.setTranslateX(0);
box.setTranslateY(0);
});
return box;
}
public static void main(String[] args) {
Application.launch(args);
}
}
Once you are familiar with the demo, try changing the root from Pane to StackPane and see the behaviour difference.

Related

java.lang.IllegalArgumentException: Problem and Hitbox

I have two problems one this that, if i want to show score with the circle object:
layoutV.getChildren().addAll(virus, score);
I get the following error:
Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Children: duplicate children added: parent = Pane#6661fc86[styleClass=root].
As far as I understand it is because the Task wants to show multiple scores. So should I use another scene or layout to show score?
My other problem is the hitbox of the object, right know everytime i click the score goes up. I looked up the mouse event getTarget but it does not seem like I can make it so that my object is the only target to use the mouse event on.
public class Main extends Application {
private Stage window;
private Pane layoutV;
private Scene scene;
private Circle virus;
private int score;
private Label scores;
#Override
public void start(Stage primaryStage) {
window = primaryStage;
window.setTitle("Enemy TEST");
this.score = 0;
scores = new Label("Score "+ score);
layoutV = new Pane();
scene = new Scene(layoutV, 600, 600);
window.setScene(scene);
window.show();
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
}
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
while (true) {
Platform.runLater(new Runnable() {
#Override
public void run() {
drawCircles();
}
});
Thread.sleep(1000);
}
}
};
public void drawCircles() {
double x = (double)(Math.random() * ((550 - 50) + 1)) + 50;
double y = (double)(Math.random() * ((550 - 50) + 1)) + 50;
double r = (double)(Math.random() * ((30 - 10) + 1)) + 10;
virus = new Circle(x, y, r, Color.VIOLET);
layoutV.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
layoutV.getChildren().remove(e.getTarget());
this.score++;
System.out.println("score: "+ this.score);
}
});
layoutV.getChildren().addAll(virus);
scene.setRoot(layoutV);
window.setScene(scene);
}
public static void main(String[] args) {
launch(args);
}
}
You have lots of issues, not just the ones from your question:
Although it will work as you coded
it, I don't advise spawning a thread to draw your circles, instead
see:
JavaFX periodic background task
You don't need to set the root in the scene and the scene in the
window every time you draw a new circle.
Nor do you need to set the
mouse handler on the layout every time you draw a circle.
Rather than setting a mouse handler on the layout, you are better off setting a mouse handler on the circles themselves (which you can do before you add them to the scene).
score is an int, not a node you can only add nodes to the scene
graph.
See the documentation for the scene package:
A node may occur at most once anywhere in the scene graph. Specifically, a node must appear no more than once in the children list of a Parent or as the clip of a Node. See the Node class for more details on these restrictions.
How you are adding the node more than once is not clear to me, because you are probably doing it in code different than the Main class you provided.
To add a circle with a score on top, use a StackPane with the score in a label, but make the label mouse transparent, so that it does not register any clicks:
Label scoreLabel = new Label(score + "");
scoreLabel.setMouseTransparent(true);
StackPane balloon = new StackPane(circle, scoreLabel);
layoutV.getChildren.add(balloon);
Add the click handler on the balloon.
And additional issues I don't detail here but are solved in the demo code provided.
To fix all your errors, I would write some code like below. Perhaps you can review it and compare it with your code to help understand one way to create this game.
The example code might not be exactly the functionality you are looking for (that is not really its purpose), but it should be enough to keep you on the right track for implementing your application.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.*;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.concurrent.ThreadLocalRandom;
public class Inoculation extends Application {
public static final int W = 600;
public static final int H = 600;
private final IntegerProperty score = new SimpleIntegerProperty(0);
private final Pane playingField = new Pane();
#Override
public void start(Stage stage) {
StackPane overlay = createOverlay();
Pane layout = new StackPane(playingField, overlay);
stage.setScene(new Scene(layout, W, H));
stage.show();
Infection infection = new Infection(playingField, score);
infection.begin();
}
private StackPane createOverlay() {
Label totalScoreLabel = new Label();
totalScoreLabel.textProperty().bind(
Bindings.concat(
"Score ", score.asString()
)
);
StackPane overlay = new StackPane(totalScoreLabel);
StackPane.setAlignment(totalScoreLabel, Pos.TOP_LEFT);
overlay.setMouseTransparent(true);
return overlay;
}
public static void main(String[] args) {
launch(args);
}
}
class Infection {
private static final Duration SPAWN_PERIOD = Duration.seconds(1);
private static final int NUM_SPAWNS = 10;
private final Timeline virusGenerator;
public Infection(Pane playingField, IntegerProperty score) {
virusGenerator = new Timeline(
new KeyFrame(
SPAWN_PERIOD,
event -> spawnVirus(
playingField,
score
)
)
);
virusGenerator.setCycleCount(NUM_SPAWNS);
}
public void begin() {
virusGenerator.play();
}
private void spawnVirus(Pane playingField, IntegerProperty score) {
Virus virus = new Virus();
virus.setOnMouseClicked(
event -> {
score.set(score.get() + virus.getVirusScore());
playingField.getChildren().remove(virus);
}
);
playingField.getChildren().add(virus);
}
}
class Virus extends StackPane {
private static final int MAX_SCORE = 3;
private static final int RADIUS_INCREMENT = 10;
private final int virusScore = nextRandInt(MAX_SCORE) + 1;
public Virus() {
double r = (MAX_SCORE + 1 - virusScore) * RADIUS_INCREMENT;
Circle circle = new Circle(
r,
Color.VIOLET
);
Text virusScoreText = new Text("" + virusScore);
virusScoreText.setBoundsType(TextBoundsType.VISUAL);
virusScoreText.setMouseTransparent(true);
getChildren().setAll(
circle,
virusScoreText
);
setLayoutX(nextRandInt((int) (Inoculation.W - circle.getRadius() * 2)));
setLayoutY(nextRandInt((int) (Inoculation.H - circle.getRadius() * 2)));
setPickOnBounds(false);
}
public int getVirusScore() {
return virusScore;
}
// next random int between 0 (inclusive) and bound (exclusive)
private int nextRandInt(int bound) {
return ThreadLocalRandom.current().nextInt(bound);
}
}
Some additional notes on this implementation that might be useful to know:
The total score is placed in an overlayPane so that it is not obscured by elements added to the playingField (which contains the virus spawns).
The overlayPane is made mouseTransparent, so that it won't intercept any mouse events, and the clicks will fall through to the items in the playing field.
The app currently generates viruses within a fixed field size, regardless of whether you resize the window. That is just the way it is designed and coded, you could code it otherwise if wished. It would be more work to do so.
The Bindings class is used to create a string expression binding which concatenates the static string "Score " with an integer property representing the score. This allows the string representing the score to be bound to the score label text in the overlay so that it automatically updates whenever the score is changed.
The virus generation uses a timeline and is based on the concepts from:
JavaFX periodic background task
The application class is kept deliberately simple to handle mostly just the core application lifecycle, and the actual functionality of the application is abstracted to an Infection class which handles the spawning of the virus and a Virus class that generates a new virus.
This technique is used to center a score for each individual virus on the virus:
how to put a text into a circle object to display it from circle's center?
The virus itself is laid out in a StackPane. The pane has pick on bounds set to false. To remove the virus infection, you must click on the circle which represents the virus and not just anywhere in the square for the stack pane.
Because circle coordinates are in local co-ordinates and the circle is in a parent stack pane representing the virus, the circle itself does not need x and y values set, instead layout x and y values are set on the enclosing pane to allow positioning of the pane representing the entire virus.
The following technique is used to generate random integers in acceptable ranges using ThreadLocalRandom:
How do I generate random integers within a specific range in Java?

Why does my image leave the screen if the X/Y is set to anything but 0?

I am trying to make the bouncing DVD logo as a means of learning to use timeline and keyframe in javaFX. The problem I am running in to is if I set the X/Y of the image to anything other than 0,0 the image will go further than the bounds of the screen. I am just confused on why this is happening and what I need to do to fix it. Thank you!
I have tried setting the image to different areas on the pane. I have tried subtracting more than just the dvd width and height to compensate. I have tried many things.
public class Main extends Application {
Stage window;
private final int WIDTH = 700;
private final int HEIGHT = 700;
private Timeline timeline;
private double xSpeed = 3;
private double ySpeed = 3;
private Parent createContent() {
Pane root = new Pane();
root.setPrefSize(WIDTH,HEIGHT);
ImageView dvd = new ImageView(new Image("/dvd.png"));
dvd.setFitHeight(100);
dvd.setFitWidth(100);
dvd.setPreserveRatio(true);
dvd.setX(100);
dvd.setY(100);
dvd.setPreserveRatio(true);
timeline = new Timeline(new KeyFrame(Duration.millis(16), e-> {
dvd.setTranslateX(dvd.getTranslateX() + xSpeed);
dvd.setTranslateY(dvd.getTranslateY() + ySpeed);
if (xSpeed + dvd.getTranslateX() >= WIDTH - dvd.getFitWidth()){
xSpeed = -xSpeed;
} else if(xSpeed + dvd.getTranslateX() <= 0)
xSpeed = -xSpeed;
if (ySpeed + dvd.getTranslateY() >= HEIGHT - dvd.getFitHeight()){
ySpeed = -ySpeed;
} else if(ySpeed + dvd.getTranslateY() <= 0)
ySpeed = -ySpeed;
}));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
root.getChildren().add(dvd);
return root;
}
#Override
public void start(Stage primaryStage) throws Exception{
window = primaryStage;
Scene mainScene = new Scene(createContent(),WIDTH,HEIGHT);
window.setResizable(false);
window.setTitle("Bouncing DVD");
window.setScene(mainScene);
window.show();
}
public static void main(String[] args) {
launch(args);
}
}
I expect to be able to place the DVD image anywhere on the screen and for it to bounce off of the walls of the scene.
The x and y properties of ImageView are ways of moving the ImageView from it's usual position without affecting the translate properties. Any changes of the rendering position by transforms such as the translate properties happen in addition to this change.
The x and y ranges where the image is rendered are [x+translateX, x+translateX+fitWidth) and[y+translateY, y+translateY+fitHeight) respectively.
The simplest way of fixing this issue is using only a single property per dimension, e.g. translateX and translateY:
dvd.setFitHeight(100);
dvd.setFitWidth(100);
dvd.setPreserveRatio(true);
dvd.setTranslateX(100);
dvd.setTranslateY(100);

How to keep an unmanaged node position fixed when its width/height changes

In JavaFX how do I keep the location (x,y coords) of an unmanaged Node the same when the width/height of the node changes?
The node is unmanaged and laid out using: resizeRelocate. This is working well but sometimes the node changes height or width and I would like to keep the current x,y coords. i.e. the node changes size but doesn't move within the scene.
I've tried listening to the boundsInLocal property and checking for differences in the minY values but the node is still moving and calling resizeRelocate from within the boundsInLocal listener triggers another boundsInLocal update.
What would be the best way to resize and reposition the node so that I keep the x,y coords but can change the width/height?
Here's an example:
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.stage.*;
import javafx.application.*;
public class SizeTest extends Application
{
public static void main (String[] args)
{
Application.launch (args);
}
#Override
public void start (Stage stage)
{
try
{
VBox b = new VBox ();
VBox other = new VBox ();
other.setManaged (false);
other.setPrefWidth (100);
other.setStyle ("-fx-border-width: 2px; -fx-border-color: red; -fx-padding: 5px;");
b.getChildren ().add (other);
VBox another = new VBox ();
another.setPrefWidth (50);
another.setMinHeight (50);
another.setPrefHeight (50);
another.setStyle ("-fx-background-color: blue;");
Button but = new Button ("Push Me");
but.setOnAction (ev ->
{
Button abut = new Button ("Another one");
other.getChildren ().add (abut);
});
other.getChildren ().addAll (another, but);
other.boundsInLocalProperty ().addListener ((p, oldv, newv) ->
{
double h = other.prefHeight (other.getPrefWidth ());
other.resizeRelocate (other.getBoundsInParent ().getMinX (),
other.getBoundsInParent ().getMinY (),
other.getBoundsInParent ().getWidth (),
h);
});
Scene sc = new Scene (b);
stage.setScene (sc);
stage.sizeToScene ();
stage.show ();
Platform.runLater (() ->
{
double h = other.prefHeight (other.getPrefWidth ());
other.resizeRelocate (100,
100,
other.getPrefWidth (),
h);
});
} catch (Exception e) {
e.printStackTrace ();
}
}
}
When you press the "Push Me" button another button is added to the container, it then overflows. I'd like the "other" container to instead resize itself to its preferred size but keep its x,y coords.
The listener for the boundsInLocal property sort of does this but you'll notice the jump in position and incorrect sizing. Removing the listener sizes and positions the node correctly but adding the new button overflows the container.
At this point I'm thinking of creating my own layout manager for this behavior.
My final solution to this was to use a javafx.scene.layout.Pane. My use case here was to have a number of popups appear above the content. I used a StackPane with the "popup pane" being the top level with a transparent background. The "popups" are added to the Pane and positioned with Node.relocate(x,y). The pane handles sizing and will keep the popup at the specified x,y coords. The pane keeps the popup at its preferred size so manual sizes changes should be made through the setPref* methods.
Addendum: while the above method works, because the popup pane would be a sibling of other children of the StackPane then events aren't delivered to the "lower levels", i.e. whatever is below the popup pane.
To get around this I added the content pane (the lower level) to the Pane and then tied the content pane's pref width/height to the size of the Pane.
i.e.
this.popupPane = new Pane ();
this.contentPane = new SplitPane ();
this.contentPane.prefWidthProperty ().bind (this.popupPane.widthProperty ());
this.contentPane.prefHeightProperty ().bind (this.popupPane.heightProperty ());
this.popupPane.getChildren ().add (this.contentPane);

JavaFX - using FillTransition and TranslateTransition misplaces the object

So I have a couple of Rectangle objects of different sizes, named r1 and r2, that initially appear in the dead center of the screen. They have different colors (say one is red and the other blue) and the smaller one is placed on top of the larger one, so even when they are stacked in the center, the two are still distinguishable. I wanted them to move around and return to their original positions, and thus used TranslateTransition as follows:
tt1 = new TranslateTransition(Duration.millis(20000), button1);
tt1.setByX(100); // moves 100 pixels to the right
tt1.setByY(-100); // moves 100 pixels down
tt1.setCycleCount(20); // oscillates 20 times
tt1.setAutoReverse(true);
tt2 = new TranslateTransition(Duration.millis(20000), button2);
tt2.setByX(-100); // moves 100 pixels to the left
tt2.setByY(100); // moves 100 pixels up
tt2.setCycleCount(20); // oscillates 20 times
tt2.setAutoReverse(true);
During their movement, however, if any of r1 and r2 is pressed by a MouseEvent, I would like them to disappear for a few seconds (say 5 seconds) and come back alive again. Using the fact the background color is completely black, I used FillTransition to achieve that effect:
FillTransition ft1 = new FillTransition(Duration.millis(5000), r1, Color.BLACK, Color.BLACK);
FillTransition ft2 = new FillTransition(Duration.millis(5000), r2, Color.BLACK, Color.BLACK);
By converting the Rectangles from Color.BLACK to Color.BLACK for 5 seconds, it gives an effect that the buttons have disappeared for 5 seconds. Also, I have the following setOnMouseClicked on r1 and r2 so they can disappear when a user input is made:
r1.setOnMouseClicked((MouseEvent t) -> {
ft1.play();
});
r2.setOnMouseClicked((MouseEvent t) -> {
ft2.play();
});
After the two objects have disappeared for 5 seconds, they must reappear in the center, as they did in the beginning, and repeat the same oscillating motion using tt1 and tt2, which I achieved with setOnFinished on ft1 and ft2:
ft1.setOnFinished((ActionEvent event) -> {
r1.setFill(color1); // restore the original color
tt1.play();
});
ft2.setOnFinished((ActionEvent event) -> {
r2.setFill(color2); // restore the original color
tt2.play();
});
The problem is, however, when r1 and r2 reappear, they are positioned not at the center, but rather at the location from which they last disappeared - in other words, the location of their rebirth is where they were at during the last TranslateTransition when a user's MouseEvent is detected. I have tried to modify this by using r1.setX(centerX) and r1.setY(centerY), where centerX and centerY are the original center coordinates used in the beginning, but it could not fix the problem. In fact, when I used r1.getX(), the returned value equaled the original centerX value even when it was conspicuous that r1 was not placed in the center. This gave me a suspicion that TranslateTransition performs its duty without altering the actual getX() values. I have also thought of using ParallelTransition somehow on TranslateTransition and FillTransition, so tt1 could finish while ft1 takes effect, but since then ft1 would start running when tt1 has already been running for some time, it would not provide a feasible solution.
So my question is, if an object's TranslateTransition is interrupted in the middle, how do I restore the object's "original" coordinate, not where the object was last left off when TranslateTransition was interrupted?
p.s. I want to avoid creating new Rectangle objects every time a MouseEvent is detected, because that means all TranslateTransition and FillTransition linked to r1 and r2 must be recreated as well.
Sample Solution
Run the program, the rectangle will start moving. Click on the rectangle and it will disappear momentarily. Shortly after it has disappeared, the rectangle will re-appear at its original start location and start moving along its original trajectory.
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Pauser extends Application {
#Override
public void start(Stage stage) throws Exception {
final Rectangle r1 = new Rectangle(
50, 150, 30, 30
);
final TranslateTransition tt1 = new TranslateTransition(
Duration.seconds(5),
r1
);
tt1.setFromX(0); // start at the layout origin for the node
tt1.setFromY(0); //
tt1.setByX(100); // moves 100 pixels to the right
tt1.setByY(-100); // moves 100 pixels down
tt1.setCycleCount(TranslateTransition.INDEFINITE);
tt1.setAutoReverse(true);
tt1.play();
final PauseTransition pt1 = new PauseTransition(
Duration.seconds(1)
);
pt1.setOnFinished(event -> {
tt1.playFromStart();
r1.setVisible(true);
});
r1.setOnMouseClicked(event -> {
r1.setVisible(false);
tt1.stop();
r1.setTranslateX(0);
r1.setTranslateY(0);
pt1.play();
});
stage.setScene(
new Scene(
new Group(r1),
200, 200
)
);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This just solves your problem for one rectangle. You can stick the solution in a loop to handle multiple rectangles.
Observations
A PauseTransition is used rather than a FillTransition and the visibility of the node is set to false while the pause is running. With a FillTransition the user can still click on the node, even though a node filled with the background color cannot be visibly distinguished from the background. So a FillTransition is probably undesirable.
fromX/fromY properties are set for the translate transition otherwise if you stop and run it from the start it will just use the current translateX/translateY values of the node rather than the origin values of 0/0 which is what you want.
While the node is not visible, there is no need to keep running the TranslateTransition, so the transition is stopped for that duration.
When the translate transition is stopped, it leaves the translateX/translateY co-ordinates at wherever they are currently set for the last animation frame. So a manual call to set translateX/translateY to 0/0 is added.
After the pause transition is complete, a request is made to play the translate transition from the start rather than wherever it was previously paused or stopped.
Understanding Transformations
A node's position on the screen is based on transformations applied to its layout position. The layout position is maintained in the node's layoutX/layoutY properties. But if you apply a translate transformation to a node (as you are implicitly doing in a TranslateTransition), then the node's screen position will be layoutX+translateX / layoutY+translateY.
Background Study
Read the Node documentation, in particular the sections on transformations and bounding rectangles.
Try out this layout bounds demonstration to help understand the concepts.
So the key was using setFromX and setFromY to start from the original coordinate.
It was also key to set manually set the translateX/translateY values to 0/0. If this was not done, the node would flash at its last translated position before starting to move from the origin position. I think this is because there is a frame delay from when you request the animation to start and when it actually starts and uses the fromX/fromY co-ordinates of 0/0. Which is kind of a strange behavior.
This works fine for me. Hopefully it good approach. There could be new class to create extends rectangle with events and transitions in it. This is just sample for two rectangles.
import javafx.animation.FadeTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Rectangle rect1 = new Rectangle (100, 40, 120, 120);
rect1.setArcHeight(42);
rect1.setArcWidth(42);
rect1.setFill(Color.AQUA);
TranslateTransition tt1;
tt1 = new TranslateTransition(Duration.millis(3000), rect1);
tt1.setByX(200);
tt1.setByY(-200);
tt1.setCycleCount(2);
tt1.setAutoReverse(true);
tt1.play();
rect1.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
FadeTransition ft = new FadeTransition(Duration.millis(1000), rect1);
ft.setFromValue(1.0);
ft.setToValue(0.0);
ft.play();
event.consume();
}
});
tt1.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
tt1.play();
FadeTransition ft = new FadeTransition(Duration.millis(1000), rect1);
ft.setToValue(1.0);
ft.play();
}
});
Rectangle rect2 = new Rectangle (100, 40, 80, 80);
rect2.setArcHeight(42);
rect2.setArcWidth(42);
rect2.setFill(Color.YELLOWGREEN);
TranslateTransition tt2;
tt2= new TranslateTransition(Duration.millis(6000), rect2);
tt2.setByX(-200);
tt2.setByY(200);
tt2.setCycleCount(2);
tt2.setAutoReverse(true);
tt2.play();
rect2.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
FadeTransition ft = new FadeTransition(Duration.millis(1000), rect2);
ft.setFromValue(1.0);
ft.setToValue(0.0);
ft.play();
event.consume();
}
});
tt2.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
tt2.play();
FadeTransition ft = new FadeTransition(Duration.millis(1000), rect2);
ft.setToValue(1.0);
ft.play();
}
});
StackPane pane = new StackPane();
pane.setAlignment(Pos.CENTER);
pane.getChildren().addAll(rect1,rect2);
Scene scene = new Scene(pane, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args){
launch(args);
}
}

JavaFX MouseEvent not firing after moving nodes in GridPane

I am attempting to create a "draggable" histogram UI with JavaFX. I have a ScrollPane containing a GridPane with 1 column and lots of rows. In each row is an HBox containing a label. Every 10 rows, there is also an HBox containing a Line.
I tried to make the HBoxes containing lines draggable by setting onMousePressed, onMouseDragged, and onMouseReleased event handlers (shown below). It works if I drag and release an hbox-line above its starting point - it ends up in whatever grid row I put it in, and I can click and drag it again. However, if I drag and release a line below its starting point, I can't get any more mouseEvents for that hBox. I tried adding log statements everywhere, nothing. I tried setting onMouseOver, it also was not fired.
Why would moving an hbox around the grid like this work for dragging up but not down?
lineContainer.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
EventTarget target = mouseEvent.getTarget();
lastY = mouseEvent.getSceneY();
}
});
lineContainer.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
Node target = (Node) mouseEvent.getTarget();
HBox hBox = null;
if (target instanceof HBox) {
hBox = (HBox) target;
}
else if (target instanceof Line) {
hBox = (HBox) target.getParent();
}
else { //should never happen
log.info("target not hbox or line: " + target.getClass());
}
if (mouseEvent.getSceneY() <= (lastY - 15)) {
int row = GridPane.getRowIndex(hBox);
GridPane.setRowIndex(hBox, --row);
lastY = mouseEvent.getSceneY();
lastRow = row - 1;
} else if (mouseEvent.getSceneY() >= (lastY + 15)) {
int row = GridPane.getRowIndex(hBox);
GridPane.setRowIndex(hBox, ++row);
lastRow = row - 1;
lastY = mouseEvent.getSceneY();
}
}
});
lineContainer.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
Node tar = (Node) mouseEvent.getTarget();
HBox hBox = null;
if (tar instanceof HBox) {
hBox = (HBox) tar;
}
else if (tar instanceof Line && tar.getParent() instanceof HBox) {
hBox = (HBox) tar.getParent();
}
else { //should never happen
log.info(mouseEvent.getTarget().getClass().toString());
}
}
});
UPDATE: I managed to get it working by creating a new HBox, resetting the onMouse... handlers, and copying its children every time the mouse is released. But I still don't know what was causing the original issue...
The following isn't a direct solution for you, but I wanted to say I have a similar problem and share my observations.
My application allows dragging in both axes (X, Y). All I've been able to figure out that some invisible element is 'obscuring' the MouseEvent hitbox. Testing it using an 'MS minesweeper' approach, shows this interfering area to extend from coords (0,0) of the root to (maxX,maxY) of another Node I have in the scene which is a layer above.
My problem was solved by changing z-order of Parent objects (let's call them layers) containing the Nodes that didn't receive their MouseEvents.
JavaFX MouseEvent doc explains that it is only the top level node which receives the event:
https://docs.oracle.com/javafx/2/api/javafx/scene/input/MouseEvent.html
Also look at the pickOnBounds property: JavaFX: How to make a Node partially mouse transparent?

Resources