mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-15 12:25:34 -05:00
PlateEdits ports the WinForms plate view's edits of the selection, with its undo labels and coalescing: Delete (parts and cut-offs, cut-offs regenerated once when the plate has any), Nudge by the offset increment or one repeat step, Push to contact, Rotate about the selection's centre keeping its lower-left corner (a quarter turn, or by wheel notches at 10 degrees or a tenth of that), repeated nudges and wheel turns undoing as one step. The Select tool drags the selection as one Move step. Cut-offs regenerate when a drag actually moved parts (WinForms also does it on a plain click, adding an extra undo step); the regeneration runs before the step completes, so a failure rolls the drag back, and a view that loses the pointer mid-drag ends the move as a release would, keeping the selection. A plate tool host now has to supply its undo wiring, the nest's cut-off settings and an edits lock (WinForms EditsLocked): while locked no edit runs and no press selects or starts a move.
200 lines
6.7 KiB
C#
200 lines
6.7 KiB
C#
using System;
|
|
using OpenNest.Desktop.Interaction;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Desktop.Tests.Interaction;
|
|
|
|
public class SelectToolMoveTests
|
|
{
|
|
private readonly Plate plate = InteractionFixtures.Plate();
|
|
private readonly TestToolHost host;
|
|
|
|
public SelectToolMoveTests()
|
|
{
|
|
host = new TestToolHost(plate);
|
|
}
|
|
|
|
private PlateTool Tool => host.Tools.Current;
|
|
|
|
private static void AssertAt(Vector expected, Vector actual) =>
|
|
Assert.True(expected.DistanceTo(actual) < 1e-9, $"expected {expected}, was {actual}");
|
|
|
|
[Fact]
|
|
public void Dragging_a_part_moves_every_selected_part_as_one_Move_step()
|
|
{
|
|
var recorder = host.RecordHistory();
|
|
host.Click(25, 25);
|
|
host.Click(25, 5, ToolModifiers.Control);
|
|
|
|
Tool.PointerPressed(host.At(25, 5));
|
|
Tool.PointerMoved(host.At(27, 6));
|
|
Tool.PointerMoved(host.At(30, 8));
|
|
Tool.PointerReleased(host.At(30, 8));
|
|
|
|
AssertAt(new Vector(25, 3), plate.Parts[1].Location);
|
|
AssertAt(new Vector(25, 23), plate.Parts[4].Location);
|
|
Assert.Equal(1, recorder.History.UndoCount);
|
|
Assert.Equal("Move", recorder.History.UndoLabel);
|
|
Assert.False(Assert.IsType<SelectTool>(Tool).IsMoving);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_window_drag_moves_nothing()
|
|
{
|
|
host.Click(25, 5);
|
|
|
|
host.Drag(15, -1, 31, 11);
|
|
|
|
AssertAt(new Vector(20, 0), plate.Parts[1].Location);
|
|
Assert.Equal(new[] { plate.Parts[1] }, host.Selection.Parts);
|
|
// The one Move label is the earlier click on the part; the window drag began none.
|
|
Assert.Equal(new[] { "Move" }, host.ChangeLabels);
|
|
}
|
|
|
|
[Fact]
|
|
public void Cancelling_mid_drag_keeps_the_partial_move_as_one_step()
|
|
{
|
|
var recorder = host.RecordHistory();
|
|
Tool.PointerPressed(host.At(25, 5));
|
|
Tool.PointerMoved(host.At(30, 5));
|
|
|
|
Tool.Cancel();
|
|
|
|
AssertAt(new Vector(25, 0), plate.Parts[1].Location);
|
|
Assert.True(host.Selection.IsEmpty);
|
|
Assert.Equal(1, recorder.History.UndoCount);
|
|
Assert.False(recorder.IsChangeInProgress);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_click_on_a_part_records_nothing_even_with_cut_offs_on_the_plate()
|
|
{
|
|
var recorder = host.RecordHistory();
|
|
|
|
host.Click(25, 5);
|
|
host.Click(25, 25);
|
|
|
|
Assert.False(recorder.HasUnrecordedChange);
|
|
Assert.Equal(0, recorder.History.UndoCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void Releasing_a_drag_rebuilds_the_cut_offs_around_the_moved_part()
|
|
{
|
|
var before = PlateEditsTests.CutOffStrokes(plate);
|
|
|
|
host.Drag(25, 25, 80, 25);
|
|
|
|
AssertAt(new Vector(75, 20), plate.Parts[4].Location);
|
|
Assert.NotEqual(before, PlateEditsTests.CutOffStrokes(plate));
|
|
}
|
|
|
|
[Fact]
|
|
public void Showing_another_plate_mid_drag_ends_the_move_as_one_Move_step()
|
|
{
|
|
var recorder = host.RecordHistory();
|
|
Tool.PointerPressed(host.At(25, 5));
|
|
Tool.PointerMoved(host.At(30, 5));
|
|
|
|
host.Tools.OnPlateChanged();
|
|
|
|
AssertAt(new Vector(25, 0), plate.Parts[1].Location);
|
|
Assert.Equal(1, recorder.History.UndoCount);
|
|
Assert.Equal("Move", recorder.History.UndoLabel);
|
|
Assert.False(recorder.IsChangeInProgress);
|
|
}
|
|
|
|
[Fact]
|
|
public void While_edits_are_locked_a_press_selects_nothing_and_a_drag_moves_nothing()
|
|
{
|
|
var recorder = host.RecordHistory();
|
|
host.Click(25, 5);
|
|
host.EditsLocked = true;
|
|
var strokes = PlateEditsTests.CutOffStrokes(plate);
|
|
|
|
Assert.False(Tool.PointerPressed(host.At(25, 25)));
|
|
host.Drag(25, 25, 80, 25);
|
|
host.Drag(60, 60, 90, 90);
|
|
host.Click(80, 50);
|
|
|
|
Assert.False(Assert.IsType<SelectTool>(Tool).IsMoving);
|
|
AssertAt(new Vector(20, 20), plate.Parts[4].Location);
|
|
Assert.Equal(new[] { plate.Parts[1] }, host.Selection.Parts);
|
|
Assert.Empty(host.Selection.CutOffs);
|
|
Assert.Equal(strokes, PlateEditsTests.CutOffStrokes(plate));
|
|
// The one Move label is the unlocked click before the lock.
|
|
Assert.Equal(new[] { "Move" }, host.ChangeLabels);
|
|
Assert.Equal(0, recorder.History.UndoCount);
|
|
Assert.False(recorder.HasUnrecordedChange);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_move_open_when_the_lock_appears_finishes_at_its_release()
|
|
{
|
|
var recorder = host.RecordHistory();
|
|
Tool.PointerPressed(host.At(25, 5));
|
|
Tool.PointerMoved(host.At(27, 5));
|
|
host.EditsLocked = true;
|
|
Tool.PointerMoved(host.At(30, 5));
|
|
Tool.PointerReleased(host.At(30, 5));
|
|
|
|
AssertAt(new Vector(25, 0), plate.Parts[1].Location);
|
|
Assert.Equal(1, recorder.History.UndoCount);
|
|
Assert.False(recorder.IsChangeInProgress);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_release_whose_cut_off_rebuild_throws_rolls_the_move_back()
|
|
{
|
|
var recorder = host.RecordHistory();
|
|
Tool.PointerPressed(host.At(25, 25));
|
|
Tool.PointerMoved(host.At(80, 25));
|
|
host.ThrowOnCutOffSettings = true;
|
|
|
|
Assert.Throws<InvalidOperationException>(() => Tool.PointerReleased(host.At(80, 25)));
|
|
|
|
Assert.False(Assert.IsType<SelectTool>(Tool).IsMoving);
|
|
Assert.False(recorder.IsChangeInProgress);
|
|
Assert.Equal(0, recorder.History.UndoCount);
|
|
var restored = recorder.Document.Nest.Plates[0];
|
|
Assert.NotSame(plate, restored);
|
|
AssertAt(new Vector(20, 20), restored.Parts[4].Location);
|
|
AssertAt(new Vector(20, 0), restored.Parts[1].Location);
|
|
Assert.False(recorder.HasUnrecordedChange);
|
|
}
|
|
|
|
[Fact]
|
|
public void Losing_the_pointer_mid_drag_ends_the_move_as_one_step_and_keeps_the_selection()
|
|
{
|
|
var recorder = host.RecordHistory();
|
|
var before = PlateEditsTests.CutOffStrokes(plate);
|
|
Tool.PointerPressed(host.At(25, 25));
|
|
Tool.PointerMoved(host.At(50, 25));
|
|
Tool.PointerMoved(host.At(80, 25));
|
|
|
|
Tool.PointerCaptureLost();
|
|
|
|
AssertAt(new Vector(75, 20), plate.Parts[4].Location);
|
|
Assert.NotEqual(before, PlateEditsTests.CutOffStrokes(plate));
|
|
Assert.Equal(new[] { plate.Parts[4] }, host.Selection.Parts);
|
|
Assert.False(Assert.IsType<SelectTool>(Tool).IsMoving);
|
|
Assert.Equal(1, recorder.History.UndoCount);
|
|
Assert.Equal("Move", recorder.History.UndoLabel);
|
|
Assert.False(recorder.IsChangeInProgress);
|
|
Assert.False(recorder.HasUnrecordedChange);
|
|
}
|
|
|
|
[Fact]
|
|
public void Losing_the_pointer_mid_window_drops_the_window_without_selecting()
|
|
{
|
|
Tool.PointerPressed(host.At(15, 15));
|
|
Tool.PointerMoved(host.At(35, 35));
|
|
|
|
Tool.PointerCaptureLost();
|
|
Tool.PointerReleased(host.At(35, 35));
|
|
|
|
Assert.Null(Assert.IsType<SelectTool>(Tool).Window);
|
|
Assert.True(host.Selection.IsEmpty);
|
|
}
|
|
}
|