mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-13 02:32:09 +10:00
Components can be dragged and their points stretched through the Underside Display mirror of a board
This commit is contained in:
@@ -751,9 +751,8 @@ public class DrawingManager {
|
||||
public List<IDIYComponent<?>> findMirroredComponentsAt(Point2D point, Project project) {
|
||||
List<IDIYComponent<?>> components = new ArrayList<IDIYComponent<?>>();
|
||||
for (IBoard board : UndersideMirror.findBoardsMirroredAt(project, point)) {
|
||||
Point2D reflected = UndersideMirror.createTransform(board).transform(point, null);
|
||||
for (IDIYComponent<?> component : findComponentsAt(reflected, project)) {
|
||||
if (UndersideMirror.isMirrored(board, component) && !components.contains(component)) {
|
||||
for (IDIYComponent<?> component : findMirroredComponentsAt(point, project, board)) {
|
||||
if (!components.contains(component)) {
|
||||
components.add(component);
|
||||
}
|
||||
}
|
||||
@@ -761,6 +760,30 @@ public class DrawingManager {
|
||||
return components;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param point
|
||||
* @param project
|
||||
* @param board
|
||||
* @return
|
||||
*/
|
||||
public List<IDIYComponent<?>> findMirroredComponentsAt(Point2D point, Project project,
|
||||
IBoard board) {
|
||||
List<IDIYComponent<?>> components = new ArrayList<IDIYComponent<?>>();
|
||||
AffineTransform mirrorTx = UndersideMirror.createTransform(board);
|
||||
if (mirrorTx == null || !UndersideMirror.getMirrorRegion(board).contains(point)) {
|
||||
return components;
|
||||
}
|
||||
for (IDIYComponent<?> component : findComponentsAt(mirrorTx.transform(point, null), project)) {
|
||||
if (UndersideMirror.isMirrored(board, component)) {
|
||||
components.add(component);
|
||||
}
|
||||
}
|
||||
return components;
|
||||
}
|
||||
|
||||
public double getExtraSpace(Project project) {
|
||||
double width = project.getWidth().convertToPixels();
|
||||
double height = project.getHeight().convertToPixels();
|
||||
|
||||
@@ -172,7 +172,12 @@ public class Presenter implements IPlugInPort {
|
||||
private Point2D dragStartPoint = null;
|
||||
private boolean dragShiftDown = false;
|
||||
private AxisLock lockedAxis = null;
|
||||
|
||||
// The reflection of the Underside Display mirror a drag started in. Every point of that drag goes
|
||||
// through it, so the drag acts on the components themselves; null for a drag in the main view.
|
||||
private AffineTransform dragMirrorTx = null;
|
||||
// the reflection of the mirror that the control points in controlPointMap were found through
|
||||
private AffineTransform hoverMirrorTx = null;
|
||||
|
||||
private DIYTest test = null;
|
||||
|
||||
private OperationMode operationMode;
|
||||
@@ -279,16 +284,8 @@ public class Presenter implements IPlugInPort {
|
||||
if (controlPointMap != null && !controlPointMap.isEmpty()) {
|
||||
return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
|
||||
}
|
||||
for (IDIYComponent<?> component : currentProject.getComponents()) {
|
||||
if (!isComponentLocked(component) && isComponentVisible(component)
|
||||
&& operationMode != OperationMode.HIGHLIGHT_CONNECTED_AREAS) {
|
||||
ComponentArea area = drawingManager.getComponentArea(component);
|
||||
// the bounds rule most components out without merging their outline into an area
|
||||
if (area != null && scaledPoint != null && area.getOutlineBounds().contains(scaledPoint)
|
||||
&& area.getOutlineArea().contains(scaledPoint)) {
|
||||
return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
|
||||
}
|
||||
}
|
||||
if (scaledPoint != null && !findComponentsAtScaled(scaledPoint, false).isEmpty()) {
|
||||
return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,8 +566,8 @@ public class Presenter implements IPlugInPort {
|
||||
return findComponentsAtScaled(point, includeLocked, true);
|
||||
}
|
||||
|
||||
// a drag does not look through the underside mirror, because moving the mouse there moves the
|
||||
// part the other way along the mirrored axis
|
||||
// a drag reflects its points out of the underside mirror itself, so its hit tests must not look
|
||||
// through the mirror a second time
|
||||
private List<IDIYComponent<?>> findComponentsAtScaled(Point2D point, boolean includeLocked,
|
||||
boolean throughMirror) {
|
||||
List<IDIYComponent<?>> components = drawingManager.findComponentsAt(point, currentProject);
|
||||
@@ -950,6 +947,42 @@ public class Presenter implements IPlugInPort {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the control points of the selected, ungrouped components that can be dragged from the
|
||||
* specified point, one per component. When the point was reflected out of the underside mirror of
|
||||
* a board, only the components that mirror shows are considered.
|
||||
*/
|
||||
private Map<IDIYComponent<?>, Set<Integer>> findControlPointsAt(Point2D point, IBoard mirrorBoard) {
|
||||
Map<IDIYComponent<?>, Set<Integer>> components = new HashMap<IDIYComponent<?>, Set<Integer>>();
|
||||
// Go backwards so we take the highest z-order components first.
|
||||
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)) {
|
||||
continue;
|
||||
}
|
||||
for (int pointIndex = 0; pointIndex < component.getControlPointCount(); pointIndex++) {
|
||||
Point2D controlPoint = component.getControlPoint(pointIndex);
|
||||
// Only consider selected components that are not grouped.
|
||||
if (selectedComponents.contains(component) && component.canPointMoveFreely(pointIndex)
|
||||
&& findAllGroupedComponents(component).size() == 1) {
|
||||
try {
|
||||
if (point.distance(controlPoint) < DrawingManager.CONTROL_POINT_SIZE &&
|
||||
component.getControlPointVisibilityPolicy(pointIndex) != VisibilityPolicy.NEVER) {
|
||||
Set<Integer> indices = new HashSet<Integer>();
|
||||
indices.add(pointIndex);
|
||||
components.put(component, indices);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Error reading control point for component of type: " + component.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return components;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved(Point point, boolean ctrlDown, boolean shiftDown, boolean altDown) {
|
||||
if (point == null)
|
||||
@@ -996,26 +1029,16 @@ public class Presenter implements IPlugInPort {
|
||||
messageDispatcher.dispatchMessage(EventType.REPAINT);
|
||||
}
|
||||
} else {
|
||||
// Go backwards so we take the highest z-order components first.
|
||||
for (int i = currentProject.getComponents().size() - 1; i >= 0; i--) {
|
||||
IDIYComponent<?> component = currentProject.getComponents().get(i);
|
||||
for (int pointIndex = 0; pointIndex < component.getControlPointCount(); pointIndex++) {
|
||||
Point2D controlPoint = component.getControlPoint(pointIndex);
|
||||
// Only consider selected components that are not grouped.
|
||||
if (selectedComponents.contains(component) && component.canPointMoveFreely(pointIndex)
|
||||
&& findAllGroupedComponents(component).size() == 1) {
|
||||
try {
|
||||
if (previousScaledPoint.distance(controlPoint) < DrawingManager.CONTROL_POINT_SIZE &&
|
||||
component.getControlPointVisibilityPolicy(pointIndex) != VisibilityPolicy.NEVER) {
|
||||
Set<Integer> indices = new HashSet<Integer>();
|
||||
indices.add(pointIndex);
|
||||
components.put(component, indices);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Error reading control point for component of type: " + component.getClass().getName());
|
||||
}
|
||||
}
|
||||
components = findControlPointsAt(previousScaledPoint, null);
|
||||
hoverMirrorTx = null;
|
||||
for (IBoard board : UndersideMirror.findBoardsMirroredAt(currentProject, previousScaledPoint)) {
|
||||
if (!components.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
AffineTransform mirrorTx = UndersideMirror.createTransform(board);
|
||||
components = findControlPointsAt(mirrorTx.transform(previousScaledPoint, null), board);
|
||||
if (!components.isEmpty()) {
|
||||
hoverMirrorTx = mirrorTx;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1364,6 +1387,14 @@ public class Presenter implements IPlugInPort {
|
||||
this.dragAction = dragAction;
|
||||
this.preDragProject = currentProject.clone();
|
||||
Point2D scaledPoint = scalePoint(point);
|
||||
if (controlPointMap != null && !controlPointMap.isEmpty()) {
|
||||
this.dragMirrorTx = hoverMirrorTx;
|
||||
} else if (forceSelectionRect || !findComponentsAtScaled(scaledPoint, false, false).isEmpty()) {
|
||||
this.dragMirrorTx = null;
|
||||
} else {
|
||||
this.dragMirrorTx = findMirrorWithComponentAt(scaledPoint);
|
||||
}
|
||||
scaledPoint = toDragSpace(scaledPoint);
|
||||
this.previousDragPoint = scaledPoint;
|
||||
this.dragStartPoint = new Point2D.Double(scaledPoint.getX(), scaledPoint.getY());
|
||||
this.dragShiftDown = shiftDown;
|
||||
@@ -1586,7 +1617,7 @@ public class Presenter implements IPlugInPort {
|
||||
|
||||
boolean resizeInProgress = false;
|
||||
|
||||
Point2D scaledPoint = scalePoint(point);
|
||||
Point2D scaledPoint = toDragSpace(scalePoint(point));
|
||||
if (controlPointMap != null && !controlPointMap.isEmpty()) {
|
||||
// We're dragging control point(s).
|
||||
Point2D targetPoint = constrainToAxis(dragStartPoint, scaledPoint, dragShiftDown);
|
||||
@@ -2004,7 +2035,7 @@ public class Presenter implements IPlugInPort {
|
||||
test.addStep(DIYTest.DRAG_END, params);
|
||||
}
|
||||
|
||||
Point2D scaledPoint = scalePoint(point);
|
||||
Point2D scaledPoint = toDragSpace(scalePoint(point));
|
||||
|
||||
if (!dragInProgress && instantiationManager.getComponentSlot() == null) {
|
||||
return;
|
||||
@@ -2056,6 +2087,7 @@ public class Presenter implements IPlugInPort {
|
||||
dragStartPoint = null;
|
||||
dragShiftDown = false;
|
||||
lockedAxis = null;
|
||||
dragMirrorTx = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -3154,6 +3186,27 @@ public class Presenter implements IPlugInPort {
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
private Point2D toDragSpace(Point2D scaledPoint) {
|
||||
return dragMirrorTx == null || scaledPoint == null ? scaledPoint
|
||||
: dragMirrorTx.transform(scaledPoint, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the reflection of the first underside mirror that shows a selectable component at the
|
||||
* specified point, or null
|
||||
*/
|
||||
private AffineTransform findMirrorWithComponentAt(Point2D scaledPoint) {
|
||||
for (IBoard board : UndersideMirror.findBoardsMirroredAt(currentProject, scaledPoint)) {
|
||||
List<IDIYComponent<?>> components =
|
||||
drawingManager.findMirroredComponentsAt(scaledPoint, currentProject, board);
|
||||
removeUnselectable(components, false);
|
||||
if (!components.isEmpty()) {
|
||||
return UndersideMirror.createTransform(board);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveSelectionAsBlock(String blockName) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package org.diylc;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
@@ -17,14 +19,19 @@ import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.appframework.miscutils.InMemoryConfigurationManager;
|
||||
import org.diylc.clipboard.ComponentTransferable;
|
||||
import org.diylc.common.ComponentType;
|
||||
import org.diylc.common.DrawOption;
|
||||
import org.diylc.common.EventType;
|
||||
import org.diylc.common.IPlugIn;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.components.boards.BlankBoard;
|
||||
import org.diylc.components.chassis.ChassisPanel;
|
||||
import org.diylc.components.composite.CompositeComponent;
|
||||
import org.diylc.core.BoardUndersideDisplay;
|
||||
import org.diylc.core.IBoard;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.Template;
|
||||
import org.diylc.presenter.Presenter;
|
||||
import org.diylc.presenter.UndersideMirror;
|
||||
import org.diylc.swing.ActionFactory;
|
||||
import org.diylc.swing.actions.ComponentCategoryAction;
|
||||
import org.diylc.swing.actions.ResetCategoriesAction;
|
||||
@@ -281,6 +288,75 @@ public class PresenterTests extends TestBase {
|
||||
presenter.dragEnded(new Point(1300, 1100));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStretchThroughUndersideMirror() {
|
||||
instantiateTwoClick("Passive", "Resistor", new Point(1000, 1000), new Point(1200, 1000));
|
||||
IDIYComponent<?> resistor = presenter.getCurrentProject().getComponents().get(0);
|
||||
IBoard board = boardUnder(resistor);
|
||||
Point2D start = new Point2D.Double(resistor.getControlPoint(1).getX(), resistor.getControlPoint(1).getY());
|
||||
Point inMirror = toScreen(UndersideMirror.createTransform(board).transform(start, null));
|
||||
|
||||
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));
|
||||
|
||||
// the mirror is flipped vertically, so moving down in it moves the point up on the board
|
||||
Assert.assertTrue(resistor.getControlPoint(1).getX() > start.getX());
|
||||
Assert.assertTrue(resistor.getControlPoint(1).getY() < start.getY());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDragThroughUndersideMirror() {
|
||||
instantiateTwoClick("Passive", "Resistor", new Point(1000, 1000), new Point(1200, 1000));
|
||||
IDIYComponent<?> resistor = presenter.getCurrentProject().getComponents().get(0);
|
||||
IBoard board = boardUnder(resistor);
|
||||
Point2D start0 = new Point2D.Double(resistor.getControlPoint(0).getX(), resistor.getControlPoint(0).getY());
|
||||
Point2D start1 = new Point2D.Double(resistor.getControlPoint(1).getX(), resistor.getControlPoint(1).getY());
|
||||
// the hit test reads the areas recorded while drawing
|
||||
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();
|
||||
Point2D middle = new Point2D.Double((start0.getX() + start1.getX()) / 2, start0.getY());
|
||||
Point inMirror = toScreen(UndersideMirror.createTransform(board).transform(middle, null));
|
||||
|
||||
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.assertTrue(resistor.getControlPoint(0).getX() > start0.getX());
|
||||
Assert.assertTrue(resistor.getControlPoint(0).getY() < start0.getY());
|
||||
Assert.assertEquals(resistor.getControlPoint(0).getY(), resistor.getControlPoint(1).getY());
|
||||
Assert.assertEquals(start1.getX() - start0.getX(),
|
||||
resistor.getControlPoint(1).getX() - resistor.getControlPoint(0).getX());
|
||||
}
|
||||
|
||||
// a board reaching 200px past the component on every side, with its underside mirror below it;
|
||||
// the component stays selected
|
||||
private IBoard boardUnder(IDIYComponent<?> component) {
|
||||
Point2D first = component.getControlPoint(0);
|
||||
Point2D last = component.getControlPoint(component.getControlPointCount() - 1);
|
||||
BlankBoard board = new BlankBoard();
|
||||
board.setControlPoint(new Point2D.Double(first.getX() - 200, first.getY() - 200), 0);
|
||||
board.setControlPoint(new Point2D.Double(last.getX() + 200, last.getY() + 200), 1);
|
||||
board.setUndersideDisplay(BoardUndersideDisplay.BELOW);
|
||||
presenter.getCurrentProject().getComponents().add(0, board);
|
||||
return board;
|
||||
}
|
||||
|
||||
// the inverse of the zoom and extra-space scaling the presenter applies to mouse points
|
||||
private Point toScreen(Point2D modelPoint) {
|
||||
double zoom = presenter.getZoomLevel();
|
||||
double extraSpace = InMemoryConfigurationManager.getInstance()
|
||||
.readBoolean(IPlugInPort.EXTRA_SPACE_KEY, true)
|
||||
? presenter.getDrawingManager().getExtraSpace(presenter.getCurrentProject())
|
||||
: 0;
|
||||
return new Point((int) Math.round((modelPoint.getX() + extraSpace) * zoom),
|
||||
(int) Math.round((modelPoint.getY() + extraSpace) * zoom));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShiftLocksAxisWhilePlacing() {
|
||||
Map<String, List<ComponentType>> componentTypes = presenter.getComponentTypes();
|
||||
|
||||
@@ -5,7 +5,7 @@ Gerber layers and the canvas shows it as a part on the far side of the board.
|
||||
|
||||
Status: the core Gerber side is done, with tests: `IBoardSideComponent` exists and the export
|
||||
sends a bottom-side part to the bottom layers, but no component implements it yet. Parts can be
|
||||
selected through the Underside Display mirror. The next steps below remain.
|
||||
selected, dragged and stretched through the Underside Display mirror. The next steps below remain.
|
||||
|
||||
## 1. Current state
|
||||
|
||||
@@ -98,14 +98,16 @@ order.
|
||||
the main view; locked and hidden parts are filtered out first, so a locked part under the mirror
|
||||
does not block it. Clicking a part in the mirror selects the original, so the property editor,
|
||||
delete, copy and the popup menu all work unchanged. Selection highlighting and the selected
|
||||
control points are drawn on both copies. A drag still tests the main view only, and so do the
|
||||
hand cursor and control-point hover, because they promise dragging; all three move to stage 2.
|
||||
- **Stage 2: dragging in the mirror.** Dragging, the hand cursor and control-point hover use the
|
||||
mirror. A drag that starts in a mirror region reflects every
|
||||
subsequent mouse point before it reaches the existing drag code. Movement along the mirrored axis
|
||||
is therefore reversed, which is what the user sees. Snapping applies to the reflected point. Moving
|
||||
the board, or changing its Underside Offset, cancels nothing; the mirror simply follows.
|
||||
Control-point drags (stretching a trace) go through the same mapping.
|
||||
control points are drawn on both copies.
|
||||
- **Stage 2: dragging in the mirror (done).** A drag that starts on a part in a mirror, or on a
|
||||
control point of a selected part there, keeps that mirror's reflection in
|
||||
`Presenter.dragMirrorTx` and reflects every mouse point of the drag before it reaches the existing
|
||||
drag code. Movement along the mirrored axis is therefore reversed, which is what the user sees,
|
||||
and snapping and the shift axis lock apply to the reflected point. Control-point hover
|
||||
(`Presenter.findControlPointsAt`) and the hand cursor look through the mirror when the main view
|
||||
has nothing. A drag that starts on empty mirror space still draws a selection rectangle in
|
||||
screen space. Moving the board, or changing its Underside Offset, cancels nothing; the mirror
|
||||
simply follows.
|
||||
- **Stage 3: placing in the mirror.** A component instantiated with its first point in a mirror
|
||||
region is created at the reflected point. If it is an `IBoardSideComponent`, it is created with
|
||||
side `_1` (Bottom) and mirrored, as Flip to Other Side would do. A `PCBLayer` component is created on
|
||||
@@ -141,7 +143,9 @@ order.
|
||||
appears only in `.gbo`, mirrored, and in the right position.
|
||||
- `UndersideMirrorTest` covers the mirror region for each `BoardUndersideDisplay` direction and
|
||||
offset, that reflecting a point twice returns it, and which components a mirror shows.
|
||||
`DrawingManagerTest.testComponentFoundThroughUndersideMirror` covers the hit test.
|
||||
`DrawingManagerTest.testComponentFoundThroughUndersideMirror` covers the hit test, and
|
||||
`PresenterTests.testStretchThroughUndersideMirror` and `testDragThroughUndersideMirror` check
|
||||
that a drag through a mirror below the board moves the part up on the board.
|
||||
- In the app, with Underside Display set to each direction: select, drag and stretch a trace in
|
||||
the mirror and confirm that the original follows correctly. Place a part in the mirror and confirm
|
||||
that it is bottom-side, mirrored, faded on the main view and solid in the mirror.
|
||||
@@ -154,6 +158,5 @@ order.
|
||||
2. Wrap bottom-side painting in `startDrawingSide(getBoardSide())`.
|
||||
3. Add the Flip to Other Side action.
|
||||
4. Split the silkscreen print-out by side.
|
||||
5. Mirror stage 2: dragging through the mirror.
|
||||
6. Mirror stage 3: placing in the mirror, after Flip to Other Side exists.
|
||||
7. Run the regression suite.
|
||||
5. Mirror stage 3: placing in the mirror, after Flip to Other Side exists.
|
||||
6. Run the regression suite.
|
||||
|
||||
Reference in New Issue
Block a user