I am new to javafx and was using it to carry out a version of the Ant Colony Optimization algorithm. I have written some code that was supposed to display a small circle as the ant which moves about randomly in the plane. This worked fine. But I could not modify the code to display the path of the ant on the scene. Basically I want the path of the ant to be highlighted by lines as the ant moves on a 2D plane.
Here is the code:
import java.util.ArrayList;
import java.util.Random;
//Line can either be a sound or a shape
import javafx.animation.Animation.Status;
import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.lang.Object;
import javafx.scene.Node;
import javafx.scene.shape.Shape;
import javafx.scene.shape.Line;
public class TestMotion extends Application {
int N = 10;
static ArrayList<Integer> move = new ArrayList<Integer>();
private double sceneWidth = 512;
private double sceneHeight = 512;
MyNode node = new MyNode(0,0,5);
Path pathA = new Path();
int n = 10;
#Override
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
root.getChildren().addAll(node, pathA);
Scene scene = new Scene( root, sceneWidth, sceneHeight);
primaryStage.setScene( scene);
primaryStage.show();
animate();
}
//convention conversion (i,j) to node number x
public static int convert(int i, int j, int N)
{
return (j*N +i+1);
}
//convention conversion node number x to (i,j)
//implement in order of definition
public static int reconvertY(int x, int N)
{
return (int) java.lang.Math.floor((x-1)/N);
}
public static int reconvertX(int x, int N, int j)
{
return (x-j*N-1);
}
private void animate() {
nextPos(move, N);
int y1 = reconvertY(move.get(move.size()-1), N);
int x1 = reconvertX(move.get(move.size()-1), N, y1);
MyNode here = new MyNode(10*x1, 10*y1, 1);
System.out.println(move.get(move.size()-1));
pathA.getElements().add (new MoveTo ( node.getTranslateX() + node.getBoundsInParent().getWidth() / 2.0, node.getTranslateY() + node.getBoundsInParent().getHeight() / 2.0));
pathA.getElements().add (new LineTo( here.getTranslateX() + here.getBoundsInParent().getWidth() / 2.0, here.getTranslateY() + here.getBoundsInParent().getHeight() / 2.0));
pathA.setStroke(Color.RED);
pathA.setStrokeWidth(1.0);
PathTransition pathTransitionA = new PathTransition();
pathTransitionA.setDuration(Duration.millis(1000));
pathTransitionA.setNode(node);
pathTransitionA.setPath(pathA);
pathTransitionA.play();
pathA = new Path();
pathTransitionA.setOnFinished( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if( pathTransitionA.getStatus() == Status.RUNNING)
{return;}
animate();
}
});
}
//allot new node to the arraylist such that it is adjacent to the previously assigned node
private void nextPos(ArrayList<Integer> array, int N)
{
//I removed this part to save space because it is probably irrelevant the the problem at hand
}
public static void main(String[] args) {
move.add(1);
launch(args);
}
public static class MyNode extends StackPane {
public MyNode(double x, double y, double r) {
// create circle
Circle circle = new Circle();
circle.setRadius(r);
circle.setFill(Color.BLUE);
// set position
setTranslateX( x);
setTranslateY( y);
getChildren().add(circle);
}
}
}
On execution the scene displays the path for the first step only and then stops displaying the path altogether. I cannot figure out why. The path is renewed each time the animate() function runs, and both node and here should be updated regularly. So shouldn't each iteration display the newest path segment of the ant?
Can someone please explain the problem? Is it not possible to exploit Path() and PathTransition() to display these segments and avoid using Line()?
Your approach is not wrong. Only the problem is whenever you are calling the animate(), the path formed is having the coordinates as before. The path coordinates are always returning the following
1.0, 1.0 for moveTo and 5.0,5.0 for LineTo
You need to pass fresh coordinates each time as that the path becomes a new one. Also to keep showing the old path along with the new one, you need to keep adding the newly created path to the Group.
Here's what I have changed in you code. Have a look.
import java.util.ArrayList;
import javafx.animation.Animation.Status;
import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TestMotion extends Application {
int N = 10;
static ArrayList<Integer> move = new ArrayList<Integer>();
private double sceneWidth = 512;
private double sceneHeight = 512;
MyNode node = new MyNode(0, 0, 5);
Group root; //Made root as global variable
int n = 10;
#Override
public void start(Stage primaryStage) throws Exception {
root = new Group();
root.getChildren().addAll(node);
Scene scene = new Scene(root, sceneWidth, sceneHeight);
primaryStage.setScene(scene);
primaryStage.show();
animate(0.0f, 0.0f, 5.0f, 5.0f); //Passing initial coordinates to the method
}
//convention conversion (i,j) to node number x
public static int convert(int i, int j, int N) {
return (j * N + i + 1);
}
//convention conversion node number x to (i,j)
//implement in order of definition
public static int reconvertY(int x, int N) {
return (int) java.lang.Math.floor((x - 1) / N);
}
public static int reconvertX(int x, int N, int j) {
return (x - j * N - 1);
}
private void animate(float moveX, float moveY, float lineX, float lineY) {
Path pathA = new Path();
root.getChildren().add(pathA);
nextPos(move, N);
int y1 = reconvertY(move.get(move.size() - 1), N);
int x1 = reconvertX(move.get(move.size() - 1), N, y1);
//Applying coordinates as obtained
pathA.getElements().add(new MoveTo(moveX, moveY));
pathA.getElements().add(new LineTo(lineX, lineY));
pathA.setStroke(Color.RED);
pathA.setStrokeWidth(1.0);
PathTransition pathTransitionA = new PathTransition();
pathTransitionA.setDuration(Duration.millis(100));
pathTransitionA.setNode(node);
pathTransitionA.setPath(pathA);
pathTransitionA.play();
pathTransitionA.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (pathTransitionA.getStatus() == Status.RUNNING) {
return;
}
animate(lineX, lineY, lineX + 2, lineY + 2); //previous path's endpoint makes the start point of the new Path
}
});
}
//allot new node to the arraylist such that it is adjacent to the previously assigned node
private void nextPos(ArrayList<Integer> array, int N) {
//I removed this part to save space because it is probably irrelevant the the problem at hand
}
public static void main(String[] args) {
move.add(1);
launch(args);
}
public static class MyNode extends StackPane {
public MyNode(double x, double y, double r) {
// create circle
Circle circle = new Circle();
circle.setRadius(r);
circle.setFill(Color.BLUE);
// set position
setTranslateX(x);
setTranslateY(y);
getChildren().add(circle);
}
}
}
Hope It helps.
Related
I created an application which generates a board with a grid pattern, consisting of nodes which hold square objects in javaFX, using GridPanel. Below is the current output:
I want to know how to return the coordinate of a node, after CLICKING on the node. I am aware I have to use an action listener of sorts, but I'm not entirely familiar when it comes to having node coordinates.
Below is the current source code, thank you very much.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class MainApp extends Application {
private final double windowWidth = 1000;
private final double windowHeight = 1000;
/*n is amount of cells per row
m is amount of cells per column*/
private final int n = 50;
private final int m = 50;
double gridWidth = windowWidth / n;
double gridHeight = windowHeight / m;
MyNode[][] playfield = new MyNode[n][m];
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Group root = new Group();
// initialize playfield
for( int i=0; i < n; i++) {
for( int j=0; j < m; j++) {
// create node
MyNode node = new MyNode( i * gridWidth, j * gridHeight, gridWidth, gridHeight);
// add node to group
root.getChildren().add( node);
// add to playfield for further reference using an array
playfield[i][j] = node;
}
}
Scene scene = new Scene( root, windowWidth, windowHeight);
primaryStage.setScene( scene);
primaryStage.show();
primaryStage.setResizable(false);
primaryStage.sizeToScene();
}
public static class MyNode extends StackPane {
public MyNode(double x, double y, double width, double height) {
// create rectangle
Rectangle rectangle = new Rectangle( width, height);
rectangle.setStroke(Color.BLACK);
rectangle.setFill(Color.LIGHTGREEN);
// set position
setTranslateX(x);
setTranslateY(y);
getChildren().addAll(rectangle);
}
}
}
You can add mouse event handler to root :
root.setOnMousePressed(e->mousePressedOnRoot(e));
Where mousePressedOnRoot(e) is defined as
private void mousePressedOnRoot(MouseEvent e) {
System.out.println("mouse pressed on (x-y): "+e.getSceneX()+"-"+e.getSceneY());
}
Edit: alternatively you can add mouse event handler to each MyNode instance by adding setOnMousePressed(e->mousePressedOnNode(e)); to its constructor.
and add the method:
private void mousePressedOnNode(MouseEvent e) {
System.out.println("mouse pressed on (x-y): "+e.getSceneX()+"-"+e.getSceneY());
}
If you need the coordinates within the clicked node use e.getX() and e.getY()
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 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);
}
}
JavaFX Basics write a program that displays a 10-by-10 square matrix. Each element in the matrix is 0 or 1, randomly generated. Display each number centered in a text field. Use TextField setText method to set value 0 or 1 as a string.
As of now I can only print one random number. How can I make display a 10-by-10 matrix?
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.GridPane;
import javafx.stage.Stage;
import java.util.Random;
public class Matrix extends Application {
public class Matrix extends Application {
Button[][] matrix; //names the grid of buttons
#Override
public void start(Stage primaryStage) {
int SIZE = 10;
int length = SIZE;
int width = SIZE;
GridPane root = new GridPane();
matrix = new Button[width][length];
for(int y = 0; y < length; y++)
{
for(int x = 0; x < width; x++)
{
Random rand = new Random();
int rand1 = rand.nextInt(2);
matrix[x][y] = new Button(/*"(" + rand1 + ")"*/);
matrix[x][y].setText("(" + rand1 + ")"); //
matrix[x][y].setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Random Binary Matrix (JavaFX)");
}
});
root.getChildren().add(matrix[x][y]);
}
}
Scene scene = new Scene(root, 500, 500);
primaryStage.setTitle("Random Binary Matrix (JavaFX)");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You actually did most of the hard work already and the only thing you missed is looking up the GridPaneAPI. What your code does is add 40 buttons on top of each other because you never change the row or column of the GridPane!
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javax.xml.soap.Text;
import java.util.ArrayList;
import java.util.Random;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
int SIZE = 10;
int length = SIZE;
int width = SIZE;
GridPane root = new GridPane();
for(int y = 0; y < length; y++){
for(int x = 0; x < width; x++){
Random rand = new Random();
int rand1 = rand.nextInt(2);
// Create a new TextField in each Iteration
TextField tf = new TextField();
tf.setPrefHeight(50);
tf.setPrefWidth(50);
tf.setAlignment(Pos.CENTER);
tf.setEditable(false);
tf.setText("(" + rand1 + ")");
// Iterate the Index using the loops
root.setRowIndex(tf,y);
root.setColumnIndex(tf,x);
root.getChildren().add(tf);
}
}
Scene scene = new Scene(root, 500, 500);
primaryStage.setTitle("Random Binary Matrix (JavaFX)");
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);
}
}