All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jhotdraw8.draw.handle.BezierNodeNonMovableEditHandle Maven / Gradle / Ivy

The newest version!
/*
 * @(#)BezierNodeEditHandle.java
 * Copyright © 2023 The authors and contributors of JHotDraw. MIT License.
 */
package org.jhotdraw8.draw.handle;

import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.Cursor;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Menu;
import javafx.scene.control.RadioMenuItem;
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.shape.Circle;
import javafx.scene.shape.ClosePath;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Transform;
import org.jhotdraw8.application.action.Actions;
import org.jhotdraw8.draw.DrawLabels;
import org.jhotdraw8.draw.DrawingView;
import org.jhotdraw8.draw.css.value.CssColor;
import org.jhotdraw8.draw.figure.Figure;
import org.jhotdraw8.fxcollection.typesafekey.MapAccessor;
import org.jhotdraw8.geom.FXTransforms;
import org.jhotdraw8.geom.Points;
import org.jhotdraw8.geom.shape.BezierNode;
import org.jhotdraw8.geom.shape.BezierPath;
import org.jspecify.annotations.Nullable;

import static org.jhotdraw8.draw.figure.TransformableFigure.ROTATE;
import static org.jhotdraw8.draw.figure.TransformableFigure.ROTATION_AXIS;
import static org.jhotdraw8.geom.shape.BezierNode.CLOSE_MASK;
import static org.jhotdraw8.geom.shape.BezierNode.MOVE_MASK;

/**
 * Handle for the point ofCollection a figure.
 *
 * @author Werner Randelshofer
 */
public class BezierNodeNonMovableEditHandle extends AbstractHandle {

    private static final @Nullable Background REGION_BACKGROUND = new Background(new BackgroundFill(Color.WHITE, null, null));
    private static final @Nullable Border REGION_BORDER = new Border(new BorderStroke(Color.BLUE, BorderStrokeStyle.SOLID, null, null));
    private static final Circle REGION_SHAPE_CUBIC = new Circle(0, 0, 4);
    private static final Rectangle REGION_SHAPE_LINEAR = new Rectangle(7, 7);
    private static final Path REGION_SHAPE_QUADRATIC = new Path();


    static {
        final ObservableList elements = REGION_SHAPE_QUADRATIC.getElements();
        elements.add(new MoveTo(2, 0));
        elements.add(new LineTo(4, 0));
        elements.add(new LineTo(6, 2));
        elements.add(new LineTo(6, 4));
        elements.add(new LineTo(4, 6));
        elements.add(new LineTo(2, 6));
        elements.add(new LineTo(0, 4));
        elements.add(new LineTo(0, 2));
        elements.add(new ClosePath());
        elements.add(new MoveTo(3, 0));
        elements.add(new LineTo(3, 6));
    }

    private final Region node;
    private @Nullable Point2D pickLocation;
    private final int nodeIndex;
    private final MapAccessor pathKey;


    public BezierNodeNonMovableEditHandle(Figure figure, MapAccessor pathKey, int nodeIndex) {
        super(figure);
        this.pathKey = pathKey;
        this.nodeIndex = nodeIndex;
        node = new Region();
        node.setShape(REGION_SHAPE_LINEAR);
        node.setManaged(false);
        node.setScaleShape(true);
        node.setCenterShape(true);
        node.resize(11, 11);
        node.setBorder(REGION_BORDER);
        node.setBackground(REGION_BACKGROUND);
    }

    @Override
    public boolean contains(DrawingView drawingView, double x, double y, double tolerance) {
        Point2D p = getLocationInView();
        return Points.squaredDistance(x, y, p.getX(), p.getY()) <= tolerance * tolerance;
    }

    private @Nullable BezierNode getBezierNode() {
        BezierPath path = owner.get(pathKey);
        return path == null || path.size() <= nodeIndex ? null : path.get(nodeIndex);

    }

    @Override
    public Cursor getCursor() {
        return Cursor.CROSSHAIR;
    }

    private Point2D getLocation() {
        return getBezierNode().getPoint(Point2D::new);

    }

    public Point2D getLocationInView() {
        return pickLocation;
    }

    @Override
    public Region getNode(DrawingView view) {
        double size = view.getEditor().getHandleSize();
        if (node.getWidth() != size) {
            node.resize(size, size);
        }
        CssColor color = view.getEditor().getHandleColor();
        BorderStroke borderStroke = node.getBorder().getStrokes().getFirst();
        if (!borderStroke.getTopStroke().equals(color.getColor())) {
            node.setBorder(new Border(
                    new BorderStroke(color.getColor(), BorderStrokeStyle.SOLID, null, null)
            ));
        }

        return node;
    }

    @Override
    public void onMouseClicked(MouseEvent event, DrawingView dv) {
        // do nothing on mouse clicked, because it may happen accidentally
    }



    @Override
    public void onMousePressed(MouseEvent event, DrawingView view) {
        if (event.isPopupTrigger()) {
            onPopupTriggered(event, view);
        }
    }

