From 61307e07f0757ae93d9d3e5a6267a952003b3310 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 22 Oct 2024 21:42:46 +0100 Subject: [PATCH 01/15] Add deflate as a supported REST response encoding Some HTTP servers don't parse Accept-Encoding headers as they should and just assume that deflate is commonly available. okhttp3 has built-in support for transparently adding gzip as an accepted encoding, and decoding the response body, which is how gzip previously Just Worked. This built-in encoding handler is conditional on the user not passing in any Accept-Encoding headers, which is assumed to indicate that the caller intends to somehow handle the encoded response body themself. We can implement our own transparent decoding support using the same mechanism that okhttp3 does internally and add an Interceptor callback that adds headers before the request and returns a new response with the encoding headers removed and the body wrapped in decoding readers. This is strictly more correct than the okhttp3 built-in decoder because Content-Encoding can be a sequence of encodings applied in order that have to be decoded in reverse order. --- .../client/functions/RESTfulFunctions.java | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java b/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java index 36ba87b3b1..10973833cf 100644 --- a/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/RESTfulFunctions.java @@ -21,9 +21,11 @@ import java.math.BigDecimal; import java.net.URI; import java.net.URISyntaxException; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.zip.Inflater; import net.rptools.maptool.client.AppPreferences; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.language.I18N; @@ -33,11 +35,17 @@ import net.rptools.parser.VariableResolver; import net.rptools.parser.function.AbstractFunction; import okhttp3.Headers; +import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.BufferedSource; +import okio.GzipSource; +import okio.InflaterSource; +import okio.Okio; /** * RESTful based functions REST.get, REST.post, REST.put, REST.patch, REST.delete @@ -67,7 +75,7 @@ public static RESTfulFunctions getInstance() { return instance; } - private final OkHttpClient client = new OkHttpClient(); + private final OkHttpClient client = buildClient(); private final Gson gson = new Gson(); @Override @@ -236,6 +244,72 @@ private Object executeClientCall(String functionName, Request request, boolean f } } + private OkHttpClient buildClient() { + return new OkHttpClient.Builder() + .addInterceptor( + (Interceptor.Chain chain) -> { + var oldRequest = chain.request(); + + // If the macro has passed its own Accept-Encoding + // it's indicating it expects to somehow handle it itself. + if (oldRequest.header("Accept-Encoding") != null) { + return chain.proceed(oldRequest); + } + + // Augment request saying we accept multiple content encodings + var newHeaders = + oldRequest + .headers() + .newBuilder() + .add("Accept-Encoding", "deflate") + .add("Accept-Encoding", "gzip") + .build(); + + var newRequest = oldRequest.newBuilder().headers(newHeaders).build(); + + var oldResponse = chain.proceed(newRequest); + + // Replace the response's request with the original one + var responseBuilder = oldResponse.newBuilder().request(oldRequest); + + // We might not have a body to decompress + var body = oldResponse.body(); + if (body != null) { + BufferedSource source = body.source(); + // The body may have been wrapped in an arbitrary encoding sequence + // and the server returns them in the order it encoded them + // so we wrap them with decoders in reverse order. + var encodings = oldResponse.headers().values("Content-Encoding"); + Collections.reverse(encodings); + for (var encoding : encodings) { + if ("deflate".equalsIgnoreCase(encoding)) { + var inflater = new Inflater(true); + source = Okio.buffer(new InflaterSource(source, inflater)); + } else if ("gzip".equalsIgnoreCase(encoding)) { + source = Okio.buffer(new GzipSource(source)); + } + } + + // Strip encoding and length headers as we've already handled them + var strippedHeaders = + oldResponse + .headers() + .newBuilder() + .removeAll("Content-Encoding") + .removeAll("Content-Length") + .build(); + responseBuilder.headers(strippedHeaders); + var contentType = MediaType.parse(oldResponse.header("Content-Type")); + // Construct a new body with an inferred Content-Length + var newBody = ResponseBody.create(contentType, -1L, source); + responseBuilder.body(newBody); + } + + return responseBuilder.build(); + }) + .build(); + } + private Headers buildHeaders(Map> headerMap) { Headers.Builder headerBuilder = new Headers.Builder(); From 1e6df323c91ee469a3abe8a847b9eb526d84f9c9 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 15:33:35 -0700 Subject: [PATCH 02/15] Fix Oval.toDto() so the layer is set --- src/main/java/net/rptools/maptool/model/drawing/Oval.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/net/rptools/maptool/model/drawing/Oval.java b/src/main/java/net/rptools/maptool/model/drawing/Oval.java index 9123ed0c9f..4b16a8425f 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/Oval.java +++ b/src/main/java/net/rptools/maptool/model/drawing/Oval.java @@ -82,6 +82,7 @@ public DrawableDto toDto() { var dto = OvalDrawableDto.newBuilder() .setId(getId().toString()) + .setLayer(getLayer().name()) .setStartPoint(Mapper.map(getStartPoint())) .setEndPoint(Mapper.map(getEndPoint())); From b08cb7707841478fceac0f890cd14a7cdfa15e7b Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Fri, 18 Oct 2024 02:18:27 -0700 Subject: [PATCH 03/15] Repaint ZoneRenderer when overlays are added or removed --- .../rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java index 0bf71db39a..2e2690e862 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java @@ -654,10 +654,12 @@ public Zone getZone() { public void addOverlay(ZoneOverlay overlay) { overlayList.add(overlay); + repaintDebouncer.dispatch(); } public void removeOverlay(ZoneOverlay overlay) { overlayList.remove(overlay); + repaintDebouncer.dispatch(); } public void moveViewBy(int dx, int dy) { From 77e4bb9a980cd1500ddc2c742b72f3fa07414212 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 14:29:47 -0700 Subject: [PATCH 04/15] Add support for instance-based tools Existing tools are all class-based, i.e., are identified by a class, and state is tracked in terms of classes. Now it is possible to define tools by passing instances. The only difference is that instance-based tools cannot be looked up by their class, but that functionality is only used for a select few transitions. --- .../rptools/maptool/client/tool/Toolbox.java | 113 +++++++++++------- .../maptool/client/ui/ToolbarPanel.java | 23 ++-- 2 files changed, 84 insertions(+), 52 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/tool/Toolbox.java b/src/main/java/net/rptools/maptool/client/tool/Toolbox.java index 9ef65a73b8..d2d79f2eb1 100644 --- a/src/main/java/net/rptools/maptool/client/tool/Toolbox.java +++ b/src/main/java/net/rptools/maptool/client/tool/Toolbox.java @@ -55,12 +55,9 @@ public Tool createTool(Class toolClass) { tool = constructor.newInstance(); // tool = constructor.newInstance((Object) null); - if (tool.hasGroup()) { - buttonGroup.add(tool); - } - + addTool(tool); toolMap.put(toolClass, tool); - tool.setToolbox(this); + return tool; } catch (InstantiationException e) { MapTool.showError(I18N.getText("msg.error.toolCannotInstantiate", toolClass.getName()), e); @@ -76,28 +73,35 @@ public Tool createTool(Class toolClass) { return null; } + /** + * Add {@code tool} to the toolbox. + * + *

