JavaFX - Shape Deletes But Anchor Points Do Not - javafx

In my program I have two rectangles that are both draggable and resizable. I have added a function that allows me to select between the two and then delete one of them according to the selection and a button click.
Before Deleting:
After Deleting:
Code:
//Fields
private DraggingRectangle drag = new DraggingRectangle();
private List<Node> selectedShapes = new ArrayList<>();
#FXML
private AnchorPane container, container2;
//CreatingTheRectangle
if (treeview.getSelectionModel().getSelectedItem() == blackrect) {
Rectangle rect = drag.createDraggableRectangle(200, 60, 200, 60);
GraphicsContext gc = canvas.getGraphicsContext2D();
rect.setFill(Color.BLACK);
container2.getChildren().addAll(rect);
rect.setOnMouseClicked(e -> onShapeSelected(e));
}
//SelectingMethod
public void onShapeSelected(MouseEvent e) {
Shape shape = (Shape) e.getSource();
shape.setStyle("-fx-effect: dropshadow(three-pass-box, red, 10, 10, 0, 0);");
if (!selectedShapes.contains(shape)) {
selectedShapes.add(shape);
System.out.println("Shape Selected:" + shape);
} else {
shape.setStyle("-fx-effect: null");
selectedShapes.remove(shape);
}
//DeletingMethod
public void deleteButton(ActionEvent e) {
deletebutton.setOnAction(a -> container2.getChildren().removeAll(selectedShapes));
}
DraggableRectangle Class:
public class DraggingRectangle {
public Rectangle createDraggableRectangle(double x, double y, double width, double height) {
final double handleRadius = 20 ;
final double handleRadius2 = 30 ;
Rectangle rect = new Rectangle(x, y, width, height);
// top left resize handle:
Circle resizeHandleNW = new Circle(handleRadius, Color.RED);
// bind to top left corner of Rectangle:
resizeHandleNW.centerXProperty().bind(rect.xProperty());
resizeHandleNW.centerYProperty().bind(rect.yProperty());
// bottom right resize handle:
Circle resizeHandleSE = new Circle(handleRadius, Color.RED);
// bind to bottom right corner of Rectangle:
resizeHandleSE.centerXProperty().bind(rect.xProperty().add(rect.widthProperty()));
resizeHandleSE.centerYProperty().bind(rect.yProperty().add(rect.heightProperty()));
// move handle:
Circle moveHandle = new Circle(handleRadius2,Color.RED);
moveHandle.setOpacity(0.0);
// bind to bottom center of Rectangle:
moveHandle.centerXProperty().bind(rect.xProperty().add(rect.widthProperty().divide(2)));
moveHandle.centerYProperty().bind(rect.yProperty().add(rect.heightProperty().divide(2)));
// force circles to live in same parent as rectangle:
rect.parentProperty().addListener((obs, oldParent, newParent) -> {
for (Circle c : Arrays.asList(resizeHandleNW, resizeHandleSE, moveHandle)) {
Pane currentParent = (Pane)c.getParent();
if (currentParent != null) {
currentParent.getChildren().remove(c);
}
((Pane)newParent).getChildren().add(c);
}
});
Wrapper<Point2D> mouseLocation = new Wrapper<>();
setUpDragging(resizeHandleNW, mouseLocation) ;
setUpDragging(resizeHandleSE, mouseLocation) ;
setUpDragging(moveHandle, mouseLocation) ;
resizeHandleNW.setOnMouseDragged(event -> {
if (mouseLocation.value != null) {
double deltaX = event.getSceneX() - mouseLocation.value.getX();
double deltaY = event.getSceneY() - mouseLocation.value.getY();
double newX = rect.getX() + deltaX ;
if (newX >= handleRadius
&& newX <= rect.getX() + rect.getWidth() - handleRadius) {
rect.setX(newX);
rect.setWidth(rect.getWidth() - deltaX);
}
double newY = rect.getY() + deltaY ;
if (newY >= handleRadius
&& newY <= rect.getY() + rect.getHeight() - handleRadius) {
rect.setY(newY);
rect.setHeight(rect.getHeight() - deltaY);
}
mouseLocation.value = new Point2D(event.getSceneX(), event.getSceneY());
}
});
resizeHandleSE.setOnMouseDragged(event -> {
if (mouseLocation.value != null) {
double deltaX = event.getSceneX() - mouseLocation.value.getX();
double deltaY = event.getSceneY() - mouseLocation.value.getY();
double newMaxX = rect.getX() + rect.getWidth() + deltaX ;
if (newMaxX >= rect.getX()
&& newMaxX <= rect.getParent().getBoundsInLocal().getWidth() - handleRadius) {
rect.setWidth(rect.getWidth() + deltaX);
}
double newMaxY = rect.getY() + rect.getHeight() + deltaY ;
if (newMaxY >= rect.getY()
&& newMaxY <= rect.getParent().getBoundsInLocal().getHeight() - handleRadius) {
rect.setHeight(rect.getHeight() + deltaY);
}
mouseLocation.value = new Point2D(event.getSceneX(), event.getSceneY());
}
});
moveHandle.setOnMouseDragged(event -> {
if (mouseLocation.value != null) {
double deltaX = event.getSceneX() - mouseLocation.value.getX();
double deltaY = event.getSceneY() - mouseLocation.value.getY();
double newX = rect.getX() + deltaX ;
double newMaxX = newX + rect.getWidth();
if (newX >= handleRadius
&& newMaxX <= rect.getParent().getBoundsInLocal().getWidth() - handleRadius) {
rect.setX(newX);
}
double newY = rect.getY() + deltaY ;
double newMaxY = newY + rect.getHeight();
if (newY >= handleRadius
&& newMaxY <= rect.getParent().getBoundsInLocal().getHeight() - handleRadius) {
rect.setY(newY);
}
mouseLocation.value = new Point2D(event.getSceneX(), event.getSceneY());
}
});
return rect ;
}
private void setUpDragging(Circle circle, Wrapper<Point2D> mouseLocation) {
circle.setOnDragDetected(event -> {
circle.getParent().setCursor(Cursor.CLOSED_HAND);
mouseLocation.value = new Point2D(event.getSceneX(), event.getSceneY());
});
circle.setOnMouseReleased(event -> {
circle.getParent().setCursor(Cursor.DEFAULT);
mouseLocation.value = null ;
});
}
static class Wrapper<T> { T value ; }
}
I realise that it has something to do with the list I am adding the rectangles too, but cannot figure out how I would add the anchor points along with it. Thank You
EXCEPTION:
Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Children: duplicate children added: parent = AnchorPane[id=container2]
at javafx.scene.Parent$2.onProposedChange(Parent.java:454)
at com.sun.javafx.collections.VetoableListDecorator.remove(VetoableListDecorator.java:329)
at com.sun.javafx.collections.VetoableListDecorator.remove(VetoableListDecorator.java:221)
at Model.DraggingRectangle.lambda$createDraggableRectangle$0(DraggingRectangle.java:43)
at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:182)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.ReadOnlyObjectWrapper$ReadOnlyPropertyImpl.fireValueChangedEvent(ReadOnlyObjectWrapper.java:176)
at javafx.beans.property.ReadOnlyObjectWrapper.fireValueChangedEvent(ReadOnlyObjectWrapper.java:142)
at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
at javafx.scene.Node.setParent(Node.java:720)
at javafx.scene.Parent$2.onProposedChange(Parent.java:497)
at com.sun.javafx.collections.VetoableListDecorator.removeFromList(VetoableListDecorator.java:149)
at com.sun.javafx.collections.VetoableListDecorator.removeAll(VetoableListDecorator.java:264)
at Controller.NewLayoutController.lambda$deleteButton$9(NewLayoutController.java:261)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Node.fireEvent(Node.java:8411)
at javafx.scene.control.Button.fire(Button.java:185)
at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:182)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:96)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:89)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3757)
at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:352)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:275)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$355(GlassViewEventHandler.java:388)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:387)
at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
at com.sun.glass.ui.View.notifyMouse(View.java:937)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$149(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)
Polygon
public ObservableList<Anchor> createAnchorsPoly(Polygon polygon, final ObservableList<Double> points) {}
polygon.parentProperty().addListener((obs, oldParent, newParent) -> {
Platform.runLater(() -> {
for (ObservableList<Double> a : Arrays.asList(points)) {
Pane currentParent = (Pane)oldParent;
if (currentParent != null) {
currentParent.getChildren().remove(a);
}
if (newParent != null) {
((Pane)newParent).getChildren().add(a); <<Error!
}
}
});

Replace
rect.parentProperty().addListener((obs, oldParent, newParent) -> {
for (Circle c : Arrays.asList(resizeHandleNW, resizeHandleSE, moveHandle)) {
Pane currentParent = (Pane)c.getParent();
if (currentParent != null) {
currentParent.getChildren().remove(c);
}
((Pane)newParent).getChildren().add(c);
}
});
with
rect.parentProperty().addListener((obs, oldParent, newParent) -> {
Platform.runLater(() -> {
for (Circle c : Arrays.asList(resizeHandleNW, resizeHandleSE, moveHandle)) {
Pane currentParent = (Pane)oldParent;
if (currentParent != null) {
currentParent.getChildren().remove(c);
}
if (newParent != null) {
((Pane)newParent).getChildren().add(c);
}
}
});
});

Related

PDFBox: draw millimeter paper

I want to draw millimeter paper into a pdf, but when I measure the printed document I'm a bit off (drawn cm < 1 cm). I'm using the size of an A4 Paper (210 * 297) and the pageWidth and pageHeight to calculate the pixel per mm (used the average of both hoping this would work). I also tried different option when printing the document (with & without margin etc.), but this didn't work as well.
public class TestPrint extends Application {
protected static final float DINA4_IN_MM_WIDTH = 210;
protected static final float DINA4_IN_MM_HEIGHT = 297;
protected static final int LEFTSIDE = 35;
protected static final int TEXT_FONT_SIZE = 11;
protected static final int TOP_MARGIN = 60;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
File file = new File("test.pdf");
double windowWidth = 600;
double windowHeight = 1000;
float x = LEFTSIDE;
PDFont font = PDType1Font.TIMES_ROMAN;
PDPage page = new PDPage(PDRectangle.A4);
PDRectangle pageSize = page.getMediaBox();
PDDocument mainDocument = new PDDocument();
mainDocument.addPage(page);
float stringHeight = font.getFontDescriptor().getFontBoundingBox().getHeight() * TEXT_FONT_SIZE;
float y = pageSize.getHeight() - stringHeight / 1000f - TOP_MARGIN;
float pixelPerMM = (pageSize.getWidth() / DINA4_IN_MM_WIDTH + pageSize.getHeight() / DINA4_IN_MM_HEIGHT) / 2;
float displayW = 520;
float displayH = 300;
try {
PDPageContentStream contents = new PDPageContentStream(mainDocument, page, AppendMode.APPEND, true);
drawBackgroundRaster(contents, x, y, displayW, displayH, pixelPerMM);
contents.close();
mainDocument.save(file);
ImageView imgView = getImageViewFromDocument(mainDocument, windowHeight);
VBox vBox = new VBox(imgView);
Scene scene = new Scene(vBox, windowWidth, windowHeight);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
// draw millimeter paper with dots, two boxes shall be 1cm
private void drawBackgroundRaster(PDPageContentStream contents, float x, float y, float displayW, float displayH,
float pixelPerMM) throws IOException {
// rasterColor = grey
Color rasterColor = new Color(175, 175, 175);
contents.setStrokingColor(rasterColor);
float dotSize = 0.5f;
// draw vertical lines
for (int i = 0; i <= displayW; i++) {
float xPos = x + i * pixelPerMM;
if (xPos > displayW + x) {
break;
}
contents.moveTo(xPos, y);
if (i % 5 == 0) {
contents.setLineDashPattern(new float[] {}, 0);
contents.lineTo(xPos, y - displayH);
}
contents.stroke();
}
// draw dots and horizontal lines
for (int i = 0; i <= displayH; i++) {
float yPos = y - i * pixelPerMM;
if (yPos < y - displayH) {
break;
}
contents.moveTo(x, yPos);
if (i % 5 == 0) {
contents.setLineDashPattern(new float[] {}, 0);
contents.lineTo(x + displayW, yPos);
} else {
contents.setLineDashPattern(new float[] { dotSize, pixelPerMM - dotSize }, dotSize / 2);
contents.lineTo(x + displayW, yPos);
}
contents.stroke();
}
contents.setLineDashPattern(new float[] {}, 0);
contents.moveTo(x, y);
contents.lineTo(x + displayW, y);
contents.lineTo(x + displayW, y - displayH);
contents.lineTo(x, y - displayH);
contents.lineTo(x, y);
contents.stroke();
}
private ImageView getImageViewFromDocument(PDDocument mainDocument, double windowHeight) throws IOException {
PDFRenderer pdfRenderer = new PDFRenderer(mainDocument);
BufferedImage bim = pdfRenderer.renderImageWithDPI(0, 150, ImageType.RGB);
Image image = SwingFXUtils.toFXImage(bim, null);
ImageView imageView = new ImageView(image);
double scaleFactor = windowHeight / imageView.getImage().getHeight();
double zoomFactor = scaleFactor * 2d * 2.55d / 3;
double width = imageView.getImage().getWidth() * zoomFactor;
double height = imageView.getImage().getHeight() * zoomFactor;
imageView.setFitWidth(width);
imageView.setFitHeight(height);
return imageView;
}
}

Updating a textField when size of shape changes - JavaFX

Suppose I have the following:
Creating A Rectangle(With A Top Left Handler):
public Group createDraggableRectangle(double x, double y, double width, double height) {
final double handleRadius = 20 ;
final double handleRadius2 = 30 ;
Rectangle rect = new Rectangle(x, y, width, height);
// top left resize handle:
Circle resizeHandleNW = new Circle(handleRadius, Color.RED);
resizeHandleNW.setOpacity(0);
// bind to top left cornerof Rectangle:
resizeHandleNW.centerXProperty().bind(rect.xProperty());
resizeHandleNW.centerYProperty().bind(rect.yProperty());
resizeHandleNW.setStyle("-fx-cursor: NW_RESIZE; ");
Group group = new Group(rect, resizeHandleNW);
Wrapper<Point2D> mouseLocation = new Wrapper<>();
setUpDragging(resizeHandleNW, mouseLocation) ;
resizeHandleNW.setOnMouseDragged(event -> {
if (mouseLocation.value != null) {
double deltaX = event.getSceneX() - mouseLocation.value.getX();
double deltaY = event.getSceneY() - mouseLocation.value.getY();
double newX = rect.getX() + deltaX ;
if (newX >= handleRadius
&& newX <= rect.getX() + rect.getWidth() - handleRadius) {
rect.setX(newX);
rect.setWidth(rect.getWidth() - deltaX);
}
double newY = rect.getY() + deltaY ;
if (newY >= handleRadius
&& newY <= rect.getY() + rect.getHeight() - handleRadius) {
rect.setY(newY);
rect.setHeight(rect.getHeight() - deltaY);
}
mouseLocation.value = new Point2D(event.getSceneX(), event.getSceneY());
}
return group;
});
Controller Class(Method For Selecting Object):
#FXML
private TextField heightField;
if (!selectedShapes.contains(shape)) {
shape.setStyle("-fx-effect: dropshadow(three-pass-box, #cece02, 6, 6, 0, 0);");
selectedShapes.add(shape);
rotate.setDisable(false);
deletebutton.setDisable(false);
}
I know that writing System.out.println(rect.getHeight()) in the Rectangle class will give me the height every-time the size is changed.
However my question is that how would I add this value to the textField, held in my controller class? I have tried various ways, however I keep getting a null pointer exception.
Thanks
Somewhere you presumably do
Group draggableRect = createDraggableRectangle(...);
So then you just do
heightField.textProperty().bind(Bindings.createStringBinding(
() -> String.format("%.1f", draggableRect.getBoundsInLocal().getHeight()),
draggableRect.boundsInLocalProperty());
or, if you want the height field's text to change in other ways (e.g. if it is editable),
draggableRect.boundsInLocalProperty().addListener((obs, oldBounds, newBounds) ->
heightField.setText(String.format("%.1f", newBounds.getHeight())));

Stop Shapes Overlapping - JavaFX

I have a program that I can add multiple rectangles that can be moved, resized, rotated around the scene. I wanted to implement some sort of collision detection so that if they collide, the selected shape does not overlap the existing one.
Problem:
Expectation:
DraggingRectangleClass:
import javafx.geometry.Point2D;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
public class DraggingRectangle {
public Group createDraggableRectangle(double x, double y, double width, double height) {
final double handleRadius = 20 ;
final double handleRadius2 = 30 ;
Rectangle rect = new Rectangle(x, y, width, height);
// top left resize handle:
Circle resizeHandleNW = new Circle(handleRadius, Color.RED);
resizeHandleNW.setOpacity(0);
// bind to top left cornerof Rectangle:
resizeHandleNW.centerXProperty().bind(rect.xProperty());
resizeHandleNW.centerYProperty().bind(rect.yProperty());
resizeHandleNW.setStyle("-fx-cursor: NW_RESIZE; ");
// bottom right resize handle:
Circle resizeHandleSE = new Circle(handleRadius, Color.RED);
resizeHandleSE.setOpacity(0);
// bind to bottom right corner of Rectangle:
resizeHandleSE.centerXProperty().bind(rect.xProperty().add(rect.widthProperty()));
resizeHandleSE.centerYProperty().bind(rect.yProperty().add(rect.heightProperty()));
resizeHandleSE.setStyle("-fx-cursor: SE_RESIZE; ");
// move handle:
Circle moveHandle = new Circle(handleRadius2,Color.RED);
moveHandle.setOpacity(0);
moveHandle.setStyle("-fx-cursor: CLOSED_HAND;");
// bind to bottom center of Rectangle:
moveHandle.centerXProperty().bind(rect.xProperty().add(rect.widthProperty().divide(2)));
moveHandle.centerYProperty().bind(rect.yProperty().add(rect.heightProperty().divide(2)));
Group group = new Group(rect, resizeHandleNW, resizeHandleSE, moveHandle);
Wrapper<Point2D> mouseLocation = new Wrapper<>();
setUpDragging(resizeHandleNW, mouseLocation) ;
setUpDragging(resizeHandleSE, mouseLocation) ;
setUpDragging(moveHandle, mouseLocation) ;
resizeHandleNW.setOnMouseDragged(event -> {
if (mouseLocation.value != null) {
double deltaX = event.getSceneX() - mouseLocation.value.getX();
double deltaY = event.getSceneY() - mouseLocation.value.getY();
double newX = rect.getX() + deltaX ;
if (newX >= handleRadius
&& newX <= rect.getX() + rect.getWidth() - handleRadius) {
rect.setX(newX);
rect.setWidth(rect.getWidth() - deltaX);
}
double newY = rect.getY() + deltaY ;
if (newY >= handleRadius
&& newY <= rect.getY() + rect.getHeight() - handleRadius) {
rect.setY(newY);
rect.setHeight(rect.getHeight() - deltaY);
}
mouseLocation.value = new Point2D(event.getSceneX(), event.getSceneY());
}
});
resizeHandleSE.setOnMouseDragged(event -> {
if (mouseLocation.value != null) {
double deltaX = event.getSceneX() - mouseLocation.value.getX();
double deltaY = event.getSceneY() - mouseLocation.value.getY();
double newMaxX = rect.getX() + rect.getWidth() + deltaX ;
if (newMaxX >= rect.getX()
&& newMaxX <= group.getParent().getBoundsInLocal().getWidth() - handleRadius) {
rect.setWidth(rect.getWidth() + deltaX);
}
double newMaxY = rect.getY() + rect.getHeight() + deltaY ;
if (newMaxY >= rect.getY()
&& newMaxY <= group.getParent().getBoundsInLocal().getHeight() - handleRadius) {
rect.setHeight(rect.getHeight() + deltaY);
}
mouseLocation.value = new Point2D(event.getSceneX(), event.getSceneY());
}
});
moveHandle.setOnMouseDragged(event -> {
if (mouseLocation.value != null) {
double deltaX = event.getSceneX() - mouseLocation.value.getX();
double deltaY = event.getSceneY() - mouseLocation.value.getY();
double newX = rect.getX() + deltaX ;
double newMaxX = newX + rect.getWidth();
if (newX >= handleRadius
&& newMaxX <=group.getParent().getBoundsInLocal().getWidth() - handleRadius) {
rect.setX(newX);
}
double newY = rect.getY() + deltaY ;
double newMaxY = newY + rect.getHeight();
if (newY >= handleRadius
&& newMaxY <=group.getParent().getBoundsInLocal().getHeight() - handleRadius) {
rect.setY(newY);
}
mouseLocation.value = new Point2D(event.getSceneX(), event.getSceneY());
}
});
return group ;
}
private void setUpDragging(Circle circle, Wrapper<Point2D> mouseLocation) {
circle.setOnDragDetected(event -> {
circle.getParent().setCursor(Cursor.CLOSED_HAND);
mouseLocation.value = new Point2D(event.getSceneX(), event.getSceneY());
});
circle.setOnMouseReleased(event -> {
circle.getParent().setCursor(Cursor.DEFAULT);
mouseLocation.value = null ;
});
}
static class Wrapper<T> { T value ; }
}
MethodToCreateRectangle:
if (treeview.getSelectionModel().getSelectedItem() == green) {
Group group = drag.createDraggableRectangle(200, 60, 180, 90);
group.getChildren().get(0).setStyle(
"-fx-fill: green;");
container2.getChildren().addAll(group);
group.setOnMouseClicked(e -> onShapeSelected(e));
}
The "Group" contains my shape along with the handles to move and resize. The "Container2" is the fx:id for my AnchorPane.
Thanks

Math to copy a path with a distance d

I have a question about the math involved to copy a path.
Let's say I have this path:
http://imgur.com/a/42l0t
I want an exact copy of this path besides the black one. I wrote a small C# program that calculates the angle between two points. Depending on the angle, an offset to the X or Y value is added.
It kind of works, this is the result:
http://imgur.com/bJQDCgq
As you can see, it's not that pretty.
Now, my real question is: What is the proper math to use for this?
Hopefully someone knwos an answer, because I'm kinda stuck on this one.
Regards,
Sascha
Code:
void Plot(List<Point> points)
{
Graphics g = pictureBox.CreateGraphics();
g.Clear(Color.White);
for (int i = 0; i < points.Count - 1; i++)
{
g.DrawLine(Pens.Black, points[i], points[i + 1]);
}
List<Point> points2 = new List<Point>();
for (int i = 0; i < points.Count - 1; i++)
{
var angle = getAngleFromPoint(points[i], points[i + 1]);
Debug.WriteLine(angle);
if (angle < 180 && angle >= 135)
{
points2.Add(new Point(points[i].X - OFFSET, points[i].Y));
}
if (angle < 135 && angle >= 90)
{
if (points[i].Y < points[i + 1].Y)
{
points2.Add(new Point(points[i].X - OFFSET / 2, points[i].Y + OFFSET));
}
else
{
}
}
if (angle < 90 && angle >= 45)
{
if (points[i].Y < points[i + 1].Y)
{
points2.Add(new Point(points[i].X - OFFSET, points[i].Y));
}
else
{
points2.Add(new Point(points[i].X + OFFSET, points[i].Y));
}
}
if (angle < 45 && angle >= 0)
{
if (points[i].Y < points[i + 1].Y)
{
points2.Add(new Point(points[i].X - OFFSET, points[i].Y));
}
else
{
points2.Add(new Point(points[i].X + OFFSET, points[i].Y));
}
}
if (angle < 360 && angle >= 315)
{
if (points[i].Y < points[i + 1].Y)
{
points2.Add(new Point(points[i].X + OFFSET, points[i].Y));
}
else
{
points2.Add(new Point(points[i].X + 10, points[i].Y - OFFSET));
}
}
if (angle < 315 && angle >= 270)
{
points2.Add(new Point(points[i].X, points[i].Y - OFFSET));
}
if (angle < 270 && angle >= 225)
{
if (points[i].Y < points[i + 1].Y)
{
points2.Add(new Point(points[i].X - OFFSET / 2, points[i].Y - OFFSET));
}
else
{
}
}
if (angle < 225 && angle >= 180)
{
if (points[i].X < points[i + 1].X)
{
points2.Add(new Point(points[i].X, points[i].Y - OFFSET));
}
else
{
if (points[i].Y < points[i + 1].Y) // \
{
points2.Add(new Point(points[i].X - OFFSET, points[i].Y));
}
else
{
}
}
}
}
for (int i = 0; i < points2.Count - 1; i++)
{
g.DrawLine(Pens.Red, points2[i], points2[i + 1]);
}
}
I think if i decrease the angles (from 45 degree steps to maybe 30 degrees) I could imnprove the result, but there must be a better solution.
I suppose one way to tackle this is to split it into line-pairs (ie: three points)
Find the parallel line (at distance d) for each line in the pair. Then find where these parallel lines intersect to give you the location of a point on the new line.
In very rough psuedo-code:
points a, b, c
distance d
lineab = findLineParallelTo(line(a,b), d)
linebc = findLineParallelTo(line(b,c), d)
return intersect(lineab, linebc)
I implemented the solution from #Jack and it works great:
public class Line
{
public PointF P { get; private set; }
public PointF Q { get; private set; }
public float Pitch
{
get; private set;
}
public Line()
{
}
public Line(float px, float py, float qx, float qy) : this(new PointF(px, py), new PointF(qx, qy))
{
}
public Line(PointF p, PointF q)
{
P = p;
Q = q;
}
#region Methods
/// <summary>
/// http://stackoverflow.com/questions/2825412/draw-a-parallel-line
/// </summary>
public Line FindParallelLine(float distance)
{
float length = (float)Math.Sqrt((P.X - Q.X) * (P.X - Q.X) + (P.Y - Q.Y) * (P.Y - Q.Y));
// This is the second line
float px = P.X + distance * (Q.Y - P.Y) / length;
float qx = Q.X + distance * (Q.Y - P.Y) / length;
float py = P.Y + distance * (P.X - Q.X) / length;
float qy = Q.Y + distance * (P.X - Q.X) / length;
return new Line(px, py, qx, qy);
}
public override string ToString()
{
return string.Format("P({0}|{1}), Q({2}|{3}) - Pitch: {4}", P.X, P.Y, Q.X, Q.Y, Pitch);
}
#endregion
}
private PointF FindIntersection(Line a, Line b)
{
PointF A = a.P;
PointF B = a.Q;
PointF C = b.P;
PointF D = b.Q;
float dy1 = B.Y - A.Y;
float dx1 = B.X - A.X;
float dy2 = D.Y - C.Y;
float dx2 = D.X - C.X;
// Check whether the two line parallel.
if (dy1 * dx2 == dy2 * dx1)
{
return PointF.Empty;
}
else
{
float x = ((C.Y - A.Y) * dx1 * dx2 + dy1 * dx2 * A.X - dy2 * dx1 * C.X) / (dy1 * dx2 - dy2 * dx1);
float y = A.Y + (dy1 / dx1) * (x - A.X);
return new PointF(x, y);
}
}
private PointF FindIntersection(PointF a, PointF b, PointF c, float distance)
{
Line line1 = new Line(a, b);
Line line2 = new Line(b, c);
Line parallel = line1.FindParallelLine(distance);
Line parallel2 = line2.FindParallelLine(distance);
return FindIntersection(parallel, parallel2);
}
private List<PointF> FindIntersections(PointF[] points, float distance)
{
List<PointF> intersections = new List<PointF>();
for (int i = 0; i < points.Length - 2; i++)
{
PointF intersection = FindIntersection(points[i], points[i + 1], points[i + 2], distance);
if (!intersection.IsEmpty && !double.IsNaN(intersection.X) && !double.IsNaN(intersection.Y))
{
intersections.Add(intersection);
}
}
return intersections;
}
private PointF GetFirstPoint(PointF[] points, float distance)
{
Line parallel = new Line(points[0], points[1]).FindParallelLine(distance);
return parallel.P;
}
private PointF GetLastPoint(PointF[] points, float distance)
{
Line parallel = new Line(points[points.Length - 2], points[points.Length - 1]).FindParallelLine(distance);
return parallel.Q;
}
Example call:
OFFSET = float.Parse(textBox1.Text);
List<PointF> points = new List<PointF>();
points.Add(new PointF(200, 180));
points.Add(new PointF(160, 160));
points.Add(new PointF(100, 160));
points.Add(new PointF(60, 140));
points.Add(new PointF(40, 100));
points.Add(new PointF(80, 60));
points.Add(new PointF(140, 100));
points.Add(new PointF(180, 140));
points.Add(new PointF(220, 80));
List<PointF> intersections = FindIntersections(points.ToArray(), OFFSET);
intersections.Insert(0, GetFirstPoint(points.ToArray(), OFFSET));
intersections.Add(GetLastPoint(points.ToArray(), OFFSET));
Graphics g = pictureBox.CreateGraphics();
g.Clear(Color.White);
g.DrawLines(Pens.Black, points.ToArray());
// Connect the intersection points.
g.DrawLines(Pens.Red, intersections.ToArray());
Example image:
http://imgur.com/onUstGT
Thanks again #Jack !

How to "stick" shapes relatively on a rectangle that is rotating in JavaFX?

As you can see bellow, i can rotate my rectangle shapes through the red anchor.
I want all anchors to stick relatively to the rectangle shape while its rotating (and after that of course) and NOT on the original locations as you see in second screenshot.
private Rectangle createDraggableRectangle(double x, double y, double width, double height) {
final double handleRadius = 5;
Rectangle rect = new Rectangle(x, y, width, height);
// top left resize handle:
Circle resizeHandleNW = new Circle(handleRadius, Color.GOLD);
// bind to top left corner of Rectangle:
resizeHandleNW.centerXProperty().bind(rect.xProperty());
resizeHandleNW.centerYProperty().bind(rect.yProperty());
// top right rotate handle:
Circle rotateHandle = new Circle(handleRadius, Color.RED);
// bind to top right corner of Rectangle:
rotateHandle.centerXProperty().bind(rect.xProperty().add(rect.widthProperty()));
rotateHandle.centerYProperty().bind(rect.yProperty());
// force circles to live in same parent as rectangle:
rect.parentProperty().addListener((obs, oldParent, newParent) -> {
for (Circle c : Arrays.asList(resizeHandleNW, resizeHandleSE, resizeHandleLeft, resizeHandleRight, resizeHandleTop, resizeHandleBottom, rotateHandle, moveHandle)) {
Pane currentParent = (Pane) c.getParent();
if (currentParent != null) {
currentParent.getChildren().remove(c);
}
((Pane) newParent).getChildren().add(c);
}
});
Wrapper<Point2D> mouseLocation = new Wrapper<>();
setUpDragging(resizeHandleNW, mouseLocation);
resizeHandleNW.setOnMouseDragged(event -> {
if (mouseLocation.value != null) {
double deltaX = event.getSceneX() - mouseLocation.value.getX();
double deltaY = event.getSceneY() - mouseLocation.value.getY();
double newX = rect.getX() + deltaX;
if (newX >= handleRadius
&& newX <= rect.getX() + rect.getWidth() - handleRadius) {
rect.setX(newX);
rect.setWidth(rect.getWidth() - deltaX);
}
double newY = rect.getY() + deltaY;
if (newY >= handleRadius
&& newY <= rect.getY() + rect.getHeight() - handleRadius) {
rect.setY(newY);
rect.setHeight(rect.getHeight() - deltaY);
}
mouseLocation.value = new Point2D(event.getSceneX(), event.getSceneY());
}
});
rotateHandle.setOnMouseDragged(event -> {
if (mouseLocation.value != null) {
double deltaX = event.getSceneX() - mouseLocation.value.getX();
double deltaY = event.getSceneY() - mouseLocation.value.getY();
double radAngle = Math.atan2(deltaY, deltaX);
double degAngle = radAngle * 180 / Math.PI;
rect.setRotate(degAngle);
}
});
return rect;
}
private void setUpDragging(Circle circle, Wrapper<Point2D> mouseLocation) {
circle.setOnDragDetected(event -> {
circle.getParent().setCursor(Cursor.CLOSED_HAND);
mouseLocation.value = new Point2D(event.getSceneX(), event.getSceneY());
});
circle.setOnMouseReleased(event -> {
circle.getParent().setCursor(Cursor.DEFAULT);
mouseLocation.value = null;
});
}
static class Wrapper<T> {
T value;
}

Resources