diff --git a/CHANGELOG.md b/CHANGELOG.md index db7863ec6..5dea67207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ All notable changes to this project will be documented in this file. - [#2370] Snap to guides, objects and pixels, Snap align, toggle with magnet icon - [#2370] Show/Hide guides, lock guides, clear guides actions from icon menu - Display grid, snap to grid +- [#2370] Snap align border space, object spacing, center alignment +- [#2370] Setting for color and snap accuracy for guides, grid +- [#2370] Dialogs for editing grid, guides and snapping ### 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 c2c4dc421..dae789b42 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 @@ -17,6 +17,8 @@ package com.jpexs.decompiler.flash.configuration; import com.jpexs.decompiler.flash.ApplicationInfo; +import com.jpexs.decompiler.flash.configuration.enums.GridSnapAccuracy; +import com.jpexs.decompiler.flash.configuration.enums.GuidesSnapAccuracy; import com.jpexs.decompiler.flash.exporters.modes.ExeExportMode; import com.jpexs.decompiler.flash.helpers.CodeFormatting; import com.jpexs.decompiler.flash.helpers.FontHelper; @@ -1110,6 +1112,34 @@ public final class Configuration { @ConfigurationCategory("display") public static ConfigurationItem guidesColor = null; + @ConfigurationCategory("display") + @ConfigurationDefaultString("NORMAL") + public static ConfigurationItem gridSnapAccuracy = null; + + @ConfigurationCategory("display") + @ConfigurationDefaultString("NORMAL") + public static ConfigurationItem guidesSnapAccuracy = null; + + @ConfigurationDefaultInt(0) + @ConfigurationCategory("display") + public static ConfigurationItem snapAlignObjectHorizontalSpace = null; + + @ConfigurationDefaultInt(0) + @ConfigurationCategory("display") + public static ConfigurationItem snapAlignObjectVerticalSpace = null; + + @ConfigurationDefaultInt(0) + @ConfigurationCategory("display") + public static ConfigurationItem snapAlignStageBorder = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("display") + public static ConfigurationItem snapAlignCenterAlignmentHorizontal = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("display") + public static ConfigurationItem snapAlignCenterAlignmentVertical = null; + private enum OSId { WINDOWS, OSX, UNIX } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/enums/GridSnapAccuracy.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/enums/GridSnapAccuracy.java new file mode 100644 index 000000000..790027894 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/enums/GridSnapAccuracy.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2010-2024 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library 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. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.configuration.enums; + +/** + * + * @author JPEXS + */ +public enum GridSnapAccuracy { + MUST_BE_CLOSE(5), + NORMAL(10), + CAN_BE_DISTANT(15), + ALWAYS_SNAP(Integer.MAX_VALUE); + + private final int distance; + + private GridSnapAccuracy(int value) { + this.distance = value; + } + + public int getDistance() { + return distance; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/enums/GuidesSnapAccuracy.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/enums/GuidesSnapAccuracy.java new file mode 100644 index 000000000..adc0fe6b0 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/enums/GuidesSnapAccuracy.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2010-2024 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library 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. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.configuration.enums; + +/** + * + * @author JPEXS + */ +public enum GuidesSnapAccuracy { + MUST_BE_CLOSE(5), + NORMAL(10), + CAN_BE_DISTANT(15); + + private final int distance; + + private GuidesSnapAccuracy(int value) { + this.distance = value; + } + + public int getDistance() { + return distance; + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/GridDialog.java b/src/com/jpexs/decompiler/flash/gui/GridDialog.java new file mode 100644 index 000000000..60ecba622 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/GridDialog.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2025 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.configuration.enums.GridSnapAccuracy; +import com.jpexs.decompiler.flash.configuration.enums.GuidesSnapAccuracy; +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.util.Objects; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; + +/** + * + * @author JPEXS + */ +public class GridDialog extends AppDialog { + private ConfigurationColorSelection colorSelection; + private JCheckBox showGridCheckBox; + private JCheckBox snapToGridCheckBox; + private JCheckBox showOverObjectsCheckBox; + private JComboBox snapAccuracyComboBox; + private JTextField spacingXTextField; + private JTextField spacingYTextField; + + public GridDialog(Window owner) { + super(owner); + setSize(800, 600); + setTitle(translate("dialog.title")); + Container cnt = getContentPane(); + + JPanel centralPanel = new JPanel(new GridBagLayout()); + centralPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(2, 2, 2, 2); + + JLabel colorLabel = new JLabel(translate("color")); + c.gridx = 0; + c.gridy = 0; + c.anchor = GridBagConstraints.LINE_END; + + centralPanel.add(colorLabel, c); + + colorSelection = new ConfigurationColorSelection(Configuration.gridColor, Configuration.gridColor.get(), null); + c.gridx = 1; + c.anchor = GridBagConstraints.LINE_START; + centralPanel.add(colorSelection, c); + + showGridCheckBox = new JCheckBox(translate("show")); + showGridCheckBox.setSelected(Configuration.showGrid.get()); + c.gridy++; + centralPanel.add(showGridCheckBox, c); + + snapToGridCheckBox = new JCheckBox(translate("snapTo")); + snapToGridCheckBox.setSelected(Configuration.snapToGrid.get()); + c.gridy++; + centralPanel.add(snapToGridCheckBox, c); + + showOverObjectsCheckBox = new JCheckBox(translate("showOverObjects")); + showOverObjectsCheckBox.setSelected(Configuration.gridOverObjects.get()); + c.gridy++; + centralPanel.add(showOverObjectsCheckBox, c); + + JLabel spacingXLabel = new JLabel(translate("spacing.x")); + c.gridx = 0; + c.gridy++; + c.anchor = GridBagConstraints.LINE_END; + centralPanel.add(spacingXLabel, c); + + spacingXTextField = new JTextField(10); + spacingXTextField.setText("" + Configuration.gridHorizontalSpace.get()); + c.gridx++; + c.anchor = GridBagConstraints.LINE_START; + centralPanel.add(spacingXTextField, c); + + JLabel spacingYLabel = new JLabel(translate("spacing.y")); + c.gridx = 0; + c.gridy++; + c.anchor = GridBagConstraints.LINE_END; + centralPanel.add(spacingYLabel, c); + + spacingYTextField = new JTextField(10); + spacingYTextField.setText("" + Configuration.gridVerticalSpace.get()); + c.gridx++; + c.anchor = GridBagConstraints.LINE_START; + centralPanel.add(spacingYTextField, c); + + JLabel snapAccuracyLabel = new JLabel(translate("snapAccuracy")); + c.gridx = 0; + c.gridy++; + c.anchor = GridBagConstraints.LINE_END; + centralPanel.add(snapAccuracyLabel, c); + + snapAccuracyComboBox = new JComboBox<>( + new AcurracyItem[] { + new AcurracyItem(GridSnapAccuracy.MUST_BE_CLOSE), + new AcurracyItem(GridSnapAccuracy.NORMAL), + new AcurracyItem(GridSnapAccuracy.CAN_BE_DISTANT), + new AcurracyItem(GridSnapAccuracy.ALWAYS_SNAP) + } + ); + + snapAccuracyComboBox.setSelectedItem(new AcurracyItem(Configuration.gridSnapAccuracy.get())); + + c.gridx++; + c.anchor = GridBagConstraints.LINE_START; + centralPanel.add(snapAccuracyComboBox, c); + + cnt.setLayout(new BorderLayout()); + cnt.add(centralPanel, BorderLayout.CENTER); + + JPanel buttonsPanel = new JPanel(); + buttonsPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + + buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.Y_AXIS)); + + JButton okButton = new JButton(translate("button.ok")); + okButton.addActionListener(this::okButtonActionPerformed); + + JButton cancelButton = new JButton(translate("button.cancel")); + cancelButton.addActionListener(this::cancelButtonActionPerformed); + + buttonsPanel.add(okButton); + buttonsPanel.add(Box.createVerticalStrut(5)); + buttonsPanel.add(cancelButton); + + cnt.add(buttonsPanel, BorderLayout.EAST); + + pack(); + View.setWindowIcon16(this, "grid"); + View.centerScreen(this); + setModal(true); + setResizable(false); + } + + private void okButtonActionPerformed(ActionEvent evt) { + int spacingX; + try { + spacingX = Integer.parseInt(spacingXTextField.getText()); + } catch (NumberFormatException nfe) { + ViewMessages.showMessageDialog(this, translate("error.invalidSpacing"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + spacingXTextField.requestFocus(); + return; + } + int spacingY; + try { + spacingY = Integer.parseInt(spacingYTextField.getText()); + } catch (NumberFormatException nfe) { + ViewMessages.showMessageDialog(this, translate("error.invalidSpacing"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + spacingYTextField.requestFocus(); + return; + } + + Configuration.gridHorizontalSpace.set(spacingX); + Configuration.gridVerticalSpace.set(spacingY); + Configuration.gridColor.set(colorSelection.getValue()); + Configuration.showGrid.set(showGridCheckBox.isSelected()); + Configuration.snapToGrid.set(snapToGridCheckBox.isSelected()); + Configuration.gridOverObjects.set(showOverObjectsCheckBox.isSelected()); + Configuration.gridSnapAccuracy.set(((AcurracyItem) snapAccuracyComboBox.getSelectedItem()).acurracy); + setVisible(false); + } + + private void cancelButtonActionPerformed(ActionEvent evt) { + setVisible(false); + } + + private class AcurracyItem { + private GridSnapAccuracy acurracy; + + public AcurracyItem(GridSnapAccuracy acurracy) { + this.acurracy = acurracy; + } + + public GridSnapAccuracy getAcurracy() { + return acurracy; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 67 * hash + Objects.hashCode(this.acurracy); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final AcurracyItem other = (AcurracyItem) obj; + return this.acurracy == other.acurracy; + } + + + + @Override + public String toString() { + switch (acurracy) { + case MUST_BE_CLOSE: + return translate("snapAccuracy.mustBeClose"); + case NORMAL: + return translate("snapAccuracy.normal"); + case CAN_BE_DISTANT: + return translate("snapAccuracy.canBeDistant"); + case ALWAYS_SNAP: + return translate("snapAccuracy.alwaysSnap"); + } + return "unknown"; + } + } + +} diff --git a/src/com/jpexs/decompiler/flash/gui/GuidesDialog.java b/src/com/jpexs/decompiler/flash/gui/GuidesDialog.java new file mode 100644 index 000000000..acb01df33 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/GuidesDialog.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2025 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.configuration.enums.GuidesSnapAccuracy; +import com.jpexs.decompiler.flash.gui.player.MediaDisplay; +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.util.Objects; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; + +/** + * + * @author JPEXS + */ +public class GuidesDialog extends AppDialog { + + private final ConfigurationColorSelection colorSelection; + private final JCheckBox showGuidesCheckBox; + private final JCheckBox snapToGuidesCheckBox; + private final JCheckBox lockGuidesCheckBox; + private final JComboBox snapAccuracyComboBox; + private final MediaDisplay mediaDisplay; + + public GuidesDialog(Window owner, MediaDisplay mediaDisplay) { + super(owner); + setSize(800, 600); + setTitle(translate("dialog.title")); + + Container cnt = getContentPane(); + + JPanel centralPanel = new JPanel(new GridBagLayout()); + centralPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(2, 2, 2, 2); + + JLabel colorLabel = new JLabel(translate("color")); + c.gridx = 0; + c.gridy = 0; + c.anchor = GridBagConstraints.LINE_END; + + centralPanel.add(colorLabel, c); + + colorSelection = new ConfigurationColorSelection(Configuration.guidesColor, Configuration.guidesColor.get(), null); + c.gridx = 1; + c.anchor = GridBagConstraints.LINE_START; + centralPanel.add(colorSelection, c); + + showGuidesCheckBox = new JCheckBox(translate("show")); + showGuidesCheckBox.setSelected(Configuration.showGuides.get()); + c.gridy++; + centralPanel.add(showGuidesCheckBox, c); + + snapToGuidesCheckBox = new JCheckBox(translate("snapTo")); + snapToGuidesCheckBox.setSelected(Configuration.snapToGuides.get()); + c.gridy++; + centralPanel.add(snapToGuidesCheckBox, c); + + lockGuidesCheckBox = new JCheckBox(translate("lock")); + lockGuidesCheckBox.setSelected(Configuration.lockGuides.get()); + c.gridy++; + centralPanel.add(lockGuidesCheckBox, c); + + JLabel snapAccuracyLabel = new JLabel(translate("snapAccuracy")); + c.gridx = 0; + c.gridy++; + c.anchor = GridBagConstraints.LINE_END; + centralPanel.add(snapAccuracyLabel, c); + + snapAccuracyComboBox = new JComboBox<>( + new AcurracyItem[] { + new AcurracyItem(GuidesSnapAccuracy.MUST_BE_CLOSE), + new AcurracyItem(GuidesSnapAccuracy.NORMAL), + new AcurracyItem(GuidesSnapAccuracy.CAN_BE_DISTANT) + } + ); + + snapAccuracyComboBox.setSelectedItem(new AcurracyItem(Configuration.guidesSnapAccuracy.get())); + + c.gridx++; + c.anchor = GridBagConstraints.LINE_START; + centralPanel.add(snapAccuracyComboBox, c); + + cnt.setLayout(new BorderLayout()); + cnt.add(centralPanel, BorderLayout.CENTER); + + JPanel buttonsPanel = new JPanel(); + buttonsPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + + buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.Y_AXIS)); + + JButton okButton = new JButton(translate("button.ok")); + okButton.addActionListener(this::okButtonActionPerformed); + + JButton cancelButton = new JButton(translate("button.cancel")); + cancelButton.addActionListener(this::cancelButtonActionPerformed); + + JButton clearGuidesButton = new JButton(translate("clear")); + clearGuidesButton.addActionListener(this::clearButtonActionPerformed); + + buttonsPanel.add(okButton); + buttonsPanel.add(Box.createVerticalStrut(5)); + buttonsPanel.add(cancelButton); + buttonsPanel.add(Box.createVerticalStrut(5)); + buttonsPanel.add(clearGuidesButton); + + cnt.add(buttonsPanel, BorderLayout.EAST); + + pack(); + View.setWindowIcon16(this, "guides"); + View.centerScreen(this); + setModal(true); + setResizable(false); + this.mediaDisplay = mediaDisplay; + } + + private void clearButtonActionPerformed(ActionEvent evt) { + mediaDisplay.clearGuides(); + } + + private void okButtonActionPerformed(ActionEvent evt) { + Configuration.guidesColor.set(colorSelection.getValue()); + Configuration.showGuides.set(showGuidesCheckBox.isSelected()); + Configuration.snapToGuides.set(snapToGuidesCheckBox.isSelected()); + Configuration.lockGuides.set(lockGuidesCheckBox.isSelected()); + Configuration.guidesSnapAccuracy.set(((AcurracyItem) snapAccuracyComboBox.getSelectedItem()).acurracy); + setVisible(false); + } + + private void cancelButtonActionPerformed(ActionEvent evt) { + setVisible(false); + } + + private class AcurracyItem { + private GuidesSnapAccuracy acurracy; + + public AcurracyItem(GuidesSnapAccuracy acurracy) { + this.acurracy = acurracy; + } + + public GuidesSnapAccuracy getAcurracy() { + return acurracy; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 67 * hash + Objects.hashCode(this.acurracy); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final AcurracyItem other = (AcurracyItem) obj; + return this.acurracy == other.acurracy; + } + + + + @Override + public String toString() { + switch (acurracy) { + case MUST_BE_CLOSE: + return translate("snapAccuracy.mustBeClose"); + case NORMAL: + return translate("snapAccuracy.normal"); + case CAN_BE_DISTANT: + return translate("snapAccuracy.canBeDistant"); + } + return "unknown"; + } + } + +} diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index c6bb9ef55..a68fa9577 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -31,7 +31,6 @@ import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; import com.jpexs.decompiler.flash.gui.player.MediaDisplay; import com.jpexs.decompiler.flash.gui.player.MediaDisplayListener; import com.jpexs.decompiler.flash.gui.player.Zoom; -import com.jpexs.decompiler.flash.math.BezierEdge; import com.jpexs.decompiler.flash.math.BezierUtils; import com.jpexs.decompiler.flash.tags.DefineButton2Tag; import com.jpexs.decompiler.flash.tags.DefineButtonSoundTag; @@ -44,7 +43,6 @@ import com.jpexs.decompiler.flash.tags.base.DisplayObjectCacheKey; import com.jpexs.decompiler.flash.tags.base.DrawableTag; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.tags.base.RenderContext; -import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.tags.base.TextTag; import com.jpexs.decompiler.flash.timeline.DepthState; @@ -57,10 +55,6 @@ import com.jpexs.decompiler.flash.types.RECT; import com.jpexs.decompiler.flash.types.RGB; import com.jpexs.decompiler.flash.types.SOUNDINFO; import com.jpexs.decompiler.flash.types.filters.BlendComposite; -import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord; -import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; -import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; -import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.Cache; import com.jpexs.helpers.Reference; @@ -195,8 +189,6 @@ public final class ImagePanel extends JPanel implements MediaDisplay { private int time = 0; - //private int selectedDepth = -1; - //private int freeTransformDepth = -1; private boolean doFreeTransform = false; private Zoom zoom = new Zoom(); @@ -313,10 +305,10 @@ public final class ImagePanel extends JPanel implements MediaDisplay { private DisplayPoint snapAlignYPoint1 = null; private DisplayPoint snapAlignYPoint2 = null; - private static final int SNAP_DISTANCE = 10; - private static final int SNAP_ALIGN_DISTANCE = 5; + private static final int SNAP_TO_OBJECTS_DISTANCE = 10; + private static final int SNAP_ALIGN_AFTER_LINE = 50; //private DisplayPoint closestPoint = null; @@ -397,6 +389,14 @@ public final class ImagePanel extends JPanel implements MediaDisplay { private int guidesCharacterId = -1; + private static int getSnapGuidesDistance() { + return Configuration.guidesSnapAccuracy.get().getDistance(); + } + + private static int getSnapGridDistance() { + return Configuration.gridSnapAccuracy.get().getDistance(); + } + @Override public boolean canHaveRuler() { return this.contentCanHaveRuler; @@ -908,7 +908,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { public void setResample(boolean resample) { this.resample = resample; } - + private static void drawGridSwf(Graphics2D g, Rectangle realRect, double zoom) { g.setColor(Configuration.gridColor.get()); double x; @@ -1005,8 +1005,8 @@ public final class ImagePanel extends JPanel implements MediaDisplay { return allowMove; } - VolatileImage renderImage; - + VolatileImage renderImage; + private void drawGridNoSwf(Graphics2D g2, int x, int y) { double zoomDouble = getRealZoom(); g2.setColor(Configuration.gridColor.get()); @@ -1965,6 +1965,14 @@ public final class ImagePanel extends JPanel implements MediaDisplay { if (timelined instanceof SWF) { RECT stageRect = timelined.getRect(); + + stageRect = new RECT( + (int) Math.round(stageRect.Xmin + Configuration.snapAlignStageBorder.get() * SWF.unitDivisor), + (int) Math.round(stageRect.Xmax - Configuration.snapAlignStageBorder.get() * SWF.unitDivisor), + (int) Math.round(stageRect.Ymin + Configuration.snapAlignStageBorder.get() * SWF.unitDivisor), + (int) Math.round(stageRect.Ymax - Configuration.snapAlignStageBorder.get() * SWF.unitDivisor) + ); + Matrix scaleMatrix = Matrix.getScaleInstance(zoomDouble / SWF.unitDivisor); Matrix matrix = new Matrix(); matrix = matrix.concatenate(Matrix.getTranslateInstance(offsetPoint.getX(), offsetPoint.getY())); @@ -2075,20 +2083,39 @@ public final class ImagePanel extends JPanel implements MediaDisplay { BoundedTag bt = (BoundedTag) ct; RECT rect = bt.getRect(); - Matrix matrix = new Matrix(); + Matrix scaleMatrix = Matrix.getScaleInstance(zoomDouble / SWF.unitDivisor); + Matrix translateMatrix = Matrix.getTranslateInstance(offsetPoint.getX(), offsetPoint.getY()); + + Matrix matrix = translateMatrix.concatenate(scaleMatrix); + + Matrix dsMatrix = new Matrix(); if (ds.matrix != null) { - matrix = matrix.preConcatenate(new Matrix(ds.matrix)); + dsMatrix = new Matrix(ds.matrix); } - Matrix scaleMatrix = Matrix.getScaleInstance(zoomDouble / SWF.unitDivisor); + Rectangle2D bounds = dsMatrix.transform(new Rectangle2D.Double(rect.Xmin, rect.Ymin, rect.Xmax - rect.Xmin, rect.Ymax - rect.Ymin)); + bounds = new Rectangle2D.Double( + bounds.getX() - Configuration.snapAlignObjectHorizontalSpace.get() * SWF.unitDivisor, + bounds.getY() - Configuration.snapAlignObjectVerticalSpace.get() * SWF.unitDivisor, + bounds.getWidth() + 2 * Configuration.snapAlignObjectHorizontalSpace.get() * SWF.unitDivisor, + bounds.getHeight() + 2 * Configuration.snapAlignObjectVerticalSpace.get() * SWF.unitDivisor + ); - matrix = matrix.preConcatenate(scaleMatrix); - matrix = matrix.preConcatenate(Matrix.getTranslateInstance(offsetPoint.getX(), offsetPoint.getY())); - - Rectangle2D bounds = matrix.transform(new Rectangle2D.Double(rect.Xmin, rect.Ymin, rect.Xmax - rect.Xmin, rect.Ymax - rect.Ymin)); + bounds = matrix.transform(bounds); if (!snapAlignedX) { - if (Math.abs(bounds.getMinX() - selectedBounds.getMinX()) < SNAP_ALIGN_DISTANCE) { + if (Configuration.snapAlignCenterAlignmentVertical.get() && Math.abs(bounds.getCenterX() - selectedBounds.getCenterX()) < SNAP_ALIGN_DISTANCE) { + snapOffsetX = bounds.getCenterX() - selectedBounds.getCenterX(); + snapAlignXPoint1 = new DisplayPoint( + (int) Math.round(bounds.getCenterX()), + (int) Math.round(Math.min(bounds.getMinY(), selectedBounds.getMinY()) - SNAP_ALIGN_AFTER_LINE) + ); + snapAlignXPoint2 = new DisplayPoint( + (int) Math.round(bounds.getCenterX()), + (int) Math.round(Math.max(bounds.getMaxY(), selectedBounds.getMaxY()) + SNAP_ALIGN_AFTER_LINE) + ); + snapAlignedX = true; + } else if (Math.abs(bounds.getMinX() - selectedBounds.getMinX()) < SNAP_ALIGN_DISTANCE) { snapOffsetX = bounds.getMinX() - selectedBounds.getMinX(); snapAlignXPoint1 = new DisplayPoint( (int) Math.round(bounds.getMinX()), @@ -2136,7 +2163,18 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } if (!snapAlignedY) { - if (Math.abs(bounds.getMinY() - selectedBounds.getMinY()) < SNAP_ALIGN_DISTANCE) { + if (Configuration.snapAlignCenterAlignmentHorizontal.get() && Math.abs(bounds.getCenterY() - selectedBounds.getCenterY()) < SNAP_ALIGN_DISTANCE) { + snapOffsetY = bounds.getCenterY() - selectedBounds.getCenterY(); + snapAlignYPoint1 = new DisplayPoint( + (int) Math.round(Math.min(bounds.getMinX(), selectedBounds.getMinX()) - SNAP_ALIGN_AFTER_LINE), + (int) Math.round(bounds.getCenterY()) + ); + snapAlignYPoint2 = new DisplayPoint( + (int) Math.round(Math.max(bounds.getMaxX(), selectedBounds.getMaxX()) + SNAP_ALIGN_AFTER_LINE), + (int) Math.round(bounds.getCenterY()) + ); + snapAlignedY = true; + } else if (Math.abs(bounds.getMinY() - selectedBounds.getMinY()) < SNAP_ALIGN_DISTANCE) { snapOffsetY = bounds.getMinY() - selectedBounds.getMinY(); snapAlignYPoint1 = new DisplayPoint( (int) Math.round(Math.min(bounds.getMinX(), selectedBounds.getMinX()) - SNAP_ALIGN_AFTER_LINE), @@ -2243,7 +2281,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { nearestPoint = windowPoint; } } - if (distance < SNAP_DISTANCE) { + if (distance < SNAP_TO_OBJECTS_DISTANCE) { snapOffsetX = nearestPoint.getX() - touchPointPos.getX(); snapOffsetY = nearestPoint.getY() - touchPointPos.getY(); } @@ -2265,7 +2303,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } } - if (distance < SNAP_DISTANCE) { + if (distance < getSnapGuidesDistance()) { snapOffsetX = nearestGuideX - touchPointPos.getX(); } } @@ -2284,7 +2322,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } } - if (distance < SNAP_DISTANCE) { + if (distance < getSnapGuidesDistance()) { snapOffsetY = nearestGuideY - touchPointPos.getY(); } } @@ -2294,18 +2332,18 @@ public final class ImagePanel extends JPanel implements MediaDisplay { if (snapOffsetX == null) { int positionPxX = (int) Math.round((touchPointPos.getX() - offsetPoint.getX()) / zoomDouble); int d = (positionPxX / Configuration.gridHorizontalSpace.get()) * Configuration.gridHorizontalSpace.get(); - if ((positionPxX - d) * zoomDouble < SNAP_DISTANCE) { + if ((positionPxX - d) * zoomDouble < getSnapGridDistance()) { snapOffsetX = d * zoomDouble - touchPointPos.getX() + offsetPoint.getX(); - } else if ((d + Configuration.gridHorizontalSpace.get() - positionPxX) * zoomDouble < SNAP_DISTANCE) { + } else if ((d + Configuration.gridHorizontalSpace.get() - positionPxX) * zoomDouble < getSnapGridDistance()) { snapOffsetX = (d + Configuration.gridHorizontalSpace.get()) * zoomDouble - touchPointPos.getX() + offsetPoint.getX(); } } if (snapOffsetY == null) { int positionPxY = (int) Math.round((touchPointPos.getY() - offsetPoint.getY()) / zoomDouble); int d = (positionPxY / Configuration.gridVerticalSpace.get()) * Configuration.gridVerticalSpace.get(); - if ((positionPxY - d) * zoomDouble < SNAP_DISTANCE) { + if ((positionPxY - d) * zoomDouble < getSnapGridDistance()) { snapOffsetY = d * zoomDouble - touchPointPos.getY() + offsetPoint.getY(); - } else if ((d + Configuration.gridVerticalSpace.get() - positionPxY) * zoomDouble < SNAP_DISTANCE) { + } else if ((d + Configuration.gridVerticalSpace.get() - positionPxY) * zoomDouble < getSnapGridDistance()) { snapOffsetY = (d + Configuration.gridVerticalSpace.get()) * zoomDouble - touchPointPos.getY() + offsetPoint.getY(); } } @@ -4916,13 +4954,12 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } } } - - + if (Configuration.showGrid.get() && (drawable instanceof SWF) && Configuration.gridOverObjects.get()) { Graphics2D g = (Graphics2D) image.getBufferedImage().getGraphics(); drawGridSwf(g, realRect, zoom); } - + img = image; /*if (shouldCache) { diff --git a/src/com/jpexs/decompiler/flash/gui/SnappingDialog.java b/src/com/jpexs/decompiler/flash/gui/SnappingDialog.java new file mode 100644 index 000000000..4e201e991 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/SnappingDialog.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2025 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import com.jpexs.decompiler.flash.configuration.Configuration; +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Window; +import java.awt.event.ActionEvent; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; + +/** + * + * @author JPEXS + */ +public class SnappingDialog extends AppDialog { + + private final JCheckBox snapAlignCheckBox; + + private final JCheckBox snapToGridCheckBox; + + private final JCheckBox snapToGuidesCheckBox; + + private final JCheckBox snapToPixelsCheckBox; + private final JCheckBox snapToObjectsCheckBox; + + private final JTextField stageBorderTextField; + private final JTextField objectHorizontalSpacingTextField; + private final JTextField objectVerticalSpacingTextField; + + private final JCheckBox centerAlignmentHorizontalCheckBox; + private final JCheckBox centerAlignmentVerticalCheckBox; + + public SnappingDialog(Window owner) { + super(owner); + setSize(800, 600); + setTitle(translate("dialog.title")); + + Container cnt = getContentPane(); + + cnt.setLayout(new BorderLayout()); + JPanel snapPanel = new JPanel(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(2, 2, 2, 2); + c.gridx = 0; + c.gridy = 0; + c.anchor = GridBagConstraints.LINE_START; + + snapAlignCheckBox = new JCheckBox(translate("snapAlign")); + snapAlignCheckBox.setSelected(Configuration.snapAlign.get()); + snapPanel.add(snapAlignCheckBox, c); + + snapToGridCheckBox = new JCheckBox(translate("snapToGrid")); + snapToGridCheckBox.setSelected(Configuration.snapToGrid.get()); + c.gridy++; + snapPanel.add(snapToGridCheckBox, c); + + snapToGuidesCheckBox = new JCheckBox(translate("snapToGuides")); + snapToGuidesCheckBox.setSelected(Configuration.snapToGuides.get()); + c.gridy++; + snapPanel.add(snapToGuidesCheckBox, c); + + snapToPixelsCheckBox = new JCheckBox(translate("snapToPixels")); + snapToPixelsCheckBox.setSelected(Configuration.snapToPixels.get()); + c.gridy++; + snapPanel.add(snapToPixelsCheckBox, c); + + snapToObjectsCheckBox = new JCheckBox(translate("snapToObjects")); + snapToObjectsCheckBox.setSelected(Configuration.snapToObjects.get()); + c.gridy++; + snapPanel.add(snapToObjectsCheckBox, c); + + c.gridx = 1; + c.gridy = 0; + c.gridheight = 5; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + snapPanel.add(new JPanel(), c); + + JPanel centralPanel = new JPanel(); + centralPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + centralPanel.setLayout(new BoxLayout(centralPanel, BoxLayout.Y_AXIS)); + centralPanel.add(snapPanel); + centralPanel.add(Box.createVerticalStrut(10)); + + JPanel snapAlignPanel = new JPanel(new GridBagLayout()); + snapAlignPanel.setBorder(BorderFactory.createTitledBorder(translate("snapAlign.settings"))); + c = new GridBagConstraints(); + c.insets = new Insets(2, 2, 2, 2); + + JLabel objectSpacingLabel = new JLabel(translate("snapAlign.objectSpacing")); + JLabel objectHorizontalSpacingLabel = new JLabel(translate("snapAlign.objectSpacing.horizontal")); + JLabel objectVerticalSpacingLabel = new JLabel(translate("snapAlign.objectSpacing.vertical")); + JLabel centerAlignmentLabel = new JLabel(translate("snapAlign.centerAlignment")); + + JLabel stageBorderLabel = new JLabel(translate("snapAlign.stageBorder")); + c.gridx = 0; + c.gridy = 0; + c.anchor = GridBagConstraints.LINE_END; + snapAlignPanel.add(stageBorderLabel, c); + + stageBorderTextField = new JTextField(10); + stageBorderTextField.setText("" + Configuration.snapAlignStageBorder.get()); + c.gridx++; + c.anchor = GridBagConstraints.LINE_START; + snapAlignPanel.add(stageBorderTextField, c); + + c.gridx = 0; + c.gridy++; + snapAlignPanel.add(objectSpacingLabel, c); + + c.gridx = 0; + c.gridy++; + c.anchor = GridBagConstraints.LINE_END; + snapAlignPanel.add(objectHorizontalSpacingLabel, c); + + objectHorizontalSpacingTextField = new JTextField(10); + objectHorizontalSpacingTextField.setText("" + Configuration.snapAlignObjectHorizontalSpace.get()); + + c.gridx++; + c.anchor = GridBagConstraints.LINE_START; + snapAlignPanel.add(objectHorizontalSpacingTextField, c); + + c.gridx = 0; + c.gridy++; + c.anchor = GridBagConstraints.LINE_END; + snapAlignPanel.add(objectVerticalSpacingLabel, c); + + objectVerticalSpacingTextField = new JTextField(10); + objectVerticalSpacingTextField.setText("" + Configuration.snapAlignObjectVerticalSpace.get()); + c.gridx++; + c.anchor = GridBagConstraints.LINE_START; + snapAlignPanel.add(objectVerticalSpacingTextField, c); + + c.anchor = GridBagConstraints.LINE_START; + c.gridx = 0; + c.gridy++; + c.gridwidth = 2; + snapAlignPanel.add(centerAlignmentLabel, c); + + centerAlignmentHorizontalCheckBox = new JCheckBox(translate("snapAlign.centerAlignment.horizontal")); + centerAlignmentHorizontalCheckBox.setSelected(Configuration.snapAlignCenterAlignmentHorizontal.get()); + c.gridy++; + snapAlignPanel.add(centerAlignmentHorizontalCheckBox, c); + + centerAlignmentVerticalCheckBox = new JCheckBox(translate("snapAlign.centerAlignment.vertical")); + centerAlignmentVerticalCheckBox.setSelected(Configuration.snapAlignCenterAlignmentVertical.get()); + c.gridy++; + snapAlignPanel.add(centerAlignmentVerticalCheckBox, c); + + centralPanel.add(snapAlignPanel); + + JPanel buttonsPanel = new JPanel(); + buttonsPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + + buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.Y_AXIS)); + + JButton okButton = new JButton(translate("button.ok")); + okButton.addActionListener(this::okButtonActionPerformed); + + JButton cancelButton = new JButton(translate("button.cancel")); + cancelButton.addActionListener(this::cancelButtonActionPerformed); + + buttonsPanel.add(okButton); + buttonsPanel.add(Box.createVerticalStrut(5)); + buttonsPanel.add(cancelButton); + + cnt.add(centralPanel, BorderLayout.CENTER); + cnt.add(buttonsPanel, BorderLayout.EAST); + + pack(); + View.setWindowIcon16(this, "snap"); + View.centerScreen(this); + setModal(true); + setResizable(false); + } + + private void okButtonActionPerformed(ActionEvent evt) { + int spacingX; + try { + spacingX = Integer.parseInt(objectHorizontalSpacingTextField.getText()); + } catch (NumberFormatException nfe) { + ViewMessages.showMessageDialog(this, translate("error.invalidSpacing"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + objectHorizontalSpacingTextField.requestFocus(); + return; + } + int spacingY; + try { + spacingY = Integer.parseInt(objectVerticalSpacingTextField.getText()); + } catch (NumberFormatException nfe) { + ViewMessages.showMessageDialog(this, translate("error.invalidSpacing"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + objectVerticalSpacingTextField.requestFocus(); + return; + } + + int stageBorder; + try { + stageBorder = Integer.parseInt(stageBorderTextField.getText()); + } catch (NumberFormatException nfe) { + ViewMessages.showMessageDialog(this, translate("error.invalidBorder"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + stageBorderTextField.requestFocus(); + return; + } + + Configuration.snapAlign.set(snapAlignCheckBox.isSelected()); + Configuration.snapToGrid.set(snapToGridCheckBox.isSelected()); + Configuration.snapToGuides.set(snapToGuidesCheckBox.isSelected()); + Configuration.snapToPixels.set(snapToPixelsCheckBox.isSelected()); + Configuration.snapToObjects.set(snapToObjectsCheckBox.isSelected()); + + Configuration.snapAlignObjectHorizontalSpace.set(spacingX); + Configuration.snapAlignObjectVerticalSpace.set(spacingY); + Configuration.snapAlignStageBorder.set(stageBorder); + + Configuration.snapAlignCenterAlignmentHorizontal.set(centerAlignmentHorizontalCheckBox.isSelected()); + Configuration.snapAlignCenterAlignmentVertical.set(centerAlignmentVerticalCheckBox.isSelected()); + + setVisible(false); + } + /* + + + private final JTextField stageBorderTextField; + private final JTextField objectHorizontalSpacingTextField; + private final JTextField objectVerticalSpacingTextField; + + private final JCheckBox centerAlignmentHorizontalCheckBox; + private final JCheckBox centerAlignmentVerticalCheckBox; + */ + + private void cancelButtonActionPerformed(ActionEvent evt) { + setVisible(false); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/View.java b/src/com/jpexs/decompiler/flash/gui/View.java index df49330e0..1ddaac788 100644 --- a/src/com/jpexs/decompiler/flash/gui/View.java +++ b/src/com/jpexs/decompiler/flash/gui/View.java @@ -334,7 +334,7 @@ public class View { images.add(loadImage(icon + "16")); images.add(loadImage(icon + "32")); f.setIconImages(images); - } + } /** * Sets icon of specified frame to ASDec icon @@ -363,6 +363,18 @@ public class View { f.setIconImages(images); } } + + /** + * Sets icon of the window - only 16 px variant. + * + * @param f + * @param icon Icon identifier. Icon must exist in 16px variant. + */ + public static void setWindowIcon16(Window f, String icon) { + List images = new ArrayList<>(); + images.add(loadImage(icon + "16")); + f.setIconImages(images); + } public static void centerScreenMain(Window f) { centerScreen(f, true); diff --git a/src/com/jpexs/decompiler/flash/gui/abc/SnapOptionsButton.java b/src/com/jpexs/decompiler/flash/gui/abc/SnapOptionsButton.java index d3d9934f3..ed7a1459a 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/SnapOptionsButton.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/SnapOptionsButton.java @@ -18,14 +18,14 @@ package com.jpexs.decompiler.flash.gui.abc; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.gui.AppStrings; +import com.jpexs.decompiler.flash.gui.Main; import com.jpexs.decompiler.flash.gui.PopupButton; +import com.jpexs.decompiler.flash.gui.SnappingDialog; import com.jpexs.decompiler.flash.gui.View; import java.awt.Insets; import java.awt.event.ActionEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; import javax.swing.JCheckBox; -import javax.swing.JCheckBoxMenuItem; +import javax.swing.JMenuItem; import javax.swing.JPopupMenu; /** @@ -69,10 +69,18 @@ public class SnapOptionsButton extends PopupButton { snapToObjectsMenuItem.setSelected(Configuration.snapToObjects.get()); snapToObjectsMenuItem.addActionListener(this::snapToObjectsMenuItemActionPerformed); popupMenu.add(snapToObjectsMenuItem); + + JMenuItem editSnappingMenuItem = new JMenuItem(AppStrings.translate("snap_options.edit")); + editSnappingMenuItem.addActionListener(this::editSnappingMenuItemActionPerformed); + popupMenu.add(editSnappingMenuItem); return popupMenu; } + private void editSnappingMenuItemActionPerformed(ActionEvent evt) { + new SnappingDialog(Main.getDefaultDialogsOwner()).setVisible(true); + } + private void snapAlignMenuItemActionPerformed(ActionEvent evt) { JCheckBox menuItem = (JCheckBox) evt.getSource(); Configuration.snapAlign.set(menuItem.isSelected()); diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties index 572152034..b4b2588ed 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties @@ -598,3 +598,24 @@ config.description.gridColor = Color of the drawn grid. config.name.guidesColor = Guides color config.description.guidesColor = Color of the drawn guides. + +config.name.gridSnapAccuracy = Grid snap accuracy +config.description.gridSnapAccuracy = How far must be cursor to grid to be snapped. + +config.name.guidesSnapAccuracy = Guides snap accuracy +config.description.guidesSnapAccuracy = How far must be cursor to guide to be snapped. + +config.name.snapAlignObjectHorizontalSpace = Snap align object horizontal spacing +config.description.snapAlignObjectHorizontalSpace = Horizontal spaces between objects during align snaping. + +config.name.snapAlignObjectVerticalSpace = Snap align object vertical spacing +config.description.snapAlignObjectVerticalSpace = Vertical spaces between objects during align snapping. + +config.name.snapAlignStageBorder = Snap align stage border +config.description.snapAlignStageBorder = Space between border during align snapping. + +config.name.snapAlignCenterAlignmentHorizontal = Snap align horizontal center alignment +config.description.snapAlignCenterAlignmentHorizontal = Enables snap align of object center horizontally. + +config.name.snapAlignCenterAlignmentVertical = Snap align vertical center alignment +config.description.snapAlignCenterAlignmentVertical = Enables snap align of object center verically. diff --git a/src/com/jpexs/decompiler/flash/gui/locales/GridDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/GridDialog.properties new file mode 100644 index 000000000..15558e34c --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/GridDialog.properties @@ -0,0 +1,31 @@ +# Copyright (C) 2025 JPEXS +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +dialog.title = Grid +button.ok = OK +button.cancel = Cancel +color = Color: +show = Show grid +showOverObjects = Show over objects +snapTo = Snap to grid +spacing.x = Horizontal spacing: +spacing.y = Vertical spacing: +snapAccuracy = Snap accuracy: +snapAccuracy.mustBeClose = Must be close +snapAccuracy.normal = Normal +snapAccuracy.canBeDistant = Can be distant +snapAccuracy.alwaysSnap = Always snap + +error.invalidSpacing = Invalid spacing value. Integer expected. \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/GuidesDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/GuidesDialog.properties new file mode 100644 index 000000000..7a6a9a427 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/GuidesDialog.properties @@ -0,0 +1,27 @@ +# Copyright (C) 2025 JPEXS +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +dialog.title = Guides +button.ok = OK +button.cancel = Cancel +color = Color: +show = Show guides +snapTo = Snap to guides +lock = Lock guides +clear = Clear all +snapAccuracy = Snap accuracy: +snapAccuracy.mustBeClose = Must be close +snapAccuracy.normal = Normal +snapAccuracy.canBeDistant = Can be distant \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 12b805b00..981c3a38d 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1029,12 +1029,16 @@ snap_options.snap_to_grid = Snap to Grid snap_options.snap_to_guides = Snap to Guides snap_options.snap_to_pixels = Snap to Pixels snap_options.snap_to_objects = Snap to Objects +snap_options.edit = Edit snapping... button.ruler.hint = Toggle ruler display -button.guides_options.hint = Guides options +button.guides_options = Guides options guides_options.show = Show guides guides_options.lock = Lock guides guides_options.clear = Clear guides +guides_options.edit = Edit guides... -button.grid.hint = Toggle grid display \ No newline at end of file +button.grid_options = Grid options +grid_options.show_grid = Show grid +grid_options.edit = Edit grid... \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/SnappingDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/SnappingDialog.properties new file mode 100644 index 000000000..6faaffc76 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/SnappingDialog.properties @@ -0,0 +1,36 @@ +# Copyright (C) 2025 JPEXS +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +dialog.title = Snapping +button.ok = OK +button.cancel = Cancel + +snapAlign = Snap Align +snapToGrid = Snap to Grid +snapToGuides = Snap to Guides +snapToPixels = Snap to Pixels +snapToObjects = Snap to Objects + +snapAlign.settings = Snap align settings +snapAlign.stageBorder = Stage border: +snapAlign.objectSpacing = Object spacing: +snapAlign.objectSpacing.horizontal = Horizontal: +snapAlign.objectSpacing.vertical = Vertical: +snapAlign.centerAlignment = Center alignment: +snapAlign.centerAlignment.horizontal = Horizontal center alignment +snapAlign.centerAlignment.vertical = Vertical center alignment + +error.invalidSpacing = Invalid spacing value. Integer expected. +error.invalidBorder = Invalid border value. Integer expected. \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/player/ZoomPanel.java b/src/com/jpexs/decompiler/flash/gui/player/ZoomPanel.java index 34651897e..45fd317f8 100644 --- a/src/com/jpexs/decompiler/flash/gui/player/ZoomPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/player/ZoomPanel.java @@ -19,6 +19,9 @@ package com.jpexs.decompiler.flash.gui.player; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.configuration.ConfigurationItemChangeListener; import com.jpexs.decompiler.flash.gui.AppStrings; +import com.jpexs.decompiler.flash.gui.GridDialog; +import com.jpexs.decompiler.flash.gui.GuidesDialog; +import com.jpexs.decompiler.flash.gui.Main; import com.jpexs.decompiler.flash.gui.PopupButton; import com.jpexs.decompiler.flash.gui.View; import com.jpexs.decompiler.flash.gui.abc.SnapOptionsButton; @@ -30,6 +33,7 @@ import java.util.Objects; import javax.swing.JButton; import javax.swing.JCheckBoxMenuItem; import javax.swing.JLabel; +import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; @@ -93,30 +97,39 @@ public class ZoomPanel extends JPanel implements MediaDisplayListener { lockGuidesMenuItem.addActionListener(ZoomPanel.this::guidesLockActionPerformed); JMenuItem clearGuidesMenuItem = new JMenuItem(AppStrings.translate("guides_options.clear")); clearGuidesMenuItem.addActionListener(ZoomPanel.this::guidesClearActionPerformed); + JMenuItem editGuidesMenuItem = new JMenuItem(AppStrings.translate("guides_options.edit")); + editGuidesMenuItem.addActionListener(ZoomPanel.this::guidesEditActionPerformed); menu.add(showGuidesMenuItem); menu.add(lockGuidesMenuItem); menu.add(clearGuidesMenuItem); + menu.add(editGuidesMenuItem); return menu; } }; - guidesOptionsButton.setToolTipText(AppStrings.translate("button.guides_options.hint")); + guidesOptionsButton.setToolTipText(AppStrings.translate("button.guides_options")); snapOptionsButton = new SnapOptionsButton(); - JToggleButton gridButton = new JToggleButton(View.getIcon("grid16")); - gridButton.addActionListener(this::gridButtonActionPerformed); - gridButton.setToolTipText(AppStrings.translate("button.grid.hint")); - gridButton.setSelected(Configuration.showGrid.get()); - Configuration.showGrid.addListener(new ConfigurationItemChangeListener() { + PopupButton gridButton = new PopupButton(View.getIcon("grid16")) { @Override - public void configurationItemChanged(Boolean newValue) { - gridButton.setSelected(newValue); - } - }); - + protected JPopupMenu getPopupMenu() { + JPopupMenu menu = new JPopupMenu(); + JCheckBoxMenuItem showGridCheckBoxMenuItem = new JCheckBoxMenuItem(AppStrings.translate("grid_options.show_grid")); + showGridCheckBoxMenuItem.addActionListener(ZoomPanel.this::showGridCheckBoxMenuItemActionPerformed); + showGridCheckBoxMenuItem.setSelected(Configuration.showGrid.get()); + menu.add(showGridCheckBoxMenuItem); + + JMenuItem editGridMenuItem = new JMenuItem(AppStrings.translate("grid_options.edit")); + editGridMenuItem.addActionListener(ZoomPanel.this::editGridMenuItemActionPerformed); + menu.add(editGridMenuItem); + + return menu; + } + }; + gridButton.setToolTipText(AppStrings.translate("button.grid_options")); setLayout(new FlowLayout()); add(percentLabel); @@ -132,8 +145,13 @@ public class ZoomPanel extends JPanel implements MediaDisplayListener { display.addEventListener(this); } - private void gridButtonActionPerformed(ActionEvent evt) { - JToggleButton source = (JToggleButton) evt.getSource(); + + private void editGridMenuItemActionPerformed(ActionEvent evt) { + new GridDialog(Main.getDefaultDialogsOwner()).setVisible(true); + } + + private void showGridCheckBoxMenuItemActionPerformed(ActionEvent evt) { + JCheckBoxMenuItem source = (JCheckBoxMenuItem) evt.getSource(); Configuration.showGrid.set(source.isSelected()); } @@ -147,6 +165,10 @@ public class ZoomPanel extends JPanel implements MediaDisplayListener { Configuration.lockGuides.set(source.isSelected()); } + private void guidesEditActionPerformed(ActionEvent evt) { + new GuidesDialog(Main.getDefaultDialogsOwner(), display).setVisible(true); + } + private void guidesClearActionPerformed(ActionEvent evt) { display.clearGuides(); }