    private void onPopupTriggered(MouseEvent event, DrawingView view) {
        ContextMenu contextMenu = new ContextMenu();

        // path menu ----
        Menu pathMenu = new Menu(DrawLabels.getResources().getString("handle.bezierNode.path.text"));
        RadioMenuItem moveToRadio = new RadioMenuItem(DrawLabels.getResources().getString("handle.bezierNode.moveTo.text"));
        RadioMenuItem lineToRadio = new RadioMenuItem(DrawLabels.getResources().getString("handle.bezierNode.lineTo.text"));

        BezierPath path = owner.get(pathKey);
        if (path == null || nodeIndex >= path.size()) {
            return;
        }
        BezierPath[] finalPath = {path};
        BezierNode bnode = finalPath[0].get(nodeIndex);

        if (nodeIndex == 0 || bnode.isMoveTo()) {
            moveToRadio.setSelected(true);
        } else {
            lineToRadio.setSelected(true);
        }

        moveToRadio.setOnAction(actionEvent -> {
            BezierNode changedNode = bnode.withMaskBitsClears(CLOSE_MASK).withMaskBitsSet(MOVE_MASK);
            finalPath[0] = finalPath[0].set(nodeIndex, changedNode);
            view.getModel().set(owner, pathKey, finalPath[0]);
            view.recreateHandles();
        });
        lineToRadio.setOnAction(actionEvent -> {
            BezierNode changedNode = bnode.withMaskBitsClears(MOVE_MASK | CLOSE_MASK);
            finalPath[0] = finalPath[0].set(nodeIndex, changedNode);
            view.getModel().set(owner, pathKey, finalPath[0]);
            view.recreateHandles();
        });
        pathMenu.getItems().addAll(moveToRadio, lineToRadio);

        // tangents menu ----
        Menu tangentsMenu = new Menu(DrawLabels.getResources().getString("handle.bezierNode.tangents.text"));

        RadioMenuItem noneRadio = new RadioMenuItem();
        Actions.bindMenuItem(noneRadio, new BezierNodeHandleNoTangentsAction(owner, pathKey, nodeIndex, view));
        RadioMenuItem inRadio;
        if (nodeIndex > 0) {
            inRadio = new RadioMenuItem();
            Actions.bindMenuItem(inRadio, new BezierNodeHandleIncomingTangentAction(owner, pathKey, nodeIndex, view));
        } else {
            inRadio = null;
        }
        RadioMenuItem outRadio;
        int nodeListSize = path.size();
        if (nodeIndex < nodeListSize - 1) {
            outRadio = new RadioMenuItem();
            Actions.bindMenuItem(outRadio, new BezierNodeHandleOutgoingTangentAction(owner, pathKey, nodeIndex, view));
        } else {
            outRadio = null;
        }
        RadioMenuItem bothRadio;
        if (nodeIndex > 0 && nodeIndex < nodeListSize - 1) {
            bothRadio = new RadioMenuItem();
            Actions.bindMenuItem(bothRadio, new BezierNodeHandleIncomingAndOutgoingTangentAction(owner, pathKey, nodeIndex, view));
        } else {
            bothRadio = null;
        }
        tangentsMenu.getItems().add(noneRadio);
        if (inRadio != null) {
            tangentsMenu.getItems().add(inRadio);
        }
        if (outRadio != null) {
            tangentsMenu.getItems().add(outRadio);
        }
        if (bothRadio != null) {
            tangentsMenu.getItems().add(bothRadio);
        }


        // assemble all menus ----
        contextMenu.getItems().add(tangentsMenu);
        contextMenu.getItems().add(pathMenu);
        contextMenu.show(node, event.getScreenX(), event.getScreenY());
        event.consume();
    }


    @Override
    public void onMouseReleased(MouseEvent event, DrawingView view) {
        if (event.isPopupTrigger()) {
            onPopupTriggered(event, view);
        }
    }

    @Override
    public boolean isSelectable() {
        return true;
    }

    @Override
    public void updateNode(DrawingView view) {
        Figure f = getOwner();
        Transform t = FXTransforms.concat(view.getWorldToView(), f.getLocalToWorld());
        BezierPath path = f.get(pathKey);
        if (path == null || nodeIndex >= path.size()) {
            node.setVisible(false);
            return;
        } else {
            node.setVisible(true);
        }
        Point2D c0 = getLocation();
        pickLocation = c0 = FXTransforms.transform(t, c0);
        double size = node.getWidth();
        node.relocate(c0.getX() - size * 0.5, c0.getY() - size * 0.5);
        // rotates the node:
        node.setRotate(f.getStyledNonNull(ROTATE));
        node.setRotationAxis(f.getStyled(ROTATION_AXIS));

        BezierNode bn = getBezierNode();
        if (bn.hasIn() && bn.hasOut()) {
            node.setShape(REGION_SHAPE_CUBIC);// FIXME this is not correct
        } else if (bn.hasIn() || bn.hasOut()) {
            node.setShape(REGION_SHAPE_QUADRATIC);// FIXME this is not correct
        } else {
            node.setShape(REGION_SHAPE_LINEAR);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy