Ball that fades away as it goes to the right - java

My task is to write a program that makes a circle ball that gradually fades away as the ball moves to the right. But it's not working, I can make the ball move when the mouse is dragged but the opacity is the same. Can you guys help me? I don't know how to convert the opacity value into double
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.scene.paint.Color;
public class Project3 extends Application
{
public static void main(String[] args) {
launch(args);
}
public void start(Stage primaryStage) {
Pane root = createRootPane();
Scene scene1 = new Scene(root);
primaryStage.setScene(scene1);
primaryStage.setTitle(" Hai Vo ");
primaryStage.show();
}
public Pane createRootPane()
{
Circle ball = new Circle (100,50,25);
Pane root = new Pane(ball);
root.setMinSize(300,300);
root.setOnMouseDragged (
event ->
{
double x = event.getX();
ball.setCenterX(event.getX());
ball.setCenterY(event.getY());
ball.opacityProperty().bind(ball.centerXProperty());
double opacity = ball.opacityProperty();
ball.setOpacity(opacity);
} );
return root;
}
}

First, move the binding out of the event handler. The binding will ensure the opacity is always updated when the centerX property updates.
Second, don't set bound values; the last two lines of the event handler just set the opacity to its current value anyway.
Third, the opacity should be between 0 and 1. You want it to be 1 when centerX is 0 and 0 when centerX is 300 (or, generally, the width of the pane).
What you need is (in pseudocode)
opacity = 1 - ball.centerX / root.width
= (ball.centerX / root.width) * (-1) + 1
which you can express in bindings with
ball.centerXProperty()
.divide(root.widthProperty())
.multiply(-1)
.add(1)
So put together, you need:
public Pane createRootPane() {
Circle ball = new Circle (100,50,25);
Pane root = new Pane(ball);
ball.opacityProperty().bind(
ball.centerXProperty()
.divide(root.widthProperty())
.multiply(-1)
.add(1)
);
root.setMinSize(300,300);
root.setOnMouseDragged (
event ->
{
ball.setCenterX(event.getX());
ball.setCenterY(event.getY());
} );
return root;
}

Related

JavaFX TranslateTransition doesn't go to the point definied

I want my blue circle go from one light blue circle to another, but only does half.
The coordinates appear being the same:
initial_lightblue: 211.7,230.5
blue: 193.7,239.1
final_lighblue: 193.7,239.1
My code
TranslateTransition transition = new TranslateTransition();
transition.setToX(c.getX());
transition.setToY(c.getY());
transition.setNode(view);
transition.setInterpolator(Interpolator.LINEAR);
transition.play();
Any sugestions?
From documentation
TranslateTransition : This Transition creates a move/translate animation that spans its duration. This is done by updating the
translateX, translateY and translateZ variables of the node at regular
interval.
So it's going to use the translate properties in order to relocate the actual node inside your pane. With that being said if you locate your node at x=50 and y=50 by setting its layoutX and layoutY properties their translation values will be 0, so if you actually try to set the end coordinates of the TranslateTransition to be ex. x = 100 y = 100 then its going to move your Node to the x = 150 and y = 150 and not to the (x,y) = (100,100) cause its going to change the translation property (x and y) from 0 to 100 which will move the node eventually to (x,y) = 150,150.
With that been said here is an example :
import javafx.animation.Interpolator;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Test extends Application {
private Circle c1;
private Circle c2;
#Override
public void start(Stage stage) throws Exception {
AnchorPane pane = new AnchorPane();
// Set only the radius
c1 = new Circle(5);
c1.setFill(Color.BLUE);
// Let's translate the c1 to the location we want
c1.setTranslateX(50);
c1.setTranslateY(60);
// The same for circle2
c2 = new Circle(5);
c2.setFill(Color.RED);
c2.setTranslateX(120);
c2.setTranslateY(200);
pane.getChildren().addAll(c1, c2);
pane.setOnMouseClicked(e -> {
startAnimation();
});
stage.setScene(new Scene(pane, 400, 500));
stage.show();
}
private void startAnimation() {
TranslateTransition transition = new TranslateTransition();
transition.setNode(c1);
transition.setToX(c2.getTranslateX());
transition.setToY(c2.getTranslateY());
transition.setInterpolator(Interpolator.LINEAR);
transition.play();
}
public static void main(String[] args) {
launch(args);
}
}
P.S : In your code example you are referring to circles but you call c.getX() to actually take the x of the circle, that is quite strange cause the Circle class doesn't have method getX() I am quessing you are referreing to getCenterX() or those are not Circles .

