diff --git a/OpenNest.WinForms.Tests/Controls/ActionCloneTests.cs b/OpenNest.WinForms.Tests/Controls/ActionCloneTests.cs index af3b8312..1f1398d0 100644 --- a/OpenNest.WinForms.Tests/Controls/ActionCloneTests.cs +++ b/OpenNest.WinForms.Tests/Controls/ActionCloneTests.cs @@ -74,4 +74,44 @@ public class ActionCloneTests view.SetAction(typeof(ActionSelect)); Assert.False(view.ActionHandlesF1); }, TimeSpan.FromMinutes(1), "The STA test did not complete."); + + [Fact] + public void ADialogsFakeMouseMoveLeavesNudgedPreviewsInPlace() => StaTestThread.Run(() => + { + using var view = new PlateView { Plate = new Plate(48, 96), Size = new System.Drawing.Size(600, 600) }; + view.ZoomToFit(); + view.SetAction(typeof(ActionClone), Square("new")); + var preview = Assert.Single(view.SelectedParts); + MoveMouse(view, new System.Drawing.Point(300, 300)); + var followed = preview.BasePart.BoundingBox.Location; + + Nudge(view); + var nudged = preview.BasePart.BoundingBox.Location; + Assert.Equal(followed.X + view.OffsetIncrementDistance, nudged.X, 6); + + // A repost of the last position, with no focus change. + MoveMouse(view, new System.Drawing.Point(300, 300)); + Assert.Equal(nudged, preview.BasePart.BoundingBox.Location); + + // Opening the dialog, then closing it: each post arrives where the cursor rests. + var rest = view.PointToClient(Cursor.Position); + Raise(view, "OnLostFocus", EventArgs.Empty); + MoveMouse(view, rest); + Assert.Equal(nudged, preview.BasePart.BoundingBox.Location); + + Raise(view, "OnGotFocus", EventArgs.Empty); + MoveMouse(view, rest); + Assert.Equal(nudged, preview.BasePart.BoundingBox.Location); + + // The next real move takes the previews back to the cursor. + MoveMouse(view, new System.Drawing.Point(rest.X + 20, rest.Y + 20)); + Assert.NotEqual(nudged, preview.BasePart.BoundingBox.Location); + }, TimeSpan.FromMinutes(1), "The STA test did not complete."); + + private static void MoveMouse(PlateView view, System.Drawing.Point point) => + Raise(view, "OnMouseMove", new MouseEventArgs(MouseButtons.None, 0, point.X, point.Y, 0)); + + private static void Nudge(PlateView view) => + typeof(PlateView).GetMethod("ProcessDialogKey", BindingFlags.Instance | BindingFlags.NonPublic)! + .Invoke(view, new object[] { Keys.Right }); } diff --git a/OpenNest/Actions/ActionClone.cs b/OpenNest/Actions/ActionClone.cs index eeeabaa0..af642ba0 100644 --- a/OpenNest/Actions/ActionClone.cs +++ b/OpenNest/Actions/ActionClone.cs @@ -16,6 +16,7 @@ namespace OpenNest.Actions private readonly List sourceParts; private double lastScale; + private System.Drawing.Point? restingCursor; public ActionClone(PlateView plateView, Drawing drawing) : this(plateView, new List { new Part(drawing) }) { } @@ -27,6 +28,8 @@ namespace OpenNest.Actions plateView.MouseMove += plateView_MouseMove; plateView.MouseDown += plateView_MouseDown; plateView.Paint += plateView_Paint; + plateView.GotFocus += plateView_FocusChanged; + plateView.LostFocus += plateView_FocusChanged; sourceParts = partsToClone; parts = new List(); @@ -101,13 +104,24 @@ namespace OpenNest.Actions lastScale = plateView.ViewScale; } + // Windows posts a mouse move when a dialog opens or closes, though the mouse stayed + // put. Following it would throw away arrow-key nudges, so only a real move counts. private void plateView_MouseMove(object sender, MouseEventArgs e) { + if (e.Location == restingCursor) + return; + + restingCursor = e.Location; var offset = plateView.CurrentPoint - parts.GetBoundingBox().Location; parts.ForEach(p => p.Offset(offset)); plateView.Invalidate(); } + // The cursor may have been moved to the dialog's OK button, so measure the rest + // position afresh whenever focus leaves or returns. + private void plateView_FocusChanged(object sender, System.EventArgs e) => + restingCursor = plateView.PointToClient(Cursor.Position); + public override bool SurvivesPlateChange => true; // F1 sets the part in place, as it always has; help stays on the Help menu. @@ -121,6 +135,8 @@ namespace OpenNest.Actions plateView.MouseMove += plateView_MouseMove; plateView.MouseDown += plateView_MouseDown; plateView.Paint += plateView_Paint; + plateView.GotFocus += plateView_FocusChanged; + plateView.LostFocus += plateView_FocusChanged; SelectPreviews(); } @@ -131,6 +147,8 @@ namespace OpenNest.Actions plateView.MouseMove -= plateView_MouseMove; plateView.MouseDown -= plateView_MouseDown; plateView.Paint -= plateView_Paint; + plateView.GotFocus -= plateView_FocusChanged; + plateView.LostFocus -= plateView_FocusChanged; plateView.SelectedParts.Clear(); plateView.Invalidate();