How to create dynamically Resizable shapes in javafx? - javafx

I have three problems:
I want to create resizable shapes with box bounding...
I also want to know how to get child seleted in a Pane.
I'm creating multiple shapes on a pane. I want to change some property of that shape say Fill.. How do i do it??
Thanx

Next example will answer your questions:
for (1) it uses binding, connecting pane size with rectangle size
for (2) it adds setOnMouseClick for each rectangle which stores clicked one in the lastOne field.
for (3) see code of setOnMouseClick() handler
public class RectangleGrid extends Application {
private Rectangle lastOne;
public void start(Stage stage) throws Exception {
Pane root = new Pane();
int grid_x = 7; //number of rows
int grid_y = 7; //number of columns
// this binding will find out which parameter is smaller: height or width
NumberBinding rectsAreaSize = Bindings.min(root.heightProperty(), root.widthProperty());
for (int x = 0; x < grid_x; x++) {
for (int y = 0; y < grid_y; y++) {
Rectangle rectangle = new Rectangle();
rectangle.setStroke(Color.WHITE);
rectangle.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
if (lastOne != null) {
lastOne.setFill(Color.BLACK);
}
// remembering clicks
lastOne = (Rectangle) t.getSource();
// updating fill
lastOne.setFill(Color.RED);
}
});
// here we position rects (this depends on pane size as well)
rectangle.xProperty().bind(rectsAreaSize.multiply(x).divide(grid_x));
rectangle.yProperty().bind(rectsAreaSize.multiply(y).divide(grid_y));
// here we bind rectangle size to pane size
rectangle.heightProperty().bind(rectsAreaSize.divide(grid_x));
rectangle.widthProperty().bind(rectangle.heightProperty());
root.getChildren().add(rectangle);
}
}
stage.setScene(new Scene(root, 500, 500));
stage.show();
}
public static void main(String[] args) { launch(); }
}

Related

JavaFX button unselect

