Files
OpenNest/OpenNest.Desktop/Interaction/PlateTool.cs
T
aj 203945dabf feat(desktop): share part delete, nudge, push, rotate and drag-move edits
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.
2026-11-14 20:26:01 -05:00

130 lines
4.4 KiB
C#

using System;
using OpenNest.CutOffs;
using OpenNest.Desktop.Rendering;
using OpenNest.Geometry;
using OpenNest.Undo;
namespace OpenNest.Desktop.Interaction;
/// <summary>The mouse button behind a pointer event.</summary>
public enum ToolButton
{
None,
Left,
Middle,
Right,
}
/// <summary>Modifier keys held during a pointer event.</summary>
[Flags]
public enum ToolModifiers
{
None = 0,
Shift = 1,
Control = 2,
Alt = 4,
}
/// <summary>
/// A pointer event as a plate tool sees it: where it is in plate coordinates and on screen, the
/// button pressed or released (for a move, the button held), and the modifier keys.
/// </summary>
public readonly record struct ToolPointer(Vector World, ScreenPoint Screen, ToolButton Button, ToolModifiers Modifiers);
/// <summary>What a plate tool works on: the view that hosts it, in either desktop app.</summary>
public interface IPlateToolHost
{
/// <summary>The plate shown, or null.</summary>
Plate Plate { get; }
PlateSelection Selection { get; }
PlateHitTester HitTester { get; }
/// <summary>The view's world/screen mapping, for tolerances given in pixels.</summary>
ViewTransform View { get; }
/// <summary>Requests a redraw.</summary>
void Invalidate();
/// <summary>
/// Opens one undoable edit of the plate (see <see cref="NestUndoRecorder.BeginChange"/>). A
/// view without a nest history records nothing.
/// </summary>
ChangeScope BeginChange(string label, object coalesceKey = null);
/// <summary>
/// The settings cut-offs are rebuilt with after an edit moves or removes parts.
/// </summary>
CutOffSettings CutOffSettings { get; }
/// <summary>
/// True while the plate must not be edited, as the WinForms view's <c>EditsLocked</c>: the
/// nest is locked (Auto Nest is running on it; later, an inspection panel shows it). Edits
/// and drag-moves are refused; panning and zooming stay live.
/// </summary>
bool EditsLocked { get; }
}
/// <summary>
/// One interactive plate-view tool, toolkit-neutral: the view forwards pointer events to the
/// active tool and draws what it exposes. Exactly one tool is active at a time; see
/// <see cref="PlateToolManager"/>.
/// </summary>
public abstract class PlateTool
{
protected PlateTool(IPlateToolHost host)
{
Host = host ?? throw new ArgumentNullException(nameof(host));
}
protected IPlateToolHost Host { get; }
/// <summary>The name shown in the status bar.</summary>
public abstract string Name { get; }
/// <summary>Status bar guidance shown after the name, or null for the name alone.</summary>
public virtual string Hint => null;
/// <summary>Whether the tool is mid-task, so Escape cancels that task rather than ending the tool.</summary>
public abstract bool IsBusy { get; }
/// <summary>Whether Escape from Select may bring this tool back after it ends.</summary>
public virtual bool ResumeOnEscape => true;
/// <summary>Whether the tool stays active when the view shows another plate.</summary>
public virtual bool SurvivesPlateChange => false;
/// <summary>Raised when <see cref="Hint"/> changes.</summary>
public event EventHandler HintChanged;
/// <summary>Called when the tool becomes active (again, when Escape resumes it).</summary>
public virtual void Activate() { }
/// <summary>Called when the tool stops being active, after <see cref="Cancel"/>.</summary>
public virtual void Deactivate() { }
/// <summary>Abandons the task in progress.</summary>
public abstract void Cancel();
/// <summary>Called when a tool that survives plate changes sees another plate.</summary>
public virtual void OnPlateChanged() { }
/// <returns>Whether the tool used the event.</returns>
public virtual bool PointerPressed(ToolPointer pointer) => false;
/// <returns>Whether the tool used the event.</returns>
public virtual bool PointerMoved(ToolPointer pointer) => false;
/// <returns>Whether the tool used the event.</returns>
public virtual bool PointerReleased(ToolPointer pointer) => false;
/// <summary>
/// The view lost the pointer mid-gesture (Alt+Tab during a drag, say), so no release will
/// follow: the tool ends the gesture in progress.
/// </summary>
public virtual void PointerCaptureLost() { }
protected void RaiseHintChanged() => HintChanged?.Invoke(this, EventArgs.Empty);
}