The bind fuction only works for the initial value - javafx

I an trying to use the fuction 'bind' but it doesn't seem to work. What I want is to bind the position of a 'RectangleFx' (which extends from javafx.scene.shape.Box) to the one of a Rectangle (which is an object) so that if the coordinates of the position of the rectangle change, on the screen the box will move. The problem is that when I use 'bind' it only gives to RectangleFX the initial value of the positione and doesn't update it when I change the one of Rectangle.
The 'Rectangle' class is one I wrote myself, it is not javafx.scene.shape.Rectangle.
public class RectangleFX extends javafx.scene.shape.Box {
private Rectangle rectangle;
public RectangleFX(Rectangle r) {
super(r.getWidth(), r.getHeight(), 30);
this.rectangle = r;
this.translateXProperty().bind(r.getCenter().propertyX());
this.translateYProperty().bind(r.getCenter().propertyY());
}
public Rectangle getRect() {
return this.rectangle;
}
}
public class Rectangle extends Body {
Vec2 min;
Vec2 max;
public Rectangle(Vec2 pos, Vec2 spd, double mass, double density, double rest, Vec2 vel, Vec2 a, Vec2 b) {
super(pos, spd, vel, mass, density, rest);
this.min = a;
this.max = b;
}
public Rectangle(Vec2 pos, double mass, double rest, Vec2 a, Vec2 b) {
super(pos, mass * (b.getX() - a.getX()) * (b.getY() * a.getY()), mass, rest);
this.min = a;
this.max = b;
}
public double get_x_extent() {
return this.max.getX() - this.min.getX();
}
public double get_y_extent() {
return this.max.getY() - this.min.getY();
}
#Override
public double get_min_x() {
return this.min.getX();
}
#Override
public double get_min_y() {
return this.min.getY();
}
#Override
public double get_max_x() {
return this.max.getX();
}
#Override
public double get_max_y() {
return this.max.getY();
}
public double getWidth() {
return Math.abs(this.get_max_x() - this.get_min_x());
}
public double getHeight() {
return Math.abs(this.get_max_y() - this.get_min_y());
}
public Vec2 getCenter() {
return new Vec2(1 / 2 * this.getWidth() + this.getPosition().getX(), 1 / 2 * this.getHeight() + this.getPosition().getY());
}
boolean contact(Rectangle a, Rectangle b) {
if (a.max.getX() < b.min.getX() || a.min.getX() > b.max.getX())
return false;
if (a.max.getY() < b.min.getY() || a.min.getY() > b.max.getY())
return false;
return true;
}
}
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public class Vec2 {
private DoubleProperty x = new SimpleDoubleProperty();
private DoubleProperty y = new SimpleDoubleProperty();
public double getX() {
return this.x.get();
}
public double getY() {
return this.y.get();
}
public void setX(double value) {
this.x.set(value);
}
public void setY(double value) {
this.y.set(value);
}
public DoubleProperty propertyX() {
return this.x;
}
public DoubleProperty propertyY() {
return this.y;
}
public Vec2(double x, double y) {
this.setX(x);
this.setY(y);
}
public static final Vec2 null_Vector = new Vec2(0, 0);
public static final Vec2 x_Vector = new Vec2(1, 0);
public static final Vec2 y_Vector = new Vec2(0, 1);
public static Vec2 addition(Vec2 v1, Vec2 v2) {
return new Vec2(v1.getX() + v2.getX(), v1.getY() + v2.getY());
}
public void increase_by(Vec2 v) {
this.setX(this.getX() + v.getX());
this.setY(this.getY() + v.getY());
}
public static Vec2 substraction(Vec2 v1, Vec2 v2) {
return new Vec2(v1.getX() - v2.getX(), v1.getY() - v2.getY());
}
public void decrease_by(Vec2 v) {
this.setX(this.getX() - v.getX());
this.setY(this.getY() - v.getY());
}
public static Vec2 product(Vec2 v1, Vec2 v2) {
return new Vec2(v1.getX() * v2.getX(), v1.getY() * v2.getY());
}
public static Vec2 product(double d, Vec2 v) {
return new Vec2(d * v.getX(), d * v.getY());
}
public void multiply_by(Vec2 v) {
this.setX(this.getX() * v.getX());
this.setY(this.getY() * v.getY());
}
public void multiply_by(double d) {
this.setX(this.getX() * d);
this.setY(this.getY() * d);
}
public static Vec2 division(Vec2 v1, Vec2 v2) {
return new Vec2(v1.getX() / v2.getX(), v1.getY() / v2.getY());
}
public static Vec2 division(double d, Vec2 v) {
if (d == 0)
throw new ArithmeticException();
return new Vec2(v.getX() / d, v.getY() / d);
}
public static Vec2 opposite(Vec2 v) {
return new Vec2(-v.getX(), -v.getY());
}
public static Vec2 invert(Vec2 v) {
return new Vec2(1 / v.getX(), 1 / v.getY());
}
public double length() {
return Math.sqrt(this.length_Squared());
}
public double length_Squared() {
return (dot_Product(this, this));
}
public Vec2 copy() {
return new Vec2(this.getX(), this.getY());
}
public static boolean compare(Vec2 v1, Vec2 v2) {
if (v1 == null) {
if (v2 == null)
return true;
return false;
} else if (v2 == null)
return false;
if (v1.getX() != v2.getX())
return false;
if (v1.getY() != v2.getY())
return false;
return true;
}
public static double dot_Product(Vec2 v1, Vec2 v2) {
return (v1.getX() * v2.getX() + v1.getY() * v2.getY());
}
public static double cross_Product(Vec2 v1, Vec2 v2) {
return (v1.getX() * v2.getY() - v1.getY() * v2.getX());
}
public static Vec2 cross_Product(Vec2 v, double d) {
return new Vec2(d * v.getY(), -d * v.getX());
}
public static Vec2 cross_Product(double d, Vec2 v) {
return new Vec2(-d * v.getY(), d * v.getX());
}
public static boolean isNull(Vec2 v) {
if (v.getX() != 0)
return false;
if (v.getY() != 0)
return false;
return true;
}
public void normalize() {
double d = length_Squared();
this.setX(this.getX() / d);
this.setY(this.getY() / d);
}
public Vec2 projection_on(Vec2 v) {
return Vec2.substraction(this, Vec2.product(Vec2.dot_Product(this, v), this));
}
public Vec2 symetric_on(Vec2 v) {
return Vec2.substraction(Vec2.product(2, this.projection_on(v)), this);
}
}
Here is an example of code that does not work, when we press NUMPAD0 the cube should move on the screen since the coordinates of the center of the rectangle to which it is bound change.
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
public class Main extends Application {
private final double HEIGHT = 800;
private final double WIDTH = 1400;
#Override
public void start(Stage primaryStage) throws Exception {
Vec2 posb = new Vec2(1000, 700);
Vec2 minb = new Vec2(30, 0);
Vec2 maxb = new Vec2(0, 30);
Rectangle r = new Rectangle(posb, 30, 10, minb, maxb);
RectangleFX b = new RectangleFX(r);
Group boxG = new Group();
boxG.getChildren().add(b);
Group root = new Group(boxG);
Camera cam = new PerspectiveCamera();
Scene scene = new Scene(root, WIDTH, HEIGHT);
scene.setFill(Color.BLACK);
scene.setCamera(cam);
primaryStage.setTitle("Test position");
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
switch (event.getCode()) {
case NUMPAD0:
r.getPosition().setX(r.getPosition().getX() + 10);
break;
default:
break;
}
});
System.out.println("test");
}
public static void main(String[] args) {
launch(args);
}
}

