Animation jumps to the right - java

Busy with some simple MVC model for my study, there's 1 thing I can't figure out.. when I run my animation, the circle jumps a bit to the right (it seems like it's the half of the radius).
Here my 4 classes:
The model:
public class Model {
private int radius;
private int xAs;
private int yAs;
public Model(int xAs, int yAs, int radius){
this.xAs = xAs;
this.yAs = yAs;
this.radius = radius;
}
public int getRadius(){
return radius;
}
public void setRadius(int radius){
this.radius = radius;
}
public int getXAs(){
return xAs;
}
public void setXAs(int xAs){
this.xAs = xAs;
}
public int getYAs(){
return yAs;
}
public void setYAs(int yAs){
this.yAs = yAs;
}
}
The view:
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
public class View extends Pane{
private Circle bal;
private Button start;
private Button stop;
private Model model;
public View(Model model){
this.model = model;
createBal();
this.getChildren().add(bal);
start = new Button("Start");
start.setMinWidth(75);
stop = new Button("Stop");
stop.setMinWidth(75);
}
public void createBal(){
bal = new Circle();
bal.setRadius(model.getRadius());
bal.setCenterX(model.getXAs()+bal.getRadius()/2);
bal.setCenterY(model.getYAs());
bal.setFill(Color.RED);
}
public Button getStart(){
return this.start;
}
public Button getStop(){
return this.stop;
}
public void adjust(){
bal.relocate(model.getXAs(), model.getYAs());
}
}
The controller:
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.util.Duration;
public class Controller {
View view;
Model model;
Timeline animatie;
private int yAsPositie = 1;
public Controller(View view, Model model){
this.view = view;
this.model = model;
animatie = new Timeline(new KeyFrame(Duration.millis(10), e -> beweegBal()));
animatie.setCycleCount(Timeline.INDEFINITE);
view.getStart().setOnAction(e -> animatie.play());
view.getStop().setOnAction(e -> animatie.stop());
}
public void beweegBal(){
model.setYAs(model.getYAs() + yAsPositie);
view.adjust();
}
}
The start(app)
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class App extends Application {
public void start(Stage primaryStage) throws Exception {
Model bal = new Model(20, 20, 20);
View view = new View(bal);
Controller controller = new Controller(view, bal);
//horizontale box aanmaken voor buttons
HBox botMenu = new HBox(30);
//buttons toevoegen aan bottom menu
botMenu.getChildren().addAll(view.getStart(), view.getStop());
//stackpane als container voor canvas
Pane canvas = new Pane();
//canvas toevoegen aan zn container
canvas.setBackground(new Background(new BackgroundFill(Color.DARKGRAY, CornerRadii.EMPTY, Insets.EMPTY)));
//borderpane aanmaken
BorderPane pane = new BorderPane();
//bottom menu als onderste gedeelte indelen
pane.setBottom(botMenu);
botMenu.setAlignment(Pos.BOTTOM_CENTER);
botMenu.setPadding(new Insets(10, 10, 10, 10));
//canvascontainer toevoegen in het midden
pane.setCenter(canvas);
//view toevoegen
canvas.getChildren().add(view);
Scene scene = new Scene(pane, 500, 500);
primaryStage.setTitle("De vallende bal");
primaryStage.setScene(scene);
primaryStage.setMaxHeight(500);
primaryStage.setMaxWidth(500);
primaryStage.setMinHeight(500);
primaryStage.setMinWidth(500);
primaryStage.show();
}
public static void main(String args[]) {
launch(args);
}
}
If you run it, you will notice it jumps a bit to the right, been stuck on it for hours now.

It looks like you are interpreting model.xAs as the x-coordinate of the left edge of the ball, since you use it as the x coordinate when you relocate it. In that case, you surely need to initialize the center with
bal.setCenterX(model.getXAs()+bal.getRadius());
since the radius of a circle is the distance from the center to the edge.

Related

How to hide and unhide the data points in a linechart in JavaFX?

In the application I need to create, I have a series of points which are connected by lines. I need to only see the line part of the graph and hide the points. However, I need to mark certain points by clicking on the appropriate places in the graph. In the code I have written I am either able to hide the points and not able to mark or I am able to mark but not hide the rest of the points. What do I do to hide the points and mark the required points?
package application;
import java.awt.Dimension;
import java.awt.Toolkit;
//import java.beans.EventHandler;
import java.util.ArrayList;
import javafx.event.EventHandler;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.Scene;
import javafx.scene.chart.Axis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Main extends Application {
public XYChart.Data<Integer, Integer> k = new XYChart.Data();
public XYChart.Series series;
String pth;
public ObservableList<XYChart.Data<Integer, Integer>> dat = FXCollections.<XYChart.Data<Integer, Integer>>observableArrayList();
public int c = 0;
ArrayList<Integer> myListx = new ArrayList<Integer>();
public void start(Stage stage) {
stage.setTitle("Line Chart Sample");
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Samples");
yAxis.setLabel("Data");
final LineChart<Number, Number> lineChart = new LineChart<Number, Number>(xAxis, yAxis);
lineChart.setCreateSymbols(false);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int width = (int) screenSize.getWidth();
int height = (int) screenSize.getHeight();
StackPane spLineChart = new StackPane();
spLineChart.getChildren().add(lineChart);
while (c < 20) {
k = new XYChart.Data<Integer, Integer>(c++, c * 6);
dat.add(k);
k.setNode(new Node(c));
}
series = new XYChart.Series("IMU Data", dat);
lineChart.getData().addAll(series);
xAxis.setVisible(true);
yAxis.setVisible(true);
spLineChart.setVisible(true);
StackPane spButton = new StackPane();
StackPane sp = new StackPane();
VBox vbox = new VBox();
VBox.setVgrow(spLineChart, Priority.ALWAYS);
vbox.getChildren().addAll(sp, spLineChart, spButton);
Scene scene = new Scene(vbox, width, height - 500);
stage.setScene(scene);
stage.show();
}
class Node extends StackPane {
Node(int priorValue) {
final Circle circle = createData();
setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (mouseEvent.getButton().equals(MouseButton.PRIMARY)) {
getChildren().setAll(circle);
myListx.add(priorValue);
} else if (mouseEvent.getButton().equals(MouseButton.SECONDARY)) {
getChildren().clear();
myListx.remove(new Integer(priorValue));
}
}
;
});
}
private Circle createData() {
Circle circle = new Circle();
circle.setFill(Color.BLACK);
circle.setStroke(Color.BLACK);
circle.setRadius(4);
return circle;
}
}
public static void main(String[] args) {
launch(args);
}
}
Use this modified Node class. Where on-mouse click event, it toggles the effect as per the existence of its children.
class Node extends StackPane {
Node(int priorValue) {
setOnMouseClicked(event -> {
if (getChildren().isEmpty()) {
final Circle circle = createData();
getChildren().setAll(circle);
} else {
getChildren().clear();
}
myListx.add(priorValue);
});
}
private Circle createData() {
Circle circle = new Circle();
circle.setFill(Color.BLACK);
circle.setStroke(Color.BLACK);
circle.setRadius(4);
return circle;
}
}
Note: You may consider renaming your class Node as it clashes with Node.

