I have a polygon that I want to grow in size when the window is resized. I have a DoubleBinding that it the baseline for the size of all of my shapes in the scene. I use this DoubleBinding to create a ListBinding that calculates how big the polygon should be. I tried to use Bindings.bindContent(list1, list2) to bind the ListBinding to the polygon's points, but it doesn't seem to work.
Polygon triangle = new Polygon();
ListBinding<Double> trianglePoints = new ListBinding<Double>() {
{
super.bind(width, height);
}
#Override
protected ObservableList<Double> computeValue() {
ObservableList<Double> points = FXCollections.observableArrayList();
points.addAll(0.0, 0.0, width.get()/2, height.get()/4, 0.0, height.get()/2);
return points;
}
};
Bindings.bindContent(triangle.getPoints(), trianglePoints.get());
I found that if I add an InvalidationListener to the triangle points and manually change the the polygon points when it fires off, it works.
trianglePoints.addListener((InvalidationListener)(listener) -> triangle.getPoints().setAll(trianglePoints.get()));
What am I doing wrong?
You should change your binding in following way:
Bindings.bindContent(triangle.getPoints(), trianglePoints);
Your previous binding did not work because trianglePoints.get() is just result of computeValue() call i.e. creating ObservableList with some values calculated upon initial width and height. Thus further changes to width and height properties were not taken into account.
I recommend not using a ListBinding. You do not need to recreate the list over and over again, you just need to modify some of the points:
#Override
public void start(Stage primaryStage) {
Polygon triangle = new Polygon(
0, 0,
0, 0,
0, 0);
StackPane root = new StackPane();
root.getChildren().add(triangle);
root.heightProperty().addListener((observable, oldH, newH) -> {
double h = newH.doubleValue();
triangle.getPoints().set(3, h / 4);
triangle.getPoints().set(5, h / 2);
});
root.widthProperty().addListener((observable, oldW, newW) -> {
double w = newW.doubleValue();
triangle.getPoints().set(2, w / 2);
});
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
Furthermore it's also possible to do this using the scale properties (unless you're using a stroke or some exotic fill):
#Override
public void start(Stage primaryStage) {
Polygon triangle = new Polygon(
0, 0,
0.5, 0.25,
0, 0.5);
StackPane root = new StackPane();
root.getChildren().add(triangle);
triangle.scaleXProperty().bind(root.widthProperty());
triangle.scaleYProperty().bind(root.heightProperty());
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
Related
I have a few rectangles that I assign rotation one by one ((javafx.scene.shape.Rectangle) shape).rotateProperty().bind(rotate);
For example, 45 degrees
My rectangle rotates around its center. Please tell me how to make the enemy a few right-angles around their common center.
To get something like this
this.rotate.addListener((obs, old, fresh) -> {
for (VObject vObject : children ) {
vObject.rotate.set(this.rotate.get());
}
});
This is how I add rotation. How can I specify the angle
Update: I used the advice below and now I set the rotation individually for each rectangle. (The selection is still a bit wrong)
this.rotate.addListener((obs, old, fresh) -> {
Rotate groupRotate = new Rotate(rotate.get(),
this.x.getValue().doubleValue() + this.width.getValue().doubleValue() / 2 ,
this.y.getValue().doubleValue() + this.height.getValue().doubleValue() / 2);
for (VObject vObject : children ) {
vObject.getShape().getTransforms().clear();
vObject.getShape().getTransforms().add(groupRotate);
}
});
But now the axis also rotates depending on the rotation.
Can I set the rotation to the rectangles without turning the coordinate axis?
If you put all rectangles into a common Group, you can rotate them at once:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class RotateAllApplication extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
// the common group
Group group = new Group();
group.getChildren().addAll(new Rectangle(10, 10, 80, 40), //
new Rectangle(110, 10, 80, 40), //
new Rectangle(10, 110, 80, 40), //
new Rectangle(110, 110, 80, 40));
// rotate the group instead of each rectangle
group.setRotate(45.0);
root.setCenter(group);
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
Update: If you don't want to create a parent Group object, you can apply the same rotation transformation to each child instead. While Node#setRotate(double) always rotates around the center of the node, adding a transformation to Node#getTransforms() is more general and not restricted to simple rotations.
The following statement will apply a rotation around the point (100.0/100.0) of the parent coordinate system to all children in the list:
childrenList.forEach(child -> child.getTransforms().add(Transform.rotate(45.0, 100.0, 100.0)));
Use a Rotate transform and specify the appropriate pivot point:
#Override
public void start(Stage primaryStage) throws IOException {
Pane pane = new Pane();
pane.setPrefSize(600, 600);
Rectangle[] rects = new Rectangle[4];
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
Rectangle rect = new Rectangle(100 + i * 200, 100 + j * 100, 150, 50);
rects[i * 2 + j] = rect;
pane.getChildren().add(rect);
}
}
Slider slider = new Slider(0, 360, 0);
double minX = Double.POSITIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
Rotate rotate = new Rotate();
// find pivot point
for (Rectangle rect : rects) {
double val = rect.getX();
if (minX > val) {
minX = val;
}
val += rect.getWidth();
if (maxX < val) {
maxX = val;
}
val = rect.getY();
if (minY > val) {
minY = val;
}
val += rect.getHeight();
if (maxY < val) {
maxY = val;
}
rect.getTransforms().add(rotate);
}
rotate.setPivotX(0.5 * (maxX + minX));
rotate.setPivotY(0.5 * (maxY + minY));
rotate.angleProperty().bind(slider.valueProperty());
Scene scene = new Scene(new VBox(10, pane, slider));
primaryStage.setScene(scene);
primaryStage.sizeToScene();
primaryStage.show();
}
If you're planing to apply multiple transformations, you may need to adjust the code for finding the pivot point to use transforms for calculating the bounds...
I am trying to set a panel that contains several points and then draw a triangle or any others forms. And all points that are inside that form will switch their colors to red.
Seems like i am doing something wrong, is this the correct way to draw point inside the for loop ?
Make sure the Polygon is created before creating the circles. This allows you to use check for intersection of the shapes as described in this answer: https://stackoverflow.com/a/15014709/2991525
Choose the circle fill accordingly:
#Override
public void start(Stage stage) {
Group root = new Group();
Polygon triangle = new Polygon(300d, 100d, 600d, 150d, 500d, 300d);
root.getChildren().add(triangle);
Scene scene = new Scene(root, 900, 500);
for (double x = 65; x < scene.getWidth(); x += 65) {
for (double y = 65; y < scene.getHeight(); y += 65) {
Circle circle = new Circle(x, y, 10);
root.getChildren().add(circle);
Shape intersection = Shape.intersect(circle, triangle);
//Setting the color of the circle
circle.setFill(intersection.getBoundsInLocal().getWidth() == -1 ? Color.BLACK : Color.RED);
}
}
triangle.setFill(Color.TRANSPARENT);
triangle.setStroke(Color.RED);
triangle.toFront();
stage.setTitle("Scale transition example");
stage.setScene(scene);
stage.show();
}
I created a program that randomly draws circles,squares,rectangles. My problem is I'm getting the click position with event.getX() and event.getY();
Here is the problem:
Green point is the where I click the Canvas.
↑→ axis must be like this. But in my program it stands like this ↓→
Here is the details:
Getting mouse position with (extending Canvas):
super.setOnMouseClicked(event -> selectFigureContaining(event.getX(),event.getY()));
Initializing part :
#Override
public void start(Stage primaryStage) {
this.color = Color.BLACK;
toggleGroup = new ToggleGroup();
hBox = new HBox();
vBox = new VBox();
label = new Label();
vBox.setPadding(new Insets(5,15,0,15));
vBox.setSpacing(10);
hBox.setPadding(new Insets(5,0,0,0));
//Radio Buttons
red = new RadioButton("Red");
red.setSelected(true);
green = new RadioButton("Green");
blue = new RadioButton("Blue");
red.setToggleGroup(toggleGroup);
green.setToggleGroup(toggleGroup);
blue.setToggleGroup(toggleGroup);
//Buttons
buttonCircle = new Button("Circle");
buttonCircle.setMinSize(70,20);
buttonCircle.setOnMouseClicked(event -> circleButtonClicked());
buttonSquare = new Button("Square");
buttonSquare.setMinSize(70,20);
buttonSquare.setOnMouseClicked(event -> squareButtonClicked());
buttonRectangle = new Button("Rectangle");
buttonRectangle.setMinSize(70,20);
buttonRectangle.setOnMouseClicked(event -> rectangleButtonClicked());
cleanAll = new Button("Clean All");
cleanAll.setMinSize(70,20);
cleanAll.setOnMouseClicked(event -> cleanAllEvent());
figureCanvas = new FigureCanvas(10,400,500, this);
vBox.getChildren().addAll(red,green,blue,buttonCircle,buttonSquare,buttonRectangle,cleanAll,label);
hBox.getChildren().addAll(vBox,figureCanvas);
Scene scene = new Scene(hBox,500,500);
primaryStage.setScene(scene);
primaryStage.setTitle("Draw Figure");
primaryStage.show();
}
I'm really new to JavaFX. I couldn't understand why y axis getting smaller on left upper side. Thanks.
Normally, the coordinate system's origin is at the top left of the screen and positive y is downwards:
. ---> +x
|
v +y
You can transform your y position like this to get expected movement (origin at the bottom left, +y goes up):
y = canvas.getHeight() - y;
Here is my following code:
public void start(Stage primaryStage) throws Exception {
Pane root = new Pane();
Scene scene = new Scene(root, 500, 600);
Random rand = new Random();
int random = rand.nextInt(100-5)+5;
Circle circle = new Circle(0, 10, random);
circle.setFill(Color.RED);
root.getChildren().add(circle);
Timeline timeline = new Timeline(
new KeyFrame(
Duration.seconds(2),
new KeyValue(circle.translateXProperty(), 600)
));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.setAutoReverse(false);
timeline.play();
primaryStage.setScene(scene);
primaryStage.show();
}
In this code it only update the random value once, but I want it to update random value after each cycle. Is there any way I can update my random value after each cycle? Any help is appreciated!
If you're using the Timeline
It is not possible to change the keyFrames of a running Timeline. If
the value of keyFrames is changed for a running Timeline, it has to be
stopped and started again to pick up the new value.
So, you can recreate the timeline everytime you want to create a new keyFrame, in your case with a new Circle(0, 10, new Random().nextInt(100-5)+5, Color.RED);
There is a constructor for Keyframe which takes multiple KeyValue. You can add to the constructor a new KeyValue with the radius of circle. As far as I can see you should create your own implementation of Interpolator to make alive the random behaviour for the radius. (Maybe there are third party solutions for that)
You should add a EventHandler<ActionEvent> to your call to the KeyValue constructor.
That is, add a parameter to new KeyValue(circle.translateXProperty(), 600) so it looks something like new KeyValue(circle.translateXProperty(), (event)->{circle.setRadius(rand.nextInt(100-5)+5);}, 600)
The default Interpolater(EASE_BOTH) clamps the value of time for current interval i.e. frac value will range from [0.0, ... , 1.0]. You can use the same information and update the radius at the beginning of the new cycle.
public void start(Stage primaryStage) {
Pane root = new Pane();
Scene scene = new Scene(root, 500, 600);
final Random random = new Random();
Circle circle = new Circle(0, 0, random.nextInt(100));
circle.setFill(Color.RED);
root.getChildren().add(circle);
Animation animation = new Transition() {
{
setCycleCount(INDEFINITE);
setCycleDuration(Duration.seconds(2));
}
#Override
protected void interpolate(double frac) {
if (frac == 0) {
final int newRadius = random.nextInt(100);
System.out.println("New Radius: " + newRadius);
circle.setRadius(newRadius + 5);
}
circle.setTranslateX(frac * 600);
}
};
animation.play();
primaryStage.setScene(scene);
primaryStage.show();
}
Add a EventHandler<ActionEvent> that updates the radius property to the last KeyFrame of the Timeline:
final Random rand = new Random();
int random = rand.nextInt(100-5)+5;
final Circle circle = new Circle(0, 10, random);
circle.setFill(Color.RED);
root.getChildren().add(circle);
Timeline timeline = new Timeline(
new KeyFrame(
Duration.seconds(2),
event -> circle.setRadius(rand.nextInt(100-5)+5),
new KeyValue(circle.translateXProperty(), 600)
));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.setAutoReverse(false);
timeline.play();
Not sure if this will slow down your code, but you might want to try setting cycle count to 1 instead of Animation.INDEFINITE and wrapping the entire block in a while loop:
public void start(Stage primaryStage) throws Exception {
Pane root = new Pane();
Scene scene = new Scene(root, 500, 600);
Random rand = new Random();
primaryStage.setScene(scene);
primaryStage.show();
while (true) {
int random = rand.nextInt(100-5)+5;
Circle circle = new Circle(0, 10, random);
circle.setFill(Color.RED);
root.getChildren().add(circle);
Timeline timeline = new Timeline(new KeyFrame(
Duration.seconds(2),
new KeyValue(circle.translateXProperty(), 600)
));
timeline.setCycleCount(1);
timeline.setAutoReverse(false);
timeline.play();
}
}
One issue is that the primaryStage.show() will get moved up (so that it still runs despite the forever loop). Consider changing the while (true) to something else if this becomes a problem.
For a Circle Path Animation I copied the Code from this link
JavaFX 2 circle path for animation,
But I need this Code to do the following:
I would like to move the green rectangle with my MouseWheel, I'm not sure if and how this works with a ScrollEvent.
With a Button "Add Rectangle" I would like to add some rectangles to this Circle path
Any help would be greatly appreciated
I actually forgot to put an answer to this, so here it is:
public void start(Stage primaryStage) throws Exception {
// Choose a Object to move around the Path
Circle objectCircle = new Circle();
objectCircle.setRadius(10);
// Use a Shape for the Path or...
Circle pathCircle = new Circle(300, 200, 150);
pathCircle.setFill(null);
pathCircle.setStroke(Color.BLACK);
// ...use different approach (see createEllipsePath below) or...
Path path = createEllipsePath(400, 200, 150, 150, 0);
// ...use Class MoveTo & CubicCurveTo
//path.getElements().add (new MoveTo (0f, 50f));
//path.getElements().add(new CubicCurveTo(40f, 10f, 390f, 240f, 1904, 50f));
// Set up a Path Transition
PathTransition pathTransitionCircle = new PathTransition();
pathTransitionCircle.setDuration(Duration.seconds(10));
pathTransitionCircle.setNode(objectCircle);
pathTransitionCircle.setPath(pathCircle);
pathTransitionCircle.setCycleCount(Timeline.INDEFINITE);
pathTransitionCircle.setAutoReverse(false);
pathTransitionCircle.setInterpolator(Interpolator.LINEAR);
pathTransitionCircle.play();
BorderPane root = new BorderPane();
root.getChildren().addAll(pathCircle,objectCircle);
// Pause Transition
root.setOnMousePressed(eve -> pathTransitionCircle.pause());
// Set up the ScrollEvent
root.setOnScroll(event -> {
//SCROLL UP
double currentTime = pathTransitionCircle.getCurrentTime().toMillis();
double jumpRate = 100; // change rate for smaller/bigger jumps
pathTransitionCircle.jumpTo(Duration.millis(currentTime + jumpRate));
//SCROLL DOWN
double deltaY = event.getDeltaY();
if (deltaY < 0) {
if (currentTime == 0) {
currentTime = 10000; // end of circle is 10000millis: at second 0 we cannot jump to negative seconds, so we set it to 10000millis
pathTransitionCircle.jumpTo(Duration.millis(currentTime - jumpRate));
} else {
pathTransitionCircle.jumpTo(Duration.millis(currentTime - jumpRate));
}
}
});
primaryStage.setTitle("Path Transition ScrollEvent");
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
}
//create an EllipsePath with ArcTo
private Path createEllipsePath(double centerX, double centerY, double radiusX, double radiusY, double rotate) {
ArcTo arcTo = new ArcTo();
arcTo.setX(centerX - radiusX + 1); // to simulate a full 360 degree circle
arcTo.setY(centerY - radiusY);
arcTo.setSweepFlag(false);
arcTo.setLargeArcFlag(true);
arcTo.setRadiusX(radiusX);
arcTo.setRadiusY(radiusY);
arcTo.setXAxisRotation(rotate);
Path pathCircle = new Path();
pathCircle.getElements().addAll(new MoveTo(centerX - radiusX, centerY - radiusY),arcTo);
pathCircle.getElements().add(new ClosePath());
// Styling
pathCircle.setStroke(Color.RED);
pathCircle.getStrokeDashArray().setAll(5d, 5d);
return pathCircle;
}
public static void main(String[] args) { launch(args); }
}