diff --git a/CHANGELOG.md b/CHANGELOG.md index 605f2a782..fb779ab79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ All notable changes to this project will be documented in this file. - [#2370] Objects display - Option to show horizontal and vertical rulers - [#2370] Objects display - Create guides by dragging from a ruler - Objects dragging - show touch point and snap it to 9 important points around object rectangle -- [#2370] Snap to guides +- [#2370] Snap to guides, objects and pixels ### Fixed - [#2424] DefineEditText handling of letterSpacing, font size on incorrect values diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java index a1177562c..6ab3ebacb 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java @@ -1057,6 +1057,18 @@ public final class Configuration { @ConfigurationCategory("display") public static ConfigurationItem showRuler = null; + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("display") + public static ConfigurationItem snapToGuides = null; + + @ConfigurationDefaultBoolean(true) + @ConfigurationCategory("display") + public static ConfigurationItem snapToObjects = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("display") + public static ConfigurationItem snapToPixels = null; + private enum OSId { WINDOWS, OSX, UNIX diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index 7a88c0aa4..41a1829ee 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -295,8 +295,6 @@ public final class ImagePanel extends JPanel implements MediaDisplay { private DepthState depthStateUnderCursor = null; - private List allDepthStatesUnderCursor = null; - private List placeObjectSelectedListeners = new ArrayList<>(); private Point[] hilightedEdge = null; @@ -308,12 +306,9 @@ public final class ImagePanel extends JPanel implements MediaDisplay { private static final int TOUCH_POINT_DISTANCE = 15; private DisplayPoint snapOffset = new DisplayPoint(0, 0); - + private static final int SNAP_DISTANCE = 10; - //TODO: Move this to config - private static final boolean SNAP_TO_GUIDES = true; - //private DisplayPoint closestPoint = null; private List pointsUnderCursor = new ArrayList<>(); private List selectedPoints = new ArrayList<>(); @@ -1134,11 +1129,10 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } if (touchPointOffset != null) { - double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value; boolean snapped = snapOffset.x != 0 || snapOffset.y != 0; - g2.setStroke(new BasicStroke((float) ((snapped ? 2 : 1) / zoomDouble))); + g2.setStroke(new BasicStroke((float) ((snapped ? 2 : 1)))); DisplayPoint p = new DisplayPoint(lastMouseEvent.getX() + touchPointOffset.x + snapOffset.x, lastMouseEvent.getY() + touchPointOffset.y + snapOffset.y); - double pointSize = (snapped ? 4 : 3) / zoomDouble; + double pointSize = (snapped ? 4 : 3); Shape pointShape = new Ellipse2D.Double(p.x - pointSize, p.y - pointSize, pointSize * 2, pointSize * 2); g2.setPaint(Color.black); g2.draw(pointShape); @@ -1437,14 +1431,11 @@ public final class ImagePanel extends JPanel implements MediaDisplay { selectedPointsOriginalValues = newPointsUnderCursorValues; - if (selectionMode && depthStateUnderCursor != null && selectedDepths.contains(depthStateUnderCursor.depth) && !allDepthStatesUnderCursor.isEmpty()) { + if (selectionMode && depthStateUnderCursor != null && selectedDepths.contains(depthStateUnderCursor.depth)) { Matrix matrix = new Matrix(); - for (DepthState ds : allDepthStatesUnderCursor) { - if (ds.matrix != null) { - matrix = matrix.preConcatenate(new Matrix(ds.matrix)); - } + if (depthStateUnderCursor.matrix != null) { + matrix = matrix.preConcatenate(new Matrix(depthStateUnderCursor.matrix)); } - Matrix scaleMatrix = Matrix.getScaleInstance(getRealZoom() / SWF.unitDivisor); matrix = matrix.preConcatenate(scaleMatrix); @@ -1452,8 +1443,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { Point2D cursorPos = new Point2D.Double(e.getX(), e.getY()); - DepthState lastDs = allDepthStatesUnderCursor.get(0); - CharacterTag ch = lastDs.getCharacter(); + CharacterTag ch = depthStateUnderCursor.getCharacter(); if (ch != null) { if (ch instanceof BoundedTag) { BoundedTag bt = (BoundedTag) ch; @@ -1804,50 +1794,121 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } //snapping - if (dragStart != null && selectionMode && !doFreeTransform) { + if (dragStart != null && (selectionMode || (doFreeTransform && mode == Cursor.MOVE_CURSOR))) { Point2D touchPointPos = new Point2D.Double(e.getX(), e.getY()); if (touchPointOffset != null) { touchPointPos = new Point2D.Double(e.getX() + touchPointOffset.x, e.getY() + touchPointOffset.y); } - int snapOffsetX = 0; - int snapOffsetY = 0; + Integer snapOffsetX = null; + Integer snapOffsetY = null; + + double zoomDouble = getRealZoom(); + - if (SNAP_TO_GUIDES) { - Double nearestGuideX = null; - double distance = Double.MAX_VALUE; - double zoomDouble = getRealZoom(); + if (Configuration.snapToObjects.get() && depthStateUnderCursor != null && !selectedDepths.contains(depthStateUnderCursor.depth)) { + CharacterTag ch = depthStateUnderCursor.getCharacter(); + if (ch != null) { + if (ch instanceof BoundedTag) { + BoundedTag bt = (BoundedTag) ch; + RECT rect = bt.getRect(); - for (Double gx : guidesX) { - gx = gx * zoomDouble + offsetPoint.getX(); - double d = Math.abs(gx - touchPointPos.getX()); - if (d < distance) { - distance = d; - nearestGuideX = gx; + Matrix matrix = new Matrix(); + if (depthStateUnderCursor.matrix != null) { + matrix = matrix.preConcatenate(new Matrix(depthStateUnderCursor.matrix)); + } + + Matrix scaleMatrix = Matrix.getScaleInstance(zoomDouble / SWF.unitDivisor); + + matrix = matrix.preConcatenate(scaleMatrix); + matrix = matrix.preConcatenate(Matrix.getTranslateInstance(offsetPoint.getX(), offsetPoint.getY())); + + Point2D[] importantPoints = new Point2D[]{ + new Point2D.Double(rect.Xmin, rect.Ymin), + new Point2D.Double((rect.Xmin + rect.Xmax) / 2.0, rect.Ymin), + new Point2D.Double(rect.Xmax, rect.Ymin), + new Point2D.Double(rect.Xmin, (rect.Ymin + rect.Ymax) / 2.0), + new Point2D.Double((rect.Xmin + rect.Xmax) / 2.0, (rect.Ymin + rect.Ymax) / 2.0), + new Point2D.Double(rect.Xmax, (rect.Ymin + rect.Ymax) / 2.0), + new Point2D.Double(rect.Xmin, rect.Ymax), + new Point2D.Double((rect.Xmin + rect.Xmax) / 2.0, rect.Ymax), + new Point2D.Double(rect.Xmax, rect.Ymax),}; + + Point2D nearestPoint = null; + double distance = Double.MAX_VALUE; + for (Point2D p : importantPoints) { + Point2D windowPoint = matrix.transform(p); + double d = windowPoint.distance(touchPointPos); + if (d < distance) { + distance = d; + nearestPoint = windowPoint; + } + } + if (distance < SNAP_DISTANCE) { + snapOffsetX = (int) Math.round(nearestPoint.getX() - touchPointPos.getX()); + snapOffsetY = (int) Math.round(nearestPoint.getY() - touchPointPos.getY()); + } } } - - if (distance < SNAP_DISTANCE) { - snapOffsetX = (int) Math.round(nearestGuideX - touchPointPos.getX()); - } - Double nearestGuideY = null; - distance = Double.MAX_VALUE; - - for (Double gy : guidesY) { - gy = gy * zoomDouble + offsetPoint.getY(); - - double d = Math.abs(gy - touchPointPos.getY()); - if (d < distance) { - distance = d; - nearestGuideY = gy; - } - } - - if (distance < SNAP_DISTANCE) { - snapOffsetY = (int) Math.round(nearestGuideY - touchPointPos.getY()); - } } + if (Configuration.snapToGuides.get()) { + if (snapOffsetX == null) { + Double nearestGuideX = null; + double distance = Double.MAX_VALUE; + + for (Double gx : guidesX) { + gx = gx * zoomDouble + offsetPoint.getX(); + double d = Math.abs(gx - touchPointPos.getX()); + if (d < distance) { + distance = d; + nearestGuideX = gx; + } + } + + if (distance < SNAP_DISTANCE) { + snapOffsetX = (int) Math.round(nearestGuideX - touchPointPos.getX()); + } + } + + if (snapOffsetY == null) { + Double nearestGuideY = null; + double distance = Double.MAX_VALUE; + + for (Double gy : guidesY) { + gy = gy * zoomDouble + offsetPoint.getY(); + + double d = Math.abs(gy - touchPointPos.getY()); + if (d < distance) { + distance = d; + nearestGuideY = gy; + } + } + + if (distance < SNAP_DISTANCE) { + snapOffsetY = (int) Math.round(nearestGuideY - touchPointPos.getY()); + } + } + } + + if (Configuration.snapToPixels.get()) { + if (snapOffsetX == null) { + int positionPxX = (int) Math.round((touchPointPos.getX() - offsetPoint.getX()) / zoomDouble); + snapOffsetX = (int) Math.round(positionPxX * zoomDouble - touchPointPos.getX() + offsetPoint.getX()); + } + if (snapOffsetY == null) { + int positionPxY = (int) Math.round((touchPointPos.getY() - offsetPoint.getY()) / zoomDouble); + snapOffsetY = (int) Math.round(positionPxY * zoomDouble - touchPointPos.getY() + offsetPoint.getY()); + } + } + + if (snapOffsetX == null) { + snapOffsetX = 0; + } + if (snapOffsetY == null) { + snapOffsetY = 0; + } + snapOffset = new DisplayPoint(snapOffsetX, snapOffsetY); } @@ -2147,8 +2208,8 @@ public final class ImagePanel extends JPanel implements MediaDisplay { repaint(); } if (mode == Cursor.MOVE_CURSOR) { - double deltaX = ex - dsx; - double deltaY = ey - dsy; + double deltaX = ex + snapOffset.x - dsx; + double deltaY = ey + snapOffset.y - dsy; AffineTransform newTransform = new AffineTransform(transform.toTransform()); AffineTransform t = parentMatrix.toTransform(); @@ -3795,7 +3856,6 @@ public final class ImagePanel extends JPanel implements MediaDisplay { this.resample = Configuration.previewResampleSound.get(); this.mutable = mutable; depthStateUnderCursor = null; - allDepthStatesUnderCursor = new ArrayList<>(); hilightedEdge = null; hilightedPoints = null; pointEditPanel.setVisible(false); @@ -4534,7 +4594,12 @@ public final class ImagePanel extends JPanel implements MediaDisplay { RenderContext renderContext = new RenderContext(); renderContext.displayObjectCache = displayObjectCache; if (cursorPosition != null && (!doFreeTransform || transformSelectionMode)) { - renderContext.cursorPosition = new Point((int) (cursorPosition.x * SWF.unitDivisor), (int) (cursorPosition.y * SWF.unitDivisor)); + DisplayPoint touchPoint = new DisplayPoint(cursorPosition); + if (touchPointOffset != null) { + touchPoint = new DisplayPoint(cursorPosition.x + touchPointOffset.x, cursorPosition.y + touchPointOffset.y); + } + + renderContext.cursorPosition = new Point((int) (touchPoint.x * SWF.unitDivisor), (int) (touchPoint.y * SWF.unitDivisor)); renderContext.cursorPosition = getParentMatrix().transform(renderContext.cursorPosition); } @@ -4688,10 +4753,8 @@ public final class ImagePanel extends JPanel implements MediaDisplay { if (!renderContext.stateUnderCursor.isEmpty()) { depthStateUnderCursor = renderContext.stateUnderCursor.get(renderContext.stateUnderCursor.size() - 1); - allDepthStatesUnderCursor = new ArrayList<>(renderContext.stateUnderCursor); } else { depthStateUnderCursor = null; - allDepthStatesUnderCursor = new ArrayList<>(); } boolean first = true; @@ -4721,7 +4784,6 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } } else { depthStateUnderCursor = null; - allDepthStatesUnderCursor = new ArrayList<>(); } ButtonTag lastMouseOverButton; diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties index 07934e7d3..3b4011f03 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties @@ -558,4 +558,13 @@ config.name.halfTransparentParentLayersEasy = Half transparent parent layers in config.description.halfTransparentParentLayersEasy = Shows parent layers when editing subMovieClips as half transparent. False = do not show parent layers at all. config.name.showRuler = Show ruler -config.description.showRuler = Show ruler when displaying / editing SWF objects. \ No newline at end of file +config.description.showRuler = Show ruler when displaying / editing SWF objects. + +config.name.snapToGuides = Snap to guides +config.description.snapToGuides = Enables snapping cursor to guides while moving objects. + +config.name.snapToObjects = Snap to objects +config.description.snapToGuides = Enables snapping cursor to other objects while moving an object. + +config.name.snapToPixels = Snap to pixels +config.description.snapToGuides = Enables snapping cursor to whole pixels while moving objects.