This tool will not be registered by its class, so methods like {@link #getTool(Class)} will + * not be able to find it. + * + * @param tool The tool to add. + */ + public void addTool(Tool tool) { + if (tool.hasGroup()) { + buttonGroup.add(tool); + } + tool.setToolbox(this); + } + public void setTargetRenderer(final ZoneRenderer renderer) { // Need to be synchronous with the timing of the invokes within this method EventQueue.invokeLater( () -> { - final Tool oldTool = currentTool; - // Disconnect the current tool from the current renderer - setSelectedTool((Tool) null); - - // Update the renderer - EventQueue.invokeLater(() -> currentRenderer = renderer); - // Attach the old tool to the new renderer - setSelectedTool(oldTool); + detach(); + currentRenderer = renderer; + attach(); }); } public void setSelectedTool(Class toolClass) { Tool tool = toolMap.get(toolClass); - if (tool != null && tool.isAvailable()) { - tool.setSelected(true); - setSelectedTool(tool); - } + setSelectedTool(tool); } public void setSelectedTool(final Tool tool) { @@ -106,35 +110,54 @@ public void setSelectedTool(final Tool tool) { if (tool == currentTool) { return; } - if (currentTool != null && currentRenderer != null) { - currentTool.removeListeners(currentRenderer); - // currentTool.addGridBasedKeys(currentRenderer, false); - currentTool.detachFrom(currentRenderer); - - if (currentTool instanceof ZoneOverlay) { - currentRenderer.removeOverlay((ZoneOverlay) currentTool); - } - } - // Update - currentTool = tool; - - if (currentTool != null) { - if (currentRenderer != null) { - // We have a renderer at this point so we can figure out the grid type and add its - // keystrokes - // to the PointerTool. - // currentTool.addGridBasedKeys(currentRenderer, true); - currentTool.addListeners(currentRenderer); - currentTool.attachTo(currentRenderer); - - if (currentTool instanceof ZoneOverlay) { - currentRenderer.addOverlay((ZoneOverlay) currentTool); - } - } - if (MapTool.getFrame() != null) { - MapTool.getFrame().setStatusMessage(I18N.getText(currentTool.getInstructions())); - } + + detach(); + var accepted = makeCurrent(tool); + if (accepted) { + attach(); } }); } + + private void attach() { + if (currentTool != null) { + if (currentRenderer != null) { + // We have a renderer at this point so we can figure out the grid type and add its + // keystrokes to the PointerTool. + // currentTool.addGridBasedKeys(currentRenderer, true); + currentTool.addListeners(currentRenderer); + currentTool.attachTo(currentRenderer); + + if (currentTool instanceof ZoneOverlay) { + currentRenderer.addOverlay((ZoneOverlay) currentTool); + } + } + } + } + + private void detach() { + if (currentTool != null && currentRenderer != null) { + currentTool.removeListeners(currentRenderer); + // currentTool.addGridBasedKeys(currentRenderer, false); + currentTool.detachFrom(currentRenderer); + + if (currentTool instanceof ZoneOverlay) { + currentRenderer.removeOverlay((ZoneOverlay) currentTool); + } + } + } + + private boolean makeCurrent(Tool tool) { + if (tool == null || !tool.isAvailable()) { + return false; + } + + currentTool = tool; + tool.setSelected(true); + if (MapTool.getFrame() != null) { + MapTool.getFrame().setStatusMessage(I18N.getText(currentTool.getInstructions())); + } + + return true; + } } diff --git a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java index 4916c28680..418ddb28ec 100644 --- a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java @@ -469,9 +469,8 @@ public void setTokenSelectionGroupEnabled(boolean enabled) { } private class OptionPanel extends JToolBar { - - private Class firstTool; - private Class currentTool; + private Tool firstTool; + private Tool currentTool; public OptionPanel() { setFloatable(false); @@ -481,18 +480,28 @@ public OptionPanel() { } public Tool add(Class toolClass) { + final Tool tool = toolbox.createTool(toolClass); + setupTool(tool); + return tool; + } + + public Tool addTool(Tool tool) { + toolbox.addTool(tool); + setupTool(tool); + return tool; + } + + private void setupTool(Tool tool) { if (firstTool == null) { - firstTool = toolClass; + firstTool = tool; } - final Tool tool = toolbox.createTool(toolClass); tool.addActionListener( e -> { if (tool.isSelected()) { - currentTool = tool.getClass(); + currentTool = tool; } }); add(tool); - return tool; } protected void activate() { From 2fbfb589607a2178a9050ec272034bda66055349 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Sun, 20 Oct 2024 10:26:05 -0700 Subject: [PATCH 05/15] Add proper generic support to Toolbox so tools can be looked up in type-safe way --- .../net/rptools/maptool/client/tool/PointerTool.java | 2 +- .../net/rptools/maptool/client/tool/StampTool.java | 2 +- .../net/rptools/maptool/client/tool/Toolbox.java | 12 ++++++------ .../maptool/client/ui/AbstractTokenPopupMenu.java | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/tool/PointerTool.java b/src/main/java/net/rptools/maptool/client/tool/PointerTool.java index 67724666d5..dab3c32092 100644 --- a/src/main/java/net/rptools/maptool/client/tool/PointerTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/PointerTool.java @@ -879,7 +879,7 @@ public void actionPerformed(ActionEvent e) { } Toolbox toolbox = MapTool.getFrame().getToolbox(); - FacingTool tool = (FacingTool) toolbox.getTool(FacingTool.class); + FacingTool tool = toolbox.getTool(FacingTool.class); tool.init( renderer.getZone().getToken(renderer.getSelectedTokenSet().iterator().next()), renderer.getSelectedTokenSet()); diff --git a/src/main/java/net/rptools/maptool/client/tool/StampTool.java b/src/main/java/net/rptools/maptool/client/tool/StampTool.java index 03da47ed7b..74cc10bf25 100644 --- a/src/main/java/net/rptools/maptool/client/tool/StampTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/StampTool.java @@ -648,7 +648,7 @@ public void actionPerformed(ActionEvent e) { return; } Toolbox toolbox = MapTool.getFrame().getToolbox(); - FacingTool tool = (FacingTool) toolbox.getTool(FacingTool.class); + FacingTool tool = toolbox.getTool(FacingTool.class); tool.init( renderer.getZone().getToken(renderer.getSelectedTokenSet().iterator().next()), renderer.getSelectedTokenSet()); diff --git a/src/main/java/net/rptools/maptool/client/tool/Toolbox.java b/src/main/java/net/rptools/maptool/client/tool/Toolbox.java index d2d79f2eb1..9990fd65a9 100644 --- a/src/main/java/net/rptools/maptool/client/tool/Toolbox.java +++ b/src/main/java/net/rptools/maptool/client/tool/Toolbox.java @@ -44,16 +44,16 @@ public Tool getSelectedTool() { return currentTool; } - public Tool getTool(Class toolClass) { - return toolMap.get(toolClass); + @SuppressWarnings("unchecked") + public T getTool(Class toolClass) { + return (T) toolMap.get(toolClass); } - public Tool createTool(Class toolClass) { - Tool tool; + public T createTool(Class toolClass) { + T tool; try { - Constructor constructor = toolClass.getDeclaredConstructor(); + Constructor constructor = toolClass.getDeclaredConstructor(); tool = constructor.newInstance(); - // tool = constructor.newInstance((Object) null); addTool(tool); toolMap.put(toolClass, tool); diff --git a/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java b/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java index a052edcb30..c2036d646f 100644 --- a/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java +++ b/src/main/java/net/rptools/maptool/client/ui/AbstractTokenPopupMenu.java @@ -677,7 +677,7 @@ public SetFacingAction() { public void actionPerformed(ActionEvent e) { Toolbox toolbox = MapTool.getFrame().getToolbox(); - FacingTool tool = (FacingTool) toolbox.getTool(FacingTool.class); + FacingTool tool = toolbox.getTool(FacingTool.class); tool.init(tokenUnderMouse, renderer.getOwnedTokens(selectedTokenSet)); toolbox.setSelectedTool(FacingTool.class); From 3bbba685e05f77b4f1d217f9a79c90fd3e904fc6 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 14:34:53 -0700 Subject: [PATCH 06/15] Increase number of shapes supported by protobuf `Path2D` is now transferable in addition to to `Area`, with the latter now being implemented on top fo the former. All `Ellipse2D` are now transferable, whereas previously only `Ellipse2D.Float` was allowed. We always use double precision for ellipses. --- .../maptool/client/tool/drawing/OvalTool.java | 4 +- .../net/rptools/maptool/server/Mapper.java | 121 ++++++++++-------- src/main/proto/drawing_dto.proto | 15 ++- 3 files changed, 82 insertions(+), 58 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java index e99f99a48e..6c32668de8 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java @@ -66,7 +66,7 @@ public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { paintTransformed( g, renderer, - new ShapeDrawable(new Ellipse2D.Float(oval.x, oval.y, oval.width, oval.height)), + new ShapeDrawable(new Ellipse2D.Double(oval.x, oval.y, oval.width, oval.height)), pen); ToolHelper.drawBoxedMeasurement( @@ -101,7 +101,7 @@ public void mousePressed(MouseEvent e) { completeDrawable( getPen(), - new ShapeDrawable(new Ellipse2D.Float(oval.x, oval.y, oval.width, oval.height), true)); + new ShapeDrawable(new Ellipse2D.Double(oval.x, oval.y, oval.width, oval.height), true)); oval = null; } diff --git a/src/main/java/net/rptools/maptool/server/Mapper.java b/src/main/java/net/rptools/maptool/server/Mapper.java index 2df3670cd2..b95e1aed90 100644 --- a/src/main/java/net/rptools/maptool/server/Mapper.java +++ b/src/main/java/net/rptools/maptool/server/Mapper.java @@ -33,11 +33,55 @@ public class Mapper { /** Instance used for log messages. */ private static final Logger log = LogManager.getLogger(Mapper.class); - public static Area map(AreaDto areaDto) { - final var segments = areaDto.getSegmentsList(); - final var path = new Path2D.Double(areaDto.getWindingValue(), segments.size()); + private static PathShapeDto map(PathIterator it) { + var builder = PathShapeDto.newBuilder(); + + double[] coords = new double[6]; + builder.setWinding(PathShapeDto.WindingRule.forNumber(it.getWindingRule())); - for (final SegmentDto currentSegment : areaDto.getSegmentsList()) { + for (; !it.isDone(); it.next()) { + var segmentBuilder = SegmentDto.newBuilder(); + switch (it.currentSegment(coords)) { + case PathIterator.SEG_MOVETO -> { + var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); + var moveTo = MoveToSegment.newBuilder().setPoint0(point0Builder); + segmentBuilder.setMoveTo(moveTo); + } + case PathIterator.SEG_LINETO -> { + var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); + var lineTo = LineToSegment.newBuilder().setPoint0(point0Builder); + segmentBuilder.setLineTo(lineTo); + } + case PathIterator.SEG_QUADTO -> { + var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); + var point1Builder = DoublePointDto.newBuilder().setX(coords[2]).setY(coords[3]); + var quadTo = QuadToSegment.newBuilder().setPoint0(point0Builder).setPoint1(point1Builder); + segmentBuilder.setQuadTo(quadTo); + } + case PathIterator.SEG_CUBICTO -> { + var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); + var point1Builder = DoublePointDto.newBuilder().setX(coords[2]).setY(coords[3]); + var point2Builder = DoublePointDto.newBuilder().setX(coords[4]).setY(coords[5]); + var cubicTo = + CubicToSegment.newBuilder() + .setPoint0(point0Builder) + .setPoint1(point1Builder) + .setPoint2(point2Builder); + segmentBuilder.setCubicTo(cubicTo); + } + case PathIterator.SEG_CLOSE -> segmentBuilder.setClose(CloseSegment.newBuilder()); + } + builder.addSegments(segmentBuilder); + } + + return builder.build(); + } + + public static Path2D map(PathShapeDto pathShapeDto) { + final var segments = pathShapeDto.getSegmentsList(); + final var path = new Path2D.Double(pathShapeDto.getWindingValue(), segments.size()); + + for (final SegmentDto currentSegment : pathShapeDto.getSegmentsList()) { switch (currentSegment.getSegmentTypeCase()) { case MOVE_TO -> { final var segment = currentSegment.getMoveTo(); @@ -74,53 +118,23 @@ public static Area map(AreaDto areaDto) { } } - return new Area(path); + return path; + } + + public static PathShapeDto map(Path2D path) { + return map(path.getPathIterator(null)); + } + + public static Area map(AreaDto areaDto) { + return new Area(map(areaDto.getPath())); } public static AreaDto map(Area area) { if (area == null) return null; var builder = AreaDto.newBuilder(); - - var it = area.getPathIterator(null); - double[] coords = new double[6]; - builder.setWinding(AreaDto.WindingRule.forNumber(it.getWindingRule())); - - for (; !it.isDone(); it.next()) { - var segmentBuilder = SegmentDto.newBuilder(); - switch (it.currentSegment(coords)) { - case PathIterator.SEG_MOVETO -> { - var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); - var moveTo = MoveToSegment.newBuilder().setPoint0(point0Builder); - segmentBuilder.setMoveTo(moveTo); - } - case PathIterator.SEG_LINETO -> { - var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); - var lineTo = LineToSegment.newBuilder().setPoint0(point0Builder); - segmentBuilder.setLineTo(lineTo); - } - case PathIterator.SEG_QUADTO -> { - var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); - var point1Builder = DoublePointDto.newBuilder().setX(coords[2]).setY(coords[3]); - var quadTo = QuadToSegment.newBuilder().setPoint0(point0Builder).setPoint1(point1Builder); - segmentBuilder.setQuadTo(quadTo); - } - case PathIterator.SEG_CUBICTO -> { - var point0Builder = DoublePointDto.newBuilder().setX(coords[0]).setY(coords[1]); - var point1Builder = DoublePointDto.newBuilder().setX(coords[2]).setY(coords[3]); - var point2Builder = DoublePointDto.newBuilder().setX(coords[4]).setY(coords[5]); - var cubicTo = - CubicToSegment.newBuilder() - .setPoint0(point0Builder) - .setPoint1(point1Builder) - .setPoint2(point2Builder); - segmentBuilder.setCubicTo(cubicTo); - } - case PathIterator.SEG_CLOSE -> segmentBuilder.setClose(CloseSegment.newBuilder()); - } - builder.addSegments(segmentBuilder); - } - + var pathDto = map(area.getPathIterator(null)); + builder.setPath(pathDto); return builder.build(); } @@ -165,7 +179,10 @@ public static Shape map(ShapeDto shapeDto) { } case ELLIPSE -> { var dto = shapeDto.getEllipse(); - return new Ellipse2D.Float(dto.getX(), dto.getY(), dto.getWidth(), dto.getHeight()); + return new Ellipse2D.Double(dto.getX(), dto.getY(), dto.getWidth(), dto.getHeight()); + } + case PATH -> { + return map(shapeDto.getPath()); } default -> { log.warn("unknown ShapeDto type: " + shapeDto.getShapeTypeCase()); @@ -195,14 +212,16 @@ public static ShapeDto map(Shape shape) { dto.addPoints(pointDto); } return shapeDto.setPolygon(dto).build(); - } else if (shape instanceof Ellipse2D.Float ellipse) { + } else if (shape instanceof Ellipse2D ellipse) { var dto = EllipseDto.newBuilder() - .setX(ellipse.x) - .setY(ellipse.y) - .setWidth(ellipse.width) - .setHeight(ellipse.height); + .setX(ellipse.getX()) + .setY(ellipse.getY()) + .setWidth(ellipse.getWidth()) + .setHeight(ellipse.getHeight()); return shapeDto.setEllipse(dto).build(); + } else if (shape instanceof Path2D path) { + return shapeDto.setPath(map(path)).build(); } else { log.warn("mapping not implemented for Shape type: " + shape.getClass()); return null; diff --git a/src/main/proto/drawing_dto.proto b/src/main/proto/drawing_dto.proto index c14353a6a5..37815323de 100644 --- a/src/main/proto/drawing_dto.proto +++ b/src/main/proto/drawing_dto.proto @@ -195,10 +195,11 @@ message ShapeDto { AreaDto area = 2; PolygonDto polygon = 3; EllipseDto ellipse = 4; + PathShapeDto path = 5; } } -message AreaDto { +message PathShapeDto { enum WindingRule { EVEN_ODD = 0; NON_ZERO = 1; @@ -207,6 +208,10 @@ message AreaDto { repeated SegmentDto segments = 2; } +message AreaDto { + PathShapeDto path = 1; +} + message SegmentDto { oneof segment_type { MoveToSegment move_to = 1; @@ -262,10 +267,10 @@ message RectangleDto { } message EllipseDto { - float x = 1; - float y = 2; - float width = 3; - float height = 4; + double x = 1; + double y = 2; + double width = 3; + double height = 4; } message DrawablePaintDto { From 9a6d544ab156a063a96c2a130be3438427a7d61c Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 14:49:17 -0700 Subject: [PATCH 07/15] Shim a new base class for drawing-like tools The existing `AbstractDrawingTool` is now in a transitional state and will not exist much longer. The new-style tools will not use it, but will inherit from `AbstractDrawingLikeTool`, which provides a bare minimum of functionality. Eventually, `AbstractDrawinTool` will be repurposed into a base class for templates, and even then may not look much like it does now. --- .../tool/drawing/AbstractDrawingLikeTool.java | 63 +++++++++++++++++++ .../tool/drawing/AbstractDrawingTool.java | 36 +---------- 2 files changed, 65 insertions(+), 34 deletions(-) create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java new file mode 100644 index 0000000000..afb6f30b69 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java @@ -0,0 +1,63 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.event.MouseEvent; +import net.rptools.maptool.client.ScreenPoint; +import net.rptools.maptool.client.swing.SwingUtil; +import net.rptools.maptool.client.tool.DefaultTool; +import net.rptools.maptool.client.ui.zone.ZoneOverlay; +import net.rptools.maptool.model.ZonePoint; + +public abstract class AbstractDrawingLikeTool extends DefaultTool implements ZoneOverlay { + private boolean isEraser; + + protected void setIsEraser(boolean eraser) { + isEraser = eraser; + } + + protected boolean isEraser() { + return isEraser; + } + + protected boolean isEraser(MouseEvent e) { + return SwingUtil.isShiftDown(e); + } + + protected boolean isSnapToGrid(MouseEvent e) { + return SwingUtil.isControlDown(e); + } + + protected boolean isSnapToCenter(MouseEvent e) { + return e.isAltDown(); + } + + protected boolean isLinearTool() { + return false; + } + + protected ZonePoint getPoint(MouseEvent e) { + ScreenPoint sp = new ScreenPoint(e.getX(), e.getY()); + ZonePoint zp = sp.convertToZoneRnd(renderer); + if (isSnapToCenter(e) && isLinearTool()) { + // Only line tools will snap to center as the Alt key for rectangle, diamond and oval + // is used for expand from center. + zp = renderer.getCellCenterAt(sp); + } else if (isSnapToGrid(e)) { + zp = renderer.getZone().getNearestVertex(zp); + } + return zp; + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java index c98aab2875..0826542b64 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java @@ -28,10 +28,8 @@ import net.rptools.maptool.client.AppStyle; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.MapToolUtil; -import net.rptools.maptool.client.ScreenPoint; import net.rptools.maptool.client.swing.SwingUtil; import net.rptools.maptool.client.swing.colorpicker.ColorPicker; -import net.rptools.maptool.client.tool.DefaultTool; import net.rptools.maptool.client.ui.zone.ZoneOverlay; import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; import net.rptools.maptool.model.Token; @@ -44,11 +42,10 @@ import net.rptools.maptool.model.drawing.ShapeDrawable; /** Tool for drawing freehand lines. */ -public abstract class AbstractDrawingTool extends DefaultTool implements ZoneOverlay { +public abstract class AbstractDrawingTool extends AbstractDrawingLikeTool implements ZoneOverlay { private static final long serialVersionUID = 9121558405484986225L; - private boolean isEraser; private boolean isSnapToGridSelected; private boolean isEraseSelected; @@ -146,14 +143,6 @@ protected void detachFrom(ZoneRenderer renderer) { super.detachFrom(renderer); } - protected void setIsEraser(boolean eraser) { - isEraser = eraser; - } - - protected boolean isEraser() { - return isEraser; - } - protected boolean isBackgroundFill(MouseEvent e) { boolean defaultValue = MapTool.getFrame().getColorPicker().isFillBackgroundSelected(); return defaultValue; @@ -177,17 +166,9 @@ protected boolean isSnapToGrid(MouseEvent e) { return defaultValue; } - protected boolean isSnapToCenter(MouseEvent e) { - boolean defaultValue = false; - if (e.isAltDown()) { - defaultValue = true; - } - return defaultValue; - } - protected Pen getPen() { Pen pen = new Pen(MapTool.getFrame().getPen()); - pen.setEraser(isEraser); + pen.setEraser(isEraser()); ColorPicker picker = MapTool.getFrame().getColorPicker(); if (picker.isFillForegroundSelected()) { @@ -205,19 +186,6 @@ protected Pen getPen() { return pen; } - protected ZonePoint getPoint(MouseEvent e) { - ScreenPoint sp = new ScreenPoint(e.getX(), e.getY()); - ZonePoint zp = sp.convertToZoneRnd(renderer); - if (isSnapToCenter(e) && this instanceof AbstractLineTool) { - // Only line tools will snap to center as the Alt key for rectangle, diamond and oval - // is used for expand from center. - zp = renderer.getCellCenterAt(sp); - } else if (isSnapToGrid(e)) { - zp = renderer.getZone().getNearestVertex(zp); - } - return zp; - } - protected Area getTokenTopology(Zone.TopologyType topologyType) { List topologyTokens = getZone().getTokensWithTopology(topologyType); From f8a656b86ea51704d3569f9a7d1b372faf586fb2 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 14:55:32 -0700 Subject: [PATCH 08/15] Add support for iso measurements using points rather than Shape This will be useful in conjunction with the new strategies so that we don't have to make assumptions about the result types. --- .../maptool/client/tool/ToolHelper.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java b/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java index 2bf739db6d..06422e4b60 100644 --- a/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java +++ b/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java @@ -20,6 +20,7 @@ import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.geom.AffineTransform; +import java.awt.geom.Line2D; import java.awt.geom.PathIterator; import java.text.NumberFormat; import javax.swing.AbstractAction; @@ -49,6 +50,34 @@ public void actionPerformed(ActionEvent e) { } }; + public static void drawIsoRectangleMeasurement( + ZoneRenderer renderer, Graphics2D g, ScreenPoint north, ScreenPoint west, ScreenPoint east) { + if (g != null) { + g.setColor(Color.white); + g.setStroke(new BasicStroke(3)); + g.draw(new Line2D.Double(north.x, north.y - 20, north.x, north.y - 10)); + g.draw(new Line2D.Double(north.x, north.y - 15, east.x, east.y - 15)); + g.draw(new Line2D.Double(east.x, east.y - 20, east.x, east.y - 10)); + g.draw(new Line2D.Double(north.x, north.y - 15, west.x, west.y - 15)); + g.draw(new Line2D.Double(west.x, west.y - 20, west.x, west.y - 10)); + + g.setColor(Color.black); + g.setStroke(new BasicStroke(1)); + // Same points, but in thin black. + g.draw(new Line2D.Double(north.x, north.y - 20, north.x, north.y - 10)); + g.draw(new Line2D.Double(north.x, north.y - 15, east.x, east.y - 15)); + g.draw(new Line2D.Double(east.x, east.y - 20, east.x, east.y - 10)); + g.draw(new Line2D.Double(north.x, north.y - 15, west.x, west.y - 15)); + g.draw(new Line2D.Double(west.x, west.y - 20, west.x, west.y - 10)); + + String displayString = + NumberFormat.getInstance().format(isometricDistance(renderer, north, east)); + GraphicsUtil.drawBoxedString(g, displayString, (int) (north.x + 25), (int) (north.y - 25)); + displayString = NumberFormat.getInstance().format(isometricDistance(renderer, north, west)); + GraphicsUtil.drawBoxedString(g, displayString, (int) (north.x - 25), (int) (north.y - 25)); + } + } + public static void drawDiamondMeasurement(ZoneRenderer renderer, Graphics2D g, Shape diamond) { double[] north = null; double[] west = null; From d24e52ae1fbec27bb5d8f80f54d97a94010171e1 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 14:25:50 -0700 Subject: [PATCH 09/15] Implement new strategies for drawing-like tools These will form the basis of a new set of tools for drawing, FoW, and topology. Instead of a spawling class hierarchy with complex interdependencies between each type of tool, the new approach will favour these new strategies for defining the shape, while the tool will define what to do with the shape, e.g., add a drawing, expose some fog, or add some topology. By using these new strategies, there will be no more need for FoW and topology tools to inherit from any drawing tools or vice versa. --- .../client/tool/drawing/CrossStrategy.java | 42 +++++++ .../client/tool/drawing/DrawingResult.java | 20 +++ .../tool/drawing/IsoRectangleStrategy.java | 76 ++++++++++++ .../client/tool/drawing/Measurement.java | 26 ++++ .../client/tool/drawing/OvalStrategy.java | 58 +++++++++ .../client/tool/drawing/PolyLineStrategy.java | 114 ++++++++++++++++++ .../tool/drawing/RectangleStrategy.java | 36 ++++++ .../maptool/client/tool/drawing/Strategy.java | 90 ++++++++++++++ .../rptools/maptool/util/GraphicsUtil.java | 13 +- 9 files changed, 471 insertions(+), 4 deletions(-) create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/CrossStrategy.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/DrawingResult.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/IsoRectangleStrategy.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/Measurement.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/OvalStrategy.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineStrategy.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/RectangleStrategy.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/Strategy.java diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/CrossStrategy.java b/src/main/java/net/rptools/maptool/client/tool/drawing/CrossStrategy.java new file mode 100644 index 0000000000..ab1126330b --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/CrossStrategy.java @@ -0,0 +1,42 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.geom.Path2D; +import javax.annotation.Nullable; +import net.rptools.maptool.model.ZonePoint; + +public class CrossStrategy implements Strategy { + @Override + public ZonePoint startNewAtPoint(ZonePoint point) { + return point; + } + + @Override + public @Nullable DrawingResult getShape( + ZonePoint state, ZonePoint currentPoint, boolean centerOnOrigin, boolean isFilled) { + var bounds = Strategy.normalizedRectangle(state, currentPoint, false); + if (bounds.width == 0 && bounds.height == 0) { + return null; + } + + var path = new Path2D.Double(); + path.moveTo(bounds.x, bounds.y); + path.lineTo(bounds.x + bounds.width, bounds.y + bounds.height); + path.moveTo(bounds.x, bounds.y + bounds.height); + path.lineTo(bounds.x + bounds.width, bounds.y); + return new DrawingResult(path, null); + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingResult.java b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingResult.java new file mode 100644 index 0000000000..1e12044606 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingResult.java @@ -0,0 +1,20 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.Shape; +import javax.annotation.Nullable; + +public record DrawingResult(Shape shape, @Nullable Measurement measurement) {} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/IsoRectangleStrategy.java b/src/main/java/net/rptools/maptool/client/tool/drawing/IsoRectangleStrategy.java new file mode 100644 index 0000000000..fbe06c7194 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/IsoRectangleStrategy.java @@ -0,0 +1,76 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.Point; +import java.awt.Polygon; +import java.util.Arrays; +import java.util.Collections; +import javax.annotation.Nullable; +import net.rptools.maptool.model.ZonePoint; + +public class IsoRectangleStrategy implements Strategy { + @Override + public ZonePoint startNewAtPoint(ZonePoint point) { + return point; + } + + private Point toPoint(ZonePoint point) { + return new Point(point.x, point.y); + } + + @Override + public @Nullable DrawingResult getShape( + ZonePoint origin_, ZonePoint currentPoint_, boolean centerOnOrigin, boolean isFilled) { + // Inversion check is not strictly needed, but simplifies some case work below. + var invertedY = currentPoint_.y < origin_.y; + var origin = toPoint(invertedY ? currentPoint_ : origin_); + var currentPoint = toPoint(invertedY ? origin_ : currentPoint_); + + final double diffX = (currentPoint.x - origin.x) / 2.; + final double diffY = currentPoint.y - origin.y; + assert diffY >= 0 : "diffY should be forced positive by the above inversion check"; + + var p1 = new Point((int) (origin.x + diffX + diffY), (int) (origin.y + (diffY + diffX) / 2)); + var p2 = new Point((int) (origin.x + diffX - diffY), (int) (origin.y + (diffY - diffX) / 2)); + + var points = new Point[] {origin, p1, currentPoint, p2}; + // For the sake of measurements, we need to know which point is in each compass direction. + // Check for edge cases, and force order of `points` to be north, east, south, west. + if (diffY < Math.abs(diffX)) { + // Inverted over the y = x / 2 axis. First rotate either p1 or p2 into north position... + Collections.rotate(Arrays.asList(points), diffX > 0 ? 1 : -1); + // ... then swap roles of origin and currentPoint due to the inversion. + Collections.swap(Arrays.asList(points), 1, 3); + } + + var xPoints = + new int[] { + points[0].x, points[1].x, points[2].x, points[3].x, + }; + var yPoints = + new int[] { + points[0].y, points[1].y, points[2].y, points[3].y, + }; + var polygon = new Polygon(xPoints, yPoints, 4); + var bounds = polygon.getBounds(); + if (bounds.width == 0 && bounds.height == 0) { + return null; + } + + return new DrawingResult( + polygon, new Measurement.IsoRectangular(points[0], points[3], points[1])); + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/Measurement.java b/src/main/java/net/rptools/maptool/client/tool/drawing/Measurement.java new file mode 100644 index 0000000000..e5b2f573b2 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/Measurement.java @@ -0,0 +1,26 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +public sealed interface Measurement { + record Rectangular(Rectangle2D bounds) implements Measurement {} + + record LineSegment(Point2D p1, Point2D p2) implements Measurement {} + + record IsoRectangular(Point2D north, Point2D west, Point2D east) implements Measurement {} +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalStrategy.java b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalStrategy.java new file mode 100644 index 0000000000..196121a26d --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalStrategy.java @@ -0,0 +1,58 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.Shape; +import java.awt.geom.Ellipse2D; +import javax.annotation.Nullable; +import net.rptools.maptool.model.ZonePoint; +import net.rptools.maptool.util.GraphicsUtil; + +public class OvalStrategy implements Strategy { + private final int steps; + + public OvalStrategy() { + this(0); + } + + public OvalStrategy(int steps) { + this.steps = steps; + } + + @Override + public ZonePoint startNewAtPoint(ZonePoint point) { + return point; + } + + @Override + public @Nullable DrawingResult getShape( + ZonePoint state, ZonePoint currentPoint, boolean centerOnOrigin, boolean isFilled) { + var bounds = Strategy.normalizedRectangle(state, currentPoint, centerOnOrigin); + if (bounds.width == 0 && bounds.height == 0) { + return null; + } + + Shape shape; + if (steps <= 0) { + shape = new Ellipse2D.Double(bounds.x, bounds.y, bounds.width, bounds.height); + } else { + shape = + GraphicsUtil.createLineSegmentEllipsePath( + bounds.x, bounds.y, bounds.x + bounds.width, bounds.y + bounds.height, steps); + } + + return new DrawingResult(shape, new Measurement.Rectangular(bounds)); + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineStrategy.java b/src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineStrategy.java new file mode 100644 index 0000000000..53036a0b08 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineStrategy.java @@ -0,0 +1,114 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.Polygon; +import java.awt.Shape; +import java.awt.geom.Path2D; +import java.awt.geom.Point2D; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; +import net.rptools.maptool.model.ZonePoint; + +public class PolyLineStrategy implements Strategy> { + private final boolean isFreehand; + + public PolyLineStrategy(boolean isFreehand) { + this.isFreehand = isFreehand; + } + + @Override + public boolean isFreehand() { + return isFreehand; + } + + @Override + public boolean isLinear() { + return true; + } + + @Override + public List startNewAtPoint(ZonePoint point) { + var points = new ArrayList(); + points.addLast(new ZonePoint(point)); + return points; + } + + @Override + public void pushPoint(List state, ZonePoint point) { + state.addLast(new ZonePoint(point)); + } + + @Override + public @Nullable DrawingResult getShape( + List state, ZonePoint currentPoint, boolean centerOnOrigin, boolean isFilled) { + var trimmedPoints = new ArrayList<>(state); + trimmedPoints.addLast(currentPoint); + trim(trimmedPoints); + assert !trimmedPoints.isEmpty() : "The list will always have at least the origin point"; + + Shape result; + if (isFilled && trimmedPoints.size() > 2) { + var xPoints = new int[trimmedPoints.size()]; + var yPoints = new int[trimmedPoints.size()]; + for (int i = 0; i < trimmedPoints.size(); ++i) { + var point = trimmedPoints.get(i); + xPoints[i] = point.x; + yPoints[i] = point.y; + } + result = new Polygon(xPoints, yPoints, trimmedPoints.size()); + } else { + var newPath = new Path2D.Double(); + var first = trimmedPoints.getFirst(); + newPath.moveTo(first.x, first.y); + for (var point : trimmedPoints.subList(1, trimmedPoints.size())) { + newPath.lineTo(point.x, point.y); + } + result = newPath; + } + + Measurement measurement = null; + if (!isFreehand()) { + ZonePoint last = trimmedPoints.removeLast(); + ZonePoint secondLast = trimmedPoints.isEmpty() ? last : trimmedPoints.removeLast(); + + measurement = + new Measurement.LineSegment( + new Point2D.Double(secondLast.x, secondLast.y), new Point2D.Double(last.x, last.y)); + } + + return new DrawingResult(result, measurement); + } + + private void trim(List points) { + if (points.isEmpty()) { + return; + } + + ZonePoint previous = null; + var iterator = points.iterator(); + while (iterator.hasNext()) { + var current = iterator.next(); + + if (previous != null && previous.equals(current)) { + iterator.remove(); + continue; + } + + previous = current; + } + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleStrategy.java b/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleStrategy.java new file mode 100644 index 0000000000..9fc75bcfae --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleStrategy.java @@ -0,0 +1,36 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import javax.annotation.Nullable; +import net.rptools.maptool.model.ZonePoint; + +public class RectangleStrategy implements Strategy { + @Override + public ZonePoint startNewAtPoint(ZonePoint point) { + return point; + } + + @Override + public @Nullable DrawingResult getShape( + ZonePoint state, ZonePoint currentPoint, boolean centerOnOrigin, boolean isFilled) { + var bounds = Strategy.normalizedRectangle(state, currentPoint, centerOnOrigin); + if (bounds.width == 0 && bounds.height == 0) { + return null; + } + + return new DrawingResult(bounds, new Measurement.Rectangular(bounds)); + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/Strategy.java b/src/main/java/net/rptools/maptool/client/tool/drawing/Strategy.java new file mode 100644 index 0000000000..9406acc0d1 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/Strategy.java @@ -0,0 +1,90 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.Rectangle; +import javax.annotation.Nullable; +import net.rptools.maptool.model.ZonePoint; + +public interface Strategy { + /** + * Check if the tool is a freehand tool. + * + *

Freehand tools have a different flow to other tools. Other tools are click-based, freehand + * tools are dragged-based. + * + * @return {@code true} if the strategy is for a freehand tool. + */ + default boolean isFreehand() { + return false; + } + + /** + * Check if the tool is a linear tool. + * + *

Linear tools support snap-to-center, but not origin-as-center since they don't have a real + * center. + * + * @return {@code true} if the strategy is for a linear tool. + */ + default boolean isLinear() { + return false; + } + + /** + * Start a new drawing using {@code point} as the first point. + * + * @param point The origin point for the new drawing. + */ + StateT startNewAtPoint(ZonePoint point); + + /** + * For linear tools, add {@code point} to the {@code state}. + * + *

Non-linear tools ignore this. + */ + default void pushPoint(StateT state, ZonePoint point) {} + + /** + * Get the current shape of the tool. + * + * @param state The current state of the tool. + * @param currentPoint The current position of the cursor. + * @param centerOnOrigin For tools that support it, {@code true} indicates that the shape should + * be centered on the original point. + * @param isFilled For linear tools, {@code true} indicates that the shape should be closed, while + * {@code false} indicates it should remain open. + * @return The shape of the new topology, or {@code null} to indicate there is nothing to add or + * remove. + */ + @Nullable + DrawingResult getShape( + StateT state, ZonePoint currentPoint, boolean centerOnOrigin, boolean isFilled); + + static Rectangle normalizedRectangle(ZonePoint p1, ZonePoint p2, boolean p1IsCenter) { + if (p1IsCenter) { + var halfWidth = Math.abs(p2.x - p1.x); + var halfHeight = Math.abs(p2.y - p1.y); + return new Rectangle(p1.x - halfWidth, p1.y - halfHeight, 2 * halfWidth, 2 * halfHeight); + } + + // AWT doesn't like drawing rectangles with negative width or height. So normalize it first. + int minX = Math.min(p1.x, p2.x); + int maxX = Math.max(p1.x, p2.x); + int minY = Math.min(p1.y, p2.y); + int maxY = Math.max(p1.y, p2.y); + return new Rectangle(minX, minY, maxX - minX, maxY - minY); + } +} diff --git a/src/main/java/net/rptools/maptool/util/GraphicsUtil.java b/src/main/java/net/rptools/maptool/util/GraphicsUtil.java index d59fbeb891..a2fa0ee24c 100644 --- a/src/main/java/net/rptools/maptool/util/GraphicsUtil.java +++ b/src/main/java/net/rptools/maptool/util/GraphicsUtil.java @@ -29,6 +29,7 @@ import java.awt.Shape; import java.awt.geom.Area; import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collections; @@ -304,6 +305,11 @@ public static Area createLineSegmentEllipse(int x1, int y1, int x2, int y2, int public static Area createLineSegmentEllipse( double x1, double y1, double x2, double y2, int steps) { + return new Area(createLineSegmentEllipsePath(x1, y1, x2, y2, steps)); + } + + public static Path2D createLineSegmentEllipsePath( + double x1, double y1, double x2, double y2, int steps) { double x = Math.min(x1, x2); double y = Math.min(y1, y2); @@ -322,21 +328,20 @@ public static Area createLineSegmentEllipse( double a = w / 2; double b = h / 2; - boolean firstMove = true; for (double t = -Math.PI; t <= Math.PI; t += (2 * Math.PI / steps)) { + // TODO Why do we accept double inputs, but round/cast to int here? int px = (int) Math.round(x + a * Math.cos(t)); int py = (int) Math.round(y + b * Math.sin(t)); - if (firstMove) { + if (path.getCurrentPoint() == null) { path.moveTo(px, py); - firstMove = false; } else { path.lineTo(px, py); } } path.closePath(); - return new Area(path); + return path; } public static void renderSoftClipping(Graphics2D g, Shape shape, int width, double initialAlpha) { From fcffcd1e8aeebe503e6f3906f81c5e43f1651918 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 15:00:57 -0700 Subject: [PATCH 10/15] Replace existing topology tool implementations with one class The new `TopologyTool` uses the previously introduced strategies to define all topology tools. They are instance-based, using the recent supporting change to the toolbox. `TopologyTool` merely interprets the results of the strategies. The strategies are responsible for defining the shapes, but have no knowledge of what the shape will be used for. --- .../tool/drawing/CrossTopologyTool.java | 135 ----------- .../tool/drawing/DiamondTopologyTool.java | 121 --------- .../drawing/HollowDiamondTopologyTool.java | 120 --------- .../tool/drawing/HollowOvalTopologyTool.java | 143 ----------- .../drawing/HollowRectangleTopologyTool.java | 132 ---------- .../client/tool/drawing/OvalTopologyTool.java | 129 ---------- .../tool/drawing/PolyLineTopologyTool.java | 70 ------ .../tool/drawing/PolygonTopologyTool.java | 132 ---------- .../tool/drawing/RectangleTopologyTool.java | 123 ---------- .../client/tool/drawing/TopologyTool.java | 229 ++++++++++++++++++ .../maptool/client/ui/ToolbarPanel.java | 66 ++++- 11 files changed, 286 insertions(+), 1114 deletions(-) delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/CrossTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/HollowDiamondTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/HollowOvalTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/HollowRectangleTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/OvalTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTopologyTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTopologyTool.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/TopologyTool.java diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/CrossTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/CrossTopologyTool.java deleted file mode 100644 index e7a6ac75a9..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/CrossTopologyTool.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Graphics2D; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import java.awt.geom.Point2D; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Cross; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.util.GraphicsUtil; - -/** - * @author CoveredInFish - */ -public class CrossTopologyTool extends AbstractDrawingTool implements MouseMotionListener { - private static final long serialVersionUID = 3258413928311830323L; - - protected Cross cross; - - public CrossTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.crosstopology.instructions"; - } - - @Override - public String getTooltip() { - return "tool.crosstopology.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g, cross, Pen.MODE_TRANSPARENT); - } - - @Override - public void mousePressed(MouseEvent e) { - ZonePoint zp = getPoint(e); - - if (SwingUtilities.isLeftMouseButton(e)) { - if (cross == null) { - cross = new Cross(zp.x, zp.y, zp.x, zp.y); - } else { - cross.getEndPoint().x = zp.x; - cross.getEndPoint().y = zp.y; - - int x1 = Math.min(cross.getStartPoint().x, cross.getEndPoint().x); - int x2 = Math.max(cross.getStartPoint().x, cross.getEndPoint().x); - int y1 = Math.min(cross.getStartPoint().y, cross.getEndPoint().y); - int y2 = Math.max(cross.getStartPoint().y, cross.getEndPoint().y); - - // Area area = new Area(new Rectangle(x1-1, y1-1, x2 - x1 + 2, y2 - y1 + 2)); - - // Area area = new Area( new Line2D.Double(x1,y1,x2,y2)); - // area.add( new Area(new Line2D.Double(x1,y2,x2,y1))); - - Area area = - GraphicsUtil.createLine(1, new Point2D.Double(x1, y1), new Point2D.Double(x2, y2)); - area.add( - GraphicsUtil.createLine(1, new Point2D.Double(x1, y2), new Point2D.Double(x2, y1))); - MapTool.serverCommand() - .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); - - cross = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (cross == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - setIsEraser(isEraser(e)); - - ZonePoint p = getPoint(e); - if (cross != null) { - cross.getEndPoint().x = p.x; - cross.getEndPoint().y = p.y; - renderer.repaint(); - } - } - - /** Stop drawing a cross and repaint the zone. */ - @Override - public void resetTool() { - if (cross != null) { - cross = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTopologyTool.java deleted file mode 100644 index 20a46db53b..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTopologyTool.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Graphics2D; -import java.awt.Shape; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -public class DiamondTopologyTool extends AbstractDrawingTool implements MouseMotionListener { - - private static final long serialVersionUID = -1497583181619555786L; - protected Shape diamond; - protected ZonePoint originPoint; - - public DiamondTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.isorectangletopology.instructions"; - } - - @Override - public String getTooltip() { - return "tool.isorectangletopology.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g, diamond); - } - - @Override - public void mousePressed(MouseEvent e) { - if (SwingUtilities.isLeftMouseButton(e)) { - ZonePoint zp = getPoint(e); - - if (diamond == null) { - originPoint = zp; - diamond = createDiamond(originPoint, originPoint); - } else { - diamond = createDiamond(originPoint, zp); - - if (diamond.getBounds().width == 0 || diamond.getBounds().height == 0) { - diamond = null; - renderer.repaint(); - return; - } - Area area = new ShapeDrawable(diamond, false).getArea(getZone()); - MapTool.serverCommand() - .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); - diamond = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (diamond == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - setIsEraser(isEraser(e)); - - ZonePoint zp = getPoint(e); - if (diamond != null) { - diamond = createDiamond(originPoint, zp); - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - @Override - public void resetTool() { - if (diamond != null) { - diamond = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowDiamondTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/HollowDiamondTopologyTool.java deleted file mode 100644 index cdf7d9bc8e..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowDiamondTopologyTool.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Graphics2D; -import java.awt.Shape; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Pen; - -public class HollowDiamondTopologyTool extends AbstractDrawingTool implements MouseMotionListener { - private static final long serialVersionUID = 7227397975734203085L; - protected Shape diamond; - protected ZonePoint originPoint; - - public HollowDiamondTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.isorectangletopology.instructions"; - } - - @Override - public String getTooltip() { - return "tool.isorectangletopologyhollow.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g, diamond, Pen.MODE_TRANSPARENT); - } - - @Override - public void mousePressed(MouseEvent e) { - ZonePoint zp = getPoint(e); - if (SwingUtilities.isLeftMouseButton(e)) { - if (diamond == null) { - originPoint = zp; - diamond = createHollowDiamond(originPoint, originPoint, getPen()); - } else { - diamond = createHollowDiamond(originPoint, zp, getPen()); - // diamond = createDiamond(originPoint, zp); - - if (diamond.getBounds().width == 0 || diamond.getBounds().height == 0) { - diamond = null; - renderer.repaint(); - return; - } - Area area = new Area(diamond); - MapTool.serverCommand() - .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); - diamond = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (diamond == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - setIsEraser(isEraser(e)); - - ZonePoint zp = getPoint(e); - if (diamond != null) { - diamond = createDiamond(originPoint, zp); - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - @Override - public void resetTool() { - if (diamond != null) { - diamond = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowOvalTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/HollowOvalTopologyTool.java deleted file mode 100644 index 93076a42bf..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowOvalTopologyTool.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Graphics2D; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Oval; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.util.GraphicsUtil; - -public class HollowOvalTopologyTool extends AbstractDrawingTool implements MouseMotionListener { - - private static final long serialVersionUID = 3258413928311830325L; - - protected Oval oval; - private ZonePoint originPoint; - - public HollowOvalTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.ovaltopology.instructions"; - } - - @Override - public String getTooltip() { - return "tool.ovaltopologyhollow.tooltip"; - } - - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g, oval, Pen.MODE_TRANSPARENT); - } - - public void mousePressed(MouseEvent e) { - - if (SwingUtilities.isLeftMouseButton(e)) { - ZonePoint zp = getPoint(e); - - if (oval == null) { - oval = new Oval(zp.x, zp.y, zp.x, zp.y); - originPoint = zp; - } else { - oval.getEndPoint().x = zp.x; - oval.getEndPoint().y = zp.y; - - Area area = - GraphicsUtil.createLineSegmentEllipse( - oval.getStartPoint().x, - oval.getStartPoint().y, - oval.getEndPoint().x, - oval.getEndPoint().y, - 10); - - // Still use the whole area if it's an erase action - if (!isEraser(e)) { - int x1 = Math.min(oval.getStartPoint().x, oval.getEndPoint().x) + 2; - int y1 = Math.min(oval.getStartPoint().y, oval.getEndPoint().y) + 2; - - int x2 = Math.max(oval.getStartPoint().x, oval.getEndPoint().x) - 2; - int y2 = Math.max(oval.getStartPoint().y, oval.getEndPoint().y) - 2; - - Area innerArea = GraphicsUtil.createLineSegmentEllipse(x1, y1, x2, y2, 10); - area.subtract(innerArea); - } - - MapTool.serverCommand() - .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); - oval = null; - } - - setIsEraser(isEraser(e)); - } - - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - - if (oval == null) { - super.mouseDragged(e); - } - } - - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - setIsEraser(isEraser(e)); - - if (oval != null) { - ZonePoint sp = getPoint(e); - - oval.getEndPoint().x = sp.x; - oval.getEndPoint().y = sp.y; - oval.getStartPoint().x = originPoint.x - (sp.x - originPoint.x); - oval.getStartPoint().y = originPoint.y - (sp.y - originPoint.y); - - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - public void resetTool() { - if (oval != null) { - oval = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowRectangleTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/HollowRectangleTopologyTool.java deleted file mode 100644 index 4cdba8717e..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/HollowRectangleTopologyTool.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Graphics2D; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.Rectangle; - -/** - * @author drice - */ -public class HollowRectangleTopologyTool extends AbstractDrawingTool - implements MouseMotionListener { - private static final long serialVersionUID = 3258413928311830323L; - - protected Rectangle rectangle; - - public HollowRectangleTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.recttopology.instructions"; - } - - @Override - public String getTooltip() { - return "tool.recttopologyhollow.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g, rectangle, Pen.MODE_TRANSPARENT); - } - - @Override - public void mousePressed(MouseEvent e) { - ZonePoint zp = getPoint(e); - - if (SwingUtilities.isLeftMouseButton(e)) { - if (rectangle == null) { - rectangle = new Rectangle(zp.x, zp.y, zp.x, zp.y); - } else { - rectangle.getEndPoint().x = zp.x; - rectangle.getEndPoint().y = zp.y; - - int x1 = Math.min(rectangle.getStartPoint().x, rectangle.getEndPoint().x); - int x2 = Math.max(rectangle.getStartPoint().x, rectangle.getEndPoint().x); - int y1 = Math.min(rectangle.getStartPoint().y, rectangle.getEndPoint().y); - int y2 = Math.max(rectangle.getStartPoint().y, rectangle.getEndPoint().y); - - Area area = new Area(new java.awt.Rectangle(x1 - 1, y1 - 1, x2 - x1 + 2, y2 - y1 + 2)); - - // Still use the whole area if it's an erase action - if (!isEraser(e)) { - Area innerArea = - new Area(new java.awt.Rectangle(x1 + 1, y1 + 1, x2 - x1 - 2, y2 - y1 - 2)); - area.subtract(innerArea); - } - MapTool.serverCommand() - .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); - rectangle = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (rectangle == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - setIsEraser(isEraser(e)); - - ZonePoint p = getPoint(e); - if (rectangle != null) { - rectangle.getEndPoint().x = p.x; - rectangle.getEndPoint().y = p.y; - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - @Override - public void resetTool() { - if (rectangle != null) { - rectangle = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTopologyTool.java deleted file mode 100644 index 50293f702f..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTopologyTool.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Graphics2D; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Oval; -import net.rptools.maptool.util.GraphicsUtil; - -public class OvalTopologyTool extends AbstractDrawingTool implements MouseMotionListener { - - private static final long serialVersionUID = 3258413928311830321L; - - protected Oval oval; - private ZonePoint originPoint; - - public OvalTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.ovaltopology.instructions"; - } - - @Override - public String getTooltip() { - return "tool.ovaltopology.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g, oval); - } - - @Override - public void mousePressed(MouseEvent e) { - if (SwingUtilities.isLeftMouseButton(e)) { - ZonePoint zp = getPoint(e); - - if (oval == null) { - oval = new Oval(zp.x, zp.y, zp.x, zp.y); - originPoint = zp; - } else { - oval.getEndPoint().x = zp.x; - oval.getEndPoint().y = zp.y; - - Area area = - GraphicsUtil.createLineSegmentEllipse( - oval.getStartPoint().x, - oval.getStartPoint().y, - oval.getEndPoint().x, - oval.getEndPoint().y, - 10); - - MapTool.serverCommand() - .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); - oval = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (oval == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - setIsEraser(isEraser(e)); - if (oval != null) { - ZonePoint sp = getPoint(e); - - oval.getEndPoint().x = sp.x; - oval.getEndPoint().y = sp.y; - oval.getStartPoint().x = originPoint.x - (sp.x - originPoint.x); - oval.getStartPoint().y = originPoint.y - (sp.y - originPoint.y); - - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - @Override - public void resetTool() { - if (oval != null) { - oval = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineTopologyTool.java deleted file mode 100644 index 16dda8be06..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/PolyLineTopologyTool.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Point; -import java.awt.Polygon; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import net.rptools.maptool.client.AppStyle; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.model.drawing.DrawableColorPaint; -import net.rptools.maptool.model.drawing.LineSegment; -import net.rptools.maptool.model.drawing.Pen; - -/** Tool for drawing freehand lines. */ -public class PolyLineTopologyTool extends PolygonTopologyTool implements MouseMotionListener { - private static final long serialVersionUID = 3258132466219627316L; - - public PolyLineTopologyTool() {} - - @Override - public String getTooltip() { - return "tool.polylinetopo.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.poly.instructions"; - } - - protected boolean isBackgroundFill(MouseEvent e) { - return false; - } - - protected Pen getPen() { - - Pen pen = new Pen(MapTool.getFrame().getPen()); - pen.setEraser(isEraser()); - pen.setForegroundMode(Pen.MODE_SOLID); - pen.setBackgroundMode(Pen.MODE_TRANSPARENT); - pen.setThickness(2.0f); - pen.setOpacity(AppStyle.topologyRemoveColor.getAlpha() / 255.0f); - pen.setPaint( - new DrawableColorPaint( - isEraser() ? AppStyle.topologyRemoveColor : AppStyle.topologyAddColor)); - - return pen; - } - - protected Polygon getPolygon(LineSegment line) { - Polygon polygon = new Polygon(); - for (Point point : line.getPoints()) { - polygon.addPoint(point.x, point.y); - } - - return polygon; - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTopologyTool.java deleted file mode 100644 index c4eb1e4f45..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTopologyTool.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.BasicStroke; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Polygon; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import java.awt.geom.Path2D; -import net.rptools.maptool.client.AppStyle; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.DrawableColorPaint; -import net.rptools.maptool.model.drawing.LineSegment; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -/** Tool for drawing freehand lines. */ -public class PolygonTopologyTool extends LineTool implements MouseMotionListener { - - private static final long serialVersionUID = 3258132466219627316L; - - public PolygonTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - protected boolean drawMeasurement() { - return false; - } - - @Override - public String getTooltip() { - return "tool.polytopo.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.poly.instructions"; - } - - @Override - protected boolean isBackgroundFill(MouseEvent e) { - return true; - } - - @Override - protected void completeDrawable(Pen pen, Drawable drawable) { - Area area = new Area(); - - if (drawable instanceof LineSegment) { - LineSegment line = (LineSegment) drawable; - BasicStroke stroke = - new BasicStroke(pen.getThickness(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); - - Path2D path = new Path2D.Double(); - Point lastPoint = null; - - for (Point point : line.getPoints()) { - if (path.getCurrentPoint() == null) { - path.moveTo(point.x, point.y); - } else if (!point.equals(lastPoint)) { - path.lineTo(point.x, point.y); - lastPoint = point; - } - } - - area.add(new Area(stroke.createStrokedShape(path))); - } else { - area = new Area(((ShapeDrawable) drawable).getShape()); - } - MapTool.serverCommand() - .updateTopology(getZone(), area, pen.isEraser(), getZone().getTopologyTypes()); - } - - @Override - protected Pen getPen() { - Pen pen = new Pen(MapTool.getFrame().getPen()); - pen.setEraser(isEraser()); - pen.setForegroundMode(Pen.MODE_TRANSPARENT); - pen.setBackgroundMode(Pen.MODE_SOLID); - pen.setThickness(1.0f); - pen.setOpacity(AppStyle.topologyRemoveColor.getAlpha() / 255.0f); - pen.setPaint( - new DrawableColorPaint( - isEraser() ? AppStyle.topologyRemoveColor : AppStyle.topologyAddColor)); - return pen; - } - - @Override - protected Polygon getPolygon(LineSegment line) { - Polygon polygon = new Polygon(); - for (Point point : line.getPoints()) { - polygon.addPoint(point.x, point.y); - } - return polygon; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g); - super.paintOverlay(renderer, g); - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTopologyTool.java deleted file mode 100644 index cb9a045595..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTopologyTool.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Graphics2D; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Rectangle; - -/** - * @author drice - */ -public class RectangleTopologyTool extends AbstractDrawingTool implements MouseMotionListener { - private static final long serialVersionUID = 3258413928311830323L; - - protected Rectangle rectangle; - - public RectangleTopologyTool() {} - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.recttopology.instructions"; - } - - @Override - public String getTooltip() { - return "tool.recttopology.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - paintTopologyOverlay(g, rectangle); - } - - @Override - public void mousePressed(MouseEvent e) { - if (SwingUtilities.isLeftMouseButton(e)) { - ZonePoint sp = getPoint(e); - - if (rectangle == null) { - rectangle = new Rectangle(sp.x, sp.y, sp.x, sp.y); - } else { - rectangle.getEndPoint().x = sp.x; - rectangle.getEndPoint().y = sp.y; - - int x1 = Math.min(rectangle.getStartPoint().x, rectangle.getEndPoint().x); - int x2 = Math.max(rectangle.getStartPoint().x, rectangle.getEndPoint().x); - int y1 = Math.min(rectangle.getStartPoint().y, rectangle.getEndPoint().y); - int y2 = Math.max(rectangle.getStartPoint().y, rectangle.getEndPoint().y); - - Area area = new Area(new java.awt.Rectangle(x1, y1, x2 - x1, y2 - y1)); - MapTool.serverCommand() - .updateTopology(getZone(), area, isEraser(e), getZone().getTopologyTypes()); - rectangle = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (rectangle == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - setIsEraser(isEraser(e)); - - ZonePoint p = getPoint(e); - if (rectangle != null) { - rectangle.getEndPoint().x = p.x; - rectangle.getEndPoint().y = p.y; - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - @Override - public void resetTool() { - if (rectangle != null) { - rectangle = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/TopologyTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/TopologyTool.java new file mode 100644 index 0000000000..669f2d7a04 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/TopologyTool.java @@ -0,0 +1,229 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.event.MouseEvent; +import java.awt.geom.Area; +import java.util.List; +import javax.annotation.Nullable; +import javax.swing.SwingUtilities; +import net.rptools.maptool.client.AppStyle; +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; +import net.rptools.maptool.model.Token; +import net.rptools.maptool.model.Zone; +import net.rptools.maptool.model.ZonePoint; + +public final class TopologyTool extends AbstractDrawingLikeTool { + private final String instructionKey; + private final String tooltipKey; + private final boolean isFilled; + private final Strategy strategy; + + /** The current state of the tool. If {@code null}, nothing is being drawn right now. */ + private @Nullable StateT state; + + private ZonePoint currentPoint = new ZonePoint(0, 0); + // Topology never supports center on origin right now, but it should in the future. + private boolean centerOnOrigin; + + public TopologyTool( + String instructionKey, String tooltipKey, boolean isFilled, Strategy strategy) { + this.instructionKey = instructionKey; + this.tooltipKey = tooltipKey; + this.isFilled = isFilled; + this.strategy = strategy; + // Consistency with topology tools before refactoring. Can be updated as part of #5002. + this.centerOnOrigin = this.strategy instanceof OvalStrategy; + } + + @Override + public String getInstructions() { + return instructionKey; + } + + @Override + public String getTooltip() { + return tooltipKey; + } + + @Override + public boolean isAvailable() { + return MapTool.getPlayer().isGM(); + } + + @Override + protected boolean isLinearTool() { + return strategy.isLinear(); + } + + /** If currently drawing, stop and clear it. */ + @Override + protected void resetTool() { + if (state != null) { + state = null; + renderer.repaint(); + } else { + super.resetTool(); + } + } + + private BasicStroke getLineStroke() { + return new BasicStroke(2.f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); + } + + private void submit(Shape shape) { + Area area; + if (shape instanceof Area tmpArea) { + area = tmpArea; + } else if (isFilled) { + // Fill the shape without stroking. + area = new Area(shape); + } else { + // Stroke the shape into an area. + var stroke = getLineStroke(); + area = new Area(stroke.createStrokedShape(shape)); + } + + MapTool.serverCommand() + .updateTopology(getZone(), area, isEraser(), getZone().getTopologyTypes()); + } + + private Area getTokenTopology(Zone.TopologyType topologyType) { + List topologyTokens = getZone().getTokensWithTopology(topologyType); + + Area tokenTopology = new Area(); + for (Token topologyToken : topologyTokens) { + tokenTopology.add(topologyToken.getTransformedTopology(topologyType)); + } + + return tokenTopology; + } + + @Override + public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { + if (!MapTool.getPlayer().isGM()) { + // Redundant check since the tool should not be available otherwise. + return; + } + + Zone zone = renderer.getZone(); + + Graphics2D g2 = (Graphics2D) g.create(); + g2.translate(renderer.getViewOffsetX(), renderer.getViewOffsetY()); + g2.scale(renderer.getScale(), renderer.getScale()); + + g2.setColor(AppStyle.tokenMblColor); + g2.fill(getTokenTopology(Zone.TopologyType.MBL)); + g2.setColor(AppStyle.tokenTopologyColor); + g2.fill(getTokenTopology(Zone.TopologyType.WALL_VBL)); + g2.setColor(AppStyle.tokenHillVblColor); + g2.fill(getTokenTopology(Zone.TopologyType.HILL_VBL)); + g2.setColor(AppStyle.tokenPitVblColor); + g2.fill(getTokenTopology(Zone.TopologyType.PIT_VBL)); + g2.setColor(AppStyle.tokenCoverVblColor); + g2.fill(getTokenTopology(Zone.TopologyType.COVER_VBL)); + + g2.setColor(AppStyle.topologyTerrainColor); + g2.fill(zone.getTopology(Zone.TopologyType.MBL)); + + g2.setColor(AppStyle.topologyColor); + g2.fill(zone.getTopology(Zone.TopologyType.WALL_VBL)); + + g2.setColor(AppStyle.hillVblColor); + g2.fill(zone.getTopology(Zone.TopologyType.HILL_VBL)); + + g2.setColor(AppStyle.pitVblColor); + g2.fill(zone.getTopology(Zone.TopologyType.PIT_VBL)); + + g2.setColor(AppStyle.coverVblColor); + g2.fill(zone.getTopology(Zone.TopologyType.COVER_VBL)); + + if (state != null) { + var result = strategy.getShape(state, currentPoint, centerOnOrigin, false); + if (result != null) { + var stroke = getLineStroke(); + var color = isEraser() ? AppStyle.topologyRemoveColor : AppStyle.topologyAddColor; + + if (!isFilled || isLinearTool()) { + // Render as a thick line. + g2.setColor(color); + g2.setStroke(stroke); + g2.draw(result.shape()); + } else { + // Render as an area with a thin border. + g2.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 255)); + g2.setStroke( + new BasicStroke( + 1 / (float) renderer.getScale(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER)); + g2.draw(result.shape()); + + g2.setColor(color); + g2.fill(result.shape()); + } + } + } + + g2.dispose(); + } + + @Override + public void mouseDragged(MouseEvent e) { + if (state == null) { + // We're not doing anything, so delegate to default behaviour. + super.mouseDragged(e); + } + } + + @Override + public void mouseMoved(MouseEvent e) { + super.mouseMoved(e); + setIsEraser(isEraser(e)); + if (state != null) { + currentPoint = getPoint(e); + renderer.repaint(); + } + } + + @Override + public void mousePressed(MouseEvent e) { + setIsEraser(isEraser(e)); + + if (SwingUtilities.isLeftMouseButton(e)) { + currentPoint = getPoint(e); + + if (state == null) { + state = strategy.startNewAtPoint(currentPoint); + } else { + var result = strategy.getShape(state, currentPoint, centerOnOrigin, isFilled); + state = null; + if (result != null) { + submit(result.shape()); + } + } + renderer.repaint(); + } else if (state != null) { + currentPoint = getPoint(e); + strategy.pushPoint(state, currentPoint); + renderer.repaint(); + } + + super.mousePressed(e); + } +} diff --git a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java index 418ddb28ec..10595a1604 100644 --- a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java @@ -342,32 +342,80 @@ protected void activate() { private OptionPanel createTopologyPanel() { OptionPanel panel = new OptionPanel(); + panel - .add(RectangleTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.recttopology.instructions", + "tool.recttopology.tooltip", + true, + new RectangleStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_BOX)); panel - .add(HollowRectangleTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.recttopology.instructions", + "tool.recttopologyhollow.tooltip", + false, + new RectangleStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_BOX_HOLLOW)); panel - .add(OvalTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.ovaltopology.instructions", + "tool.ovaltopology.tooltip", + true, + // 10 steps to keep number of topology vertices reasonable. + new OvalStrategy(10))) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_OVAL)); panel - .add(HollowOvalTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.ovaltopology.instructions", + "tool.ovaltopologyhollow.tooltip", + false, + // 10 steps to keep number of topology vertices reasonable. + new OvalStrategy(10))) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_OVAL_HOLLOW)); panel - .add(PolygonTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.poly.instructions", + "tool.polytopo.tooltip", + true, + new PolyLineStrategy(false))) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_POLYGON)); panel - .add(PolyLineTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.poly.instructions", + "tool.polylinetopo.tooltip", + false, + new PolyLineStrategy(false))) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_POLYLINE)); panel - .add(CrossTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.crosstopology.instructions", + "tool.crosstopology.tooltip", + false, + new CrossStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_CROSS)); panel - .add(DiamondTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.isorectangletopology.instructions", + "tool.isorectangletopology.tooltip", + true, + new IsoRectangleStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_DIAMOND)); panel - .add(HollowDiamondTopologyTool.class) + .addTool( + new TopologyTool<>( + "tool.isorectangletopology.instructions", + "tool.isorectangletopologyhollow.tooltip", + false, + new IsoRectangleStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_TOPOLOGY_DIAMOND_HOLLOW)); // Add with separator to separate mode button group from shape button group. From 282d0ae021303a64b09ecfab598afa192eb90e6d Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 15:07:04 -0700 Subject: [PATCH 11/15] Replace existing expose tool implementations with one class Analogous to `TopologyTool`, we now have `ExposeTool` that can work with any strategy and merely interprets the results. This also fixes a minor bug where the iso rectangle tool did not result in the fog border being used. --- .../tool/drawing/DiamondExposeTool.java | 88 ------- .../client/tool/drawing/ExposeTool.java | 238 ++++++++++++++++++ .../tool/drawing/FreehandExposeTool.java | 108 -------- .../client/tool/drawing/OvalExposeTool.java | 92 ------- .../tool/drawing/PolygonExposeTool.java | 104 -------- .../tool/drawing/RectangleExposeTool.java | 91 ------- .../maptool/client/ui/ToolbarPanel.java | 26 +- .../client/ui/zone/renderer/ZoneRenderer.java | 12 +- 8 files changed, 261 insertions(+), 498 deletions(-) delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/DiamondExposeTool.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/FreehandExposeTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/OvalExposeTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/PolygonExposeTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/RectangleExposeTool.java diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondExposeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondExposeTool.java deleted file mode 100644 index ee70cf3314..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondExposeTool.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.event.MouseEvent; -import java.awt.geom.Area; -import java.util.Set; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.Pen; - -public class DiamondExposeTool extends DiamondTool { - private static final long serialVersionUID = 2577704127916955192L; - - public DiamondExposeTool() {} - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.rectexpose.instructions"; - } - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - protected boolean isBackgroundFill(MouseEvent e) { - // Expose tools are implied to be filled - return false; - } - - @Override - protected Pen getPen() { - Pen pen = super.getPen(); - pen.setBackgroundMode(Pen.MODE_TRANSPARENT); - pen.setThickness(1); - return pen; - } - - @Override - protected void completeDrawable(Pen pen, Drawable drawable) { - if (!MapTool.getPlayer().isGM()) { - MapTool.showError("msg.error.fogexpose"); - MapTool.getFrame().refresh(); - return; - } - Zone zone = getZone(); - Area area = new Area(drawable.getArea(zone)); - Set selectedToks = renderer.getSelectedTokenSet(); - if (pen.isEraser()) { - zone.hideArea(area, selectedToks); - MapTool.serverCommand().hideFoW(zone.getId(), area, selectedToks); - } else { - MapTool.serverCommand().exposeFoW(zone.getId(), area, selectedToks); - } - MapTool.getFrame().refresh(); - } - - @Override - public String getTooltip() { - return "tool.isorectangleexpose.tooltip"; - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java new file mode 100644 index 0000000000..c74c30643f --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java @@ -0,0 +1,238 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.event.MouseEvent; +import java.awt.geom.Area; +import java.util.Set; +import javax.annotation.Nullable; +import javax.swing.SwingUtilities; +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.ScreenPoint; +import net.rptools.maptool.client.tool.ToolHelper; +import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; +import net.rptools.maptool.model.GUID; +import net.rptools.maptool.model.Zone; +import net.rptools.maptool.model.ZonePoint; + +public final class ExposeTool extends AbstractDrawingLikeTool { + private final String instructionKey; + private final String tooltipKey; + private final Strategy strategy; + + /** The current state of the tool. If {@code null}, nothing is being drawn right now. */ + private @Nullable StateT state; + + private ZonePoint currentPoint = new ZonePoint(0, 0); + private boolean centerOnOrigin = false; + + public ExposeTool(String instructionKey, String tooltipKey, Strategy strategy) { + this.instructionKey = instructionKey; + this.tooltipKey = tooltipKey; + this.strategy = strategy; + } + + @Override + public String getInstructions() { + return instructionKey; + } + + @Override + public String getTooltip() { + return tooltipKey; + } + + @Override + public boolean isAvailable() { + return MapTool.getPlayer().isGM(); + } + + @Override + protected boolean isLinearTool() { + return strategy.isLinear(); + } + + /** If currently drawing, stop and clear it. */ + @Override + protected void resetTool() { + if (state != null) { + state = null; + renderer.repaint(); + } else { + super.resetTool(); + } + } + + private void submit(Shape shape) { + if (!MapTool.getPlayer().isGM()) { + MapTool.showError("msg.error.fogexpose"); + MapTool.getFrame().refresh(); + return; + } + + Area area; + if (shape instanceof Area tmpArea) { + area = tmpArea; + } else { + // Fill the shape. + area = new Area(shape); + } + + Zone zone = getZone(); + Set selectedToks = renderer.getSelectedTokenSet(); + + if (isEraser()) { + zone.hideArea(area, selectedToks); + MapTool.serverCommand().hideFoW(zone.getId(), area, selectedToks); + } else { + MapTool.serverCommand().exposeFoW(zone.getId(), area, selectedToks); + } + } + + @Override + public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { + Graphics2D g2 = (Graphics2D) g.create(); + g2.translate(renderer.getViewOffsetX(), renderer.getViewOffsetY()); + g2.scale(renderer.getScale(), renderer.getScale()); + + if (state != null) { + var result = strategy.getShape(state, currentPoint, centerOnOrigin, false); + if (result != null) { + var color = isEraser() ? Color.white : Color.black; + + if (!isLinearTool()) { + // Render the interior for better user feedback. + g2.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 64)); + g2.fill(result.shape()); + } + + // Render the line. + g2.setColor(color); + g2.setStroke( + new BasicStroke( + 1 / (float) renderer.getScale(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER)); + g2.draw(result.shape()); + + // Measurements + var measurement = result.measurement(); + switch (measurement) { + case null -> {} + case Measurement.Rectangular rectangular -> { + var rectangle = rectangular.bounds(); + ToolHelper.drawBoxedMeasurement( + renderer, + g, + ScreenPoint.fromZonePoint(renderer, rectangle.getX(), rectangle.getY()), + ScreenPoint.fromZonePoint(renderer, rectangle.getMaxX(), rectangle.getMaxY())); + } + case Measurement.LineSegment lineSegment -> { + var p1 = + ScreenPoint.fromZonePoint( + renderer, lineSegment.p1().getX(), lineSegment.p1().getY()); + var p2 = + ScreenPoint.fromZonePoint( + renderer, lineSegment.p2().getX(), lineSegment.p2().getY()); + ToolHelper.drawMeasurement(renderer, g, p1, p2); + } + case Measurement.IsoRectangular isoRectangular -> { + var north = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.north().getX(), isoRectangular.north().getY()); + var west = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.west().getX(), isoRectangular.west().getY()); + var east = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.east().getX(), isoRectangular.east().getY()); + ToolHelper.drawIsoRectangleMeasurement(renderer, g, north, west, east); + } + } + } + } + + g2.dispose(); + } + + @Override + public void mouseDragged(MouseEvent e) { + if (state == null) { + // We're not doing anything, so delegate to default behaviour. + super.mouseDragged(e); + } else if (strategy.isFreehand()) { + // Extend the line. + setIsEraser(isEraser(e)); + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); // Pointless, but it doesn't hurt for consistency. + strategy.pushPoint(state, currentPoint); + renderer.repaint(); + } + } + + @Override + public void mouseMoved(MouseEvent e) { + super.mouseMoved(e); + setIsEraser(isEraser(e)); + if (state != null) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + renderer.repaint(); + } + } + + @Override + public void mousePressed(MouseEvent e) { + setIsEraser(isEraser(e)); + + if (SwingUtilities.isLeftMouseButton(e)) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + + if (state == null) { + state = strategy.startNewAtPoint(currentPoint); + } else if (!strategy.isFreehand()) { + var result = strategy.getShape(state, currentPoint, centerOnOrigin, true); + state = null; + if (result != null) { + submit(result.shape()); + } + } + renderer.repaint(); + } else if (state != null && !strategy.isFreehand()) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + strategy.pushPoint(state, currentPoint); + renderer.repaint(); + } + + super.mousePressed(e); + } + + @Override + public void mouseReleased(MouseEvent e) { + if (strategy.isFreehand() && SwingUtilities.isLeftMouseButton(e)) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + var result = strategy.getShape(state, currentPoint, centerOnOrigin, true); + state = null; + if (result != null) { + submit(result.shape()); + } + } + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/FreehandExposeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/FreehandExposeTool.java deleted file mode 100644 index 059edb246d..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/FreehandExposeTool.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import java.util.Set; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.LineSegment; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -/** Tool for drawing freehand lines. */ -public class FreehandExposeTool extends FreehandTool implements MouseMotionListener { - private static final long serialVersionUID = 3258132466219627316L; - - public FreehandExposeTool() {} - - @Override - public String getTooltip() { - return "tool.freehandexpose.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.freehandexpose.instructions"; - } - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - protected Pen getPen() { - Pen pen = super.getPen(); - pen.setThickness(1); - return pen; - } - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - protected boolean isBackgroundFill(MouseEvent e) { - // Expose tools are implied to be filled - return false; - } - - @Override - protected void stopLine(MouseEvent e) { - LineSegment line = getLine(); - - if (line == null) return; // Escape has been pressed - addPoint(e); - completeDrawable(getPen(), line); - resetTool(); - } - - @Override - protected void completeDrawable(Pen pen, Drawable drawable) { - if (!MapTool.getPlayer().isGM()) { - MapTool.showError("msg.error.fogexpose"); - MapTool.getFrame().refresh(); - return; - } - Zone zone = getZone(); - - Area area = null; - if (drawable instanceof LineSegment) { - area = new Area(getPolygon((LineSegment) drawable)); - } - if (drawable instanceof ShapeDrawable) { - area = new Area(((ShapeDrawable) drawable).getShape()); - } - Set selectedToks = renderer.getSelectedTokenSet(); - if (pen.isEraser()) { - zone.hideArea(area, selectedToks); - MapTool.serverCommand().hideFoW(zone.getId(), area, selectedToks); - } else { - MapTool.serverCommand().exposeFoW(zone.getId(), area, selectedToks); - } - MapTool.getFrame().refresh(); - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalExposeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalExposeTool.java deleted file mode 100644 index 6f8d6f72f0..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalExposeTool.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Rectangle; -import java.awt.event.MouseEvent; -import java.awt.geom.Area; -import java.awt.geom.Ellipse2D; -import java.util.Set; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.Pen; - -public class OvalExposeTool extends OvalTool { - private static final long serialVersionUID = -9023090752132286356L; - - public OvalExposeTool() {} - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - protected boolean isBackgroundFill(MouseEvent e) { - // Expose tools are implied to be filled - return false; - } - - @Override - protected Pen getPen() { - Pen pen = super.getPen(); - pen.setBackgroundMode(Pen.MODE_TRANSPARENT); - pen.setThickness(1); - return pen; - } - - @Override - public String getTooltip() { - return "tool.ovalexpose.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.ovalexpose.instructions"; - } - - @Override - protected void completeDrawable(Pen pen, Drawable drawable) { - if (!MapTool.getPlayer().isGM()) { - MapTool.showError("msg.error.fogexpose"); - MapTool.getFrame().refresh(); - return; - } - Zone zone = getZone(); - - Rectangle bounds = drawable.getBounds(zone); - Area area = new Area(new Ellipse2D.Double(bounds.x, bounds.y, bounds.width, bounds.height)); - Set selectedToks = renderer.getSelectedTokenSet(); - if (pen.isEraser()) { - zone.hideArea(area, selectedToks); - MapTool.serverCommand().hideFoW(zone.getId(), area, selectedToks); - } else { - MapTool.serverCommand().exposeFoW(zone.getId(), area, selectedToks); - } - MapTool.getFrame().refresh(); - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonExposeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonExposeTool.java deleted file mode 100644 index 1c7e9d059f..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonExposeTool.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Polygon; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Area; -import java.util.Set; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.LineSegment; -import net.rptools.maptool.model.drawing.Pen; - -/** Tool for drawing freehand lines. */ -public class PolygonExposeTool extends PolygonTool implements MouseMotionListener { - private static final long serialVersionUID = 3258132466219627316L; - - public PolygonExposeTool() {} - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getTooltip() { - return "tool.polyexpose.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.polyexpose.instructions"; - } - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - protected boolean isBackgroundFill(MouseEvent e) { - // Expose tools are implied to be filled - return false; - } - - @Override - protected Pen getPen() { - Pen pen = super.getPen(); - pen.setBackgroundMode(Pen.MODE_TRANSPARENT); - pen.setThickness(1); - return pen; - } - - @Override - protected void stopLine(MouseEvent e) { - LineSegment line = getLine(); - - if (line == null) return; // Escape has been pressed - addPoint(e); - completeDrawable(getPen(), line); - resetTool(); - } - - @Override - protected void completeDrawable(Pen pen, Drawable drawable) { - if (!MapTool.getPlayer().isGM()) { - MapTool.showError("msg.error.fogexpose"); - MapTool.getFrame().refresh(); - return; - } - Zone zone = getZone(); - - Polygon polygon = getPolygon((LineSegment) drawable); - Area area = new Area(polygon); - Set selectedToks = renderer.getSelectedTokenSet(); - if (pen.isEraser()) { - zone.hideArea(area, selectedToks); - MapTool.serverCommand().hideFoW(zone.getId(), area, selectedToks); - } else { - MapTool.serverCommand().exposeFoW(zone.getId(), area, selectedToks); - } - MapTool.getFrame().refresh(); - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleExposeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleExposeTool.java deleted file mode 100644 index 7a1336b0bb..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleExposeTool.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Rectangle; -import java.awt.event.MouseEvent; -import java.awt.geom.Area; -import java.util.Set; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.Pen; - -public class RectangleExposeTool extends RectangleTool { - private static final long serialVersionUID = 2072551559910263728L; - - public RectangleExposeTool() {} - - @Override - public boolean isAvailable() { - return MapTool.getPlayer().isGM(); - } - - @Override - public String getInstructions() { - return "tool.rectexpose.instructions"; - } - - @Override - // Override abstracttool to prevent color palette from - // showing up - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - // Hide the drawable color palette - MapTool.getFrame().removeControlPanel(); - } - - @Override - protected boolean isBackgroundFill(MouseEvent e) { - // Expose tools are implied to be filled - return false; - } - - @Override - protected Pen getPen() { - Pen pen = super.getPen(); - pen.setBackgroundMode(Pen.MODE_TRANSPARENT); - pen.setThickness(1); - return pen; - } - - @Override - protected void completeDrawable(Pen pen, Drawable drawable) { - if (!MapTool.getPlayer().isGM()) { - MapTool.showError("msg.error.fogexpose"); - MapTool.getFrame().refresh(); - return; - } - Zone zone = getZone(); - - Rectangle bounds = drawable.getBounds(zone); - Area area = new Area(bounds); - Set selectedToks = renderer.getSelectedTokenSet(); - if (pen.isEraser()) { - zone.hideArea(area, selectedToks); - MapTool.serverCommand().hideFoW(zone.getId(), area, selectedToks); - } else { - MapTool.serverCommand().exposeFoW(zone.getId(), area, selectedToks); - } - MapTool.getFrame().refresh(); - } - - @Override - public String getTooltip() { - return "tool.rectexpose.tooltip"; - } -} diff --git a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java index 10595a1604..21405ce336 100644 --- a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java @@ -322,21 +322,37 @@ protected void activate() { } } }; + panel - .add(RectangleExposeTool.class) + .addTool( + new ExposeTool<>( + "tool.rectexpose.instructions", "tool.rectexpose.tooltip", new RectangleStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_FOG_EXPOSE_BOX)); panel - .add(OvalExposeTool.class) + .addTool( + new ExposeTool<>( + "tool.ovalexpose.instructions", "tool.ovalexpose.tooltip", new OvalStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_FOG_EXPOSE_OVAL)); panel - .add(PolygonExposeTool.class) + .addTool( + new ExposeTool<>( + "tool.polyexpose.instructions", + "tool.polyexpose.tooltip", + new PolyLineStrategy(false))) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_FOG_EXPOSE_POLYGON)); panel - .add(FreehandExposeTool.class) + .addTool( + new ExposeTool<>( + "tool.freehand.instructions", "tool.freehand.tooltip", new PolyLineStrategy(true))) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_FOG_EXPOSE_FREEHAND)); panel - .add(DiamondExposeTool.class) + .addTool( + new ExposeTool<>( + "tool.rectexpose.instructions", + "tool.isorectangleexpose.tooltip", + new IsoRectangleStrategy())) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_FOG_EXPOSE_DIAMOND)); + return panel; } diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java index 2e2690e862..b91fb4fe9d 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java @@ -48,10 +48,7 @@ import net.rptools.maptool.client.tool.PointerTool; import net.rptools.maptool.client.tool.StampTool; import net.rptools.maptool.client.tool.Tool; -import net.rptools.maptool.client.tool.drawing.FreehandExposeTool; -import net.rptools.maptool.client.tool.drawing.OvalExposeTool; -import net.rptools.maptool.client.tool.drawing.PolygonExposeTool; -import net.rptools.maptool.client.tool.drawing.RectangleExposeTool; +import net.rptools.maptool.client.tool.drawing.ExposeTool; import net.rptools.maptool.client.ui.Scale; import net.rptools.maptool.client.ui.theme.Borders; import net.rptools.maptool.client.ui.theme.Images; @@ -2723,12 +2720,7 @@ protected void renderTokens( } if (useIF && token.getLayer().supportsVision() && zoneView.isUsingVision()) { Tool tool = MapTool.getFrame().getToolbox().getSelectedTool(); - if (tool - instanceof - RectangleExposeTool // XXX Change to use marker interface such as ExposeTool? - || tool instanceof OvalExposeTool - || tool instanceof FreehandExposeTool - || tool instanceof PolygonExposeTool) { + if (tool instanceof ExposeTool) { selectedBorder = RessourceManager.getBorder(Borders.FOW_TOOLS); } } From c7b7224e5c2c8c2f9099bd4c259dcee1d818005d Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 15:14:45 -0700 Subject: [PATCH 12/15] Replace existing drawing tool implementations with one class Analogous to `TopologyTool` and `ExposeTool`, we now have `DrawingTool` that can work with any strategy and merely interprets the results as drawings to add to the zone. --- .../client/tool/drawing/AbstractLineTool.java | 181 ---------- .../client/tool/drawing/DiamondTool.java | 116 ------ .../client/tool/drawing/DrawingTool.java | 334 ++++++++++++++++++ .../client/tool/drawing/FreehandTool.java | 66 ---- .../maptool/client/tool/drawing/LineTool.java | 77 ---- .../maptool/client/tool/drawing/OvalTool.java | 164 --------- .../client/tool/drawing/PolygonTool.java | 55 --- .../client/tool/drawing/RectangleTool.java | 143 -------- .../maptool/client/ui/ToolbarPanel.java | 31 +- .../ui/drawpanel/DrawPanelPopupMenu.java | 2 +- .../maptool/model/drawing/LineSegment.java | 56 ++- .../maptool/model/drawing/ShapeDrawable.java | 4 - 12 files changed, 409 insertions(+), 820 deletions(-) delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/AbstractLineTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTool.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/FreehandTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/LineTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTool.java delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTool.java diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractLineTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractLineTool.java deleted file mode 100644 index 30b15fe03b..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractLineTool.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Polygon; -import java.awt.event.MouseEvent; -import java.util.Collections; -import java.util.List; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.AppPreferences.RenderQuality; -import net.rptools.maptool.client.ScreenPoint; -import net.rptools.maptool.client.tool.Tool; -import net.rptools.maptool.client.tool.ToolHelper; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.DrawableColorPaint; -import net.rptools.maptool.model.drawing.LineSegment; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -/** Tool for drawing freehand lines. */ -public abstract class AbstractLineTool extends AbstractDrawingTool { - private int currentX; - private int currentY; - - private LineSegment line; - protected boolean drawMeasurementDisabled; - - protected int getCurrentX() { - return currentX; - } - - protected int getCurrentY() { - return currentY; - } - - protected LineSegment getLine() { - return this.line; - } - - protected void startLine(MouseEvent e) { - line = new LineSegment(getPen().getThickness(), getPen().getSquareCap()); - addPoint(e); - } - - protected Point addPoint(MouseEvent e) { - if (SwingUtilities.isRightMouseButton(e)) { - return null; - } - ZonePoint zp = getPoint(e); - - if (line == null) return null; // Escape has been pressed - Point ret = new Point(zp.x, zp.y); - - line.getPoints().add(ret); - currentX = zp.x; - currentY = zp.y; - - renderer.repaint(); - return ret; - } - - protected void removePoint(Point p) { - if (line == null) return; // Escape has been pressed - - // Remove most recently added - // TODO: optimize this - Collections.reverse(line.getPoints()); - line.getPoints().remove(p); - Collections.reverse(line.getPoints()); - } - - protected void stopLine(MouseEvent e) { - if (line == null) return; // Escape has been pressed - addPoint(e); - - LineSegment trimLine = getTrimLine(line); - Drawable drawable = trimLine; - if (isBackgroundFill(e) && line.getPoints().size() > 2) { - drawable = new ShapeDrawable(getPolygon(trimLine)); - } - completeDrawable(getPen(), drawable); - - line = null; - currentX = -1; - currentY = -1; - } - - protected Polygon getPolygon(LineSegment line) { - Polygon polygon = new Polygon(); - for (Point point : line.getPoints()) { - polygon.addPoint(point.x, point.y); - } - return polygon; - } - - /** - * Due to mouse movement, a user drawn line often has duplicated points, especially at the end. To - * draw a clean line with miter joints these duplicates should be removed. - * - * @param line the {@link LineSegment} to trim. - * @return the trimmed {@link LineSegment}. - */ - protected LineSegment getTrimLine(LineSegment line) { - LineSegment newLine = new LineSegment(line.getWidth(), line.isSquareCap()); - Point lastPoint = null; - for (Point point : line.getPoints()) { - if (!point.equals(lastPoint)) newLine.getPoints().add(point); - lastPoint = point; - } - return newLine; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - if (line != null) { - // For the line currently being drawn we are more concerned with speed of prettiness - var og = (Graphics2D) g.create(); - RenderQuality.LOW_SCALING.setRenderingHints(og); - Pen pen = getPen(); - pen.setForegroundMode(Pen.MODE_SOLID); - - if (pen.isEraser()) { - pen = new Pen(pen); - pen.setEraser(false); - pen.setPaint(new DrawableColorPaint(Color.white)); - } - paintTransformed(og, renderer, line, pen); - - List pointList = line.getPoints(); - if (!drawMeasurementDisabled && pointList.size() > 1 && drawMeasurement()) { - - Point start = pointList.get(pointList.size() - 2); - Point end = pointList.get(pointList.size() - 1); - - ScreenPoint sp = ScreenPoint.fromZonePoint(renderer, start.x, start.y); - ScreenPoint ep = ScreenPoint.fromZonePoint(renderer, end.x, end.y); - - // ep.y -= 15; - - ToolHelper.drawMeasurement(renderer, og, sp, ep); - } - og.dispose(); - } - } - - protected boolean drawMeasurement() { - return true; - } - - /** - * @see Tool#resetTool() - */ - @Override - protected void resetTool() { - if (line != null) { - line = null; - currentX = -1; - currentY = -1; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTool.java deleted file mode 100644 index 970ae65f30..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/DiamondTool.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Shape; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.tool.ToolHelper; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.DrawableColorPaint; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -public class DiamondTool extends AbstractDrawingTool implements MouseMotionListener { - - private static final long serialVersionUID = 8239333601131612106L; - protected Shape diamond; - protected ZonePoint originPoint; - - public DiamondTool() {} - - @Override - public String getInstructions() { - return "tool.rect.instructions"; - } - - @Override - public String getTooltip() { - return "tool.isorectangle.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - if (diamond != null) { - Pen pen = getPen(); - if (pen.isEraser()) { - pen = new Pen(pen); - pen.setEraser(false); - pen.setPaint(new DrawableColorPaint(Color.white)); - pen.setBackgroundPaint(new DrawableColorPaint(Color.white)); - } - paintTransformed(g, renderer, new ShapeDrawable(diamond, false), pen); - ToolHelper.drawDiamondMeasurement(renderer, g, diamond); - } - } - - @Override - public void mousePressed(MouseEvent e) { - ZonePoint zp = getPoint(e); - if (SwingUtilities.isLeftMouseButton(e)) { - if (diamond == null) { - originPoint = zp; - diamond = createDiamond(originPoint, originPoint); - } else { - diamond = createDiamond(originPoint, zp); - - if (diamond.getBounds().width == 0 || diamond.getBounds().height == 0) { - diamond = null; - renderer.repaint(); - return; - } - // ToolHelper.drawDiamondMeasurement(renderer, null, diamond); - completeDrawable(getPen(), new ShapeDrawable(diamond, false)); - diamond = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (diamond == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - if (diamond != null) { - ZonePoint p = getPoint(e); - diamond = createDiamond(originPoint, p); - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - @Override - public void resetTool() { - if (diamond != null) { - diamond = null; - originPoint = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java new file mode 100644 index 0000000000..9232325049 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java @@ -0,0 +1,334 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.event.MouseEvent; +import java.awt.geom.Area; +import java.awt.geom.Path2D; +import javax.annotation.Nullable; +import javax.swing.SwingUtilities; +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.MapToolUtil; +import net.rptools.maptool.client.ScreenPoint; +import net.rptools.maptool.client.swing.colorpicker.ColorPicker; +import net.rptools.maptool.client.tool.ToolHelper; +import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; +import net.rptools.maptool.model.Zone; +import net.rptools.maptool.model.ZonePoint; +import net.rptools.maptool.model.drawing.Drawable; +import net.rptools.maptool.model.drawing.DrawableColorPaint; +import net.rptools.maptool.model.drawing.LineSegment; +import net.rptools.maptool.model.drawing.Pen; +import net.rptools.maptool.model.drawing.ShapeDrawable; + +public final class DrawingTool extends AbstractDrawingLikeTool { + private final String instructionKey; + private final String tooltipKey; + private final Strategy strategy; + + /** The current state of the tool. If {@code null}, nothing is being drawn right now. */ + private @Nullable StateT state; + + private ZonePoint currentPoint = new ZonePoint(0, 0); + private boolean centerOnOrigin = false; + + public DrawingTool(String instructionKey, String tooltipKey, Strategy strategy) { + this.instructionKey = instructionKey; + this.tooltipKey = tooltipKey; + this.strategy = strategy; + } + + @Override + public String getInstructions() { + return instructionKey; + } + + @Override + public String getTooltip() { + return tooltipKey; + } + + @Override + public boolean isAvailable() { + return MapTool.getPlayer().isGM(); + } + + @Override + protected boolean isLinearTool() { + return strategy.isLinear(); + } + + @Override + protected void attachTo(ZoneRenderer renderer) { + super.attachTo(renderer); + if (MapTool.getPlayer().isGM()) { + MapTool.getFrame() + .showControlPanel(MapTool.getFrame().getColorPicker(), getLayerSelectionDialog()); + } else { + MapTool.getFrame().showControlPanel(MapTool.getFrame().getColorPicker()); + } + renderer.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); + } + + @Override + protected void detachFrom(ZoneRenderer renderer) { + MapTool.getFrame().removeControlPanel(); + renderer.setCursor(Cursor.getDefaultCursor()); + + super.detachFrom(renderer); + } + + /** If currently drawing, stop and clear it. */ + @Override + protected void resetTool() { + if (state != null) { + state = null; + renderer.repaint(); + } else { + super.resetTool(); + } + } + + @Override + protected boolean isEraser(MouseEvent e) { + // Use the color picker as the default, but invert based on key state. + var inverted = super.isEraser(e); + boolean defaultValue = MapTool.getFrame().getColorPicker().isEraseSelected(); + if (inverted) { + defaultValue = !defaultValue; + } + return defaultValue; + } + + @Override + protected boolean isSnapToGrid(MouseEvent e) { + // Use the color picker as the default, but invert based on key state. + var inverted = super.isSnapToGrid(e); + boolean defaultValue = MapTool.getFrame().getColorPicker().isSnapSelected(); + if (inverted) { + // Invert from the color panel + defaultValue = !defaultValue; + } + return defaultValue; + } + + private boolean isBackgroundFill() { + return MapTool.getFrame().getColorPicker().isFillBackgroundSelected(); + } + + private boolean hasPaint(Pen pen) { + return pen.getForegroundMode() != Pen.MODE_TRANSPARENT + || pen.getBackgroundMode() != Pen.MODE_TRANSPARENT; + } + + private Pen getPen() { + Pen pen = new Pen(MapTool.getFrame().getPen()); + pen.setEraser(isEraser()); + + ColorPicker picker = MapTool.getFrame().getColorPicker(); + if (picker.isFillForegroundSelected()) { + pen.setForegroundMode(Pen.MODE_SOLID); + } else { + pen.setForegroundMode(Pen.MODE_TRANSPARENT); + } + if (picker.isFillBackgroundSelected()) { + pen.setBackgroundMode(Pen.MODE_SOLID); + } else { + pen.setBackgroundMode(Pen.MODE_TRANSPARENT); + } + pen.setSquareCap(picker.isSquareCapSelected()); + pen.setThickness(picker.getStrokeWidth()); + return pen; + } + + private Drawable toDrawable(Shape shape) { + if (shape instanceof Path2D path) { + if (isLinearTool()) { + // Preserve the path. Will be an unbroken path of straight segments. In the future we can + // look at handling more general paths. + var pen = getPen(); + return new LineSegment(pen.getThickness(), pen.getSquareCap(), path); + } else { + // The path describes an area. + return new ShapeDrawable(new Area(path), true); + } + } + + // All other shapes are solid and represented by ShapeDrawable. + return new ShapeDrawable(shape, true); + } + + private void submit(Shape shape) { + var pen = getPen(); + if (!hasPaint(pen)) { + return; + } + + Zone zone = getZone(); + var drawable = toDrawable(shape); + if (drawable.getBounds(zone) == null) { + return; + } + + if (MapTool.getPlayer().isGM()) { + drawable.setLayer(getSelectedLayer()); + } else { + drawable.setLayer(Zone.Layer.getDefaultPlayerLayer()); + } + + // Send new textures + MapToolUtil.uploadTexture(pen.getPaint()); + MapToolUtil.uploadTexture(pen.getBackgroundPaint()); + + // Tell the local/server to render the drawable. + MapTool.serverCommand().draw(zone.getId(), pen, drawable); + + // Allow it to be undone + zone.addDrawable(pen, drawable); + } + + @Override + public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { + Graphics2D g2 = (Graphics2D) g.create(); + g2.translate(renderer.getViewOffsetX(), renderer.getViewOffsetY()); + g2.scale(renderer.getScale(), renderer.getScale()); + + if (state != null) { + // Linear tools are not filled until completed. + var result = strategy.getShape(state, currentPoint, centerOnOrigin, false); + if (result != null) { + var drawable = toDrawable(result.shape()); + + Pen pen = getPen(); + if (isEraser()) { + pen = new Pen(pen); + pen.setEraser(false); + pen.setPaint(new DrawableColorPaint(Color.white)); + pen.setBackgroundPaint(new DrawableColorPaint(Color.white)); + } + + drawable.draw(renderer.getZone(), g2, pen); + + // Measurements + var measurement = result.measurement(); + switch (measurement) { + case null -> {} + case Measurement.Rectangular rectangular -> { + var rectangle = rectangular.bounds(); + ToolHelper.drawBoxedMeasurement( + renderer, + g, + ScreenPoint.fromZonePoint(renderer, rectangle.getX(), rectangle.getY()), + ScreenPoint.fromZonePoint(renderer, rectangle.getMaxX(), rectangle.getMaxY())); + } + case Measurement.LineSegment lineSegment -> { + var p1 = + ScreenPoint.fromZonePoint( + renderer, lineSegment.p1().getX(), lineSegment.p1().getY()); + var p2 = + ScreenPoint.fromZonePoint( + renderer, lineSegment.p2().getX(), lineSegment.p2().getY()); + ToolHelper.drawMeasurement(renderer, g, p1, p2); + } + case Measurement.IsoRectangular isoRectangular -> { + var north = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.north().getX(), isoRectangular.north().getY()); + var west = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.west().getX(), isoRectangular.west().getY()); + var east = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.east().getX(), isoRectangular.east().getY()); + ToolHelper.drawIsoRectangleMeasurement(renderer, g, north, west, east); + } + } + } + } + + g2.dispose(); + } + + @Override + public void mouseDragged(MouseEvent e) { + if (state == null) { + // We're not doing anything, so delegate to default behaviour. + super.mouseDragged(e); + } else if (strategy.isFreehand()) { + // Extend the line. + setIsEraser(isEraser(e)); + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); // Pointless, but it doesn't hurt for consistency. + strategy.pushPoint(state, currentPoint); + renderer.repaint(); + } + } + + @Override + public void mouseMoved(MouseEvent e) { + super.mouseMoved(e); + setIsEraser(isEraser(e)); + if (state != null) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + renderer.repaint(); + } + } + + @Override + public void mousePressed(MouseEvent e) { + setIsEraser(isEraser(e)); + + if (SwingUtilities.isLeftMouseButton(e)) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + + if (state == null) { + state = strategy.startNewAtPoint(currentPoint); + } else if (!strategy.isFreehand()) { + var result = strategy.getShape(state, currentPoint, centerOnOrigin, isBackgroundFill()); + state = null; + if (result != null) { + submit(result.shape()); + } + } + renderer.repaint(); + } else if (state != null && !strategy.isFreehand()) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + strategy.pushPoint(state, currentPoint); + renderer.repaint(); + } + + super.mousePressed(e); + } + + @Override + public void mouseReleased(MouseEvent e) { + if (strategy.isFreehand() && SwingUtilities.isLeftMouseButton(e)) { + currentPoint = getPoint(e); + centerOnOrigin = e.isAltDown(); + var result = strategy.getShape(state, currentPoint, centerOnOrigin, isBackgroundFill()); + state = null; + if (result != null) { + submit(result.shape()); + } + } + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/FreehandTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/FreehandTool.java deleted file mode 100644 index 5f2840bbc4..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/FreehandTool.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import javax.swing.SwingUtilities; - -/** Tool for drawing freehand lines. */ -public class FreehandTool extends AbstractLineTool implements MouseMotionListener { - private static final long serialVersionUID = 3904963036442998837L; - - public FreehandTool() { - // Don't ever show measurement drawing with freehand tool - drawMeasurementDisabled = true; - } - - @Override - public String getTooltip() { - return "tool.freehand.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.freehand.instructions"; - } - - //// - // MOUSE LISTENER - @Override - public void mousePressed(MouseEvent e) { - if (SwingUtilities.isLeftMouseButton(e)) { - startLine(e); - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseReleased(MouseEvent e) { - if (SwingUtilities.isLeftMouseButton(e)) { - stopLine(e); - } - super.mouseReleased(e); - } - - //// - // MOUSE MOTION LISTENER - @Override - public void mouseDragged(java.awt.event.MouseEvent e) { - addPoint(e); - super.mouseDragged(e); - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/LineTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/LineTool.java deleted file mode 100644 index 6de6cd9cc0..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/LineTool.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Point; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import javax.swing.SwingUtilities; - -/** Tool for drawing freehand lines. */ -public class LineTool extends AbstractLineTool implements MouseMotionListener { - private static final long serialVersionUID = 3258132466219627316L; - private Point tempPoint; - - public LineTool() {} - - @Override - public String getTooltip() { - return "tool.line.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.line.instructions"; - } - - //// - // MOUSE LISTENER - @Override - public void mousePressed(MouseEvent e) { - if (SwingUtilities.isLeftMouseButton(e)) { - if (getLine() == null) { - startLine(e); - setIsEraser(isEraser(e)); - } else { - tempPoint = null; - stopLine(e); - } - } else if (getLine() != null) { - // Create a joint - tempPoint = null; - return; - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (getLine() == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - if (getLine() != null) { - if (tempPoint != null) { - removePoint(tempPoint); - } - tempPoint = addPoint(e); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java deleted file mode 100644 index 6c32668de8..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import java.awt.geom.Ellipse2D; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.ScreenPoint; -import net.rptools.maptool.client.tool.Tool; -import net.rptools.maptool.client.tool.ToolHelper; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.DrawableColorPaint; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -/** - * @author drice - */ -public class OvalTool extends AbstractDrawingTool implements MouseMotionListener { - private static final long serialVersionUID = 3258413928311830323L; - - protected Rectangle oval; - private ZonePoint originPoint; - - public OvalTool() {} - - @Override - public String getTooltip() { - return "tool.oval.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.oval.instructions"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - if (oval != null) { - Pen pen = getPen(); - - if (pen.isEraser()) { - pen = new Pen(pen); - pen.setEraser(false); - pen.setPaint(new DrawableColorPaint(Color.white)); - pen.setBackgroundPaint(new DrawableColorPaint(Color.white)); - } - - paintTransformed( - g, - renderer, - new ShapeDrawable(new Ellipse2D.Double(oval.x, oval.y, oval.width, oval.height)), - pen); - - ToolHelper.drawBoxedMeasurement( - renderer, - g, - ScreenPoint.fromZonePoint(renderer, oval.x, oval.y), - ScreenPoint.fromZonePoint(renderer, oval.x + oval.width, oval.y + oval.height)); - } - } - - @Override - public void mousePressed(MouseEvent e) { - - if (SwingUtilities.isLeftMouseButton(e)) { - ZonePoint zp = getPoint(e); - - if (oval == null) { - originPoint = zp; - oval = createRect(zp, zp); - } else { - oval = createRect(originPoint, zp); - - // Draw from center if ALT is held down - if (e.isAltDown()) { - if (zp.x > originPoint.x) oval.x -= oval.width; - - if (zp.y > originPoint.y) oval.y -= oval.height; - - oval.width *= 2; - oval.height *= 2; - } - - completeDrawable( - getPen(), - new ShapeDrawable(new Ellipse2D.Double(oval.x, oval.y, oval.width, oval.height), true)); - oval = null; - } - - setIsEraser(isEraser(e)); - } - - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - - if (oval == null) { - super.mouseDragged(e); - } - } - - /* - * (non-Javadoc) - * - * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent) - */ - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - if (oval != null) { - - ZonePoint sp = getPoint(e); - - oval = createRect(originPoint, sp); - - // Draw from center if ALT is held down - if (e.isAltDown()) { - if (sp.x > originPoint.x) oval.x -= oval.width; - - if (sp.y > originPoint.y) oval.y -= oval.height; - - oval.width *= 2; - oval.height *= 2; - } - - renderer.repaint(); - } - } - - /** - * @see Tool#resetTool() - */ - @Override - protected void resetTool() { - - if (oval != null) { - oval = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTool.java deleted file mode 100644 index d7f3156669..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/PolygonTool.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Point; -import java.awt.Polygon; -import java.awt.event.MouseMotionListener; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.LineSegment; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -/** Tool for drawing freehand lines. */ -public class PolygonTool extends LineTool implements MouseMotionListener { - private static final long serialVersionUID = 3258132466219627316L; - - public PolygonTool() {} - - @Override - public String getTooltip() { - return "tool.poly.tooltip"; - } - - @Override - public String getInstructions() { - return "tool.poly.instructions"; - } - - @Override - protected void completeDrawable(Pen pen, Drawable drawable) { - LineSegment line = (LineSegment) drawable; - super.completeDrawable(pen, new ShapeDrawable(getPolygon(line))); - } - - @Override - protected Polygon getPolygon(LineSegment line) { - Polygon polygon = new Polygon(); - for (Point point : line.getPoints()) { - polygon.addPoint(point.x, point.y); - } - return polygon; - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTool.java deleted file mode 100644 index aa68e050a2..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/RectangleTool.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; -import javax.swing.SwingUtilities; -import net.rptools.maptool.client.ScreenPoint; -import net.rptools.maptool.client.tool.ToolHelper; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.DrawableColorPaint; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -/** - * @author drice - */ -public class RectangleTool extends AbstractDrawingTool implements MouseMotionListener { - private static final long serialVersionUID = 3258413928311830323L; - - protected Rectangle rectangle; - protected ZonePoint originPoint; - - public RectangleTool() {} - - @Override - public String getInstructions() { - return "tool.rect.instructions"; - } - - @Override - public String getTooltip() { - return "tool.rect.tooltip"; - } - - @Override - public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { - if (rectangle != null) { - Pen pen = getPen(); - if (pen.isEraser()) { - pen = new Pen(pen); - pen.setEraser(false); - pen.setPaint(new DrawableColorPaint(Color.white)); - pen.setBackgroundPaint(new DrawableColorPaint(Color.white)); - } - paintTransformed(g, renderer, new ShapeDrawable(rectangle, false), pen); - ToolHelper.drawBoxedMeasurement( - renderer, - g, - ScreenPoint.fromZonePoint(renderer, rectangle.x, rectangle.y), - ScreenPoint.fromZonePoint( - renderer, rectangle.x + rectangle.width, rectangle.y + rectangle.height)); - } - } - - @Override - public void mousePressed(MouseEvent e) { - ZonePoint zp = getPoint(e); - if (SwingUtilities.isLeftMouseButton(e)) { - if (rectangle == null) { - originPoint = zp; - rectangle = createRect(originPoint, originPoint); - } else { - rectangle = createRect(originPoint, zp); - - if (rectangle.width == 0 || rectangle.height == 0) { - rectangle = null; - renderer.repaint(); - return; - } - // Draw Rectangle with initial point as Center - if (e.isAltDown()) { - if (zp.x > originPoint.x) rectangle.x -= rectangle.width; - - if (zp.y > originPoint.y) rectangle.y -= rectangle.height; - - rectangle.width *= 2; - rectangle.height *= 2; - } - // System.out.println("Adding Rectangle to zone: " + rectangle); - completeDrawable(getPen(), new ShapeDrawable(rectangle, false)); - rectangle = null; - } - setIsEraser(isEraser(e)); - } - super.mousePressed(e); - } - - @Override - public void mouseDragged(MouseEvent e) { - if (rectangle == null) { - super.mouseDragged(e); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - - if (rectangle != null) { - ZonePoint p = getPoint(e); - rectangle = createRect(originPoint, p); - - // Draw Rectangle with initial point as Center - if (e.isAltDown()) { - if (p.x > originPoint.x) rectangle.x -= rectangle.width; - - if (p.y > originPoint.y) rectangle.y -= rectangle.height; - - rectangle.width *= 2; - rectangle.height *= 2; - } - renderer.repaint(); - } - } - - /** Stop drawing a rectangle and repaint the zone. */ - @Override - public void resetTool() { - if (rectangle != null) { - rectangle = null; - renderer.repaint(); - } else { - super.resetTool(); - } - } -} diff --git a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java index 21405ce336..84c66e7124 100644 --- a/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/ToolbarPanel.java @@ -264,12 +264,33 @@ private OptionPanel createDrawPanel() { panel .add(DeleteDrawingTool.class) .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_DELETE)); - panel.add(FreehandTool.class).setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_FREEHAND)); - panel.add(LineTool.class).setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_LINE)); - panel.add(RectangleTool.class).setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_BOX)); - panel.add(OvalTool.class).setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_OVAL)); + + panel + .addTool( + new DrawingTool<>( + "tool.freehand.instructions", "tool.freehand.tooltip", new PolyLineStrategy(true))) + .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_FREEHAND)); + panel + .addTool( + new DrawingTool<>( + "tool.line.instructions", "tool.line.tooltip", new PolyLineStrategy(false))) + .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_LINE)); + panel + .addTool( + new DrawingTool<>( + "tool.rect.instructions", "tool.rect.tooltip", new RectangleStrategy())) + .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_BOX)); + panel + .addTool( + new DrawingTool<>("tool.oval.instructions", "tool.oval.tooltip", new OvalStrategy())) + .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_OVAL)); panel.add(TextTool.class).setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_TEXT)); - panel.add(DiamondTool.class).setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_DIAMOND)); + panel + .addTool( + new DrawingTool<>( + "tool.rect.instructions", "tool.isorectangle.tooltip", new IsoRectangleStrategy())) + .setIcon(RessourceManager.getBigIcon(Icons.TOOLBAR_DRAW_DIAMOND)); + return panel; } diff --git a/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java b/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java index 292c8fc78b..f6624b773b 100644 --- a/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java +++ b/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java @@ -292,7 +292,7 @@ public void actionPerformed(ActionEvent e) { Shape s = a; Pen newPen = new Pen(elementUnderMouse.getPen()); if (elementUnderMouse.getDrawable() instanceof LineSegment) newPen = invertPen(newPen); - DrawnElement de = new DrawnElement(new ShapeDrawable(s), newPen); + DrawnElement de = new DrawnElement(new ShapeDrawable(s, true), newPen); de.getDrawable().setLayer(elementUnderMouse.getDrawable().getLayer()); MapTool.serverCommand().draw(renderer.getZone().getId(), newPen, de.getDrawable()); MapTool.getFrame().updateDrawTree(); diff --git a/src/main/java/net/rptools/maptool/model/drawing/LineSegment.java b/src/main/java/net/rptools/maptool/model/drawing/LineSegment.java index 68c268e396..7b01a4c000 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/LineSegment.java +++ b/src/main/java/net/rptools/maptool/model/drawing/LineSegment.java @@ -21,7 +21,10 @@ import java.awt.Rectangle; import java.awt.geom.Area; import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; import javax.annotation.Nonnull; @@ -63,6 +66,28 @@ public LineSegment(LineSegment other) { } } + public LineSegment(float width, boolean squareCap, Path2D path) { + this.width = width; + this.squareCap = squareCap; + + // Assume an unbroken line string. + double[] coordinates = new double[6]; + for (var it = path.getPathIterator(null); !it.isDone(); it.next()) { + var type = it.currentSegment(coordinates); + // type will be SEG_MOVETO or SEG_LINETO or SEG_CLOSE. The first two mean the same to + // us, while the latter ends the line. + if (type == PathIterator.SEG_CLOSE) { + // Should not be possible to be empty, but can't hurt to check. + if (!points.isEmpty()) { + addPoint(points.getFirst()); + } + break; + } else { + addPoint((int) coordinates[0], (int) coordinates[1]); + } + } + } + @Override public Drawable copy() { return new LineSegment(this); @@ -78,16 +103,31 @@ private Object readResolve() { } /** - * Manipulate the points by calling {@link #getPoints} and then adding {@link Point} objects to - * the returned {@link List}. + * Add a point to the line. + * + * @param x + * @param y + */ + public void addPoint(int x, int y) { + area = null; + points.add(new Point(x, y)); + } + + /** + * Add a point to the line. * + * @param point + * @see #addPoint(int, int) + */ + public void addPoint(Point point) { + addPoint(point.x, point.y); + } + + /** * @return the list of point */ public List getPoints() { - // This is really, really ugly, but we need to flush the area on any change to the shape - // and typically the reason for calling this method is to change the list - area = null; - return points; + return Collections.unmodifiableList(points); } @Override @@ -108,14 +148,14 @@ public DrawableDto toDto() { if (getName() != null) dto.setName(StringValue.of(getName())); - getPoints().forEach(p -> dto.addPoints(Mapper.map(p))); + points.forEach(p -> dto.addPoints(Mapper.map(p))); return DrawableDto.newBuilder().setLineSegment(dto).build(); } public static LineSegment fromDto(LineSegmentDrawableDto dto) { var id = GUID.valueOf(dto.getId()); var drawable = new LineSegment(id, dto.getWidth(), dto.getSquareCap()); - var points = drawable.getPoints(); + var points = drawable.points; var pointDtos = dto.getPointsList(); pointDtos.forEach(p -> points.add(Mapper.map(p))); if (dto.hasName()) { diff --git a/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java b/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java index fede411fa6..aa91dda1b2 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java +++ b/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java @@ -44,10 +44,6 @@ public ShapeDrawable(Shape shape, boolean useAntiAliasing) { this.useAntiAliasing = useAntiAliasing; } - public ShapeDrawable(Shape shape) { - this(shape, true); - } - public ShapeDrawable(ShapeDrawable other) { super(other); this.useAntiAliasing = other.useAntiAliasing; From 3717c627fede321e748e1a7dcfebed845f8b576c Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 15:32:56 -0700 Subject: [PATCH 13/15] Deprecate now-unused drawing types `Oval`, `Rectangle`, and `Cross` no longer serve a purpose and so have been deprecated. It is possible for existing campaigns to have these serialized in them, so we are keeping the structure around. However, each one will `readResolve()` itself into a modern type. Also be clear about the set of supported types in `ShapeDrawable`. The new `ShapeDrawable#getShapeTypeName()` outputs one of `"Rectangle"`, `"Oval"`, `"Polygon"`, `"Area"`, or `"Unknown"`, which callers can use to make meaningful decisions about the general type of shape wrapped in the drawable. This matters especially for ellipses, where several places refers to the simple name of `Ellipse2D.Float`, which is just `"Float"`. Now they refer to `"Oval"`, or use proper type checks where needed. --- .../client/functions/DrawingFunctions.java | 19 +-- .../ui/drawpanel/DrawPanelPopupMenu.java | 14 +- .../drawpanel/DrawPanelTreeCellRenderer.java | 24 +-- .../rptools/maptool/model/drawing/Cross.java | 150 ++++-------------- .../maptool/model/drawing/Drawable.java | 3 - .../rptools/maptool/model/drawing/Oval.java | 110 +++---------- .../maptool/model/drawing/Rectangle.java | 139 +++------------- .../maptool/model/drawing/ShapeDrawable.java | 21 +++ src/main/proto/drawing_dto.proto | 28 ---- .../rptools/maptool/language/i18n.properties | 3 +- 10 files changed, 123 insertions(+), 388 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java b/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java index f60cfafb37..218fb799f8 100644 --- a/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/DrawingFunctions.java @@ -17,6 +17,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import java.awt.Point; +import java.awt.geom.Ellipse2D; import java.awt.geom.PathIterator; import java.math.BigDecimal; import java.util.List; @@ -274,13 +275,8 @@ private JsonObject boundsToJSON(Zone map, AbstractDrawing d) { private String getDrawbleType(AbstractDrawing d) { if (d instanceof LineSegment) { return "Line"; - } else if (d instanceof ShapeDrawable) { - String shape = ((ShapeDrawable) d).getShape().getClass().getSimpleName(); - if ("Float".equalsIgnoreCase(shape)) { - return "Oval"; - } else { - return shape; - } + } else if (d instanceof ShapeDrawable sd) { + return sd.getShapeTypeName(); } else if (d instanceof DrawablesGroup) { return "Group"; } else { @@ -299,14 +295,15 @@ private JsonArray pathToJSON(AbstractDrawing d) { pinfo.add(info); } return pinfo; - } else if (d instanceof ShapeDrawable) { - String shape = ((ShapeDrawable) d).getShape().getClass().getSimpleName(); - if ("Float".equalsIgnoreCase(shape)) { + } else if (d instanceof ShapeDrawable sd) { + var shape = sd.getShape(); + if (shape instanceof Ellipse2D) { + // We don't support converting ellipses to path. return new JsonArray(); } else { // Convert shape into path JsonArray pinfo = new JsonArray(); - final PathIterator pathIter = ((ShapeDrawable) d).getShape().getPathIterator(null); + final PathIterator pathIter = shape.getPathIterator(null); float[] coords = new float[6]; JsonObject lastinfo = new JsonObject(); while (!pathIter.isDone()) { diff --git a/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java b/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java index f6624b773b..b9317f9c64 100644 --- a/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java +++ b/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelPopupMenu.java @@ -20,6 +20,7 @@ import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; import java.awt.geom.Path2D; import java.util.ArrayList; import java.util.Iterator; @@ -536,11 +537,14 @@ private boolean hasPath(Set selectedDrawSet) { * @return boolean */ private boolean hasPath(DrawnElement drawnElement) { - if (drawnElement == null) return false; - if (drawnElement.getDrawable() instanceof LineSegment) return true; - if (drawnElement.getDrawable() instanceof ShapeDrawable) { - ShapeDrawable sd = (ShapeDrawable) drawnElement.getDrawable(); - return "Float".equalsIgnoreCase(sd.getShape().getClass().getSimpleName()) == false; + if (drawnElement == null) { + return false; + } + if (drawnElement.getDrawable() instanceof LineSegment) { + return true; + } + if (drawnElement.getDrawable() instanceof ShapeDrawable sd) { + return !(sd.getShape() instanceof Ellipse2D); } return false; } diff --git a/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelTreeCellRenderer.java b/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelTreeCellRenderer.java index c1f6197dde..e87f5a8497 100644 --- a/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelTreeCellRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/drawpanel/DrawPanelTreeCellRenderer.java @@ -51,28 +51,20 @@ public Component getTreeCellRendererComponent( String text = ""; this.row = row; - if (value instanceof DrawnElement) { - String key = "panel.DrawExplorer.Unknown.Shape"; - DrawnElement de = (DrawnElement) value; + if (value instanceof DrawnElement de) { text = de.getDrawable().toString(); if (de.getDrawable() instanceof DrawablesGroup) { text = I18N.getString("panel.DrawExplorer.group"); - } else if (de.getDrawable() instanceof ShapeDrawable) { - ShapeDrawable sd = (ShapeDrawable) de.getDrawable(); - key = - String.format( - "panel.DrawExplorer.%s.%s", - sd.getClass().getSimpleName(), sd.getShape().getClass().getSimpleName()); + } else if (de.getDrawable() instanceof ShapeDrawable sd) { + var key = String.format("panel.DrawExplorer.ShapeDrawable.%s", sd.getShapeTypeName()); text = I18N.getText(key, sd.getBounds().width, sd.getBounds().height); setLeafIcon(setDrawPanelIcon(key, de.getPen().isEraser())); - } else if (de.getDrawable() instanceof LineSegment) { - LineSegment ls = (LineSegment) de.getDrawable(); - key = String.format("panel.DrawExplorer.%s.Line", ls.getClass().getSimpleName()); + } else if (de.getDrawable() instanceof LineSegment ls) { + var key = "panel.DrawExplorer.LineSegment.Line"; text = I18N.getText(key, ls.getPoints().size(), de.getPen().getThickness()); setLeafIcon(setDrawPanelIcon(key, de.getPen().isEraser())); - } else if (de.getDrawable() instanceof AbstractTemplate) { - AbstractTemplate at = (AbstractTemplate) de.getDrawable(); - key = String.format("panel.DrawExplorer.Template.%s", at.getClass().getSimpleName()); + } else if (de.getDrawable() instanceof AbstractTemplate at) { + var key = String.format("panel.DrawExplorer.Template.%s", at.getClass().getSimpleName()); text = I18N.getText(key, at.getRadius()); setLeafIcon(setDrawPanelIcon(key, de.getPen().isEraser())); } @@ -116,7 +108,7 @@ private Icon setDrawPanelIcon(String key, boolean eraser) { case "panel.DrawExplorer.ShapeDrawable.Polygon": if (eraser) return RessourceManager.getSmallIcon(Icons.DRAWPANEL_POLYGON_ERASE); else return RessourceManager.getSmallIcon(Icons.DRAWPANEL_POLYGON_DRAW); - case "panel.DrawExplorer.ShapeDrawable.Float": + case "panel.DrawExplorer.ShapeDrawable.Oval": if (eraser) return RessourceManager.getSmallIcon(Icons.DRAWPANEL_ELLIPSE_ERASE); else return RessourceManager.getSmallIcon(Icons.DRAWPANEL_ELLIPSE_DRAW); case "panel.DrawExplorer.ShapeDrawable.Rectangle": diff --git a/src/main/java/net/rptools/maptool/model/drawing/Cross.java b/src/main/java/net/rptools/maptool/model/drawing/Cross.java index 58ed13e4b4..f9d96cc430 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/Cross.java +++ b/src/main/java/net/rptools/maptool/model/drawing/Cross.java @@ -14,131 +14,35 @@ */ package net.rptools.maptool.model.drawing; -import com.google.protobuf.StringValue; -import java.awt.Graphics2D; import java.awt.Point; -import java.awt.RenderingHints; -import java.awt.geom.Area; -import javax.annotation.Nonnull; +import java.awt.geom.Path2D; +import java.io.Serial; +import java.io.Serializable; import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.server.Mapper; -import net.rptools.maptool.server.proto.drawing.CrossDrawableDto; -import net.rptools.maptool.server.proto.drawing.DrawableDto; -/** An Cross */ -public class Cross extends AbstractDrawing { - protected Point startPoint; - protected Point endPoint; - private transient java.awt.Rectangle bounds; - - public Cross(int startX, int startY, int endX, int endY) { - startPoint = new Point(startX, startY); - endPoint = new Point(endX, endY); - } - - public Cross(GUID id, int startX, int startY, int endX, int endY) { - super(id); - startPoint = new Point(startX, startY); - endPoint = new Point(endX, endY); - } - - public Cross(Cross other) { - super(other); - - this.startPoint = new Point(other.startPoint); - this.endPoint = new Point(other.endPoint); - } - - @Override - public Drawable copy() { - return new Cross(this); - } - - @Override - public @Nonnull Area getArea(Zone zone) { - return new Area(getBounds(zone)); - } - - @Override - public DrawableDto toDto() { - var dto = - CrossDrawableDto.newBuilder() - .setId(getId().toString()) - .setLayer(getLayer().name()) - .setStartPoint(Mapper.map(getStartPoint())) - .setEndPoint(Mapper.map(getEndPoint())); - - if (getName() != null) dto.setName(StringValue.of(getName())); - - return DrawableDto.newBuilder().setCrossDrawable(dto).build(); - } - - public static Cross fromDto(CrossDrawableDto dto) { - var id = GUID.valueOf(dto.getId()); - var startPoint = dto.getStartPoint(); - var endPoint = dto.getEndPoint(); - var drawable = - new Cross(id, startPoint.getX(), startPoint.getY(), endPoint.getX(), endPoint.getY()); - if (dto.hasName()) { - drawable.setName(dto.getName().getValue()); - } - drawable.setLayer(Zone.Layer.valueOf(dto.getLayer())); - return drawable; - } - - @Override - public java.awt.Rectangle getBounds(Zone zone) { - if (bounds == null) { - int x = Math.min(startPoint.x, endPoint.x); - int y = Math.min(startPoint.y, endPoint.y); - int width = Math.abs(endPoint.x - startPoint.x); - int height = Math.abs(endPoint.y - startPoint.y); - - bounds = new java.awt.Rectangle(x, y, width, height); - } - - return bounds; - } - - public Point getStartPoint() { - return startPoint; - } - - public Point getEndPoint() { - return endPoint; - } - - @Override - protected void draw(Zone zone, Graphics2D g) { - - int minX = Math.min(startPoint.x, endPoint.x); - int minY = Math.min(startPoint.y, endPoint.y); - - int width = Math.abs(startPoint.x - endPoint.x); - int height = Math.abs(startPoint.y - endPoint.y); - - Object oldAA = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); - // g.drawRect(minX, minY, width, height); - - g.drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y); - g.drawLine(startPoint.x, endPoint.y, endPoint.x, startPoint.y); - - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAA); - } - - @Override - protected void drawBackground(Zone zone, Graphics2D g) { - int minX = Math.min(startPoint.x, endPoint.x); - int minY = Math.min(startPoint.y, endPoint.y); - - int width = Math.abs(startPoint.x - endPoint.x); - int height = Math.abs(startPoint.y - endPoint.y); - - Object oldAA = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); - g.fillRect(minX, minY, width, height); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAA); +/** + * An X-shaped cross + * + * @deprecated This is a legacy class not currently in use. It is kept here in case it has been + * serialized in any existing campaigns. It used to extend {@link AbstractDrawing} but is now * + * just a holder for data and will replace itself with a {@link ShapeDrawable}. + */ +@Deprecated +public final class Cross implements Serializable { + private GUID id; + private String layer; + private String name; + private Point startPoint; + private Point endPoint; + + @Serial + private Object readResolve() { + var path = new Path2D.Double(); + path.moveTo(startPoint.x, startPoint.y); + path.lineTo(endPoint.x, endPoint.y); + path.moveTo(startPoint.x, endPoint.y); + path.lineTo(endPoint.x, startPoint.y); + + return new ShapeDrawable(id, path, false); } } diff --git a/src/main/java/net/rptools/maptool/model/drawing/Drawable.java b/src/main/java/net/rptools/maptool/model/drawing/Drawable.java index 034fa9fcc2..a2ee068b0b 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/Drawable.java +++ b/src/main/java/net/rptools/maptool/model/drawing/Drawable.java @@ -48,9 +48,6 @@ public interface Drawable { static Drawable fromDto(DrawableDto drawableDto) { return switch (drawableDto.getDrawableTypeCase()) { case SHAPE_DRAWABLE -> ShapeDrawable.fromDto(drawableDto.getShapeDrawable()); - case RECTANGLE_DRAWABLE -> Rectangle.fromDto(drawableDto.getRectangleDrawable()); - case OVAL_DRAWABLE -> Oval.fromDto(drawableDto.getOvalDrawable()); - case CROSS_DRAWABLE -> Cross.fromDto(drawableDto.getCrossDrawable()); case DRAWN_LABEL -> DrawnLabel.fromDto(drawableDto.getDrawnLabel()); case LINE_SEGMENT -> LineSegment.fromDto(drawableDto.getLineSegment()); case DRAWABLES_GROUP -> DrawablesGroup.fromDto(drawableDto.getDrawablesGroup()); diff --git a/src/main/java/net/rptools/maptool/model/drawing/Oval.java b/src/main/java/net/rptools/maptool/model/drawing/Oval.java index 4b16a8425f..f785431961 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/Oval.java +++ b/src/main/java/net/rptools/maptool/model/drawing/Oval.java @@ -14,93 +14,33 @@ */ package net.rptools.maptool.model.drawing; -import com.google.protobuf.StringValue; -import java.awt.Graphics2D; -import java.awt.geom.Area; +import java.awt.Point; import java.awt.geom.Ellipse2D; -import javax.annotation.Nonnull; +import java.io.Serial; +import java.io.Serializable; import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.server.Mapper; -import net.rptools.maptool.server.proto.drawing.DrawableDto; -import net.rptools.maptool.server.proto.drawing.OvalDrawableDto; -/** An oval. */ -public class Oval extends Rectangle { - /** - * @param x the x offset - * @param y the y offset - * @param width the width of the oval - * @param height the height of the oval - */ - public Oval(int x, int y, int width, int height) { - super(x, y, width, height); - } - - public Oval(GUID id, int x, int y, int width, int height) { - super(id, x, y, width, height); - } - - public Oval(Oval other) { - super(other); - } - - @Override - public Drawable copy() { - return new Oval(this); - } - - @Override - protected void draw(Zone zone, Graphics2D g) { - int minX = Math.min(startPoint.x, endPoint.x); - int minY = Math.min(startPoint.y, endPoint.y); - - int width = Math.abs(startPoint.x - endPoint.x); - int height = Math.abs(startPoint.y - endPoint.y); - - g.drawOval(minX, minY, width, height); - } - - @Override - protected void drawBackground(Zone zone, Graphics2D g) { - int minX = Math.min(startPoint.x, endPoint.x); - int minY = Math.min(startPoint.y, endPoint.y); - - int width = Math.abs(startPoint.x - endPoint.x); - int height = Math.abs(startPoint.y - endPoint.y); - - g.fillOval(minX, minY, width, height); - } - - @Override - public @Nonnull Area getArea(Zone zone) { - java.awt.Rectangle r = getBounds(zone); - return new Area(new Ellipse2D.Double(r.x, r.y, r.width, r.height)); - } - - public DrawableDto toDto() { - var dto = - OvalDrawableDto.newBuilder() - .setId(getId().toString()) - .setLayer(getLayer().name()) - .setStartPoint(Mapper.map(getStartPoint())) - .setEndPoint(Mapper.map(getEndPoint())); - - if (getName() != null) dto.setName(StringValue.of(getName())); - - return DrawableDto.newBuilder().setOvalDrawable(dto).build(); - } - - public static Oval fromDto(OvalDrawableDto dto) { - var id = GUID.valueOf(dto.getId()); - var startPoint = dto.getStartPoint(); - var endPoint = dto.getEndPoint(); - var drawable = - new Oval(id, startPoint.getX(), startPoint.getY(), endPoint.getX(), endPoint.getY()); - if (dto.hasName()) { - drawable.setName(dto.getName().getValue()); - } - drawable.setLayer(Zone.Layer.valueOf(dto.getLayer())); - return drawable; +/** + * An oval. + * + * @deprecated This is a legacy class not currently in use. It is kept here in case it has been + * serialized in any existing campaigns. It used to extend {@link Rectangle} and {@link + * AbstractDrawing} but is now just a holder for data and will replace itself with a {@link + * ShapeDrawable}. + */ +@Deprecated +public final class Oval implements Serializable { + private GUID id; + private String layer; + private String name; + private Point startPoint; + private Point endPoint; + + @Serial + private Object readResolve() { + var ellipse = + new Ellipse2D.Double( + startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y - startPoint.y); + return new ShapeDrawable(id, ellipse, true); } } diff --git a/src/main/java/net/rptools/maptool/model/drawing/Rectangle.java b/src/main/java/net/rptools/maptool/model/drawing/Rectangle.java index b0dbfa771e..5ac1c830e6 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/Rectangle.java +++ b/src/main/java/net/rptools/maptool/model/drawing/Rectangle.java @@ -14,124 +14,31 @@ */ package net.rptools.maptool.model.drawing; -import com.google.protobuf.StringValue; -import java.awt.Graphics2D; import java.awt.Point; -import java.awt.RenderingHints; -import java.awt.geom.Area; -import javax.annotation.Nonnull; +import java.io.Serial; +import java.io.Serializable; import net.rptools.maptool.model.GUID; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.server.Mapper; -import net.rptools.maptool.server.proto.drawing.DrawableDto; -import net.rptools.maptool.server.proto.drawing.RectangleDrawableDto; -/** An rectangle */ -public class Rectangle extends AbstractDrawing { - protected Point startPoint; - protected Point endPoint; - private transient java.awt.Rectangle bounds; - - public Rectangle(GUID id, int startX, int startY, int endX, int endY) { - super(id); - startPoint = new Point(startX, startY); - endPoint = new Point(endX, endY); - } - - public Rectangle(int startX, int startY, int endX, int endY) { - startPoint = new Point(startX, startY); - endPoint = new Point(endX, endY); - } - - public Rectangle(Rectangle other) { - super(other); - this.startPoint = new Point(other.startPoint); - this.endPoint = new Point(other.endPoint); - } - - @Override - public Drawable copy() { - return new Rectangle(this); - } - - @Override - public @Nonnull Area getArea(Zone zone) { - return new Area(getBounds(zone)); - } - - @Override - public DrawableDto toDto() { - var dto = - RectangleDrawableDto.newBuilder() - .setId(getId().toString()) - .setLayer(getLayer().name()) - .setStartPoint(Mapper.map(getStartPoint())) - .setEndPoint(Mapper.map(getEndPoint())); - - if (getName() != null) dto.setName(StringValue.of(getName())); - - return DrawableDto.newBuilder().setRectangleDrawable(dto).build(); - } - - public static Rectangle fromDto(RectangleDrawableDto dto) { - var id = GUID.valueOf(dto.getId()); - var startPoint = dto.getStartPoint(); - var endPoint = dto.getEndPoint(); - var drawable = - new Rectangle(id, startPoint.getX(), startPoint.getY(), endPoint.getX(), endPoint.getY()); - if (dto.hasName()) { - drawable.setName(dto.getName().getValue()); - } - drawable.setLayer(Zone.Layer.valueOf(dto.getLayer())); - return drawable; - } - - @Override - public java.awt.Rectangle getBounds(Zone zone) { - if (bounds == null) { - int x = Math.min(startPoint.x, endPoint.x); - int y = Math.min(startPoint.y, endPoint.y); - int width = Math.abs(endPoint.x - startPoint.x); - int height = Math.abs(endPoint.y - startPoint.y); - - bounds = new java.awt.Rectangle(x, y, width, height); - } - return bounds; - } - - public Point getStartPoint() { - return startPoint; - } - - public Point getEndPoint() { - return endPoint; - } - - @Override - protected void draw(Zone zone, Graphics2D g) { - int minX = Math.min(startPoint.x, endPoint.x); - int minY = Math.min(startPoint.y, endPoint.y); - - int width = Math.abs(startPoint.x - endPoint.x); - int height = Math.abs(startPoint.y - endPoint.y); - - Object oldAA = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); - g.drawRect(minX, minY, width, height); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAA); - } - - @Override - protected void drawBackground(Zone zone, Graphics2D g) { - int minX = Math.min(startPoint.x, endPoint.x); - int minY = Math.min(startPoint.y, endPoint.y); - - int width = Math.abs(startPoint.x - endPoint.x); - int height = Math.abs(startPoint.y - endPoint.y); - - Object oldAA = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); - g.fillRect(minX, minY, width, height); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAA); +/** + * A rectangle. + * + * @deprecated This is a legacy class not currently in use. It is kept here in case it has been + * serialized in any existing campaigns. It used to extend {@link AbstractDrawing} but is now + * just a holder for data and will replace itself with a {@link ShapeDrawable}. + */ +@Deprecated +public final class Rectangle implements Serializable { + private GUID id; + private String layer; + private String name; + private Point startPoint; + private Point endPoint; + + @Serial + private Object readResolve() { + var rectangle = + new java.awt.Rectangle( + startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y - startPoint.y); + return new ShapeDrawable(this.id, rectangle, false); } } diff --git a/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java b/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java index aa91dda1b2..6a2fae623f 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java +++ b/src/main/java/net/rptools/maptool/model/drawing/ShapeDrawable.java @@ -17,9 +17,11 @@ import com.google.protobuf.StringValue; import java.awt.Graphics2D; import java.awt.Polygon; +import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; import java.awt.geom.RectangularShape; import javax.annotation.Nonnull; import net.rptools.maptool.model.GUID; @@ -62,6 +64,25 @@ public Drawable copy() { return new ShapeDrawable(this); } + /** + * Get a descriptive name for the type of shape wrapped by this {@code ShapeDrawable}. + * + *

Note: do not use this method for type checks. It is only useful for producing human-readable + * text, including by building translation keys. If you need type checking, using {@link + * #getShape()} along with `instanceof`! + * + * @return The type of shape contained in this drawable. + */ + public String getShapeTypeName() { + return switch (shape) { + case Rectangle ignored -> "Rectangle"; + case Ellipse2D ignored -> "Oval"; + case Polygon ignored -> "Polygon"; + case Area ignored -> "Area"; + default -> "Unknown"; + }; + } + public boolean getUseAntiAliasing() { return useAntiAliasing; } diff --git a/src/main/proto/drawing_dto.proto b/src/main/proto/drawing_dto.proto index 37815323de..f77e9428b3 100644 --- a/src/main/proto/drawing_dto.proto +++ b/src/main/proto/drawing_dto.proto @@ -36,9 +36,6 @@ message PenDto { message DrawableDto { oneof drawable_type { ShapeDrawableDto shape_drawable = 1; - RectangleDrawableDto rectangle_drawable = 2; - OvalDrawableDto oval_drawable = 3; - CrossDrawableDto cross_drawable = 4; DrawnLabelDto drawn_label = 5; LineSegmentDrawableDto line_segment = 6; DrawablesGroupDto drawables_group = 7; @@ -156,31 +153,6 @@ message DrawnLabelDto { string font = 6; } -message RectangleDrawableDto { - string id = 1; - string layer = 2; - google.protobuf.StringValue name = 3; - IntPointDto start_point = 4; - IntPointDto end_point = 5; -} - -message OvalDrawableDto { - string id = 1; - string layer = 2; - google.protobuf.StringValue name = 3; - IntPointDto start_point = 4; - IntPointDto end_point = 5; -} - -message CrossDrawableDto { - string id = 1; - string layer = 2; - google.protobuf.StringValue name = 3; - IntPointDto start_point = 4; - IntPointDto end_point = 5; -} - - message ShapeDrawableDto { string id = 1; string layer = 2; diff --git a/src/main/resources/net/rptools/maptool/language/i18n.properties b/src/main/resources/net/rptools/maptool/language/i18n.properties index 92cf3b9bab..7e685d9436 100644 --- a/src/main/resources/net/rptools/maptool/language/i18n.properties +++ b/src/main/resources/net/rptools/maptool/language/i18n.properties @@ -2489,9 +2489,10 @@ panel.DrawExplorer = Draw Explorer panel.DrawExplorer.description = Dockable window for managing drawings. panel.DrawExplorer.LineSegment.Line = points({0}) pen width({1}) panel.DrawExplorer.ShapeDrawable.Area = width({0}) height({1}) -panel.DrawExplorer.ShapeDrawable.Float = width({0}) height({1}) +panel.DrawExplorer.ShapeDrawable.Oval = width({0}) height({1}) panel.DrawExplorer.ShapeDrawable.Polygon = width({0}) height({1}) panel.DrawExplorer.ShapeDrawable.Rectangle = width({0}) height({1}) +panel.DrawExplorer.ShapeDrawable.Unknown = width({0}) height({1}) panel.DrawExplorer.Template.BlastTemplate = Blast ({0}) panel.DrawExplorer.Template.BurstTemplate = Burst ({0}) panel.DrawExplorer.Template.ConeTemplate = Cone ({0}) From b55ccfb9047e5c5a75fe621a8bdb7428934b47ec Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Thu, 17 Oct 2024 18:09:01 -0700 Subject: [PATCH 14/15] Clean up - Remove unused `ToolHelper` methods. - Move measurement rendering into `AbstractDrawingLikeTool` so it isn't duplicated. - Rename `AbstractDrawingTool` to `AbstractTemplateTool` since that is all it does now. Also remove anything that isn't used anymore. --- .../maptool/client/tool/ToolHelper.java | 77 ---- .../tool/drawing/AbstractDrawingLikeTool.java | 38 ++ .../tool/drawing/AbstractDrawingTool.java | 328 ------------------ .../tool/drawing/AbstractTemplateTool.java | 150 ++++++++ .../client/tool/drawing/DrawingTool.java | 36 +- .../client/tool/drawing/ExposeTool.java | 36 +- .../tool/drawing/RadiusCellTemplateTool.java | 4 +- .../tool/drawing/RadiusTemplateTool.java | 4 +- 8 files changed, 194 insertions(+), 479 deletions(-) delete mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java create mode 100644 src/main/java/net/rptools/maptool/client/tool/drawing/AbstractTemplateTool.java diff --git a/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java b/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java index 06422e4b60..532a3cfa56 100644 --- a/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java +++ b/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java @@ -17,11 +17,8 @@ import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; -import java.awt.Shape; import java.awt.event.ActionEvent; -import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; -import java.awt.geom.PathIterator; import java.text.NumberFormat; import javax.swing.AbstractAction; import javax.swing.SwingUtilities; @@ -78,72 +75,6 @@ public static void drawIsoRectangleMeasurement( } } - public static void drawDiamondMeasurement(ZoneRenderer renderer, Graphics2D g, Shape diamond) { - double[] north = null; - double[] west = null; - double[] east = null; - PathIterator path = diamond.getPathIterator(getPaintTransform(renderer)); - while (!path.isDone()) { - double[] coords = new double[2]; - int segType = path.currentSegment(coords); - if (segType != PathIterator.SEG_CLOSE) { - if (north == null) { - north = coords; - } - if (west == null) { - west = coords; - } - if (east == null) { - east = coords; - } - if (coords[1] < north[1]) { - north = coords; - } - if (coords[0] < west[0]) { - west = coords; - } - if (coords[0] > east[0]) { - east = coords; - } - } - path.next(); - } - // Measure - int nx = (int) north[0]; - int ny = (int) north[1]; - int ex = (int) east[0]; - int ey = (int) east[1]; - int wx = (int) west[0]; - int wy = (int) west[1]; - if (g != null) { - g.setColor(Color.white); - g.setStroke(new BasicStroke(3)); - g.drawLine(nx, ny - 20, nx, ny - 10); - g.drawLine(nx, ny - 15, ex, ey - 15); - g.drawLine(ex, ey - 20, ex, ey - 10); - g.drawLine(nx, ny - 15, wx, wy - 15); - g.drawLine(wx, wy - 20, wx, wy - 10); - g.setColor(Color.black); - g.setStroke(new BasicStroke(1)); - g.drawLine(nx, ny - 20, nx, ny - 10); - g.drawLine(nx, ny - 15, ex, ey - 15); - g.drawLine(ex, ey - 20, ex, ey - 10); - g.drawLine(nx, ny - 15, wx, wy - 15); - g.drawLine(wx, wy - 20, wx, wy - 10); - // g.setPaintMode(); - String displayString = - NumberFormat.getInstance() - .format( - isometricDistance(renderer, new ScreenPoint(nx, ny), new ScreenPoint(ex, ey))); - GraphicsUtil.drawBoxedString(g, displayString, nx + 25, ny - 25); - displayString = - NumberFormat.getInstance() - .format( - isometricDistance(renderer, new ScreenPoint(nx, ny), new ScreenPoint(wx, wy))); - GraphicsUtil.drawBoxedString(g, displayString, nx - 25, ny - 25); - } - } - public static void drawBoxedMeasurement( ZoneRenderer renderer, Graphics2D g, ScreenPoint startPoint, ScreenPoint endPoint) { if (!MapTool.getFrame().isPaintDrawingMeasurement()) { @@ -243,17 +174,9 @@ private static double euclideanDistance(ZoneRenderer renderer, ScreenPoint p1, S private static double isometricDistance(ZoneRenderer renderer, ScreenPoint p1, ScreenPoint p2) { double b = p2.y - p1.y; - // return b; return 2 * b * renderer.getZone().getUnitsPerCell() / renderer.getScaledGridSize(); } - protected static AffineTransform getPaintTransform(ZoneRenderer renderer) { - AffineTransform transform = new AffineTransform(); - transform.translate(renderer.getViewOffsetX(), renderer.getViewOffsetY()); - transform.scale(renderer.getScale(), renderer.getScale()); - return transform; - } - protected static AbstractAction getDeleteTokenAction() { return deleteTokenAction; } diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java index afb6f30b69..04632be58f 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java @@ -14,11 +14,14 @@ */ package net.rptools.maptool.client.tool.drawing; +import java.awt.Graphics2D; import java.awt.event.MouseEvent; import net.rptools.maptool.client.ScreenPoint; import net.rptools.maptool.client.swing.SwingUtil; import net.rptools.maptool.client.tool.DefaultTool; +import net.rptools.maptool.client.tool.ToolHelper; import net.rptools.maptool.client.ui.zone.ZoneOverlay; +import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; import net.rptools.maptool.model.ZonePoint; public abstract class AbstractDrawingLikeTool extends DefaultTool implements ZoneOverlay { @@ -60,4 +63,39 @@ protected ZonePoint getPoint(MouseEvent e) { } return zp; } + + /** Draws the shape measurement as part of the overlay. */ + protected void drawMeasurementOverlay( + ZoneRenderer renderer, Graphics2D g, Measurement measurement) { + switch (measurement) { + case null -> {} + case Measurement.Rectangular rectangular -> { + var rectangle = rectangular.bounds(); + ToolHelper.drawBoxedMeasurement( + renderer, + g, + ScreenPoint.fromZonePoint(renderer, rectangle.getX(), rectangle.getY()), + ScreenPoint.fromZonePoint(renderer, rectangle.getMaxX(), rectangle.getMaxY())); + } + case Measurement.LineSegment lineSegment -> { + var p1 = + ScreenPoint.fromZonePoint(renderer, lineSegment.p1().getX(), lineSegment.p1().getY()); + var p2 = + ScreenPoint.fromZonePoint(renderer, lineSegment.p2().getX(), lineSegment.p2().getY()); + ToolHelper.drawMeasurement(renderer, g, p1, p2); + } + case Measurement.IsoRectangular isoRectangular -> { + var north = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.north().getX(), isoRectangular.north().getY()); + var west = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.west().getX(), isoRectangular.west().getY()); + var east = + ScreenPoint.fromZonePoint( + renderer, isoRectangular.east().getX(), isoRectangular.east().getY()); + ToolHelper.drawIsoRectangleMeasurement(renderer, g, north, west, east); + } + } + } } diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java deleted file mode 100644 index 0826542b64..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java +++ /dev/null @@ -1,328 +0,0 @@ -/* - * This software Copyright by the RPTools.net development team, and - * licensed under the Affero GPL Version 3 or, at your option, any later - * version. - * - * MapTool Source Code is distributed in the hope that it will be - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the GNU Affero General Public - * License * along with this source Code. If not, please visit - * and specifically the Affero license - * text at . - */ -package net.rptools.maptool.client.tool.drawing; - -import java.awt.BasicStroke; -import java.awt.Cursor; -import java.awt.Graphics2D; -import java.awt.Polygon; -import java.awt.Rectangle; -import java.awt.Shape; -import java.awt.event.MouseEvent; -import java.awt.geom.AffineTransform; -import java.awt.geom.Area; -import java.awt.geom.Path2D; -import java.util.List; -import net.rptools.maptool.client.AppStyle; -import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.MapToolUtil; -import net.rptools.maptool.client.swing.SwingUtil; -import net.rptools.maptool.client.swing.colorpicker.ColorPicker; -import net.rptools.maptool.client.ui.zone.ZoneOverlay; -import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; -import net.rptools.maptool.model.Token; -import net.rptools.maptool.model.Zone; -import net.rptools.maptool.model.Zone.Layer; -import net.rptools.maptool.model.ZonePoint; -import net.rptools.maptool.model.drawing.Drawable; -import net.rptools.maptool.model.drawing.DrawableColorPaint; -import net.rptools.maptool.model.drawing.Pen; -import net.rptools.maptool.model.drawing.ShapeDrawable; - -/** Tool for drawing freehand lines. */ -public abstract class AbstractDrawingTool extends AbstractDrawingLikeTool implements ZoneOverlay { - - private static final long serialVersionUID = 9121558405484986225L; - - private boolean isSnapToGridSelected; - private boolean isEraseSelected; - - protected Rectangle createRect(ZonePoint originPoint, ZonePoint newPoint) { - int x = Math.min(originPoint.x, newPoint.x); - int y = Math.min(originPoint.y, newPoint.y); - - int w = Math.max(originPoint.x, newPoint.x) - x; - int h = Math.max(originPoint.y, newPoint.y) - y; - - return new Rectangle(x, y, w, h); - } - - protected Shape createDiamond(ZonePoint originPoint, ZonePoint newPoint) { - int ox = originPoint.x; - int oy = originPoint.y; - int nx = newPoint.x; - int ny = newPoint.y; - int x1 = ox - (ny - oy) + ((nx - ox) / 2); - int y1 = ((oy + ny) / 2) - ((nx - ox) / 4); - int x2 = ox + (ny - oy) + ((nx - ox) / 2); - int y2 = ((oy + ny) / 2) + ((nx - ox) / 4); - int x[] = {originPoint.x, x1, nx, x2}; - int y[] = {originPoint.y, y1, ny, y2}; - return new Polygon(x, y, 4); - } - - protected Shape createHollowDiamond(ZonePoint originPoint, ZonePoint newPoint, Pen pen) { - int ox = originPoint.x; - int oy = originPoint.y; - int nx = newPoint.x; - int ny = newPoint.y; - int x1 = ox - (ny - oy) + ((nx - ox) / 2); - int y1 = ((oy + ny) / 2) - ((nx - ox) / 4); - int x2 = ox + (ny - oy) + ((nx - ox) / 2); - int y2 = ((oy + ny) / 2) + ((nx - ox) / 4); - int x[] = {originPoint.x, x1, nx, x2, originPoint.x}; - int y[] = {originPoint.y, y1, ny, y2, originPoint.y}; - - BasicStroke stroke = - new BasicStroke(pen.getThickness(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); - - Path2D path = new Path2D.Double(); - - for (int l = 0; l < 5; l++) { - if (path.getCurrentPoint() == null) { - path.moveTo(x[l], y[l]); - } else { - path.lineTo(x[l], y[l]); - } - } - - Area area = new Area(stroke.createStrokedShape(path)); - return area; - } - - protected AffineTransform getPaintTransform(ZoneRenderer renderer) { - AffineTransform transform = new AffineTransform(); - transform.translate(renderer.getViewOffsetX(), renderer.getViewOffsetY()); - transform.scale(renderer.getScale(), renderer.getScale()); - return transform; - } - - protected void paintTransformed(Graphics2D g, ZoneRenderer renderer, Drawable drawing, Pen pen) { - AffineTransform transform = getPaintTransform(renderer); - AffineTransform oldTransform = g.getTransform(); - g.transform(transform); - drawing.draw(renderer.getZone(), g, pen); - g.setTransform(oldTransform); - } - - @Override - protected void attachTo(ZoneRenderer renderer) { - super.attachTo(renderer); - if (MapTool.getPlayer().isGM()) { - MapTool.getFrame() - .showControlPanel(MapTool.getFrame().getColorPicker(), getLayerSelectionDialog()); - } else { - MapTool.getFrame().showControlPanel(MapTool.getFrame().getColorPicker()); - } - renderer.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); - - MapTool.getFrame().getColorPicker().setSnapSelected(isSnapToGridSelected); - MapTool.getFrame().getColorPicker().setEraseSelected(isEraseSelected); - } - - @Override - protected void detachFrom(ZoneRenderer renderer) { - MapTool.getFrame().removeControlPanel(); - renderer.setCursor(Cursor.getDefaultCursor()); - - isSnapToGridSelected = MapTool.getFrame().getColorPicker().isSnapSelected(); - isEraseSelected = MapTool.getFrame().getColorPicker().isEraseSelected(); - - super.detachFrom(renderer); - } - - protected boolean isBackgroundFill(MouseEvent e) { - boolean defaultValue = MapTool.getFrame().getColorPicker().isFillBackgroundSelected(); - return defaultValue; - } - - protected boolean isEraser(MouseEvent e) { - boolean defaultValue = MapTool.getFrame().getColorPicker().isEraseSelected(); - if (SwingUtil.isShiftDown(e)) { - // Invert from the color panel - defaultValue = !defaultValue; - } - return defaultValue; - } - - protected boolean isSnapToGrid(MouseEvent e) { - boolean defaultValue = MapTool.getFrame().getColorPicker().isSnapSelected(); - if (SwingUtil.isControlDown(e)) { - // Invert from the color panel - defaultValue = !defaultValue; - } - return defaultValue; - } - - protected Pen getPen() { - Pen pen = new Pen(MapTool.getFrame().getPen()); - pen.setEraser(isEraser()); - - ColorPicker picker = MapTool.getFrame().getColorPicker(); - if (picker.isFillForegroundSelected()) { - pen.setForegroundMode(Pen.MODE_SOLID); - } else { - pen.setForegroundMode(Pen.MODE_TRANSPARENT); - } - if (picker.isFillBackgroundSelected()) { - pen.setBackgroundMode(Pen.MODE_SOLID); - } else { - pen.setBackgroundMode(Pen.MODE_TRANSPARENT); - } - pen.setSquareCap(picker.isSquareCapSelected()); - pen.setThickness(picker.getStrokeWidth()); - return pen; - } - - protected Area getTokenTopology(Zone.TopologyType topologyType) { - List topologyTokens = getZone().getTokensWithTopology(topologyType); - - Area tokenTopology = new Area(); - for (Token topologyToken : topologyTokens) { - tokenTopology.add(topologyToken.getTransformedTopology(topologyType)); - } - - return tokenTopology; - } - - @Override - public abstract void paintOverlay(ZoneRenderer renderer, Graphics2D g); - - protected void paintTopologyOverlay(Graphics2D g, Drawable drawable) { - paintTopologyOverlay(g, drawable, Pen.MODE_SOLID); - } - - protected void paintTopologyOverlay(Graphics2D g, Shape shape) { - ShapeDrawable drawable = null; - - if (shape != null) { - drawable = new ShapeDrawable(shape, false); - } - - paintTopologyOverlay(g, drawable, Pen.MODE_SOLID); - } - - protected void paintTopologyOverlay(Graphics2D g, Shape shape, int penMode) { - ShapeDrawable drawable = null; - - if (shape != null) { - drawable = new ShapeDrawable(shape, false); - } - - paintTopologyOverlay(g, drawable, penMode); - } - - protected void paintTopologyOverlay(Graphics2D g) { - Rectangle rectangle = null; - paintTopologyOverlay(g, rectangle, Pen.MODE_SOLID); - } - - protected void paintTopologyOverlay(Graphics2D g, Drawable drawable, int penMode) { - if (MapTool.getPlayer().isGM()) { - Zone zone = renderer.getZone(); - - Graphics2D g2 = (Graphics2D) g.create(); - g2.translate(renderer.getViewOffsetX(), renderer.getViewOffsetY()); - g2.scale(renderer.getScale(), renderer.getScale()); - - g2.setColor(AppStyle.tokenMblColor); - g2.fill(getTokenTopology(Zone.TopologyType.MBL)); - g2.setColor(AppStyle.tokenTopologyColor); - g2.fill(getTokenTopology(Zone.TopologyType.WALL_VBL)); - g2.setColor(AppStyle.tokenHillVblColor); - g2.fill(getTokenTopology(Zone.TopologyType.HILL_VBL)); - g2.setColor(AppStyle.tokenPitVblColor); - g2.fill(getTokenTopology(Zone.TopologyType.PIT_VBL)); - g2.setColor(AppStyle.tokenCoverVblColor); - g2.fill(getTokenTopology(Zone.TopologyType.COVER_VBL)); - - g2.setColor(AppStyle.topologyTerrainColor); - g2.fill(zone.getTopology(Zone.TopologyType.MBL)); - - g2.setColor(AppStyle.topologyColor); - g2.fill(zone.getTopology(Zone.TopologyType.WALL_VBL)); - - g2.setColor(AppStyle.hillVblColor); - g2.fill(zone.getTopology(Zone.TopologyType.HILL_VBL)); - - g2.setColor(AppStyle.pitVblColor); - g2.fill(zone.getTopology(Zone.TopologyType.PIT_VBL)); - - g2.setColor(AppStyle.coverVblColor); - g2.fill(zone.getTopology(Zone.TopologyType.COVER_VBL)); - - g2.dispose(); - } - - if (drawable != null) { - Pen pen = new Pen(); - pen.setEraser(getPen().isEraser()); - pen.setOpacity(AppStyle.topologyRemoveColor.getAlpha() / 255.0f); - pen.setBackgroundMode(penMode); - - if (penMode == Pen.MODE_TRANSPARENT) { - pen.setThickness(3.0f); - } - - if (pen.isEraser()) { - pen.setEraser(false); - } - if (isEraser()) { - pen.setBackgroundPaint(new DrawableColorPaint(AppStyle.topologyRemoveColor)); - } else { - pen.setBackgroundPaint(new DrawableColorPaint(AppStyle.topologyAddColor)); - } - paintTransformed(g, renderer, drawable, pen); - } - } - - /** - * Render a drawable on a zone. This method consolidates all of the calls to the server in one - * place so that it is easier to keep them in sync. - * - * @param pen The pen used to draw. - * @param drawable What is being drawn. - */ - protected void completeDrawable(Pen pen, Drawable drawable) { - var zone = getZone(); - - if (!hasPaint(pen)) { - return; - } - if (drawable.getBounds(zone) == null) { - return; - } - if (MapTool.getPlayer().isGM()) { - drawable.setLayer(getSelectedLayer()); - } else { - drawable.setLayer(Layer.getDefaultPlayerLayer()); - } - - // Send new textures - MapToolUtil.uploadTexture(pen.getPaint()); - MapToolUtil.uploadTexture(pen.getBackgroundPaint()); - - // Tell the local/server to render the drawable. - MapTool.serverCommand().draw(zone.getId(), pen, drawable); - - // Allow it to be undone - zone.addDrawable(pen, drawable); - } - - private boolean hasPaint(Pen pen) { - return pen.getForegroundMode() != Pen.MODE_TRANSPARENT - || pen.getBackgroundMode() != Pen.MODE_TRANSPARENT; - } -} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractTemplateTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractTemplateTool.java new file mode 100644 index 0000000000..f794433fad --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractTemplateTool.java @@ -0,0 +1,150 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.client.tool.drawing; + +import java.awt.Cursor; +import java.awt.Graphics2D; +import java.awt.event.MouseEvent; +import java.awt.geom.AffineTransform; +import net.rptools.maptool.client.MapTool; +import net.rptools.maptool.client.MapToolUtil; +import net.rptools.maptool.client.swing.SwingUtil; +import net.rptools.maptool.client.swing.colorpicker.ColorPicker; +import net.rptools.maptool.client.ui.zone.ZoneOverlay; +import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; +import net.rptools.maptool.model.Zone.Layer; +import net.rptools.maptool.model.drawing.Drawable; +import net.rptools.maptool.model.drawing.Pen; + +/** Base class for tools that draw templates. */ +public abstract class AbstractTemplateTool extends AbstractDrawingLikeTool implements ZoneOverlay { + + private static final long serialVersionUID = 9121558405484986225L; + + private boolean isSnapToGridSelected; + private boolean isEraseSelected; + + protected AffineTransform getPaintTransform(ZoneRenderer renderer) { + AffineTransform transform = new AffineTransform(); + transform.translate(renderer.getViewOffsetX(), renderer.getViewOffsetY()); + transform.scale(renderer.getScale(), renderer.getScale()); + return transform; + } + + @Override + protected void attachTo(ZoneRenderer renderer) { + super.attachTo(renderer); + if (MapTool.getPlayer().isGM()) { + MapTool.getFrame() + .showControlPanel(MapTool.getFrame().getColorPicker(), getLayerSelectionDialog()); + } else { + MapTool.getFrame().showControlPanel(MapTool.getFrame().getColorPicker()); + } + renderer.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); + + MapTool.getFrame().getColorPicker().setSnapSelected(isSnapToGridSelected); + MapTool.getFrame().getColorPicker().setEraseSelected(isEraseSelected); + } + + @Override + protected void detachFrom(ZoneRenderer renderer) { + MapTool.getFrame().removeControlPanel(); + renderer.setCursor(Cursor.getDefaultCursor()); + + isSnapToGridSelected = MapTool.getFrame().getColorPicker().isSnapSelected(); + isEraseSelected = MapTool.getFrame().getColorPicker().isEraseSelected(); + + super.detachFrom(renderer); + } + + protected boolean isEraser(MouseEvent e) { + boolean defaultValue = MapTool.getFrame().getColorPicker().isEraseSelected(); + if (SwingUtil.isShiftDown(e)) { + // Invert from the color panel + defaultValue = !defaultValue; + } + return defaultValue; + } + + protected boolean isSnapToGrid(MouseEvent e) { + boolean defaultValue = MapTool.getFrame().getColorPicker().isSnapSelected(); + if (SwingUtil.isControlDown(e)) { + // Invert from the color panel + defaultValue = !defaultValue; + } + return defaultValue; + } + + protected Pen getPen() { + Pen pen = new Pen(MapTool.getFrame().getPen()); + pen.setEraser(isEraser()); + + ColorPicker picker = MapTool.getFrame().getColorPicker(); + if (picker.isFillForegroundSelected()) { + pen.setForegroundMode(Pen.MODE_SOLID); + } else { + pen.setForegroundMode(Pen.MODE_TRANSPARENT); + } + if (picker.isFillBackgroundSelected()) { + pen.setBackgroundMode(Pen.MODE_SOLID); + } else { + pen.setBackgroundMode(Pen.MODE_TRANSPARENT); + } + pen.setSquareCap(picker.isSquareCapSelected()); + pen.setThickness(picker.getStrokeWidth()); + return pen; + } + + @Override + public abstract void paintOverlay(ZoneRenderer renderer, Graphics2D g); + + /** + * Render a drawable on a zone. This method consolidates all of the calls to the server in one + * place so that it is easier to keep them in sync. + * + * @param pen The pen used to draw. + * @param drawable What is being drawn. + */ + protected void completeDrawable(Pen pen, Drawable drawable) { + var zone = getZone(); + + if (!hasPaint(pen)) { + return; + } + if (drawable.getBounds(zone) == null) { + return; + } + if (MapTool.getPlayer().isGM()) { + drawable.setLayer(getSelectedLayer()); + } else { + drawable.setLayer(Layer.getDefaultPlayerLayer()); + } + + // Send new textures + MapToolUtil.uploadTexture(pen.getPaint()); + MapToolUtil.uploadTexture(pen.getBackgroundPaint()); + + // Tell the local/server to render the drawable. + MapTool.serverCommand().draw(zone.getId(), pen, drawable); + + // Allow it to be undone + zone.addDrawable(pen, drawable); + } + + private boolean hasPaint(Pen pen) { + return pen.getForegroundMode() != Pen.MODE_TRANSPARENT + || pen.getBackgroundMode() != Pen.MODE_TRANSPARENT; + } +} diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java index 9232325049..2a74a42dd2 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java @@ -25,9 +25,7 @@ import javax.swing.SwingUtilities; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.MapToolUtil; -import net.rptools.maptool.client.ScreenPoint; import net.rptools.maptool.client.swing.colorpicker.ColorPicker; -import net.rptools.maptool.client.tool.ToolHelper; import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; import net.rptools.maptool.model.Zone; import net.rptools.maptool.model.ZonePoint; @@ -226,39 +224,7 @@ public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { drawable.draw(renderer.getZone(), g2, pen); // Measurements - var measurement = result.measurement(); - switch (measurement) { - case null -> {} - case Measurement.Rectangular rectangular -> { - var rectangle = rectangular.bounds(); - ToolHelper.drawBoxedMeasurement( - renderer, - g, - ScreenPoint.fromZonePoint(renderer, rectangle.getX(), rectangle.getY()), - ScreenPoint.fromZonePoint(renderer, rectangle.getMaxX(), rectangle.getMaxY())); - } - case Measurement.LineSegment lineSegment -> { - var p1 = - ScreenPoint.fromZonePoint( - renderer, lineSegment.p1().getX(), lineSegment.p1().getY()); - var p2 = - ScreenPoint.fromZonePoint( - renderer, lineSegment.p2().getX(), lineSegment.p2().getY()); - ToolHelper.drawMeasurement(renderer, g, p1, p2); - } - case Measurement.IsoRectangular isoRectangular -> { - var north = - ScreenPoint.fromZonePoint( - renderer, isoRectangular.north().getX(), isoRectangular.north().getY()); - var west = - ScreenPoint.fromZonePoint( - renderer, isoRectangular.west().getX(), isoRectangular.west().getY()); - var east = - ScreenPoint.fromZonePoint( - renderer, isoRectangular.east().getX(), isoRectangular.east().getY()); - ToolHelper.drawIsoRectangleMeasurement(renderer, g, north, west, east); - } - } + drawMeasurementOverlay(renderer, g, result.measurement()); } } diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java index c74c30643f..5d3b1876cc 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java @@ -24,8 +24,6 @@ import javax.annotation.Nullable; import javax.swing.SwingUtilities; import net.rptools.maptool.client.MapTool; -import net.rptools.maptool.client.ScreenPoint; -import net.rptools.maptool.client.tool.ToolHelper; import net.rptools.maptool.client.ui.zone.renderer.ZoneRenderer; import net.rptools.maptool.model.GUID; import net.rptools.maptool.model.Zone; @@ -130,39 +128,7 @@ public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { g2.draw(result.shape()); // Measurements - var measurement = result.measurement(); - switch (measurement) { - case null -> {} - case Measurement.Rectangular rectangular -> { - var rectangle = rectangular.bounds(); - ToolHelper.drawBoxedMeasurement( - renderer, - g, - ScreenPoint.fromZonePoint(renderer, rectangle.getX(), rectangle.getY()), - ScreenPoint.fromZonePoint(renderer, rectangle.getMaxX(), rectangle.getMaxY())); - } - case Measurement.LineSegment lineSegment -> { - var p1 = - ScreenPoint.fromZonePoint( - renderer, lineSegment.p1().getX(), lineSegment.p1().getY()); - var p2 = - ScreenPoint.fromZonePoint( - renderer, lineSegment.p2().getX(), lineSegment.p2().getY()); - ToolHelper.drawMeasurement(renderer, g, p1, p2); - } - case Measurement.IsoRectangular isoRectangular -> { - var north = - ScreenPoint.fromZonePoint( - renderer, isoRectangular.north().getX(), isoRectangular.north().getY()); - var west = - ScreenPoint.fromZonePoint( - renderer, isoRectangular.west().getX(), isoRectangular.west().getY()); - var east = - ScreenPoint.fromZonePoint( - renderer, isoRectangular.east().getX(), isoRectangular.east().getY()); - ToolHelper.drawIsoRectangleMeasurement(renderer, g, north, west, east); - } - } + drawMeasurementOverlay(renderer, g, result.measurement()); } } diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusCellTemplateTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusCellTemplateTool.java index 1a0036ef85..3c58104802 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusCellTemplateTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusCellTemplateTool.java @@ -42,7 +42,7 @@ * * @author naciron */ -public class RadiusCellTemplateTool extends AbstractDrawingTool implements MouseMotionListener { +public class RadiusCellTemplateTool extends AbstractTemplateTool implements MouseMotionListener { /*--------------------------------------------------------------------------------------------- * Instance Variables *-------------------------------------------------------------------------------------------*/ @@ -337,7 +337,7 @@ protected void resetTool() { /** * It is OK to modify the pen returned by this method * - * @see net.rptools.maptool.client.tool.drawing.AbstractDrawingTool#getPen() + * @see AbstractTemplateTool#getPen() */ @Override protected Pen getPen() { diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusTemplateTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusTemplateTool.java index a5c84ff856..9683bee0b3 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusTemplateTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/RadiusTemplateTool.java @@ -44,7 +44,7 @@ * @version $Revision: 5945 $ $Date: 2013-06-03 04:35:50 +0930 (Mon, 03 Jun 2013) $ $Author: * azhrei_fje $ */ -public class RadiusTemplateTool extends AbstractDrawingTool implements MouseMotionListener { +public class RadiusTemplateTool extends AbstractTemplateTool implements MouseMotionListener { /*--------------------------------------------------------------------------------------------- * Instance Variables *-------------------------------------------------------------------------------------------*/ @@ -340,7 +340,7 @@ protected void resetTool() { /** * It is OK to modify the pen returned by this method * - * @see net.rptools.maptool.client.tool.drawing.AbstractDrawingTool#getPen() + * @see AbstractTemplateTool#getPen() */ @Override protected Pen getPen() { From b5362ebcf4e1d671ace7a5cdf1fa8c0df99b3b30 Mon Sep 17 00:00:00 2001 From: Kenneth VanderLinde Date: Fri, 1 Nov 2024 10:31:07 -0700 Subject: [PATCH 15/15] Fix erase flag when DungeonDraftImporter adds topology to the zone --- .../client/utilities/DungeonDraftImporter.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/rptools/maptool/client/utilities/DungeonDraftImporter.java b/src/main/java/net/rptools/maptool/client/utilities/DungeonDraftImporter.java index 6dbf648826..761efb6fc9 100644 --- a/src/main/java/net/rptools/maptool/client/utilities/DungeonDraftImporter.java +++ b/src/main/java/net/rptools/maptool/client/utilities/DungeonDraftImporter.java @@ -203,8 +203,8 @@ public void importVTT() throws IOException { WALL_VBL_STROKE.createStrokedShape( getVBLPath(v.getAsJsonArray(), pixelsPerCell))); if (finalDo_transform) vblArea.transform(at); - zone.updateTopology(vblArea, true, Zone.TopologyType.WALL_VBL); - zone.updateTopology(vblArea, true, Zone.TopologyType.MBL); + zone.updateTopology(vblArea, false, Zone.TopologyType.WALL_VBL); + zone.updateTopology(vblArea, false, Zone.TopologyType.MBL); }); } @@ -215,8 +215,8 @@ public void importVTT() throws IOException { v -> { Area vblArea = new Area(getVBLPath(v.getAsJsonArray(), pixelsPerCell)); if (finalDo_transform) vblArea.transform(at); - zone.updateTopology(vblArea, true, Zone.TopologyType.HILL_VBL); - zone.updateTopology(vblArea, true, Zone.TopologyType.PIT_VBL); + zone.updateTopology(vblArea, false, Zone.TopologyType.HILL_VBL); + zone.updateTopology(vblArea, false, Zone.TopologyType.PIT_VBL); }); } @@ -239,8 +239,8 @@ public void importVTT() throws IOException { Area vblArea = new Area(DOOR_VBL_STROKE.createStrokedShape(getVBLPath(bounds, pixelsPerCell))); if (finalDo_transform) vblArea.transform(at); - zone.updateTopology(vblArea, true, Zone.TopologyType.WALL_VBL); - zone.updateTopology(vblArea, true, Zone.TopologyType.MBL); + zone.updateTopology(vblArea, false, Zone.TopologyType.WALL_VBL); + zone.updateTopology(vblArea, false, Zone.TopologyType.MBL); } }); }