how to animate arcs in Javafx

I'm working on a simple app and it features a pie chart. My goal is that if a user hovers their mouse over any section of the chart, it will expand and present more information. In my program, the chart is made of 3 arcs. Here's my code.
import javafx.scene.Group; //Maybe too many imports, I just use em all
import javafx.scene.Scene; //because I'm lazy
import javafx.stage.Stage;
import javafx.util.Duration;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.Rectangle;
import javafx.event.EventHandler;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.application.Application;
import javafx.scene.shape.*;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.input.MouseEvent;
import javafx.animation.ScaleTransition;
import java.lang.Thread;
public class AnimatingDemo extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
//create 3 arc variables
Arc arc1 = new Arc();
Arc arc2 = new Arc();
Arc arc3 = new Arc();
//set up arc1 in place and to right color
arc1.setFill(Color.rgb(35,25,43,1.0));
arc1.setCenterX(250);
arc1.setCenterY(250);
arc1.setRadiusX(100.0f);
arc1.setRadiusY(100.0f);
arc1.setStartAngle(315);
arc1.setLength(-90);
arc1.setType(ArcType.ROUND);
//set up and color arc2
arc2.setFill(Color.rgb(39,70,144,1.0));
arc2.setCenterX(250);
arc2.setCenterY(250);
arc2.setRadiusX(100.0f);
arc2.setRadiusY(100.0f);
arc2.setStartAngle(90);
arc2.setLength(-135);
arc2.setType(ArcType.ROUND);
//set up and color arc3
arc3.setFill(Color.rgb(54,65,86,1.0));
arc3.setCenterX(250);
arc3.setCenterY(250);
arc3.setRadiusX(100.0f);
arc3.setRadiusY(100.0f);
arc3.setStartAngle(225);
arc3.setLength(-135);
arc3.setType(ArcType.ROUND);
//create root group
Group root = new Group();
//set up window
//add nodes to root
root.getChildren().addAll(arc1,arc2,arc3);
Scene scene = new Scene(root, 500,500);
stage.setTitle("Testing arc animation");
stage.setScene(scene);
stage.show();
}
}
This is just a sample I made so I can recreate the problem, but it gets the point across. I did research about various methods of animation in Javafx. The animation class seemed most viable, so I tried it in my program. I used the following code:
arc1.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
ScaleTransition st = new ScaleTransition(Duration.millis(1500),arc1);
st.setByX(0.3);
st.setByY(0.3);
st.setCycleCount(1);
st.setAutoReverse(false);
st.play();
}
});
I repeated it 3 times for each arc but that's redundant here.
Anyway, the result is that both ends of the arc scale, so the pi chart looks messy and isn't centered anymore, also the scaling increases depending on the size of the arc so it's inconsistent.
I then decided to move onto a more basic method, using thread.sleep().
arc1.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
for(int a = 0; a < 10; a++) {
try {
arc1.setRadiusX(arc1.getRadiusX() + 1);
arc1.setRadiusY(arc1.getRadiusY() + 1);
Thread.sleep(100);
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
}
});
this doesn't work either, the circle just instantly expands by the given amount of units I wanted (instantly).
So my question is, what can I do? Is there a method to prevent the skewing in the animation class? Can I make the thread.sleep animation fluent in some way? Any advice helps, thank you for your time!
P.S. If it needs more comments let me know
I'm not sure this is exactly what you are looking for, since you went with Arc's instead of a proper PieChart, but I used the PieChart class and did everything you did and it works out just fine.
Here I took the PieChart.Data class and made three separate ones just for testing.
I added an EventFilter by calling .getNode() on the PieChart.Data variable, and then just pasted your method you supplied above.
Again, I assume there was a reason you used Arc instead of PieChart, but I got it to work this way.
#Override
public void start(Stage stage) {
ObservableList<PieChart.Data> data = FXCollections.observableArrayList();
PieChart pieChart = new PieChart(data);
PieChart.Data one = new PieChart.Data("one", 50.0);
PieChart.Data two = new PieChart.Data("two", 33.0);
PieChart.Data three = new PieChart.Data("three", 17.0);
data.addAll(one, two, three);
one.getNode().addEventFilter(MouseEvent.MOUSE_PRESSED, mouseEvent -> {
ScaleTransition st = new ScaleTransition(Duration.millis(1500),one.getNode());
st.setByX(0.3);
st.setByY(0.3);
st.setCycleCount(1);
st.setAutoReverse(false);
st.play();
});
two.getNode().addEventFilter(MouseEvent.MOUSE_PRESSED, mouseEvent -> {
ScaleTransition st = new ScaleTransition(Duration.millis(1500),two.getNode());
st.setByX(0.3);
st.setByY(0.3);
st.setCycleCount(1);
st.setAutoReverse(false);
st.play();
});
three.getNode().addEventFilter(MouseEvent.MOUSE_PRESSED, mouseEvent -> {
ScaleTransition st = new ScaleTransition(Duration.millis(1500),three.getNode());
st.setByX(0.3);
st.setByY(0.3);
st.setCycleCount(1);
st.setAutoReverse(false);
st.play();
});
//create root group
Group root = new Group();
//set up window
//add nodes to root
root.getChildren().addAll(pieChart);
Scene scene = new Scene(root, 500,500);
stage.setTitle("Testing arc animation");
stage.setScene(scene);
stage.show();
}