I'm trying to make a sprite editor using JavaFX using buttons as the pixels. I am able to change the colour of each button on press, but I'm trying to get it so if I click and drag I can paint multiple pixels.
The problem I'm finding is that after clicking and holding onto a button, I am unable to select the new button when I move the mouse over new button to select that one too. If I click and drag re-entering that button I can get the "Paint Dragged Pixel" debug message, but not if I enter a new pixel with the mouse down, which is what I want. Also I can get the pixel button to print "Entered Pixel" when the mouse enters any button, but not for when I click and drag to a new pixel.
I think the problem is that when I click on one pixel I am locked in, and unable to select a new pixel by hovering over a new one. Is there a way to unbind this selection, or is the problem a different one.
Main Application:
public class Main extends Application {
boolean mousePressed = false;
public boolean isMousePressed() {
return mousePressed;
}
#Override
public void start(Stage primaryStage) throws Exception{
BorderPane borderPane = new BorderPane();
primaryStage.setTitle("SpriteSheet");
Group root = new Group();
Scene scene = new Scene(borderPane, 500,200);
scene.setFill(Color.BLACK);
primaryStage.setScene(scene);
GridPane gridPane = new GridPane();
borderPane.setCenter(root);
for(int x = 0; x < 10; x++)
{
for(int y = 0; y < 10; y++) {
PixelButton button = new PixelButton();
button.setParentMain(this);
button.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
mousePressed = true;
System.out.println("mouseDown");
}
});
button.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
mousePressed = false;
System.out.println("mouseUp");
}
});
gridPane.add(button, x, y);
}
}
root.getChildren().add(gridPane);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The class for the PixelButton.
public class PixelButton extends Button {
Main parentMain;
public void setParentMain(Main parent) {
parentMain = parent;
}
public PixelButton() {
this.setMinSize(10, 10);
this.setPrefSize(10, 10);
this.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
}
});
this.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println("Entered Pixel");
if(parentMain.isMousePressed()){
System.out.println("Paint Dragged Pixel");
}
}
});
}
}
Thank you in advance.
Ok so I have been thinking about this one for a bit and have finally come up with a solution I simplified my solution a bit and used rectangles instead of buttons but you can transfer most of the code to the buttons as well so to start with this is not the exact functionality you were looking for but as close as I could get basically I fire an event on mouse press that releases the mouse click and as long as that event is not coming from the rectangle then dont flip the painting boolean paint and so you basically click to enter a "Paint Mode" and click again to get out of coloring tiles
public class Main extends Application {
private boolean mousePressed;
#Override
public void start(Stage primaryStage) throws Exception{
BorderPane borderPane = new BorderPane();
primaryStage.setTitle("SpriteSheet");
Group root = new Group();
Scene scene = new Scene(borderPane, 500,200);
// scene.setFill(Color.BLACK);
primaryStage.setScene(scene);
GridPane gridPane = new GridPane();
borderPane.setCenter(root);
for(int x = 0; x < 10; x++) {
for(int y = 0; y < 10; y++) {
Rectangle rectangle = new Rectangle(10, 10);
rectangle.setOnMousePressed(event -> {
mousePressed = true;
System.out.println("mouseDown");
rectangle.fireEvent(new MouseEvent(MouseEvent.MOUSE_RELEASED,
rectangle.getLayoutX(), rectangle.getLayoutY(), rectangle.getLayoutX(), rectangle.getLayoutY(),
MouseButton.PRIMARY, 1,
false, false, false, false,
false, false, false, false,
false, false, null));
});
rectangle.setOnMouseReleased(event -> {
System.out.println(event.getSource());
if(!event.getSource().toString().equals("Rectangle[x=0.0, y=0.0, width=10.0, height=10.0, fill=0x000000ff]")) {
mousePressed = false;
System.out.println("mouseUp");
}
});
rectangle.setOnMouseMoved(event -> {
if(mousePressed) {
rectangle.setFill(Color.BLUE);
}
});
gridPane.add(rectangle, x, y);
}
}
root.getChildren().add(gridPane);
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}

Fitting rotated ImageView into Application Window / Scene

In JavaFX I am trying to show an rotated ImageView in an Application Window.
Therefore I have put it into a stackPane to have it always centered and I have bound the widths/heights of the ImageView and the stackPane to the scene's width/height to view it just as large as possible.
This works fine as soon as the Image is not rotated.
As soon as I rotate the Image by 90° using stackPane.setRotate(90) (and exchange binding for width/height) then the stackPane is no longer bound to the upper left corner of the Application Window (or scene).
What can I do to place the rotated image correctly?
In the example code [any key] will toggle the rotation 90°/0° so the location problem of the rotated image becomes visible:
public class RotationTest extends Application {
boolean rotated = false;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Rotation test");
Group root = new Group();
Scene scene = new Scene(root, 1024,768);
//a stackPane is used to center the image
StackPane stackPane = new StackPane();
stackPane.setStyle("-fx-background-color: black;");
stackPane.prefHeightProperty().bind(scene.heightProperty());
stackPane.prefWidthProperty().bind(scene.widthProperty());
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
//toggle rotate 90° / no rotation
rotated = !rotated;
stackPane.prefHeightProperty().unbind();
stackPane.prefWidthProperty().unbind();
if (rotated){
stackPane.setRotate(90);
//rotation: exchange width and height for binding to scene
stackPane.prefWidthProperty().bind(scene.heightProperty());
stackPane.prefHeightProperty().bind(scene.widthProperty());
}else{
stackPane.setRotate(0);
//no rotation: height is height and width is width
stackPane.prefHeightProperty().bind(scene.heightProperty());
stackPane.prefWidthProperty().bind(scene.widthProperty());
}
}
});
final ImageView imageView = new ImageView("file:D:/test.jpg");
imageView.setPreserveRatio(true);
imageView.fitWidthProperty().bind(stackPane.prefWidthProperty());
imageView.fitHeightProperty().bind(stackPane.prefHeightProperty());
stackPane.getChildren().add(imageView);
root.getChildren().add(stackPane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Results:
Without rotation the stackPane (black) fits the window perfectly and the image has the correct size even if the window is resized with the mouse.
After pressing [any key] the stackPane is rotated. The stackPane (black) seems to have the correct width/height and also the image seems to be correctly rotated. But the stackPane is no longer in the upper left corner??? It moves around when the window is resized with the mouse???
Why not simply leave the Group and the preferred sizes out of the equation?
The root is automatically resized to fit the scene and you can use it's width/height properties to bind the fitWidth and fitHeight properties:
private static void setRotated(boolean rotated, ImageView targetNode, Pane parent) {
double angle;
if (rotated) {
angle = 90;
targetNode.fitWidthProperty().bind(parent.heightProperty());
targetNode.fitHeightProperty().bind(parent.widthProperty());
} else {
angle = 0;
targetNode.fitWidthProperty().bind(parent.widthProperty());
targetNode.fitHeightProperty().bind(parent.heightProperty());
}
targetNode.setRotate(angle);
}
#Override
public void start(Stage primaryStage) {
Image image = new Image("file:D:/test.jpg");
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
StackPane root = new StackPane(imageView);
root.setStyle("-fx-background-color: black;");
// initialize unrotated
setRotated(false, imageView, root);
Scene scene = new Scene(root, 1024, 768);
scene.setOnKeyPressed(evt -> {
// toggle between 0° and 90° rotation
setRotated(imageView.getRotate() == 0, imageView, root);
});
primaryStage.setScene(scene);
primaryStage.show();
}
Note that this may not result in correct layout, if placed in some other layout, since the size constraints may be calculated wrong.
You could implement your own region though to fix this:
public class CenteredImage extends Region {
private final BooleanProperty rotated = new SimpleBooleanProperty();
private final ImageView imageView = new ImageView();
public CenteredImage() {
// make sure layout gets invalidated when the image changes
InvalidationListener listener = o -> requestLayout();
imageProperty().addListener(listener);
rotated.addListener((o, oldValue, newValue) -> {
imageView.setRotate(newValue ? 90 : 0);
requestLayout();
});
getChildren().add(imageView);
imageView.setPreserveRatio(true);
}
public final BooleanProperty rotatedProperty() {
return rotated;
}
public final void setRotated(boolean value) {
this.rotated.set(value);
}
public boolean isRotated() {
return rotated.get();
}
public final void setImage(Image value) {
imageView.setImage(value);
}
public final Image getImage() {
return imageView.getImage();
}
public final ObjectProperty<Image> imageProperty() {
return imageView.imageProperty();
}
#Override
protected double computeMinWidth(double height) {
return 0;
}
#Override
protected double computeMinHeight(double width) {
return 0;
}
#Override
protected double computePrefWidth(double height) {
Image image = getImage();
Insets insets = getInsets();
double add = 0;
if (image != null && height > 0) {
height -= insets.getBottom() + insets.getTop();
add = isRotated()
? height / image.getWidth() * image.getHeight()
: height / image.getHeight() * image.getWidth();
}
return insets.getLeft() + insets.getRight() + add;
}
#Override
protected double computePrefHeight(double width) {
Image image = getImage();
Insets insets = getInsets();
double add = 0;
if (image != null && width > 0) {
width -= insets.getLeft() + insets.getRight();
add = isRotated()
? width / image.getHeight() * image.getWidth()
: width / image.getWidth() * image.getHeight();
}
return insets.getTop() + insets.getBottom() + add;
}
#Override
protected double computeMaxWidth(double height) {
return Double.MAX_VALUE;
}
#Override
protected double computeMaxHeight(double width) {
return Double.MAX_VALUE;
}
#Override
protected void layoutChildren() {
Insets insets = getInsets();
double left = insets.getLeft();
double top = insets.getTop();
double availableWidth = getWidth() - left - insets.getRight();
double availableHeight = getHeight() - top - insets.getBottom();
// set fit sizes
if (isRotated()) {
imageView.setFitWidth(availableHeight);
imageView.setFitHeight(availableWidth);
} else {
imageView.setFitWidth(availableWidth);
imageView.setFitHeight(availableHeight);
}
// place image
layoutInArea(imageView, left, top, availableWidth, availableHeight, 0, null, false,
false, HPos.CENTER, VPos.CENTER);
}
}
#Override
public void start(Stage primaryStage) {
Image image = new Image("file:D:/test.jpg");
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
CenteredImage imageArea = new CenteredImage();
imageArea.setImage(image);
imageArea.setStyle("-fx-background-color: black;");
imageArea.setPrefWidth(300);
SplitPane splitPane = new SplitPane(new Region(), imageArea);
SplitPane.setResizableWithParent(imageArea, true);
Scene scene = new Scene(splitPane, 1024, 768);
scene.setOnKeyPressed(evt -> {
// toggle between 0° and 90° rotation
imageArea.setRotated(!imageArea.isRotated());
});
primaryStage.setScene(scene);
primaryStage.show();
}
I found a solution :-) Fabian's approach inspired me (thank you!!) And my old friend Pit helped me with debugging (also thank you!!)
It seems that the layout location algorithm of JavaFX has a problem when resize() is applied to rotated Panes (or even Nodes - I have not tried):
Following Fabian's idea I debugged into the layoutChildren() method of class Pane. I found that the relocation after setRotate() is correct and keeps the center of the child pane as expected. But as soon as resize() is called (which is done because of fitting the rotated child pane again into its father and additionally always when the window is resized by the user) the origin calculation goes wrong:
The picture above depicts a sequence of setRotate(90), resize() and relocate() in green and the same for setRotate(270) in blue. A little blue/green circle depicts the corresponding origin together with its coordinates in the 1024x786 example.
Analysis
It seems that for calculation the position of the Pane resize() does not use the height and width from BoundsInParent-Property (see JavaFX-Docu of Node) but from getWidth() and getHeight() which seem to reflect BoundsInLocal. As a consequence, for rotations of 90° or 270° height and width seem to be interchanged. Therefore the error in the calculation for the new origin is just the half of the difference between width and height (delta=(width-height)/2) when resize() tries to center the child pane again after the resizing.
Solution
A relocation(delta,-delta) needs to be applied after resizing for Panes with rotation=90 or 270 degrees.
The structure of my implementation follows Fabian's basic idea: I have build a layouter RotatablePaneLayouter:Region that just overwrites the layoutChildren() method. In its constructor it gets a Pane (in my example a StackPane) which can contain any number of children (in my example an ImageView) and that can be rotated.
LayoutChildren() then just executes resize() and relocate() for the child pane to fit it completely into the RotateablePaneLayouter respecting the orientation of the child pane.
The Layouter Helper (RotateablePaneLayouter:Region)
public class RotatablePaneLayouter extends Region {
private Pane child;
public RotatablePaneLayouter(Pane child) {
getChildren().add(child);
this.child = child;
// make sure layout gets invalidated when the child orientation changes
child.rotateProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
requestLayout();
}
});
}
#Override
protected void layoutChildren() {
// set fit sizes:
//resize child to fit into RotatablePane and correct movement caused by resizing if necessary
if ((child.getRotate() == 90)||(child.getRotate() == 270)) {
//vertical
child.resize( getHeight(), getWidth() ); //exchange width and height
// and relocate to correct movement caused by resizing
double delta = (getWidth() - getHeight()) / 2;
child.relocate(delta,-delta);
} else {
//horizontal
child.resize( getWidth(), getHeight() ); //keep width and height
//with 0° or 180° resize does no movement to be corrected
child.relocate(0,0);
}
}
}
To use it: Place the Pane to be rotated into the Layouter first instead of placing the Pane directly.
Here the code for the example's main program. You can use the space bar to rotate the child pane by 90, 180, 270 and again 0 degrees. You can also resize the window with the mouse. The layouter always manages to place the rotated pane correctly.
Expample for using the Layouter
public class RotationTest extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
//image in a StackPane to be rotated
final ImageView imageView = new ImageView("file:D:/Test_org.jpg");
imageView.setPreserveRatio(true);
StackPane stackPane = new StackPane(imageView); //a stackPane is used to center the image
stackPane.setStyle("-fx-background-color: black;");
imageView.fitWidthProperty().bind(stackPane.widthProperty());
imageView.fitHeightProperty().bind(stackPane.heightProperty());
//container for layouting rotated Panes
RotatablePaneLayouter root = new RotatablePaneLayouter(stackPane);
root.setStyle("-fx-background-color: blue;");
Scene scene = new Scene(root, 1024,768);
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.SPACE) {
//rotate additionally 90°
stackPane.setRotate((stackPane.getRotate() + 90) % 360);
}
}
});
primaryStage.setTitle("Rotation test");
primaryStage.setScene(scene);
primaryStage.show();
}
}
For me this seems like a workaround of a javaFX bug in resize().