I found the problem. I was just binding the wrong values...

Related

Exception in thread "JavaFX Application Thread" java.lang.IndexOutOfBoundsException while adding cloned object to Pane

I have the following class in which I am trying to implement the prototype pattern:
public class Element extends Group implements Cloneable{
private final double ELEMENT_WIDTH = 50;
private final double ELEMENT_HEIGHT = 70;
private final double CIRCLE_RADIUS = 1;
private int minimalNumberOfInputs;
private Shape body = new Rectangle(ELEMENT_WIDTH, ELEMENT_HEIGHT, Color.WHITE);
private Circle output = new Circle(CIRCLE_RADIUS);
private Line outputLine = new Line(ELEMENT_WIDTH, ELEMENT_HEIGHT / 2, ELEMENT_WIDTH + 15, ELEMENT_HEIGHT / 2);
private ArrayList<Circle> inputs;
private ArrayList<Line> inputLines;
private Circle inversionDesignation;
private Text symbol;
private Integer identifier;
private double bodyCorX;
private double bodyCorY;
private double corX = 0;
private double corY = 0;
private double mouseX = 0;
private double mouseY = 0;
private boolean dragging = false;
public Integer getIdentifier() {
return identifier;
}
public ArrayList<Circle> getInputs() {
return inputs;
}
public Circle getOutput() {
return output;
}
public Circle getInversionDesignation() {
return inversionDesignation;
}
public double getELEMENT_WIDTH() {
return ELEMENT_WIDTH;
}
public double getELEMENT_HEIGHT() {
return ELEMENT_HEIGHT;
}
public double getCIRCLE_RADIUS() {
return CIRCLE_RADIUS;
}
public int getMinimalNumberOfInputs() {
return minimalNumberOfInputs;
}
public Shape getBody() {
return body;
}
public Line getOutputLine() {
return outputLine;
}
public ArrayList<Line> getInputLines() {
return inputLines;
}
public Text getSymbol() {
return symbol;
}
public double getBodyCorX() {
return bodyCorX;
}
public double getBodyCorY() {
return bodyCorY;
}
public double getCorX() {
return corX;
}
public double getCorY() {
return corY;
}
public double getMouseX() {
return mouseX;
}
public double getMouseY() {
return mouseY;
}
public boolean isDragging() {
return dragging;
}
public Element(){
}
public Element(Circle inversionDesignation, Text symbol, int minimalNumberOfInputs) {
this.minimalNumberOfInputs = minimalNumberOfInputs;
this.body.setStroke(Color.BLACK);
this.body.setStrokeType(StrokeType.INSIDE);
this.body.setStrokeWidth(2.5);
this.output.setFill(Color.BLACK);
this.output.toFront();
this.inversionDesignation = inversionDesignation;
this.symbol = symbol;
this.inputs = new ArrayList<>();
this.inputLines = new ArrayList<>();
this.identifier = this.hashCode();
this.outputLine.setStrokeWidth(2);
this.createStartInputs();
this.configureInputPoints();
this.bindGraphicalElements();
elementMovementEvents();
elementEnteredEvents();
}
private void bindGraphicalElements() {
this.getChildren().add(body);
this.getChildren().add(output);
this.getChildren().add(outputLine);
this.getChildren().addAll(inputs);
this.getChildren().addAll(inputLines);
if (this.symbol != null) {
this.getChildren().add(symbol);
symbol.relocate((ELEMENT_WIDTH / 2) - symbol.getTabSize() / 2, ELEMENT_HEIGHT / 8);
symbol.setFont(new Font("Consolas", 14));
}
if (this.inversionDesignation != null) {
this.getChildren().add(inversionDesignation);
this.inversionDesignation.setStrokeType(StrokeType.INSIDE);
this.inversionDesignation.setStrokeWidth(1);
this.inversionDesignation.setStroke(Color.BLACK);
this.inversionDesignation.relocate((this.bodyCorX + this.ELEMENT_WIDTH) - (this.inversionDesignation.getRadius() + 1), (this.bodyCorY + this.ELEMENT_HEIGHT / 2) - this.inversionDesignation.getRadius());
inversionDesignation.toFront();
}
}
private void addGraphicalElement(Shape shape) {
this.getChildren().add(shape);
}
private void createStartInputs() {
for (int i = 0; i < this.minimalNumberOfInputs; i++) {
inputs.add(new Circle(CIRCLE_RADIUS));
inputs.get(i).setFill(Color.BLACK);
inputs.get(i).toFront();
}
configureInputPoints();
for (int i = 0; i < inputs.size(); i++) {
Line line = new Line(inputs.get(i).getLayoutX() - 15, inputs.get(i).getLayoutY(), inputs.get(i).getLayoutX(), inputs.get(i).getLayoutY());
line.setStrokeWidth(2);
inputLines.add(line);
}
}
private void addNewInput() {
Circle newCircle = new Circle(CIRCLE_RADIUS);
newCircle.setFill(Color.BLACK);
this.inputs.add(newCircle);
this.configureInputPoints();
this.addGraphicalElement(newCircle);
}
private void configureInputPoints() {
this.output.relocate((this.bodyCorX + this.ELEMENT_WIDTH) - (output.getRadius() + 1), (this.bodyCorY + this.ELEMENT_HEIGHT / 2) - output.getRadius());
int distance = (int) ELEMENT_HEIGHT / (inputs.size() + 1); //Растояние между точками входа.
for (int i = 0; i < inputs.size(); i++) {
inputs.get(i).relocate(this.bodyCorX - (CIRCLE_RADIUS - 1), this.bodyCorY + (distance * (i + 1) - CIRCLE_RADIUS));
}
}
private void elementMovementEvents() {
onMousePressedProperty().set(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
mouseX = event.getSceneX();
mouseY = event.getSceneY();
corX = getLayoutX();
corY = getLayoutY();
}
});
onMouseDraggedProperty().set(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
double offsetX = event.getSceneX() - mouseX; //смещение по X
double offsetY = event.getSceneY() - mouseY;
corX += offsetX;
corY += offsetY;
double scaledX = corX;
double scaledY = corY;
setLayoutX(scaledX);
setLayoutY(scaledY);
dragging = true;
mouseX = event.getSceneX();
mouseY = event.getSceneY();
event.consume();
}
});
onMouseClickedProperty().set(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
dragging = false;
}
});
}
private void testEvent() {
this.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
this.addNewInput();
});
}
private void elementEnteredEvents() {
onMouseClickedProperty().set(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getTarget() instanceof Circle) {
System.out.println("Circle!");
}
}
});
}
#Override
public Element clone() throws CloneNotSupportedException{
return (Element)super.clone();
}
#Override
public String toString() {
return "Element " + this.hashCode() + ": location = " + this.getLayoutX() + ": output = " + this.minimalNumberOfInputs;
}
#Override
public boolean equals(Object obj) {
if(!(obj instanceof Element)) return false;
Element element = (Element) obj;
return element.getBodyCorX() == bodyCorX && element.getBodyCorY() == bodyCorY && element.getBody() == body;
}
}
I am trying to implement a pattern using the following method:
#Override
public Element clone() throws CloneNotSupportedException{
return (Element)super.clone();
}
I have a controller like this:
public class FXMLController {
#FXML
private AnchorPane anchorPane;
#FXML
private AnchorPane workPane;
//prototypes
private Element AndPrototype = new Element(null, new Text("&"), 2);
private Element OrPrototype = new Element(null, new Text("1"), 2);
private Element NotPrototype = new Element(new Circle(5, Color.WHITE), null, 1);
private Element AndNotPrototype = new Element(new Circle(5, Color.WHITE), new Text("&"), 2);
private Element OrNotPrototype = new Element(new Circle(5, Color.WHITE), new Text("1"), 2);
#FXML
public void initialize() {
}
#FXML
private void method() throws CloneNotSupportedException {
workPane.getChildren().add(AndPrototype.clone());
}
}
In this method, I am trying to make a clone and add it to the AnchorPane
#FXML
private void method() throws CloneNotSupportedException {
workPane.getChildren().add(AndPrototype.clone());
}
As a result, when I click on the button, I get an error of the following content:
Exception in thread "JavaFX Application Thread" java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 8
at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:266)
at java.base/java.util.Objects.checkIndex(Objects.java:359)
at java.base/java.util.ArrayList.get(ArrayList.java:427)
at javafx.base/com.sun.javafx.collections.ObservableListWrapper.get(ObservableListWrapper.java:89)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.get(VetoableListDecorator.java:305)
at javafx.graphics/javafx.scene.Parent.updateCachedBounds(Parent.java:1704)
at javafx.graphics/javafx.scene.Parent.recomputeBounds(Parent.java:1648)
at javafx.graphics/javafx.scene.Parent.doComputeGeomBounds(Parent.java:1501)
at javafx.graphics/javafx.scene.Parent$1.doComputeGeomBounds(Parent.java:115)
at javafx.graphics/com.sun.javafx.scene.ParentHelper.computeGeomBoundsImpl(ParentHelper.java:84)
at javafx.graphics/com.sun.javafx.scene.NodeHelper.computeGeomBounds(NodeHelper.java:115)
at javafx.graphics/javafx.scene.Node.updateGeomBounds(Node.java:3847)
at javafx.graphics/javafx.scene.Node.getGeomBounds(Node.java:3809)
at javafx.graphics/javafx.scene.Node.doComputeLayoutBounds(Node.java:3657)
at javafx.graphics/javafx.scene.Node$1.doComputeLayoutBounds(Node.java:449)
at javafx.graphics/com.sun.javafx.scene.NodeHelper.computeLayoutBoundsImpl(NodeHelper.java:166)
at javafx.graphics/com.sun.javafx.scene.GroupHelper.computeLayoutBoundsImpl(GroupHelper.java:63)
at javafx.graphics/com.sun.javafx.scene.NodeHelper.computeLayoutBounds(NodeHelper.java:106)
at javafx.graphics/javafx.scene.Node$13.computeBounds(Node.java:3509)
at javafx.graphics/javafx.scene.Node$LazyBoundsProperty.get(Node.java:9782)
at javafx.graphics/javafx.scene.Node$LazyBoundsProperty.get(Node.java:9752)
at javafx.graphics/javafx.scene.Node.getLayoutBounds(Node.java:3524)
at javafx.graphics/javafx.scene.layout.AnchorPane.computeWidth(AnchorPane.java:272)
at javafx.graphics/javafx.scene.layout.AnchorPane.computeMinWidth(AnchorPane.java:248)
at javafx.graphics/javafx.scene.Parent.minWidth(Parent.java:1048)
at javafx.graphics/javafx.scene.layout.Region.minWidth(Region.java:1553)
at javafx.graphics/javafx.scene.layout.Region.computeChildPrefAreaWidth(Region.java:2012)
at javafx.graphics/javafx.scene.layout.AnchorPane.computeChildWidth(AnchorPane.java:315)
at javafx.graphics/javafx.scene.layout.AnchorPane.layoutChildren(AnchorPane.java:353)
at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1207)
at javafx.graphics/javafx.scene.Scene.doLayoutPass(Scene.java:576)
at javafx.graphics/javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2476)
at javafx.graphics/com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:413)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
at javafx.graphics/com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:412)
at javafx.graphics/com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:439)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:563)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:543)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:536)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:342)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
at java.base/java.lang.Thread.run(Thread.java:831)
I have absolutely no idea what this error may be connected with and in which direction they are moving.
Usually this is caused because you modified the scene graph or an attribute of a scene graph node off of the JavaFX thread.
Similar stack traces all caused by threading errors:
javafx vlcj play mutiple video get IndexOutOfBoundsException error
Exception on JavaFX when moving Labels around their container.(IndexOutOfBoundsException)
How to fix IndexOutOfBounds exception when javafx recomputes Parent/Node Bounds
How do I find out what's causing this Java FX Application Thread exception?
If it is a multi-threading issue, usually it can be fixed by either removing unnecessary threading. Or if multi-threading is unavoidable, using tools like the javafx.concurrent package or Platform.runLater to ensure nodes in the active scene graph are only modified on the JavaFX thread.
However, if you don’t have any multi-threading going on, it might be down to the weird cloning stuff you have going on which may be ill-advised. JavaFX nodes can only occur once in the scene and the clones may cause glitches in the framework.

