How to fill a shape with an image JavaFX - image

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!

Related

JavaFX resets my colors when adding buttons

I am using JavaFx to create a front-end to the cli application called spicetify. I am not using an fxml file for the layout instead I am using different classes for layout purposes.
One such class is the Sidebar class. In it I define how the sidebar should look and then create an object of it on the window/page that I need. Whenever I add buttons to the sidebar The colors of the window where the Sidebar object is created go blank/white.
I am unable to find anything by googling and hope that the information provided is enough.
Screenshot of the window without buttons
Screenshot of the window with buttons
Project Structure
Sidebar class:
package spicetify;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
public class Sidebar {
BaseWindow baseWindow = new BaseWindow();
private String[] buttonIconLocation = {
"file:src/main/resources/spicetify/images/buttons/home.png",
"file:src/main/resources/spicetify/images/buttons/theme.png",
"file:src/main/resources/spicetify/images/buttons/extension.png",
"file:src/main/resources/spicetify/images/buttons/edit.png",
};
private ImageView[] buttonIcon = new ImageView[4];
private VBox sidebar;
private Button[] buttons = new Button[4];
public Sidebar(int height, int width, String html, Parent root){
this.sidebar = new VBox();
this.sidebar.setPrefHeight(height);
this.sidebar.setPrefWidth(width);
this.sidebar.setStyle("-fx-background-color: " + html);
for (int i = 0; i < buttonIcon.length; i++){
buttonIcon[i] = new ImageView(buttonIconLocation[i]);
baseWindow.transform(buttonIcon[i], 50, 50, true);
}
for (int i = 0; i < buttons.length; i++){
buttons[i] = new Button("", buttonIcon[i]);
sidebar.getChildren().add(buttons[i]);
buttons[i].setTranslateX(20);
buttons[i].setTranslateY(i * 100);
buttons[i].setStyle("-fx-background-color: " + html);
}
}
public String[] getButtonIconLocation() {
return buttonIconLocation;
}
public void setButtonIconLocation(String[] buttonIconLocation) {
this.buttonIconLocation = buttonIconLocation;
}
public ImageView[] getButtonIcon() {
return buttonIcon;
}
public void setButtonIcon(ImageView[] buttonIcon) {
this.buttonIcon = buttonIcon;
}
public VBox getSidebar() {
return sidebar;
}
public void setSidebar(VBox sidebar) {
this.sidebar = sidebar;
}
}
BaseWindow class:
package spicetify;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
public class BaseWindow{
private Parent root;
private Scene scene;
private Stage stage;
private String title;
private int width;
private int height;
private int minWidth;
private int minHeight;
private String htmlColor;
public int getMinWidth() {
return minWidth;
}
public void setMinWidth(int minWidth) {
this.minWidth = minWidth;
}
public int getMinHeight() {
return minHeight;
}
public void setMinHeight(int minHeight) {
this.minHeight = minHeight;
}
public String getHtmlColor() {
return htmlColor;
}
public void setHtmlColor(String htmlColor) {
this.htmlColor = htmlColor;
}
public Parent getRoot() {
return root;
}
public void setRoot(Parent root) {
this.root = root;
}
public Scene getScene() {
return scene;
}
public void setScene(Scene scene) {
this.scene = scene;
}
public Stage getStage() {
return stage;
}
public void setStage(Stage stage) {
this.stage = stage;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public void transform(ImageView view, int width, int height, boolean preserveRatio){
view.setFitWidth(width);
view.setFitHeight(height);
view.setPreserveRatio(preserveRatio);
}
public void start(Stage stage){
stage.setScene(scene);
stage.setTitle(title);
stage.show();
}
}
HomeWindow class:
package spicetify;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class HomeWindow extends BaseWindow {
private ImageView logoView;
private ImageView preview;
private Parent root;
private Scene scene;
BaseWindow baseWindow = new BaseWindow();
Sidebar sidebar = new Sidebar(baseWindow.getHeight(), 100, "#282828", root);
public HomeWindow(int width, int height, String html){
this.logoView = new ImageView(new Image("file:src/main/resources/spicetify/images/essentials/logo.png"));
this.preview = new ImageView(new Image("file:src/main/resources/spicetify/images/essentials/Preview.png"));
this.root = new BorderPane();
baseWindow.setWidth(width);
baseWindow.setMinWidth(width);
baseWindow.setHeight(height);
baseWindow.setMinHeight(height);
baseWindow.setHtmlColor(html);
}
public void start(Stage stage){
((BorderPane) root).setLeft(sidebar.getSidebar());
baseWindow.transform(logoView, 600, 700, true);
((BorderPane) root).setCenter(logoView);
baseWindow.setStage(stage);
baseWindow.setTitle("Spicetify");
baseWindow.setRoot(root);
baseWindow.setScene(new Scene(baseWindow.getRoot(), baseWindow.getWidth(), baseWindow.getHeight(), Color.web(baseWindow.getHtmlColor())));
baseWindow.getStage().setScene(baseWindow.getScene());
baseWindow.getStage().setMinWidth(baseWindow.getMinWidth());
baseWindow.getStage().setMinHeight(baseWindow.getMinHeight());
baseWindow.getStage().setTitle(baseWindow.getTitle());
baseWindow.getStage().show();
}
}
Default Modena CSS has a gray background in panes.
When controls are loaded CSS will be applied to the entire scene, as controls use CSS.
Without any controls, CSS (for performance in straight rendering of graphics primitives) will only be applied to the scene if you specifically apply it.
For more information, and steps you can take to remove the default color from pane backgrounds, see the related question:
JavaFX 8 tooltip removes transparency of stage

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

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);
}
}

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.
}

