Issue
With the help of several posts from here I created a draggable and rotable rectangle. I want that user can drag an anchor to rotate rectangle from center and resize it by one corner while opposite corner stays at same place, if rectangle keep unrotated calculations to do it are very simple. Unfortunally in this case the bounding rectangle can be rotated and trigonometry and how apply here is not very clear to me. After days googling so far any attemp I made to make this work fail miserably. Sorry for any mistake, english is not my native language. Thanks in advance
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.stage.Stage;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Rotate;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
final Delta dragDelta = new Delta();
final Rotate rotate = new Rotate();
Wrapper<Point2D> mouseLocation = new Wrapper<>();
Pane root = new Pane();
Rectangle rect = new Rectangle(100,100);
//set pivot on rectangle center
rotate.setPivotX((rect.getX() + rect.getWidth())/2);
rotate.setPivotY((rect.getY() + rect.getHeight())/2);
rect.setStyle(
"-fx-stroke: blue; " +
"-fx-stroke-width: 2px; " +
"-fx-stroke-dash-array: 12 2 4 2; " +
"-fx-stroke-dash-offset: 6; " +
"-fx-stroke-line-cap: butt; " +
"-fx-fill: rgba(255, 255, 255, .0);"
);
Group group = new Group();
// make a rectangle movable by dragging it around with the mouse.
rect.setOnMousePressed(mouseEvent -> {
// record a delta distance for the drag and drop operation.
mouseLocation.value = new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY());
group.getScene().setCursor(Cursor.MOVE);
});
rect.setOnMouseReleased(mouseEvent -> {
mouseLocation.value = null ;
group.getScene().setCursor(Cursor.HAND);
});
rect.setOnMouseDragged(mouseEvent -> {
// Get the mouse deltas
double deltaX = mouseEvent.getSceneX() - mouseLocation.value.getX();
double deltaY = mouseEvent.getSceneY() - mouseLocation.value.getY();
group.setTranslateX(group.getTranslateX() + deltaX);
group.setTranslateY(group.getTranslateY() + deltaY);
mouseLocation.value = new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY());
});
Circle topLeft = new Circle(7);
topLeft.centerXProperty().bind(rect.xProperty());
topLeft.centerYProperty().bind(rect.yProperty());
//Here is the tricky part
topLeft.setOnMousePressed(e->{
});
topLeft.setOnMouseDragged(e->{
});
//Anchor for rotate the rectangle
Circle rotateCircle = new Circle(7);
rotateCircle.centerXProperty().bind(rect.xProperty().add(rect.widthProperty()).divide(2));
rotateCircle.centerYProperty().bind(rect.yProperty().subtract(25d));
rotateCircle.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
dragDelta.x = event.getSceneX();
dragDelta.x = event.getSceneY();
});
// When it's dragged rotate the box
rotateCircle.addEventHandler(MouseEvent.MOUSE_DRAGGED, event -> {
var localToScene = group.getLocalToSceneTransform();
double x1 = dragDelta.x;
double y1 = dragDelta.y;
var x2 = event.getSceneX();
var y2 = event.getSceneY();
var px = rotate.getPivotX() + localToScene.getTx();
var py = rotate.getPivotY() + localToScene.getTy();
// Work out the angle rotated
double th1 = deltaAngle(x1, y1, px, py);
double th2 = deltaAngle(x2, y2, px, py);
var angle = rotate.getAngle();
angle += th2 - th1;
// Rotate the rectangle
rotate.setAngle(angle);
dragDelta.x = event.getSceneX();
dragDelta.y = event.getSceneY();
});
group.getChildren().addAll(rect, rotateCircle, topLeft);
group.setLayoutX(100);
group.setLayoutY(100);
group.getTransforms().add(rotate);
root.getChildren().add(group);
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
// Return the angle from 0 to 360
public static double deltaAngle (double x, double y, double px, double py) {
double dx = x - px;
double dy = y - py;
double angle = Math.abs(Math.toDegrees(Math.atan2(dy, dx)));
if(dy < 0) {
angle = 360 - angle;
}
return angle;
}
// records relative x and y co-ordinates.
private static class Delta {
double x, y;
}
static class Wrapper<T> {
T value ;
}
public static void main(String[] args) {
launch(args);
}
}
Solution
Something like this...
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.NonInvertibleTransformException;
import javafx.scene.transform.Rotate;
public class FancyRectangle extends Application {
@Override
public void start(Stage primaryStage) {
final MouseContext refPoint = new MouseContext();
final Rotate rotate = new Rotate();
Pane root = new Pane();
Rectangle rect = new Rectangle(100, 100);
//set pivot on rectangle center
rotate.setPivotX((rect.getX() + rect.getWidth()) / 2);
rotate.setPivotY((rect.getY() + rect.getHeight()) / 2);
rect.setStyle(
"-fx-stroke: blue; "
+ "-fx-stroke-width: 2px; "
+ "-fx-stroke-dash-array: 12 2 4 2; "
+ "-fx-stroke-dash-offset: 6; "
+ "-fx-stroke-line-cap: butt; "
+ "-fx-fill: rgba(255, 255, 255, .0);"
);
Group group = new Group();
// make a rectangle movable by dragging it around with the mouse.
rect.setOnMousePressed(mouseEvent -> {
// record a delta distance for the drag and drop operation.
refPoint.sceneX = mouseEvent.getSceneX();
refPoint.sceneY = mouseEvent.getSceneY();
group.getScene().setCursor(Cursor.MOVE);
});
rect.setOnMouseReleased(mouseEvent -> {
group.getScene().setCursor(Cursor.HAND);
});
rect.setOnMouseDragged(mouseEvent -> {
// Get the mouse deltas
double deltaX = mouseEvent.getSceneX() - refPoint.sceneX;
double deltaY = mouseEvent.getSceneY() - refPoint.sceneY;
group.setTranslateX(group.getTranslateX() + deltaX);
group.setTranslateY(group.getTranslateY() + deltaY);
refPoint.sceneX = mouseEvent.getSceneX();
refPoint.sceneY = mouseEvent.getSceneY();
});
Circle resizeCircle = new Circle(7);
resizeCircle.centerXProperty().bind(rect.xProperty());
resizeCircle.centerYProperty().bind(rect.yProperty());
// Handle resize
resizeCircle.setOnMousePressed(e -> {
var sceneCenter = resizeCircle.localToScene(resizeCircle.getCenterX(), resizeCircle.getCenterY());
refPoint.sceneX = e.getSceneX();
refPoint.sceneY = e.getSceneY();
refPoint.offX = rect.getWidth();
refPoint.offY = rect.getHeight();
});
resizeCircle.setOnMouseDragged(e -> {
try {
var sceneToLocal = group.getLocalToSceneTransform().createInverse();
var localRef = sceneToLocal.transform(refPoint.sceneX, refPoint.sceneY);
var localMouse = sceneToLocal.transform(e.getSceneX(), e.getSceneY());
var dx = localMouse.getX() - localRef.getX();
var dy = localMouse.getY() - localRef.getY();
var x = localMouse.getX();
var y = localMouse.getY();
var w = Math.abs(refPoint.offX-dx);
var h = Math.abs(refPoint.offY-dy);
rect.setX(x);
rect.setY(y);
rect.setWidth(w);
rect.setHeight(h);
rotate.setPivotX(x + w/2);
rotate.setPivotY(y + h/2);
} catch (NonInvertibleTransformException ex) {}
e.consume();
});
// Anchor for rotate the rectangle
Circle rotateCircle = new Circle(7);
rotateCircle.centerXProperty().bind(rect.xProperty().add(rect.widthProperty().divide(2)));
rotateCircle.centerYProperty().bind(rect.yProperty().subtract(25d));
rotateCircle.addEventHandler(MouseEvent.MOUSE_PRESSED, e -> {
var sceneCenter = rotateCircle.localToScene(rotateCircle.getCenterX(), rotateCircle.getCenterY());
refPoint.sceneX = e.getSceneX();
refPoint.sceneY = e.getSceneY();
refPoint.offX = sceneCenter.getX()-refPoint.sceneX;
refPoint.offY = sceneCenter.getY()-refPoint.sceneY;
});
// When it's dragged rotate the box
rotateCircle.addEventHandler(MouseEvent.MOUSE_DRAGGED, event -> {
var localToScene = group.getLocalToSceneTransform();
var x2 = event.getSceneX()+refPoint.offX;
var y2 = event.getSceneY()+refPoint.offY;
var pivotPoint = localToScene.transform(rotate.getPivotX(), rotate.getPivotY());
double angle = Math.toDegrees(Math.atan2(x2-pivotPoint.getX(), pivotPoint.getY()-y2));
// Rotate the rectangle
rotate.setAngle(angle);
});
group.getChildren().addAll(rect, rotateCircle, resizeCircle);
group.setLayoutX(100);
group.setLayoutY(100);
group.getTransforms().add(rotate);
root.getChildren().add(group);
Scene scene = new Scene(root, 400, 400);
//scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
// records relative x and y co-ordinates.
private static class MouseContext {
double sceneX, sceneY;
double offX, offY;
}
public static void main(String[] args) {
launch(args);
}
}
Answered By - swpalmer
Answer Checked By - Marie Seifert (JavaFixing Admin)