Drawing using Canvas in Java FX is very blurry when running on PC

I used JavaFX for GUI development and Canvas component for drawing. It was very vague when running on PC, but relatively clear on Mac. I want to know the reason
I have tried to use some hd screens to show the Canvas drawing effect,But it's still very vague
public class FyEntity {
private double x;
private double y;
public FyEntity(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
}
public class FvcVtCurve extends Canvas {
public GraphicsContext gc;
public double thisWidth;
public double thisHeight;
public ArrayList<FyEntity> entityList = new ArrayList<>();
public FvcVtCurve(double x, double y, double width, double height) {
this.setLayoutX(x);
this.setLayoutY(y);
this.setWidth(width);
this.setHeight(height);
gc = this.getGraphicsContext2D();
}
public void paint() {
this.drawVtCurve();
}
public void drawVtCurve() {
gc.clearRect(0,0,this.getWidth(),this.getHeight());
gc.setLineWidth(1.0);
gc.setFill(AppUtils.rgb("#000000"));
gc.setStroke(AppUtils.rgb("#000000"));
gc.setLineCap(StrokeLineCap.BUTT);
gc.setLineJoin(StrokeLineJoin.ROUND);
for (int i = 0;i < entityList.size();i++){
if (i < entityList.size()-1){
FyEntity preFyEntity = entityList.get(i);
FyEntity nextFyEntity = entityList.get(i+1);
gc.strokeLine(preFyEntity.getX(),preFyEntity.getY(),
nextFyEntity.getX(),nextFyEntity.getY());
gc.restore();
}
}
}
}

Can't rotate a shape in Javafx

I'm having trouble rotating a shape in JavaFX. Here's some code in which I want the arrowhead to point in the same direction as the line (yes, I will be adding a translation later, to put it on the end of the line, but one thing at a time).
The println statement is showing me that theta is updating correctly as I move the start and end points around, but the arrowhead stays stubbornly at the original angle. Is there another step I have to do in order to get shape to rotate, or should I be doing it a different way?
package application.view;
public class Connector extends Group {
private final DoubleProperty startX;
private final DoubleProperty startY;
private final DoubleProperty endX;
private final DoubleProperty endY;
public Connector(Line line, Polygon arrowhead, Preferences prefs) {
arrowhead.setStroke(Color.BLACK);
arrowhead.setStrokeWidth(1);
arrowhead.setFill(Color.CORNFLOWERBLUE);
startX = new SimpleDoubleProperty();
startY = new SimpleDoubleProperty();
endX = new SimpleDoubleProperty();
endY = new SimpleDoubleProperty();
thetaBinding = new DoubleBinding() {
{
super.bind(startX, startY, endX, endY);
}
#Override
protected double computeValue() {
return Math.atan2(getEndY() - getStartY(), getEndX() - getStartX());
}
};
theta = new SimpleDoubleProperty();
theta.bind(thetaBinding);
theta.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
System.out.println(newValue);
arrowhead.rotateProperty().set((Double)newValue);
}
});
lineLength = new DoubleBinding() {
{
super.bind(startX, startY, endX, endY);
}
#Override
protected double computeValue() {
return Math.hypot(getEndX() - getStartX(), getEndY() - getStartY()) - prefs.getDouble("CONNECTOR_ARROW_LENGTH", Defaults.CONNECTOR_ARROW_LENGTH);
}
};
lineEndX = new DoubleBinding() {
{
super.bind(startX, lineLength, theta);
}
#Override
protected double computeValue() {
return startX.get() + lineLength.get() * Math.cos(theta.get());
}
};
lineEndY = new DoubleBinding() {
{
super.bind(startY, lineLength, theta);
}
#Override
protected double computeValue() {
return startY.get() + lineLength.get() * Math.sin(theta.get());
}
};
arrowhead.getPoints().setAll(new Double[] {
0.0, 0.0,
0.0, prefs.getDouble("CONNECTOR_ARROW_HALF_WIDTH", Defaults.CONNECTOR_ARROW_HALF_WIDTH),
prefs.getDouble("CONNECTOR_ARROW_LENGTH", Defaults.CONNECTOR_ARROW_LENGTH), 0.0,
0.0, -prefs.getDouble("CONNECTOR_ARROW_HALF_WIDTH", Defaults.CONNECTOR_ARROW_HALF_WIDTH),
});
line.startXProperty().bind(startX);
line.startYProperty().bind(startY);
line.endXProperty().bind(lineEndX);
line.endYProperty().bind(lineEndY);
getChildren().addAll(line, arrowhead);
}
public final DoubleBinding thetaBinding;
public final DoubleProperty theta;
public final DoubleBinding lineLength;
public final DoubleBinding lineEndX;
public final DoubleBinding lineEndY;
public double getStartX() {
return startX.get();
}
public void setStartX(double value) {
startX.set(value);
}
public DoubleProperty startXProperty() {
return startX;
}
public double getStartY() {
return startY.get();
}
public void setStartY(double value) {
startY.set(value);
}
public DoubleProperty startYProperty() {
return startY;
}
public double getEndX() {
return endX.get();
}
public void setEndX(double value) {
endX.set(value);
}
public DoubleProperty endXProperty() {
return endX;
}
public double getEndY() {
return endY.get();
}
public void setEndY(double value) {
endY.set(value);
}
public DoubleProperty endYProperty() {
return endY;
}
}

