I want to create a java FX application that draws lines on a Canvas one step at a time, with a noticable time between line segments. In the below application I have what I imagined would draw a diagonal line, stall a second and then draw the next diagonal line. Instead, the FX window pops up blank, waits 2 seconds, and then shows the two diagonal lines at the same time. How do I achieve the effect I am looking for? Is javafx.scene.canvas.Canvas not the right object to be using?
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
public class FrameCanvas extends Application{
public static void main(String[] args){
launch(args);
}
#Override
public void start(Stage primaryStage)throws Exception{
////////////////////Basic FX stuff
Canvas theCanvas = new Canvas(900,900);
StackPane theLayout = new StackPane();
theLayout.getChildren().add(theCanvas);
Scene theScene = new Scene(theLayout,900,900);
primaryStage.setScene(theScene);
primaryStage.show();
///////////////////////
/////Drawing an X
///////////////////////
GraphicsContext gc = theCanvas.getGraphicsContext2D();
Thread.sleep(1000);
gc.strokeLine(0,0,200,200);
Thread.sleep(1000);
gc.strokeLine(200,0,0,200);
/////////////////////////////
}
}
Don't block (e.g. Thread.sleep(...)) the FX Application Thread. That thread is responsible for rendering the scene, so you will prevent any updates from being rendered.
Instead, use an animation for functionality like this (after all, an animation is really what you're creating here):
public void start(Stage primaryStage)throws Exception{
////////////////////Basic FX stuff
Canvas theCanvas = new Canvas(900,900);
StackPane theLayout = new StackPane();
theLayout.getChildren().add(theCanvas);
Scene theScene = new Scene(theLayout,900,900);
primaryStage.setScene(theScene);
primaryStage.show();
///////////////////////
/////Drawing an X
///////////////////////
GraphicsContext gc = theCanvas.getGraphicsContext2D();
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(1), e -> gc.strokeLine(0,0,200,200)),
new KeyFrame(Duration.seconds(2), e -> gc.strokeLine(200,0,0,200))
);
timeline.play();
/////////////////////////////
}
Related
My goal for now is to make the animation of a little circle, going along the path of a big circle. In the future, I would need to dynamically change the speed of revolution using a slider, but that is not a problem now. I can't figure out how to make this animation smooth? By this I mean that I want my little circle to not slow down when it reaches the end of animation. Is this possible using path transition? Or should I use some other method to implement animation in order for it to be smooth?
Here is what I have for now:
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
import javafx.util.Duration;
public class SpinningCircles extends Application {
BorderPane root = new BorderPane();
int BigCircRad = 200;
int BigCircX = 750;
int BigCircY = 400;
double duration = 1;
public static void main(String[] args) {
launch();
}
#Override
public void start(Stage stage){
Circle cir1 = new Circle(BigCircRad);
Circle cir2 = new Circle(BigCircRad/10);
cir1.setFill(Color.WHITE);
cir2.setFill(Color.WHITE);
cir1.setStroke(Color.BLUE);
cir2.setStroke(Color.BLUE);
cir1.setStrokeWidth(4);
cir2.setStrokeWidth(4);
cir1.setCenterX(BigCircX);
cir2.setCenterX(BigCircX);
cir1.setCenterY(BigCircY);
cir2.setCenterY(BigCircY-BigCircRad);
PathTransition pt = new PathTransition();
pt.setNode(cir2);
pt.setDuration(Duration.seconds(duration));
pt.setPath(cir1);
pt.setDelay(Duration.seconds(1));
pt.setCycleCount(Animation.INDEFINITE);
pt.play();
root.getChildren().addAll(cir1, cir2);
Scene scene = new Scene(root, 1000, 500);
stage.setScene(scene);
stage.setTitle("Spinning circles");
stage.setMaximized(true);
stage.setFullScreen(true);
stage.show();
}
}
You need to set the interpolator for the animation.
From the docs, the interpolator
Controls the timing for acceleration and deceleration at each
Transition cycle.
Default interpolator is set to Interpolator.EASE_BOTH.
The default, Interpolator.EASE_BOTH
will make an animation start slow, then accelerate and slow down again towards the end, all in a smooth manner.
Instead, use a LINEAR interpolator:
pt.setInterpolator(Interpolator.LINEAR);
Note the interpolator must be set before the animation is started:
PathTransition pt = new PathTransition();
pt.setNode(cir2);
pt.setDuration(Duration.seconds(duration));
pt.setPath(cir1);
pt.setDelay(Duration.seconds(1));
pt.setCycleCount(Animation.INDEFINITE);
pt.setInterpolator(Interpolator.LINEAR);
pt.play();
I am making a game of snake, but whenever I try to update my canvas in the draw() method, the new snake won't draw. It draws in the run thread. I have tried a bunch of different things, but I can't seem to get it working.
Imports:
import javafx.application.Application;
import javafx.event.*;
import javafx.scene.*;
import javafx.stage.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.geometry.*;
import java.io.*;
import java.util.*;
import javafx.scene.input.KeyEvent;
import javafx.event.EventHandler;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.text.*;
import javafx.scene.paint.*;
import javafx.scene.canvas.*;
Actual class:
public class Snake extends Application implements Runnable
{
//Snake parts and locations
boolean dead;
int headX=0;
int headY=0;
// for the game
Group root = new Group();
Scene snakeG = new Scene(root, 550, 550,Color.BLACK);
final Canvas canvas = new Canvas(550,550);
GraphicsContext gc = canvas.getGraphicsContext2D();
//Start Game
VBox newGame = new VBox(3);
Scene startC = new Scene(newGame, 200, 200);
Label info = new Label("Snake Game \nCreated by: Austin");
Label rules = new Label("Rules\n1.) If you hit the edge you die\n2.) If you touch your snake you die\n3.)Collect fruit to increase your snakes size");
Button startBut = new Button("Start Game");
Stage startS;
public static void main ( String[] args )
{
launch(args);
}
#Override
public void start ( Stage primaryStage )throws Exception
{
startS=primaryStage;
startS.setTitle("Snake");
newGame.getChildren().addAll(info,rules,startBut);
newGame.setAlignment(Pos.CENTER);
startS.setScene(startC);
startS.show();
startBut.setOnAction(e ->
{
startGame();
});
}
public void startGame()
{
System.out.println("Success");
headX=275;
headY=275;
dead = false;
gc.clearRect(0,0,800,800);
startS.setScene(snakeG);
gc.setFill(Color.GREEN);
gc.fillRect(headX,headY,10,10);
root.getChildren().add(canvas);
(new Thread ( new Snake())).start();
}
public void run()
{
draw();
}
// draws the snake
public void draw()
{
System.out.println("DRAW STARTED");
gc.setFill(Color.GREEN);
gc.fillRect(50,50,10,10);
}
}
If you know of a better way to draw graphics in JavaFX, please tell me. This is the only way I could find for what I am doing.
There are some problems with your approach.
You don't need your own thread.
You can only modify the active scene graph (including a canvas) using the JavaFX Thread. Read the JavaFX concurrency documentation:
The JavaFX scene graph, which represents the graphical user interface of a JavaFX application, is not thread-safe and can only be accessed and modified from the UI thread also known as the JavaFX Application thread.
Your main application class should not implement Runnable.
Some suggestions:
You may find it easier to use SceneGraph nodes for your game objects rather than a Canvas, but either should work.
You can implement your game loop using an AnimationTimer as demoed here.
Here is a sample of using an AnimationTimer for display.
I have a project in class where I need to display a traffic light with simply three cirlces. I started with the yellow one, and then attempted to add a red one in some random other place just to see if I could do it, however the yellow one is the only one showing. I can't tell if the red one is somehow underneath the yellow one, but in any case it doesn't make much sense to me as to why the red circle isn't showing.
package tryingGraphicsStuff;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.scene.control.*;
public class TryingGraphicsStuff extends Application{
#Override
public void start(Stage stage) throws Exception {
// create circle
Circle circle = new Circle();
circle.setCenterX(150);
circle.setCenterY(150);
circle.setRadius(50);
circle.setFill(Color.RED);
// place on pane
StackPane p = new StackPane();
p.getChildren().add(circle);
// ensure it stays centered if window resized
//circle.centerXProperty().bind(p.widthProperty().divide(2));
//circle.centerYProperty().bind(p.heightProperty().divide(2));
Circle circleTwo = new Circle();
circleTwo.setCenterX(400);
circleTwo.setCenterY(400);
circleTwo.setRadius(50);
circleTwo.setFill(Color.YELLOW);
// place on pane
p.getChildren().add(circleTwo);
// create scene from pane
Scene scene = new Scene(p, 300, 1000);
// place scene on stage
stage.setTitle("Circle");
stage.setScene(scene);
stage.show();
}
public static void main (String [] args)
{
Application.launch(args);
}
}
A StackPane "lays out its children in a back-to-front stack". (The stack here is in z-coordinates). It is a "layout pane" which actually manages the placement of the child nodes for you. Consequently, the centerX and centerY properties of the circles are ignored, and they appear one on top of the other in the order they are added (so the red one is underneath the yellow one, and the only one you see is the yellow one). By default, the stack pane centers them.
All "layout panes" position the nodes for you. For example, a VBox will position nodes in a vertical stack, with the first one at the top, the second below, and so on. So if you used a VBox instead of a StackPane, the circles would appear one below the other (in the y-direction), but note they would still not respect the centerX and centerY properties.
The Pane class itself does not manage the layout of its child nodes; so if you want to use the coordinates for shape objects, Pane is probably your best option. Group behaves similarly, but takes on the bounds of the union of its child bounds, so it acts like Pane but its local coordinate system is different.
The following demo shows all these options. Again, Pane will be the one that behaves in an intuitive way.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class CircleLayoutExample extends Application {
#Override
public void start(Stage primaryStage) {
TabPane tabs = new TabPane();
tabs.getTabs().add(createTab(new StackPane()));
tabs.getTabs().add(createTab(new VBox()));
tabs.getTabs().add(createTab(new Pane()));
tabs.getTabs().add(createTab(new Group()));
Scene scene = new Scene(tabs, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private Tab createTab(Pane pane) {
Circle c1 = new Circle(150, 150, 50, Color.RED);
Circle c2 = new Circle(400, 400, 50, Color.YELLOW);
pane.getChildren().addAll(c1, c2);
Tab tab = new Tab(pane.getClass().getSimpleName());
tab.setContent(pane);
return tab ;
}
// annoyingly, Pane and Group do not have a common superclass with a getChildren()
// method, so just reproduce the code...
private Tab createTab(Group pane) {
Circle c1 = new Circle(150, 150, 50, Color.RED);
Circle c2 = new Circle(400, 400, 50, Color.YELLOW);
pane.getChildren().addAll(c1, c2);
Tab tab = new Tab(pane.getClass().getSimpleName());
tab.setContent(pane);
return tab ;
}
public static void main(String[] args) {
launch(args);
}
}
Yeah your both the circles are overlapping.
You can simply use a VBox instead of StackPane. It will solve your issue.
VBox p = new VBox();
As other answers have suggested, using a VBox would help you out the most here, since it will automatically put its children into a vertical row. Here is a brief snippet using an array (so you can make as many circles as you want)
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Circle;
import javafx.scene.paint.*;
public class TryingGraphicsStuff extends Application{
#Override
public void start(Stage stage) throws Exception {
Circle[] circle = new Circle[3]; // create 3 circles
VBox vBox = new VBox(); // vbox will put circles in vertical row
vBox.setAlignment(Pos.CENTER); // center circles
for(int i = 0; i < circle.length; i++){
circle[i] = new Circle(50); // initialize circles with radius of 50
vBox.getChildren().add(circle[i]);
}
circle[0].setFill(Color.RED);
circle[1].setFill(Color.YELLOW);
circle[2].setFill(Color.GREEN);
// add vbox to scene
Scene scene = new Scene(vBox, 300, 800);
stage.setTitle("Circle");
stage.setScene(scene);
stage.show();
}
public static void main (String [] args){
Application.launch(args);
}
}
As always, please understand the code and don't just mindlessly copy and paste. Cheers!
I'm actually a bit confused by the code above. According to your numbers the red one should be the one showing and not the yellow one. Your scene is only 300px wide and you center the yellow circle at 400 which will put it out of view (having a radius of only 50).
Either increase your scene size or move your circle inside your view.
I would like to execute multiple stage operations in one frame :
stage.sizeToScene()
stage.centerOnScreen()
Currently I can see that the stage is first resized, then centered. I would like both operations to be done atomically on the same re-paint.
Here is a working example :
package sample;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Main extends Application {
private HBox first = new HBox();
private HBox second = new HBox();
private Button change1 = new Button("Go to 2nd");
private Button change2 = new Button("Go to 1st");
#Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("Hello World");
first.setSpacing(10);
first.setAlignment(Pos.CENTER_LEFT);
first.getChildren().addAll(
change1, new Label("Hello"), new Label("World")
);
second.setSpacing(10);
second.setAlignment(Pos.CENTER_LEFT);
second.getChildren().addAll(
change2, new Label("BYE MY FRIENDS, THIS IS MUCH LONGER!")
);
change1.setOnAction(event -> {
primaryStage.getScene().setRoot(second);
primaryStage.sizeToScene();
primaryStage.centerOnScreen();
});
change2.setOnAction(event -> {
primaryStage.getScene().setRoot(first);
primaryStage.sizeToScene();
primaryStage.centerOnScreen();
});
primaryStage.setScene(new Scene(first));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
To reproduce, launch the application, move the window top-left and make it bigger. Then click on the button. You will see that the window is first resized, then moved at the center.
On this example case it's really really fast, because the application is really light. But with a real-world application it's much more noticable.
When I run the following program
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Ellipse;
import javafx.stage.Stage;
public class Test1 extends Application {
public static void main(String[] args) throws Exception {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
Pane topPane = new Pane();
Scene scene = new Scene(topPane, 600, 400);
StackPane sp = new StackPane();
Label l1 = new Label("1 2");
Ellipse e1 = new Ellipse(100, 50);
e1.setOpacity(0.5);
sp.getChildren().addAll(l1, e1);
e1.radiusXProperty().bind(l1.widthProperty());
e1.radiusYProperty().bind(l1.heightProperty());
topPane.getChildren().add(sp);
sp.relocate(200, 100);
sp.setStyle("-fx-border-color: RED;");
Platform.runLater(() -> {
//l1.setText("123");
//l1.setText("1 2");
});
stage.setScene(scene);
stage.show();
}
}
I get a red box surrounding the text label only, but when I uncomment the two lines inside the Platform.runLater() block above, I get a red box surrounding the outer ellipse, which is what I want.
So it seems to me the layout bounds of the stack pane is not set correctly from the model description, since the bounds are determined only from the label control. But when I force an invalidation in Platform.runLater() the layout bounds are where they should be.
Why is this happening and how do I prevent it? I would like to be able to just specify my model/graph and then it should render correctly on the first show, or?
add this sp.requestLayout(); after stage.Show();