diff --git a/diylc/diylc-core/src/main/java/org/diylc/components/AbstractComponent.java b/diylc/diylc-core/src/main/java/org/diylc/components/AbstractComponent.java index 64ef5bba3..288b1e30d 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/components/AbstractComponent.java +++ b/diylc/diylc-core/src/main/java/org/diylc/components/AbstractComponent.java @@ -181,10 +181,26 @@ public abstract class AbstractComponent implements IDIYComponent { maxY = p.getY(); } } - Rectangle2D rect = new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY); + double margin = getClipMargin(); + Rectangle2D rect = new Rectangle2D.Double(minX - margin, minY - margin, + maxX - minX + 2 * margin, maxY - minY + 2 * margin); return !clip.intersects(rect); } + /** + * Distance in pixels that the drawing may reach beyond the bounding box of the control points, + * such as half the width of a stroke running through them. {@link #checkPointsClipped(Shape)} + * widens the box by this much before testing it against the clip. The margin must stay positive, + * because {@link Shape#intersects(Rectangle2D)} reports a box with no width or height as disjoint + * from everything, which would hide a straight horizontal or vertical component whose control + * points all lie outside the clip. + * + * @return margin around the control points. + */ + protected double getClipMargin() { + return 1; + } + @SuppressWarnings("unchecked") public IDIYComponent clone() throws CloneNotSupportedException { try { diff --git a/diylc/diylc-core/src/test/java/org/diylc/components/AbstractCurvedComponentTest.java b/diylc/diylc-core/src/test/java/org/diylc/components/AbstractCurvedComponentTest.java index d14a0bd5b..b12d6d249 100644 --- a/diylc/diylc-core/src/test/java/org/diylc/components/AbstractCurvedComponentTest.java +++ b/diylc/diylc-core/src/test/java/org/diylc/components/AbstractCurvedComponentTest.java @@ -22,10 +22,12 @@ package org.diylc.components; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.awt.Color; import java.awt.Graphics2D; +import java.awt.Rectangle; import java.awt.geom.Path2D; import java.awt.geom.Point2D; @@ -172,6 +174,37 @@ public class AbstractCurvedComponentTest { } } + @Test + public void checkPointsClipped_keepsAStraightRunThatCrossesTheClipWithBothEndsOutside() { + CurvedTestComponent horizontal = new CurvedTestComponent(); + horizontal.setPointCount(PointCount.TWO); + horizontal.stretchBetween(new Point2D.Double(-100, 50), new Point2D.Double(300, 50)); + assertFalse(horizontal.checkPointsClipped(new Rectangle(0, 0, 200, 100))); + + CurvedTestComponent vertical = new CurvedTestComponent(); + vertical.setPointCount(PointCount.TWO); + vertical.stretchBetween(new Point2D.Double(50, -100), new Point2D.Double(50, 300)); + assertFalse(vertical.checkPointsClipped(new Rectangle(0, 0, 200, 100))); + } + + @Test + public void checkPointsClipped_skipsAStraightRunThatMissesTheClip() { + CurvedTestComponent c = new CurvedTestComponent(); + c.setPointCount(PointCount.TWO); + c.stretchBetween(new Point2D.Double(-100, 500), new Point2D.Double(300, 500)); + assertTrue(c.checkPointsClipped(new Rectangle(0, 0, 200, 100))); + } + + @Test + public void checkPointsClipped_keepsARunWhoseStrokeReachesIntoTheClip() { + CurvedTestComponent c = new CurvedTestComponent(); + c.setPointCount(PointCount.TWO); + c.stretchBetween(new Point2D.Double(-100, 105), new Point2D.Double(300, 105)); + assertTrue(c.checkPointsClipped(new Rectangle(0, 0, 200, 100))); + c.clipMargin = 10; + assertFalse(c.checkPointsClipped(new Rectangle(0, 0, 200, 100))); + } + private static void assertPoint(double x, double y, Point2D actual) { assertEquals(x, actual.getX(), DELTA); assertEquals(y, actual.getY(), DELTA); @@ -181,6 +214,13 @@ public class AbstractCurvedComponentTest { private static final long serialVersionUID = 1L; + private double clipMargin = 1; + + @Override + protected double getClipMargin() { + return clipMargin; + } + @Override protected void drawCurve(Path2D curve, Graphics2D g2d, ComponentState componentState, IDrawingObserver drawingObserver) {} diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CurvedTrace.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CurvedTrace.java index 443a6bdcd..021bb9744 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CurvedTrace.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CurvedTrace.java @@ -68,6 +68,11 @@ public class CurvedTrace extends AbstractCurvedComponent implements ILayer return COLOR; } + @Override + protected double getClipMargin() { + return size.convertToPixels() / 2 + 1; + } + @Override protected void drawCurve(Path2D curve, Graphics2D g2d, ComponentState componentState, IDrawingObserver drawingObserver) { float thickness = (float) size.convertToPixels(); diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/HookupWire.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/HookupWire.java index 735e960fd..ecedf149a 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/HookupWire.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/HookupWire.java @@ -61,9 +61,18 @@ public class HookupWire extends AbstractCurvedComponent implements IContinu return COLOR; } + private int getThickness() { + return (int) (gauge.diameterIn() * Constants.PIXELS_PER_INCH * (1 + 2 * INSULATION_THICKNESS_PCT)) - 1; + } + + @Override + protected double getClipMargin() { + return getThickness() / 2d + 1; + } + @Override protected void drawCurve(Path2D curve, Graphics2D g2d, ComponentState componentState, IDrawingObserver drawingObserver) { - int thickness = (int) (gauge.diameterIn() * Constants.PIXELS_PER_INCH * (1 + 2 * INSULATION_THICKNESS_PCT)) - 1; + int thickness = getThickness(); Color curveColor = componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ? SELECTION_COLOR : color; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/OrthogonalLine.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/OrthogonalLine.java index ed16a2766..f0b426b5b 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/OrthogonalLine.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/OrthogonalLine.java @@ -78,6 +78,12 @@ public class OrthogonalLine extends AbstractOrthogonalComponent implements g2d.draw(createIconPath(width, height)); } + // an arrow head spreads one arrow size to either side of the end point it sits on + @Override + protected double getClipMargin() { + return Math.max(getThickness().convertToPixels() / 2, getArrowSize().convertToPixels()) + 1; + } + @Override protected void drawCurve(Path2D curve, Graphics2D g2d, ComponentState componentState, IDrawingObserver drawingObserver) { diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/OrthogonalWire.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/OrthogonalWire.java index 3353fe8a7..d10a41e8e 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/OrthogonalWire.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/OrthogonalWire.java @@ -65,9 +65,18 @@ public class OrthogonalWire extends AbstractOrthogonalComponent implements return COLOR; } + private int getThickness() { + return (int) (gauge.diameterIn() * Constants.PIXELS_PER_INCH * (1 + 2 * INSULATION_THICKNESS_PCT)) - 1; + } + + @Override + protected double getClipMargin() { + return getThickness() / 2d + 1; + } + @Override protected void drawCurve(Path2D curve, Graphics2D g2d, ComponentState componentState, IDrawingObserver drawingObserver) { - int thickness = (int) (gauge.diameterIn() * Constants.PIXELS_PER_INCH * (1 + 2 * INSULATION_THICKNESS_PCT)) - 1; + int thickness = getThickness(); Color curveColor = componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ? SELECTION_COLOR : color; diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/TwistedWire.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/TwistedWire.java index 18e2d6d3b..1bcae383a 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/TwistedWire.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/TwistedWire.java @@ -83,12 +83,21 @@ public class TwistedWire extends AbstractCurvedComponent implements IConti return COLOR; } + private int getThickness() { + return (int) (gauge.diameterIn() * Constants.PIXELS_PER_INCH * (1 + 2 * INSULATION_THICKNESS_PCT)) + - 1; + } + + // the two leads swing out to either side of the curve as they twist, see recalculate + @Override + protected double getClipMargin() { + return getThickness() * 2 + 1; + } + @Override protected void drawCurve(Path2D curve, Graphics2D g2d, ComponentState componentState, IDrawingObserver drawingObserver) { - int thickness = - (int) (gauge.diameterIn() * Constants.PIXELS_PER_INCH * (1 + 2 * INSULATION_THICKNESS_PCT)) - - 1; + int thickness = getThickness(); Color curveColor1 = componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ? SELECTION_COLOR