From 2ea643aec33d16257b84fe3f61e301d0a3ece570 Mon Sep 17 00:00:00 2001 From: Jussi Koskela Date: Tue, 25 Aug 2020 13:54:39 +0300 Subject: [PATCH] Match SelectionNode's size and shape with the element pick area gitlab simantics/platform#587 Change-Id: Ic492a6f2dbe0af7acfdbf4eed6cfecccf01e24f3 --- .../simantics/g2d/diagram/DiagramHints.java | 1 + .../diagram/participant/ElementPainter.java | 9 -- .../scenegraph/g2d/nodes/SelectionNode.java | 106 +++++++++++++----- 3 files changed, 81 insertions(+), 35 deletions(-) diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramHints.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramHints.java index 9b36d845f..4c5f0f12d 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramHints.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramHints.java @@ -190,6 +190,7 @@ public class DiagramHints { * For specifying a user-defined padding for selections * * @since 1.33.0 + * @deprecated in favor of G2DSceneGraph.PICK_DISTANCE */ public static final Key SELECTION_PADDING_SCALE_FACTOR = new KeyOf(Double.class, "SELECTION_PADDING_SCALE_FACTOR"); diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java index d4d0609b2..ade560852 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java @@ -94,7 +94,6 @@ import org.simantics.scenegraph.g2d.nodes.SingleElementNode; import org.simantics.scenegraph.g2d.nodes.UnboundedNode; import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode; import org.simantics.scenegraph.utils.ColorUtil; -import org.simantics.scenegraph.utils.GeometryUtils; import org.simantics.scenegraph.utils.NodeUtil; import org.simantics.utils.datastructures.collections.CollectionUtils; import org.simantics.utils.datastructures.hints.HintListenerAdapter; @@ -1043,11 +1042,6 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos Rectangle2D bounds = shape.getBounds2D(); //System.out.println("selection bounds: "+bounds); - Point2D scale = GeometryUtils.getScale2D(selectionTransform); - final double marginX = Math.abs(scale.getX()) > 1e-10 ? 1 / scale.getX() : 1; - final double marginY = Math.abs(scale.getY()) > 1e-10 ? 1 / scale.getY() : 1; - - bounds.setFrame(bounds.getMinX() - marginX, bounds.getMinY() - marginY, bounds.getWidth() + 2*marginX, bounds.getHeight() + 2*marginY); List ss = e.getElementClass().getItemsByClass(SelectionSpecification.class); if (!ss.isEmpty()) { @@ -1117,9 +1111,6 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos } else { SelectionNode s = selectionNode.getOrCreateNode(getNodeId("shape", e), SelectionNode.class); s.init(selectionId, selectionTransform, bounds, color); - Double paddingFactor = diagram.getHint(DiagramHints.SELECTION_PADDING_SCALE_FACTOR); - if (paddingFactor != null) - s.setPaddingFactor(paddingFactor); } } diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SelectionNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SelectionNode.java index 98aaebb25..2bdf5da74 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SelectionNode.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SelectionNode.java @@ -11,16 +11,19 @@ *******************************************************************************/ package org.simantics.scenegraph.g2d.nodes; -import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; -import java.awt.Composite; import java.awt.Graphics2D; +import java.awt.Shape; import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import org.apache.batik.ext.awt.geom.Polygon2D; import org.simantics.scenegraph.g2d.G2DNode; +import org.simantics.scenegraph.g2d.G2DSceneGraph; import org.simantics.scenegraph.utils.GeometryUtils; +import org.simantics.scenegraph.utils.NodeUtil; public class SelectionNode extends G2DNode implements Decoration { /** @@ -38,7 +41,6 @@ public class SelectionNode extends G2DNode implements Decoration { protected transient BasicStroke scaledStroke; protected transient double previousScaleRecip = Double.NaN; protected boolean ignore = false; - protected double paddingFactor = 5.0; protected int selectionId; public int getSelectionId() { @@ -49,8 +51,10 @@ public class SelectionNode extends G2DNode implements Decoration { ignore = value; } + /** + * @deprecated in favor of G2DSceneGraph.PICK_DISTANCE + */ public void setPaddingFactor(double factor) { - paddingFactor = factor; } @SyncField({"transform", "bounds", "color"}) @@ -75,42 +79,92 @@ public class SelectionNode extends G2DNode implements Decoration { if (transform.getDeterminant() == 0) return; - AffineTransform ot = g.getTransform(); - - g.setColor(color); - g.transform(transform); - - AffineTransform tx = g.getTransform(); - //System.out.println("tx: " + tx); - double scale = GeometryUtils.getScale(tx); - //System.out.println("scale: " + scale); - double scaleRecip = 1.0 / scale; - //System.out.println("scale: " + scaleRecip); - + NavigationNode nn = NodeUtil.findNearestParentNode(this, NavigationNode.class); + double scale = 1.0; + if (nn != null) { + scale = GeometryUtils.getScale(nn.getTransform()); + } + double scaleRecip = 1 / scale; // Prevent stroke reallocation while panning. // Zooming will trigger reallocation. if (scaledStroke == null || scaleRecip != previousScaleRecip) { scaledStroke = GeometryUtils.scaleStroke( SELECTION_STROKE, (float) scaleRecip); previousScaleRecip = scaleRecip; } - g.setStroke(scaledStroke); - double padding = paddingFactor * scaleRecip; - double paddingX = padding; - double paddingY = padding; + G2DSceneGraph sg = NodeUtil.getRootNode(nn); + double padding = sg.getGlobalProperty(G2DSceneGraph.PICK_DISTANCE, 0.0) / scale; + + g.setStroke(scaledStroke); + g.setColor(color); + Shape selectionShape = transformAndExpand(bounds, transform, padding); + g.draw(selectionShape); if (rect == null) rect = new Rectangle2D.Double(); - rect.setFrame(bounds.getMinX() - paddingX, bounds.getMinY() - paddingY, - bounds.getWidth() + 2.0*paddingX, bounds.getHeight() + 2.0*paddingY); - g.draw(rect); - - g.setTransform(ot); + Rectangle2D r = transform.createTransformedShape(bounds).getBounds2D(); + rect.setFrame(r.getMinX() - padding, r.getMinY() - padding, + r.getWidth() + 2.0*padding, r.getHeight() + 2.0*padding); + } + private static Shape transformAndExpand(Rectangle2D r, AffineTransform t, double padding) { + + if ((t.getShearX() == 0 && t.getShearY() == 0) || t.getScaleX() == 0 && t.getScaleY() == 0) { + // Simple case for axis-aligned selection + Rectangle2D result = t.createTransformedShape(r).getBounds2D(); + result.setRect(result.getMinX() - padding, result.getMinY() - padding, result.getWidth() + 2 * padding, result.getHeight() + 2 * padding); + return result; + } else { + // General case + Point2D.Double corners[] = new Point2D.Double[4]; + corners[0] = new Point2D.Double(r.getMinX(), r.getMinY()); + corners[1] = new Point2D.Double(r.getMinX(), r.getMaxY()); + corners[2] = new Point2D.Double(r.getMaxX(), r.getMaxY()); + corners[3] = new Point2D.Double(r.getMaxX(), r.getMinY()); + t.transform(corners, 0, corners, 0, 4); + + double det = t.getDeterminant(); + double step = Math.PI / 2; + double diagonalPadding = padding * Math.sqrt(2); + + Polygon2D poly = new Polygon2D(); + for (int edge = 0; edge < 4; edge++) { + int p0 = edge % 4; + int p1 = (edge + 1) % 4; + int p2 = (edge + 2) % 4; + + double a1 = Math.atan2(corners[p1].y - corners[p0].y, corners[p1].x - corners[p0].x); + double a2 = Math.atan2(corners[p2].y - corners[p1].y, corners[p2].x - corners[p1].x); + if (det > 0) { + if (a1 < a2) { + a1 += Math.PI * 2; + } + int dir1 = (int)Math.ceil(a1 / step); + int dir2 = (int)Math.floor(a2 / step); + for (int dir = dir1; dir > dir2; dir--) { + poly.addPoint(new Point2D.Double( + corners[p1].x + Math.cos((dir + 0.5) * step) * diagonalPadding, + corners[p1].y + Math.sin((dir + 0.5) * step) * diagonalPadding)); + } + } else { + if (a1 > a2) { + a2 += Math.PI * 2; + } + int dir1 = (int)Math.floor(a1 / step); + int dir2 = (int)Math.ceil(a2 / step); + for (int dir = dir1; dir < dir2; dir++) { + poly.addPoint(new Point2D.Double( + corners[p1].x + Math.cos((dir - 0.5) * step) * diagonalPadding, + corners[p1].y + Math.sin((dir - 0.5) * step) * diagonalPadding)); + } + } + } + return poly; + } } public Rectangle2D getRect() { - return transform.createTransformedShape(rect).getBounds2D(); + return rect; } @Override -- 2.47.1