diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/controls/GridControl.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/controls/GridControl.java index 517a422001..dd4eec2cb1 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/controls/GridControl.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/entities/controls/GridControl.java @@ -26,7 +26,14 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.ugs.nbp.designer.gui.Drawing; import com.willwinder.ugs.nbp.designer.logic.Controller; -import java.awt.*; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.util.Optional; @@ -36,69 +43,112 @@ This file is part of Universal Gcode Sender (UGS). */ public class GridControl extends AbstractEntity implements Control { - public static final int MINIMUM_SIZE = 300; - public static final int LARGE_GRID_SIZE = 50; - public static final int SMALL_GRID_SIZE = 10; + public static final int LARGE_GRID_SIZE = 100; + public static final int SMALL_GRID_SIZE = 20; private final Controller controller; public GridControl(Controller controller) { this.controller = controller; } - @Override - public void render(Graphics2D graphics, Drawing drawing) { - double gridSize = LARGE_GRID_SIZE; - - Rectangle2D bounds = controller.getDrawing().getRootEntity().getBounds(); - int calculatedMinimumWidth = (int) Math.round(Math.floor(bounds.getMaxX() / gridSize) * gridSize + (gridSize * 2)); - int calculatedMinimumHeight = (int) Math.round(Math.floor(bounds.getMaxY() / gridSize) * gridSize + (gridSize * 2)); - - int width = Math.max(calculatedMinimumWidth, MINIMUM_SIZE); - int height = Math.max(calculatedMinimumHeight, MINIMUM_SIZE); - - graphics.setColor(Color.WHITE); - graphics.fillRect(0, 0, width, height); + private static void drawZeroLines(Graphics2D graphics, Drawing drawing, int startPosX, int startPosY, int endPosX, int endPosY) { + // Draw zero lines + graphics.setStroke(new BasicStroke((float) (0.5 / drawing.getScale()))); + graphics.setColor(Color.LIGHT_GRAY); + graphics.drawLine(0, startPosY, 0, endPosY); + graphics.drawLine(startPosX, 0, endPosX, 0); + } - graphics.setStroke(new BasicStroke(Double.valueOf(0.1 / drawing.getScale()).floatValue())); + private static void drawSmallGrid(Graphics2D graphics, Drawing drawing, int startPosX, int startPosY, int endPosX, int endPosY, int gridSize) { + graphics.setStroke(new BasicStroke((float) (0.2 / drawing.getScale()))); graphics.setColor(Color.LIGHT_GRAY); - for (int x = 0; x <= width; x += SMALL_GRID_SIZE) { - graphics.drawLine(x, 0, x, height); + for (int x = startPosX; x <= endPosX; x += gridSize) { + graphics.drawLine(x, startPosY, x, endPosY); } - for (int y = 0; y <= height; y += SMALL_GRID_SIZE) { - graphics.drawLine(0, y, width, y); + for (int y = startPosY; y <= endPosY; y += gridSize) { + graphics.drawLine(startPosX, y, endPosX, y); } + } - - + private static void drawLargeGridAndText(Graphics2D graphics, Drawing drawing, int startPosX, int startPosY, int endPosX, int endPosY, int gridSize) { + Rectangle2D clipBounds = graphics.getClipBounds(); AffineTransform affineTransform = AffineTransform.getScaleInstance(1 / drawing.getScale(), -1 / drawing.getScale()); - affineTransform.rotate(Math.PI/2); + affineTransform.rotate(Math.PI / 2); Font font = new Font(null, Font.PLAIN, 10).deriveFont(affineTransform); graphics.setFont(font); FontMetrics fontMetrics = graphics.getFontMetrics(); - graphics.setStroke(new BasicStroke(Double.valueOf(0.2 / drawing.getScale()).floatValue())); - for (int x = 0; x <= width; x += LARGE_GRID_SIZE) { - graphics.drawLine(x, 0, x, height); + graphics.setStroke(new BasicStroke((float) (0.3 / drawing.getScale()))); + graphics.setColor(Color.LIGHT_GRAY); + + int fontOffset = (int) Math.round(6 / drawing.getScale()); + + for (int x = startPosX; x <= endPosX; x += gridSize) { + if (x < clipBounds.getMinX() || x > clipBounds.getMaxX()) { + continue; + } + graphics.drawLine(x, startPosY, x, endPosY); String text = x + " mm"; - int y = -fontMetrics.stringWidth(text); - graphics.drawString(text, x - (int) Math.round(3 / drawing.getScale()), y - (int) Math.round(8 / drawing.getScale())); + int y = -fontMetrics.stringWidth(text) - fontOffset; + graphics.drawString(text, x + fontOffset, y); } - affineTransform = AffineTransform.getScaleInstance(1 / drawing.getScale(), -1 / drawing.getScale()); font = new Font(null, Font.PLAIN, 10).deriveFont(affineTransform); graphics.setFont(font); fontMetrics = graphics.getFontMetrics(); - for (int y = 0; y <= height; y += LARGE_GRID_SIZE) { - graphics.drawLine(0, y, width, y); + for (int y = startPosY; y <= endPosY; y += gridSize) { + if (y < clipBounds.getMinY() || y > clipBounds.getMaxY()) { + continue; + } + graphics.drawLine(startPosX, y, endPosX, y); String text = y + " mm"; - int x = -fontMetrics.stringWidth(text); - graphics.drawString(text, x - (int) Math.round(8 / drawing.getScale()), y - (int) Math.round(3 / drawing.getScale())); + int x = -fontMetrics.stringWidth(text); + graphics.drawString(text, x - fontOffset, y + fontOffset); + } + } + + @Override + public void render(Graphics2D graphics, Drawing drawing) { + double gridSize = LARGE_GRID_SIZE; + + + Rectangle2D bounds = controller.getDrawing().getRootEntity().getBounds(); + double gridMargin = (gridSize * 100); + + + int startPosX = (int) Math.min(-gridMargin, (Math.floor(bounds.getMinX() / gridSize) * gridSize + gridMargin)); + int startPosY = (int) Math.min(-gridMargin, (Math.floor(bounds.getMinY() / gridSize) * gridSize + gridMargin)); + int endPosX = (int) Math.max(gridMargin, (Math.floor(bounds.getMaxX() / gridSize) * gridSize + gridMargin)); + int endPosY = (int) Math.max(gridMargin, (Math.floor(bounds.getMaxY() / gridSize) * gridSize + gridMargin)); + + int width = endPosX - startPosX; + int height = endPosY - startPosY; + graphics.setColor(Color.WHITE); + graphics.fillRect(startPosX, startPosY, width, height); + + int smallGridSize; + int largeGridSize; + if (drawing.getScale() < 1.3) { + smallGridSize = SMALL_GRID_SIZE; + largeGridSize = LARGE_GRID_SIZE; + } else if (drawing.getScale() < 4) { + smallGridSize = SMALL_GRID_SIZE / 2; + largeGridSize = LARGE_GRID_SIZE / 2; + } else if (drawing.getScale() < 12) { + smallGridSize = SMALL_GRID_SIZE / 4; + largeGridSize = LARGE_GRID_SIZE / 4; + } else { + smallGridSize = SMALL_GRID_SIZE / 10; + largeGridSize = LARGE_GRID_SIZE / 10; } + drawSmallGrid(graphics, drawing, startPosX, startPosY, endPosX, endPosY, smallGridSize); + drawLargeGridAndText(graphics, drawing, startPosX, startPosY, endPosX, endPosY, largeGridSize); + drawZeroLines(graphics, drawing, startPosX, startPosY, endPosX, endPosY); } @Override diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/Drawing.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/Drawing.java index 5f5ea101c2..8c04b76591 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/Drawing.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/Drawing.java @@ -47,6 +47,7 @@ This file is part of Universal Gcode Sender (UGS). import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.geom.AffineTransform; @@ -72,6 +73,9 @@ public class Drawing extends JPanel { private final transient Set listeners = Sets.newConcurrentHashSet(); private final transient Throttler refreshThrottler; private double scale; + private AffineTransform transform; + private long lastUpdate; + private final transient Rectangle2D currentBounds = new Rectangle(0, 0, 8, 8); public Drawing(Controller controller) { refreshThrottler = new Throttler(this::refresh, 2000); @@ -220,8 +224,10 @@ public void setScale(double scale) { @Override public Dimension getMinimumSize() { - Rectangle2D bounds = globalRoot.getBounds(); - return new Dimension((int) (bounds.getMaxX() * scale) + (MARGIN * 2), (int) (bounds.getMaxY() * scale) + (MARGIN * 2)); + int margin = (int) (MARGIN * 2 * scale); + int width = (int) (currentBounds.getWidth() * scale) + margin; + int height = (int) (currentBounds.getHeight() * scale) + margin; + return new Dimension(width, height); } @Override @@ -242,10 +248,17 @@ public void addListener(DrawingListener listener) { } public AffineTransform getTransform() { - AffineTransform transform = AffineTransform.getScaleInstance(1, -1); - transform.translate(0, -getHeight()); - transform.translate(MARGIN, MARGIN); - transform.scale(scale, scale); + // Don't update this every time or else it will be hard to move entites outside the canvas + if (System.currentTimeMillis() > lastUpdate + 50) { + + transform = AffineTransform.getScaleInstance(1, -1); + transform.translate(0, -getHeight()); + transform.scale(scale, scale); + transform.translate(MARGIN / 4d, MARGIN / 4d); + transform.translate(-getBounds().getMinX(), -getBounds().getMinY()); + lastUpdate = System.currentTimeMillis(); + } + return transform; } @@ -263,4 +276,16 @@ public List getControls() { public void clear() { entitiesRoot.removeAll(); } + + + @Override + public Rectangle getBounds() { + Rectangle2D bounds = globalRoot.getBounds(); + double minX = Math.min(currentBounds.getMinX(), bounds.getMinX()); + double minY = Math.min(currentBounds.getMinY(), bounds.getMinY()); + double maxX = Math.max(currentBounds.getMaxX(), bounds.getMaxX()); + double maxY = Math.max(currentBounds.getMaxY(), bounds.getMaxY()); + currentBounds.setRect(minX, minY, maxX - minX, maxY - minY); + return currentBounds.getBounds(); + } } diff --git a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/DrawingContainer.java b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/DrawingContainer.java index 0378c7ffad..331a3cea92 100644 --- a/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/DrawingContainer.java +++ b/ugs-platform/ugs-platform-plugin-designer/src/main/java/com/willwinder/ugs/nbp/designer/gui/DrawingContainer.java @@ -24,9 +24,22 @@ This file is part of Universal Gcode Sender (UGS). import com.willwinder.ugs.nbp.lib.lookup.CentralLookup; import com.willwinder.universalgcodesender.model.BackendAPI; -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; +import javax.swing.Box; +import javax.swing.JLayeredPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ScrollPaneConstants; +import javax.swing.SwingUtilities; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.KeyboardFocusManager; +import java.awt.Rectangle; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.KeyEvent; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; /** * A simple container that contains a Drawing instance and keeps it @@ -37,6 +50,7 @@ This file is part of Universal Gcode Sender (UGS). */ public class DrawingContainer extends JPanel implements ComponentListener, MouseWheelListener { + public static final double CENTER_ZOOM_SCALE_FACTOR = 0.8; private final transient Controller controller; private JScrollPane scrollPane; private JPanel buttonPanel; @@ -82,6 +96,7 @@ protected void processMouseWheelEvent(MouseWheelEvent e) { scrollPane.getHorizontalScrollBar().setUnitIncrement(5); scrollPane.setPreferredSize(getSize()); scrollPane.setBounds(0, 0, getWidth(), getHeight()); + scrollPane.setWheelScrollingEnabled(false); buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); buttonPanel.setOpaque(false); @@ -135,19 +150,22 @@ public void componentHidden(ComponentEvent e) { @Override public void mouseWheelMoved(MouseWheelEvent e) { BackendAPI backend = CentralLookup.getDefault().lookup(BackendAPI.class); - double scaleFactor = (e.getPreciseWheelRotation() * 0.1) * (backend.getSettings().isInvertMouseZoom() ? -1d : 1d); - controller.getDrawing().setScale(controller.getDrawing().getScale() + scaleFactor); + Rectangle viewRect = scrollPane.getViewport().getViewRect(); + Dimension size = scrollPane.getViewport().getView().getSize(); - double currentViewPortCenterX = (scrollPane.getHorizontalScrollBar().getValue() + (scrollPane.getWidth() / 2d)) / controller.getDrawing().getScale(); - double currentViewPortCenterY = (scrollPane.getVerticalScrollBar().getValue() + (scrollPane.getHeight() / 2d)) / controller.getDrawing().getScale(); + // Get the mouse position relative to the center + double mouseX = (e.getPoint().getX() - viewRect.getCenterX()) * CENTER_ZOOM_SCALE_FACTOR; + double mouseY = (e.getPoint().getY() - viewRect.getCenterY()) * CENTER_ZOOM_SCALE_FACTOR; - double mouseX = e.getPoint().getX() / controller.getDrawing().getScale(); - double mouseY = e.getPoint().getY() / controller.getDrawing().getScale(); + // Get the current view position in percent + double previousXScrollbarPercent = (viewRect.getX() + mouseX) / size.getWidth(); + double previousYScrollbarPercent = (viewRect.getY() + mouseY) / size.getHeight(); - double x = ((mouseX - currentViewPortCenterX) * controller.getDrawing().getScale()) * scaleFactor; - double y = ((mouseY - currentViewPortCenterY) * controller.getDrawing().getScale()) * scaleFactor; + // Apply the scaling + double scaleFactor = (e.getPreciseWheelRotation() * controller.getDrawing().getScale() * 0.1) * (backend.getSettings().isInvertMouseZoom() ? -1d : 1d); + controller.getDrawing().setScale(controller.getDrawing().getScale() + scaleFactor); - scrollPane.getHorizontalScrollBar().setValue(scrollPane.getHorizontalScrollBar().getValue() + (int) Math.round(x + 0.5)); - scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getValue() + (int) Math.round(y + 0.5)); + scrollPane.getHorizontalScrollBar().setValue((int) Math.round((controller.getDrawing().getMinimumSize().getWidth() * previousXScrollbarPercent))); + scrollPane.getVerticalScrollBar().setValue((int) Math.round((controller.getDrawing().getMinimumSize().getHeight() * previousYScrollbarPercent))); } }