JavaFX. Draw a ball and drag a line from its center - java

The idea is to draw a ball on MOUSE_CLICKED
And draw a line starting from its center till it released in the MOUSE_DRAGGED handler.
but it does complete opposite and I do not simply get it - it draws first a line and after i release the mouse the ball appears.
Does anyone see where is the problem?
public class Step extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Canvas layer1 = new Canvas(500, 500);
Group root = new Group();
root.getChildren().add(layer1);
Ball c_ball = new Ball(0, 0, 50, 0, 0);
Arrow arrow = new Arrow(0, 0, 0, 0);
layer1.addEventHandler(MouseEvent.MOUSE_CLICKED,
ev -> {
c_ball.x = ev.getX();
c_ball.y = ev.getY();
arrow.start_x = ev.getX();
arrow.start_y = ev.getY();
GraphicsContext gc = layer1.getGraphicsContext2D();
gc.setFill(Color.DARKCYAN);
gc.fillOval(c_ball.x - c_ball.size / 2, c_ball.y - c_ball.size / 2, c_ball.size, c_ball.size);
});
layer1.addEventHandler(MouseEvent.MOUSE_DRAGGED,
ev -> {
GraphicsContext gc_arr = layer1.getGraphicsContext2D();
gc_arr.clearRect(0, 0, layer1.getWidth(), layer1.getHeight());
gc_arr.strokeLine(arrow.start_x, arrow.start_y, ev.getX(), ev.getY());
});
Scene scene = new Scene(root, 500, 500);
stage.setScene(scene);
stage.show();
}
}
Here is class Ball :
public class Ball {
public double x, y;
public double dx, dy;
public double size;
public Ball(double x, double y, double size, double dx, double dy) {
this.x = x;
this.y = y;
this.size = size;
this.dx = dx;
this.dy = dy;
}}
And arrow
public class Arrow {
public double start_x, start_y;
public double end_x, end_y;
public Arrow(double x1, double y1, double x2, double y2) {
this.start_x = x1;
this.start_y = y1;
this.end_x = x2;
this.end_y = y2;
}}

Looking at MouseEvent.MOUSE_CLICKED documentation you will see :
EventType javafx.scene.input.MouseEvent.MOUSE_CLICKED
This event occurs when mouse button has been clicked (pressed and
released on the same node). This event provides a button-like
behavior to any node. Note that even long drags can generate click
event (it is delivered to the top-most node on which the mouse was
both pressed and released).
The mouse event you are looking for is MouseEvent.MOUSE_PRESSED but in the case your code is not going to work neither. The reason is you clear everything inside the MOUSE_DRAGGED event, when you call gc_arr.clearRect(0, 0, layer1.getWidth(), layer1.getHeight());.Thus you will draw the circle but you will clear the canvas and only draw the line. If you remove that line you will have multiple lines each time you drag your mouse but that's a different problem.
You will need to make a big step back and think, do i really need to implement my program with Canvas?
If the answer is YES you will need to keep inside a list (or some data structure like array etc) all the components and when you want to change something you need to update the objects and redraw everything
If the answer is NO then you are taking the correct approach and you will need to change the canvas to an AnchorPane or Pane etc and just add the Nodes ( Circle , Lines etc ) directly on the pane.
Here is a simple example with AnchorPane instead of Canvas.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class ShapesTest extends Application {
public static void main(String[] args) {
launch(args);
}
private double arrayStartX;
private double arrayStartY;
private AnchorPane root;
private Line l;
#Override
public void start(Stage stage) {
root = new AnchorPane();
root.addEventHandler(MouseEvent.MOUSE_PRESSED, ev -> {
addBall(ev.getX(), ev.getY());
arrayStartX = ev.getX();
arrayStartY = ev.getY();
});
root.addEventHandler(MouseEvent.MOUSE_DRAGGED, ev -> {
if (l == null) {
addLine(ev.getX(), ev.getY());
} else {
l.setEndX(ev.getX());
l.setEndY(ev.getY());
}
});
root.addEventHandler(MouseEvent.MOUSE_RELEASED, ev -> {
l = null;
});
Scene scene = new Scene(root, 500, 500);
stage.setScene(scene);
stage.show();
}
private void addLine(double x, double y) {
l = new Line(arrayStartX, arrayStartY, x, y);
root.getChildren().add(l);
}
private void addBall(double x, double y) {
Circle c = new Circle(x, y, 15);
c.fillProperty().set(Color.DARKCYAN);
root.getChildren().add(c);
}
}
In the example above you could keeps the Circle and the Lines informations on your own custom object if you like and then apply physics on each ball when the mouse is released.