JavaFX Polygons share Points

I have a class "Vertex" that contains double x, y.
Another class "Face" holds a list of "Vertex" objects. Neighboring faces share the same vertices.
At the moment I'm creating a javafx.scene.shape.Polygon for every Face and add them all to my scene, which looks like this:
Screenshot
Now I'm planning to modify the polygons, similar to this: JavaFX modify polygons
The problem is that the polygons don't save references to my Vertex objects but double values. When I change the position of one point, the same point in the neighboring polygons is still at the old position. How can I link those points to each other? And also how to save the changes back to my "Face" object?
Code example as requested: pastebin.com/C3JHb2nM
Here you go. Calculated common anchor positions then adjusted all of the common ones when one was moved.
Anchor contains addCommon function which adds an anchor that is common to it. The common variable stores all the common anchors. Then when handle is called, all of the common ones x and y positions change as well.
Also, I will suggest that you hold the common points in Faces. I've created a simple method that will calculate all the Faces that share a vertex in said class. But following a MVC guideline, you need to have a model that provides all of necessary data to create a GUI. I would suggest Mesh, Face, and Vertex should provide all necessary information to create the GUI, and not as a two way street. Basically, Anchor should not alter your models in any way.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
public class Main extends Application {
public class Vertex {
private double x, y;
public Vertex(double x, double y) {
this.x = x;
this.y = y;
}
public Double[] getPoint() {
return new Double[]{x, y};
}
}
public class Face {
private List<Vertex> verts;
public Face(Vertex... verts) {
this.verts = new ArrayList<>(Arrays.asList(verts));
}
public Polygon getPolygon() {
Polygon polygon = new Polygon();
polygon.setFill(Color.GRAY);
polygon.setStroke(Color.BLACK);
for (Vertex vertex : verts) {
polygon.getPoints().addAll(vertex.getPoint());
}
return polygon;
}
public boolean containsVertex(Vertex ver) {
for (Vertex v : this.verts) {
if (v.x == ver.x && v.y == ver.y) {
return true;
}
}
return false;
}
}
public class Mesh {
private List<Vertex> verts = new ArrayList<Vertex>();
private List<Face> faces = new ArrayList<Face>();
private Map<Vertex, List<Face>> commonVertices = new HashMap<>();
public List<Polygon> getPolygons() {
List<Polygon> polygons = new ArrayList<Polygon>();
for (Face face : faces) {
polygons.add(face.getPolygon());
}
return polygons;
}
public Mesh() {
verts.add(new Vertex(50, 50));
verts.add(new Vertex(300, 50));
verts.add(new Vertex(500, 50));
verts.add(new Vertex(50, 300));
verts.add(new Vertex(250, 300));
verts.add(new Vertex(500, 300));
verts.add(new Vertex(50, 600));
verts.add(new Vertex(300, 700));
verts.add(new Vertex(500, 700));
faces.add(new Face(verts.get(0), verts.get(1), verts.get(4), verts.get(3)));
faces.add(new Face(verts.get(4), verts.get(1), verts.get(2), verts.get(5)));
faces.add(new Face(verts.get(3), verts.get(4), verts.get(7), verts.get(6)));
faces.add(new Face(verts.get(7), verts.get(4), verts.get(5)));
faces.add(new Face(verts.get(7), verts.get(5), verts.get(8)));
findCommonVertices();
}
private void findCommonVertices() {
for (Vertex ver : this.verts) {
List<Face> share = new ArrayList<>();
for (Face face : this.faces) {
if (face.containsVertex(ver)) {
share.add(face);
}
}
commonVertices.put(ver, share);
}
}
public Map<Vertex, List<Face>> getCommonVertices() {
return this.commonVertices;
}
}
#Override
public void start(Stage stage) {
Group root = new Group();
Scene scene = new Scene(root, 1024, 768);
stage.setScene(scene);
Group g = new Group();
Mesh mesh = new Mesh();
List<Polygon> polygons = mesh.getPolygons();
g.getChildren().addAll(polygons);
List<Anchor> anchors = new ArrayList<>();
for (Polygon p : polygons) {
ObservableList<Anchor> temp = createControlAnchorsFor(p.getPoints());
g.getChildren().addAll(temp);
for (Anchor kk : temp) {
anchors.add(kk);
}
}
for (int i = 0; i < anchors.size(); i++) {
List<Anchor> common = new ArrayList<>();
for (int j = 0; j < anchors.size(); j++) {
if (i != j) {
if (anchors.get(i).x.doubleValue() == anchors.get(j).x.doubleValue() && anchors.get(i).y.doubleValue() == anchors.get(j).y.doubleValue()) {
anchors.get(i).addCommon(anchors.get(j));
System.out.println("COMMON " + i + " " + j);
}
}
}
// anchors.get(i).setCommon(common);
}
scene.setRoot(g);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
// Everything below was copied from here: https://gist.github.com/jewelsea/5375786
// #return a list of anchors which can be dragged around to modify points in the format [x1, y1, x2, y2...]
private ObservableList<Anchor> createControlAnchorsFor(final ObservableList<Double> points) {
ObservableList<Anchor> anchors = FXCollections.observableArrayList();
for (int i = 0; i < points.size(); i += 2) {
final int idx = i;
DoubleProperty xProperty = new SimpleDoubleProperty(points.get(i));
DoubleProperty yProperty = new SimpleDoubleProperty(points.get(i + 1));
xProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number oldX, Number x) {
points.set(idx, (double) x);
}
});
yProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number oldY, Number y) {
points.set(idx + 1, (double) y);
}
});
anchors.add(new Anchor(Color.GOLD, xProperty, yProperty));
}
return anchors;
}
// a draggable anchor displayed around a point.
class Anchor extends Circle {
private final DoubleProperty x, y;
List<Anchor> common = new ArrayList<>();
public void setCommon(List<Anchor> common) {
this.common = common;
}
public void addCommon(Anchor com) {
common.add(com);
enableDrag();
}
Anchor(Color color, DoubleProperty x, DoubleProperty y) {
super(x.get(), y.get(), 10);
setFill(color.deriveColor(1, 1, 1, 0.5));
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
this.x = x;
this.y = y;
x.bind(centerXProperty());
y.bind(centerYProperty());
enableDrag();
}
// make a node movable by dragging it around with the mouse.
private void enableDrag() {
final Delta dragDelta = new Delta();
setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = getCenterX() - mouseEvent.getX();
dragDelta.y = getCenterY() - mouseEvent.getY();
getScene().setCursor(Cursor.MOVE);
}
});
setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
getScene().setCursor(Cursor.HAND);
}
});
setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
double newX = mouseEvent.getX() + dragDelta.x;
if (newX > 0 && newX < getScene().getWidth()) {
setCenterX(newX);
if (common != null) {
for (Anchor an : common) {
an.setCenterX(newX);
System.out.println("CALLED");
}
}
}
double newY = mouseEvent.getY() + dragDelta.y;
if (newY > 0 && newY < getScene().getHeight()) {
setCenterY(newY);
if (common != null) {
for (Anchor an : common) {
an.setCenterY(newY);
}
}
}
}
});
setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.HAND);
}
}
});
setOnMouseExited(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.DEFAULT);
}
}
});
}
// records relative x and y co-ordinates.
private class Delta {
double x, y;
}
}
}
Make the x and y coordinates of the vertices properties. You could then add listeners to those properties that modify the points of the Polygons:
public class Vertex {
public DoubleProperty xProperty() {
...
}
public DoubleProperty yProperty() {
...
}
}
public class VertexListener implements InvalidationListerner {
private final Vertex vertex;
private final int firstIndex;
private final List<Double> points;
public VertexListener(Vertex vertex, Polygon polygon, int pointIndex) {
this.firstIndex = 2 * pointIndex;
this.vertex = vertex;
this.points = polygon.getPoints();
vertex.xProperty().addListener(this);
vertex.yProperty().addListener(this);
}
public void dispose() {
vertex.xProperty().removeListener(this);
vertex.yProperty().removeListener(this);
}
#Override
public void invalidated(Observable observable) {
double x = vertex.getX();
double y = vertex.getY();
points.set(firstIndex, x);
points.set(firstIndex+1, y);
}
}
This way you only need to adjust the property values in Vertex and the listeners will add all incident faces...

