Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jhotdraw8.draw.handle.RotateHandle Maven / Gradle / Ivy
/*
* @(#)RotateHandle.java
* Copyright © 2023 The authors and contributors of JHotDraw. MIT License.
*/
package org.jhotdraw8.draw.handle;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Point3D;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.SVGPath;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
import javafx.scene.shape.StrokeType;
import javafx.scene.transform.Transform;
import org.jhotdraw8.draw.DrawingView;
import org.jhotdraw8.draw.css.value.Paintable;
import org.jhotdraw8.draw.figure.Figure;
import org.jhotdraw8.draw.figure.TransformableFigure;
import org.jhotdraw8.draw.model.DrawingModel;
import org.jhotdraw8.geom.Angles;
import org.jhotdraw8.geom.FXRectangles;
import org.jhotdraw8.geom.FXTransforms;
import org.jhotdraw8.geom.Points;
import org.jspecify.annotations.Nullable;
import java.util.HashSet;
import java.util.Set;
import static org.jhotdraw8.draw.figure.TransformableFigure.ROTATE;
import static org.jhotdraw8.draw.figure.TransformableFigure.ROTATION_AXIS;
import static org.jhotdraw8.draw.figure.TransformableFigure.SCALE_Y;
/**
* A Handle to rotate a TransformableFigure around the center of its bounds in
* local.
*
* @author Werner Randelshofer
*/
public class RotateHandle extends AbstractHandle {
public static final @Nullable BorderStrokeStyle INSIDE_STROKE = new BorderStrokeStyle(StrokeType.INSIDE, StrokeLineJoin.MITER, StrokeLineCap.BUTT, 1.0, 0, null);
private static final @Nullable Background HANDLE_REGION_BACKGROUND = new Background(new BackgroundFill(Color.WHITE, null, null));
private static final @Nullable Border HANDLE_REGION_BORDER = new Border(new BorderStroke(Color.PURPLE, BorderStrokeStyle.SOLID, null, null));
private static final Circle PICK_NODE_SHAPE = new Circle(3);
private static final SVGPath PIVOT_NODE_SHAPE = new SVGPath();
private static final @Nullable Background PIVOT_REGION_BACKGROUND = new Background(new BackgroundFill(Color.PURPLE, null, null));
private static final @Nullable Border PIVOT_REGION_BORDER = null;
static {
PIVOT_NODE_SHAPE.setContent("M-5,-1 L -1,-1 -1,-5 1,-5 1,-1 5,-1 5 1 1,1 1,5 -1,5 -1,1 -5,1 Z");
}
private final Group group;
private Set groupReshapeableFigures;
private final Line line;
private double lineLength = 10.0;
private Point2D pickLocation;
private final Region pickNode;
private final Region pivotNode;
public RotateHandle(TransformableFigure figure) {
super(figure);
group = new Group();
pickNode = new Region();
pickNode.setShape(PICK_NODE_SHAPE);
pickNode.setManaged(false);
pickNode.setScaleShape(true);
pickNode.setCenterShape(true);
pickNode.resize(11, 11); // size must be odd
pickNode.getStyleClass().clear();
// pickNode.getStyleClass().add(handleStyleclass);
pickNode.setBorder(HANDLE_REGION_BORDER);
pickNode.setBackground(HANDLE_REGION_BACKGROUND);
pivotNode = new Region();
pivotNode.setShape(PIVOT_NODE_SHAPE);
pivotNode.setManaged(false);
pivotNode.setScaleShape(true);
pivotNode.setCenterShape(true);
pivotNode.resize(11, 11); // size must be odd
pivotNode.getStyleClass().clear();
// pivotNode.getStyleClass().add(pivotStyleclass);
pivotNode.setBorder(PIVOT_REGION_BORDER);
pivotNode.setBackground(PIVOT_REGION_BACKGROUND);
pivotNode.setVisible(false);
line = new Line();
line.getStyleClass().clear();
//line.getStyleClass().add(lineStyleclass);
group.getChildren().addAll(line, pickNode, pivotNode);
}
@Override
public boolean contains(DrawingView dv, double x, double y, double tolerance) {
Point2D p = getLocationInView();
return Points.squaredDistance(x, y, p.getX(), p.getY()) <= tolerance * tolerance;
}
@Override
public Cursor getCursor() {
return Cursor.CROSSHAIR;
}
public Point2D getLocationInView() {
return pickLocation;
}
@Override
public Group getNode(DrawingView view) {
double size = view.getEditor().getHandleSize();
lineLength = size * 1.5;
if (pickNode.getWidth() != size) {
pickNode.resize(size, size);
}
Paint color = Paintable.getPaint(view.getEditor().getHandleColor());
line.setStroke(color);
BorderStroke borderStroke = pickNode.getBorder().getStrokes().getFirst();
if (borderStroke == null || !borderStroke.getTopStroke().equals(color)) {
Border border = new Border(
new BorderStroke(color, INSIDE_STROKE, null, null)
);
pickNode.setBorder(border);
pivotNode.setBorder(border);
}
return group;
}
@Override
public TransformableFigure getOwner() {
return (TransformableFigure) super.getOwner();
}
private @Nullable Transform getRotateToWorld() {
TransformableFigure o = getOwner();
Transform t = o.getParentToWorld();
Point2D center = FXRectangles.center(o.getLayoutBounds());
Transform translate = Transform.translate(o.getStyled(TransformableFigure.TRANSLATE_X), o.getStyled(TransformableFigure.TRANSLATE_Y));
Transform scale = Transform.scale(o.getStyled(TransformableFigure.SCALE_X), o.getStyled(TransformableFigure.SCALE_Y), center.getX(), center.getY());
Transform rotate = Transform.rotate(o.getStyled(TransformableFigure.ROTATE), center.getX(), center.getY());
// t = ((TransformableFigure)o).getInverseTransform().createConcatenation(rotate).createConcatenation(scale).createConcatenation(translate);
t = FXTransforms.concat(t, FXTransforms.concat(translate, rotate));//.createConcatenation(translate).createConcatenation(t);
return t;
}
private @Nullable Transform getWorldToRotate() {
TransformableFigure o = getOwner();
Transform t = o.getWorldToParent();
Point2D center = FXRectangles.center(o.getLayoutBounds());
Transform translate = Transform.translate(-o.getStyled(TransformableFigure.TRANSLATE_X), -o.getStyled(TransformableFigure.TRANSLATE_Y));
Transform scale = Transform.scale(1.0 / o.getStyled(TransformableFigure.SCALE_X), 1.0 / o.getStyled(TransformableFigure.SCALE_Y), center.getX(), center.getY());
Transform rotate = Transform.rotate(-o.getStyled(TransformableFigure.ROTATE), center.getX(), center.getY());
// t = ((TransformableFigure)o).getInverseTransform().createConcatenation(rotate).createConcatenation(scale).createConcatenation(translate);
t = FXTransforms.concat(t, translate);
return t;
}
@Override
public void onMouseDragged(MouseEvent event, DrawingView view) {
TransformableFigure o = getOwner();
Point2D center = FXRectangles.center(o.getLayoutBounds());
Transform t = FXTransforms.concat(getWorldToRotate(), view.getViewToWorld());
Point2D newPoint = FXTransforms.transform(t, new Point2D(event.getX(), event.getY()));
double newRotate = 90 + Math.toDegrees(Angles.angle(center.getX(), center.getY(), newPoint.getX(), newPoint.getY()));
newRotate = newRotate % 360;
if (newRotate < 0) {
newRotate += 360;
}
if (!event.isAltDown() && !event.isControlDown()) {
// alt or control turns the constrainer off
newRotate = view.getConstrainer().constrainAngle(getOwner(), newRotate);
}
if (event.isMetaDown()) {
// meta snaps the location of the handle to the grid
}
DrawingModel model = view.getModel();
if (event.isShiftDown()) {
// shift transforms all selected figures
for (Figure f : groupReshapeableFigures) {
if (f instanceof TransformableFigure) {
model.set(f, TransformableFigure.ROTATE, newRotate);
}
}
} else {
model.set(getOwner(), TransformableFigure.ROTATE, newRotate);
}
}
@Override
public void onMousePressed(MouseEvent event, DrawingView view) {
pivotNode.setVisible(true);
// determine which figures can be reshaped together as a group
Set selectedFigures = view.getSelectedFigures();
groupReshapeableFigures = new HashSet<>();
for (Figure f : view.getSelectedFigures()) {
if (f.isGroupReshapeableWith(selectedFigures)) {
groupReshapeableFigures.add(f);
}
}
groupReshapeableFigures = view.getFiguresWithCompatibleHandle(groupReshapeableFigures, this);
}
@Override
public void onMouseReleased(MouseEvent event, DrawingView dv) {
pivotNode.setVisible(false);
// FIXME fireDrawingModelEvent undoable edit event
}
@Override
public boolean isSelectable() {
return true;
}
@Override
public void updateNode(DrawingView view) {
TransformableFigure o = getOwner();
Transform t = FXTransforms.concat(view.getWorldToView(), getRotateToWorld());
Bounds b = o.getLayoutBounds();
Point2D centerInLocal = FXRectangles.center(b);
double scaleY = o.getStyled(SCALE_Y);
Point2D p = new Point2D(centerInLocal.getX(), centerInLocal.getY() - b.getHeight() * 0.5 * scaleY);
p = FXTransforms.transform(t, p);
// rotates the node:
double rotate = o.getStyledNonNull(ROTATE);
pickNode.setRotate(rotate);
Point3D rotationAxis = o.getStyled(ROTATION_AXIS);
pickNode.setRotationAxis(rotationAxis);
pivotNode.setRotate(rotate);
pivotNode.setRotationAxis(rotationAxis);
Point2D centerInView = FXTransforms.transform(t, centerInLocal);
Point2D vector = new Point2D(p.getX() - centerInView.getX(), p.getY() - centerInView.getY());
double size = pickNode.getWidth();
vector = vector.normalize();
pickLocation = new Point2D(p.getX() + vector.getX() * lineLength, p.getY() + vector.getY() * lineLength);
pickNode.relocate(pickLocation.getX() - size * 0.5, pickLocation.getY() - size * 0.5);
pivotNode.relocate(centerInView.getX() - size * 0.5, centerInView.getY() - size * 0.5);
line.setStartX(pickLocation.getX());
line.setStartY(pickLocation.getY());
line.setEndX(p.getX());
line.setEndY(p.getY());
}
}