Related

Saving mouse position to objects with JavaFX

I have a world map and I want to have a posibility to mark 3 dots on map by left clicking on the map, and save positions of dots (x,y) to 3 objects.
I think that the best approach would be to create 3 objects of class Point, save position (x,y) of each dot to 3 objects of class Point. I did it only for one object (redPoint) because I'm not sure how I can do this for more objects.
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("../resources/fxml/test.fxml"));
primaryStage.setTitle("test");
Point point = new Point();
root.setOnMouseClicked(point::handle);
primaryStage.setScene(new Scene(root, 1280, 720));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
public class Point {
private double x;
private double y;
public Point() {
}
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public void handle(MouseEvent mouseEvent) {
x = mouseEvent.getX();
y = mouseEvent.getY();
Point redPoint = new Point(x, y);
System.out.println(redPoint.getX() + " " + redPoint.getY());
}
}
Current output:
"459.0 220.0" - x, y of place where I clicked but only for one dot and this dot is not placed on map
So I'm not sure:
how to add colored dots in random place where I clicked
how to save position of these dots to 3 different objects
Using JFX Canvas to display the map and the dot can be clicked as a little circle with color of course. Example
Canvas screen = new Canvas(580, 500);
gc = screen.getGraphicsContext2D();
screen.setOnMouseReleased(e -> {
double x = e.getX(), y = e.getY(); // the coordinates
draw(x, y);
});
...
privat void draw(double x, double y) {
...
gc.drawImage(img, 0, 0);
gc.setFill(Color.RED);
gc.fillOval(x, y, 15, 15); // the dot
...
}
private GraphicsContext gc;
private Image img = new Image(getClass().getResourceAsStream("map.jpg"));
...
Hope that could help.

javafx code to pick only the boundary of an arc on MOUSE_CLICKED event