How can I change the starting position of a node on PathTransition in JavaFX?

I'm learning about JavaFX by trying to build a (very) small platforming game. I thought it might be a good idea to simulate jumping by using PathTransition along an Arc shape. Like this:
import javafx.util.Duration;
import javafx.animation.PathTransition;
import javafx.scene.shape.Arc;
public void jump() {
PathTransition jump = new PathTransition();
Arc path = new Arc(
figure().getX() + 20, //figure is a custom shape
figure().getY() + figure().getHeight() / 2,
20, 80, 360, 180);
jump.setPath(path);
jump.setNode(figure);
jump.setAutoReverse(false);
jump.setDuration(Duration.millis(3000));
jump.setCycleCount(1);
jump.play();
}
Event handler:
figure.setOnKeyPressed((e) -> {
if (e.getCode() == KeyCode.SPACE) {
jump();
}
});
I discovered that doing this actually makes my figure jump BACKWARDS because the animation starts at the right end of the arc and ends at the left end. I tried looking through the documentation for Arc and PathTransition but couldn't find anything to help me solve the problem. What am I missing here?
The last two values you provide to the Arc constructor are the start angle and angular length of the arc (in degrees). Angles are measured relative to the positive x-axis in a counter-clockwise direction. So you are starting at 360 degrees (which of course is equivalent to 0 degrees: i.e. (centerX + radiusX, centerY)) and heading counter-clockwise (up and left) for a half circle. To jump right, I think you want the start angle to be 180 and the angular length to be -180 (negative to rotate clockwise, a half-ellipse).
Note the PathTransition moves the center of the node along the path, by changing its translateX and translateY properties. So to do a half-turn clockwise from the negative x-axis to the positive x-axis, you need centerX to be the initial horizontal center of the shape, plus the radiusX, and centerY to be the initial vertical center of the shape.
Here's a SSCCE that jumps the way you want (I think...). I used set methods on the Arc instead of the constructor, just for clarity as what each value is doing. You can achieve the same with the constructor if you prefer.
import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ArcTransitionTest extends Application {
private final double xJumpRadius = 20 ;
private final double yJumpRadius = 80 ;
#Override
public void start(Stage primaryStage) {
Rectangle rect = new Rectangle(50, 200, 50, 100);
rect.setFill(Color.CORNFLOWERBLUE);
Button left = new Button("<");
left.setOnAction(e -> jumpLeft(rect));
Button right = new Button(">");
right.setOnAction(e -> jumpRight(rect));
HBox controls = new HBox(5, left, right);
controls.setPadding(new Insets(10));
controls.setAlignment(Pos.CENTER);
Pane pane = new Pane(rect);
BorderPane root = new BorderPane(pane, null, null, controls, null);
primaryStage.setScene(new Scene(root, 600, 600));
primaryStage.show();
}
private void jumpRight(Rectangle rect) {
jump(rect, 180, -180, getHorizontalCenter(rect) + xJumpRadius, getVerticalCenter(rect));
}
private void jumpLeft(Rectangle rect) {
jump(rect, 0, 180, getHorizontalCenter(rect) - xJumpRadius, getVerticalCenter(rect));
}
private void jump(Rectangle rect, double startAngle, double angularLength, double centerX, double centerY) {
Arc arc = new Arc();
arc.setCenterX(centerX);
arc.setCenterY(centerY);
arc.setRadiusX(xJumpRadius);
arc.setRadiusY(yJumpRadius);
arc.setStartAngle(startAngle);
arc.setLength(angularLength);
PathTransition transition = new PathTransition(Duration.seconds(1), arc, rect);
transition.playFromStart();
}
private double getHorizontalCenter(Rectangle rect) {
return rect.getX() + rect.getTranslateX() + rect.getWidth() / 2 ;
// Alternatively:
// Bounds b = rect.getBoundsInParent();
// return (b.getMinX() + b.getMaxX()) / 2 ;
}
private double getVerticalCenter(Rectangle rect) {
return rect.getY() + rect.getTranslateY() + rect.getHeight() / 2 ;
// Alternatively:
// Bounds b = rect.getBoundsInParent();
// return (b.getMinY() + b.getMaxY()) / 2 ;
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX Container Draggable

i have a draggable container in JavaFX. This Container is implemented in a PopUp. I can drag the Container, but if i drag it, the mouse-event hasn't a constant coordinate. There switchs the mouse position very fast between 2 fix Positions.
Thats my code:
container.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent me) {
// TODO Auto-generated method stub
if(dragAct==true){
//Set the Position of the PopUp to the Position of the Mouse
setX(me.getX());
setY(me.getY());
}
}
});
The containe is a VBox. The Main-Class is a extended Version of the PopUp-Class.
JavaFX Container Draggable
The setX and setY methods you call set the position of the Popup in screen coordinates. The calls to me.getX() and me.getY() give you the coordinates of the mouse relative to the container. When you move the popup, the container also moves, so the position of the mouse has changed relative to the container. So your calculations are not going to be consistent from one dragging event to the next.
The fix is to compute the positions relative to something that is fixed. Since you are moving the popup, which is a window, the fixed coordinate system is the screen coordinate system. MouseEvent has getScreenX and getScreenY methods you can use to easily get these.
I like to implement dragging by saving the last mouse location and then computing the distance moved on drag. There are other (possibly less verbose) ways to do this but to me this is clearest:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Popup;
import javafx.stage.Stage;
public class DraggingPopup extends Application {
#Override
public void start(Stage primaryStage) {
Button button = new Button("Show popup");
button.setOnAction(event -> showDraggablePopup(primaryStage));
StackPane root = new StackPane(button);
Scene scene = new Scene(root, 250, 75);
primaryStage.setScene(scene);
primaryStage.show();
}
private void showDraggablePopup(Stage owner) {
Popup popup = new Popup();
Button closeButton = new Button("Close");
closeButton.setOnAction(event -> popup.hide());
StackPane container = new StackPane(closeButton);
container.setStyle("-fx-background-color: steelblue;");
container.setMinWidth(300);
container.setMinHeight(125);
// Dragging implementation:
ObjectProperty<Point2D> mouseLocation = new SimpleObjectProperty<>();
container.setOnMousePressed(event ->
mouseLocation.set(new Point2D(event.getScreenX(), event.getScreenY())));
container.setOnMouseDragged(event -> {
if (mouseLocation.get() != null) {
double x = event.getScreenX();
double deltaX = x - mouseLocation.get().getX() ;
double y = event.getScreenY();
double deltaY = y - mouseLocation.get().getY() ;
//in case of 2 or more computer screens this help me to avoid get stuck on 1 screen
if(Math.abs(popup.getX()-x)>popup.getWidth()){
popup.setX(x);
popup.setY(y);
}else {
popup.setX(popup.getX() + deltaX);
popup.setY(popup.getY() + deltaY);
}
mouseLocation.set(new Point2D(x, y));
}
});
container.setOnMouseReleased(event -> mouseLocation.set(null));
popup.getScene().setRoot(container);
popup.show(owner);
}
public static void main(String[] args) {
launch(args);
}
}

