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.SCircle;
|
||||||
import ovh.gasser.newshapes.shapes.SCollection;
|
import ovh.gasser.newshapes.shapes.SCollection;
|
||||||
|
import ovh.gasser.newshapes.shapes.SPolygon;
|
||||||
import ovh.gasser.newshapes.shapes.SRectangle;
|
import ovh.gasser.newshapes.shapes.SRectangle;
|
||||||
import ovh.gasser.newshapes.shapes.SText;
|
import ovh.gasser.newshapes.shapes.SText;
|
||||||
import ovh.gasser.newshapes.shapes.STriangle;
|
import ovh.gasser.newshapes.shapes.STriangle;
|
||||||
@@ -16,4 +17,6 @@ public interface ShapeVisitor {
|
|||||||
void visitTriangle(STriangle sTriangle);
|
void visitTriangle(STriangle sTriangle);
|
||||||
|
|
||||||
void visitText(SText sText);
|
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);
|
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) {
|
private Color resolveTextColor(ColorAttributes attrs) {
|
||||||
if (attrs == null) {
|
if (attrs == null) {
|
||||||
return Color.BLACK;
|
return Color.BLACK;
|
||||||
|
|||||||
@@ -19,11 +19,17 @@ public class MenuAddListener implements ActionListener {
|
|||||||
private final String shape;
|
private final String shape;
|
||||||
private final ShapesView view;
|
private final ShapesView view;
|
||||||
private final SCollection model;
|
private final SCollection model;
|
||||||
|
private final Runnable onModelChanged;
|
||||||
|
|
||||||
public MenuAddListener(String shape, SCollection model, ShapesView view) {
|
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.shape = shape;
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.view = view;
|
this.view = view;
|
||||||
|
this.onModelChanged = onModelChanged;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
@@ -41,6 +47,7 @@ public class MenuAddListener implements ActionListener {
|
|||||||
s.addAttributes(new SelectionAttributes());
|
s.addAttributes(new SelectionAttributes());
|
||||||
s.addAttributes(new ColorAttributes(true, false, randomColor(), Color.BLACK));
|
s.addAttributes(new ColorAttributes(true, false, randomColor(), Color.BLACK));
|
||||||
model.add(s);
|
model.add(s);
|
||||||
|
onModelChanged.run();
|
||||||
view.repaint();
|
view.repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ package ovh.gasser.newshapes.ui.visitors;
|
|||||||
|
|
||||||
import ovh.gasser.newshapes.ShapeVisitor;
|
import ovh.gasser.newshapes.ShapeVisitor;
|
||||||
import ovh.gasser.newshapes.attributes.ColorAttributes;
|
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 ovh.gasser.newshapes.shapes.Shape;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@@ -105,6 +110,36 @@ public class HTMLDraftman implements ShapeVisitor {
|
|||||||
cssOutput.println("}");
|
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) {
|
private String attributesToCss(Shape shape) {
|
||||||
ColorAttributes attrs = (ColorAttributes) shape.getAttributes(ColorAttributes.ID);
|
ColorAttributes attrs = (ColorAttributes) shape.getAttributes(ColorAttributes.ID);
|
||||||
String strokedColor = "#ffffff";
|
String strokedColor = "#ffffff";
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import ovh.gasser.newshapes.ShapeVisitor;
|
|||||||
import ovh.gasser.newshapes.attributes.ColorAttributes;
|
import ovh.gasser.newshapes.attributes.ColorAttributes;
|
||||||
import ovh.gasser.newshapes.shapes.SCircle;
|
import ovh.gasser.newshapes.shapes.SCircle;
|
||||||
import ovh.gasser.newshapes.shapes.SCollection;
|
import ovh.gasser.newshapes.shapes.SCollection;
|
||||||
|
import ovh.gasser.newshapes.shapes.SPolygon;
|
||||||
import ovh.gasser.newshapes.shapes.SRectangle;
|
import ovh.gasser.newshapes.shapes.SRectangle;
|
||||||
import ovh.gasser.newshapes.shapes.SText;
|
import ovh.gasser.newshapes.shapes.SText;
|
||||||
import ovh.gasser.newshapes.shapes.STriangle;
|
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) {
|
public void generateSVG(SCollection model) {
|
||||||
output.println(String.format(SVG_PRELUDE, App.WIN_SIZE.width, App.WIN_SIZE.height));
|
output.println(String.format(SVG_PRELUDE, App.WIN_SIZE.width, App.WIN_SIZE.height));
|
||||||
visitCollection(model);
|
visitCollection(model);
|
||||||
|
|||||||
Reference in New Issue
Block a user