diff --git a/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingPointerTool.java b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingPointerTool.java index 2b7dc76ca5..b53c541e54 100644 --- a/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingPointerTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/drawing/DrawingPointerTool.java @@ -23,6 +23,7 @@ import java.awt.event.MouseListener; import java.awt.geom.AffineTransform; import java.awt.geom.Area; +import java.awt.geom.RectangularShape; import java.io.Serial; import java.util.*; import java.util.List; @@ -74,16 +75,12 @@ * * - * - *
Once selected (templates only):
- * - * * *
Once selected (single template only):
@@ -119,6 +116,12 @@ public class DrawingPointerTool extends DefaultTool implements ZoneOverlay, Mous */ private static final Set draggedDrawnElementSet = new HashSet<>(); + /** + * Stores pre-drag bounds of {@link Drawable}s. Used for snapping dragged drawings to grid based + * on their original position. + */ + private static final Map draggedStartBoundsMap = new HashMap<>(); + /** * Factory to generate {@link FlatImageLabel}s for drawing/template name labels. Will be assigned * prior to each use as user color preferences may change. @@ -140,7 +143,7 @@ public class DrawingPointerTool extends DefaultTool implements ZoneOverlay, Mous private static final DrawPanelPopupMenu.DuplicateDrawingAction duplicateAction = new DrawPanelPopupMenu.DuplicateDrawingAction(selectedDrawableIdSet); - private static final Logger LOGGER = LogManager.getLogger(DrawingPointerTool.class); + private static final Logger log = LogManager.getLogger(DrawingPointerTool.class); /** Color picker related variables */ private boolean isSnapToGridSelected; @@ -166,6 +169,7 @@ public class DrawingPointerTool extends DefaultTool implements ZoneOverlay, Mous private ZonePoint dragStartVertex = null; private ZonePoint dragWorkingCell = null; + private ZonePoint dragWorkingZonePoint = null; /** An enumeration of template cursor types. */ private enum templateCursorType { @@ -250,7 +254,7 @@ protected void installKeystrokes(Map actionMap) { } /** - * Either drag drawings, or draw a draggable selection box. + * Either drag templates, drag drawings, or draw a draggable selection box. * * @param e the event to be processed */ @@ -258,12 +262,10 @@ protected void installKeystrokes(Map actionMap) { public void mouseDragged(MouseEvent e) { super.mouseDragged(e); - if (isDraggingDrawings) { + if (isDraggingDrawings && selectedTool.equals(TemplatePointerTool.class)) { ZonePoint dragTargetCell = getCellAtMouse(e); if (!dragWorkingCell.equals(dragTargetCell)) { - for (DrawnElement de : draggedDrawnElementSet) { - // Currently drag templates only Drawable d = de.getDrawable(); if (d instanceof AbstractTemplate at) { updateDraggedDrawnElements(e, at); @@ -272,6 +274,26 @@ public void mouseDragged(MouseEvent e) { dragWorkingCell = dragTargetCell; renderer.repaint(); } + + } else if (isDraggingDrawings && selectedTool.equals(DrawingPointerTool.class)) { + ZonePoint dragTargetZonePoint = + new ScreenPoint(e.getX(), e.getY()).convertToZone(renderer.getViewModel().getZoneScale()); + if (e.isControlDown()) { + dragTargetZonePoint = renderer.getZone().getGrid().getNearestVertex(dragTargetZonePoint); + } + for (DrawnElement de : draggedDrawnElementSet) { + Drawable d = de.getDrawable(); + if (d instanceof DrawablesGroup dg) { + updateDraggedDrawnElements(e, dg); + } else if (d instanceof ShapeDrawable sd) { + updateDraggedDrawnElements(e, sd); + } else if (d instanceof LineSegment ls) { + updateDraggedDrawnElements(e, ls); + } + } + dragWorkingZonePoint = dragTargetZonePoint; + renderer.repaint(); + } else if (isDraggingSelectionBox) { int x1 = dragStartPoint.x; int y1 = dragStartPoint.y; @@ -452,25 +474,24 @@ public void mouseReleased(MouseEvent e) { for (DrawnElement de : drawableList) { Drawable d = de.getDrawable(); - // Only select drawing types relevant to the tool - // Object selectedTool = MapTool.getFrame().getToolbox().getSelectedTool().getClass(); + // Only select drawn element types relevant to the tool boolean isTemplate = isTemplate(de); if (selectedTool == TemplatePointerTool.class && isTemplate || selectedTool == DrawingPointerTool.class && !isTemplate) { GUID id = d.getId(); - // Check if the template bounds is within the bounds of the selection box + // Check if the drawable bounds is within the bounds of the selection box if (zoneTemplateSelectionBox.contains(d.getBounds(zone))) { boolean isControlCheck = true; boolean isAltCheck = true; - // CTRL key - check if the template border color matches the color picker + // CTRL key - check if the border color matches the color picker if (e.isControlDown()) { isControlCheck = drawablePaintToString(de.getPen().getPaint()) .equals(drawablePaintToString(getPen().getPaint())); } - // ALT key - check if the template fill color matches the color picker + // ALT key - check if the fill color matches the color picker if (e.isAltDown()) { isAltCheck = drawablePaintToString(de.getPen().getBackgroundPaint()) @@ -609,17 +630,20 @@ public void paintOverlay(ZoneRenderer renderer, Graphics2D g) { */ private void dragDrawnElementsStart(MouseEvent e) { - if (isTemplate(drawnElementAtMouse)) { - renderer.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); - } + renderer.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); isDraggingDrawings = true; isDraggingSelectionBox = false; dragStartPoint = new Point(e.getX(), e.getY()); dragWorkingCell = getCellAtMouse(e); + dragWorkingZonePoint = + new ScreenPoint(e.getX(), e.getY()).convertToZone(renderer.getViewModel().getZoneScale()); if (drawnElementAtMouse.getDrawable() instanceof AbstractTemplate at) { dragStartVertex = new ZonePoint(at.getVertex()); + } else { + dragStartVertex = renderer.getZone().getGrid().getNearestVertex(dragWorkingZonePoint); } + setDraggedDrawnElementsSet(); } @@ -851,8 +875,11 @@ void onZoneActivated(ZoneActivated event) { } /** - * Paints the drawings which are being changed via dragging. This could be a change in position, - * path, radius, direction, etc. + * Paints the drawables which are being changed via dragging. + * + *

For templates this could be a change in position, path, radius, direction, etc. + * + *

For drawings this is just a change in position * * @param g where to paint. */ @@ -862,8 +889,9 @@ private void paintDraggedDrawings(Graphics2D g) { for (DrawnElement de : draggedDrawnElementSet) { if (de != null) { - // Templates only if (de.getDrawable() instanceof AbstractTemplate at) { + // i.e. templates only + Pen pen = de.getPen(); AffineTransform oldTransform = g.getTransform(); AffineTransform newTransform = g.getTransform(); @@ -890,6 +918,36 @@ private void paintDraggedDrawings(Graphics2D g) { if (drawnElementAtMouse.getDrawable().getId() == at.getId()) { paintTemplateMovementLabel(g, dragStartVertex, at); } + + } else if (de.getDrawable() instanceof LineSegment ls) { + // e.g. points and lines + + AffineTransform oldTransform = g.getTransform(); + AffineTransform newTransform = g.getTransform(); + newTransform.concatenate(getPaintTransform(renderer)); + g.setTransform(newTransform); + ls.draw(getZone(), g, de.getPen()); + g.setTransform(oldTransform); + + } else if (de.getDrawable() instanceof ShapeDrawable sd) { + // e.g. rectangles, ellipses, etc + + AffineTransform oldTransform = g.getTransform(); + AffineTransform newTransform = g.getTransform(); + newTransform.concatenate(getPaintTransform(renderer)); + g.setTransform(newTransform); + sd.draw(getZone(), g, de.getPen()); + g.setTransform(oldTransform); + + } else if (de.getDrawable() instanceof DrawablesGroup dg) { + // groups of drawables (inc. other groups) + + AffineTransform oldTransform = g.getTransform(); + AffineTransform newTransform = g.getTransform(); + newTransform.concatenate(getPaintTransform(renderer)); + g.setTransform(newTransform); + dg.draw(getZone(), g, de.getPen()); + g.setTransform(oldTransform); } } } // end for @@ -1134,9 +1192,12 @@ private void paintTemplateRadiusLabel(Graphics2D g, ZonePoint zp, AbstractTempla } /** - * Creates a copy of the {@link DrawnElement}s being dragged. This {@link Set} is used for - * displaying the dragged drawings. e.g. for templates this could be either a change in position, - * path, direction, and/or size. + * Creates a copy of the {@link DrawnElement}s being dragged and stores their pre-drag bounds. + * + *

The {@link Set} is used for displaying the dragged drawings. e.g. for templates this could + * be either a change in position, path, direction, and/or size. + * + *

The {@link Map} is used when snapping drawings to grid whilst dragging. * *

In the event of the Escape key being pressed while dragging, any changes to * dragged drawings will not be applied to the original drawings. @@ -1145,20 +1206,41 @@ private void setDraggedDrawnElementsSet() { List drawableList = getDrawnElementsOnLayerList(false); draggedDrawnElementSet.clear(); + draggedStartBoundsMap.clear(); if (!selectedDrawableIdSet.isEmpty()) { for (DrawnElement de : drawableList) { Drawable d = de.getDrawable(); GUID id = d.getId(); if (selectedDrawableIdSet.contains(id)) { - if (d instanceof AbstractTemplate) { - DrawnElement deCopy = new DrawnElement(de); - draggedDrawnElementSet.add(deCopy); + DrawnElement deCopy = new DrawnElement(de); + draggedDrawnElementSet.add(deCopy); + draggedStartBoundsMap.put(id, de.getDrawable().getBounds(getZone())); + if (d instanceof DrawablesGroup dg) { + setGroupDraggedStartBoundsMap(dg); } } } } } + /** + * Get the bounds of the drawables group contents, recursively if there are child groups. + * + * @param dg the drawables group + */ + private void setGroupDraggedStartBoundsMap(DrawablesGroup dg) { + + for (DrawnElement de : dg.getDrawableList()) { + if (de.getDrawable() instanceof DrawablesGroup dg2) { + setGroupDraggedStartBoundsMap(dg2); + } else if (de.getDrawable() instanceof LineSegment ls) { + draggedStartBoundsMap.put(ls.getId(), ls.getBounds(getZone())); + } else if (de.getDrawable() instanceof ShapeDrawable sd) { + draggedStartBoundsMap.put(sd.getId(), sd.getBounds(getZone())); + } + } + } + /** * Helper method that sets the path vertex for the template (for known template types that have * them), but prevents setting it where it would equal the vertex. @@ -1198,6 +1280,96 @@ private void updateDrawablesPanel() { } } + private void updateDraggedDrawnElements(MouseEvent e, DrawablesGroup dg) { + + if (!MapTool.getPlayer().isGM() && MapTool.getServerPolicy().isMovementLocked()) { + // i.e. not allowed + return; + } + + for (DrawnElement de : dg.getDrawableList()) { + if (de.getDrawable() instanceof DrawablesGroup dg2) { + updateDraggedDrawnElements(e, dg2); + } else if (de.getDrawable() instanceof LineSegment ls) { + updateDraggedDrawnElements(e, ls); + } else if (de.getDrawable() instanceof ShapeDrawable sd) { + updateDraggedDrawnElements(e, sd); + } + } + } + + private void updateDraggedDrawnElements(MouseEvent e, LineSegment ls) { + + if (!MapTool.getPlayer().isGM() && MapTool.getServerPolicy().isMovementLocked()) { + // i.e. not allowed + return; + } + + ZonePoint dragPointOffset = + new ScreenPoint(e.getX(), e.getY()).convertToZone(renderer.getViewModel().getZoneScale()); + + if (e.isControlDown()) { + // Snap to grid based on the drawing's original (i.e. pre-dragged) position. + ZonePoint dragNearestVertex = renderer.getZone().getGrid().getNearestVertex(dragPointOffset); + dragPointOffset.x = + dragStartVertex.x + - draggedStartBoundsMap.get(ls.getId()).getBounds().x + - dragNearestVertex.x + + ls.getBounds(getZone()).x; + dragPointOffset.y = + dragStartVertex.y + - draggedStartBoundsMap.get(ls.getId()).getBounds().y + - dragNearestVertex.y + + ls.getBounds(getZone()).y; + } else { + // Not snapping to grid + dragPointOffset.x = dragWorkingZonePoint.x - dragPointOffset.x; + dragPointOffset.y = dragWorkingZonePoint.y - dragPointOffset.y; + } + + ls.translate(-dragPointOffset.x, -dragPointOffset.y); + } + + private void updateDraggedDrawnElements(MouseEvent e, ShapeDrawable sd) { + + if (!MapTool.getPlayer().isGM() && MapTool.getServerPolicy().isMovementLocked()) { + // i.e. not allowed + return; + } + + ZonePoint dragPointOffset = + new ScreenPoint(e.getX(), e.getY()).convertToZone(renderer.getViewModel().getZoneScale()); + + if (e.isControlDown()) { + // Snap to grid based on the drawing's original (i.e. pre-dragged) position. + ZonePoint dragNearestVertex = renderer.getZone().getGrid().getNearestVertex(dragPointOffset); + dragPointOffset.x = + dragStartVertex.x + - draggedStartBoundsMap.get(sd.getId()).getBounds().x + - dragNearestVertex.x + + sd.getBounds().x; + dragPointOffset.y = + dragStartVertex.y + - draggedStartBoundsMap.get(sd.getId()).getBounds().y + - dragNearestVertex.y + + sd.getBounds().y; + } else { + // Not snapping to grid + dragPointOffset.x = dragWorkingZonePoint.x - dragPointOffset.x; + dragPointOffset.y = dragWorkingZonePoint.y - dragPointOffset.y; + } + + if (sd.getShape() instanceof RectangularShape rs) { + rs.setFrame( + rs.getBounds().x - dragPointOffset.x, + rs.getBounds().y - dragPointOffset.y, + rs.getWidth(), + rs.getHeight()); + } else if (sd.getShape() instanceof Polygon p) { + p.translate(-dragPointOffset.x, -dragPointOffset.y); + } + } + /** * Handles moving the template by moving the vertex and (if relevant) the pathVertex. * @@ -1289,30 +1461,11 @@ private void updateOriginalDrawnElements() { for (DrawnElement deDragged : draggedDrawnElementSet) { GUID id = deDragged.getDrawable().getId(); if (id == deOriginal.getDrawable().getId()) { - if (deOriginal.getDrawable() instanceof AbstractTemplate atOriginal - && deDragged.getDrawable() instanceof AbstractTemplate atDragged) { - - // Update the radius and vertex (all templates have these) - atOriginal.setVertex(atDragged.getVertex()); - atOriginal.setRadius(atDragged.getRadius()); - - // Update the path vertex (if applicable to the template type) - setTemplatePathVertex(atOriginal, getTemplatePathVertex(atDragged)); - - // Update other special things (applicable to specific template types) - String templateType = getTemplateType(atOriginal); - if (templateType.equals("BlastTemplate")) { - int OffsetX = ((BlastTemplate) atDragged).getOffsetX(); - int OffsetY = ((BlastTemplate) atDragged).getOffsetY(); - ((BlastTemplate) atOriginal).setControlCellOffset(OffsetX, OffsetY); - } else if (templateType.equals("ConeTemplate")) { - ((ConeTemplate) atOriginal).setDirection(((ConeTemplate) atDragged).getDirection()); - } + deOriginal.setDrawable(deDragged.getDrawable()); - // Server drawing update - MapTool.serverCommand().updateDrawing(zone.getId(), deOriginal.getPen(), deOriginal); - renderer.getZone().updateDrawable(deOriginal, deOriginal.getPen()); - } + // Server drawing update + MapTool.serverCommand().updateDrawing(zone.getId(), deOriginal.getPen(), deOriginal); + renderer.getZone().updateDrawable(deOriginal, deOriginal.getPen()); } } // end for } // end for 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 dd6fee0e50..e36125fae9 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 @@ -92,9 +92,7 @@ public DrawPanelPopupMenu( add(new SetPropertiesAction()); add(new SetDrawingName()); add(new GetDrawingId()); - if (isDrawnElementTemplate(elementUnderMouse)) { - add(new DuplicateDrawingAction(selectedDrawSet)); - } + add(new DuplicateDrawingAction(selectedDrawSet)); addGMItem(new JSeparator()); add(createPathVblMenu()); add(createShapeVblMenu()); @@ -178,11 +176,7 @@ public void actionPerformed(ActionEvent e) { } } - /** - * Duplicates selected drawings... - * - *

... but currently limited to templates only as we can drag and manipulate those - */ + /** Duplicates selected drawings... */ public static class DuplicateDrawingAction extends AbstractAction { public DuplicateDrawingAction() { @@ -208,18 +202,15 @@ public void actionPerformed(ActionEvent e) { return; } - // check to see if this is the required action for (GUID id : selectedDrawings) { DrawnElement de = renderer.getZone().getDrawnElement(id); Drawable d = de.getDrawable(); - if (de.getDrawable() instanceof AbstractTemplate) { - AbstractTemplate at = (AbstractTemplate) d.copy(); - at.setId(new GUID()); - // Draw it - MapTool.serverCommand().draw(renderer.getZone().getId(), de.getPen(), at); - // Allow it to be undone - renderer.getZone().addDrawable(de.getPen(), at); - } + AbstractDrawing ad = (AbstractDrawing) d.copy(); + ad.setId(new GUID()); + // Draw it + MapTool.serverCommand().draw(renderer.getZone().getId(), de.getPen(), ad); + // Allow it to be undone + renderer.getZone().addDrawable(de.getPen(), ad); } renderer.repaint(); MapTool.getFrame().updateDrawTree(); diff --git a/src/main/java/net/rptools/maptool/model/drawing/LineSegment.java b/src/main/java/net/rptools/maptool/model/drawing/LineSegment.java index 7b01a4c000..18e615e63d 100644 --- a/src/main/java/net/rptools/maptool/model/drawing/LineSegment.java +++ b/src/main/java/net/rptools/maptool/model/drawing/LineSegment.java @@ -130,6 +130,19 @@ public List getPoints() { return Collections.unmodifiableList(points); } + /** + * Translate the line segment + * + * @param deltaX offset in the X axis + * @param deltaY offset in the Y axis + */ + public void translate(int deltaX, int deltaY) { + points.replaceAll(point1 -> new Point(point1.x + deltaX, point1.y + deltaY)); + if (cachedBounds != null) { + cachedBounds.translate(deltaX, deltaY); + } + } + @Override public @Nonnull Area getArea(Zone zone) { if (area == null) {