JavaFX Button EventHandler works one time(Card Shuffler)

I'm trying to make a program that will display a random set of 4 cards, then when I click the button again it will clear the old set and display a new random set.
Right now my program will display 4 random images of cards when I click the button; however, when I try to click it again nothing happens. I'm assuming it has something to do with the EventHandler no longer being registered to the button after I clear the root children. However, I don't know how to go about fixing this. Any help is greatly appreciated! I haven't been able to find an answer to this yet, and have only been learning JavaFX for about a week. Thank you.
The code I have so far:
public class CardShuffle extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
StackPane root = new StackPane();
Scanner input = new Scanner(System.in);
File cardsFolder = new File("C:\\Users\\timsp\\Pictures\\JPEG");
ArrayList<File> cardsFilePaths = new ArrayList<File> (Arrays.asList(cardsFolder.listFiles()));
Button deal = new Button("DEAL");
Pane hb = new HBox(10);
hb.setPadding(new Insets(5, 5, 5, 5));
root.getChildren().add(deal);
deal.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
root.getChildren().clear();
ArrayList<ImageView> cards = getRandomCards(cardsFilePaths);
for (int i = 0; i < 4; i++) {
cards.get(i).setFitWidth(150);
cards.get(i).setFitHeight(100);
hb.getChildren().add(cards.get(i));
}
root.getChildren().addAll(deal, hb);
}
});
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public ArrayList<ImageView> getRandomCards(ArrayList<File> cardsFilePaths) {
ArrayList<ImageView> cards = new ArrayList<ImageView>();
try {
for (int i = 0; i < 4; i++) {
Image card = new Image((new FileInputStream(cardsFilePaths.get((int) (Math.random() * 52)).getPath())));
ImageView temp = new ImageView();
temp.setImage(card);
cards.add(temp);
}
} catch (FileNotFoundException e) {
e.getMessage();
}
return cards;
}
}
Many problems here :
the first one, and the most important (because it hides your further error) is the root layout : you use a StackPane, the first thing you should do is to replace it by a VBox for example and rerun your program, it will be easier to see what really happens. (you will not have 4 cards, but 8, 12, 16 and so on).
the first one generates the second one. By doing this root.getChildren().addAll(deal, hb); you put the HBox layout above the button, and the click is first consumed by the HBox. Here is an example to see it more easily :
// Add the HBox as soon as the initialization
root.getChildren().add(deal);
hb.setOnMouseClicked(e -> System.out.println("HBox clicked"));
deal.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
root.getChildren().clear();
ArrayList<ImageView> cards = getRandomCards(cardsFilePaths);
for(int i = 0; i < 4; i++) {
cards.get(i).setFitWidth(150);
cards.get(i).setFitHeight(100);
hb.getChildren().add(cards.get(i));
}
hb.setStyle("-fx-background-color:CORNFLOWERBLUE;-fx-opacity:0.8;");
root.getChildren().addAll(deal, hb);
}
});
And the last one, you don't really want to remove all root's children, what you want is to replace your cards by another 4 ones. Thus it is not necessary to remove the button, only the HBox can be manipulated as shown by the following example :
// Add the HBox as soon as the initialization
root.getChildren().addAll(hb, deal);
deal.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
// root.getChildren().clear();
// Replace the previous line by the following :
hb.getChildren().clear();
ArrayList<ImageView> cards = getRandomCards(cardsFilePaths);
for(int i = 0; i < 4; i++) {
cards.get(i).setFitWidth(150);
cards.get(i).setFitHeight(100);
hb.getChildren().add(cards.get(i));
}
// The following is useless now.
// root.getChildren().addAll(hb, deal);
}
});