I want to be able to click only the boundary of Arcs overlapping each other in a StackPane. Currently, my code below also picks the fill of the Arcs and I'm not able to fire the MOUSE_CLICKED event of the arc1 when I click point p3 in the below depiction. The explanation of my desired outcome and current outcome is also below.
When user clicks p1, code works as intended, prints "arc2"
When user clicks p2, code prints "arc2" whereas the intended operation is to do nothing
When user clicks p3, code prints "arc2" whereas the intended operation is to print "arc1"
This is because the fill area of the Arc2 gets picked before Arc1 even though the fill is transparent and PickOnBounds is set to false. I'm trying to find a way to not get the fill area to be picked. Failing that, I want to have the event handlers for both Arcs to be fired so I can handle the behavior myself.
Code representing my issue:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.stage.Stage;
public class Example extends Application {
#Override
public void start(Stage primaryStage) {
Arc arc1 = new Arc(100, 100, 100, 100, 90, 90);
Arc arc2 = new Arc(0, 100, 100, 100, 0, 90);
arc1.setStroke(Color.BLACK);
arc2.setStroke(Color.BLACK);
arc1.setFill(Color.TRANSPARENT);
arc2.setFill(Color.TRANSPARENT);
arc1.setStrokeWidth(10);
arc2.setStrokeWidth(10);
arc1.setType(ArcType.OPEN);
arc2.setType(ArcType.OPEN);
arc1.setPickOnBounds(false);
arc2.setPickOnBounds(false);
arc1.setOnMouseClicked(event -> System.out.println("arc1"));
arc2.setOnMouseClicked(event -> System.out.println("arc2"));
StackPane root = new StackPane();
root.getChildren().add(arc1);
root.getChildren().add(arc2);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Picking Issues");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
EDIT:
With c0der's help, I implemented my intended behavior as follows:
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.stage.Stage;
public class Example extends Application {
private Arc arc1, arc2;
private StackPane root;
#Override
public void start(Stage primaryStage) {
arc1 = getArc(200, 200, 200, 200, 90, 90, "arc1");
arc2 = getArc(0, 200, 200, 200, 0, 90, "arc2");
root = new StackPane(arc1, arc2);
root.setOnMouseClicked(event -> onStackPaneClick(event.getX(), event.getY()));
Scene scene = new Scene(root, 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
private Arc getArc(double x, double y, double radiusX, double radiusY, double startAngle, double endAngle, String userData){
Arc arc = new Arc(x,y, radiusX,radiusY,startAngle, endAngle);
arc.setStroke(Color.BLACK);
arc.setFill(Color.TRANSPARENT);
arc.setStrokeWidth(10);
arc.setType(ArcType.OPEN);
arc.setPickOnBounds(false);
arc.setUserData(userData);
return arc;
}
private void onStackPaneClick(double x, double y) {
for (Node itNode : root.getChildren()) {
if (itNode.contains(new Point2D(x, y))) {
if (itNode instanceof Arc) {
Arc itArc = (Arc) itNode;
double centerDistance = Math.sqrt(
(x - itArc.getCenterX()) * (x - itArc.getCenterX()) +
(y - itArc.getCenterY()) * (y - itArc.getCenterY()));
if (centerDistance >= itArc.getRadiusX() - itArc.getStrokeWidth() / 2) {
System.out.println(itArc.getUserData());
break;
}
}
}
}
}
public static void main(String[] args) {
launch(args);
}
}
However, the question still remains technically unanswered as the initial question needs either;
A way to exclude the "fill" area from click events, or,
A way to have the event handlers of all shapes containing the clicked point to be fired when a clicked point is contained within more than one shape
Simply check if Node contains the clicked point:
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.stage.Stage;
public class FxmlTest extends Application {
private Arc arc1, arc2;
#Override
public void start(Stage primaryStage) {
arc1 = getArc(100, 100, 100, 100, 90, 90);
arc2 = getArc(0, 100, 100, 100, 0, 90);
StackPane root = new StackPane(arc1, arc2);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
private Arc getArc(double x, double y, double radiusX, double radiusY, double startAngle, double endAngle){
Arc arc = new Arc(x,y, radiusX,radiusY,startAngle, endAngle);
arc.setStroke(Color.BLACK);
arc.setFill(Color.TRANSPARENT);
arc.setStrokeWidth(10);
arc.setType(ArcType.OPEN);
arc.setPickOnBounds(false);
arc.setOnMouseClicked(event -> checkClickedPoint(event.getX(), event.getY()));
return arc;
}
private void checkClickedPoint(double x, double y) {
Point2D clickedPoint = new Point2D(x, y);
if(arc1.contains(clickedPoint)) {System.out.println("arc1");}
if(arc2.contains(clickedPoint)) {System.out.println("arc2");}
}
public static void main(String[] args) {
launch(null);
}
}
Edit:
To avoid mouse click events from the fill area of the arc (the yellow area in the image)
you could construct a shape of the contour only, without the fill area.
Let's call the arc in the image arc1.
Construct a second arc, arc2 which is smaller and enclosed in arc1.
arc2 is constructed so it covers the yellow area.
Subtracting arc2 from arc1 returns a shape which is the contour of arc1:
class ArcContour {
private final Shape arcContour;
private static final double STROKE = 10;
public ArcContour(double x, double y, double radiusX, double radiusY, double startAngle, double length) {
Arc arc1 = new Arc(x,y, radiusX,radiusY,startAngle, length);
arc1.setStroke(Color.BLACK);
arc1.setStrokeWidth(STROKE);
arc1.setStrokeType(StrokeType.INSIDE);
arc1.setType(ArcType.OPEN);
arc1.setPickOnBounds(false);
Arc arc2 = new Arc(x,y, radiusX - STROKE,radiusY - STROKE,startAngle, length);
arc2.setStrokeType(StrokeType.INSIDE);
arcContour = Shape.subtract(arc1, arc2);
}
Shape getShape() {
return arcContour;
}
}
Test it by:
public class FxmlTest extends Application {
private Shape shape;
private static final double RADIUS = 100;
#Override
public void start(Stage primaryStage) {
shape = new ArcContour(150, 150, RADIUS, RADIUS, 30, 150).getShape();
shape.setOnMouseClicked(event -> checkClickedPoint(event.getX(), event.getY()));
StackPane root = new StackPane(shape);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
//this functinality can be refactored to ArcContour
private void checkClickedPoint(double x, double y) {
if( shape.contains(new Point2D(x, y))) {System.out.println("Arc clicked");}
}
public static void main(String[] args) {
launch(null);
}
}

Create an Ellipse in javavfx where the scaling is from the top left (like a Rectangle) rather than center

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);
}
});

Get Object from Collection

I'm building a grid out of rectangles.
I want to click one of the rectangles and its color should change.
However, I dont know how to access the rectangles in Main AFTER they've been created.
Main:
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
public class Main extends Application{
public static void main(String[] args) {
launch(args);
}
public void changeColor(Pane p) {
p.setOnMouseClicked(me -> {
double posX = me.getX();
double posY = me.getY();
int colX = (int)(posX / 30);
int colY = (int) (posY / 30);
ObservableList<Node> children = p.getChildren();
for( Node d : children) {
if(d.getLayoutX() == colX && d.getLayoutY() == colY) {
// how can i access my rectangle here?
// basically, i want to be able to do .setFill()
}
}
});
}
#Override
public void start(Stage primaryStage) throws Exception {
Grid g = new Grid(30,30, 30);
Pane window = g.render();
Scene scene = new Scene(window, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
this.changeColor(window);
}
}
Grid:
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
public class Grid {
Integer width, height, squareSize;
Color fill = Color.ALICEBLUE,
stroke = Color.BLACK;
public Grid(int x, int y, int squareSize){
this.width = x;
this.height = y;
this.squareSize = squareSize;
}
public Pane render() {
Pane p = new Pane();
Rectangle [][] rect = new Rectangle[this.width][this.height];
for(int i = 0; i < this.width; i++) {
for(int j = 0; j < this.height; j++) {
rect[i][j] = new Rectangle();
rect[i][j].setX(i * width);
rect[i][j].setY(j * height);
rect[i][j].setWidth(this.squareSize);
rect[i][j].setHeight(this.squareSize);
rect[i][j].setFill(this.fill);
rect[i][j].setStroke(this.stroke);
p.getChildren().add(rect[i][j]);
}
}
return p;
}
}
Can someone please help me to figure out how I can access my rectangles again in the main file?
Keeping with your current design, you simply need to test if the mouse clicked within a child and if that child is an instance of Rectangle; then you can cast and call setFill. However, I recommend changing the name of changeColor as that name does not represent what that method is doing.
public void installChangeColorHandler(Pane pane) {
pane.setOnMouseClicked(event -> {
for (Node child : pane.getChildren()) {
if (child instanceof Rectangle
&& child.contains(child.parentToLocal(event.getX(), event.getY()))) {
((Rectangle) child).setFill(/* YOUR COLOR */);
event.consume();
break;
}
}
});
}
Since the event handler is added to Pane the x and y mouse coordinates are relative to said Pane. But since your Rectangles are direct children of Pane we can call Node.parentToLocal to transform those coordinates into the Rectangle's space1. We then need to test if the bounds of the Rectangle contain those coordinates using Node.contains; if it does, change the fill.
That said, you may want to modify your code so that you're adding an/the EventHandler directly to the Rectangles. That way you can use Event.getSource(). For instance2:
public Pane render(EventHandler<MouseEvent> onClick) {
// outer loop...
// inner loop...
Rectangle r = new Rectangle();
// configure r...
r.setOnMouseClicked(onClick);
// end inner loop...
// end outer loop...
}
...
// may want to consume event
Pane window = new Grid(30, 30, 30).render(event ->
((Rectangle) event.getSource()).setFill(/* YOUR COLOR */));
1. Even if they weren't direct children you can still transform the coordinates into the local space. For example, you can use Node.sceneToLocal and the scene coordinates provided by the MouseEvent (i.e. getSceneX()/getSceneY()).
2. This is still staying close to your design. You may want to rethink things, however, into a proper MVC (or other) architecture. Applying MVC With JavaFx

How to draw circles inside each other with different widths?

Im trying to draw circles inside of each other which have the same centres.
But the width should be different for each circle - it should be done inside a while loop.
The result should look like the picture i have uploaded:
My code is shown below:
package modelwhile;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class exercise4_figure3 extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) {
GridPane root = initContent();
Scene scene = new Scene(root);
stage.setTitle("Loops");
stage.setScene(scene);
stage.show();
}
private GridPane initContent() {
GridPane pane = new GridPane();
Canvas canvas = new Canvas(200, 200);
pane.add(canvas, 0, 0);
drawShapes(canvas.getGraphicsContext2D());
return pane;
}
// ------------------------------------------------------------------------
// circle figure begins here
private void drawShapes(GraphicsContext gc) {
int x = 80;
int y = 80;
int r1 = 20;
int r2 = 60;
while (r1 <= 80) {
gc.strokeOval(x - r2, y - r2, r1, r2);
r1 = r1 + 10;
}
}
}
any help would be appreciated.
The issue is that you aren't moving on the x-axis to account for the added width of each new oval. You need to move half the distance being added to the oval in order to keep them all in the same relative position.
Below is your drawShapes() method updated to include this movement. You'll notice I removed your x, y, and r2 variables because they didn't really have any need to be variables since nothing was done with them.
private void drawShapes(GraphicsContext gc) {
int r = 20;
while (r <= 80) {
gc.strokeOval(80-(r/2), 80, r, 60);
r = r + 10;
}
}

Categories

Resources