I don't understand why this is happening. Currently I'm trying to drag a node (Canvas) by right clicking it, holding right click down, and moving the mouse. At first it's smooth, but then it starts getting this weird jitter. This only occurs while I hold down the mouse button. If you release it, then it goes back to normal (but can very quickly get jittery again).
With some debugging, it appears that the more you move it, you gain this 'imprecision' between each drag event where the mouse position goes out of sync more and more. The end result is that it flip flops back and forth, which looks very bad.
Code being used:
Main.java
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("CanvasPane.fxml"));
Pane root = fxmlLoader.load();
Scene scene = new Scene(root, 512, 512);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Controller.java
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
public class Controller {
#FXML
private Pane rootPane;
#FXML
private Canvas canvas;
private double mouseX;
private double mouseY;
#FXML
private void initialize() {
GraphicsContext gc = canvas.getGraphicsContext2D();
// Set the writing pen.
gc.setLineCap(StrokeLineCap.ROUND);
gc.setLineJoin(StrokeLineJoin.ROUND);
gc.setLineWidth(1);
gc.setStroke(Color.BLACK);
// Set the background to be transparent.
gc.setFill(Color.BLUE);
gc.fillRect(0, 0, 256, 256);
// Handle moving the canvas.
canvas.setOnMousePressed(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
gc.moveTo(e.getX(), e.getY());
} else if (e.getButton() == MouseButton.SECONDARY) {
mouseX = e.getX();
mouseY = e.getY();
}
});
canvas.setOnMouseDragged(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
gc.lineTo(e.getX(), e.getY());
gc.stroke();
} else if (e.getButton() == MouseButton.SECONDARY) {
double newMouseX = e.getX();
double newMouseY = e.getY();
double deltaX = newMouseX - mouseX;
double deltaY = newMouseY - mouseY;
canvas.setLayoutX(canvas.getLayoutX() + deltaX);
canvas.setLayoutY(canvas.getLayoutY() + deltaY);
mouseX = newMouseX;
mouseY = newMouseY;
// Debug: Why is this happening?
if (Math.abs(deltaX) > 4 || Math.abs(deltaY) > 4)
System.out.println(deltaX + " " + deltaY);
}
});
}
}
CanvasPane.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.canvas.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.Pane?>
<Pane fx:id="rootPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="0.0" minWidth="0.0" prefHeight="512.0" prefWidth="512.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="your-controller-here">
<children>
<Canvas fx:id="canvas" height="256.0" layoutX="122.0" layoutY="107.0" width="256.0" />
</children>
</Pane>
USAGE INSTRUCTIONS
1) Run application
2) Right click on the canvas and move it very slightly (like only one pixel per second)
3) Move it a bit faster so the debug prints
4) When you go back to moving slow, for some reason you'll see it hopping back and forth (like the delta movement between the old and new position is greater than 5 pixels) even though you only moved it one pixel.
It then appears to try to fix itself next time you move, which gives it an ugly jittery look.
The reason I'm confused is because there are times where it works great, and then the accuracy just drops as you continue dragging.
Did I code something wrong, or is this a potential bug?
The coordinates you are measuring with MouseEvent.getX() and MouseEvent.getY() are relative to the node on which the event occurred: i.e. relative to your Canvas. Since you then move the canvas, the old coordinates are now incorrect (since they were relative to the old position, not the new one). Consequently your values for deltaX and deltaY are incorrect.
To fix this, just measure relative to something that is "fixed", e.g. the Scene. You can do this by using MouseEvent.getSceneX() and MouseEvent.getSceneY().
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
public class CanvasDragController {
#FXML
private Pane rootPane;
#FXML
private Canvas canvas;
private double mouseX;
private double mouseY;
#FXML
private void initialize() {
GraphicsContext gc = canvas.getGraphicsContext2D();
// Set the writing pen.
gc.setLineCap(StrokeLineCap.ROUND);
gc.setLineJoin(StrokeLineJoin.ROUND);
gc.setLineWidth(1);
gc.setStroke(Color.BLACK);
// Set the background to be transparent.
gc.setFill(Color.BLUE);
gc.fillRect(0, 0, 256, 256);
// Handle moving the canvas.
canvas.setOnMousePressed(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
gc.moveTo(e.getX(), e.getY());
} else if (e.getButton() == MouseButton.SECONDARY) {
mouseX = e.getSceneX();
mouseY = e.getSceneY();
}
});
canvas.setOnMouseDragged(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
gc.lineTo(e.getX(), e.getY());
gc.stroke();
} else if (e.getButton() == MouseButton.SECONDARY) {
double newMouseX = e.getSceneX();
double newMouseY = e.getSceneY();
double deltaX = newMouseX - mouseX;
double deltaY = newMouseY - mouseY;
canvas.setLayoutX(canvas.getLayoutX() + deltaX);
canvas.setLayoutY(canvas.getLayoutY() + deltaY);
mouseX = newMouseX;
mouseY = newMouseY;
// Why is this happening?
if (Math.abs(deltaX) > 4 || Math.abs(deltaY) > 4)
System.out.println(deltaX + " " + deltaY);
}
});
}
}
An alternative approach (which is not quite as intuitive, to me at least) is to use MouseEvent.getX() and MouseEvent.getY() but not to update the "last coordinates" of the mouse. The way to think of this is that, as the node is dragged around, you want it not to be moving in relation to the mouse - in other words you want the node to move so that the coordinates of the mouse remain fixed relative to it. This version looks like:
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
public class CanvasDragController {
#FXML
private Pane rootPane;
#FXML
private Canvas canvas;
private double mouseX;
private double mouseY;
#FXML
private void initialize() {
GraphicsContext gc = canvas.getGraphicsContext2D();
// Set the writing pen.
gc.setLineCap(StrokeLineCap.ROUND);
gc.setLineJoin(StrokeLineJoin.ROUND);
gc.setLineWidth(1);
gc.setStroke(Color.BLACK);
// Set the background to be transparent.
gc.setFill(Color.BLUE);
gc.fillRect(0, 0, 256, 256);
// Handle moving the canvas.
canvas.setOnMousePressed(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
gc.moveTo(e.getX(), e.getY());
} else if (e.getButton() == MouseButton.SECONDARY) {
mouseX = e.getX();
mouseY = e.getY();
}
});
canvas.setOnMouseDragged(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
gc.lineTo(e.getX(), e.getY());
gc.stroke();
} else if (e.getButton() == MouseButton.SECONDARY) {
double newMouseX = e.getX();
double newMouseY = e.getY();
double deltaX = newMouseX - mouseX;
double deltaY = newMouseY - mouseY;
canvas.setLayoutX(canvas.getLayoutX() + deltaX);
canvas.setLayoutY(canvas.getLayoutY() + deltaY);
// Why is this happening?
if (Math.abs(deltaX) > 4 || Math.abs(deltaY) > 4)
System.out.println(deltaX + " " + deltaY);
}
});
}
}
Related
So basically i am making a game in which the circle will move in the four directions. When the "S" or "RIGHT" key is pressed once, the circle will move till it reaches the maxX of the pane. And when i press the "A" key or "LEFT", the circle will move till it reaches minX of pane. The "UP" or "W" and "DOWN" or "Z" keys also follow similar logic. If it get to maxY of pane it stops till another key is pressed and vice versa.
Here is the code for the controller class
#FXML
private Pane board;
private BooleanProperty wPressed = new SimpleBooleanProperty();
private BooleanProperty aPressed = new SimpleBooleanProperty();
private BooleanProperty zPressed = new SimpleBooleanProperty();
private BooleanProperty sPressed = new SimpleBooleanProperty();
private BooleanBinding keyPressed = wPressed.or(aPressed).or(zPressed).or(sPressed);
private Circle circle = new Circle(20,40,20);
private Rectangle[][] grid;
private int size = 960;
private int spots = 16;
private int squareSize = size / spots;
#FXML
void handleB(ActionEvent event) {
}
AnimationTimer timer = new AnimationTimer() {
double deltaX = 2;
double deltaY = 2;
#Override
public void handle(long timestamp) {
double rectX = circle.getLayoutX();
Bounds bounds = board.getBoundsInLocal();
double maxX = bounds.getMaxX();
double maxY = bounds.getMaxY();
double minX = bounds.getMinX();
double minY = bounds.getMinY();
if (circle.getLayoutX() >= ( maxX- circle.getRadius())) {
deltaX = 0;
//System.out.println(rect.getLayoutX());
}
if (circle.getLayoutY() >= (maxY - circle.getRadius())) {
deltaY = 0;
}
if (circle.getLayoutX() <= ( minX + circle.getRadius())) {
deltaX = 0;
//System.out.println(rect.getLayoutX());
}
if (circle.getLayoutY() <= ( minY + circle.getRadius())) {
deltaY = 0;
}
if(wPressed.get()) {
circle.setLayoutY(circle.getLayoutY() - deltaY);
}
if(zPressed.get()){
circle.setLayoutY(circle.getLayoutY() + deltaY);
}
if(aPressed.get()){
circle.setLayoutX(circle.getLayoutX() - deltaX);
}
if(sPressed.get()){
circle.setLayoutX(circle.getLayoutX() + deltaX);
}
}
};
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
grid = new Rectangle[spots][spots];
for (int i = 0; i < size; i += squareSize) {
for (int j = 0; j < size; j += squareSize) {
Rectangle r = new Rectangle(i, j, squareSize, squareSize);
grid[i / squareSize][j / squareSize] = r;
// Filling each grid with the cell image
r.setFill(Color.GRAY);
r.setStroke(Color.BLACK);
board.getChildren().add(r);
}
}
board.getChildren().addAll(circle);
movementSetup();
keyPressed.addListener(((observableValue, aBoolean, t1) -> {
if(!aBoolean){
timer.start();
} else {
timer.stop();
}
}));
}
private void movementSetup() {
board.setOnKeyPressed(e -> {
if(e.getCode() == KeyCode.UP || e.getCode() == KeyCode.W ) {
zPressed.set(false);
sPressed.set(false);
aPressed.set(false);
wPressed.set(true);
}
if(e.getCode() == KeyCode.A || e.getCode() == KeyCode.LEFT) {
zPressed.set(false);
sPressed.set(false);
wPressed.set(false);
aPressed.set(true);
}
if(e.getCode() == KeyCode.Z || e.getCode() == KeyCode.DOWN) {
wPressed.set(false);
aPressed.set(false);
sPressed.set(false);
zPressed.set(true);
}
if(e.getCode() == KeyCode.S || e.getCode() == KeyCode.RIGHT) {
wPressed.set(false);
aPressed.set(false);
zPressed.set(false);
sPressed.set(true);
}
});
}
}
** I did the pane in scenebuilder which i inserted it into an anchorpane as well**
The fxml file is below (ui.fxml)
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.Pane?>
<AnchorPane maxHeight="1000.0" maxWidth="1200.0" minHeight="0.0" minWidth="0.0" prefHeight="1000.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fx.game.Controller">
<children>
<Pane fx:id="board" maxHeight="960.0" maxWidth="960.0" minHeight="0.0" minWidth="0.0" prefHeight="960.0" prefWidth="960.0">
<children>
<Button layoutX="367.0" layoutY="934.0" mnemonicParsing="false" onAction="#handleB" text="Button" />
</children>
</Pane>
</children>
</AnchorPane>
Lastly the main class
package fx.game;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class HelloApplication extends Application {
#Override
public void start(Stage stage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("ui.fxml"));
stage.setTitle("Hello World");
stage.setScene(new Scene(root));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Am really bad at game development
I might be doing it completely wrong
Anyone who can give me a better solution will be appreciated
From my limited understanding and testing, board.setOnKeyPressed isn't responding to key events, I used the Scene instead. You should also be taking into account onKeyReleased.
I don't know why you're starting and stoping the timer, just start it and on each, evaluate the state and make the changes that are required.
Your movement logic seems to be off as well. Get the circles current location, apply any movement to it as required, then determine if the new position is outside the acceptable bounds, adjust the position as needed and then update the circles position.
I also found that the Bounds the board would update based on the position of it's children, which made it hard to do bounds checking on, instead, I calculate the expected size based on the squareSize and the number of spots
For example...
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class App extends Application {
private BooleanProperty wPressed = new SimpleBooleanProperty();
private BooleanProperty aPressed = new SimpleBooleanProperty();
private BooleanProperty zPressed = new SimpleBooleanProperty();
private BooleanProperty sPressed = new SimpleBooleanProperty();
private BooleanBinding keyPressed = wPressed.or(aPressed).or(zPressed).or(sPressed);
private Circle circle = new Circle(20, 20, 20);
private Rectangle[][] grid;
private int size = 960;
private int spots = 16;
private int squareSize = size / spots;
AnimationTimer timer = new AnimationTimer() {
double deltaX = 2;
double deltaY = 2;
#Override
public void handle(long timestamp) {
double rectX = circle.getLayoutX();
Bounds bounds = board.getBoundsInLocal();
double width = squareSize * spots;
double height = squareSize * spots;
double diameter = circle.getRadius() * 2;
double x = circle.getLayoutX();
double y = circle.getLayoutY();
if (wPressed.get()) {
y -= deltaY;
}
if (zPressed.get()) {
y += deltaY;
}
if (aPressed.get()) {
x -= deltaX;
}
if (sPressed.get()) {
x += deltaX;
}
if (x >= (width - diameter)) {
x = width - diameter;
}
if (x < 0) {
x = 0;
}
if (y > (height - diameter)) {
y = height - diameter;
}
if (y < 0) {
y = 0;
}
circle.setLayoutX(x);
circle.setLayoutY(y);
}
};
private Pane board = new AnchorPane();
#Override
public void start(Stage stage) {
stage.setTitle("Dice Game");
setup();
Scene scene = new Scene(board);
movementSetup(scene);
stage.setScene(scene);
stage.sizeToScene();
stage.show();
}
private void movementSetup(Scene scene) {
scene.setOnKeyPressed(e -> {
zPressed.set(false);
sPressed.set(false);
aPressed.set(false);
wPressed.set(false);
if (e.getCode() == KeyCode.UP || e.getCode() == KeyCode.W) {
wPressed.set(true);
} else if (e.getCode() == KeyCode.A || e.getCode() == KeyCode.LEFT) {
aPressed.set(true);
}else if (e.getCode() == KeyCode.Z || e.getCode() == KeyCode.DOWN) {
zPressed.set(true);
}else if (e.getCode() == KeyCode.S || e.getCode() == KeyCode.RIGHT) {
sPressed.set(true);
}
});
}
protected void setup() {
grid = new Rectangle[spots][spots];
for (int i = 0; i < size; i += squareSize) {
for (int j = 0; j < size; j += squareSize) {
Rectangle r = new Rectangle(i, j, squareSize, squareSize);
grid[i / squareSize][j / squareSize] = r;
// Filling each grid with the cell image
r.setFill(Color.GRAY);
r.setStroke(Color.BLACK);
board.getChildren().add(r);
}
}
board.getChildren().addAll(circle);
timer.start();
}
public static void main(String[] args) {
launch();
}
}
nb: I'm a complete JavaFX noob, so there's probably better ways to approach this issue and maybe having a look at things like Introduction to JavaFX for Game Development (or other "javafx game development blogs" might provide better ideas
I have a javafx program that generates a canvas where a user can draw an Ellipse. I am using the press down, drag and release technique for the mouse.
However I want to be able to scale the shape in size from the top left corner of the shape (much like a Rectangle is scaled) rather than its center. Is there any suggestions to how I could achieve this?
#Override
public void start(Stage ellipseStage) {
//Create ellipse
ellipsePane = new Pane();
ellipsePane.setMinSize(600,600);
ellipsePane.setOnMousePressed(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
ellipse = new Ellipse();
ellipse.setCenterX(e.getX());
ellipse.setCenterY(e.getY());
ellipse.setStroke(Color.ORANGE);
ellipse.setFill(Color.BLACK);
ellipsePane.getChildren().add(ellipse);
}
//if we double click
if (e.getClickCount() == 2) {
ellipse = null;
}
});
//When the mouse is dragged the ellipse expands
ellipsePane.setOnMouseDragged(e -> {
if (ellipse != null) {
ellipse.setRadiusX(e.getX() - ellipse.getCenterX());
ellipse.setRadiusY(e.getY() - ellipse.getCenterY());
}
});
Scene scene = new Scene(ellipsePane);
ellipseStage.setScene(scene);
ellipseStage.show();
}
public static void main(String[] args) {
launch(args);
}}
One option is to keep track of the location of the original mouse press, then set the center X/Y properties of the Ellipse to always be the midpoint between the origin and where the mouse is currently (i.e. where it's dragged). The radii would be half the distance between the origin and the current location as well. Here's an example:
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Ellipse;
import javafx.stage.Stage;
public class Main extends Application {
private Pane pane;
private Point2D origin;
private Ellipse ellipse;
#Override
public void start(Stage primaryStage) {
pane = new Pane();
pane.setOnMousePressed(this::handleMousePressed);
pane.setOnMouseDragged(this::handleMouseDragged);
pane.setOnMouseReleased(this::handleMouseReleased);
primaryStage.setScene(new Scene(pane, 600.0, 400.0));
primaryStage.show();
}
private void handleMousePressed(MouseEvent event) {
event.consume();
origin = new Point2D(event.getX(), event.getY());
ellipse = new Ellipse(event.getX(), event.getY(), 0.0, 0.0);
pane.getChildren().add(ellipse);
}
private void handleMouseDragged(MouseEvent event) {
event.consume();
ellipse.setCenterX((origin.getX() + event.getX()) / 2.0);
ellipse.setCenterY((origin.getY() + event.getY()) / 2.0);
ellipse.setRadiusX(Math.abs(event.getX() - origin.getX()) / 2.0);
ellipse.setRadiusY(Math.abs(event.getY() - origin.getY()) / 2.0);
}
private void handleMouseReleased(MouseEvent event) {
event.consume();
ellipse = null;
origin = null;
}
}
So I presume what you mean is that when you drag the ellipse, the edge position changes but you want the edge position to stay the same and the center to move.
ellipsePane.setOnMouseDragged(e -> {
if (ellipse != null) {
double x0 = ellipse.getCenterX() - ellipse.getRadiusX();
double y0 = ellipse.getCenterY() - ellipse.getRadiusY();
//the new radii
double rxp = e.getX() - x0;
double ryp = e.getY() - y0;
//the new center positions. to keep the origin the same.
double cx = x0 + rxp;
double cy = y0 + ryp;
ellipse.setRadiusX(rxp);
ellipse.setRadiusY(ryp);
ellipse.setCenterX(cx);
ellipse.setCenterY(cy);
}
});
How would I go about adding a constraint to how much a line can be dragged? I have a stick man and you can drag all his arms and legs, head and back about but I want them to stay the same length as they started off, so you can't stretch them longer or shorter than they should be, just move them up and down, side to side, in a circle etc. I guess i have to do something with the start/end x and y but im not sure how to set a set constraint to it and also still have it be draggable and stay the same length
private Line connectLines(Line line, Circle startNode, Circle endNode) {
line.startXProperty().bind(startNode.centerXProperty().add(startNode.translateXProperty()));
line.startYProperty().bind(startNode.centerYProperty().add(startNode.translateYProperty()));
line.endXProperty().bind(endNode.centerXProperty().add(endNode.translateXProperty()));
line.endYProperty().bind(endNode.centerYProperty().add(endNode.translateYProperty()));
return line;
}
//mouse pressed event
EventHandler<MouseEvent> mousePressed = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
System.out.println("pressed");
sceneX = e.getSceneX();
sceneY = e.getSceneY();
translateCircleX = ((Circle)(e.getSource())).getTranslateX();
translateCircleY = ((Circle)(e.getSource())).getTranslateY();
}
};
//mouse dragged event
EventHandler<MouseEvent> mouseDragged = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
System.out.println("dragged");
double offsetX = e.getSceneX() - sceneX;
double offsetY = e.getSceneY() - sceneY;
double newTranslateCircleX = translateCircleX + offsetX;
double newTranslateCircleY = translateCircleY + offsetY;
((Circle)(e.getSource())).setTranslateX(newTranslateCircleX);
((Circle)(e.getSource())).setTranslateY(newTranslateCircleY);
}
};
Here is an example. This example does not use Circle.setTranslate#. It uses Circle.setCenter#. It also uses Math.hypot to keep track of the Line length. If the line length becomes greater than or equal to 100, the change in the shape movements is subtracted.
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class TableViewDemo2 extends Application
{
double sceneX, sceneY;
Circle circle = new Circle(15, Color.RED);
Circle circle2 = new Circle(15, Color.BLUE);
Line line = new Line();
private Line connectLines(Line line, Circle startNode, Circle endNode)
{
line.startXProperty().bind(startNode.centerXProperty());
line.startYProperty().bind(startNode.centerYProperty());
line.endXProperty().bind(endNode.centerXProperty());
line.endYProperty().bind(endNode.centerYProperty());
return line;
}
//mouse pressed event
EventHandler<MouseEvent> mousePressed = new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent e)
{
System.out.println("pressed");
sceneX = e.getSceneX();
sceneY = e.getSceneY();
Circle tempCircle = ((Circle) e.getSource());
tempCircle.toFront();
}
};
//mouse dragged event
EventHandler<MouseEvent> mouseDragged = new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent e)
{
System.out.println(Math.hypot(line.getBoundsInLocal().getWidth(), line.getBoundsInLocal().getHeight()));
System.out.println("dragged");
double offSetX = e.getSceneX() - sceneX;
double offSetY = e.getSceneY() - sceneY;
Circle tempCircle = ((Circle) (e.getSource()));
tempCircle.setCenterX(tempCircle.getCenterX() + offSetX);
tempCircle.setCenterY(tempCircle.getCenterY() + offSetY);
if (Math.hypot(line.getBoundsInLocal().getWidth(), line.getBoundsInLocal().getHeight()) >= 100) {
tempCircle.setCenterX(tempCircle.getCenterX() - offSetX);
tempCircle.setCenterY(tempCircle.getCenterY() - offSetY);
}
sceneX = e.getSceneX();
sceneY = e.getSceneY();
}
};
#Override
public void start(Stage stage)
{
circle.setOnMouseDragged(mouseDragged);
circle2.setOnMouseDragged(mouseDragged);
Line returnLine = connectLines(line, circle, circle2);
StackPane root = new StackPane(new Pane(circle, circle2, returnLine));
stage.setTitle("TableView (o7planning.org)");
Scene scene = new Scene(root, 450, 300);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
I was confused by one of the exercises of my book. It requires me to create a circle on the position of my mouse arrow every time when I click Mouse Left button, and then delete this node if my mouse is just in this circle and I click the right button. Adding circle into pane is so easy, so I could finish it quickly but remove it quite hard, so I was trapped in this part, could someone add some codes in order to delete the circle?
package com.company;
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import java.util.ArrayList;
public class AddOrDeletePoint extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
double radius = 5;
pane.setOnMouseClicked(e -> {
double X = e.getSceneX();
double Y = e.getSceneY();
Circle circle = new Circle(X, Y, radius);
circle.setFill(Color.WHITE);
circle.setStroke(Color.BLACK);
if (e.getButton() == MouseButton.PRIMARY) {
pane.getChildren().add(circle);
} else if (e.getButton() == MouseButton.SECONDARY) {
pane.getChildren().remove(circle);//this is the remove part, but it does not work!
}
});
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
So just like this, how to remove the circle from the pane if my mouse stays on that circle and then click the right button?
You always create a circle. In case the secondary button is clicked, this circle was never added to the scene and nothing is removed from the child list of pane. You need to remove a existing circle.
Register the "Remove circle" listener to the circles you create:
pane.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
double X = e.getX(); // remove pane's coordinate system here
double Y = e.getY(); // remove pane's coordinate system here
final Circle circle = new Circle(X, Y, radius);
circle.setFill(Color.WHITE);
circle.setStroke(Color.BLACK);
circle.setOnMouseClicked(evt -> {
if (evt.getButton() == MouseButton.SECONDARY) {
evt.consume();
pane.getChildren().remove(circle);
}
});
pane.getChildren().add(circle);
}
});
Alternatively you could use the pick result:
pane.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
double X = e.getX(); // remove pane's coordinate system here
double Y = e.getY(); // remove pane's coordinate system here
Circle circle = new Circle(X, Y, radius);
circle.setFill(Color.WHITE);
circle.setStroke(Color.BLACK);
pane.getChildren().add(circle);
} else if (e.getButton() == MouseButton.SECONDARY) {
// check if cicle was clicked and remove it if this is the case
Node picked = e.getPickResult().getIntersectedNode();
if (picked instanceof Circle) {
pane.getChildren().remove(picked);
}
}
});
I am trying to create a very simple, easy-to-use program that actively reads and displays the position of the mouse. I have seen many tutorials that create programs that read the position of the mouse only when it is inside the window of the GUI application, or after hitting a button, but I want one that displays the position of the mouse in all areas of the screen. This is what I have:
import java.awt.MouseInfo;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class MouseCoordinates extends Application{
public static void main(String[] args) {
launch();
}
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("Mouse Reader");
Label x = new Label();
Label y = new Label();
StackPane layout = new StackPane();
layout.getChildren().addAll(x, y);
Scene scene = new Scene(layout, 600, 500);
primaryStage.setScene(scene);
primaryStage.show ();
double mouseX = 1.0;
double mouseY = 1.0;
while(true){
mouseX = MouseInfo.getPointerInfo().getLocation().getX();
mouseY = MouseInfo.getPointerInfo().getLocation().getY();
x.setText("" + mouseX);
y.setText("" + mouseY);
}
}
}
I understand that this while-loop is the cause of the window crashing, but I can not figure out a way around it. Can anyone explain why I can not use a while-loop for JavaFX, as well as a way to solve this?
Your start() method don't have any change to exit the loop and therefore to return as you defined an infinite loop : while(true){...} without return statement.
Why not use a Timeline ?
Timeline timeLine = new Timeline(new KeyFrame(Duration.seconds(1), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
mouseX = MouseInfo.getPointerInfo().getLocation().getX();
mouseY = MouseInfo.getPointerInfo().getLocation().getY();
x.setText("" + mouseX);
y.setText("" + mouseY);
}
}));
timeLine.setCycleCount(Timeline.INDEFINITE);
timeLine.play();
or with a lambda :
Timeline timeLine = new Timeline(new KeyFrame(Duration.seconds(1),
e -> {
mouseX = MouseInfo.getPointerInfo().getLocation().getX();
mouseY = MouseInfo.getPointerInfo().getLocation().getY();
x.setText("" + mouseX);
y.setText("" + mouseY);
}
));
timeLine.setCycleCount(Timeline.INDEFINITE);
timeLine.play();
Another way to address your requirement could be using addEventFilter( MouseEvent.MOUSE_MOVED) on the StackPane object :
layout.addEventFilter(MouseEvent.MOUSE_MOVED, e -> {
x.setText("" + e.getScreenX());
y.setText("" + e.getScreenY());
});
The MouseEvent class provides both X and Y absolute position on the device and
on the source component :
getScreenX()
Returns absolute horizontal position of the event.
getScreenY()
Returns the absolute vertical y position of the event
getX();
Horizontal position of the event relative to the origin of the
MouseEvent's source.
getY();
Vertical position of the event relative to the origin of the
MouseEvent's source.