Setting animation to move in reverse & showing shape from class when button is clicked

I am working on a javafx program to create three buttons which are "circle", "ellipse" and "reverse". The circle button is the first thing shown when the program is run, the reverse button is supposed to correspond with the movement of the rectangle around the circle. I am having trouble getting the reverse button to work, I set autoReverse to true but it isn't doing anything. My second issue with the code is when the ellipse button is clicked the class MyEllipse isn't showing the ellipse shape like it is supposed to and it's not removing the circle animation from the pane. I tried to create a new pane in the EventHandler for the buttonellipse but I am assuming that isn't the correct way to do it. Any help on these two issues would be greatly appreciated.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.animation.PathTransition;
import javafx.scene.paint.Color;
import javafx.animation.Timeline;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Ellipse;
import javafx.scene.control.Button;
import javafx.event.ActionEvent;
import javafx.scene.layout.HBox;
import javafx.event.EventHandler;
import javafx.stage.Stage;
public class exam3b extends Application {
#Override
public void start(Stage primaryStage) {
Rectangle rectangle = new Rectangle (0, 0, 25, 50);
rectangle.setFill(Color.ORANGE);
Circle circle = new Circle(115, 90, 45);
circle.setFill(Color.WHITE);
circle.setStroke(Color.BLACK);
PathTransition pt = new PathTransition();
pt.setDuration(Duration.millis(4000));
pt.setPath(circle);
pt.setNode(rectangle);
pt.setOrientation(
PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pt.setCycleCount(Timeline.INDEFINITE);
pt.setAutoReverse(false);
pt.play();
circle.setOnMousePressed(e -> pt.pause());
circle.setOnMouseReleased(e -> pt.play());
HBox panel = new HBox(10);
panel.setAlignment(Pos.BOTTOM_CENTER);
Button button = new Button("Circle");
Button buttonellipse = new Button("Ellipse");
Button reverse = new Button("Reverse");
panel.getChildren().addAll(button,buttonellipse,reverse);
button.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent e)
{
reverse.setVisible(true);
}
});
reverse.setOnAction(new EventHandler<ActionEvent>() //supposed to make rectangle move in reverse direction
{
#Override
public void handle(ActionEvent e)
{
pt.setAutoReverse(true);
}
});
buttonellipse.setOnAction(new EventHandler<ActionEvent>() //button to make ellipse appear from class MyEllipse, reverse button is supposed to disappear
{
#Override
public void handle(ActionEvent e)
{
reverse.setVisible(false);
Pane ellipse = new Pane();
ellipse.getChildren().add(new MyEllipse());
}
});
Pane pane = new Pane();
pane.getChildren().addAll(panel,circle,rectangle);
Scene scene = new Scene(pane, 350, 250);
primaryStage.setTitle("exam3b");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class MyEllipse extends Pane
{
private void paint() {
getChildren().clear();
for (int i = 0; i < 16; i++) {
Ellipse e1 = new Ellipse(getWidth() / 2, getHeight() / 2,
getWidth() / 2 - 50, getHeight() / 2 - 50);
e1.setStroke(Color.color(Math.random(), Math.random(),
Math.random()));
e1.setFill(Color.WHITE);
e1.setRotate(i * 180 / 16);
getChildren().add(e1);
}
}
#Override
public void setWidth(double width) {
super.setWidth(width);
paint();
}
#Override
public void setHeight(double height) {
super.setHeight(height);
paint();
}
}
You had an issue with how you were using MyEllipse, in that you were never adding it to your root pane nor were you settign the width/height (which leads to your paint method being called). I renamed your root pane to 'root'. Created an instance of 'MyEllipse', and in the 'Ellipse' button i add the instance of 'MyEllipse' to the root pane.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.animation.PathTransition;
import javafx.scene.paint.Color;
import javafx.animation.Timeline;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Ellipse;
import javafx.scene.control.Button;
import javafx.event.ActionEvent;
import javafx.scene.layout.HBox;
import javafx.event.EventHandler;
import javafx.stage.Stage;
public class exam3b extends Application {
#Override
public void start(Stage primaryStage) {
Rectangle rectangle = new Rectangle(0, 0, 25, 50);
rectangle.setFill(Color.ORANGE);
Circle circle = new Circle(115, 90, 45);
circle.setFill(Color.WHITE);
circle.setStroke(Color.BLACK);
PathTransition pt = new PathTransition();
pt.setDuration(Duration.millis(4000));
pt.setPath(circle);
pt.setNode(rectangle);
pt.setOrientation(
PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pt.setCycleCount(Timeline.INDEFINITE);
pt.setAutoReverse(true);
pt.play();
circle.setOnMousePressed(e -> pt.pause());
circle.setOnMouseReleased(e -> pt.play());
HBox panel = new HBox(10);
panel.setAlignment(Pos.BOTTOM_CENTER);
Button button = new Button("Circle");
Button buttonellipse = new Button("Ellipse");
Button reverse = new Button("Reverse");
panel.getChildren().addAll(button, buttonellipse, reverse);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
reverse.setVisible(true);
}
});
reverse.setOnAction(new EventHandler<ActionEvent>() //supposed to make rectangle move in reverse direction
{
#Override
public void handle(ActionEvent e) {
pt.setAutoReverse(true);
}
});
Pane root = new Pane();
MyEllipse myEllipse = new MyEllipse();
myEllipse.setWidth(200);
myEllipse.setHeight(400);
buttonellipse.setOnAction(new EventHandler<ActionEvent>() //button to make ellipse appear from class MyEllipse, reverse button is supposed to disappear
{
#Override
public void handle(ActionEvent e) {
// reverse.setVisible(false);
if (root.getChildren().contains(myEllipse)) {
root.getChildren().remove(myEllipse);
} else {
System.out.println("adding ellipse");
root.getChildren().add(myEllipse);
}
}
});
root.getChildren().addAll(panel, circle, rectangle);
Scene scene = new Scene(root, 350, 250);
primaryStage.setTitle("exam3b");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class MyEllipse extends Pane {
public MyEllipse() {
}
private void paint() {
getChildren().clear();
for (int i = 0; i < 16; i++) {
Ellipse e1 = new Ellipse(getWidth() / 2, getHeight() / 2,
getWidth() / 2 - 50, getHeight() / 2 - 50);
e1.setStroke(Color.color(Math.random(), Math.random(),
Math.random()));
e1.setFill(Color.WHITE);
e1.setStrokeWidth(1);
e1.setRotate(i * 180 / 16);
getChildren().add(e1);
}
}
#Override
public void setWidth(double width) {
super.setWidth(width);
paint();
}
#Override
public void setHeight(double height) {
super.setHeight(height);
paint();
}
}

