javajavafxpolygonshapesanchorpoint

Anchor Points Do Not Follow Polygon - JavaFX


Currently I have a polygon which can be resized and shaped as required. The problem is when I move the polygon the anchor points remain fixed to their position and do not move with the polygon, I am really confused and was hoping someone could help or guide me.

public Polygon createStartingFloorPlan(ActionEvent event) throws IOException {
    Polygon fp = new Polygon();
    ObjectProperty<Point2D> mousePosition = new SimpleObjectProperty<>();
    fp.getPoints().setAll(
            150d, 50d,
            450d, 50d,
            750d, 50d,
            750d, 350d,
            750d, 650d,
            450d, 650d,
            150d, 650d,
            150d, 350d

    );

    fp.setOnMousePressed(new EventHandler<MouseEvent>() {
        @Override
        public void handle(MouseEvent event) {
            mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));
        }
    });

    fp.setOnMouseDragged(new EventHandler<MouseEvent>() {
        @Override
        public void handle(MouseEvent event) {
            double deltaX = event.getSceneX() - mousePosition.get().getX();
            double deltaY = event.getSceneY() - mousePosition.get().getY();
            fp.setLayoutX(fp.getLayoutX()+deltaX);
            fp.setLayoutY(fp.getLayoutY()+deltaY);
            mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));
            createControlAnchorsFor(fp.getPoints());
        }
    });

    fp.setStroke(Color.FORESTGREEN);
    fp.setStrokeWidth(4);
    fp.setStrokeLineCap(StrokeLineCap.ROUND);
    fp.setFill(Color.CORNSILK.deriveColor(0, 1.2, 1, 0.6));
    container.getChildren().add(fp);
    container.getChildren().addAll(createControlAnchorsFor(fp.getPoints()));
    return fp;
}


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;

    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);
                }
                double newY = mouseEvent.getY() + dragDelta.y;
                if (newY > 0 && newY < getScene().getHeight()) {
                    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;
    }

}

Problem enter image description here


Solution

  • The coordinates where the points of the Polygon are drawn are (layoutX + pointX, layoutY + pointY) assuming there are no transforms applied to the node.

    You never adjust the layoutX and layoutY properties of the Anchors though. To fix this you can bind them to the properties of the Polygon:

    private ObservableList<Anchor> createControlAnchorsFor(Polygon polygon, final ObservableList<Double> points) {
        ...
    
        Anchor anchor = new Anchor(Color.GOLD, xProperty, yProperty);
    
        anchor.layoutXProperty().bind(polygon.layoutXProperty());
        anchor.layoutYProperty().bind(polygon.layoutYProperty());
    
        anchors.add(anchor);
    
        ...
    
    }
    

    Furthermore I recommend using a custom DoubleProperty class instead of using SimpleDoublePropertys with listeners, since by this you make the code a bit more readable and also reduce the number of objects by one per coordinate:

    private static class ListWriteDoubleProperty extends SimpleDoubleProperty {
        private final ObservableList<Double> list;
        private final int index;
    
        public ListWriteDoubleProperty(ObservableList<Double> list, int index) {
            super(list.get(index));
            this.list = list;
            this.index = index;
        }
    
        @Override
        protected void invalidated() {
            list.set(index, get());
        }
    
    }
    
    DoubleProperty xProperty = new ListWriteDoubleProperty(points, i);
    DoubleProperty yProperty = new ListWriteDoubleProperty(points, i + 1);
    // no more listeners/final index copy required...