New to java and JavaFX so please bear with me
I need to do a presentation of 5 3d fruit models that show in a continuous loop, 15 seconds apart: fruit1 for 15 seconds then fruit2 for 15 seconds ad so on and so forth.. until fruit5 for 15 seconds and then back to fruit1 and continues until I hit the ESC key which should close the window.
I also understand that it's ideal to change the root group object that makes up the scene instead of changing the scene, so I changed that in my code
I understand that a timeline is needed in order to change something in the scene as it plays out, but I've tried with something similar to what this answer says but I don't get the logic of how to switch the root of the scene every 15seconds
UPDATE:
I gave up on the timeline option and I found the platform.run option as seen on this article which seems to work as I see the window updates iterating from the first fruit in the scenes array to second one but I'm not sure why it only runs once when I need it to run every 15 seconds which means that my scene switcher: nextSceneIndex() should go back and forth between 1 and 0.
UPDATE2:
I went back to the timeline suggestion and I implemented Sedrick's solution and it worked... I can't be happier :)
Here's my working code!
public void start(Stage stage) throws Exception {
BorderPane[] scenes = new BorderPane[]{createSceneApple(),createSceneCarrot(),createSceneTomato()};
Timeline tl = new Timeline();
tl.setCycleCount(Animation.INDEFINITE);
KeyFrame kf_fruit = new KeyFrame(Duration.seconds(10),
new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
if (index==0){
root.getChildren().setAll(scenes[0]);
index = 1;
}else if(index==1){
root.getChildren().setAll(scenes[1]);
index = 2;
}else if(index==2){
root.getChildren().setAll(scenes[2]);
index = 0;
}
}
});
tl.getKeyFrames().add(kf_fruit);
tl.play();
Scene scene = new Scene(root, windowWidth, windowHeight);
stage.setScene(scene);
stage.show();
}
Maybe you can get some ideas from here. This uses the code from the link I posted above. Timeline is used to loop through a list of Shape and info about that shape.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
* JavaFX App
*/
public class App extends Application {
#Override
public void start(Stage stage) {
List<MyShape> shapes = new ArrayList();
shapes.add(new MyShape("Circle", "Shape.Circle", "More Circle Info", new Circle(25, Color.BLUE)));
shapes.add(new MyShape("Rectangle", "Shape.Rectangle", "More Rectangle Info", new Rectangle(100, 50, Color.RED)));
shapes.add(new MyShape("Line", "Shape.Line", "More Line Info", new Line(0, 0, 100, 100)));
TextField tf1 = new TextField();
TextField tf2 = new TextField();
TextArea ta1 = new TextArea();
VBox leftWindow = new VBox(tf1, tf2, ta1);
StackPane rightWindow = new StackPane(shapes.get(1).getShape());
AtomicInteger counter = new AtomicInteger();
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println(counter.get() % shapes.size());
MyShape currentShape = shapes.get(counter.getAndIncrement() % shapes.size());
tf1.setText(currentShape.getName());
tf2.setText(currentShape.getType());
ta1.setText(currentShape.getMoreInfo());
rightWindow.getChildren().set(0, currentShape.getShape());
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
BorderPane root = new BorderPane();
root.setLeft(new StackPane(leftWindow));
root.setRight(rightWindow);
var scene = new Scene(root, 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Update:
If you only have two scenes, that simplifies some things. You basically need to set the initial view. You then need to switch out the view currently showing every two seconds. (I used two seconds so that you can see the views before they are switched out). I created my own version of createSceneCarrot and createSceneApple since I don't know your implementation.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
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 javafx.util.Duration;
/**
* JavaFX App
*/
public class App extends Application {
#Override
public void start(Stage stage) {
BorderPane[] scenes = new BorderPane[]{createSceneApple(),createSceneCarrot()};
StackPane root = new StackPane(scenes[0]);//Set initial view;
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(2), (ActionEvent event) -> {
if(root.getChildren().get(0).equals(scenes[0]))//If the first scene is loaded, load the second scene.
{
root.getChildren().set(0, scenes[1]);
}
else
{
root.getChildren().set(0, scenes[0]);
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
var scene = new Scene(root, 640, 640);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
public BorderPane createSceneApple()
{
BorderPane borderPane = new BorderPane();
TextField tf1 = new TextField("Rectangle 1");
TextField tf2 = new TextField("Rectangle Color: Blue");
TextArea ta1 = new TextArea("20x40");
VBox leftWindow = new VBox(tf1, tf2, ta1);
borderPane.setLeft(leftWindow);
StackPane rightWindow = new StackPane(new Rectangle(20, 40, Color.BLUE));
borderPane.setRight(rightWindow);
return borderPane;
}
public BorderPane createSceneCarrot()
{
BorderPane borderPane = new BorderPane();
TextField tf1 = new TextField("Circle 1");
TextField tf2 = new TextField("Circle Color: Blue");
TextArea ta1 = new TextArea("Radius: 50");
VBox leftWindow = new VBox(tf1, tf2, ta1);
borderPane.setLeft(leftWindow);
StackPane rightWindow = new StackPane(new Circle(50, Color.RED));
borderPane.setRight(rightWindow);
return borderPane;
}
}
Related
I am creating a Solitaire game using JavaFX. I want a Restart button to start a new game, but also a Reset button that will restart the same game i.e. the same scene to its original state.
Restart works seamlessly as I was able to call the start method but I am unable to get Reset working.
package guisolitaire;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class GUISolitaire extends Application {
Game game;
Pane root;
ScoreTimer timer = new ScoreTimer();
#Override
public void start(Stage primaryStage) {
Canvas canvas = new Canvas(1000, 800);
root = new Pane(canvas);
root.setStyle("-fx-background-color: green");
game = new Game(canvas.getGraphicsContext2D());
canvas.setOnMouseClicked(game::handleMouseClicked);
timer.restart();
timer.start();
Scene scene = new Scene(root, Color.GREEN);
primaryStage.setScene(scene);
primaryStage.setTitle("Solitaire");
primaryStage.show();
Label l = new Label(); // Timer label
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO, ae -> l.setText("Time elapsed: " + timer.s)),
new KeyFrame(Duration.seconds(1))
);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
l.setTranslateX(800);
l.setTranslateY(765);
root.getChildren().add(l);
Button newGameButton = new Button();
newGameButton.setText("New Game");
newGameButton.setTranslateX(10);
newGameButton.setTranslateY(765);
root.getChildren().add(newGameButton);
EventHandler<ActionEvent> newGameEvent = ae -> {
timer.stop();
timer.restart();
start(primaryStage);
};
newGameButton.setOnAction(newGameEvent);
Button restart = new Button();
restart.setText("Restart");
restart.setTranslateX(100);
restart.setTranslateY(765);
root.getChildren().add(restart);
EventHandler<ActionEvent> resetGameEvent = ae -> {
primaryStage.setScene(scene);
primaryStage.show();
timer.stop();
timer.restart();
};
restart.setOnAction(resetGameEvent);
}
public static void main(String[] args) {
launch(args);
}
}
I have tried to reset primaryStage with the same scene and call the show method but that does not restart the game, it does not do anything.
Any ideas on how this may be able to be implemented?
I am trying to add close option in tabpane like a browser have it. Could you please tell me how to add closing feature in this tabpane? I tried this line but didn't solve my problem tabPane.setTabClosingPolicy(TabClosingPolicy.ALL_TABS);
I am using javafx jfoenix library for UI.
package tabsDemo;
import java.awt.Color;
import java.awt.Dimension;
import java.math.BigInteger;
import java.security.SecureRandom;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane.TabClosingPolicy;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTabPane;
import com.jfoenix.controls.JFXTextField;
public class TabsDemo extends Application {
private String msg = "Tab 0";
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Tabs");
Group root = new Group();
Scene scene = new Scene(root, 800, 600);
JFXButton b1 = new JFXButton();
b1.setId("back");
b1.setGraphic(new ImageView(new Image("/tabsDemo/back.png")));
b1.setMinWidth(20);
b1.setMinHeight(20);
b1.setMaxWidth(20);
b1.setMaxHeight(20);
JFXButton b2 = new JFXButton();
b2.setId("farword");
b2.setGraphic(new ImageView(new Image("/tabsDemo/forward.png")));
b2.setMinWidth(20);
b2.setMinHeight(20);
b2.setMaxWidth(20);
b2.setMaxHeight(20);
JFXButton b3 = new JFXButton();
b3.setId("refresh");
b3.setGraphic(new ImageView(new Image("/tabsDemo/refresh.png")));
b3.setMinWidth(20);
b3.setMinHeight(20);
b3.setMaxWidth(20);
b3.setMaxHeight(20);
JFXTextField t1 = new JFXTextField();
t1.setMinWidth(100);
// t1.setPrefWidth(900);
// t1.setMaxWidth(1000);
t1.setMinHeight(30);
t1.setMaxHeight(30);
GridPane gridPane = new GridPane();
gridPane.setHgap(4);
gridPane.add(b1, 0, 0);
gridPane.add(b2, 1, 0);
gridPane.add(b3, 2, 0);
gridPane.add(t1, 3, 0);
gridPane.setHgrow(t1, Priority.ALWAYS);
BorderPane borderPane = new BorderPane();
borderPane.setTop(gridPane);
JFXTabPane tabPane = new JFXTabPane();
Tab tab1 = new Tab();
tab1.setText("Tab1");
tab1.setContent(borderPane);
VBox vbox = new VBox();
vbox.getChildren().addAll(new JFXButton("B1"), new JFXButton("B2"), new JFXButton("B3"), new JFXButton("B4"));
Tab tab2 = new Tab();
tab2.setText("Tab2");
tab2.setContent(vbox);
tabPane.getTabs().addAll(tab1, tab2);
tabPane.setPrefSize(800, 600);
// I add here the closing option for tab but it's not working
tabPane.setTabClosingPolicy(TabClosingPolicy.ALL_TABS);
SingleSelectionModel<Tab> selectionModel = tabPane.getSelectionModel();
selectionModel.select(1);
JFXButton button = new JFXButton("Add New Tab");
button.setOnMouseClicked((o) -> {
Tab temp = new Tab();
int count = tabPane.getTabs().size();
temp.setText(msg + count);
temp.setContent(new Label("Tab 0" + count));
tabPane.getTabs().add(temp);
});
borderPane.setRight(button);
tabPane.setMaxSize(800, 600);
/*
* HBox hbox = new HBox(); hbox.getChildren().addAll(button, tabPane);
* hbox.setSpacing(50); hbox.setAlignment(Pos.CENTER);
* hbox.setStyle("-fx-padding:20");
*/
BorderPane rootBorderpane = new BorderPane();
rootBorderpane.setCenter(tabPane);
root.getChildren().addAll(rootBorderpane);
scene.getStylesheets().add(TabsDemo.class.getResource("jfoenix-components.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.setTitle("JFX Tabs Demo");
}
private SecureRandom random = new SecureRandom();
public String nextSessionId() {
return new BigInteger(50, random).toString(16);
}
}
Thank you!
Your JFXTabPane is a custom component from the JFoenix library, so there's no guarantee that all standard JavaFX settings, such as TabClosingPolicy in your case, are fully implemented.
I checked their GitHub repository and looks like there's an unresolved issue regarding this missing feature. Someone forked the repository, apparently resolved the problem and submitted a pull request, but it's still has an open status. In other words, the changes are not included in the current build, so the bug still exists.
My suggestion is to stick with the standard JavaFX components, which should provide everything you need to create simple applications, including the closeable tabs, which are are working well as presented in the following example.
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("JFX Tabs Demo");
TabPane tabPane = new TabPane();
tabPane.setTabClosingPolicy(TabClosingPolicy.ALL_TABS);
tabPane.getTabs().add(new Tab("Test"));
Scene scene = new Scene(tabPane, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
I'm writing a small game. And i want a pause menu over the blured game menu to be shown when esc is pressed in the middle of the game.
What I do is that i make a new scene which has a stackPane wrapping the past root and the pause menu root and then i set the opacity of the past root 0.4 .
Then when the resume button in the pause menu is clicked i change the opacity back to 1 and set the past scene on stage but then its freezed. does anyone know why? can anyone help me achieve this?
Here is the part i make the new scene and then i put this on stage:
StackPane wrapper = new StackPane();
previousScene = main.getPrimaryStage().getScene();
previousScene.getRoot().setOpacity(.4);
vBox.setId("pausedWrapper");
wrapper.getChildren().add(previousScene.getRoot());
wrapper.getChildren().add(vBox);
scene = new Scene(wrapper, 1200, 700);
return scene;
Here is the part i change it back to where it was:
resumeGame.setOnAction(event -> {
System.out.println("game resumed!");
previousScene.getRoot().setOpacity(1);
main.getPrimaryStage().setScene(previousScene);
});
But then it does not work and the opacity does not change back to normal and the strange thing is when i check the sound on box the music is played but the box does not get checked like everything works but the view is freezed.
A node cannot be part of two different scene graphs. This happens in your code to the root of previousScene, because it is part of both previousScene and the new scene you create in your first block of code. Most likely what is happening is that it is removed from the first scene when you add it to the second (though it is hard to tell from the code you posted).
Consider instead using a Popup to display the pauseMenu on top of the existing window, or just use a modal Stage with undecorated StageStyle, as in the following SSCCE:
import javafx.animation.Animation;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Modality;
import javafx.stage.Popup;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
public class PauseExample extends Application {
#Override
public void start(Stage primaryStage) {
Rectangle rect = new Rectangle(50, 50, 50, 50);
rect.setFill(Color.CORAL);
TranslateTransition animation = createAnimation(rect);
Button pauseButton = new Button("Pause");
Pane pane = new Pane(rect);
pane.setMinSize(600, 150);
BorderPane root = new BorderPane(pane, null, null, pauseButton, new Label("This is\nthe main\nscene"));
pauseButton.setOnAction(e -> {
animation.pause();
root.setEffect(new GaussianBlur());
VBox pauseRoot = new VBox(5);
pauseRoot.getChildren().add(new Label("Paused"));
pauseRoot.setStyle("-fx-background-color: rgba(255, 255, 255, 0.8);");
pauseRoot.setAlignment(Pos.CENTER);
pauseRoot.setPadding(new Insets(20));
Button resume = new Button("Resume");
pauseRoot.getChildren().add(resume);
Stage popupStage = new Stage(StageStyle.TRANSPARENT);
popupStage.initOwner(primaryStage);
popupStage.initModality(Modality.APPLICATION_MODAL);
popupStage.setScene(new Scene(pauseRoot, Color.TRANSPARENT));
resume.setOnAction(event -> {
root.setEffect(null);
animation.play();
popupStage.hide();
});
popupStage.show();
});
BorderPane.setAlignment(pauseButton, Pos.CENTER);
BorderPane.setMargin(pauseButton, new Insets(5));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
private TranslateTransition createAnimation(Rectangle rect) {
TranslateTransition animation = new TranslateTransition(Duration.seconds(1), rect);
animation.setByX(400);
animation.setCycleCount(Animation.INDEFINITE);
animation.setAutoReverse(true);
animation.play();
return animation;
}
public static void main(String[] args) {
launch(args);
}
}
I am trying to make an application where an object moves across the screen, only changing its x value. I am not getting any errors but it is not working when I run it. the objects are showing but the rectangle is not animating. I am not sure what to do
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private BorderPane root;
private Stage stage;
private Scene scene;
private AnchorPane anchorPane;
private Rectangle player;
private Circle circles;
private Button up, down;
private Timeline timer;
#Override
public void start(Stage primaryStage) {
stage = primaryStage;
root = new BorderPane();
scene = new Scene(root, 600, 500); //width and height of application
stage.setScene(scene);
stage.setTitle("Hoop runner"); //text for the title bar of the window
scene.getStylesheets().add("hoop.css");
anchorPane = new AnchorPane();
anchorPane.setMaxHeight(500);
anchorPane.setMinHeight(500);
anchorPane.setMinWidth(600);
anchorPane.setMaxWidth(600);
player = new Rectangle();
circles = new Circle();
up = new Button("˄");
down = new Button("˅");
root.setCenter(new VBox(new VBox(circles),
new HBox(player)));
root.setCenter(anchorPane);
player.setX(5);
player.setY(380);
player.setHeight(80);
player.setWidth(120);
anchorPane.getChildren().add(player);
timer = new Timeline(Animation.INDEFINITE);
timer.setCycleCount(Animation.INDEFINITE);
timer.getKeyFrames().add(new KeyFrame(Duration.INDEFINITE, event -> maverick()));
timer.play();
stage.show();
}
private void maverick() {
timer.play();
System.out.println("hi");
player.setX(player.getX() + 2);
timer.play();
}
public static void main(String[] args) {
launch(args);
}
}
Use setTranslateX instead of setX if you want to position or animate Nodes.
Change timer = new Timeline(Animation.INDEFINITE) to timer = new Timeline().
Change new KeyFrame(Duration.INDEFINITE, event -> maverick()) to new KeyFrame(Duration.millis(500), event -> maverick()) (for example).
A KeyFrame has to have a point in time. If, like the example, you use 500ms, then the KeyFrame's EventHandler will get called after 500ms. Since your TimeLine has a cycleCount of INDEFINITE, it will loop and execute the KeyFrame every 500ms.
I am using RotateTransiton to rotate a line, but it seems to rotate through center of the line. I would like to rotate it with pivot as one end of the line. How to do this?
The RotateTransition works by changing the rotate property, which - as you have observed - defines a rotation around the center of the Node.
If you want to rotate around a different point, define a Rotate transform, set its pivot, add it to the line's list of transforms, and use a Timeline to manipulate its angle.
Here's an example:
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
public class RotateLineAboutEnd extends Application {
#Override
public void start(Stage primaryStage) {
Line line = new Line(200, 200, 200, 350);
Pane pane = new Pane(line);
Rotate rotation = new Rotate();
rotation.pivotXProperty().bind(line.startXProperty());
rotation.pivotYProperty().bind(line.startYProperty());
line.getTransforms().add(rotation);
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(rotation.angleProperty(), 0)),
new KeyFrame(Duration.seconds(1), new KeyValue(rotation.angleProperty(), 360)));
Button button = new Button("Rotate");
button.setOnAction(evt -> timeline.play());
button.disableProperty().bind(timeline.statusProperty().isEqualTo(Animation.Status.RUNNING));
HBox controls = new HBox(button);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(12));
BorderPane root = new BorderPane(pane, null, null, controls, null);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
(on JavaFX 2.2 and JavaFX 8)
The best way in my opinion is translating the layoutBounds of the node (which contains the pivot point) and translating the node itself the opposite way.
An example:
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
primaryStage.setScene(new Scene(root, 140, 140));
Rectangle rect = new Rectangle(1, 1, 40, 40);
// comment movePivot to get the default rotation
movePivot(rect, -20, -20);
RotateTransition rt = new RotateTransition(Duration.seconds(4),rect);
rt.setToAngle(720);
rt.setCycleCount(Timeline.INDEFINITE);
rt.setAutoReverse(true);
rt.play();
primaryStage.show();
}
// this is the function you want
private void movePivot(Node node, double x, double y){
node.getTransforms().add(new Translate(-x,-y));
node.setTranslateX(x); node.setTranslateY(y);
}
public static void main(String[] args) {
launch(args);
}
}