How to add an ImagePattern to a Rectangle while still maintaining the Rectangles background color

I have a chess board and I am trying to add pieces to the board. Every spot on the board is a Rectangle so I thought the best way to add pieces would be to add an ImagePattern to each Rectangle that gets a piece. The problem I encountered was when I added an ImagePattern to a Rectangle it would make the background of that Rectangle white despite what the color was before the ImagePattern was added. So my question is, is there a way for me to preserve the background color of a Rectangle after an ImagePattern is added?
For demo purposes my code only adds one piece to the board.
public class ChessBoard extends Application {
GridPane root = new GridPane();
final int size = 8;
public void start(Stage primaryStage) {
for (int row = 0; row < size; row++) {
for (int col = 0; col < size; col++) {
Rectangle square = new Rectangle();
Color color;
if ((row + col) % 2 == 0)
color = Color.WHITE;
else
color = Color.BLACK;
square.setFill(color);
root.add(square, col, row);
if(row == 4 && col == 3){
Image p = new Image("Peices/Black/0.png");
ImagePattern pat = new ImagePattern(p);
square.setFill(pat);
}
square.widthProperty().bind(root.widthProperty().divide(size));
square.heightProperty().bind(root.heightProperty().divide(size));
square.setOnMouseClicked(e->{
square.setFill(Color.BLUE);
});
}
}
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I think you are searching for blending which is done with the BlendMode. For example:
Image p = new Image("Peices/Black/0.png");
ImagePattern pat = new ImagePattern(p);
Rectangle r1 = new Rectangle();
r1.setX(50);
r1.setY(50);
r1.setWidth(50);
r1.setHeight(50);
r1.setFill(pat);
Rectangle r = new Rectangle();
r.setX(50);
r.setY(50);
r.setWidth(50);
r.setHeight(50);
r.setFill(Color.BLUE);
r.setBlendMode(BlendMode.ADD);
As far as I know there is no direct way to accomplish this.
Source: https://docs.oracle.com/javase/8/javafx/api/javafx/scene/effect/BlendMode.html
No, you cannot use more than a single fill with a Rectangle. Theoretically you could use a Region with multiple backgrounds, but this is probably a bad idea. Most likely you'll want at least some of the following functionality for the pieces, which will not work, if you do not make pieces their own nodes:
Dragging a piece from one field to another
animating moves
I recommend using a StackPane and put the Board in the background and put a Pane on top of it to place the pieces. Simply use ImageViews for the pieces.
Example:
#Override
public void start(Stage primaryStage) {
GridPane board = new GridPane();
Region[][] fields = new Region[8][8];
for (int i = 0; i < fields.length; i++) {
Region[] flds = fields[i];
for (int j = 0; j < flds.length; j++) {
Region field = new Region();
flds[j] = field;
field.setBackground(new Background(new BackgroundFill((i + j) % 2 == 0 ? Color.WHITE : Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
}
board.addRow(i, flds);
}
// use 1/8 of the size of the Grid for each field
RowConstraints rowConstraints = new RowConstraints();
rowConstraints.setPercentHeight(100d / 8);
ColumnConstraints columnConstraints = new ColumnConstraints();
columnConstraints.setPercentWidth(100d / 8);
for (int i = 0; i < 8; i++) {
board.getColumnConstraints().add(columnConstraints);
board.getRowConstraints().add(rowConstraints);
}
Pane piecePane = new Pane();
StackPane root = new StackPane(board, piecePane);
NumberBinding boardSize = Bindings.min(root.widthProperty(), root.heightProperty());
ImageView queen = new ImageView("https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Chess_qdt45.svg/480px-Chess_qdt45.svg.png");
DropShadow shadow = new DropShadow(BlurType.GAUSSIAN, Color.WHITE, 2, 1, 0, 0);
// drop shadow for black piece on black field
queen.setEffect(shadow);
// trigger move to topleft field on mouse click
queen.setOnMouseClicked(evt -> {
Node source = (Node) evt.getSource();
TranslateTransition animation = new TranslateTransition(Duration.seconds(0.5), source);
Region targetRegion = fields[0][0];
final PositionChangeListener listener = (PositionChangeListener) source.getUserData();
listener.setField(null);
animation.setByX(targetRegion.getLayoutX() - source.getLayoutX());
animation.setByY(targetRegion.getLayoutY() - source.getLayoutY());
animation.setOnFinished(e -> {
source.setTranslateX(0);
source.setTranslateY(0);
listener.setField(targetRegion);
});
animation.play();
});
PositionChangeListener changeListener = new PositionChangeListener(queen);
queen.setUserData(changeListener);
changeListener.setField(fields[4][3]);
// board size should be as large as possible but at most the min of the parent sizes
board.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE);
board.maxWidthProperty().bind(boardSize);
board.maxHeightProperty().bind(boardSize);
// same size for piecePane
piecePane.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE);
piecePane.maxWidthProperty().bind(boardSize);
piecePane.maxHeightProperty().bind(boardSize);
// add piece to piecePane
piecePane.getChildren().add(queen);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private static class PositionChangeListener implements ChangeListener<Bounds> {
private final ImageView piece;
public PositionChangeListener(ImageView piece) {
this.piece = piece;
}
private Region currentField;
public void setField(Region newRegion) {
// register/unregister listeners to bounds changes of associated field
if (currentField != null) {
currentField.boundsInParentProperty().removeListener(this);
}
currentField = newRegion;
if (newRegion != null) {
newRegion.boundsInParentProperty().addListener(this);
changed(null, null, newRegion.getBoundsInParent());
}
}
#Override
public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) {
// align piece with field
piece.setLayoutX(newValue.getMinX());
piece.setLayoutY(newValue.getMinY());
piece.setFitHeight(newValue.getHeight());
piece.setFitWidth(newValue.getWidth());
}
}

JavaFx Determining whether a mouse is clicking on the background or a Circle

I am creating a game where circles fall from the top of the screen to the bottom. When the circle is clicked its suppose to re-spawn in a random position on top of the screen and with a random color. I am pretty sure my problem has to do with my line to determine if the mouse click was on one of the circles or not is working correctly. So my questions are how would I determine if a mouse click happened on one of the circles or on the background screen? and What is wrong with the following line? (Because I am almost certain that my problem is from that line)
if((shape.get(i).getLayoutX() == e.getX())&&(shape.get(i).getLayoutY() == e.getY())){
My entire code is here:
public class ShapesWindow extends Application{
final int WIDTH = 640;
final int HEIGHT = WIDTH / 12 * 9;
Random r = new Random();
Circle circle;
double yCord;
long startNanoTime;
Group root = new Group();
Scene scene = new Scene(root, WIDTH, HEIGHT);
Canvas can = new Canvas(WIDTH,HEIGHT);
GraphicsContext gc = can.getGraphicsContext2D();
ArrayList<Shape> shape = new ArrayList<>();
#Override
public void start(Stage theStage) throws Exception {
theStage.setTitle("Click the bubbles!");
theStage.setScene(scene);
root.getChildren().add(can);
gc.setFill(Color.LIGHTBLUE);
gc.fillRect(0,0,WIDTH,HEIGHT);
/* This adds 10 circles to my Group */
for(int i = 0; i < 10; i++){
gc.setFill(Color.LIGHTBLUE);
gc.fillRect(0,0,WIDTH,HEIGHT);
circle = new Circle(15,randomColor());
root.getChildren().add(circle);
circle.setLayoutX(r.nextInt(WIDTH+15));
circle.setLayoutY(0);
shape.add(circle);
}
/* This my attempt at trying to handle the Mouse Events for each thing */
for(int i = 0; i < 10; i++){
shape.get(i).setOnMouseClicked(
new EventHandler<MouseEvent>(){
public void handle(MouseEvent e){
shapeClicked(e);
}
});
}
startNanoTime = System.nanoTime();
new AnimationTimer(){
public void handle(long currentNanoTime){
double t = (currentNanoTime - startNanoTime) / 1000000000.0;
yCord = t*20;
for(int i = 0; i < 10; i++){
/* This if statment allows nodes to wrap around from bottom to top */
if(yCord >=HEIGHT){
shape.get(i).setLayoutX(r.nextInt(WIDTH+15));
shape.get(i).setLayoutY(0);
shape.get(i).setFill(randomColor());
resetNan();
}
shape.get(i).setLayoutY(yCord);
}
}
}.start();
theStage.show();
}
/*
* This Function is suppose the change the color and position of the circle that was clicked
*/
public void shapeClicked(MouseEvent e){
for(int i = 0; i < shape.size();i++){
if((shape.get(i).getLayoutX() == e.getX())&&(shape.get(i).getLayoutY() == e.getY())){
shape.get(i).setLayoutX(r.nextInt(WIDTH+15));
shape.get(i).setLayoutY(0);
shape.get(i).setFill(randomColor());
}
}
/*
* This allows the value of startNanoTime to be indrectly change it can not be changed diretly
* inside of handle() inside of the Animation class
*/
public void resetNan(){
startNanoTime = System.nanoTime();
}
public Color randomColor(){
double R = r.nextDouble();
double G = r.nextDouble();
double B = r.nextDouble();
double opacity = .6;
Color color = new Color(R, G, B, opacity);
return color.brighter();
}
public static void main(String[] args){
launch(args);
}
}
Why not just
for(int i = 0; i < 10; i++){
Shape s = shape.get(i);
s.setOnMouseClicked(
new EventHandler<MouseEvent>(){
public void handle(MouseEvent e){
s.setLayoutX(r.nextInt(WIDTH+15));
s.setLayoutY(0);
s.setFill(randomColor());
}
});
}
I know long time passed by, but if anyone else need to check if a mouse click event was on a Circle or any other shape it is better to use the built in .contains method. Thanks to Point2D from JavaFx geometry class you can check if a click (x,y coordinate) is on a shape or not, you don't have to worry about the click position: in the center or border.
for (Circle circle:listOfCircles) {
Point2D point2D = new Point2D(event.getX(),event.getY());
if (circle.contains(point2D)){
System.out.println("circle clicked");
}
}

Resources