How to slide the node when added to an HBox in JavaFX? - animation

I am creating a panel with an HBox that has 2 or 3 children. The first child is a VBox with icons and stays on the left side of the screen, when I hover the mouse over the VBox (1st child) I want to add my second child that is a VBox with buttons. My third child is an AnchorPane that supports my content.
My issue is, how do I add my second VBox to the HBox with a transition slide (left to right)?
The finality of hide my second child (VBox with buttons) was at increase my width content of third child (content AnchorPane);
Example Code
public class Test extends Application {
#Override
public void start(Stage primaryStage) {
HBox root = new HBox();
Scene scene = new Scene(root, 300, 250);
VBox c1 = new VBox();
ImageView i1 = new ImageView(new Image(getClass().getResourceAsStream("home/home.png")));
ImageView i2 = new ImageView(new Image(getClass().getResourceAsStream("home/contactos.png")));
ImageView i3 = new ImageView(new Image(getClass().getResourceAsStream("home/info.png")));
c1.getChildren().addAll(i1, i2, i3);
VBox c2 = new VBox();
Button b1 = new Button("home opção1");
Button b2 = new Button("home opção2");
c2.getChildren().addAll(b1, b2);
AnchorPane c3 = new AnchorPane();
// Set backgrounds
c1.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
c2.setBackground(new Background(new BackgroundFill(Color.GRAY, CornerRadii.EMPTY, Insets.EMPTY)));
c3.setBackground(new Background(new BackgroundFill(Color.rgb(255,255,148), CornerRadii.EMPTY, Insets.EMPTY)));
root.getChildren().addAll(c1, c3);
c1.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
root.getChildren().add(1, c2);
// Fault transation slide
}
});
c1.setOnMouseExited(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
root.getChildren().remove(1);
}
});
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

Unfortunately there is no predefined approach for getting the sliding effect in JavaFX. You can get that effect by understanding/implementing the concept of clipping. Internally the same concept is used for ScrollPane and other controls where we need to clip the content.
The idea is that, we clip the layout that we want to slide and change its value gradually by using a timeline. A more detailed explanation regarding this can be found in my blog. (I wrote this blog back in early 2012, so the code may look a bit obsolete ;) but you should get the concept)
I quickly worked the below demo for your requirement. This should help you to get some idea.
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.stream.Stream;
public class SlideMenuDemo extends Application {
/* THE ONLY DRAWBACK OF THIS APPROACH IS YOU HAVE TO KNOW THE WIDTH OF THE SUBMENU AHEAD*/
double subMenuWidth = 140;
HBox root;
VBox subMenu;
VBox menuPane;
StackPane subMenuPane;
private Rectangle clipRect;
private Timeline timelineHide;
private Timeline timelineShow;
private final Color[] colors = {Color.RED, Color.BLUE, Color.GREEN, Color.LAVENDER, Color.PINK};
private final String[] shapes = {"cirlce", "triangle", "square", "rectangle"};
#Override
public void start(Stage primaryStage) {
root = new HBox();
Scene scene = new Scene(root, 400, 300);
primaryStage.setTitle("Sliding Demo");
primaryStage.setScene(scene);
primaryStage.show();
// Main menu pane
menuPane = new VBox();
menuPane.setStyle("-fx-background-color:#DDDDDD;");
Stream.of(shapes).forEach(shape -> menuPane.getChildren().addAll(buildMenuButton(shape, Color.BLACK, null)));
// Sub menu pane
subMenu = new VBox();
subMenuPane = new StackPane(subMenu);
subMenuPane.setMinWidth(0);
subMenuPane.setPrefWidth(0);
StackPane subMenuContainer = new StackPane(subMenuPane);
subMenuContainer.setStyle("-fx-background-color:#AAAAAA");
HBox menuBox = new HBox(menuPane, subMenuContainer);
menuBox.setOnMouseExited(e -> hideSubMenu());
// Content Pane
StackPane contentPane = new StackPane(new Text("Hello Slide Checking"));
contentPane.setAlignment(Pos.TOP_LEFT);
contentPane.setPadding(new Insets(15));
HBox.setHgrow(contentPane, Priority.ALWAYS);
contentPane.setStyle("-fx-background-color:#0000FF70,#FFFFFF;-fx-background-insets:0,1;");
root.getChildren().addAll(menuBox, contentPane);
setAnimation();
}
private void hideSubMenu() {
timelineHide.play();
}
private void showSubMenu() {
timelineShow.play();
}
private void setAnimation() {
clipRect = new Rectangle();
clipRect.setWidth(0);
clipRect.heightProperty().bind(root.heightProperty());
clipRect.translateXProperty().set(subMenuWidth);
subMenuPane.setClip(clipRect);
subMenuPane.translateXProperty().set(-subMenuWidth);
/* Event handler hide is finished. */
EventHandler<ActionEvent> onFinished = e -> {
menuPane.getChildren().stream().forEach(n -> n.setStyle(null));
subMenu.getChildren().clear();
};
timelineShow = new Timeline();
timelineHide = new Timeline();
/* Animation for show. */
timelineShow.setCycleCount(1);
final KeyValue kvDwn1a = new KeyValue(clipRect.widthProperty(), subMenuWidth);
final KeyValue kvDwn1b = new KeyValue(subMenuPane.prefWidthProperty(), subMenuWidth);
final KeyValue kvDwn1c = new KeyValue(subMenuPane.minWidthProperty(), subMenuWidth);
final KeyValue kvDwn2 = new KeyValue(clipRect.translateXProperty(), 0);
final KeyValue kvDwn3 = new KeyValue(subMenuPane.translateXProperty(), 0);
final KeyFrame kfDwn = new KeyFrame(Duration.millis(200), kvDwn1a, kvDwn1b, kvDwn1c, kvDwn2, kvDwn3);
timelineShow.getKeyFrames().add(kfDwn);
/* Animation for hide. */
timelineHide.setCycleCount(1);
final KeyValue kvUp1a = new KeyValue(clipRect.widthProperty(), 0);
final KeyValue kvUp1b = new KeyValue(subMenuPane.prefWidthProperty(), 0);
final KeyValue kvUp1c = new KeyValue(subMenuPane.minWidthProperty(), 0);
final KeyValue kvUp2 = new KeyValue(clipRect.translateXProperty(), subMenuWidth);
final KeyValue kvUp3 = new KeyValue(subMenuPane.translateXProperty(), -subMenuWidth);
final KeyFrame kfUp = new KeyFrame(Duration.millis(200), onFinished, kvUp1a, kvUp1b, kvUp1c, kvUp2, kvUp3);
timelineHide.getKeyFrames().add(kfUp);
}
private StackPane buildMenuButton(String type, Color color, String text) {
double size = 50;
double sSize = (size / 5) * 4;
double hSize = sSize / 2;
StackPane menuButton = new StackPane();
menuButton.setPadding(new Insets(0, 5, 0, 5));
menuButton.setMaxHeight(size);
menuButton.setMinHeight(size);
Node shape = null;
switch (type) {
case "triangle":
StackPane s = new StackPane();
s.setPrefSize(sSize, sSize);
s.setMaxSize(sSize, sSize);
s.setBackground(new Background(new BackgroundFill(color, CornerRadii.EMPTY, Insets.EMPTY)));
s.setStyle("-fx-shape:\"M0 1 L1 1 L.5 0 Z\";");
s.setPadding(new Insets(hSize));
shape = s;
break;
case "square":
shape = new Rectangle(sSize, sSize, color);
break;
case "rectangle":
shape = new Rectangle(sSize, hSize, color);
break;
default:
shape = new Circle(hSize, color);
break;
}
HBox hb = new HBox(shape);
hb.setAlignment(Pos.CENTER_LEFT);
if (text != null) {
hb.setSpacing(10);
hb.getChildren().add(new Label(text));
}
menuButton.getChildren().add(hb);
if (text == null) {
// Main menu button
menuButton.setOnMouseEntered(e -> {
menuPane.getChildren().stream().forEach(n -> n.setStyle(null));
subMenu.getChildren().clear();
menuButton.setStyle("-fx-background-color:#AAAAAA;");
Stream.of(colors).forEach(c -> subMenu.getChildren().addAll(buildMenuButton(type, c, c.toString())));
if (subMenuPane.getWidth() == 0) {
showSubMenu();
}
});
} else {
// Sub menu button
menuButton.setPrefWidth(subMenuWidth);
menuButton.setMinWidth(subMenuWidth);
menuButton.setOnMouseEntered(e -> menuButton.setStyle("-fx-background-color:#777777;"));
menuButton.setOnMouseExited(e -> menuButton.setStyle(null));
}
return menuButton;
}
public static void main(String[] args) {
launch(args);
}
}

