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...
Related
I have some code, in javafx, that has a pane that works like a paint canvas. I need to be able to fill the background color of the pane from a color picker.
Currently I have a Color variable that gets the color chosen from the color picker and I try to set it to my Pane (named canvas) as below:
Color newColour = backgroundColourPicker.getValue();
canvas.setStyle("-fx-background-color: " + newColour + ";");
However I get this output:
June 11, 2022 7:47:57 PM javafx.css.CssParser term
WARNING: CSS Error parsing '*{-fx-background-color: 0x00ffffff;}: Unexpected token '0x' at [1,24]
How do I either swap my Color value to a String to be able to remove the leading 0x and make it work or how do I get my Pane to accept the color value as a Color?
I have found this code worked for me if anyone needs it in the future:
Color newColour = backgroundColourPicker.getValue();
Double red = newColour.getRed()*100;
int rInt = red.intValue();
Double green = newColour.getGreen()*100;
int gInt = green.intValue();
Double blue = newColour.getBlue()*100;
int bInt = blue.intValue();
String hex = String.format("#%02X%02X%02X", rInt, gInt, bInt);
canvas.setStyle("-fx-background-color: " + hex + ";");
If you don't require all the features of the region's background—image, fills, etc.— and a solid color will suffice, also consider binding the color picker's value property to the fill color of a suitable Shape that fills the region. Based on the examples seen here, the variation below illustrates a ColorPane that renders a Rectangle whose size is bound to the enclosing Pane and whose color is bound to an ObjectProperty<Color> holding the rectangle's color. A similar arrangement is made for the foreground Circle. One benefit is that a custom color updates live as the the controls are adjusted. Several related examples are collected here.
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/q/72583321/230513
* #see https://stackoverflow.com/a/70312046/230513
*/
public class ColorTest extends Application {
private final class ColorPane extends Pane {
public static final Color bgColor = Color.BLUE;
public static final Color fgColor = Color.CYAN;
private final Rectangle r = new Rectangle();
private final Circle c = new Circle(8, fgColor);
private final ObjectProperty<Color> bg = new SimpleObjectProperty<>(bgColor);
private final ObjectProperty<Color> fg = new SimpleObjectProperty<>(fgColor);
private final InvalidationListener listener = (o) -> update();
public ColorPane() {
this.setPrefSize(256, 256);
r.widthProperty().bind(this.widthProperty());
r.heightProperty().bind(this.heightProperty());
r.setFill(bgColor);
this.getChildren().add(r);
c.centerXProperty().bind(widthProperty().divide(2));
c.centerYProperty().bind(heightProperty().divide(2));
NumberBinding diameter = Bindings.min(widthProperty(), heightProperty());
c.radiusProperty().bind(diameter.divide(2).subtract(diameter.divide(10)));
this.getChildren().add(c);
bg.addListener(listener);
fg.addListener(listener);
}
private void update() {
r.setFill(bg.get());
c.setFill(fg.get());
}
public ObjectProperty<Color> bgProperty() {
return bg;
}
public ObjectProperty<Color> fgProperty() {
return fg;
}
}
private Pane createControlPane(ColorPane view) {
ColorPicker bgPicker = new ColorPicker(view.bgProperty().get());
bgPicker.setTooltip(new Tooltip("Background color."));
view.bgProperty().bindBidirectional(bgPicker.valueProperty());
ColorPicker fgPicker = new ColorPicker(view.fgProperty().get());
fgPicker.setTooltip(new Tooltip("Foreground color."));
view.fgProperty().bindBidirectional(fgPicker.valueProperty());
return new VBox(16, bgPicker, fgPicker);
}
#Override
public void start(Stage stage) {
var root = new BorderPane();
ColorPane colorPane = new ColorPane();
root.setCenter(colorPane);
root.setLeft(createControlPane(colorPane));
stage.setScene(new Scene(root));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you do require features of the region's background, the following example illustrates dynamically replacing the background of a GradientPane with one that contains a LinearGradient based on the chosen color properties. The approach has the same benefit of live custom color updates as the the controls are adjusted, illustrated above.
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/q/72583321/230513
* #see https://stackoverflow.com/a/70312046/230513
*/
public class ColorTest extends Application {
private final class GradientPane extends Pane {
public static final Color c1Color = Color.BLUE;
public static final Color c2Color = Color.CYAN;
private final Circle c = new Circle(8, c2Color);
private final ObjectProperty<Color> c1 = new SimpleObjectProperty<>(c1Color);
private final ObjectProperty<Color> c2 = new SimpleObjectProperty<>(c2Color);
private final InvalidationListener listener = (o) -> update();
public GradientPane() {
this.setPrefSize(256, 256);
c.centerXProperty().bind(widthProperty().divide(2));
c.centerYProperty().bind(heightProperty().divide(2));
NumberBinding diameter = Bindings.min(widthProperty(), heightProperty());
c.radiusProperty().bind(diameter.divide(2).subtract(diameter.divide(10)));
this.getChildren().add(c);
c1.addListener(listener);
c2.addListener(listener);
update();
}
private void update() {
Stop[] stops = new Stop[]{new Stop(0, c1.get()), new Stop(1, c2.get())};
LinearGradient lg = new LinearGradient(0.5, 0, 0.5, 1, true, CycleMethod.NO_CYCLE, stops);
this.setBackground(new Background(new BackgroundFill(lg, CornerRadii.EMPTY, Insets.EMPTY)));
stops = new Stop[]{new Stop(0, c2.get()), new Stop(1, c1.get())};
lg = new LinearGradient(0.5, 0, 0.5, 1, true, CycleMethod.NO_CYCLE, stops);
c.setFill(lg);
}
public ObjectProperty<Color> c1Property() {
return c1;
}
public ObjectProperty<Color> c2Property() {
return c2;
}
}
private Pane createControlPane(GradientPane view) {
ColorPicker bgPicker = new ColorPicker(view.c1Property().get());
bgPicker.setTooltip(new Tooltip("Color stop one."));
view.c1Property().bindBidirectional(bgPicker.valueProperty());
ColorPicker fgPicker = new ColorPicker(view.c2Property().get());
fgPicker.setTooltip(new Tooltip("Color stop two."));
view.c2Property().bindBidirectional(fgPicker.valueProperty());
VBox vBox = new VBox(10, bgPicker, fgPicker);
vBox.setPadding(new Insets(10));
return vBox;
}
#Override
public void start(Stage stage) {
var root = new BorderPane();
GradientPane colorPane = new GradientPane();
root.setCenter(colorPane);
root.setLeft(createControlPane(colorPane));
stage.setScene(new Scene(root));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
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.
How can I draw shapes with delay?
For example at the beginning, draw few rectangles, after 1 second draw fillOval, after 3 seconds draw another oval and so on.
You are describing an animation. For that, you should use the Animation API.
E.g., using a Timeline:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ShapeAnimationExample extends Application {
private final Random rng = new Random();
#Override
public void start(Stage primaryStage) {
List<Rectangle> fewRectangles = new ArrayList<>();
for (int i = 0 ; i < 5 ; i++) {
Rectangle r = new Rectangle(rng.nextInt(300)+50, rng.nextInt(300)+50, rng.nextInt(100)+50, rng.nextInt(100)+50);
r.setFill(randomColor());
fewRectangles.add(r);
}
List<Ellipse> ovals = new ArrayList<>();
for (int i = 0 ; i < 5 ; i++) {
Ellipse e = new Ellipse(rng.nextInt(400)+50, rng.nextInt(400)+50, rng.nextInt(50)+50, rng.nextInt(50)+50);
e.setFill(randomColor());
ovals.add(e);
}
Pane pane = new Pane();
pane.setMinSize(600, 600);
Timeline timeline = new Timeline();
Duration timepoint = Duration.ZERO ;
Duration pause = Duration.seconds(1);
KeyFrame initial = new KeyFrame(timepoint, e -> pane.getChildren().addAll(fewRectangles));
timeline.getKeyFrames().add(initial);
for (Ellipse oval : ovals) {
timepoint = timepoint.add(pause);
KeyFrame keyFrame = new KeyFrame(timepoint, e -> pane.getChildren().add(oval));
timeline.getKeyFrames().add(keyFrame);
}
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
timeline.play();
}
private Color randomColor() {
return new Color(rng.nextDouble(), rng.nextDouble(), rng.nextDouble(), 1.0);
}
public static void main(String[] args) {
launch(args);
}
}
What I want to do is scale an image on a cylinder in Java3D. The code I have so far is shown below. I have no clue how to make the image not stretch over the whole cylinder.
package javafxapplication2;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.paint.Material;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Cylinder;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class EarthCylinder extends Application {
double anchorX, anchorY;
private double anchorAngleX = 0;
private double anchorAngleY = 0;
private final DoubleProperty angleX = new SimpleDoubleProperty(0);
private final DoubleProperty angleY = new SimpleDoubleProperty(0);
PerspectiveCamera scenePerspectiveCamera = new PerspectiveCamera(false);
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
// Remove this line once dirtyopts bug is fixed for 3D primitive
System.setProperty("prism.dirtyopts", "false");
launch(args);
}
#Override
public void start(Stage primaryStage) {
Image diffuseMap
= new Image(EarthCylinder.class
.getResource("download.jpg")
.toExternalForm());
PhongMaterial material = new PhongMaterial();
material.setDiffuseMap(diffuseMap);
final Cylinder cylinder = new Cylinder(200, 400);
cylinder.setMaterial(material);
final Group parent = new Group(cylinder);
parent.setTranslateX(450);
parent.setTranslateY(450);
parent.setTranslateZ(0);
Rotate xRotate;
Rotate yRotate;
parent.getTransforms().setAll(
xRotate = new Rotate(0, Rotate.X_AXIS),
yRotate = new Rotate(0, Rotate.Y_AXIS));
xRotate.angleProperty().bind(angleX);
yRotate.angleProperty().bind(angleY);
final Group root = new Group();
root.getChildren().add(parent);
final Scene scene = new Scene(root, 900, 900, true);
scene.setFill(Color.BLACK);
scene.setOnMousePressed(event -> {
anchorX = event.getSceneX();
anchorY = event.getSceneY();
anchorAngleX = angleX.get();
anchorAngleY = angleY.get();
});
scene.setOnMouseDragged(event -> {
angleX.set(anchorAngleX - (anchorY - event.getSceneY()));
angleY.set(anchorAngleY + anchorX - event.getSceneX());
});
PointLight pointLight = new PointLight(Color.WHITE);
pointLight.setTranslateX(400);
pointLight.setTranslateY(400);
pointLight.setTranslateZ(-3000);
scene.setCamera(scenePerspectiveCamera);
root.getChildren().addAll(pointLight, scenePerspectiveCamera);
primaryStage.setScene(scene);
primaryStage.show();
}
}
The result of the above code is shown here:
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.