Implement #38: Polygon Shapes
Some checks failed
CI / build-and-test (pull_request) Failing after 16s
Some checks failed
CI / build-and-test (pull_request) Failing after 16s
This commit is contained in:
1
new-shapes.wiki
Submodule
1
new-shapes.wiki
Submodule
Submodule new-shapes.wiki added at 731a57b080
@@ -2,6 +2,7 @@ package ovh.gasser.newshapes;
|
||||
|
||||
import ovh.gasser.newshapes.shapes.SCircle;
|
||||
import ovh.gasser.newshapes.shapes.SCollection;
|
||||
import ovh.gasser.newshapes.shapes.SPolygon;
|
||||
import ovh.gasser.newshapes.shapes.SRectangle;
|
||||
import ovh.gasser.newshapes.shapes.SText;
|
||||
import ovh.gasser.newshapes.shapes.STriangle;
|
||||
@@ -16,4 +17,6 @@ public interface ShapeVisitor {
|
||||
void visitTriangle(STriangle sTriangle);
|
||||
|
||||
void visitText(SText sText);
|
||||
|
||||
void visitPolygon(SPolygon sPolygon);
|
||||
}
|
||||
|
||||
110
src/main/java/ovh/gasser/newshapes/shapes/SPolygon.java
Normal file
110
src/main/java/ovh/gasser/newshapes/shapes/SPolygon.java
Normal file
@@ -0,0 +1,110 @@
|
||||
package ovh.gasser.newshapes.shapes;
|
||||
|
||||
import ovh.gasser.newshapes.ShapeVisitor;
|
||||
import ovh.gasser.newshapes.attributes.ColorAttributes;
|
||||
import ovh.gasser.newshapes.attributes.SelectionAttributes;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class SPolygon extends AbstractShape {
|
||||
private final List<Point> points;
|
||||
|
||||
private SPolygon(List<Point> points) {
|
||||
super(calculateBounds(points));
|
||||
this.points = new ArrayList<>(points);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(ShapeVisitor visitor) {
|
||||
visitor.visitPolygon(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translate(int dx, int dy) {
|
||||
super.translate(dx, dy);
|
||||
for (Point p : points) {
|
||||
p.translate(dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(ResizeHandle handle, int dx, int dy) {
|
||||
Rectangle oldBounds = getBounds();
|
||||
super.resize(handle, dx, dy);
|
||||
Rectangle newBounds = getBounds();
|
||||
|
||||
if (oldBounds.width == 0 || oldBounds.height == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
double scaleX = (double) newBounds.width / oldBounds.width;
|
||||
double scaleY = (double) newBounds.height / oldBounds.height;
|
||||
|
||||
for (Point p : points) {
|
||||
int relativeX = p.x - oldBounds.x;
|
||||
int relativeY = p.y - oldBounds.y;
|
||||
p.x = newBounds.x + (int) (relativeX * scaleX);
|
||||
p.y = newBounds.y + (int) (relativeY * scaleY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape clone() {
|
||||
List<Point> clonedPoints = points.stream()
|
||||
.map(p -> new Point(p.x, p.y))
|
||||
.toList();
|
||||
SPolygon clone = new SPolygon(clonedPoints);
|
||||
ColorAttributes color = (ColorAttributes) this.getAttributes(ColorAttributes.ID);
|
||||
if (color != null) {
|
||||
clone.addAttributes(new ColorAttributes(color.filled, color.stroked, color.filledColor, color.strokedColor));
|
||||
}
|
||||
clone.addAttributes(new SelectionAttributes());
|
||||
return clone;
|
||||
}
|
||||
|
||||
public List<Point> getPoints() {
|
||||
return Collections.unmodifiableList(points);
|
||||
}
|
||||
|
||||
public static SPolygon create(List<Point> points) {
|
||||
if (points == null || points.size() < 3) {
|
||||
throw new IllegalArgumentException("Polygon must have at least 3 points");
|
||||
}
|
||||
SPolygon polygon = new SPolygon(points);
|
||||
polygon.addAttributes(new SelectionAttributes());
|
||||
polygon.addAttributes(new ColorAttributes(false, true, Color.BLACK, Color.BLACK));
|
||||
return polygon;
|
||||
}
|
||||
|
||||
public static SPolygon create(Point... points) {
|
||||
return create(List.of(points));
|
||||
}
|
||||
|
||||
private static Rectangle calculateBounds(List<Point> points) {
|
||||
if (points == null || points.isEmpty()) {
|
||||
return new Rectangle(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
int minX = points.get(0).x;
|
||||
int minY = points.get(0).y;
|
||||
int maxX = points.get(0).x;
|
||||
int maxY = points.get(0).y;
|
||||
|
||||
for (Point p : points) {
|
||||
minX = Math.min(minX, p.x);
|
||||
minY = Math.min(minY, p.y);
|
||||
maxX = Math.max(maxX, p.x);
|
||||
maxY = Math.max(maxY, p.y);
|
||||
}
|
||||
|
||||
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SPolygon{points=" + points.size() + ", bounds=" + super.toString() + "}";
|
||||
}
|
||||
}
|
||||
@@ -117,6 +117,30 @@ public class ShapeDraftman implements ShapeVisitor {
|
||||
drawHandlerIfSelected(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPolygon(SPolygon polygon) {
|
||||
ColorAttributes colAttrs = (ColorAttributes) polygon.getAttributes(ColorAttributes.ID);
|
||||
if (colAttrs == null) {
|
||||
colAttrs = DEFAULT_COLOR_ATTRIBUTES;
|
||||
}
|
||||
|
||||
java.util.List<Point> points = polygon.getPoints();
|
||||
int[] xPoints = points.stream().mapToInt(p -> p.x).toArray();
|
||||
int[] yPoints = points.stream().mapToInt(p -> p.y).toArray();
|
||||
int nPoints = points.size();
|
||||
|
||||
if (colAttrs.filled) {
|
||||
this.g2d.setColor(colAttrs.filledColor);
|
||||
this.g2d.fillPolygon(xPoints, yPoints, nPoints);
|
||||
}
|
||||
if (colAttrs.stroked) {
|
||||
this.g2d.setColor(colAttrs.strokedColor);
|
||||
this.g2d.drawPolygon(xPoints, yPoints, nPoints);
|
||||
}
|
||||
|
||||
drawHandlerIfSelected(polygon);
|
||||
}
|
||||
|
||||
private Color resolveTextColor(ColorAttributes attrs) {
|
||||
if (attrs == null) {
|
||||
return Color.BLACK;
|
||||
|
||||
@@ -19,11 +19,17 @@ public class MenuAddListener implements ActionListener {
|
||||
private final String shape;
|
||||
private final ShapesView view;
|
||||
private final SCollection model;
|
||||
private final Runnable onModelChanged;
|
||||
|
||||
public MenuAddListener(String shape, SCollection model, ShapesView view) {
|
||||
this(shape, model, view, () -> { });
|
||||
}
|
||||
|
||||
public MenuAddListener(String shape, SCollection model, ShapesView view, Runnable onModelChanged) {
|
||||
this.shape = shape;
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
this.onModelChanged = onModelChanged;
|
||||
}
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
@@ -41,6 +47,7 @@ public class MenuAddListener implements ActionListener {
|
||||
s.addAttributes(new SelectionAttributes());
|
||||
s.addAttributes(new ColorAttributes(true, false, randomColor(), Color.BLACK));
|
||||
model.add(s);
|
||||
onModelChanged.run();
|
||||
view.repaint();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,12 @@ package ovh.gasser.newshapes.ui.visitors;
|
||||
|
||||
import ovh.gasser.newshapes.ShapeVisitor;
|
||||
import ovh.gasser.newshapes.attributes.ColorAttributes;
|
||||
import ovh.gasser.newshapes.shapes.*;
|
||||
import ovh.gasser.newshapes.shapes.SCircle;
|
||||
import ovh.gasser.newshapes.shapes.SCollection;
|
||||
import ovh.gasser.newshapes.shapes.SPolygon;
|
||||
import ovh.gasser.newshapes.shapes.SRectangle;
|
||||
import ovh.gasser.newshapes.shapes.SText;
|
||||
import ovh.gasser.newshapes.shapes.STriangle;
|
||||
import ovh.gasser.newshapes.shapes.Shape;
|
||||
|
||||
import java.awt.*;
|
||||
@@ -105,6 +110,36 @@ public class HTMLDraftman implements ShapeVisitor {
|
||||
cssOutput.println("}");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPolygon(SPolygon polygon) {
|
||||
int id = polygon.hashCode();
|
||||
Rectangle bounds = polygon.getBounds();
|
||||
htmlOutput.printf("<svg class=\"poly%d\" viewBox=\"%d %d %d %d\">\n", id, bounds.x, bounds.y, bounds.width, bounds.height);
|
||||
|
||||
var points = new StringJoiner(" ");
|
||||
for (var point : polygon.getPoints()) {
|
||||
points.add("%d,%d".formatted(point.x, point.y));
|
||||
}
|
||||
|
||||
ColorAttributes attrs = (ColorAttributes) polygon.getAttributes(ColorAttributes.ID);
|
||||
String fill = attrs.filled ? formatCSSColor(attrs.filledColor) : "none";
|
||||
String stroke = attrs.stroked ? formatCSSColor(attrs.strokedColor) : "none";
|
||||
int strokeWidth = attrs.stroked ? 1 : 0;
|
||||
|
||||
htmlOutput.printf(" <polygon points=\"%s\" fill=\"%s\" stroke=\"%s\" stroke-width=\"%d\" />\n",
|
||||
points, fill, stroke, strokeWidth);
|
||||
htmlOutput.println("</svg>");
|
||||
|
||||
cssOutput.printf(".poly%d{\n", id);
|
||||
cssOutput.println(" position: absolute;");
|
||||
cssOutput.printf(" top: %dpx;\n", bounds.y);
|
||||
cssOutput.printf(" left: %dpx;\n", bounds.x);
|
||||
cssOutput.printf(" width: %dpx;\n", bounds.width);
|
||||
cssOutput.printf(" height: %dpx;\n", bounds.height);
|
||||
cssOutput.println(" overflow: visible;");
|
||||
cssOutput.println("}");
|
||||
}
|
||||
|
||||
private String attributesToCss(Shape shape) {
|
||||
ColorAttributes attrs = (ColorAttributes) shape.getAttributes(ColorAttributes.ID);
|
||||
String strokedColor = "#ffffff";
|
||||
|
||||
@@ -5,6 +5,7 @@ import ovh.gasser.newshapes.ShapeVisitor;
|
||||
import ovh.gasser.newshapes.attributes.ColorAttributes;
|
||||
import ovh.gasser.newshapes.shapes.SCircle;
|
||||
import ovh.gasser.newshapes.shapes.SCollection;
|
||||
import ovh.gasser.newshapes.shapes.SPolygon;
|
||||
import ovh.gasser.newshapes.shapes.SRectangle;
|
||||
import ovh.gasser.newshapes.shapes.SText;
|
||||
import ovh.gasser.newshapes.shapes.STriangle;
|
||||
@@ -92,6 +93,21 @@ public class SVGDraftman implements ShapeVisitor {
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPolygon(SPolygon sPolygon) {
|
||||
var points = new StringJoiner(" ");
|
||||
for (var point : sPolygon.getPoints()) {
|
||||
points.add("%d,%d".formatted(point.x, point.y));
|
||||
}
|
||||
ColorAttributes attrs = (ColorAttributes) sPolygon.getAttributes(ColorAttributes.ID);
|
||||
var style = new StringJoiner(";", "fill:", "");
|
||||
style.add(attrs.filled ? colorToHex(attrs.filledColor) : "none");
|
||||
if (attrs.stroked) {
|
||||
style.add("stroke:%s;stroke-width:1".formatted(colorToHex(attrs.strokedColor)));
|
||||
}
|
||||
this.output.printf("<polygon points=\"%s\" style=\"%s\" />\n", points, style);
|
||||
}
|
||||
|
||||
public void generateSVG(SCollection model) {
|
||||
output.println(String.format(SVG_PRELUDE, App.WIN_SIZE.width, App.WIN_SIZE.height));
|
||||
visitCollection(model);
|
||||
|
||||
Reference in New Issue
Block a user