how to change the rotation direction on keypress in javafx

I have two circles that I want to turn them around a pivot clockwise if the right key is pressed and counter clockwise if the left key is pressed but my code does not work.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.layout.*;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.text.Font;
import javafx.scene.transform.Rotate;
import javafx.stage.Popup;
import javafx.stage.Stage;
import javafx.util.Duration;
import static java.lang.Math.cos;
import static java.lang.Math.sin;
public class Main extends Application {
public static final double CIRCLES_CENTER_X = 600;
public static final double CIRCLES_CENTER_Y = 450;
public static final double CIRCLES_RADIUS = 15;
public static final double CIRCLES_DISTANCE = 300;
public static final double GAME_HEIGHT = 700;
public static final double GAME_WIDTH = 1200;
private Stage primaryStage;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
primaryStage.setMinWidth(GAME_WIDTH);
primaryStage.setMinHeight(GAME_HEIGHT);
final Scene scene;
BorderPane root = new BorderPane();
scene = new Scene(root, Main.GAME_WIDTH, Main.GAME_HEIGHT);
Circle orangeCircle = new Circle(Main.CIRCLES_CENTER_X + Main.CIRCLES_DISTANCE / 2 * cos(0),
Main.CIRCLES_CENTER_Y + Main.CIRCLES_DISTANCE / 2 * sin(0),
Main.CIRCLES_RADIUS, Color.ORANGE);
Circle yellowCircle = new Circle(Main.CIRCLES_CENTER_X - Main.CIRCLES_DISTANCE / 2 * cos(0),
Main.CIRCLES_CENTER_Y - Main.CIRCLES_DISTANCE / 2 * sin(0),
Main.CIRCLES_RADIUS, Color.YELLOW);
Pane game = new Pane(orangeCircle, yellowCircle);
root.setCenter(game);
SimpleIntegerProperty angle = new SimpleIntegerProperty(0);
root.setOnKeyPressed(ke -> {
if (ke.getCode().toString().equals("RIGHT")) {
angle.set(360);
}
if (ke.getCode().toString().equals("LEFT")) {
angle.set(-360);
}
});
root.setOnKeyReleased(ke -> {
if (ke.getCode().toString().equals("RIGHT")) {
angle.set(0);
}
if (ke.getCode().toString().equals("LEFT")) {
angle.set(0);
}
});
Rotate orangeCircleRotation = new Rotate(0, Main.CIRCLES_CENTER_X, Main.CIRCLES_CENTER_Y);
orangeCircle.getTransforms().add(orangeCircleRotation);
Rotate yellowCircleRotation = new Rotate(0, Main.CIRCLES_CENTER_X, Main.CIRCLES_CENTER_Y);
yellowCircle.getTransforms().add(yellowCircleRotation);
Timeline rotationAnimation = new Timeline();
rotationAnimation.setCycleCount(Timeline.INDEFINITE);
angle.addListener((ov, old_val, new_val) -> {
System.out.println("fk");
rotationAnimation.stop();
while (rotationAnimation.getKeyFrames().size() > 0) {
rotationAnimation.getKeyFrames().remove(0);
}
rotationAnimation.getKeyFrames().add(
new KeyFrame(Duration.millis(2000),
new KeyValue(orangeCircleRotation.angleProperty(), angle.getValue())));
rotationAnimation.getKeyFrames().add(
new KeyFrame(Duration.millis(2000),
new KeyValue(yellowCircleRotation.angleProperty(), angle.getValue())));
rotationAnimation.play();
}
);
primaryStage.setScene(scene);
primaryStage.show();
}
public Stage getPrimaryStage() {
return primaryStage;
}
}
It works almost fine but when i press a key and release it the circles does not stop. they just start turning backward until the last rotation change point and keep repeating that so also when i press the key again it jumps sometimes.(Because the backward turn has reached its end and starts from the beginning)(Hard to explain!you have to see it for yourself!)
Does anyone know how to fix or achieve this?
I wouldn't try to manipulate the key frames while the animation is in progress. Instead you can just pause/play the animation and change the rate. The only "gotcha" here is that it seems the animation ignores the change in rate if it is paused, so you need to call play() before setRate(...).
Here's the modified SSCCE:
import static java.lang.Math.cos;
import static java.lang.Math.sin;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
public class RotatingCircles extends Application {
public static final double CIRCLES_CENTER_X = 600;
public static final double CIRCLES_CENTER_Y = 450;
public static final double CIRCLES_RADIUS = 15;
public static final double CIRCLES_DISTANCE = 300;
public static final double GAME_HEIGHT = 700;
public static final double GAME_WIDTH = 1200;
private Stage primaryStage;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
primaryStage.setMinWidth(GAME_WIDTH);
primaryStage.setMinHeight(GAME_HEIGHT);
final Scene scene;
BorderPane root = new BorderPane();
scene = new Scene(root, GAME_WIDTH, GAME_HEIGHT);
Circle orangeCircle = new Circle(CIRCLES_CENTER_X + CIRCLES_DISTANCE / 2 * cos(0),
CIRCLES_CENTER_Y + CIRCLES_DISTANCE / 2 * sin(0),
CIRCLES_RADIUS, Color.ORANGE);
Circle yellowCircle = new Circle(CIRCLES_CENTER_X - CIRCLES_DISTANCE / 2 * cos(0),
CIRCLES_CENTER_Y - CIRCLES_DISTANCE / 2 * sin(0),
CIRCLES_RADIUS, Color.YELLOW);
Pane game = new Pane(orangeCircle, yellowCircle);
root.setCenter(game);
Rotate orangeCircleRotation = new Rotate(0, CIRCLES_CENTER_X, CIRCLES_CENTER_Y);
orangeCircle.getTransforms().add(orangeCircleRotation);
Rotate yellowCircleRotation = new Rotate(0, CIRCLES_CENTER_X, CIRCLES_CENTER_Y);
yellowCircle.getTransforms().add(yellowCircleRotation);
Timeline rotationAnimation = new Timeline();
rotationAnimation.setCycleCount(Timeline.INDEFINITE);
rotationAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(2), new KeyValue(orangeCircleRotation.angleProperty(), 360)));
rotationAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(2), new KeyValue(yellowCircleRotation.angleProperty(), 360)));
root.setOnKeyPressed(ke -> {
if (ke.getCode() == KeyCode.RIGHT) {
rotationAnimation.play();
rotationAnimation.setRate(1);
} else if (ke.getCode() == KeyCode.LEFT) {
rotationAnimation.play();
rotationAnimation.setRate(-1);
}
});
root.setOnKeyReleased(ke -> {
rotationAnimation.pause();
});
primaryStage.setScene(scene);
primaryStage.show();
root.requestFocus();
}
public Stage getPrimaryStage() {
return primaryStage;
}
}
BTW In this code you really don't need two separate rotations, since they are identical. Just create a single rotation and add it to both circles' transforms lists. It may be different in your real code, of course...

