I have this code where I have random rectangles generating over the entire canvas. I also have 1 rectangle at the center.
I need to get the 1 rectangle at the center to move around when the user hovers the mouse over the canvas.
Here is my code
package sample;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Random;
public class Main extends Application {
private static final int DRAW_WIDTH = 800;
private static final int DRAW_HEIGHT = 500;
private Animation myAnimation;
private Canvas canvas;
private GraphicsContext gtx;
#Override
public void start(Stage stage) throws Exception
{
stage.setTitle("tRIPPy BoXXX");
canvas = new Canvas(DRAW_WIDTH, DRAW_HEIGHT);
gtx = canvas.getGraphicsContext2D();
gtx.setLineWidth(3);
gtx.setFill(Color.BLACK);
gtx.fillRect(0, 0, DRAW_WIDTH, DRAW_HEIGHT);
VBox vBox = new VBox();
vBox.getChildren().addAll(canvas);
Scene scene = new Scene(vBox, DRAW_WIDTH, DRAW_HEIGHT);
stage.setScene(scene);
stage.show();
myAnimation = new Animation();
myAnimation.start();
}
class Animation extends AnimationTimer
{
#Override
public void handle(long now)
{
Random rand = new Random();
double red = rand.nextDouble();
double green = rand.nextDouble();
double blue = rand.nextDouble();
Color boxColor = Color.color(red,green,blue);
gtx.setStroke(boxColor);
....
This is the box that I want to have moving around with the users mouse. I have tried some things on my own, however I can't get the rect to stay the way it is in the code.
.....
int rectX = 800 / 2;
int rectY = 500 / 2;
for (int side = 10; side <= 100; side += 10) {
gtx.strokeRect(rectX - side / 2, rectY - side / 2, side, side);
}
int centerX = (int)(Math.random()*800);
int centerY = (int)(Math.random()*800);
for (int side = (int)(Math.random()*100); side <= Math.random()* 100; side += Math.random()*100)
{
gtx.strokeRect(centerX - side / 2, centerY - side / 2, side, side);
}
}
}
public static void main(String[] args)
{
launch(args);
}
If you plan to move some graphics arround, why do you then start with a canvas? On a canvas you cannot move anything arround unless you constantly want to redraw everything again and again. Putting your rectangles into the scene graph is much better suited for this task.
Related
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
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;
}
}
I'm currently developing a game in Java, and I've been trying to figure out how to draw a shape (e.g. a circle) to the canvas, on top of a different shape (e.g. a square), but to only draw the parts of the circle which are intersecting the square, similar to a clipping mask between layers in Photoshop.
I've tried using GraphicsContext.clearRect() to clear the areas where the bottom shape is not, but that removes the background.
The code below produces this result:
However, this is the result I desire:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class CircleWithinSquareTest extends Application {
#Override
public void start(Stage stage) throws Exception {
int width = 200;
int height = 200;
Canvas canvas = new Canvas(width, height);
GraphicsContext gc = canvas.getGraphicsContext2D();
AnimationTimer timer = new AnimationTimer() {
final int bgCellSize = 8;
final int x = 100;
final int y = 100;
double angle = 0;
#Override
public void handle(long now) {
/* Draw checkered background */
gc.setFill(Color.WHITE);
gc.fillRect(0, 0, width, height);
gc.setFill(Color.LIGHTGRAY);
boolean odd = false;
for (int y = 0; y < height; y += bgCellSize) {
odd = !odd;
for (int x = odd ? 0 : bgCellSize; x < width; x += bgCellSize * 2) {
gc.fillRect(x, y, bgCellSize, bgCellSize);
}
}
/* Draw square */
gc.setFill(Color.BLUE);
gc.fillRect(x, y, 50, 50);
/* Draw circle */
gc.save();
angle += 5;
if (angle >= 360) {
angle = 0;
}
Rotate r = new Rotate(angle, x, y);
gc.setTransform(r.getMxx(), r.getMyx(), r.getMxy(), r.getMyy(), r.getTx(), r.getTy());
gc.setFill(Color.RED);
gc.fillOval(x, y, 30, 30);
gc.restore();
}
};
timer.start();
Group root = new Group(canvas);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
}
You can use clipping, add next code before setTransform:
gc.beginPath();
gc.rect(x, y, 50, 50);
gc.closePath();
gc.clip();
I want to get the current position (x,y) of a Circle (javafx.scene.shape.Circle) i am moving via a PathTransition, while the transition is running/happening.
So i need some kind of task, that checks the position of the circle every 50 milliseconds (for example).
I also tried this solution Current circle position of javafx transition which was suggested on Stack Overflow, but i didn't seem to work for me.
Circle projectile = new Circle(Playground.PROJECTILE_SIZE, Playground.PROJECTILE_COLOR);
root.getChildren().add(projectile);
double duration = distance / Playground.PROJECTILE_SPEED;
double xOff = (0.5-Math.random())*Playground.WEAPON_OFFSET;
double yOff = (0.5-Math.random())*Playground.WEAPON_OFFSET;
Line shotLine = new Line(player.getCurrentX(), player.getCurrentY(), aimLine.getEndX() + xOff, aimLine.getEndY() + yOff);
shotLine.setEndX(shotLine.getEndX() + (Math.random()*Playground.WEAPON_OFFSET));
PathTransition pt = new PathTransition(Duration.seconds(duration), shotLine, projectile);
// Linear movement for linear speed
pt.setInterpolator(Interpolator.LINEAR);
pt.setOnFinished(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
// Remove bullet after hit/expiration
projectile.setVisible(false);
root.getChildren().remove(projectile);
}
});
projectile.translateXProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
double x = collider.getTranslateX() - projectile.getTranslateX();
double y = collider.getTranslateY() - projectile.getTranslateY();
double distance = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
System.out.println("Distance: "+ distance);
if (distance < 50) {
System.out.println("hit");
}
}
});
pt.play();
A PathTransition will move a node by manipulating its translateX and translateY properties. (A TranslateTransition works the same way.)
It's hard to answer your question definitively as your code is so incomplete, but if the projectile and collider have the same parent in the scene graph, converting the initial coordinates of the projectile and collider by calling localToParent will give the coordinates in the parent, including the translation. So you can observe the translateX and translateY properties and use that conversion to check for a collision. If they have different parents, you can do the same with localToScene instead and just convert both to coordinates relative to the scene.
Here's a quick SSCCE. Use the left and right arrows to aim, space to shoot:
import javafx.animation.Animation;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ShootingGame extends Application {
#Override
public void start(Stage primaryStage) {
final double width = 400 ;
final double height = 400 ;
final double targetRadius = 25 ;
final double projectileRadius = 5 ;
final double weaponLength = 25 ;
final double weaponX = width / 2 ;
final double weaponStartY = height ;
final double weaponEndY = height - weaponLength ;
final double targetStartX = targetRadius ;
final double targetY = targetRadius * 2 ;;
Pane root = new Pane();
Circle target = new Circle(targetStartX, targetY, targetRadius, Color.BLUE);
TranslateTransition targetMotion = new TranslateTransition(Duration.seconds(2), target);
targetMotion.setByX(350);
targetMotion.setAutoReverse(true);
targetMotion.setCycleCount(Animation.INDEFINITE);
targetMotion.play();
Line weapon = new Line(weaponX, weaponStartY, weaponX, weaponEndY);
weapon.setStrokeWidth(5);
Rotate weaponRotation = new Rotate(0, weaponX, weaponStartY);
weapon.getTransforms().add(weaponRotation);
Scene scene = new Scene(root, width, height);
scene.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.LEFT) {
weaponRotation.setAngle(Math.max(-45, weaponRotation.getAngle() - 2));
}
if (e.getCode() == KeyCode.RIGHT) {
weaponRotation.setAngle(Math.min(45, weaponRotation.getAngle() + 2));
}
if (e.getCode() == KeyCode.SPACE) {
Point2D weaponEnd = weapon.localToParent(weaponX, weaponEndY);
Circle projectile = new Circle(weaponEnd.getX(), weaponEnd.getY(), projectileRadius);
TranslateTransition shot = new TranslateTransition(Duration.seconds(1), projectile);
shot.setByX(Math.tan(Math.toRadians(weaponRotation.getAngle())) * height);
shot.setByY(-height);
shot.setOnFinished(event -> root.getChildren().remove(projectile));
BooleanBinding hit = Bindings.createBooleanBinding(() -> {
Point2D targetLocation = target.localToParent(targetStartX, targetY);
Point2D projectileLocation = projectile.localToParent(weaponEnd);
return (targetLocation.distance(projectileLocation) < targetRadius + projectileRadius) ;
}, projectile.translateXProperty(), projectile.translateYProperty());
hit.addListener((obs, wasHit, isNowHit) -> {
if (isNowHit) {
System.out.println("Hit");
root.getChildren().remove(projectile);
root.getChildren().remove(target);
targetMotion.stop();
shot.stop();
}
});
root.getChildren().add(projectile);
shot.play();
}
});
root.getChildren().addAll(target, weapon);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I recently wanted to create an animated background in JavaFX, similar to the Swing example seen here. I used a Canvas on which to draw, as shown in Working with the Canvas API, and an AnimationTimer for the drawing loop, as shown in Animation Basics. Unfortunately, I'm not sure how to resize the Canvas automatically as the enclosing Stage is resized. What is a good approach?
A similar question is examined in How to make canvas Resizable in javaFX?, but the accepted answer there lacks the binding illustrated in the accepted answer here.
In the example below, the static nested class CanvasPane wraps an instance of Canvas in a Pane and overrides layoutChildren() to make the canvas dimensions match the enclosing Pane. Note that Canvas returns false from isResizable(), so "the parent cannot resize it during layout," and Pane "does not perform layout beyond resizing resizable children to their preferred sizes." The width and height used to construct the canvas become its initial size. A similar approach is used in the Ensemble particle simulation, FireworksApp, to scale a background image while retaining its aspect ratio.
As an aside, note the difference from using fully saturated colors compared to the original. These related examples illustrate placing controls atop the animated background.
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/a/31761362/230513
* #see https://stackoverflow.com/a/8616169/230513
*/
public class Baubles extends Application {
private static final int MAX = 64;
private static final double WIDTH = 640;
private static final double HEIGHT = 480;
private static final Random RND = new Random();
private final Queue<Bauble> queue = new LinkedList<>();
private Canvas canvas;
#Override
public void start(Stage stage) {
CanvasPane canvasPane = new CanvasPane(WIDTH, HEIGHT);
canvas = canvasPane.getCanvas();
BorderPane root = new BorderPane(canvasPane);
CheckBox cb = new CheckBox("Animate");
cb.setSelected(true);
root.setBottom(cb);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
for (int i = 0; i < MAX; i++) {
queue.add(randomBauble());
}
AnimationTimer loop = new AnimationTimer() {
#Override
public void handle(long now) {
GraphicsContext g = canvas.getGraphicsContext2D();
g.setFill(Color.BLACK);
g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
for (Bauble b : queue) {
g.setFill(b.c);
g.fillOval(b.x, b.y, b.d, b.d);
}
queue.add(randomBauble());
queue.remove();
}
};
loop.start();
cb.selectedProperty().addListener((Observable o) -> {
if (cb.isSelected()) {
loop.start();
} else {
loop.stop();
}
});
}
private static class Bauble {
private final double x, y, d;
private final Color c;
public Bauble(double x, double y, double r, Color c) {
this.x = x - r;
this.y = y - r;
this.d = 2 * r;
this.c = c;
}
}
private Bauble randomBauble() {
double x = RND.nextDouble() * canvas.getWidth();
double y = RND.nextDouble() * canvas.getHeight();
double r = RND.nextDouble() * MAX + MAX / 2;
Color c = Color.hsb(RND.nextDouble() * 360, 1, 1, 0.75);
return new Bauble(x, y, r, c);
}
private static class CanvasPane extends Pane {
private final Canvas canvas;
public CanvasPane(double width, double height) {
canvas = new Canvas(width, height);
getChildren().add(canvas);
}
public Canvas getCanvas() {
return canvas;
}
#Override
protected void layoutChildren() {
super.layoutChildren();
final double x = snappedLeftInset();
final double y = snappedTopInset();
// Java 9 - snapSize is deprecated, use snapSizeX() and snapSizeY() accordingly
final double w = snapSize(getWidth()) - x - snappedRightInset();
final double h = snapSize(getHeight()) - y - snappedBottomInset();
canvas.setLayoutX(x);
canvas.setLayoutY(y);
canvas.setWidth(w);
canvas.setHeight(h);
}
}
public static void main(String[] args) {
launch(args);
}
}
I combined both prior solutions ( #trashgod and #clataq's ) by putting the canvas in a Pane and binding it to it:
private static class CanvasPane extends Pane {
final Canvas canvas;
CanvasPane(double width, double height) {
setWidth(width);
setHeight(height);
canvas = new Canvas(width, height);
getChildren().add(canvas);
canvas.widthProperty().bind(this.widthProperty());
canvas.heightProperty().bind(this.heightProperty());
}
}
Couldn't you do this with a Binding as well? The following seems to produce the same results without having to add the derived class.
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.DoubleBinding;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
* #see http://stackoverflow.com/a/31761362/230513
* #see http://stackoverflow.com/a/8616169/230513
*/
public class Baubles extends Application {
private static final int MAX = 64;
private static final double WIDTH = 640;
private static final double HEIGHT = 480;
private static final Random RND = new Random();
private final Queue<Bauble> queue = new LinkedList<>();
private Canvas canvas;
#Override
public void start(Stage stage) {
canvas = new Canvas(WIDTH, HEIGHT);
BorderPane root = new BorderPane(canvas);
CheckBox cb = new CheckBox("Animate");
cb.setSelected(true);
root.setBottom(cb);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
// Create bindings for resizing.
DoubleBinding heightBinding = root.heightProperty()
.subtract(root.bottomProperty().getValue().getBoundsInParent().getHeight());
canvas.widthProperty().bind(root.widthProperty());
canvas.heightProperty().bind(heightBinding);
for (int i = 0; i < MAX; i++) {
queue.add(randomBauble());
}
AnimationTimer loop = new AnimationTimer() {
#Override
public void handle(long now) {
GraphicsContext g = canvas.getGraphicsContext2D();
g.setFill(Color.BLACK);
g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
for (Bauble b : queue) {
g.setFill(b.c);
g.fillOval(b.x, b.y, b.d, b.d);
}
queue.add(randomBauble());
queue.remove();
}
};
loop.start();
cb.selectedProperty().addListener((Observable o) -> {
if (cb.isSelected()) {
loop.start();
} else {
loop.stop();
}
});
}
private static class Bauble {
private final double x, y, d;
private final Color c;
public Bauble(double x, double y, double r, Color c) {
this.x = x - r;
this.y = y - r;
this.d = 2 * r;
this.c = c;
}
}
private Bauble randomBauble() {
double x = RND.nextDouble() * canvas.getWidth();
double y = RND.nextDouble() * canvas.getHeight();
double r = RND.nextDouble() * MAX + MAX / 2;
Color c = Color.hsb(RND.nextDouble() * 360, 1, 1, 0.75);
return new Bauble(x, y, r, c);
}
public static void main(String[] args) {
launch(args);
}
}