Related

How to fill a shape with an image JavaFX

I am currently trying to create a game with falling "asteroids" that a ship has to dodge. I have currently created a pane that is a large circle stacked with multiple smaller circles. Unfortunately, the pane's "hitbox" is a square/rectangle, rather than a circle, which is frustrating. The other alternate way of fixing this would be to create a circle and fill it with an image of an asteroid, but I couldn't find this anywhere. I know of:
circle.setFill(Color.WHATEVER);
but i would like for it to look like an asteroid, not just a simple grey circle falling. To sum it up, how can I set an image to a shape? Or, if there is a way to change the hitbox of a stackpane, a way to do that would also help fix my issue!
Any help is greatly appreciated! Thanks!
EDIT:
Below are the 4 files that will make a simplified version of my game work with the same problem:
DodgerRemake:
package javafxapplication6;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class DodgerRemake extends Application
{
private Spaceship thisSpaceShip = new Spaceship();
private BoxPane gamePane = new BoxPane(thisSpaceShip);
BorderPane pane = new BorderPane();
private BooleanProperty upPressed = new SimpleBooleanProperty();
private BooleanProperty rightPressed = new SimpleBooleanProperty();
private BooleanProperty downPressed = new SimpleBooleanProperty();
private BooleanProperty leftPressed = new SimpleBooleanProperty();
private BooleanBinding anyPressed = upPressed.or(rightPressed).or(downPressed).or(leftPressed);
protected BorderPane getPane()
{
StackPane centerPane = new StackPane();
centerPane.getChildren().add(gamePane);
pane.setCenter(centerPane);
Button thisButton = new Button("Start");
thisButton.setAlignment(Pos.CENTER);
thisButton.setFocusTraversable(false);
thisButton.setOnAction(new startGame());
pane.setLeft(thisButton);
return pane;
}
#Override
public void start(Stage primaryStage) {
// Create a scene and place it in the stage
Scene scene = new Scene(getPane(), 960, 800);
primaryStage.setTitle("Dodger"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.setResizable(false);
primaryStage.show(); // Display the stage
scene.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.UP) {
upPressed.set(true);
}
if (e.getCode() == KeyCode.DOWN) {
downPressed.set(true);
}
if (e.getCode() == KeyCode.RIGHT) {
rightPressed.set(true);
}
if (e.getCode() == KeyCode.LEFT) {
leftPressed.set(true);
}
});
scene.setOnKeyReleased(e -> {
if (e.getCode() == KeyCode.UP) {
upPressed.set(false);
}
if (e.getCode() == KeyCode.DOWN) {
downPressed.set(false);
}
if (e.getCode() == KeyCode.RIGHT) {
rightPressed.set(false);
}
if (e.getCode() == KeyCode.LEFT) {
leftPressed.set(false);
}
});
AnimationTimer timer = new AnimationTimer()
{
#Override
public void handle(long timestamp) {
if (upPressed.get()){
gamePane.moveShipUp();
}
if (downPressed.get()){
gamePane.moveShipDown();
}
if (rightPressed.get()) {
gamePane.moveShipRight();
}
if (leftPressed.get()) {
gamePane.moveShipLeft();
}
}
};
anyPressed.addListener((obs, wasPressed, isNowPressed) ->
{
if (isNowPressed) {
timer.start();
} else {
timer.stop();
}
});
}
class startGame implements EventHandler<ActionEvent>
{
#Override
public void handle(ActionEvent e)
{
gamePane.addSpaceship();
gamePane.startGame();
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
BoxPane:
package javafxapplication6;
import java.util.Random;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.scene.layout.Pane;
import javafx.util.Duration;
class BoxPane extends Pane
{
private Spaceship spaceShip;
private Timeline asteroids;
private double x;
private double y;
BoxPane(Spaceship thisSpaceShip)
{
spaceShip = thisSpaceShip;
spaceShip.setRotate(180);
}
public void addSpaceship()
{
getChildren().add(spaceShip);
x = 400 - spaceShip.getWidth()/2;
y = 740;
spaceShip.setTranslateX(x);
spaceShip.setTranslateY(y);
}
public void moveShipLeft()
{
if(x > 0)
{
spaceShip.setTranslateX(x-5);
x = x-5;
}
else
{
spaceShip.setTranslateX(0);
x = 0;
}
}
public void moveShipRight()
{
if(x+spaceShip.getWidth() < getWidth())
{
spaceShip.setTranslateX(x+5);
x = x+5;
}
else
{
spaceShip.setTranslateX(getWidth() - spaceShip.getWidth());
x = getWidth() - spaceShip.getWidth();
}
}
public void moveShipUp()
{
if(y > 0)
{
spaceShip.setTranslateY(y-5);
y = y-5;
}
else
{
spaceShip.setTranslateY(0);
y = 0;
}
}
public void moveShipDown()
{
if(y+spaceShip.getHeight() < getHeight())
{
spaceShip.setTranslateY(y+5);
y = y+5;
}
else
{
spaceShip.setTranslateY(getHeight() - spaceShip.getHeight());
y = getHeight() - spaceShip.getHeight();
}
}
public void startGame()
{
//addSpaceship();
asteroids = new Timeline(new KeyFrame(Duration.seconds(.5), e -> displayAsteroid()));
asteroids.setCycleCount(Timeline.INDEFINITE);
asteroids.play();
}
public void displayAsteroid()
{
Asteroid asteroid = new Asteroid();
getChildren().add(asteroid);
Random rand = new Random();
int randomNum = rand.nextInt((int) (getWidth() - asteroid.getWidth()));
asteroid.setY(-200);
asteroid.setX(randomNum);
asteroid.setTranslateY(asteroid.getY());
asteroid.setTranslateX(asteroid.getX());
Timeline timeline = new Timeline();
KeyFrame keyFrame = new KeyFrame(Duration.millis(50), event -> {
if(asteroid.getY() > getHeight()|| asteroid.getBoundsInParent().intersects(spaceShip.getBoundsInParent()))
{
timeline.stop();
getChildren().remove(asteroid);
}
else
{
asteroid.setY(asteroid.getY()+5);
asteroid.setTranslateY(asteroid.getY());
}
});
timeline.setCycleCount(Animation.INDEFINITE);
timeline.getKeyFrames().add(keyFrame);
timeline.play();
}
}
asteroid class:
package javafxapplication6;
import javafx.geometry.Pos;
import java.util.Random;
import java.util.Vector;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Ellipse;
public class Asteroid extends StackPane
{
private Ellipse asteroid = new Ellipse();
private Ellipse hole1 = new Ellipse();
private Ellipse hole2 = new Ellipse();
private Ellipse hole3 = new Ellipse();
private int speed;
private double y = 0;
private double x = 0;
Asteroid()
{
Random rand = new Random();
int asteroidNum = rand.nextInt(10);
if(asteroidNum < 6)
{
asteroidNum = 0;
}
else if(asteroidNum < 8)
{
asteroidNum = 1;
}
else
{
asteroidNum = 2;
}
StackPane thisAsteroid = new StackPane();
VBox vbox = new VBox();
HBox hbox = new HBox();
vbox.setAlignment(Pos.CENTER);
hbox.setAlignment(Pos.CENTER);
asteroid.setFill(Color.GREY);
asteroid.setStroke(Color.DIMGREY);
hole1.setFill(Color.DIMGREY);
hole1.setStroke(Color.BLACK);
hole2.setFill(Color.DIMGREY);
hole2.setStroke(Color.BLACK);
hole3.setFill(Color.DIMGREY);
hole3.setStroke(Color.BLACK);
switch(asteroidNum)
{
case 0: asteroid1();
vbox.setSpacing(10);
hbox.setSpacing(10);
break;
case 1: asteroid2();
vbox.setSpacing(15);
hbox.setSpacing(15);
break;
case 2: asteroid3();
vbox.setSpacing(25);
hbox.setSpacing(25);
break;
}
int holeLayout = rand.nextInt(3);
switch(holeLayout)
{
case 0: hbox.getChildren().addAll(hole1, hole2);
vbox.getChildren().addAll(hole3, hbox);
thisAsteroid.getChildren().addAll(asteroid, vbox);
break;
case 1: vbox.getChildren().addAll(hole2, hole3);
hbox.getChildren().addAll(vbox, hole1);
thisAsteroid.getChildren().addAll(asteroid, hbox);
break;
case 2: vbox.getChildren().addAll(hole1, hole3);
hbox.getChildren().addAll(hole2, vbox);
thisAsteroid.getChildren().addAll(asteroid, hbox);
break;
case 3: hbox.getChildren().addAll(hole1, hole2);
vbox.getChildren().addAll(hbox, hole3);
thisAsteroid.getChildren().addAll(asteroid, vbox);
break;
}
this.getChildren().add(thisAsteroid);
}
public void asteroid1()
{
speed = 10;
asteroid.setRadiusX(40);
asteroid.setRadiusY(35);
asteroid.setStrokeWidth(3);
hole1.setRadiusX(7);
hole1.setRadiusY(8);
hole2.setRadiusX(9);
hole2.setRadiusY(6);
hole3.setRadiusX(6);
hole3.setRadiusY(5);
}
public void asteroid2()
{
speed = 6;
asteroid.setRadiusX(60);
asteroid.setRadiusY(50);
asteroid.setStrokeWidth(5);
hole1.setRadiusX(10);
hole1.setRadiusY(12);
hole2.setRadiusX(12);
hole2.setRadiusY(10);
hole3.setRadiusX(14);
hole3.setRadiusY(13);
}
public void asteroid3()
{
speed = 4;
asteroid.setRadiusX(100);
asteroid.setRadiusY(90);
asteroid.setStrokeWidth(8);
hole1.setRadiusX(20);
hole1.setRadiusY(18);
hole2.setRadiusX(18);
hole2.setRadiusY(22);
hole3.setRadiusX(23);
hole3.setRadiusY(19);
}
public void setY(double nY)
{
y = nY;
}
public void setX(double nX)
{
x = nX;
}
public double getX()
{
return x;
}
public double getY()
{
return y;
}
public int getSpeed()
{
return speed;
}
}
spaceship class:
package javafxapplication6;
import javafx.geometry.Pos;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.Rectangle;
public class Spaceship extends StackPane
{
private Rectangle leftWingBottom = new Rectangle();
private Rectangle leftWingGun = new Rectangle();
private Rectangle leftWingDesign = new Rectangle();
private Rectangle leftWingAttachment = new Rectangle();
private Rectangle leftTransparent = new Rectangle();
private Rectangle rightWingBottom = new Rectangle();
private Rectangle rightWingGun = new Rectangle();
private Rectangle rightWingDesign = new Rectangle();
private Rectangle rightWingAttachment = new Rectangle();
private Rectangle rightTransparent = new Rectangle();
private Rectangle centerCompartmentBottom = new Rectangle();
private Rectangle centerCompartmentDesign = new Rectangle();
private Ellipse centerCompartmentTop = new Ellipse();
Spaceship()
{
HBox ship = new HBox();
VBox leftWing = new VBox();
VBox rightWing = new VBox();
VBox leftAttachment = new VBox();
VBox rightAttachment = new VBox();
StackPane leftWingStack = new StackPane();
StackPane rightWingStack = new StackPane();
StackPane centerCompartmentShip = new StackPane();
leftTransparent.setFill(Color.TRANSPARENT);
rightTransparent.setFill(Color.TRANSPARENT);
// creates the right wing
rightWingBottom.setHeight(30);
rightWingBottom.setWidth(8);
rightWingBottom.setStrokeWidth(1);
rightWingDesign.setHeight(25);
rightWingDesign.setWidth(3);
rightWingStack.setAlignment(Pos.CENTER);
rightWingStack.getChildren().addAll(rightWingBottom, rightWingDesign);
rightWingGun.setHeight(5);
rightWingGun.setWidth(2);
rightWing.setAlignment(Pos.TOP_CENTER);
rightWing.getChildren().addAll(rightWingStack, rightWingGun);
// creates the left wing and gun
leftWingBottom.setHeight(30);
leftWingBottom.setWidth(8);
leftWingBottom.setStrokeWidth(1);
leftWingDesign.setHeight(25);
leftWingDesign.setWidth(3);
leftWingStack.setAlignment(Pos.CENTER);
leftWingStack.getChildren().addAll(leftWingBottom, leftWingDesign);
leftWingGun.setHeight(5);
leftWingGun.setWidth(2);
leftWing.setAlignment(Pos.TOP_CENTER);
leftWing.getChildren().addAll(leftWingStack, leftWingGun);
// attaches the cockpit and right wing together
rightTransparent.setHeight(5);
rightTransparent.setWidth(5);
rightWingAttachment.setHeight(3);
rightWingAttachment.setWidth(5);
rightAttachment.setAlignment(Pos.TOP_CENTER);
rightAttachment.getChildren().addAll(rightTransparent, rightWingAttachment);
// attaches the cockpit and left wing together
leftTransparent.setHeight(5);
leftTransparent.setWidth(5);
leftWingAttachment.setHeight(3);
leftWingAttachment.setWidth(5);
leftAttachment.setAlignment(Pos.TOP_CENTER);
leftAttachment.getChildren().addAll(leftTransparent, leftWingAttachment);
// creates the cockpit
centerCompartmentBottom.setHeight(25);
centerCompartmentBottom.setWidth(20);
centerCompartmentBottom.setStrokeWidth(2);
centerCompartmentTop.setRadiusX(10);
centerCompartmentTop.setRadiusY(25);
centerCompartmentTop.setStrokeWidth(2);
centerCompartmentDesign.setHeight(25);
centerCompartmentDesign.setWidth(5);
centerCompartmentShip.setAlignment(Pos.TOP_CENTER);
centerCompartmentShip.getChildren().addAll(centerCompartmentTop, centerCompartmentBottom, centerCompartmentDesign);
rightWingBottom.setFill(Color.WHITE);
rightWingBottom.setStroke(Color.GREY);
leftWingBottom.setFill(Color.WHITE);
leftWingBottom.setStroke(Color.GREY);
rightWingGun.setFill(Color.WHITE);
leftWingGun.setFill(Color.WHITE);
rightWingDesign.setFill(Color.TRANSPARENT);
leftWingDesign.setFill(Color.TRANSPARENT);
rightWingAttachment.setFill(Color.WHITE);
rightWingAttachment.setStroke(Color.GREY);
leftWingAttachment.setFill(Color.WHITE);
leftWingAttachment.setStroke(Color.GREY);
centerCompartmentBottom.setFill(Color.WHITE);
centerCompartmentBottom.setStroke(Color.GREY);
centerCompartmentTop.setFill(Color.WHITE);
centerCompartmentTop.setStroke(Color.GREY);
centerCompartmentDesign.setFill(Color.TRANSPARENT);
// adds everything to final hbox
ship.getChildren().addAll(rightWing,
rightAttachment,
centerCompartmentShip,
leftAttachment,
leftWing);
// adds it to the SpaceShip object
this.getChildren().add(ship);
}
}
Hope this helps, thanks!

Animating a sprite with JavaFX on key input

I'm new to JavaFX but have a good understanding of object orientated Java. The following program is a combination of two examples, one that animates and moves shapes , the other animates an object on a mouse button press. Much of the functionality has been removed or changed for my needs.
I've searched through many examples but haven't found one I fully understand regarding moving a sprite and animating on key press.In my program I'm sure that I'm not using the right classes to create the game object, even though with some tweaking I'm sure it could work.
I added some println functions to test the animation. The problem seems to be that the KeyFrame part in the walkSouth animation isn't working/playing.
My question is:
Should I be using different JavaFX classes to create the sprite-sheet animation?
Can this code be easily adapted to function so I can get a better understanding of how JavaFX works.
Here is the main class:
package testing;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private enum UserAction{
NONE,NORTH,SOUTH;
}
private static int APP_W = 200;
private static int APP_H = 200;
private Scene scene;
private UserAction action = UserAction.NONE;
private Timeline timeline = new Timeline();
private boolean running = true;
private int FPS = 60;
private Parent createContent(){
Pane root = new Pane();
root.setPrefSize(APP_W,APP_H);
Image cat_image = new Image("file:res/cata.png");
GameObject obj = new GameObject(cat_image,12,8);
obj.setTranslateX(100);
obj.setTranslateY(100);
KeyFrame frame = new KeyFrame(Duration.millis(1000/FPS), event -> {
if(!running)
return;
switch(action){
case NORTH:
obj.setTranslateY(obj.getTranslateY()-1);
break;
case SOUTH:
obj.walkSouth();
obj.setTranslateY(obj.getTranslateY()+1);
break;
case NONE:
obj.pauseAnimation();
break;
}
});
timeline.getKeyFrames().add(frame);
timeline.setCycleCount(Timeline.INDEFINITE);
root.getChildren().add(obj);
return root;
}
private void restartGame(){
stopGame();
startGame();
}
private void stopGame(){
running = false;
timeline.stop();
}
private void startGame(){
timeline.play();
running = true;
}
public void start(Stage primaryStage) throws Exception{
scene = new Scene(createContent());
scene.setOnKeyPressed(event -> {
switch (event.getCode()) {
case W:
action = UserAction.NORTH;
break;
case S:
action = UserAction.SOUTH;
break;
}
});
scene.setOnKeyReleased(event -> {
switch (event.getCode()) {
case W:
action = UserAction.NONE;
break;
case S:
action = UserAction.NONE;
break;
}
});
primaryStage.setTitle("Simple Animation");
primaryStage.setScene(scene);
primaryStage.show();
startGame();
}
public static void main(String[] args) {
launch(args);
}
}
Here is the GameObject class:
package testing;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Rectangle2D;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.util.Duration;
/**
* Created by matt on 26/02/17.
*/
public class GameObject extends Pane {
ObjectImage objectImage;
public GameObject( Image image, int columns, int rows){
objectImage = new ObjectImage(image,columns,rows);
getChildren().setAll(objectImage);
}
public void pauseAnimation(){
getChildren().setAll(objectImage);
objectImage.pauseAnimation();
}
public void walkSouth(){
getChildren().setAll(objectImage);
objectImage.walkSouth();
}
}
class ObjectImage extends ImageView {
private Rectangle2D[] clips;
private double width,height;
private Timeline timeline = new Timeline();
public ObjectImage(Image image,int columns,int rows){
width = image.getWidth()/columns;
height = image.getHeight()/rows;
clips = new Rectangle2D[rows*columns];
int count=0;
for(int row =0;row < rows;row++ )
for(int column = 0 ; column < columns; column++,count++)
clips[count] = new Rectangle2D(width * column, height * row,width,height);
setImage(image);
setViewport(clips[0]);
}
public void pauseAnimation(){
timeline.pause();
}
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
KeyFrame frame = new KeyFrame( Duration.millis(1000/5), event -> {
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
System.out.println("frame test");
});
timeline.setCycleCount(timeline.INDEFINITE);
timeline.getKeyFrames();
timeline.play();
}
}
This is the sprite-sheet image I'm working with
This is the outcome
As hinted by the comment, you did forget to add the frame in the walkSouth method. (Also you set each picture frame in walkSouth method to 200ms. Did you meant to change that?) Here's the code after changing:
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
KeyFrame frame = new KeyFrame( Duration.millis(1000/FPS), event -> {
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
});
timeline.setCycleCount(timeline.INDEFINITE);
timeline.getKeyFrames().add(frame); //This was the offending line.
timeline.play();
}
To answer your first question, yes there are many other options of classes you could use. Two options you could do is use the AnimationTimer or the Transition class. Here's a brief explanation for both (with code samples).
AnimationTimer is called every cycle or frame of rendering, which I believe you might be wanting this one:
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
AnimationTimer tmr = new AnimationTimer() {
#Override
public void handle(long nanoTime)
{
//nanoTime specifies the current time at the beginning of the frame in nano seconds.
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
}
};
tmr.start();
//call tmr.stop() to stop/ pause timer.
}
If however, you don't want an animation to be called each frame, you could extend Transition. A transition has an frac (fractional) value ranging from 0 to 1 that increases with respect to time. I'm not going to go into a whole lot detail, but I'm sure you could look up some more information on the api.
public void walkSouth(){
System.out.println("walk south test");
IntegerProperty count = new SimpleIntegerProperty(0);
Transition trans = new Transition() {
{
setCycleDuration(Duration.millis(1000 / 60.0));
}
#Override
public void interpolate(double frac)
{
if (frac != 1)
return;
//End of one cycle.
if(count.get() < 2) count.set(count.get()+1);
else count.set(0);
setViewport(clips[count.get()]);
}
};
trans.setCycleCount(Animation.INDEFINITE);
trans.playFromStart();
//Use trans.pause to pause, trans.stop to stop.
}