Make different things occur on Mouse Pressed depending on Object in Javafx

I want the mouse clicked event to either drag the existing circle or create a new circle if the mouse clicked event occurs outside of an existing circle. Right now the code will drag the circle, but also creates a new one on top of the other circle. I would really appreciate any help on cleaning the code up and making it do one or the other. Here is the code. Ive been trying to do all kinds of different things to get it to work, but I can't figure out how to get it to only do one or the other.
import javafx.application.Application;
import javafx.scene.Cursor;
import javafx.scene.Group;
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.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.scene.control.ChoiceBox;
import javafx.collections.FXCollections;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
public class Main extends Application {
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Drawing Operations Test");
Group root = new Group();
Pane canvas = new Pane();
canvas.setStyle("-fx-background-color: black;");
canvas.setPrefSize(200,200);
Rectangle rectangle = new Rectangle(100,100,Color.RED);
rectangle.relocate(70,70);
canvas.getChildren().add(rectangle);
canvas.addEventHandler(MouseEvent.MOUSE_PRESSED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
Object source = e.getSource();
if (!(source instanceof Circle)) {
Circle circle = new Circle(20,Color.BLUE);
circle.setCursor(Cursor.MOVE);
circle.setOnMousePressed(circleOnMousePressedEventHandler);
circle.setOnMouseDragged(circleOnMouseDraggedEventHandler);
circle.relocate((e.getX()-10),(e.getY()-10));
canvas.getChildren().add(circle);
System.out.println(source);
}
}
});
root.getChildren().add(canvas);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
EventHandler<MouseEvent> circleOnMousePressedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
orgTranslateX = ((Circle)(t.getSource())).getTranslateX();
orgTranslateY = ((Circle)(t.getSource())).getTranslateY();
System.out.println(t.getSource());
}
};
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
((Circle)(t.getSource())).setTranslateX(newTranslateX);
((Circle)(t.getSource())).setTranslateY(newTranslateY);
}
};
}
I found it! the answer for me was creating an if else statement and consuming the event on the circle. I'm sure there is a much more elegant way to do this, but I'm not too elegant in Java..... yet.
public class Main extends Application {
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
Pane canvas;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Drawing Operations Test");
Group root = new Group();
canvas = new Pane();
canvas.setStyle("-fx-background-color: black;");
canvas.setPrefSize(200,200);
Rectangle rectangle = new Rectangle(100,100,Color.RED);
rectangle.relocate(70,70);
canvas.getChildren().add(rectangle);
canvas.setOnMousePressed(MousePressedEventHandler);
root.getChildren().add(canvas);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
EventHandler<MouseEvent> MousePressedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
if (e.getSource() instanceof Circle) {
orgSceneX = e.getSceneX();
orgSceneY = e.getSceneY();
orgTranslateX = ((Circle)(e.getSource())).getTranslateX();
orgTranslateY = ((Circle)(e.getSource())).getTranslateY();
e.consume();
System.out.println(e.getSource());
}
else {
Circle circle = new Circle(20,Color.BLUE);
circle.setCursor(Cursor.MOVE);
circle.setOnMousePressed(MousePressedEventHandler);
circle.setOnMouseDragged(MouseDraggedEventHandler);
circle.relocate((e.getX()-10),(e.getY()-10));
canvas.getChildren().add(circle);
System.out.println(e.getSource());
}
}
};
EventHandler<MouseEvent> MouseDraggedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
((Circle)(t.getSource())).setTranslateX(newTranslateX);
((Circle)(t.getSource())).setTranslateY(newTranslateY);
}
};
}
You can consume the event on the child node (circle) before it delegates to the Parent (pane).
EventHandler<MouseEvent> circleOnMousePressedEventHandler = event -> {
orgSceneX = event.getSceneX();
orgSceneY = event.getSceneY();
orgTranslateX = ((Circle) (event.getSource())).getTranslateX();
orgTranslateY = ((Circle) (event.getSource())).getTranslateY();
event.consume();
};

