I am making a javafx application that creates a bounded rectangle around the circles the user creates when primary mouse clicking. The user can also remove a circle with the secondary mouse button and the bounding rectangle should react accordingly and use the remaining circles as its upper and lower limits. The problem with my program is that it does not work when I try to remove more than one circle in a row (it only removes and resizes for the last circle)
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Collections;
public class BoundRectangle extends Application {
double minX, maxX, minY, maxY;
Pane pane = new Pane();
Rectangle rectangle;
ArrayList<Circle> allCircles = new ArrayList<>();
#Override
public void start(Stage primaryStage) {
VBox mainBox = new VBox();
Pane mainPane = new Pane(mainBox);
mainPane.setPadding(new Insets(10, 10, 10, 10));
pane.getChildren().addAll(mainPane);
mainBox.setLayoutX(10);
mainBox.setLayoutY(10);
pane.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
Circle circle = new Circle(e.getX(), e.getY(), 10);
allCircles.add(circle);
pane.getChildren().add(drawRectangle());
pane.getChildren().add(circle);
System.out.println("maxX " + maxX);
System.out.println("maxY " + maxY);
System.out.println("minX " + minX);
System.out.println("minY " + minY);
circle.setOnMouseClicked(evt -> {
if (evt.getButton() == MouseButton.SECONDARY) {
pane.getChildren().remove(circle);
allCircles.remove(circle);
pane.getChildren().add(drawRectangle());
}
});
circle.setStroke(Color.BURLYWOOD);
circle.setStrokeWidth(3);
circle.setFill(Color.TRANSPARENT);
}
});
Scene scene = new Scene(pane, 600, 600);
primaryStage.setScene(scene);
primaryStage.setTitle("click circles, make rectangle");
primaryStage.show();
}
public Rectangle drawRectangle() {
refresh();
getMinMax();
if (pane.getChildren().size() == 1)
{
Rectangle rect0 = new Rectangle(0, 0, 0, 0);
return rect0;
}
Rectangle boundingRect = new Rectangle();
boundingRect.setX(minX - 10 - 2);
boundingRect.setY(minY - 10 - 2);
boundingRect.setWidth(maxX - minX + 2.0 * 10 + 2);
boundingRect.setHeight(maxY - minY + 2.0 * 10 + 2);
boundingRect.setStroke(Color.BLACK);
boundingRect.setStrokeWidth(2);
boundingRect.setFill(Color.TRANSPARENT);
return boundingRect;
}
public void getMinMax() {
maxY = allCircles.get(0).getCenterY();
minY = allCircles.get(0).getCenterY();
maxX = allCircles.get(0).getCenterX();
minX = allCircles.get(0).getCenterX();
for (Circle c : allCircles) {
if (c.getCenterX() < minX)
minX = c.getCenterX();
if (c.getCenterX() > maxX)
maxX = c.getCenterX();
if (c.getCenterY() < minY)
minY = c.getCenterY();
if (c.getCenterY() > maxY)
maxY = c.getCenterY();
}
}
private void refresh() {
ObservableList<Node> list = pane.getChildren();
for (Node c : list) {
if (c instanceof Rectangle) {
pane.getChildren().remove(c);
break;
}
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
The last circle you add appears on top of the rectangle; however the rectangle appears on top of all the other circles. So only the last circle added will get the mouse clicks (clicks on other circles are targeted to the rectangle).
The quickest fix is to make the rectangle mouse transparent:
boundingRect.setMouseTransparent(true);
A better solution overall might be just to create one rectangle and update its x, y, width and height properties. That way you can just ensure the rectangle is added once and remains at the bottom of the stack.
Related
In my application, I'm drawing a lot of circles of different radii on a javafx canvas using the fillOval and strokeOval methods. I am trying to optimize this draw operation by drawing only those circles which will be visible(partially also) in my screen. So I want to know is there a method that can tell me that drawing this circle, text, line, image etc. will be visible in the screen.
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
Pane pane;
Canvas canvas;
double transX = 0.0, transY = 0.0;
public void draw() {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.clearRect(canvas.getLayoutX(), canvas.getLayoutY(), canvas.getWidth(), canvas.getHeight());
gc.setStroke(Color.FIREBRICK);
gc.setFill(Color.TRANSPARENT);
gc.setLineWidth(2.0);
for(int i = 1; i <= 10000; ++i) {
double r = 10 * i;
// some function to check the below drawing operations will be visible or not
// Pls. note centre of the circles need not be at the centre of the screen, if canvas is panned to right/left/top/bottom by mouse drag operation where translation values will be stored in transX and transY
gc.fillOval(pane.getWidth() / 2 - r + transX, pane.getHeight() / 2 - r + transY, 2 * r, 2 * r);
gc.strokeOval(pane.getWidth() / 2 - r + transX, pane.getHeight() / 2 - r + transY, 2 * r, 2 * r);
}
}
#Override
public void start(Stage primaryStage) {
pane = new Pane();
canvas = new Canvas();
canvas.layoutXProperty().bind(pane.layoutXProperty());
canvas.layoutYProperty().bind(pane.layoutYProperty());
canvas.widthProperty().bind(pane.widthProperty());
canvas.heightProperty().bind(pane.heightProperty());
pane.widthProperty().addListener((observableValue, number, t1) -> draw());
pane.heightProperty().addListener((observableValue, number, t1) -> draw());
pane.getChildren().addAll(canvas);
primaryStage.setScene(new Scene(pane, primaryStage.getWidth(), primaryStage.getHeight()));
primaryStage.setTitle("Canvas Demo");
primaryStage.setMaximized(true);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
I am working on a project that has to do with animation in javaFx, I wrote the GUI for it and it is a Stick Figure. I am trying to make the stick figure walk to the right side of the pane and then when it touches the right side turn and go back the other way all the way to the left wall of the pane. I have this code that is a moving ball that does exactly what I want for my stick figure to do but I cannot seem to modify that code into my stick figure program. any ideas on how to do this? both codes are below:
package animationdemo;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class MovingBallDemo_3 extends Application {
#Override
public void start(Stage primaryStage) {
BallPane ballPane = new BallPane(); // Create a ball pane
// Pause and resume animation
ballPane.setOnMousePressed(e -> ballPane.pause());
ballPane.setOnMouseReleased(e -> ballPane.play());
// Create a scene and place it in the stage
Scene scene = new Scene(ballPane, 250, 150);
primaryStage.setTitle("Bouncing Ball Control"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
public static void main(String[] args) {
launch(args);
}
}
class BallPane extends Pane {
public final double radius = 20;
private double x = 2 * radius, y = 3 * radius;
private double dx = 3; // Number of pixels to move each time
private Circle circle = new Circle(x, y, radius);
private Timeline animation;
public BallPane() {
circle.setFill(Color.RED); // Set ball color
getChildren().add(circle); // Place a ball into this pane
// Create the animation for 25 millisecond events
animation = new Timeline(new KeyFrame(Duration.millis(25), e -> moveBall()));
animation.setCycleCount(Timeline.INDEFINITE);
animation.play(); // Start animation
}
public void play() {
animation.play();
}
public void pause() {
animation.pause();
}
// Move the ball. When a wall is encountered, reverse direction
protected void moveBall() {
if (x <= radius || x >= getWidth() - radius) {
dx *= -1; // Change direction
}
// Adjust ball position
x += dx;
circle.setCenterX(x);
}
}
the above is the animation that I want the stick figure GUI to do. here is my attempt of merging my code in with the code above to make the stick figure work but nothing happens:
package Stickfigure;
import java.awt.Graphics;
import javafx.animation.KeyFrame;
import javafx.animation.PathTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Stickfigure extends Application {
#Override
public void start(Stage primaryStage) {
BallPane ballPane = new BallPane();
ballPane.setOnMousePressed(e -> ballPane.pause());
ballPane.setOnMouseReleased(e -> ballPane.play());
Circle circle = new Circle(100, 100, 0);//head
Circle circle1 = new Circle(120, 80, 50);//eye
circle1.setRadius(5);//radius of eye
circle.setRadius(50);//radius of head
circle.setStroke(Color.BLACK);//circle color
circle1.setStroke(Color.BLACK);//circle color
circle.setFill(null);//makes the head empty(no brain haha)
circle.setStrokeWidth(5);//sets the line thickness of circle (head)
Arc arc = new Arc();//mouth
arc.setCenterX(110.0f);//mouth position
arc.setCenterY(120.0f);//mouth position
arc.setRadiusX(35.0f);//mouth size
arc.setRadiusY(25.0f);//mouth size
arc.setStartAngle(1.0f);//angle of mouth
arc.setLength(5.0f);//length of mouth
arc.setType(ArcType.ROUND);
Line line1 = new Line(100, 250, 100, 150); //body of stick figure
Line line2 = new Line(); //left leg
Line line3 = new Line();//right leg
Line line4 = new Line();//right arm
Line line5 = new Line();//left arm
line2.setStartX(30.0f); //left leg starting position y
line2.setStartY(350.0f);//left leg starting position y
line2.setEndX(100.0f);//left leg end pos x
line2.setEndY(250.0f);//left leg end pos y
line3.setStartX(200.0f); //right leg start pos x
line3.setStartY(350.0f);// right leg start pos y
line3.setEndX(100.0f); //right leg end pos x
line3.setEndY(250.0f); //right leg end pos y
line4.setStartX(100.0f);//right arm start pos x
line4.setStartY(200.0f); //right arm start pos y
line4.setEndX(200.0f); //right arm end pos x
line4.setEndY(170.0f); //right arm end pos y
line5.setStartX(30.0f);//left arm arm statt pos x
line5.setStartY(250.0f); // left arm start pos y
line5.setEndX(100.0f);//left arm end pos x
line5.setEndY(200.0f);//left arm end pos y
line1.setStrokeWidth(5); //thickness of line
line1.setStroke(Color.BLACK);//color of line
line2.setStrokeWidth(5);//thickness of line
line2.setStroke(Color.BLACK);//color of line
line3.setStrokeWidth(5);//thickness of line
line3.setStroke(Color.BLACK);//color of line
line4.setStrokeWidth(5);//thickness of line
line4.setStroke(Color.BLACK);//color of line
line5.setStrokeWidth(5);//thickness of line
line5.setStroke(Color.BLACK);//color of line
// Create a pane to hold the circle
ballPane.getChildren().add(circle); //adds circle to picture
ballPane.getChildren().add(circle1);//adds circle to picture
ballPane.getChildren().add(line1);//adds line
ballPane.getChildren().add(line2);//adds line
ballPane.getChildren().add(line3);//adds line
ballPane.getChildren().add(line4);//adds line
ballPane.getChildren().add(line5);//adds line
ballPane.getChildren().add(arc);//adds arc
Scene scene = new Scene( ballPane, 400, 400);
primaryStage.setTitle("stick figure");//title
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
class BallPane extends Pane {
public final double radius = 20;
private double x = 2 * radius, y = 3 * radius;
private double dx = 3; // Number of pixels to move each time
private Timeline animation;
public BallPane() {
// Create the animation for 25 millisecond events
animation = new Timeline(new KeyFrame(Duration.millis(25), e -> moveBall()));
animation.setCycleCount(Timeline.INDEFINITE);
animation.play(); // Start animation
}
public void play() {
animation.play();
}
public void pause() {
animation.pause();
}
// Move the ball. When a wall is encountered, reverse direction
protected void moveBall() {
if (x <= radius || x >= getWidth() - radius) {
dx *= -1; // Change direction
}
// Adjust ball position
x += dx;
}
}
}
You are not doing any moving in the moveBall() method. You just change local values. Have a look at the Node class from which Pane is inheriting and the translateXProperty. You want to change that, not just your fields.
You even had the actual translation in your Ball class: circle.setCenterX(x);
protected void moveBall() {
if (x <= radius || x >= getWidth() - radius) {
dx *= -1; // Change direction
}
// Adjust ball position
x += dx;
//#######################################
setTranslateX(x); // Move the Pane!!! ###
//#######################################
}
I have JAVAFX application with zoom and scale as described here:
Scale at pivot point in an already scaled node
What I need to achieve is to place the image to right pane and keep the image on the left as depicted in the figure below.
Question
How can I embed this application into SPlitPane, where on left will be another panel.
SplitPane splitPane = new SplitPane();
splitPane.getItems().add(new Label("Left Panel"));
splitPane.getItems().add(group);
Scene scene = new Scene(splitPane, 1024, 768);
Unfortunately the code results in wrong coordinates,
For zooming inside a ScrollPane, you need to adjust the scroll positions. This is a bit more complex than just appending a transformation , which could be done, if no ScrollPane was used.
Basically you need to check the current scroll position before scaling the node and after scaling modify the scroll positions to prevent the pivot point from changing it's position because of the changed scale factor.
The following example lets you move a circle around "in" a grid and move around the grid when not clicking on a circle (pannable ScrollPane) in addition to allowing you to adjust the zoom when moving the mouse wheel while holding down the CTRL key.
import java.net.MalformedURLException;
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class PivotZoom extends Application {
public static Region createContent() {
double width = 1000;
double height = 1000;
Canvas canvas = new Canvas(width, height);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.LIGHTGREY);
gc.fillRect(0, 0, width, height);
gc.setStroke(Color.BLUE);
gc.beginPath();
for (int i = 50; i < width; i += 50) {
gc.moveTo(i, 0);
gc.lineTo(i, height);
}
for (int i = 50; i < height; i += 50) {
gc.moveTo(0, i);
gc.lineTo(width, i);
}
gc.stroke();
Pane content = new Pane(
new Circle(50, 50, 20),
new Circle(120, 90, 20, Color.RED),
new Circle(200, 70, 20, Color.GREEN)
);
StackPane result = new StackPane(canvas, content);
result.setAlignment(Pos.TOP_LEFT);
class DragData {
double startX;
double startY;
double startLayoutX;
double startLayoutY;
Node dragTarget;
}
DragData dragData = new DragData();
content.setOnMousePressed(evt -> {
if (evt.getTarget() != content) {
// initiate drag gesture, if a child of content receives the
// event to prevent ScrollPane from panning.
evt.consume();
evt.setDragDetect(true);
}
});
content.setOnDragDetected(evt -> {
Node n = (Node) evt.getTarget();
if (n != content) {
// set start paremeters
while (n.getParent() != content) {
n = n.getParent();
}
dragData.startX = evt.getX();
dragData.startY = evt.getY();
dragData.startLayoutX = n.getLayoutX();
dragData.startLayoutY = n.getLayoutY();
dragData.dragTarget = n;
n.startFullDrag();
evt.consume();
}
});
// stop dragging when mouse is released
content.setOnMouseReleased(evt -> dragData.dragTarget = null);
content.setOnMouseDragged(evt -> {
if (dragData.dragTarget != null) {
// move dragged node
dragData.dragTarget.setLayoutX(evt.getX() + dragData.startLayoutX - dragData.startX);
dragData.dragTarget.setLayoutY(evt.getY() + dragData.startLayoutY - dragData.startY);
Point2D p = new Point2D(evt.getX(), evt.getY());
evt.consume();
}
});
return result;
}
#Override
public void start(Stage primaryStage) throws MalformedURLException {
Region zoomTarget = createContent();
zoomTarget.setPrefSize(1000, 1000);
zoomTarget.setOnDragDetected(evt -> {
Node target = (Node) evt.getTarget();
while (target != zoomTarget && target != null) {
target = target.getParent();
}
if (target != null) {
target.startFullDrag();
}
});
Group group = new Group(zoomTarget);
// stackpane for centering the content, in case the ScrollPane viewport
// is larget than zoomTarget
StackPane content = new StackPane(group);
group.layoutBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
// keep it at least as large as the content
content.setMinWidth(newBounds.getWidth());
content.setMinHeight(newBounds.getHeight());
});
ScrollPane scrollPane = new ScrollPane(content);
scrollPane.setPannable(true);
scrollPane.viewportBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
// use vieport size, if not too small for zoomTarget
content.setPrefSize(newBounds.getWidth(), newBounds.getHeight());
});
content.setOnScroll(evt -> {
if (evt.isControlDown()) {
evt.consume();
final double zoomFactor = evt.getDeltaY() > 0 ? 1.2 : 1 / 1.2;
Bounds groupBounds = group.getLayoutBounds();
final Bounds viewportBounds = scrollPane.getViewportBounds();
// calculate pixel offsets from [0, 1] range
double valX = scrollPane.getHvalue() * (groupBounds.getWidth() - viewportBounds.getWidth());
double valY = scrollPane.getVvalue() * (groupBounds.getHeight() - viewportBounds.getHeight());
// convert content coordinates to zoomTarget coordinates
Point2D posInZoomTarget = zoomTarget.parentToLocal(group.parentToLocal(new Point2D(evt.getX(), evt.getY())));
// calculate adjustment of scroll position (pixels)
Point2D adjustment = zoomTarget.getLocalToParentTransform().deltaTransform(posInZoomTarget.multiply(zoomFactor - 1));
// do the resizing
zoomTarget.setScaleX(zoomFactor * zoomTarget.getScaleX());
zoomTarget.setScaleY(zoomFactor * zoomTarget.getScaleY());
// refresh ScrollPane scroll positions & content bounds
scrollPane.layout();
// convert back to [0, 1] range
// (too large/small values are automatically corrected by ScrollPane)
groupBounds = group.getLayoutBounds();
scrollPane.setHvalue((valX + adjustment.getX()) / (groupBounds.getWidth() - viewportBounds.getWidth()));
scrollPane.setVvalue((valY + adjustment.getY()) / (groupBounds.getHeight() - viewportBounds.getHeight()));
}
});
StackPane left = new StackPane(new Label("Left Menu"));
SplitPane root = new SplitPane(left, scrollPane);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Try adding what it is you wish to zoom and scroll to a group layout. Then add the group to a scrollpane. This will keep the node you are zooming on the left position of the scrollpane. Hope this solves your problem!
Node you are zooming inside group,
Group inside scrollpane,
Scrollpane on the rightside of the
Splitpane!!
I have a program where 2 circles are dragged in a pane. There is also a line connecting them and the distance displayed above it. My problem lies when I drag the circles at a slow pace with the mouse they move fine, but when I move it more quickly the circles stop.
here is where the circle drag is calculated
pane.setOnMouseDragged(e -> {
if (circle1.contains(e.getX(), e.getY())) {
pane.getChildren().clear();
circle1.setCenterX(e.getX());
circle1.setCenterY(e.getY());
pane.getChildren().addAll(getLine(circle1, circle2), circle1,
circle2, getText(circle1, circle2));
}
else if (circle2.contains(e.getX(), e.getY())) {
pane.getChildren().clear();
circle2.setCenterX(e.getX());
circle2.setCenterY(e.getY());
pane.getChildren().addAll(getLine(circle1, circle2), circle1,
circle2, getText(circle1, circle2));
}
});
I think what is happening is that when the mouse moves quickly, the distance moved between processing two consecutive events takes it outside the bounds of the circle, so the if condition becomes false. You probably need to register the mouse handlers on the circles themselves, instead of the pane. (As an aside, why clear and rebuild the pane, instead of just update the line?)
Here's an example using these techniques:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class DraggingCircles extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Circle circle1 = createDraggingCircle(50, 50, 25, Color.BLUE);
Circle circle2 = createDraggingCircle(350, 350, 25, Color.RED);
Line line = new Line();
line.startXProperty().bind(circle1.centerXProperty());
line.startYProperty().bind(circle1.centerYProperty());
line.endXProperty().bind(circle2.centerXProperty());
line.endYProperty().bind(circle2.centerYProperty());
pane.getChildren().addAll(circle1, circle2, line);
Scene scene = new Scene(pane, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private Circle createDraggingCircle(double x, double y, double radius, Color fill) {
Circle circle = new Circle(x, y, radius, fill);
ObjectProperty<Point2D> mouseLocation = new SimpleObjectProperty<>();
circle.setOnMousePressed(e -> {
mouseLocation.set(new Point2D(e.getX(), e.getY()));
});
circle.setOnMouseDragged(e -> {
double deltaX = e.getX() - mouseLocation.get().getX();
double deltaY = e.getY() - mouseLocation.get().getY();
circle.setCenterX(circle.getCenterX() + deltaX);
circle.setCenterY(circle.getCenterY() + deltaY);
mouseLocation.set(new Point2D(e.getX(), e.getY()));
});
return circle ;
}
public static void main(String[] args) {
launch(args);
}
}
I have a set of Nodes, Circles, on the stage.
I want to be able to click on one of them and 'select it' (just get a reference to it so I can move it around, change color etc.)
Pane root = new Pane();
root.getChildren().addAll( /* an array of Circle objects */ );
Scene scene = new Scene(root, 500, 500, BACKGROUND_COLOR);
scene.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
// how do I get which Circle I clicked on?
}
});
stage.setTitle(TITLE);
stage.setScene(scene);
stage.show();
I would simply register a listener with each circle itself. Then you already have the reference to the circle with which the listener was registered.
This example pushes the limit a little as to usability, because it has 10,000 circles shown all at once, but it demonstrates the technique:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class GridOfCircles extends Application {
private static final PseudoClass SELECTED_P_C = PseudoClass.getPseudoClass("selected");
private final int numColumns = 100 ;
private final int numRows = 100 ;
private final double radius = 4 ;
private final double spacing = 2 ;
private final ObjectProperty<Circle> selectedCircle = new SimpleObjectProperty<>();
private final ObjectProperty<Point2D> selectedLocation = new SimpleObjectProperty<>();
#Override
public void start(Stage primaryStage) {
selectedCircle.addListener((obs, oldSelection, newSelection) -> {
if (oldSelection != null) {
oldSelection.pseudoClassStateChanged(SELECTED_P_C, false);
}
if (newSelection != null) {
newSelection.pseudoClassStateChanged(SELECTED_P_C, true);
}
});
Pane grid = new Pane();
for (int x = 0 ; x < numColumns; x++) {
double gridX = x*(spacing + radius + radius) + spacing ;
grid.getChildren().add(new Line(gridX, 0, gridX, numRows*(spacing + radius + radius)));
}
for (int y = 0; y < numRows ; y++) {
double gridY = y*(spacing + radius + radius) + spacing ;
grid.getChildren().add(new Line(0, gridY, numColumns*(spacing + radius + radius), gridY));
}
for (int x = 0 ; x < numColumns; x++) {
for (int y = 0 ;y < numRows ; y++) {
grid.getChildren().add(createCircle(x, y));
}
}
Label label = new Label();
label.textProperty().bind(Bindings.createStringBinding(() -> {
Point2D loc = selectedLocation.get();
if (loc == null) {
return "" ;
}
return String.format("Location: [%.0f, %.0f]", loc.getX(), loc.getY());
}, selectedLocation));
BorderPane root = new BorderPane(new ScrollPane(grid));
root.setTop(label);
Scene scene = new Scene(root);
scene.getStylesheets().add("grid.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private Circle createCircle(int x, int y) {
Circle circle = new Circle();
circle.getStyleClass().add("intersection");
circle.setCenterX(x * (spacing + radius + radius) + spacing);
circle.setCenterY(y * (spacing + radius + radius) + spacing);
circle.setRadius(radius);
circle.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
selectedCircle.set(circle);
selectedLocation.set(new Point2D(x, y));
});
return circle ;
}
public static void main(String[] args) {
launch(args);
}
}
with the file grid.css:
.intersection {
-fx-fill: blue ;
}
.intersection:selected {
-fx-fill: gold ;
}
You can get the reference by using getSource of the MouseEvent.
Example in which you can drag a Circle and any other Node:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Circle circle = new Circle( 100,100,50);
circle.setStroke(Color.BLUE);
circle.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.3));
Rectangle rectangle = new Rectangle( 0,0,100,100);
rectangle.relocate(200, 200);
rectangle.setStroke(Color.GREEN);
rectangle.setFill(Color.GREEN.deriveColor(1, 1, 1, 0.3));
Text text = new Text( "Example Text");
text.relocate(300, 300);
Pane root = new Pane();
root.getChildren().addAll(circle, rectangle, text);
MouseGestures mouseGestures = new MouseGestures();
mouseGestures.makeDraggable(circle);
mouseGestures.makeDraggable(rectangle);
mouseGestures.makeDraggable(text);
Scene scene = new Scene(root, 1024, 768);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class MouseGestures {
class DragContext {
double x;
double y;
}
DragContext dragContext = new DragContext();
public void makeDraggable( Node node) {
node.setOnMousePressed( onMousePressedEventHandler);
node.setOnMouseDragged( onMouseDraggedEventHandler);
node.setOnMouseReleased(onMouseReleasedEventHandler);
}
EventHandler<MouseEvent> onMousePressedEventHandler = event -> {
if( event.getSource() instanceof Circle) {
Circle circle = (Circle) (event.getSource());
dragContext.x = circle.getCenterX() - event.getSceneX();
dragContext.y = circle.getCenterY() - event.getSceneY();
} else {
Node node = (Node) (event.getSource());
dragContext.x = node.getTranslateX() - event.getSceneX();
dragContext.y = node.getTranslateY() - event.getSceneY();
}
};
EventHandler<MouseEvent> onMouseDraggedEventHandler = event -> {
if( event.getSource() instanceof Circle) {
Circle circle = (Circle) (event.getSource());
circle.setCenterX( dragContext.x + event.getSceneX());
circle.setCenterY( dragContext.y + event.getSceneY());
} else {
Node node = (Node) (event.getSource());
node.setTranslateX( dragContext.x + event.getSceneX());
node.setTranslateY( dragContext.y + event.getSceneY());
}
};
EventHandler<MouseEvent> onMouseReleasedEventHandler = event -> {
};
}
public static void main(String[] args) {
launch(args);
}
}