Javafx : How can I produce an animation with 3D path?

I'm new to JavaFX, and I encountered a problem when trying to deal with animation.
I know class PathTransition provides methods to move a node between two points along an arbitrary curve by class Path; but it seems that all the classes that are related to PathTransition, like Path and MoveTo and CubicCurveTo and including itself, can only work in the xy plane. What if I want to move a node in the yz plane or xz plane? I just can't find any information about it on the internet. Any advice would be appreciated.
As shown in Animation Basics, Animations, you can compose multiple kinds of Transition, including PathTransition, in a SequentialTransition or ParallelTransition. The approach is especially convenient when the equation of motion can be expressed in parametric form. Motion along a helix, shown below, uses a ParallelTransition to combine a PathTransition along a Circle with a Timeline along a line.
animation = new ParallelTransition(
createTransition(circle, arrow),
createTimeline(size / 2));
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.ParallelTransition;
import javafx.animation.PathTransition;
import javafx.animation.PathTransition.OrientationType;
import javafx.animation.Timeline;
import javafx.animation.Transition;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.effect.Bloom;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Shape;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
* #see http://stackoverflow.com/a/37370840/230513
*/
public class Helix extends Application {
private static final double SIZE = 300;
private final Content content = Content.create(SIZE);
public void play() {
content.animation.play();
}
private static final class Content {
private static final Duration DURATION = Duration.seconds(4);
private static final Color COLOR = Color.AQUA;
private static final double WIDTH = 3;
private final Group group = new Group();
private final Rotate rx = new Rotate(0, Rotate.X_AXIS);
private final Rotate ry = new Rotate(0, Rotate.Y_AXIS);
private final Rotate rz = new Rotate(0, Rotate.Z_AXIS);
private final Box xAxis;
private final Box yAxis;
private final Box zAxis;
private final Shape circle;
private final Shape arrow;
private final Animation animation;
private static Content create(double size) {
Content c = new Content(size);
c.group.getChildren().addAll(c.arrow, c.circle,
c.xAxis, c.yAxis, c.zAxis);
c.group.getTransforms().addAll(c.rz, c.ry, c.rx);
c.group.setTranslateX(-size / 2);
c.group.setTranslateY(-size / 2);
c.group.setTranslateZ(size / 2);
c.rx.setAngle(35);
c.ry.setAngle(-45);
return c;
}
private Content(double size) {
xAxis = createBox(size, WIDTH, WIDTH);
yAxis = createBox(WIDTH, size, WIDTH);
zAxis = createBox(WIDTH, WIDTH, size);
circle = createCircle(size);
arrow = createShape();
animation = new ParallelTransition(
createTransition(circle, arrow),
createTimeline(size / 2));
}
private Circle createCircle(double size) {
Circle c = new Circle(size / 4);
c.setFill(Color.TRANSPARENT);
c.setStroke(COLOR);
return c;
}
private Box createBox(double w, double h, double d) {
Box b = new Box(w, h, d);
b.setMaterial(new PhongMaterial(COLOR));
return b;
}
private Shape createShape() {
Shape s = new Polygon(0, 0, -10, -10, 10, 0, -10, 10);
s.setStrokeWidth(WIDTH);
s.setStrokeLineCap(StrokeLineCap.ROUND);
s.setStroke(COLOR);
s.setEffect(new Bloom());
return s;
}
private Transition createTransition(Shape path, Shape node) {
PathTransition t = new PathTransition(DURATION, path, node);
t.setOrientation(OrientationType.ORTHOGONAL_TO_TANGENT);
t.setCycleCount(Timeline.INDEFINITE);
t.setInterpolator(Interpolator.LINEAR);
return t;
}
private Timeline createTimeline(double size) {
Timeline t = new Timeline();
t.setCycleCount(Timeline.INDEFINITE);
t.setAutoReverse(true);
KeyValue keyX = new KeyValue(group.translateXProperty(), size);
KeyValue keyY = new KeyValue(group.translateYProperty(), size);
KeyValue keyZ = new KeyValue(group.translateZProperty(), -size);
KeyFrame keyFrame = new KeyFrame(DURATION.divide(2), keyX, keyY, keyZ);
t.getKeyFrames().add(keyFrame);
return t;
}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("JavaFX 3D");
Scene scene = new Scene(content.group, SIZE * 2, SIZE * 2, true);
primaryStage.setScene(scene);
scene.setFill(Color.BLACK);
scene.setOnMouseMoved((final MouseEvent e) -> {
content.rx.setAngle(e.getSceneY() * 360 / scene.getHeight());
content.ry.setAngle(e.getSceneX() * 360 / scene.getWidth());
});
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setFarClip(SIZE * 6);
camera.setTranslateZ(-3 * SIZE);
scene.setCamera(camera);
scene.setOnScroll((final ScrollEvent e) -> {
camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
});
primaryStage.show();
play();
}
public static void main(String[] args) {
launch(args);
}
}
In this related example, the yellow shapes follow a Timeline animation comprised of rotations of the cube's orthogonal axes, while also following a PathTransition along the edges of a cube's face.
cube.setOnMouseMoved(new EventHandler<MouseEvent>() {
#Override
public void handle(final MouseEvent e) {
animation = new Timeline();
animation.getKeyFrames().addAll(
new KeyFrame(new Duration(2000),
new KeyValue(cube.rx.angleProperty(), e.getY()),
new KeyValue(cube.ry.angleProperty(), -e.getX()),
new KeyValue(cube.rz.angleProperty(), e.getY())
));
animation.play();
}
});
…
pathBackFaceTransition = new PathTransition();
pathBackFaceTransition.setPath(rectangleBackFace);
…
pathFrontFaceTransition = new PathTransition();
pathFrontFaceTransition.setPath(rectangleFrontFace);
…
public void play() {
pathBackFaceTransition.play();
pathFrontFaceTransition.play();
}
Finally, you can simulate motion along the x, y and z axes by using a KeyValue that targets the scale and translate properties. Referring again to Animation Basics, the following change to TimelineEvents creates the illusion of a 3-D shape moving to and fro.
//create a keyValue with factory: scaling the circle 2times
KeyValue keyValueX = new KeyValue(stack.scaleXProperty(), 2);
KeyValue keyValueY = new KeyValue(stack.scaleYProperty(), 2);
KeyValue keyTransX = new KeyValue(stack.translateXProperty(), 100);
KeyValue keyTransY = new KeyValue(stack.translateYProperty(), 100);
//create a keyFrame, the keyValue is reached at time 2s
Duration duration = Duration.millis(2000);
//one can add a specific action when the keyframe is reached
EventHandler<ActionEvent> onFinished = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
//reset counter
i = 0;
}
};
KeyFrame keyFrame = new KeyFrame(duration, onFinished,
keyValueX, keyValueY, keyTransX, keyTransY);