Sort Javafx table on multiple columns

Can a default Javafx table sort on multiple fields by dragging the columns on a dropzone?
My user need to select one or multiple columns to sort on different columns. The application is fully written in Java8 with JavaFX.
The source code that I now use is:
import java.util.Collections;
import java.util.Comparator;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class GroupByTable extends Application {
public enum Color { GREEN, BLUE, RED }
public enum Shape { RECTANGLE, CIRCLE, TRIANGLE }
public enum Size { SMALL, MEDIUM, LARGE }
private Label groupByLabel;
private Comparator<Item> groupingComparator ;
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.getColumns().add(column("Size", Item::getSize));
table.getColumns().add(column("Color", Item::getColor));
table.getColumns().add(column("Shape", Item::getShape));
groupByLabel = new Label("Grouping");
groupByLabel.setOnDragOver(e -> {
if (groupingComparator != null && "grouping".equals(e.getDragboard().getString())) {
e.acceptTransferModes(TransferMode.COPY);
}
});
groupByLabel.setOnDragDropped(e -> {
if (groupingComparator != null && "grouping".equals(e.getDragboard().getString())) {
table.getItems().sort(groupingComparator);
e.setDropCompleted(true);
}
});
for (Color color : Color.values()) {
for (Size size : Size.values()) {
for (Shape shape : Shape.values()) {
table.getItems().add(new Item(color, shape, size));
}
}
}
Collections.shuffle(table.getItems());
BorderPane root = new BorderPane(table);
BorderPane.setAlignment(groupByLabel, Pos.CENTER);
BorderPane.setMargin(groupByLabel, new Insets(20));
root.setTop(groupByLabel);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private <T extends Comparable<T>> TableColumn<Item,T> column(String title, Function<Item,T> property) {
TableColumn<Item,T> col = new TableColumn<>();
col.setCellValueFactory(cellData -> new SimpleObjectProperty<>(property.apply(cellData.getValue())));
Label graphic = new Label(title);
graphic.setOnDragDetected(e -> {
groupingComparator = Comparator.comparing(property);
Dragboard dragboard = graphic.startDragAndDrop(TransferMode.COPY);
ClipboardContent cc = new ClipboardContent();
cc.putString("grouping");
dragboard.setContent(cc);
});
graphic.setOnDragDone(e -> {
groupingComparator = null ;
});
col.setGraphic(graphic);
return col ;
}
public static class Item {
private final Color color ;
private final Shape shape ;
private final Size size ;
public Item(Color color, Shape shape, Size size) {
super();
this.color = color;
this.shape = shape;
this.size = size;
}
public Color getColor() {
return color;
}
public Shape getShape() {
return shape;
}
public Size getSize() {
return size;
}
#Override
public String toString() {
return String.format("%s %s %s", size, color, shape);
}
}
public static void main(String[] args) {
launch(args);
}
}
I view the TableView API and found that the JavaFX table does have a default implementation of this. Just click on the columns using the shift key.

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