/*
* 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 javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.Path;
import javafx.scene.shape.StrokeLineCap;
import net.sf.latexdraw.models.ShapeFactory;
import net.sf.latexdraw.models.interfaces.shape.DotStyle;
import net.sf.latexdraw.models.interfaces.shape.IDot;
import net.sf.latexdraw.models.interfaces.shape.IPoint;
import net.sf.latexdraw.models.interfaces.shape.IShape;
import org.eclipse.jdt.annotation.NonNull;
/**
* The JFX shape view for dot shapes.
* @author Arnaud Blouin
*/
public class ViewDot extends ViewShape<IDot> {
final Path path;
final Ellipse dot;
final ChangeListener<Object> updateDot = (observable, oldValue, newValue) -> updateDot();
/**
* Creates the view.
* @param sh The model.
*/
ViewDot(final @NonNull IDot sh) {
super(sh);
path = new Path();
dot = new Ellipse();
getChildren().addAll(dot, path);
model.styleProperty().addListener(updateDot);
model.getPosition().xProperty().addListener(updateDot); // FIXME to optimise by adding addListener in IPoint
model.getPosition().yProperty().addListener(updateDot);
model.diametreProperty().addListener(updateDot);
model.fillingColProperty().addListener(updateDot);
rotateProperty().bind(Bindings.createDoubleBinding(() -> Math.toDegrees(model.getRotationAngle()), model.rotationAngleProperty()));
updateDot();
}
private void updateDot() {
final DotStyle dotStyle = model.getDotStyle();
path.setVisible(dotStyle != DotStyle.DOT && dotStyle != DotStyle.O);
dot.setVisible(dotStyle == DotStyle.DOT || dotStyle == DotStyle.O || dotStyle == DotStyle.OPLUS || dotStyle == DotStyle.OTIMES);
path.getElements().clear();
setStroke();
setFill();
switch(dotStyle) {
case ASTERISK:
setPathAsterisk();
break;
case BAR:
setPathBar();
break;
case DIAMOND:
setPathDiamond();
break;
case O:
case DOT:
setPathO();
break;
case FDIAMOND:
setPathDiamond();
break;
case PENTAGON:
case FPENTAGON:
setPathPentagon();
break;
case SQUARE:
case FSQUARE:
setPathSquare();
break;
case TRIANGLE:
case FTRIANGLE:
setPathTriangle();
break;
case OPLUS:
setPathOPlus();
break;
case OTIMES:
setPathOTime();
break;
case PLUS:
setPathPlus();
break;
case X:
setPathX();
break;
}
}
private void setPathOTime() {
final IPoint centre = model.getPosition();
final IPoint br = model.getLazyBottomRightPoint();
final IPoint tl = model.getLazyTopLeftPoint();
final double dec = model.getGeneralGap();
final double tlx = tl.getX();
final double tly = tl.getY();
final double brx = br.getX();
final double bry = br.getY();
setPathOLikeDot(model.getOGap());
IPoint p1 = ShapeFactory.INST.createPoint((tlx + brx) / 2d, tly + dec * 2d);
IPoint p2 = ShapeFactory.INST.createPoint((tlx + brx) / 2d, bry - dec * 2d);
p1 = p1.rotatePoint(centre, Math.PI / 4d);
p2 = p2.rotatePoint(centre, Math.PI / 4d);
path.getElements().add(ViewFactory.INSTANCE.createMoveTo(p1.getX(), p1.getY()));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(p2.getX(), p2.getY()));
p1.setPoint(tlx + dec * 2d, (tly + bry) / 2d);
p2.setPoint(brx - dec * 2d, (tly + bry) / 2d);
p1 = p1.rotatePoint(centre, Math.PI / 4d);
p2 = p2.rotatePoint(centre, Math.PI / 4d);
path.getElements().add(ViewFactory.INSTANCE.createMoveTo(p1.getX(), p1.getY()));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(p2.getX(), p2.getY()));
}
private void setPathOPlus() {
final double dec = model.getGeneralGap();
final IPoint br = model.getLazyBottomRightPoint();
final IPoint tl = model.getLazyTopLeftPoint();
setPathOLikeDot(model.getOGap());
path.getElements().add(ViewFactory.INSTANCE.createMoveTo((tl.getX() + br.getX()) / 2d, tl.getY() + dec * 2d));
path.getElements().add(ViewFactory.INSTANCE.createLineTo((tl.getX() + br.getX()) / 2d, br.getY() - dec * 2d));
path.getElements().add(ViewFactory.INSTANCE.createMoveTo(tl.getX() + dec * 2d, (tl.getY() + br.getY()) / 2d));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(br.getX() - dec * 2d, (tl.getY() + br.getY()) / 2d));
}
private void setPathO() {
setPathOLikeDot(model.getOGap());
}
private void setPathOLikeDot(final double dec) {
final IPoint pos = model.getPosition();
final double radius = (model.getDiametre() - dec) / 2d;
dot.setCenterX(pos.getX());
dot.setCenterY(pos.getY());
dot.setRadiusX(radius);
dot.setRadiusY(radius);
}
private void setPathBar() {
final IPoint br = model.getLazyBottomRightPoint();
final IPoint tl = model.getLazyTopLeftPoint();
path.getElements().add(ViewFactory.INSTANCE.createMoveTo((tl.getX() + br.getX()) / 2d, tl.getY() + model.getBarThickness() / 2d));
path.getElements().add(ViewFactory.INSTANCE.createLineTo((tl.getX() + br.getX()) / 2d, br.getY() + model.getBarGap()));
}
private void setPathPlus() {
final double plusGap = model.getPlusGap();
final IPoint br = model.getLazyBottomRightPoint();
final IPoint tl = model.getLazyTopLeftPoint();
path.getElements().add(ViewFactory.INSTANCE.createMoveTo((tl.getX() + br.getX()) / 2d, tl.getY() - plusGap));
path.getElements().add(ViewFactory.INSTANCE.createLineTo((tl.getX() + br.getX()) / 2d, br.getY() + plusGap));
path.getElements().add(ViewFactory.INSTANCE.createMoveTo(tl.getX() - plusGap, (tl.getY() + br.getY()) / 2d));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(br.getX() + plusGap, (tl.getY() + br.getY()) / 2d));
}
private void setPathSquare() {
final double dec = model.getDiametre() / IDot.THICKNESS_O_STYLE_FACTOR;
final IPoint tl = model.getLazyTopLeftPoint();
final double width = model.getDiametre() - dec * 3d;
final double x = tl.getX() + dec + dec / 2d;
final double y = tl.getY() + dec + dec / 2d;
path.getElements().add(ViewFactory.INSTANCE.createMoveTo(x, y));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(x + width, y));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(x + width, y + width));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(x, y + width));
path.getElements().add(ViewFactory.INSTANCE.createClosePath());
}
private void setPathX() {
final IPoint br = model.getLazyBottomRightPoint();
final IPoint tl = model.getLazyTopLeftPoint();
final double crossGap = model.getCrossGap();
path.getElements().add(ViewFactory.INSTANCE.createMoveTo(tl.getX() + crossGap, tl.getY() + crossGap));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(br.getX() - crossGap, br.getY() - crossGap));
path.getElements().add(ViewFactory.INSTANCE.createMoveTo(br.getX() - crossGap, tl.getY() + crossGap));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(tl.getX() + crossGap, br.getY() - crossGap));
}
private void setPathAsterisk() {
final IPoint br = model.getLazyBottomRightPoint();
final IPoint tl = model.getLazyTopLeftPoint();
final double width = model.getDiametre();
final double dec = width / IDot.THICKNESS_O_STYLE_FACTOR;
final double xCenter = (tl.getX() + br.getX()) / 2d;
final double yCenter = (tl.getY() + br.getY()) / 2d;
final double radius = Math.abs(tl.getY() + width / 10d - (br.getY() - width / 10d)) / 2d + dec;
path.getElements().add(ViewFactory.INSTANCE.createMoveTo(xCenter, tl.getY() + width / 10d - dec));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(xCenter, br.getY() - width / 10d + dec));
path.getElements().add(ViewFactory.INSTANCE.createMoveTo(Math.cos(Math.PI / 6d) * radius + xCenter, radius / 2d + yCenter));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(Math.cos(7d * Math.PI / 6d) * radius + xCenter, Math.sin(7d * Math.PI / 6d) * radius + yCenter));
path.getElements().add(ViewFactory.INSTANCE.createMoveTo(Math.cos(5d * Math.PI / 6d) * radius + xCenter, Math.sin(5d * Math.PI / 6d) * radius + yCenter));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(Math.cos(11d * Math.PI / 6d) * radius + xCenter, Math.sin(11d * Math.PI / 6d) * radius + yCenter));
}
/**
* Creates a diamond (one of the possibles shapes of a dot).
*/
private void setPathDiamond() {
final IPoint tl = model.getLazyTopLeftPoint();
final IPoint br = model.getLazyBottomRightPoint();
final double dec = model.getDiametre() / IDot.THICKNESS_O_STYLE_FACTOR;
// This diamond is a golden diamond
// cf. http://mathworld.wolfram.com/GoldenRhombus.html
final double midY = (tl.getY() + br.getY()) / 2d;
final double a = Math.abs(tl.getX() - br.getX()) / (2d * Math.sin(IShape.GOLDEN_ANGLE));
final double p = 2d * a * Math.cos(IShape.GOLDEN_ANGLE);
final double x1 = br.getX() - dec - 0.5 * dec;
final double x3 = tl.getX() + dec + 0.5 * dec;
path.getElements().add(ViewFactory.INSTANCE.createMoveTo((x1 + x3) / 2d, midY + p / 2d - dec - 0.5 * dec));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(x1, midY));
path.getElements().add(ViewFactory.INSTANCE.createLineTo((x1 + x3) / 2d, midY - p / 2d + dec + 0.5 * dec));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(x3, midY));
path.getElements().add(ViewFactory.INSTANCE.createClosePath());
}
/**
* Creates a pentagon (one of the possibles shapes of a dot)
*/
private void setPathPentagon() {
final double dec = model.getDiametre() / IDot.THICKNESS_O_STYLE_FACTOR;
final IPoint tl = model.getLazyTopLeftPoint();
final IPoint br = model.getLazyBottomRightPoint();
final double yCenter = (tl.getY() + br.getY()) / 2d - dec;
final double xCenter = (tl.getX() + br.getX()) / 2d;
final double dist = Math.abs(tl.getY() - br.getY()) / 2d + dec;
final double c1 = 0.25 * (Math.sqrt(5d) - 1d) * dist;
final double s1 = Math.sin(2 * Math.PI / 5d) * dist;
final double c2 = 0.25 * (Math.sqrt(5d) + 1d) * dist;
final double s2 = Math.sin(4 * Math.PI / 5d) * dist;
path.getElements().add(ViewFactory.INSTANCE.createMoveTo(xCenter, tl.getY() - dec));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(s1 + xCenter, -c1 + yCenter + dec));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(s2 + xCenter, c2 + yCenter + dec));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(-s2 + xCenter, c2 + yCenter + dec));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(-s1 + xCenter, -c1 + yCenter + dec));
path.getElements().add(ViewFactory.INSTANCE.createClosePath());
}
/**
* Creates a triangle (one of the possibles shapes of a dot).
*/
private void setPathTriangle() {
final IPoint tl = model.getLazyTopLeftPoint();
final double dec = model.getDiametre() / IDot.THICKNESS_O_STYLE_FACTOR;
final IPoint br = model.getLazyBottomRightPoint();
path.getElements().add(ViewFactory.INSTANCE.createMoveTo((br.getX() + tl.getX()) / 2d, tl.getY() - 1.5 * dec));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(tl.getX() - 0.3 * dec, br.getY() - 3d * dec));
path.getElements().add(ViewFactory.INSTANCE.createLineTo(br.getX() + 0.3 * dec, br.getY() - 3d * dec));
path.getElements().add(ViewFactory.INSTANCE.createClosePath());
}
public void setFill() {
switch(model.getDotStyle()) {
case O:
dot.setFill(model.getFillingCol().toJFX());
break;
case OPLUS:
case OTIMES:
dot.setFill(null);
break;
case DIAMOND:
case PENTAGON:
case SQUARE:
case TRIANGLE:
path.setFill(model.getFillingCol().toJFX());
break;
case DOT:
dot.setFill(model.getLineColour().toJFX());
break;
case FDIAMOND:
case FPENTAGON:
case FSQUARE:
case FTRIANGLE:
path.setFill(model.getLineColour().toJFX());
break;
}
}
private void setStroke() {
path.setStroke(model.getLineColour().toJFX());
dot.setStroke(model.getLineColour().toJFX());
switch(model.getDotStyle()) {
case O:
dot.setStrokeLineCap(StrokeLineCap.SQUARE);
dot.setStrokeWidth(model.getGeneralGap());
break;
case DOT:
dot.setStrokeLineCap(StrokeLineCap.SQUARE);
dot.setStrokeWidth(model.getDiametre() / IDot.THICKNESS_O_STYLE_FACTOR);
break;
case OPLUS:
case OTIMES:
dot.setStrokeLineCap(StrokeLineCap.SQUARE);
dot.setStrokeWidth(model.getDiametre() / IDot.THICKNESS_O_STYLE_FACTOR);
path.setStrokeLineCap(StrokeLineCap.BUTT);
path.setStrokeWidth(model.getGeneralGap());
break;
case FTRIANGLE:
case TRIANGLE:
case FPENTAGON:
case PENTAGON:
case FDIAMOND:
case DIAMOND:
case ASTERISK:
path.setStrokeLineCap(StrokeLineCap.SQUARE);
path.setStrokeWidth(model.getGeneralGap());
break;
case BAR:
path.setStrokeLineCap(StrokeLineCap.SQUARE);
path.setStrokeWidth(model.getBarThickness());
break;
case FSQUARE:
case SQUARE:
path.setStrokeLineCap(StrokeLineCap.BUTT);
path.setStrokeWidth(model.getGeneralGap());
break;
case PLUS:
path.setStrokeLineCap(StrokeLineCap.SQUARE);
path.setStrokeWidth(model.getDiametre() / IDot.PLUS_COEFF_WIDTH);
break;
case X:
path.setStrokeLineCap(StrokeLineCap.SQUARE);
path.setStrokeWidth(model.getCrossGap());
break;
}
}
@Override
public void flush() {
model.styleProperty().removeListener(updateDot);
model.getPosition().xProperty().removeListener(updateDot);
model.getPosition().yProperty().removeListener(updateDot);
model.diametreProperty().removeListener(updateDot);
model.fillingColProperty().removeListener(updateDot);
super.flush();
}
}