Text area for progress bar

I would like to add a text area where the user can see some information that i can see in the console while the progress bar is updating.
How can i add the text area ?
Here is a sample of the code I have used to make the progress bar. Can i add below the progress bar the text area which should fill while computations are mare?
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class ProgressDialogExample extends Application {
static int option = 0;
static ProgressForm pForm = new ProgressForm();
#Override
public void start(Stage primaryStage) {
Button startButton = new Button("Start");
startButton.setOnAction(e -> {
// In real life this task would do something useful and return
// some meaningful result:
Task<Void> task = new Task<Void>() {
#Override
public Void call() throws InterruptedException {
for (int i = 0; i < 10; i++) {
updateProgress(i, 10);
Thread.sleep(200);
}
updateProgress(10, 10);
return null;
}
};
// binds progress of progress bars to progress of task:
pForm.activateProgressBar(task);
// in real life this method would get the result of the task
// and update the UI based on its value:
task.setOnSucceeded(event -> {
startButton.setDisable(false);
});
startButton.setDisable(true);
pForm.getDialogStage().show();
Thread thread = new Thread(task);
thread.start();
});
StackPane root = new StackPane(startButton);
Scene scene = new Scene(root, 350, 75);
primaryStage.setScene(scene);
primaryStage.show();
}
private int closeWindow() {
return option;
}
private static void setCloseWindow() {
// TODO Auto-generated method stub
option = 1;
}
public static class ProgressForm {
private final Stage dialogStage;
private final ProgressBar pb = new ProgressBar();
private final ProgressIndicator pin = new ProgressIndicator();
public ProgressForm() {
dialogStage = new Stage();
dialogStage.initStyle(StageStyle.UTILITY);
// dialogStage.setResizable(false);
// dialogStage.setWidth(400);
// dialogStage.setHeight(300);
// final VBox vbox = new VBox();
dialogStage.initModality(Modality.APPLICATION_MODAL);
final Button exitButton = new Button("Exit");
exitButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
pForm.getDialogStage().close();
setCloseWindow();
}
});
// PROGRESS BAR
pb.setProgress(-1F);
pin.setProgress(-1F);
final HBox hb = new HBox();
hb.setSpacing(5);
hb.setAlignment(Pos.CENTER);
hb.getChildren().addAll(pb, pin, exitButton);
Scene scene = new Scene(hb);
dialogStage.setScene(scene);
}
public void activateProgressBar(final Task<?> task) {
pb.progressProperty().bind(task.progressProperty());
pin.progressProperty().bind(task.progressProperty());
dialogStage.show();
}
public Stage getDialogStage() {
return dialogStage;
}
}
public static void main(String[] args) {
launch(args);
}
}
Have you heard of Label Labelled before use Label instead of the Text Node you want to use and write your text when you want to, when you want to show your Progressbars do
Label.setGraphic(myprogressbarNode);
Hope it helps
EDIT
I would like to add a text area where the user can see some information that i can see in the console while the progress bar is updating... Can i add below the progress bar the text area which should fill while computations are mare?
and i gave you a solution to it.
suppose you have your ProgressBar pb; and you are adding your ProgressBar to a StackPane you do not have to add the ProgressBar directly but rather add your Text which you will implement that by Label so presume your Label is lb this is how your code will look like
StackPane sp; // parent
ProgressBar pb; // progress indicator
Lable lb; // my text area
sp.getChildren().add(lb);// i have added the text area
lb.setGraphic(pb); we have added the progressbar to the text area
lb.setContentDisplay(ContentDisplay.***);//the *** represents the position you want your graphic
//whether top or left or bottom
//now you are done, your pb will show aligned to your lb, and be updated
//if you want to show text lb.setText("your text");
how implicit is this? :-)
Try adding a listener to the task's messageProperty that will append the text to a TextArea whenever it is changed.
TextArea ta = new TextArea();
public void activateTextArea(final Task<?> task) {
task.messageProperty().addListener((property, oldValue, newValue) -> {
ta.setText(ta.getText() + "\n" + newValue);
});
}
Then put something like this inside the task:
Task<Void> task = new Task<Void>() {
#Override
public Void call() throws InterruptedException {
for (int i = 0; i < 10; i++) {
updateProgress(i, 10);
updateMessage((i*10) + "% done");
Thread.sleep(200);
}
updateProgress(10, 10);
updateMessage("All done!");
return null;
}
};

