Added: Display grid, snap to grid

This commit is contained in:
Jindra Petřík
2025-05-07 00:40:58 +02:00
parent e39ad70255
commit aff0b8aea3
8 changed files with 223 additions and 4 deletions

View File

@@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file.
- Objects dragging - show touch point and snap it to 9 important points around object rectangle
- [#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
### Fixed
- [#2424] DefineEditText handling of letterSpacing, font size on incorrect values

View File

@@ -1081,6 +1081,26 @@ public final class Configuration {
@ConfigurationCategory("display")
public static ConfigurationItem<Boolean> lockGuides = null;
@ConfigurationDefaultBoolean(false)
@ConfigurationCategory("display")
public static ConfigurationItem<Boolean> showGrid = null;
@ConfigurationDefaultInt(10)
@ConfigurationCategory("display")
public static ConfigurationItem<Integer> gridVerticalSpace = null;
@ConfigurationDefaultInt(10)
@ConfigurationCategory("display")
public static ConfigurationItem<Integer> gridHorizontalSpace = null;
@ConfigurationDefaultBoolean(false)
@ConfigurationCategory("display")
public static ConfigurationItem<Boolean> snapToGrid = null;
@ConfigurationDefaultBoolean(false)
@ConfigurationCategory("display")
public static ConfigurationItem<Boolean> gridOverObjects = null;
private enum OSId {
WINDOWS, OSX, UNIX
}

View File

@@ -910,6 +910,54 @@ 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(new Color(0x94, 0x94, 0x94));
double x;
double y;
int ix;
int iy;
int minIx = 0;
int minIy = 0;
int maxIx;
int maxIy;
ix = 0;
while ((double) realRect.x + ix * Configuration.gridHorizontalSpace.get() * zoom < realRect.getMaxX()) {
ix++;
}
maxIx = ix;
iy = 0;
while ((double) realRect.y + iy * Configuration.gridVerticalSpace.get() * zoom < realRect.getMaxY()) {
iy++;
}
maxIy = iy;
for (ix = minIx; ix <= maxIx; ix++) {
x = realRect.x + ix * Configuration.gridHorizontalSpace.get() * zoom;
Point2D p1 = new Point2D.Double(x, realRect.y + minIy * Configuration.gridVerticalSpace.get() * zoom);
Point2D p2 = new Point2D.Double(x, realRect.y + maxIy * Configuration.gridVerticalSpace.get() * zoom);
g.drawLine(
(int) Math.round(p1.getX()),
(int) Math.round(p1.getY()),
(int) Math.round(p2.getX()),
(int) Math.round(p2.getY())
);
}
for (iy = minIy; iy <= maxIy; iy++) {
y = realRect.y + iy * Configuration.gridVerticalSpace.get() * zoom;
Point2D p1 = new Point2D.Double(realRect.x + minIx * Configuration.gridHorizontalSpace.get() * zoom, y);
Point2D p2 = new Point2D.Double(realRect.x + maxIx * Configuration.gridHorizontalSpace.get() * zoom, y);
g.drawLine(
(int) Math.round(p1.getX()),
(int) Math.round(p1.getY()),
(int) Math.round(p2.getX()),
(int) Math.round(p2.getY())
);
}
}
private class IconPanel extends JPanel {
@@ -959,7 +1007,69 @@ 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(new Color(0x94, 0x94, 0x94));
double gx;
double gy;
int ix = 0;
int iy = 0;
int minIx;
int minIy;
int maxIx;
int maxIy;
double sx = x + offsetPoint.getX();
double sy = y + offsetPoint.getY();
while (sx + ix * Configuration.gridHorizontalSpace.get() * zoomDouble > 0) {
ix--;
}
minIx = ix;
ix = 0;
while (sx + ix * Configuration.gridHorizontalSpace.get() * zoomDouble < getWidth()) {
ix++;
}
maxIx = ix;
while (sy + iy * Configuration.gridVerticalSpace.get() * zoomDouble > 0) {
iy--;
}
minIy = iy;
iy = 0;
while (sy + iy * Configuration.gridVerticalSpace.get() * zoomDouble < getHeight()) {
iy++;
}
maxIy = iy;
for (ix = minIx; ix <= maxIx; ix++) {
gx = sx + ix * Configuration.gridHorizontalSpace.get() * zoomDouble;
Point2D p1 = new Point2D.Double(gx, sy + minIy * Configuration.gridVerticalSpace.get() * zoomDouble);
Point2D p2 = new Point2D.Double(gx, sy + maxIy * Configuration.gridVerticalSpace.get() * zoomDouble);
g2.drawLine(
(int) Math.round(p1.getX()),
(int) Math.round(p1.getY()),
(int) Math.round(p2.getX()),
(int) Math.round(p2.getY())
);
}
for (iy = minIy; iy <= maxIy; iy++) {
gy = sy + iy * Configuration.gridVerticalSpace.get() * zoomDouble;
Point2D p1 = new Point2D.Double(sx + minIx * Configuration.gridHorizontalSpace.get() * zoomDouble, gy);
Point2D p2 = new Point2D.Double(sx + maxIx * Configuration.gridHorizontalSpace.get() * zoomDouble, gy);
g2.drawLine(
(int) Math.round(p1.getX()),
(int) Math.round(p1.getY()),
(int) Math.round(p2.getX()),
(int) Math.round(p2.getY())
);
}
}
public void render() {
SerializableImage img = getImg();
@@ -1001,8 +1111,18 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
y = (int) offsetPoint.getY();
}
double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value;
if (Configuration.showGrid.get() && !(timelined instanceof SWF) && !Configuration.gridOverObjects.get()) {
drawGridNoSwf(g2, x, y);
}
g2.drawImage(img.getBufferedImage(), x, y, x + img.getWidth(), y + img.getHeight(), 0, 0, img.getWidth(), img.getHeight(), null);
if (Configuration.showGrid.get() && !(timelined instanceof SWF) && Configuration.gridOverObjects.get()) {
drawGridNoSwf(g2, x, y);
}
if (hilightedEdge != null || hilightedPoints != null) {
hilightEdgeColor += hilightEdgeColorStep;
if (hilightEdgeColor < 100 || hilightEdgeColor > 255) {
@@ -1010,7 +1130,6 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
hilightEdgeColor += hilightEdgeColorStep * 2;
}
RECT timRect = timelined.getRect();
double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value;
AffineTransform trans = new AffineTransform();
trans.translate(offsetPoint.getX(), offsetPoint.getY());
trans.scale(1 / SWF.unitDivisor, 1 / SWF.unitDivisor);
@@ -1104,7 +1223,6 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
if (!(timelined instanceof SWF) && (doFreeTransform || hilightedPoints != null)) {
int axisX = 0;
int axisY = 0;
double zoomDouble = zoom.fit ? getZoomToFit() : zoom.value;
RECT timRect = timelined.getRect();
axisX = (int) Math.round(offsetPoint.getX());
axisY = (int) Math.round(offsetPoint.getY());
@@ -2174,6 +2292,27 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
}
}
if (Configuration.showGrid.get() && Configuration.snapToGrid.get()) {
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) {
snapOffsetX = d * zoomDouble - touchPointPos.getX() + offsetPoint.getX();
} else if ((d + Configuration.gridHorizontalSpace.get() - positionPxX) * zoomDouble < SNAP_DISTANCE) {
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) {
snapOffsetY = d * zoomDouble - touchPointPos.getY() + offsetPoint.getY();
} else if ((d + Configuration.gridVerticalSpace.get() - positionPxY) * zoomDouble < SNAP_DISTANCE) {
snapOffsetY = (d + Configuration.gridVerticalSpace.get()) * zoomDouble - touchPointPos.getY() + offsetPoint.getY();
}
}
}
if (Configuration.snapToPixels.get()) {
if (snapOffsetX == null) {
int positionPxX = (int) Math.round((touchPointPos.getX() - offsetPoint.getX()) / zoomDouble);
@@ -4633,6 +4772,11 @@ public final class ImagePanel extends JPanel implements MediaDisplay {
g.fillRect(realRect.x, realRect.y, realRect.width, realRect.height);
}
if (Configuration.showGrid.get() && (drawable instanceof SWF) && !Configuration.gridOverObjects.get()) {
Graphics2D g = (Graphics2D) image.getBufferedImage().getGraphics();
drawGridSwf(g, realRect, zoom);
}
parentMatrix = new Matrix();
List<Integer> ignoreDepths = new ArrayList<>();
for (int i = 0; i < parentTimelineds.size(); i++) {
@@ -4774,6 +4918,13 @@ 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) {

View File

@@ -50,6 +50,11 @@ public class SnapOptionsButton extends PopupButton {
snapAlignMenuItem.addActionListener(this::snapAlignMenuItemActionPerformed);
popupMenu.add(snapAlignMenuItem);
JCheckBox snapToGridMenuItem = new JCheckBox(AppStrings.translate("snap_options.snap_to_grid"));
snapToGridMenuItem.setSelected(Configuration.snapToGrid.get());
snapToGridMenuItem.addActionListener(this::snapToGridMenuItemActionPerformed);
popupMenu.add(snapToGridMenuItem);
JCheckBox snapToGuidesMenuItem = new JCheckBox(AppStrings.translate("snap_options.snap_to_guides"));
snapToGuidesMenuItem.setSelected(Configuration.snapToGuides.get());
snapToGuidesMenuItem.addActionListener(this::snapToGuidesMenuItemActionPerformed);
@@ -73,6 +78,11 @@ public class SnapOptionsButton extends PopupButton {
Configuration.snapAlign.set(menuItem.isSelected());
}
private void snapToGridMenuItemActionPerformed(ActionEvent evt) {
JCheckBox menuItem = (JCheckBox) evt.getSource();
Configuration.snapToGrid.set(menuItem.isSelected());
}
private void snapToGuidesMenuItemActionPerformed(ActionEvent evt) {
JCheckBox menuItem = (JCheckBox) evt.getSource();
Configuration.snapToGuides.set(menuItem.isSelected());

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -577,3 +577,19 @@ config.description.showGuides = Set this to false to hide guides.
config.name.lockGuides = Lock guides
config.description.lockGuides = Disables moving of guides so they stay in position and cannot be moved.
config.name.showGrid = Show grid
config.description.showGrid = Shows grid on stage
config.name.gridVerticalSpace = Grid vertical spacing (px)
config.description.gridVerticalSpace = Vertical space between grid lines in pixels.
config.name.gridHorizontalSpace = Grid horizontal spacing (px)
config.description.gridHorizontalSpace = Horizontal space between grid lines in pixels.
config.name.snapToGrid = Snap to grid
config.description.snapToGrid = Enables snapping cursor to grid while moving objects.
config.name.gridOverObjects = Show grid over objects
config.description.gridOverObjects = When the grid is displayed, it is shown over objects on stage.

View File

@@ -1025,6 +1025,7 @@ filter.swf_spl = SWF files (*.swf, *.spl)
#after 22.0.2
button.snap_options = Snap options
snap_options.snap_align = Snap Align
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
@@ -1034,4 +1035,6 @@ button.ruler.hint = Toggle ruler display
button.guides_options.hint = Guides options
guides_options.show = Show guides
guides_options.lock = Lock guides
guides_options.clear = Clear guides
guides_options.clear = Clear guides
button.grid.hint = Toggle grid display

View File

@@ -105,6 +105,18 @@ public class ZoomPanel extends JPanel implements MediaDisplayListener {
guidesOptionsButton.setToolTipText(AppStrings.translate("button.guides_options.hint"));
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<Boolean>() {
@Override
public void configurationItemChanged(Boolean newValue) {
gridButton.setSelected(newValue);
}
});
setLayout(new FlowLayout());
add(percentLabel);
@@ -113,11 +125,17 @@ public class ZoomPanel extends JPanel implements MediaDisplayListener {
add(zoomNoneButton);
add(zoomFitButton);
add(rulerButton);
add(gridButton);
add(guidesOptionsButton);
add(snapOptionsButton);
display.addEventListener(this);
}
private void gridButtonActionPerformed(ActionEvent evt) {
JToggleButton source = (JToggleButton) evt.getSource();
Configuration.showGrid.set(source.isSelected());
}
private void guidesShowActionPerformed(ActionEvent evt) {
JCheckBoxMenuItem source = (JCheckBoxMenuItem) evt.getSource();