/* * This file is part of LaTeXDraw. * Copyright (c) 2005-2017 Arnaud BLOUIN * LaTeXDraw is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later version. * LaTeXDraw is distributed without any warranty; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. */ package net.sf.latexdraw.view.jfx; import java.util.Objects; import javafx.scene.Group; import javafx.scene.shape.Arc; import javafx.scene.shape.ClosePath; import javafx.scene.shape.Ellipse; import javafx.scene.shape.LineTo; import javafx.scene.shape.MoveTo; import javafx.scene.shape.Path; import net.sf.latexdraw.models.MathUtils; import net.sf.latexdraw.models.interfaces.shape.ArrowStyle; import net.sf.latexdraw.models.interfaces.shape.IArrow; import net.sf.latexdraw.models.interfaces.shape.ILine; import net.sf.latexdraw.models.interfaces.shape.IPoint; /** * The JFX view of an arrow. * @author Arnaud Blouin */ public class ViewArrow extends Group { final IArrow arrow; final Path path; final Group additionalShapes; /** * Creates the view. * @param model The arrow. Cannot be null. * @throws NullPointerException if the given arrow is null. */ ViewArrow(final IArrow model) { super(); arrow = Objects.requireNonNull(model); path = new Path(); additionalShapes = new Group(); getChildren().add(path); getChildren().add(additionalShapes); } // public void paint(final Color fColour, final boolean asShadow) { // if(!arrow.hasStyle()) return; // // final ILine arrowLine = arrow.getArrowLine(); // if(arrowLine == null) return; // // final IPoint pt1 = arrowLine.getPoint1(); // final double lineAngle = arrowLine.getLineAngle(); // final double lineB = arrowLine.getB(); // final double c2x; // final double c2y; // final double c3x; // final double c3y; // // if(LNumber.equalsDouble(Math.abs(lineAngle), Math.PI / 2.)) { // final double cx = pt1.getX(); // final double cy = pt1.getY(); // c2x = Math.cos(lineAngle) * cx - Math.sin(lineAngle) * cy; // c2y = Math.sin(lineAngle) * cx + Math.cos(lineAngle) * cy; // c3x = Math.cos(-lineAngle) * (cx - c2x) - Math.sin(-lineAngle) * (cy - c2y); // c3y = Math.sin(-lineAngle) * (cx - c2x) + Math.cos(-lineAngle) * (cy - c2y); // }else { // c2x = -Math.sin(lineAngle) * lineB; // c2y = Math.cos(lineAngle) * lineB; // c3x = Math.cos(-lineAngle) * -c2x - Math.sin(-lineAngle) * (lineB - c2y); // c3y = Math.sin(-lineAngle) * -c2x + Math.cos(-lineAngle) * (lineB - c2y); // } // // if(!LNumber.equalsDouble(lineAngle % (Math.PI * 2.), 0.)) { // g.rotate(lineAngle); // g.translate(c3x, c3y); // } // // paintArrow(fColour, asShadow); // // if(!LNumber.equalsDouble(lineAngle % (Math.PI * 2.), 0.)) { // g.translate(-c3x, -c3y); // g.rotate(-lineAngle); // } // } // // // protected void paintCircle(final Graphics2D g, final Color fillColour, final Color lineColour) { // g.setColor(fillColour); // g.fill(path); // g.setColor(lineColour); // g.draw(path); // } // // // protected void paintDisk(final Graphics2D g, final Color lineColour) { // g.setColor(lineColour); // g.setStroke(new BasicStroke((float) model.getShape().getThickness(), BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER)); // g.fill(path); // g.draw(path); // } // // // protected void paintRoundBracket(final Graphics2D g, final Color lineColor) { // g.setColor(lineColor); // g.setStroke(new BasicStroke((float) model.getShape().getThickness(), BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER)); // g.draw(path); // } // // // protected void paintBarBracket(final Graphics2D g, final Color lineColor) { // g.setStroke(new BasicStroke((float) model.getShape().getFullThickness(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER)); // g.setColor(lineColor); // g.draw(path); // } // // // protected void paintArrow(final Color lineColor) { // g.setColor(lineColor); // g.fill(path); // } // // protected void paintArrow(final Graphics2D g, final Color fColour, final boolean asShadow) { // final IShape shape = model.getShape(); // final Color lineColor = asShadow ? shape.getShadowCol() : shape.getLineColour(); // final Color fillColor = asShadow ? shape.getShadowCol() : fColour; // // switch(model.getArrowStyle()) { // case LEFT_DBLE_ARROW: // case RIGHT_DBLE_ARROW: // case RIGHT_ARROW: // case LEFT_ARROW: // paintArrow(g, lineColor); // break; // case CIRCLE_END: // case CIRCLE_IN: // paintCircle(g, fillColor, lineColor); // break; // case DISK_END: // case DISK_IN: // paintDisk(g, lineColor); // break; // case LEFT_ROUND_BRACKET: // case RIGHT_ROUND_BRACKET: // paintRoundBracket(g, lineColor); // break; // case BAR_END: // case BAR_IN: // case LEFT_SQUARE_BRACKET: // case RIGHT_SQUARE_BRACKET: // paintBarBracket(g, lineColor); // break; // case ROUND_IN: // case ROUND_END: // g.setColor(lineColor); // g.setStroke(new BasicStroke((float) model.getShape().getFullThickness(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER)); // g.draw(path); // break; // case SQUARE_END: // g.setColor(lineColor); // g.setStroke(new BasicStroke((float) model.getShape().getFullThickness(), BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER)); // g.draw(path); // break; // case NONE: // break; // } // } private void updatePathDiskCircleEnd(final double xRot, final double yRot) { final double lineWidth = arrow.getShape().getFullThickness(); final double arrowRadius = arrow.getRoundShapedArrowRadius(); final Ellipse ell = new Ellipse(arrowRadius * 2d - lineWidth, arrowRadius * 2d - lineWidth); ell.setLayoutX(xRot - arrowRadius + lineWidth / 2d); ell.setLayoutY(yRot - arrowRadius + lineWidth / 2d); additionalShapes.getChildren().add(ell); } private void updatePathDiskCircleIn(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2) { final double arrowRadius = arrow.getRoundShapedArrowRadius(); final double lineWidth = arrow.getShape().getFullThickness(); double x = xRot + lineWidth / 2d; if(!isArrowInPositiveDirection(pt1, pt2)) { x -= 2d * arrowRadius; } final Ellipse ell = new Ellipse(arrowRadius * 2d - lineWidth, arrowRadius * 2d - lineWidth); ell.setLayoutX(x); ell.setLayoutY(yRot - arrowRadius + lineWidth / 2d); additionalShapes.getChildren().add(ell); } private void updatePathRightLeftSquaredBracket(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2) { final boolean invert = arrow.isInverted(); final double[] xs = new double[2]; final double[] ys = new double[2]; final double lineWidth = arrow.getShape().getFullThickness(); double lgth = arrow.getBracketShapedArrowLength() + arrow.getShape().getFullThickness() / 2d; if((!isArrowInPositiveDirection(pt1, pt2) || invert) && (isArrowInPositiveDirection(pt1, pt2) || !invert)) { lgth *= -1d; } updatePathBarIn(xRot, yRot, pt1, pt2, xs, ys); final double x3 = xs[0] + lgth; final double x4 = xs[1] + lgth; path.getElements().add(new MoveTo(xs[0], ys[0] + lineWidth / 2d)); path.getElements().add(new LineTo(x3, ys[0] + lineWidth / 2d)); path.getElements().add(new MoveTo(xs[1], ys[1] - lineWidth / 2d)); path.getElements().add(new LineTo(x4, ys[1] - lineWidth / 2d)); } private void updatePathBarIn(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2, final double[] xs, final double[] ys) { final double width = arrow.getBarShapedArrowWidth(); final double lineWidth = arrow.getShape().getThickness(); final double dec = isArrowInPositiveDirection(pt1, pt2) ? lineWidth / 2d : -lineWidth / 2d; xs[0] = xRot + dec; xs[1] = xRot + dec; ys[0] = yRot - width / 2d; ys[1] = yRot + width / 2d; path.getElements().add(new MoveTo(xs[0], ys[0])); path.getElements().add(new LineTo(xs[1], ys[1])); } private void updatePathBarEnd(final double xRot, final double yRot) { final double width = arrow.getBarShapedArrowWidth(); path.getElements().add(new MoveTo(xRot, yRot - width / 2d)); path.getElements().add(new LineTo(xRot, yRot + width / 2d)); } private void updatePathArrow(final double x1, final double y1, final double x2, final double y2, final double x3, final double y3, final double x4, final double y4) { path.getElements().add(new MoveTo(x1, y1)); path.getElements().add(new LineTo(x2, y2)); path.getElements().add(new LineTo(x3, y3)); path.getElements().add(new LineTo(x4, y4)); path.getElements().add(new ClosePath()); } private void updatePathRightLeftArrow(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2) { final boolean invert = arrow.isInverted(); final double width = arrow.getArrowShapedWidth(); double length = arrow.getArrowLength() * width; double inset = arrow.getArrowInset() * length; double x = xRot; if(invert) { x += isArrowInPositiveDirection(pt1, pt2) ? length : -length; } if((!isArrowInPositiveDirection(pt1, pt2) || invert) && (isArrowInPositiveDirection(pt1, pt2) || !invert)) { length *= -1d; inset *= -1d; } updatePathArrow(x, yRot, x + length, yRot - width / 2., x + length - inset, yRot, x + length, yRot + width / 2d); } private boolean isArrowInPositiveDirection(final IPoint pt1, final IPoint pt2) { return pt1.getX() < pt2.getX() || pt1.getX() == pt2.getX() && pt1.getY() < pt2.getY(); } private void updatePathRoundLeftRightBracket(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2) { final boolean invert = arrow.isInverted(); final double width = arrow.getBarShapedArrowWidth(); final double lgth = arrow.getRBracketNum() * width; final double xarc = arrow.isInverted() ? xRot : xRot + arrow.getShape().getThickness() / 2d; final double widtharc = lgth * 2d + (invert ? arrow.getShape().getThickness() / 2d : 0d); final Arc arc = new Arc(xarc, yRot - width / 2d, widtharc, width, 130d, 100d); if((!isArrowInPositiveDirection(pt1, pt2) || invert) && (isArrowInPositiveDirection(pt1, pt2) || !invert)) { final double rotX = Math.cos(Math.PI) * xRot - Math.sin(Math.PI) * yRot; final double rotY = Math.sin(Math.PI) * xRot + Math.cos(Math.PI) * yRot; arc.setTranslateX(xRot - rotX); arc.setTranslateY(yRot - rotY); arc.setRotate(Math.PI); } additionalShapes.getChildren().add(arc); } private void updatePathDoubleLeftRightArrow(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2) { final boolean invert = arrow.isInverted(); final double width = arrow.getArrowShapedWidth(); double length = arrow.getArrowLength() * width; double inset = arrow.getArrowInset() * length; double x = xRot; if(invert) { x += isArrowInPositiveDirection(pt1, pt2) ? 2d * length : -2d * length; } if((!isArrowInPositiveDirection(pt1, pt2) || invert) && (isArrowInPositiveDirection(pt1, pt2) || !invert)) { length *= -1d; inset *= -1d; } updatePathArrow(x, yRot, x + length, yRot - width / 2d, x + length - inset, yRot, x + length, yRot + width / 2d); updatePathArrow(x + length, yRot, x + 2d * length, yRot - width / 2d, x + 2d * length - inset, yRot, x + 2d * length, yRot + width / 2d); final double x2 = x + length - inset; final double x2bis = x + 2d * length - inset; path.getElements().add(new LineTo(x2, yRot)); path.getElements().add(new MoveTo(x2bis, yRot)); } private void updatePathSquareRoundEnd(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2) { path.getElements().add(new LineTo(pt1.getX() < pt2.getX() ? xRot + 1d : xRot - 1d, yRot)); path.getElements().add(new MoveTo(xRot, yRot)); } private void updatePathRoundIn(final double xRot, final double yRot, final IPoint pt1, final IPoint pt2) { final double lineWidth = isArrowInPositiveDirection(pt1, pt2) ? arrow.getShape().getFullThickness() : -arrow.getShape().getFullThickness(); final double x = xRot + lineWidth / 2d; path.getElements().add(new MoveTo(x, yRot)); path.getElements().add(new LineTo(x, yRot)); } public void updatePath() { path.getElements().clear(); additionalShapes.getChildren().clear(); final ILine arrowLine = arrow.getArrowLine(); if(arrow.getArrowStyle() == ArrowStyle.NONE || arrowLine == null) { return; } if(!arrow.hasStyle()) return; final double xRot; final double yRot; final double lineAngle = arrowLine.getLineAngle(); final IPoint pt1 = arrowLine.getPoint1(); final IPoint pt2 = arrowLine.getPoint2(); final double lineB = arrowLine.getB(); final double c2x; final double c2y; final double c3x; final double c3y; if(MathUtils.INST.equalsDouble(Math.abs(lineAngle), Math.PI / 2d)) { final double cx = pt1.getX(); final double cy = pt1.getY(); xRot = cx; yRot = cy; c2x = Math.cos(lineAngle) * cx - Math.sin(lineAngle) * cy; c2y = Math.sin(lineAngle) * cx + Math.cos(lineAngle) * cy; c3x = Math.cos(-lineAngle) * (cx - c2x) - Math.sin(-lineAngle) * (cy - c2y); c3y = Math.sin(-lineAngle) * (cx - c2x) + Math.cos(-lineAngle) * (cy - c2y); }else { c2x = -Math.sin(lineAngle) * lineB; c2y = Math.cos(lineAngle) * lineB; c3x = Math.cos(-lineAngle) * -c2x - Math.sin(-lineAngle) * (lineB - c2y); c3y = Math.sin(-lineAngle) * -c2x + Math.cos(-lineAngle) * (lineB - c2y); xRot = Math.cos(-lineAngle) * pt1.getX() - Math.sin(-lineAngle) * (pt1.getY() - lineB); yRot = Math.sin(-lineAngle) * pt1.getX() + Math.cos(-lineAngle) * (pt1.getY() - lineB) + lineB; } switch(arrow.getArrowStyle()) { case BAR_END: updatePathBarEnd(xRot, yRot); break; case BAR_IN: updatePathBarIn(xRot, yRot, pt1, pt2, new double[2], new double[2]); break; case CIRCLE_END: case DISK_END: updatePathDiskCircleEnd(xRot, yRot); break; case CIRCLE_IN: case DISK_IN: updatePathDiskCircleIn(xRot, yRot, pt1, pt2); break; case RIGHT_ARROW: case LEFT_ARROW: updatePathRightLeftArrow(xRot, yRot, pt1, pt2); break; case RIGHT_DBLE_ARROW: case LEFT_DBLE_ARROW: updatePathDoubleLeftRightArrow(xRot, yRot, pt1, pt2); break; case RIGHT_ROUND_BRACKET: case LEFT_ROUND_BRACKET: updatePathRoundLeftRightBracket(xRot, yRot, pt1, pt2); break; case LEFT_SQUARE_BRACKET: case RIGHT_SQUARE_BRACKET: updatePathRightLeftSquaredBracket(xRot, yRot, pt1, pt2); break; case SQUARE_END: case ROUND_END: updatePathSquareRoundEnd(xRot, yRot, pt1, pt2); break; case ROUND_IN: updatePathRoundIn(xRot, yRot, pt1, pt2); break; case NONE: break; } if(!MathUtils.INST.equalsDouble(lineAngle % (Math.PI * 2d), 0d)) { path.setRotate(Math.toDegrees(lineAngle)); // path.setTranslateX(c3x); // path.setTranslateY(c3y); additionalShapes.setRotate(Math.toDegrees(lineAngle)); // additionalShapes.setTranslateX(c3x); // additionalShapes.setTranslateY(c3y); } } public void flush() { getChildren().clear(); } }