refactor: extract CutOffHandler from PlateView

Move cut-off drag interaction mechanics into a dedicated CutOffHandler
class, reducing PlateView complexity and following the same pattern
established by SelectionManager extraction in Task 1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-07 15:13:42 -04:00
parent 089df67627
commit 25faba430c
2 changed files with 95 additions and 45 deletions

View File

@@ -0,0 +1,81 @@
using OpenNest.CNC;
using OpenNest.Geometry;
using System.Collections.Generic;
namespace OpenNest.Controls
{
internal class CutOffHandler
{
private readonly PlateView view;
private Dictionary<Part, Geometry.Entity> dragPerimeterCache;
public CutOffHandler(PlateView view)
{
this.view = view;
}
public bool IsDragging { get; private set; }
public CutOff TryStartDrag(Vector point, double tolerance)
{
var hitCutOff = GetCutOffAtPoint(point, tolerance);
if (hitCutOff == null)
return null;
IsDragging = true;
dragPerimeterCache = Plate.BuildPerimeterCache(view.Plate);
return hitCutOff;
}
public void UpdateDrag(Vector currentPoint, CutOff cutOff)
{
if (!IsDragging || cutOff == null)
return;
if (cutOff.Axis == CutOffAxis.Vertical)
cutOff.Position = new Vector(currentPoint.X, cutOff.Position.Y);
else
cutOff.Position = new Vector(cutOff.Position.X, currentPoint.Y);
cutOff.Regenerate(view.Plate, view.CutOffSettings, dragPerimeterCache);
view.Invalidate();
}
public void EndDrag()
{
if (!IsDragging)
return;
IsDragging = false;
dragPerimeterCache = null;
view.Plate.RegenerateCutOffs(view.CutOffSettings);
view.Invalidate();
}
public CutOff GetCutOffAtPoint(Vector point, double tolerance)
{
if (view.Plate?.CutOffs == null)
return null;
foreach (var cutoff in view.Plate.CutOffs)
{
var program = cutoff.Drawing?.Program;
if (program == null)
continue;
for (var i = 0; i < program.Codes.Count - 1; i += 2)
{
if (program.Codes[i] is RapidMove rapid &&
program.Codes[i + 1] is LinearMove linear)
{
var line = new Line(rapid.EndPoint, linear.EndPoint);
if (line.ClosestPointTo(point).DistanceTo(point) <= tolerance)
return cutoff;
}
}
}
return null;
}
}
}

View File