How to know if the resizing of a window is done from the left or from the right?

I am using JavaFX to build a GUI and I'm having problems on knowing the mouse location when resized. The idea is that if a mouse gets on the very edge of the GUI it changes to a double sided arrow indicating that you can now press the mouse and resize the window.
I need the location of the mouse pointer on this edge, but I don't know how to do that. I need to know in which direction the window is resized.
Updated Added new options and discussed pros and cons.
This is tricky. The issue is that the window keeps track of its top, left, width, and height. When it is resized from the right or bottom, things are easy enough: the width or height change. But when it is resized from the left, both x and width must change. These two changes do not happen atomically, as x and width are stored as two independent properties.
The first approach to this is to keep track of the mouse coordinates, and just see if it's in the left half or the right half. (Obviously you can do the same with the height.) This approach is independent of any implementation details, but the user can cause it to fail by being extremely careful with the mouse. If you move the mouse to the right edge of the window to the exact pixel of the window boundary, then resize, you can see incorrect output.
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
class MouseLocation {
double x,y ;
}
MouseLocation mouseLocation = new MouseLocation();
scene.setOnMouseMoved(event -> {
mouseLocation.x = event.getX();
mouseLocation.y = event.getY();
});
primaryStage.widthProperty().addListener((obs, oldWidth, newWidth) -> {
if (mouseLocation.x < primaryStage.getWidth() / 2) {
System.out.println("Resized from left");
} else {
System.out.println("Resized from right");
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The second approach is to keep track of the last known horizontal range of the window (minX and maxX), and update that range when the width changes. Then you can check to see whether the minX or maxX has changed. The problem with this approach is that it's dependent on undocumented implementation details. It appears (on my system, using the current version, etc) that when the window is resized from the left, x is changed first, then the width is changed. If that were to change in a subsequent release, the following would break:
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
class MutableDouble {
double value ;
}
MutableDouble windowLeftEdge = new MutableDouble();
primaryStage.widthProperty().addListener((obs, oldWidth, newWidth) -> {
if (primaryStage.getX() == windowLeftEdge.value) {
System.out.println("Resized from right");
} else {
System.out.println("Resized from left");
}
windowLeftEdge.value = primaryStage.getX() ;
});
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The third approach (that I can think of) is to coalesce changes that happen quickly into a single change. This is a bit tricky to program correctly, so instead of doing it from scratch, I used the third party ReactFX framework which models "event streams" and has a built-in mechanism for combining events that happen in quick succession. This is probably the most robust of the three solutions presented here, but at the cost of either a degree of complexity, or the inclusion of an external framework.
import java.time.Duration;
import org.reactfx.Change;
import org.reactfx.EventStream;
import org.reactfx.EventStreams;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ReactFXVersion extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
ObservableValue<Bounds> windowBounds = Bindings.createObjectBinding(() ->
new BoundingBox(primaryStage.getX(), primaryStage.getY(), primaryStage.getWidth(), primaryStage.getHeight()),
primaryStage.xProperty(), primaryStage.yProperty(), primaryStage.widthProperty(), primaryStage.heightProperty());
EventStream<Change<Bounds>> bounds = EventStreams.changesOf(windowBounds)
.reduceSuccessions((previousChange, nextChange) ->
new Change<>(previousChange.getOldValue(), nextChange.getNewValue()),
Duration.ofMillis(10));
bounds.subscribe(boundsChange -> {
Bounds newBounds = boundsChange.getNewValue();
Bounds oldBounds = boundsChange.getOldValue();
if (newBounds.getWidth() != oldBounds.getWidth()) {
if (newBounds.getMinX() != oldBounds.getMinX()) {
System.out.println("Resized from left");
} else if (newBounds.getMaxX() != oldBounds.getMaxX()) {
System.out.println("Resized from right");
}
}
if (newBounds.getHeight() != oldBounds.getHeight()) {
if (newBounds.getMinY() != oldBounds.getMinY()) {
System.out.println("Resized from top");
} else if (newBounds.getMaxY() != oldBounds.getMaxY()) {
System.out.println("Resized from bottom");
}
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Categories