JavaFX : Canvas to Image in non GUI Thread

I have to visualize lot of data (real-time) and I am using JavaFX 2.2. So I have decided to "pre-visualize" data before they are inserted into GUI thread.
In my opinion the fastest way to do it (with antialliasing etc.) is let some NON GUI thread to generate image/bitmap and then put in GUI thread (so the UI is still responsive for user).
But I can't find way how to conver Canvas to Image and then use:
Image imageToDraw = convert_tmpCanvasToImage(tmpCanvas);
Platform.runLater(new Runnable() {
#Override
public void run() {
canvas.getGraphicsContext2D().drawImage(imageToDraw, data.offsetX, data.offsetY);
}
});
Thx for some usable answers. :-)
btw: I have made test app to show my problem.
package canvasandthreads02;
import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class CanvasAndThreads02 extends Application {
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Paint");
final AnchorPane root = new AnchorPane();
final Canvas canvas = new Canvas(900, 800);
canvas.setLayoutX(50);
canvas.setLayoutY(50);
root.getChildren().add(canvas);
root.getChildren().add(btn);
Scene scene = new Scene(root, 900, 800);
primaryStage.setTitle("Painting in JavaFX");
primaryStage.setScene(scene);
primaryStage.show();
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Start painting");
/**
* Start Thread where some data will be visualized
*/
new Thread(new PainterThread(canvas, new DataToPaint())).start();
}
});
}
private class PainterThread implements Runnable{
private final DataToPaint data;
private final Canvas canvas;
public PainterThread(Canvas canvas, DataToPaint data){
this.canvas = canvas;
this.data = data;
}
#Override
public void run() {
long currentTimeMillis = System.currentTimeMillis();
Canvas tmpCanvas = new Canvas(data.width, data.height);
GraphicsContext graphicsContext2D = tmpCanvas.getGraphicsContext2D();
graphicsContext2D.setFill(data.color;);
for (int i = 0; i < data.height; i++) {
for (int j = 0; j < data.width; j++) {
graphicsContext2D.fillRect(j, i, 1, 1); //draw 1x1 rectangle
}
}
/**
* And now I need still in this Thread convert tmpCanvas to Image,
* or use some other method to put result to Main GIU Thread using Platform.runLater(...);
*/
final Image imageToDraw = convert_tmpCanvasToImage(tmpCanvas);
System.out.println("Canvas painting: " + (System.currentTimeMillis()-currentTimeMillis));
Platform.runLater(new Runnable() {
#Override
public void run() {
//Start painting\n Canvas painting: 430 \n Time to convert:62
//long currentTimeMillis1 = System.currentTimeMillis();
//Image imageToDraw = tmpCanvas.snapshot(null, null);
//System.out.println("Time to convert:" + (System.currentTimeMillis()-currentTimeMillis1));
canvas.getGraphicsContext2D().drawImage(imageToDraw, data.offsetX, data.offsetY);
}
});
}
}
private class DataToPaint{
double offsetX = 0;
double offsetY = 0;
Color color;
int width = 500;
int height = 250;
public DataToPaint(){
Random rand = new Random();
color = new Color(rand.nextDouble(), rand.nextDouble(), rand.nextDouble(), rand.nextDouble());
offsetX = rand.nextDouble() * 20;
offsetY = rand.nextDouble() * 20;
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
use Canvas' snapshot(...) method to create a WritableImage from the Canvas' content. ^^
Works fine for me.
I know this is a really old question, but just for anyone who cares:
There is now a second version of canvas.snapshot that takes a callback and works asynchronously!
public void snapshot(Callback<SnapshotResult,Void> callback,
SnapshotParameters params,
WritableImage image)

Resources