@@ -32,8 +32,7 @@ namespace OpenNest.Controls
private Action previousAction; private Action previousAction;
private CutOffSettings cutOffSettings = new CutOffSettings(); private CutOffSettings cutOffSettings = new CutOffSettings();
private SelectionManager selection; private SelectionManager selection;
private bool draggingCutOff; private CutOffHandler cutOffHandler;
private Dictionary<Part, Geometry.Entity> dragPerimeterCache;
protected List<LayoutPart> parts; protected List<LayoutPart> parts;
private List<LayoutPart> stationaryParts = new List<LayoutPart>(); private List<LayoutPart> stationaryParts = new List<LayoutPart>();
private List<LayoutPart> activeParts = new List<LayoutPart>(); private List<LayoutPart> activeParts = new List<LayoutPart>();
@@ -70,6 +69,7 @@ namespace OpenNest.Controls
public ReadOnlyCollection<LayoutPart> Parts => new ReadOnlyCollection<LayoutPart>(parts); public ReadOnlyCollection<LayoutPart> Parts => new ReadOnlyCollection<LayoutPart>(parts);
internal SelectionManager Selection => selection; internal SelectionManager Selection => selection;
internal CutOffHandler CutOffs => cutOffHandler;
public event EventHandler<ItemAddedEventArgs<Part>> PartAdded; public event EventHandler<ItemAddedEventArgs<Part>> PartAdded;
public event EventHandler<ItemRemovedEventArgs<Part>> PartRemoved; public event EventHandler<ItemRemovedEventArgs<Part>> PartRemoved;
@@ -93,6 +93,7 @@ namespace OpenNest.Controls
origin = new PointF(); origin = new PointF();
parts = new List<LayoutPart>(); parts = new List<LayoutPart>();
selection = new SelectionManager(this); selection = new SelectionManager(this);
cutOffHandler = new CutOffHandler(this);
redrawTimer = new Timer() redrawTimer = new Timer()
{ {
@@ -273,17 +274,18 @@ namespace OpenNest.Controls
if (e.Button == MouseButtons.Left && currentAction is ActionSelect) if (e.Button == MouseButtons.Left && currentAction is ActionSelect)
{ {
var hitCutOff = GetCutOffAtPoint(CurrentPoint, 5.0 / ViewScale); var hitCutOff = cutOffHandler.TryStartDrag(CurrentPoint, 5.0 / ViewScale);
if (hitCutOff != null) if (hitCutOff != null)
{ {
SelectedCutOff = hitCutOff; selection.DeselectParts();
draggingCutOff = true; selection.SelectedCutOffs.Clear();
dragPerimeterCache = Plate.BuildPerimeterCache(Plate); selection.SelectedCutOffs.Add(hitCutOff);
Invalidate();
return; return;
} }
else else
{ {
SelectedCutOff = null; selection.DeselectCutOffs();
} }
} }
@@ -304,12 +306,9 @@ namespace OpenNest.Controls
} }
} }
if (draggingCutOff && SelectedCutOff != null) if (cutOffHandler.IsDragging && selection.SelectedCutOffs.Count > 0)
{ {
draggingCutOff = false; cutOffHandler.EndDrag();
dragPerimeterCache = null;
Plate.RegenerateCutOffs(cutOffSettings);
Invalidate();
return; return;
} }
@@ -369,15 +368,9 @@ namespace OpenNest.Controls
lastPoint = e.Location; lastPoint = e.Location;
if (draggingCutOff && SelectedCutOff != null) if (cutOffHandler.IsDragging && selection.SelectedCutOffs.Count > 0)
{ {
if (SelectedCutOff.Axis == CutOffAxis.Vertical) cutOffHandler.UpdateDrag(CurrentPoint, selection.SelectedCutOffs[0]);
SelectedCutOff.Position = new Vector(CurrentPoint.X, SelectedCutOff.Position.Y);
else
SelectedCutOff.Position = new Vector(SelectedCutOff.Position.X, CurrentPoint.Y);
SelectedCutOff.Regenerate(Plate, cutOffSettings, dragPerimeterCache);
Invalidate();
return; return;
} }
@@ -566,31 +559,7 @@ namespace OpenNest.Controls
Invalidate(); Invalidate();
} }
public CutOff GetCutOffAtPoint(Vector point, double tolerance) public CutOff GetCutOffAtPoint(Vector point, double tolerance) => cutOffHandler.GetCutOffAtPoint(point, tolerance);
{
if (Plate?.CutOffs == null)
return null;
foreach (var cutoff in Plate.CutOffs)
{
var program = cutoff.Drawing?.Program;
if (program == null)
continue;
for (var i = 0; i < program.Codes.Count - 1; i += 2)
{
if (program.Codes[i] is RapidMove rapid &&
program.Codes[i + 1] is LinearMove linear)
{
var line = new Geometry.Line(rapid.EndPoint, linear.EndPoint);
if (line.ClosestPointTo(point).DistanceTo(point) <= tolerance)
return cutoff;
}
}
}
return null;
}
public LayoutPart GetPartAtControlPoint(Point pt) => selection.GetPartAtControlPoint(pt); public LayoutPart GetPartAtControlPoint(Point pt) => selection.GetPartAtControlPoint(pt);
public LayoutPart GetPartAtGraphPoint(PointF pt) => selection.GetPartAtGraphPoint(pt); public LayoutPart GetPartAtGraphPoint(PointF pt) => selection.GetPartAtGraphPoint(pt);