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/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/ToolHelper.java b/src/main/java/net/rptools/maptool/client/tool/ToolHelper.java index 2bf739db6d..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,10 +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.PathIterator; +import java.awt.geom.Line2D; import java.text.NumberFormat; import javax.swing.AbstractAction; import javax.swing.SwingUtilities; @@ -49,69 +47,31 @@ public void actionPerformed(ActionEvent e) { } }; - 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]; + 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.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.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)); - 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(); + // 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, 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); + 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)); } } @@ -214,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/Toolbox.java b/src/main/java/net/rptools/maptool/client/tool/Toolbox.java index 9ef65a73b8..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,23 +44,20 @@ 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); - - 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/tool/drawing/AbstractDrawingLikeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java new file mode 100644 index 0000000000..04632be58f --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingLikeTool.java @@ -0,0 +1,101 @@ +/* + * 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 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 { + 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; + } + + /** 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 c98aab2875..0000000000 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/AbstractDrawingTool.java +++ /dev/null @@ -1,360 +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.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; -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 DefaultTool implements ZoneOverlay { - - private static final long serialVersionUID = 9121558405484986225L; - - private boolean isEraser; - 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 void setIsEraser(boolean eraser) { - isEraser = eraser; - } - - protected boolean isEraser() { - return isEraser; - } - - 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 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); - - 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 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); - - 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/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/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/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/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/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/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/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/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/DrawingTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java new file mode 100644 index 0000000000..2a74a42dd2 --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingTool.java @@ -0,0 +1,300 @@ +/* + * 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.swing.colorpicker.ColorPicker; +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 + drawMeasurementOverlay(renderer, g, result.measurement()); + } + } + + 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/ExposeTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java new file mode 100644 index 0000000000..5d3b1876cc --- /dev/null +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/ExposeTool.java @@ -0,0 +1,204 @@ +/* + * 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.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 + drawMeasurementOverlay(renderer, g, result.measurement()); + } + } + + 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/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/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/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/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/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/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/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/OvalTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/OvalTool.java deleted file mode 100644 index e99f99a48e..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.Float(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.Float(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/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/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/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/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/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/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/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() { 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/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/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/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/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/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/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); 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..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; } @@ -322,52 +343,116 @@ 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; } 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. @@ -469,9 +554,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 +565,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() { 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..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; @@ -292,7 +293,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(); @@ -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/client/ui/zone/renderer/ZoneRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java index 0bf71db39a..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; @@ -654,10 +651,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) { @@ -2721,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); } } 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/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/Oval.java b/src/main/java/net/rptools/maptool/model/drawing/Oval.java index 9123ed0c9f..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,92 +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()) - .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 fede411fa6..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; @@ -44,10 +46,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; @@ -66,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/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/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) { diff --git a/src/main/proto/drawing_dto.proto b/src/main/proto/drawing_dto.proto index c14353a6a5..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; @@ -195,10 +167,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 +180,10 @@ message AreaDto { repeated SegmentDto segments = 2; } +message AreaDto { + PathShapeDto path = 1; +} + message SegmentDto { oneof segment_type { MoveToSegment move_to = 1; @@ -262,10 +239,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 { 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})