JavaFX Flip Node with PerspectiveTransform

JavaFX 2.x
What I want to do:
This script translated into Java source code. I tried that myself, but some of that stuff is deprecated (e.g. PerspectiveTransform#time - not found in JavaFX 2.2)
Flipping like this and like that.
What I don't want to do:
Use RotateTransition because it depends on the PerspectiveCamera. Since I'll have many flippable tiles next to each other, the front/back replacement halfway through the animation won't go well.
What I have so far:
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.effect.PerspectiveTransformBuilder;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* #author ggrec
*
*/
public class FX_Tester extends Application
{
#Override
public void start(final Stage stage) throws Exception
{
final StackPane stackPane = new StackPane();
final ImageView img1 = new ImageView("http://img3.wikia.nocookie.net/__cb20120816162009/mario/images/thumb/1/15/MarioNSMB2.png/200px-MarioNSMB2.png");
final ImageView img2 = new ImageView("http://img2.wikia.nocookie.net/__cb20120518002849/mario/images/thumb/7/78/Tanooki_Mario_Artwork_-_Super_Mario_Bros._3.png/180px-Tanooki_Mario_Artwork_-_Super_Mario_Bros._3.png");
final FlipView flipPane = new FlipView(img1, img2);
stackPane.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override public void handle(final MouseEvent arg0)
{
flipPane.doFlip();
}
});
stackPane.getChildren().setAll(flipPane);
stage.setScene(new Scene(stackPane));
stage.show();
}
public static void main(final String[] args)
{
launch();
}
private class FlipView extends Group
{
private Node frontNode;
private Node backNode;
private boolean isFlipped = false;
private SimpleDoubleProperty time = new SimpleDoubleProperty(Math.PI / 2);
private Timeline anim = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(time, Math.PI / 2)),
new KeyFrame(Duration.ONE, new KeyValue(time, - Math.PI / 2)),
new KeyFrame(Duration.ONE, new EventHandler<ActionEvent>() {
#Override public void handle(final ActionEvent arg0)
{
isFlipped = !isFlipped;
}
})
);
private FlipView(final Node frontNode, final Node backNode)
{
this.frontNode = frontNode;
this.backNode = backNode;
getChildren().setAll(frontNode, backNode);
frontNode.setEffect(getPT(time.doubleValue()));
backNode.setEffect(getPT(time.doubleValue()));
frontNode.visibleProperty().bind(time.greaterThan(0));
backNode.visibleProperty().bind(time.lessThan(0));
}
private PerspectiveTransform getPT(final double t)
{
final double width = 200;
final double height = 200;
final double radius = width / 2;
final double back = height / 10;
return PerspectiveTransformBuilder.create()
.ulx(radius - Math.sin(t)*radius)
.uly(0 - Math.cos(t)*back)
.urx(radius + Math.sin(t)*radius)
.ury(0 + Math.cos(t)*back)
.lrx(radius + Math.sin(t)*radius)
.lry(height - Math.cos(t)*back)
.llx(radius - Math.sin(t)*radius)
.lly(height + Math.cos(t)*back)
.build();
}
public void doFlip()
{
if (isFlipped)
{
anim.setRate(1.0);
anim.setDelay(Duration.ZERO);
}
else
{
anim.setRate(-1.0);
anim.setDelay(Duration.ONE);
}
anim.play();
}
}
}
After heavy R&D, I've managed to implement the flip functionality without PerspectiveCamera, using only PerspectiveTransform.
If you're too lazy to run this SSCCE, then go here to see a demo on how the below code works.
Q: But George, how is this different from the other methods???
A: Well, first of: since you're not using PerspectiveCamera, the user's perspective won't be affected if say you have 100 flipping tiles on the screen. Second and last: The back node is ALREADY flipped. So it's not mirrored, it's not rotate, it's not scaled. It's "normal". Ain't that great?
Cheers.
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* #author ggrec
*
*/
public class DFXFlipPaneTester extends Application
{
// ==================== 1. Static Fields ========================
/*
* Mmm... pie.
*/
private static final Double PIE = Math.PI;
private static final Double HALF_PIE = Math.PI / 2;
private static final double ANIMATION_DURATION = 10000;
private static final double ANIMATION_RATE = 10;
// ====================== 2. Instance Fields =============================
private Timeline animation;
private StackPane flipPane;
private SimpleDoubleProperty angle = new SimpleDoubleProperty(HALF_PIE);
private PerspectiveTransform transform = new PerspectiveTransform();
private SimpleBooleanProperty flippedProperty = new SimpleBooleanProperty(true);
// ==================== 3. Static Methods ====================
public static void main(final String[] args)
{
Application.launch(args);
}
// ==================== 5. Creators ====================
#Override
public void start(final Stage primaryStage) throws Exception
{
primaryStage.setScene(new Scene(createFlipPane()));
primaryStage.show();
}
private StackPane createFlipPane()
{
angle = createAngleProperty();
flipPane = new StackPane();
flipPane.setPadding(new Insets(30));
flipPane.setMinHeight(500);
flipPane.setMinWidth(500);
flipPane.getChildren().setAll(createBackNode(), createFrontNode());
flipPane.widthProperty().addListener(new ChangeListener<Number>() {
#Override public void changed(final ObservableValue<? extends Number> arg0, final Number arg1, final Number arg2)
{
recalculateTransformation(angle.doubleValue());
}
});
flipPane.heightProperty().addListener(new ChangeListener<Number>() {
#Override public void changed(final ObservableValue<? extends Number> arg0, final Number arg1, final Number arg2)
{
recalculateTransformation(angle.doubleValue());
}
});
return flipPane;
}
private StackPane createFrontNode()
{
final StackPane node = new StackPane();
node.setEffect(transform);
node.visibleProperty().bind(flippedProperty);
node.getChildren().setAll(createButton("Front Button")); //$NON-NLS-1$
return node;
}
private StackPane createBackNode()
{
final StackPane node = new StackPane();
node.setEffect(transform);
node.visibleProperty().bind(flippedProperty.not());
node.getChildren().setAll(createButton("Back Button")); //$NON-NLS-1$
return node;
}
private Button createButton(final String text)
{
final Button button = new Button(text);
button.setMaxHeight(Double.MAX_VALUE);
button.setMaxWidth(Double.MAX_VALUE);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(final ActionEvent arg0)
{
flip();
}
});
return button;
}
private SimpleDoubleProperty createAngleProperty()
{
// --------------------- <Angle> -----------------------
final SimpleDoubleProperty angle = new SimpleDoubleProperty(HALF_PIE);
angle.addListener(new ChangeListener<Number>() {
#Override public void changed(final ObservableValue<? extends Number> obsValue, final Number oldValue, final Number newValue)
{
recalculateTransformation(newValue.doubleValue());
}
});
return angle;
}
private Timeline createAnimation()
{
return new Timeline(
new KeyFrame(Duration.millis(0), new KeyValue(angle, HALF_PIE)),
new KeyFrame(Duration.millis(ANIMATION_DURATION / 2), new KeyValue(angle, 0, Interpolator.EASE_IN)),
new KeyFrame(Duration.millis(ANIMATION_DURATION / 2), new EventHandler<ActionEvent>() {
#Override public void handle(final ActionEvent arg0)
{
// TODO -- Do they another way or API to do this?
flippedProperty.set( flippedProperty.not().get() );
}
}),
new KeyFrame(Duration.millis(ANIMATION_DURATION / 2), new KeyValue(angle, PIE)),
new KeyFrame(Duration.millis(ANIMATION_DURATION), new KeyValue(angle, HALF_PIE, Interpolator.EASE_OUT))
);
}
// ==================== 6. Action Methods ====================
private void flip()
{
if (animation == null)
animation = createAnimation();
animation.setRate( flippedProperty.get() ? ANIMATION_RATE : -ANIMATION_RATE );
animation.play();
}
// ==================== 8. Business Methods ====================
private void recalculateTransformation(final double angle)
{
final double insetsTop = flipPane.getInsets().getTop() * 2;
final double insetsLeft = flipPane.getInsets().getLeft() * 2;
final double radius = flipPane.widthProperty().subtract(insetsLeft).divide(2).doubleValue();
final double height = flipPane.heightProperty().subtract(insetsTop).doubleValue();
final double back = height / 10;
/*
* Compute transform.
*
* Don't bother understanding these unless you're a math passionate.
*
* You may Google "Affine Transformation - Rotation"
*/
transform.setUlx(radius - Math.sin(angle) * radius);
transform.setUly(0 - Math.cos(angle) * back);
transform.setUrx(radius + Math.sin(angle) * radius);
transform.setUry(0 + Math.cos(angle) * back);
transform.setLrx(radius + Math.sin(angle) * radius);
transform.setLry(height - Math.cos(angle) * back);
transform.setLlx(radius - Math.sin(angle) * radius);
transform.setLly(height + Math.cos(angle) * back);
}
}
Oracle created a sample called DisplayShelf. It is similar to the PhotoFlip application you linked, but is implemented for Java 2+. The Oracle sample code is in the Ensemble Sample Application. You can review the DisplayShelf source in the JavaFX open source repository.
The DisplayShelf is a Cover Flow style implementation of PerspectiveTransform animations, so its not exactly the same as a full image flip. But many of the principles are the same, so you should be able to study the DisplayShelf example, then develop the code which you need to fit your requirement.
Related image flipping question for JavaFX => Flip a card animation.

Categories