Lineargradient for Line in javafx

How can I use lineargradient for Line in Javafx?
I tried this:
LinearGradient lg = new LinearGradient(...);
line.setFill(lg);
....
It doesn't work.
Based on this answer, you need to specify the gradient on absolute coordinates.
Something like this will work for any given ones:
LinearGradient linearGradient = new LinearGradient(x1, y1, x2, y2, false, CycleMethod.REFLECT, new Stop(0,Color.RED),new Stop(1,Color.GREEN));
line.setStroke(linearGradient);
Based on this answer, you can create a simple app to test it how it works when you move the anchor nodes around the scene:
private final DoubleProperty x1 = new SimpleDoubleProperty();
public final double getX1() { return x1.get(); }
public final void setX1(double value) { x1.set(value); }
public final DoubleProperty x1Property() { return x1; }
private final DoubleProperty x2 = new SimpleDoubleProperty();
public final double getX2() { return x2.get(); }
public final void setX2(double value) { x2.set(value); }
public final DoubleProperty x2Property() { return x2; }
private final DoubleProperty y1 = new SimpleDoubleProperty();
public final double getY1() { return y1.get(); }
public final void setY1(double value) { y1.set(value); }
public final DoubleProperty y1Property() { return y1; }
private final DoubleProperty y2 = new SimpleDoubleProperty();
public final double getY2() { return y2.get(); }
public final void setY2(double value) { y2.set(value); }
public final DoubleProperty y2Property() { return y2; }
private Line line;
#Override
public void start(Stage primaryStage) {
Group group = new Group();
primaryStage.setScene(new Scene(group, 400, 400));
x1.set(100);
y1.set(50);
x2.set(200);
y2.set(300);
line = new Line(x1.get(), y1.get(), x2.get(), y2.get());
line.startXProperty().bind(x1);
line.startYProperty().bind(y1);
line.endXProperty().bind(x2);
line.endYProperty().bind(y2);
line.setStrokeWidth(12);
line.setMouseTransparent(true);
Anchor start = new Anchor(Color.BLUE, x1, y1);
Anchor end = new Anchor(Color.YELLOW, x2, y2);
group.getChildren().setAll(line, start, end);
x1Property().addListener(o -> updateLine());
x2Property().addListener(o -> updateLine());
y1Property().addListener(o -> updateLine());
y2Property().addListener(o -> updateLine());
updateLine();
primaryStage.show();
}
private void updateLine() {
LinearGradient linearGradient = new LinearGradient(x1.get(), y1.get(), x2.get(), y2.get(), false, CycleMethod.REFLECT, new Stop(0,Color.RED),new Stop(1,Color.GREEN));
line.setStroke(linearGradient);
}
private class Anchor extends Circle {
Anchor(Color color, DoubleProperty x, DoubleProperty y) {
super(x.get(), y.get(), 10);
setFill(color.deriveColor(1, 1, 1, 0.5));
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
x.bind(centerXProperty());
y.bind(centerYProperty());
enableDrag();
}
// make a node movable by dragging it around with the mouse.
private void enableDrag() {
final Delta dragDelta = new Delta();
setOnMousePressed(mouseEvent -> {
// record a delta distance for the drag and drop operation.
dragDelta.x = getCenterX() - mouseEvent.getX();
dragDelta.y = getCenterY() - mouseEvent.getY();
getScene().setCursor(Cursor.MOVE);
});
setOnMouseReleased(mouseEvent -> {
getScene().setCursor(Cursor.HAND);
});
setOnMouseDragged(mouseEvent -> {
double newX = mouseEvent.getX() + dragDelta.x;
if (newX > 0 && newX < getScene().getWidth()) {
setCenterX(newX);
}
double newY = mouseEvent.getY() + dragDelta.y;
if (newY > 0 && newY < getScene().getHeight()) {
setCenterY(newY);
}
});
setOnMouseEntered(mouseEvent -> {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.HAND);
}
});
setOnMouseExited(mouseEvent -> {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.DEFAULT);
}
});
}
// records relative x and y co-ordinates.
private class Delta { double x, y; }
}
Use this
line.setStyle("-fx-background-color: linear-gradient(to right, #color_1, #color_2);");

Resources