From 86b54509bfe488f94266f56b1a8d97ac46a7e696 Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Thu, 12 Nov 2026 14:34:05 +0100 Subject: [PATCH] A board can no longer be selected, dragged or stretched through its own Underside Display, where dragging it moved the mirror under the mouse and the board went the wrong way --- .../org/diylc/presenter/DrawingManager.java | 9 +++-- .../java/org/diylc/presenter/Presenter.java | 2 +- .../org/diylc/presenter/UndersideMirror.java | 9 +++++ .../test/java/org/diylc/PresenterTests.java | 40 +++++++++++++++++++ diylc/docs/plans/bottom-side-components.md | 5 ++- 5 files changed, 59 insertions(+), 6 deletions(-) diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingManager.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingManager.java index cdab1e2fd..b1108df69 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingManager.java +++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingManager.java @@ -768,8 +768,9 @@ public class DrawingManager { } /** - * Finds the components that the underside mirror of a board shows at the specified point, top - * first, or none when the point is not in a mirror. + * Finds the components that the underside mirror of a board shows at the specified point and that + * can be edited through it, top first, or none when the point is not in a mirror. The board + * itself is left out (see {@link UndersideMirror#isEditableThrough}). * * @param point * @param project @@ -789,7 +790,7 @@ public class DrawingManager { /** * Finds the components that the underside mirror of the specified board shows at the specified - * point, top first, or none when the point is not in that mirror. + * point, other than the board itself, top first, or none when the point is not in that mirror. * * @param point * @param project @@ -804,7 +805,7 @@ public class DrawingManager { return components; } for (IDIYComponent component : findComponentsAt(mirrorTx.transform(point, null), project)) { - if (UndersideMirror.isMirrored(board, component)) { + if (UndersideMirror.isEditableThrough(board, component)) { components.add(component); } } diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java index 615fe42d3..a1ba2445e 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java +++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java @@ -966,7 +966,7 @@ public class Presenter implements IPlugInPort { for (int i = currentProject.getComponents().size() - 1; i >= 0; i--) { IDIYComponent component = currentProject.getComponents().get(i); if (mirrorBoard != null && selectedComponents.contains(component) - && !UndersideMirror.isMirrored(mirrorBoard, component)) { + && !UndersideMirror.isEditableThrough(mirrorBoard, component)) { continue; } for (int pointIndex = 0; pointIndex < component.getControlPointCount(); pointIndex++) { diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/UndersideMirror.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/UndersideMirror.java index 2aecdb605..3318fa45b 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/presenter/UndersideMirror.java +++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/UndersideMirror.java @@ -160,6 +160,15 @@ public class UndersideMirror { return false; } + /** + * @return true when the component can be selected, dragged or stretched through the board's + * mirror: any component the mirror shows except the board itself. The mirror is placed + * relative to the board, so moving the board through it would move the mirror as well. + */ + public static boolean isEditableThrough(IBoard board, IDIYComponent component) { + return component != board && isMirrored(board, component); + } + /** * @return the boards whose mirror contains the point, the topmost first, which is the one drawn * over the others where mirrors overlap diff --git a/diylc/diylc-swing/src/test/java/org/diylc/PresenterTests.java b/diylc/diylc-swing/src/test/java/org/diylc/PresenterTests.java index 16ce915af..b41e93c6f 100644 --- a/diylc/diylc-swing/src/test/java/org/diylc/PresenterTests.java +++ b/diylc/diylc-swing/src/test/java/org/diylc/PresenterTests.java @@ -391,6 +391,46 @@ public class PresenterTests extends TestBase { Assert.assertTrue(board.getBoardRectangle().contains(resistor.getControlPoint(0))); } + @Test + public void testBoardIsNotSelectableThroughItsUndersideMirror() { + BlankBoard board = mirroredBoard(); + drawProject(); + Rectangle2D region = UndersideMirror.getMirrorRegion(board); + Point inMirror = toScreen(new Point2D.Double(region.getCenterX(), region.getCenterY())); + + Assert.assertTrue(presenter.findComponentsAt(inMirror, false).isEmpty()); + presenter.mouseClicked(inMirror, MouseEvent.BUTTON1, false, false, false, 1); + Assert.assertTrue(presenter.getSelectedComponents().isEmpty()); + } + + @Test + public void testBoardIsNotDraggedThroughItsUndersideMirror() { + BlankBoard board = mirroredBoard(); + drawProject(); + Rectangle2D region = UndersideMirror.getMirrorRegion(board); + Rectangle2D before = board.getBoardRectangle(); + // selected on the board itself, then dragged by its picture in the mirror + Point onBoard = toScreen(new Point2D.Double(before.getCenterX(), before.getCenterY())); + presenter.mouseClicked(onBoard, MouseEvent.BUTTON1, false, false, false, 1); + Assert.assertTrue(presenter.getSelectedComponents().contains(board)); + Point inMirror = toScreen(new Point2D.Double(region.getCenterX(), region.getCenterY())); + + presenter.mouseMoved(inMirror, false, false, false); + presenter.dragStarted(inMirror, java.awt.dnd.DnDConstants.ACTION_MOVE, false, false); + presenter.dragOver(new Point(inMirror.x + 100, inMirror.y + 100)); + presenter.dragEnded(new Point(inMirror.x + 100, inMirror.y + 100)); + + Assert.assertEquals(before, board.getBoardRectangle()); + } + + // the hit tests read the areas recorded while drawing + private void drawProject() { + BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = img.createGraphics(); + presenter.draw(g2d, EnumSet.of(DrawOption.ZOOM), null, null, null, null); + g2d.dispose(); + } + // a board with its underside mirror below it, with nothing on it yet private BlankBoard mirroredBoard() { BlankBoard board = new BlankBoard(); diff --git a/diylc/docs/plans/bottom-side-components.md b/diylc/docs/plans/bottom-side-components.md index e4e0bff61..2efbe3777 100644 --- a/diylc/docs/plans/bottom-side-components.md +++ b/diylc/docs/plans/bottom-side-components.md @@ -121,7 +121,10 @@ order. - **Shared mirror geometry (done).** `org.diylc.presenter.UndersideMirror` holds the reflection for a board, the region its mirror occupies (the board rectangle reflected about the pivot), and which components a mirror shows: the board and every component with a control point on it. - `DrawingManager` draws the mirror with it, so drawing and hit-testing cannot drift apart. + `DrawingManager` draws the mirror with it, so drawing and hit-testing cannot drift apart. The + board itself cannot be selected, dragged or stretched through its own mirror + (`UndersideMirror.isEditableThrough`): the mirror is placed relative to the board, so moving the + board through it moved the mirror under the mouse and the board went the wrong way. - **Stage 1: selecting in the mirror (done).** `DrawingManager.findMirroredComponentsAt` reflects a point that falls in a mirror region and tests it again, limited to the components that board mirrors. `Presenter.findComponentsAtScaled` falls back to it when nothing selectable is hit in