I have been having this issue for a long time, and no matter what answers on here I try I keep having an issue.
This is a previous question of mine that had no resolution to it JavaFX 3D PerspectiveCamera affects drag position of a node
(nor did the answers/links provided in the question)
Essentially I am trying to drag a node while keeping the mouse position at to the clicked position of the node while dragging.
The original thought was to do an event.getScreenX() or event.getSceneX() to get the initial position on mouseClicked, and then compare/update in the mouse dragged.
The issue is that when I zoom the camera in and out(camera.setTranslateZ()), for some reason the values will increase/decrease depending on the zoom, i.e., the node drags slower/stays with the mouse when the camera is zoomed out.
For what it's worth I also have scaled the main node by 10, which I think might have something to do with this as one of the examples did seem to break when the scale and/or camera were changed; however the example also doesn't work, with no scale.
Does anyone have any idea? It's extremely frustrating with how simple this task is, yet hard to actually accomplish. I would think that as the mouse would drag, regardless if it dragged 1 pixel with the mouse zoomed in, or 100 pixels with the mouse zoomed out that it wouldn't cause this issue, so I'm wondering if there is some sort of bug with this? Any thoughts are appreciated, thank you.
public class Move extends Application {
double x0,xDiff;
double y0,yDiff;
#Override
public void start(Stage primaryStage) {
Box b = new Box(100,100,1);
b.setLayoutX(0);
b.setLayoutY(0);
// b.setTranslateZ(20000);
Pane root = new Pane();
root.getChildren().add(b);
PhongMaterial p = new PhongMaterial();
p.setDiffuseColor(Color.RED);
b.setMaterial(p);
Scene scene = new Scene(root, 2000, 1250,true);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setTranslateZ(-1000);
camera.setFarClip(2000);
scene.setCamera(camera);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
b.setOnMousePressed(event
->{
x0 = event.getSceneX();
y0 = event.getSceneY();
event.consume();
});
b.setOnMouseDragged(event
->{
xDiff = event.getSceneX() - x0;
yDiff = event.getSceneY() - y0;
b.setLayoutX(b.getLayoutX() + xDiff);
b.setLayoutY(b.getLayoutY() + yDiff);
x0 = event.getSceneX();
y0 = event.getSceneY();
});
primaryStage.setOnScroll(event
->{
if (event.getDeltaY() > 0)
{
camera.setTranslateZ(camera.getTranslateZ() + 45);
}
else
{
camera.setTranslateZ(camera.getTranslateZ() - 45);
}
});
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Related
I am creating a board game (first ported to JavaFX) in which the player must kill the opponent's piece going through a loop.
The above is provided on the Wikipedia page for Surakurta (another name for Permanin). But, I have been only able to build a grid of this sort:
How can I create those roundabouts in the corners?
Implementation details for already built grid: GridPane filled with 36 BoardInput extends javafx.scene.control.Button objects. These objects are special because they automatically create a Background with three BackgroundFill objects - horizontal line, vertical line, and the pebble circular fill.
Use a Path. ArcTo elements allow you to create the circular parts. HLineTo, VLineTo and ClosePath can be used for the straight sections:
Furthermore I don't recommend using BackgroundFills. I'd prefer overlaying invisible buttons on top of the board visuals or handling MouseEvents for the GridPane itself.
Example
private static ArcTo createArc(double radius, double dx, double dy) {
ArcTo result = new ArcTo(radius, radius, 0, dx, dy, true, true);
result.setAbsolute(false);
return result;
}
private static HLineTo createHLine(double length) {
HLineTo result = new HLineTo(length);
result.setAbsolute(false);
return result;
}
private static VLineTo createVLine(double length) {
VLineTo result = new VLineTo(length);
result.setAbsolute(false);
return result;
}
private static Path createPath(double radius, double midSize, Color storke) {
final double lineLength = 2 * radius + midSize;
Path result = new Path(
new MoveTo(radius, 2 * radius), // start at left end of top horizontal line
createArc(radius, radius, -radius), // top left loop
createVLine(lineLength), // down
createArc(radius, -radius, -radius), // bottom left loop
createHLine(lineLength), // right
createArc(radius, -radius, radius), // bottom right loop
createVLine(-lineLength), // up
createArc(radius, radius, radius),
new ClosePath() // left
);
result.setStroke(storke);
result.setStrokeWidth(10);
return result;
}
#Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(new StackPane(
createPath(100, 50, Color.GREEN),
createPath(50, 150, Color.AQUA)
));
primaryStage.setScene(scene);
primaryStage.show();
}
Output
I am making a simple simulation, and have had a good amount of trouble finding the X and Y coordinates of a rotated, weirdly sized, imageView node. (The blue bit is the front)
The goal is to find out an XY coordinate relative to the direction that the imageView is pointing, after it has been rotated. I can find the angle that the imageView is at relative to its starting position but I cannot figure out how to get an XY coordinate of the imageView relative to this angle. Since the .setRotate(angle) method does not change the X and Y location of the imageView, how should I go about finding a point that the imageView is facing?
Minimal example of the rotation and imageView I am using:
Main.java
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root, 500, 500));
Image robotImage = null;
try {
robotImage = new Image(new FileInputStream("res\\robot.png"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
ImageView robot = new ImageView(robotImage);
robot.setLayoutX(125);
robot.setLayoutY(125);
System.out.println("PreRotate X: " + robot.getLayoutX() + "PreRotate Y: " + robot.getLayoutY());
robot.setRotate(45);
System.out.println("PostRotate X: " + robot.getLayoutX() + "PostRotate Y: " + robot.getLayoutY());
root.getChildren().add(robot);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I have already tried using the bounds of the imageView along with lines that lay on top of the imageView, but that requires me to find the new max/min x/y every time that the imageView changes its max/min x/y.
For example:
if (turnAngle < 35) {
directionLine.setStartX(robotLeftRightAngle.getBoundsInParent().getMaxX());
directionLine.setStartY(robotLeftRightAngle.getBoundsInParent().getMinY());
directionLine.setEndX(robotRightLeftAngle.getBoundsInParent().getMinX() + ((robotLeftRightAngle.getBoundsInParent().getMaxX() - robotRightLeftAngle.getBoundsInParent().getMinX()) / 2));
directionLine.setEndY(robotRightLeftAngle.getBoundsInParent().getMinY() + ((robotLeftRightAngle.getBoundsInParent().getMinY() - robotRightLeftAngle.getBoundsInParent().getMinY()) / 2));
}
else if (turnAngle < 55) {
directionLine.setStartX(robotLeftRightAngle.getBoundsInParent().getMaxX());
directionLine.setStartY(robotLeftRightAngle.getBoundsInParent().getMaxY());
directionLine.setEndX(robotRightLeftAngle.getBoundsInParent().getMinX() + ((robotLeftRightAngle.getBoundsInParent().getMaxX() - robotRightLeftAngle.getBoundsInParent().getMinX()) / 2));
directionLine.setEndY(robotRightLeftAngle.getBoundsInParent().getMinY() + ((robotLeftRightAngle.getBoundsInParent().getMaxY() - robotRightLeftAngle.getBoundsInParent().getMinY()) / 2));
}
And so on all the way to 360. DRY yikes.
How should I approach this? Am I using the wrong transformation? Did I not see a method that can be used for this? I know that there must be a better approach. Thanks for reading.
I'm not sure I 100% understand the question. The transformations between coordinate systems but it's hard to tell the coordinate systems you need to convert between from your description, so I assume you want to convert between the coordinate system of robot to the coordinate system of group.
It's possible to use localToParent to convert from the coordinate system of a node to that of the parent which accomodates for all transforms. (parentToLocal would achieve the inverse transformation, but this does not seem to be the required transformation in this case.)
The following example modifies the start and end points of a line to the coordinates of the top left and a point 100 px above of the Rectangle in the Rectangle's coordinate system:
#Override
public void start(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root, 500, 500));
Rectangle robot = new Rectangle(100, 20, Color.RED);
robot.setLayoutX(125);
robot.setLayoutY(125);
Line line = new Line(125, 125, 125, 25);
robot.rotateProperty().addListener(o -> {
Point2D start = robot.localToParent(0, 0);
Point2D end = robot.localToParent(0, -100);
line.setStartX(start.getX());
line.setStartY(start.getY());
line.setEndX(end.getX());
line.setEndY(end.getY());
});
RotateTransition rotateTransition = new RotateTransition(Duration.seconds(5), robot);
rotateTransition.setCycleCount(Animation.INDEFINITE);
rotateTransition.setFromAngle(0);
rotateTransition.setToAngle(360);
rotateTransition.setInterpolator(Interpolator.LINEAR);
rotateTransition.play();
root.getChildren().addAll(robot, line);
primaryStage.show();
}
Right now I'm trying to code a simple abacus with JavaFX. There are balls on horizontal rails arranged in a gridpane, and I'm using a translateTransition to move them to the right on click. The following is the code I have right now, which works fine on any of the balls in the grid except it only animates the movement to the right. On the second click, the ball jumps back to the left to its original position without the animation and I can't figure out why it's not animating. Any help or ideas would be greatly appreciated!
private void onClick(final Circle circle) {
circle.setOnMouseClicked(new EventHandler<MouseEvent>() {
public void handle(MouseEvent mouseEvent) {
//set movement direction, check if circle has been moved
int targetX = 200;
if (circle.getTranslateX() > 1) {
targetX = 0;
}
//animation trigger and details
TranslateTransition push = new TranslateTransition(Duration.millis(500));
push.setNode(circle);
push.setFromX(circle.getCenterX());
push.setToX(targetX);
push.play();
}
});
}
I'm working on a interactive sort application. I must represent numbers as rectangles, and for example, when the sorting algorithm is running, when two numbers are swapped, the rectangles must be swapped. I want to do this with animations. How can I swap the rectangles? I currently testing this using transition but I have some problems. I have two rectangles in a group. When I try to swap the rectangles, both will meet in the middle and stop. Here's the code:
Rectangle r1 = rectangles.get(numbers[0]);
Rectangle r2 = rectangles.get(numbers[1]);
TranslateTransition translateTransition = new TranslateTransition();
translateTransition.setNode(r1);
translateTransition.setDuration(Duration.millis(1000));
translateTransition.setFromX(r1.getX());
translateTransition.setToX(r2.getX());
TranslateTransition translateTransition2 = new TranslateTransition();
translateTransition2.setNode(r2);
translateTransition2.setDuration(Duration.millis(1000));
translateTransition2.setFromX(r2.getX());
translateTransition2.setToX(r1.getX());
translateTransition2.play();
translateTransition.play();
I need a pane similar with canvas. I need to be able to set the rectangles coordinates.
TranslateTransition works with translateX property of the Node. Thus, if you positioned rectangles using setLayout, relocate or just constructor parameter TranslateTransition wouldn't work for you.
You either need to start using translateX coordinates or use Timeline instead of TranslateTransition.
You can read more about layout and translate in JavaDoc for layout
Here is translateX based swap example:
public void start(Stage primaryStage) {
final Rectangle r1 = new Rectangle(50, 50, Color.RED);
final Rectangle r2 = new Rectangle(50, 50, Color.BLUE);
// note I use translate to position rectangles
r1.setTranslateX(50);
r2.setTranslateX(250);
Button btn = new Button();
btn.setText("Move it");
btn.relocate(100, 100);
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
double x1 = r1.getTranslateX();
double x2 = r2.getTranslateX();
TranslateTransition translateTransition = new TranslateTransition();
translateTransition.setNode(r1);
translateTransition.setDuration(Duration.millis(1000));
translateTransition.setToX(x2);
TranslateTransition translateTransition2 = new TranslateTransition();
translateTransition2.setNode(r2);
translateTransition2.setDuration(Duration.millis(1000));
translateTransition2.setToX(x1);
translateTransition2.play();
translateTransition.play();
}
});
Pane root = new Pane();
root.getChildren().addAll(btn, r1, r2);
Scene scene = new Scene(root, 400, 350);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
I'm converting a Swing/Graphics2D app with a lot of custom painting to a JavaFX2 app. Although I absolutely love the new API, I seem to have a performance problem when painting an ellipse that I want to paint below the mouse cursor wherever the mouse is moved. When I move my mouse in a steady way, not ridicously fast, I notice the ellipse is always drawn a few centimeters behind on the mouse trail, and only catches up when I stop moving the cursor. This in a scenegraph with only a handful nodes. In my Swing app I didn't have that problem.
I'm wondering if this is the correct approach for drawing a shape where the mousecursor is?
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.EllipseBuilder;
import javafx.stage.Stage;
public class TestApp extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Pane p = new Pane();
final Ellipse ellipse = EllipseBuilder.create().radiusX(10).radiusY(10).fill(Color.RED).build();
p.getChildren().add(ellipse);
p.setOnMouseMoved(new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
ellipse.setCenterX(event.getX());
ellipse.setCenterY(event.getY());
}
});
Scene scene = SceneBuilder.create().root(p).width(1024d).height(768d).build();
primaryStage.setScene(scene);
primaryStage.show();
}
}
Small update: I upgraded to JavaFX 2.2 and Java7u6 (on Windows 7 64bit), doesn't seem to make a difference though.
Here is some code I use to allow a Label to be dragged around in a Pane.
I don't notice any significant lag behind the mouse trail with it.
// allow the label to be dragged around.
final Delta dragDelta = new Delta();
label.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = label.getLayoutX() - mouseEvent.getSceneX();
dragDelta.y = label.getLayoutY() - mouseEvent.getSceneY();
label.setCursor(Cursor.MOVE);
}
});
label.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
label.setCursor(Cursor.HAND);
}
});
label.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
label.setLayoutX(mouseEvent.getSceneX() + dragDelta.x);
label.setLayoutY(mouseEvent.getSceneY() + dragDelta.y);
}
});
label.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
label.setCursor(Cursor.HAND);
}
});
. . .
// records relative x and y co-ordinates.
class Delta { double x, y; }
Here is a small complete example app using the above code.
Update
The above example, will still lag the object being dragged behind the cursor when the objects being dragged are small.
An alternate approach is to use an ImageCursor comprising of a MousePointer superimposed over the an image representation of the node being dragged, then hide and show the actual node at the start and completion of the drag. This means that the node drag rendering will not lag the cursor (as the image representation of the node is now the cursor). However this approach does have drawbacks => there are restrictions on the size and format of ImageCursors, plus you need to convert your Node to an Image to place it in an ImageCursor, for which you may need advanced Node => Image conversion operations only to available in JavaFX 2.2+.
The lag that you're describing (between your mouse and the dragged shape) is a known JavaFX bug:
https://bugs.openjdk.java.net/browse/JDK-8087922
You can work around it (on Windows, at least) by using an undocumented JVM flag:
-Djavafx.animation.fullspeed=true
This flag is normally for internal performance testing, which is why it is undocumented, but we've been using it for months and haven't had any problems with it so far.
EDIT:
There's another, similar way to workaround this bug that might be a little easier on CPU usage. Simply turn off Prism's vertical sync:
-Dprism.vsync=false
In our app, either of these workarounds solves the lag; there's no need to do both.
To me it doesn't look like a question of painting performance, but how the sequence of mouse events is generated. The events are not generated in real time, some are skipped, when the mouse moves fast. For the most applications this will be the sufficent way. The mouse pointer moves in real time without any time lag.
If you don't want this effect you will have to listen to the mouse pointer directly or find a way to get the events in higher density. I don't know how myself.
There's this "cacheHint" property, available on all Nodes and that may help ?
http://docs.oracle.com/javafx/2/api/javafx/scene/Node.html#cacheHintProperty
Under certain circumstances, such as animating nodes that are very expensive to render, it is desirable to be able to perform transformations on the node without having to regenerate the cached bitmap. An option in such cases is to perform the transforms on the cached bitmap itself.
This technique can provide a dramatic improvement to animation performance, though may also result in a reduction in visual quality. The cacheHint variable provides a hint to the system about how and when that trade-off (visual quality for animation performance) is acceptable.
If your ellipse remains the same the whole time, but is redrawn every time you move it by one pixel, this seems to be a huge slowdown.
I was having the same problem while trying to make nodes on a chart draggable. I fixed it by calling chart.setAnimated(false); In my case the lag was being caused by JavaFX applying a nice smooth animation to the changes my code was making.
here is the code to drag and drop label using mouse in javafx
#FXML
public void lblDragMousePressed(MouseEvent m)
{
System.out.println("Mouse is pressed");
prevLblCordX= (int) lblDragTest.getLayoutX();
prevLblCordY= (int) lblDragTest.getLayoutY();
prevMouseCordX= (int) m.getX();
prevMouseCordY= (int) m.getY();
}
//set this method on mouse released event for lblDrag
#FXML
public void lblDragMouseReleased(MouseEvent m)
{
System.out.println("Label Dragged");
}
// set this method on Mouse Drag event for lblDrag
#FXML
public void lblDragMouseDragged(MouseEvent m)
{
diffX= (int) (m.getX()- prevMouseCordX);
diffY= (int) (m.getY()-prevMouseCordY );
int x = (int) (diffX+lblDragTest.getLayoutX()-rootAnchorPane.getLayoutX());
int y = (int) (diffY+lblDragTest.getLayoutY()-rootAnchorPane.getLayoutY());
if (y > 0 && x > 0 && y < rootAnchorPane.getHeight() && x < rootAnchorPane.getWidth())
{
lblDragTest.setLayoutX(x);
lblDragTest.setLayoutY(y);
}
}
you can use : Node.setCache(true);
(i use it with a Pane with many childrens like a TextField)
Drag Sphere
#Override
public void initialize(URL location, ResourceBundle resources) {
super.initialize(location, resources);
labelTableName.setText("Table Categories");
final PhongMaterial blueMaterial = new PhongMaterial();
blueMaterial.setDiffuseColor(Color.BLUE);
blueMaterial.setSpecularColor(Color.LIGHTBLUE);
final Sphere sphere = new Sphere(50);
sphere.setMaterial(blueMaterial);
final Measure dragMeasure = new Measure();
final Measure position = new Measure();
sphere.setOnMousePressed(mouseEvent -> {
dragMeasure.x = mouseEvent.getSceneX() - position.x;
dragMeasure.y = mouseEvent.getSceneY() - position.y;
sphere.setCursor(Cursor.MOVE);
});
sphere.setOnMouseDragged(mouseEvent -> {
position.x = mouseEvent.getSceneX() - dragMeasure.x;
position.y = mouseEvent.getSceneY() - dragMeasure.y;
sphere.setTranslateX(position.x);
sphere.setTranslateY(position.y);
});
sphere.setOnMouseReleased(mouseEvent -> sphere.setCursor(Cursor.HAND));
sphere.setOnMouseEntered(mouseEvent -> sphere.setCursor(Cursor.HAND));
bottomHeader.getChildren().addAll( sphere);
}
class Measure {
double x, y;
public Measure() {
x = 0; y = 0;
}
}
this is modified Kotlin code based on answer from #jewelsea
var dragDelta = Delta()
var releasedDelta = Delta()
scene.setOnMousePressed {
if (releasedDelta.x > 0 && releasedDelta.y > 0) {
val offsetX = it.sceneX - releasedDelta.x
var offsetY = it.sceneY - releasedDelta.y
dragDelta.x = dragDelta.x + offsetX
dragDelta.y = dragDelta.y + offsetY
} else {
dragDelta.x = it.sceneX
dragDelta.y = it.sceneY
}
scene.cursor = Cursor.MOVE;
releasedDelta = Delta()
}
scene.setOnMouseReleased {
releasedDelta.x = it.sceneX
releasedDelta.y = it.sceneY
scene.cursor = Cursor.HAND;
}
scene.setOnMouseDragged {
scene.translateX = it.sceneX - dragDelta.x;
scene.translateY = it.sceneY - dragDelta.y;
}
scene.setOnMouseEntered {
scene.cursor = Cursor.HAND
}