diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3bfcc4db7..f188ad995 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, objects and pixels, Snap align
+- [#2370] Snap to guides, objects and pixels, Snap align, toggle with magnet icon
### Fixed
- [#2424] DefineEditText handling of letterSpacing, font size on incorrect values
diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java
index ee9b26781..46722241e 100644
--- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java
+++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java
@@ -399,6 +399,11 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
private int guidesCharacterId = -1;
+ @Override
+ public boolean canUseSnapping() {
+ return selectionMode || doFreeTransform || hilightedPoints != null;
+ }
+
public void setFrozenButtons(boolean frozenButtons) {
this.frozenButtons = frozenButtons;
}
diff --git a/src/com/jpexs/decompiler/flash/gui/PopupButton.java b/src/com/jpexs/decompiler/flash/gui/PopupButton.java
new file mode 100644
index 000000000..37d3a8467
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/gui/PopupButton.java
@@ -0,0 +1,109 @@
+/*
+ * 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 java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import javax.swing.Icon;
+import javax.swing.JPopupMenu;
+import javax.swing.JToggleButton;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+
+/**
+ * Button with a popup on click.
+ * The logic ensures that it stays down when menu is shown.
+ * @author JPEXS
+ */
+public abstract class PopupButton extends JToggleButton {
+
+ private boolean insideButton = false;
+
+ private PopupMenuListener popupListener;
+
+ private JPopupMenu popupMenu;
+
+ public PopupButton(Icon icon) {
+ super(icon);
+ initListeners();
+ }
+
+ public PopupButton(String text) {
+ super(text);
+ initListeners();
+ }
+
+ public PopupButton(String text, Icon icon) {
+ super(text, icon);
+ initListeners();
+ }
+
+ private void initListeners() {
+ addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ insideButton = true;
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ insideButton = false;
+ }
+ });
+ addActionListener(this::popupOpenActionPerformed);
+ popupListener = new PopupMenuListener() {
+ @Override
+ public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+
+ }
+
+ @Override
+ public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+ if (!insideButton) {
+ PopupButton.this.setSelected(false);
+ }
+ if (popupMenu != null) {
+ popupMenu.removePopupMenuListener(popupListener);
+ }
+ }
+
+ @Override
+ public void popupMenuCanceled(PopupMenuEvent e) {
+
+ }
+ };
+ }
+
+ /**
+ * Method for getting (constructing?) a popup menu
+ * @return A popup menu
+ */
+ protected abstract JPopupMenu getPopupMenu();
+
+ private void popupOpenActionPerformed(ActionEvent evt) {
+ if (!isSelected()) {
+ return;
+ }
+
+ popupMenu = getPopupMenu();
+ popupMenu.addPopupMenuListener(popupListener);
+
+ JToggleButton sourceButton = (JToggleButton) evt.getSource();
+ popupMenu.show(sourceButton, 0, sourceButton.getHeight());
+ }
+}
diff --git a/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java b/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java
index 7144c3e7c..2ecf22982 100644
--- a/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java
+++ b/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java
@@ -92,6 +92,11 @@ public class SoundTagPlayer implements MediaDisplay {
private int instanceId = totalInstances++;
+ @Override
+ public boolean canUseSnapping() {
+ return false;
+ }
+
public int getInstanceId() {
return instanceId;
}
diff --git a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java
index 627c7f67e..47de868f3 100644
--- a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java
+++ b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java
@@ -57,6 +57,7 @@ import com.jpexs.decompiler.flash.gui.FasterScrollPane;
import com.jpexs.decompiler.flash.gui.HeaderLabel;
import com.jpexs.decompiler.flash.gui.Main;
import com.jpexs.decompiler.flash.gui.MainPanel;
+import com.jpexs.decompiler.flash.gui.PopupButton;
import com.jpexs.decompiler.flash.gui.SearchListener;
import com.jpexs.decompiler.flash.gui.SearchPanel;
import com.jpexs.decompiler.flash.gui.TagEditorPanel;
@@ -978,8 +979,23 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener();
libraryComboBox.addItem("AIR (airglobal.swc)");
@@ -1029,9 +1047,8 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener saveListeners = new ArrayList<>();
public void addSaveListener(ActionListener listener) {
@@ -74,8 +77,19 @@ public class LinkDialog extends JDialog {
}
}
- public LinkDialog(MainPanel mainPanel) {
+ public LinkDialog(MainPanel mainPanel, JToggleButton linkButton) {
this.mainPanel = mainPanel;
+ linkButton.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ overLinkButton = true;
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ overLinkButton = false;
+ }
+ });
setUndecorated(true);
setResizable(false);
getRootPane().setWindowDecorationStyle(JRootPane.NONE);
@@ -86,6 +100,9 @@ public class LinkDialog extends JDialog {
public void windowDeactivated(WindowEvent e) {
save(swf, false);
swf = null;
+ if (!overLinkButton) {
+ linkButton.setSelected(false);
+ }
dispose();
}
});
diff --git a/src/com/jpexs/decompiler/flash/gui/abc/SnapOptionsButton.java b/src/com/jpexs/decompiler/flash/gui/abc/SnapOptionsButton.java
new file mode 100644
index 000000000..640304546
--- /dev/null
+++ b/src/com/jpexs/decompiler/flash/gui/abc/SnapOptionsButton.java
@@ -0,0 +1,91 @@
+/*
+ * 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.abc;
+
+import com.jpexs.decompiler.flash.configuration.Configuration;
+import com.jpexs.decompiler.flash.gui.AppStrings;
+import com.jpexs.decompiler.flash.gui.PopupButton;
+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.JPopupMenu;
+
+/**
+ *
+ * @author JPEXS
+ */
+public class SnapOptionsButton extends PopupButton {
+
+
+ public SnapOptionsButton() {
+ super(View.getIcon("snap16"));
+ setToolTipText(AppStrings.translate("button.snap_options"));
+ setMargin(new Insets(0, 0, 0, 0));
+ }
+
+ @Override
+ protected JPopupMenu getPopupMenu() {
+ JPopupMenu popupMenu = new JPopupMenu();
+
+ JCheckBox snapAlignMenuItem = new JCheckBox(AppStrings.translate("snap_options.snap_align"));
+ snapAlignMenuItem.setSelected(Configuration.snapAlign.get());
+ snapAlignMenuItem.addActionListener(this::snapAlignMenuItemActionPerformed);
+ popupMenu.add(snapAlignMenuItem);
+
+ JCheckBox snapToGuidesMenuItem = new JCheckBox(AppStrings.translate("snap_options.snap_to_guides"));
+ snapToGuidesMenuItem.setSelected(Configuration.snapToGuides.get());
+ snapToGuidesMenuItem.addActionListener(this::snapToGuidesMenuItemActionPerformed);
+ popupMenu.add(snapToGuidesMenuItem);
+
+ JCheckBox snapToPixelsMenuItem = new JCheckBox(AppStrings.translate("snap_options.snap_to_pixels"));
+ snapToPixelsMenuItem.setSelected(Configuration.snapToPixels.get());
+ snapToPixelsMenuItem.addActionListener(this::snapToPixelsMenuItemActionPerformed);
+ popupMenu.add(snapToPixelsMenuItem);
+
+ JCheckBox snapToObjectsMenuItem = new JCheckBox(AppStrings.translate("snap_options.snap_to_objects"));
+ snapToObjectsMenuItem.setSelected(Configuration.snapToObjects.get());
+ snapToObjectsMenuItem.addActionListener(this::snapToObjectsMenuItemActionPerformed);
+ popupMenu.add(snapToObjectsMenuItem);
+
+ return popupMenu;
+ }
+
+ private void snapAlignMenuItemActionPerformed(ActionEvent evt) {
+ JCheckBox menuItem = (JCheckBox) evt.getSource();
+ Configuration.snapAlign.set(menuItem.isSelected());
+ }
+
+ private void snapToGuidesMenuItemActionPerformed(ActionEvent evt) {
+ JCheckBox menuItem = (JCheckBox) evt.getSource();
+ Configuration.snapToGuides.set(menuItem.isSelected());
+ }
+
+ private void snapToPixelsMenuItemActionPerformed(ActionEvent evt) {
+ JCheckBox menuItem = (JCheckBox) evt.getSource();
+ Configuration.snapToPixels.set(menuItem.isSelected());
+ }
+
+ private void snapToObjectsMenuItemActionPerformed(ActionEvent evt) {
+ JCheckBox menuItem = (JCheckBox) evt.getSource();
+ Configuration.snapToObjects.set(menuItem.isSelected());
+ }
+
+}
diff --git a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java
index 2c316888e..97775de56 100644
--- a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java
+++ b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java
@@ -47,6 +47,7 @@ import com.jpexs.decompiler.flash.gui.GraphDialog;
import com.jpexs.decompiler.flash.gui.HeaderLabel;
import com.jpexs.decompiler.flash.gui.Main;
import com.jpexs.decompiler.flash.gui.MainPanel;
+import com.jpexs.decompiler.flash.gui.PopupButton;
import com.jpexs.decompiler.flash.gui.ScrollablePanel;
import com.jpexs.decompiler.flash.gui.SearchListener;
import com.jpexs.decompiler.flash.gui.SearchPanel;
@@ -92,6 +93,7 @@ import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
+import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
@@ -196,8 +198,8 @@ public class ActionPanel extends JPanel implements SearchListener {
decompiledEditor.setShowMarkers(false);
if (src.getSwf().needsCalculatingAS2UninitializeClassTraits(src)) {
@@ -946,8 +948,23 @@ public class ActionPanel extends JPanel implements SearchListener