Author SHA1 Message Date
aj 04c9f748f9 Merge remote-tracking branch 'github/master' into feat/inspection-view-navigation 2026-10-15 07:42:46 -04:00
aj 29f48789dd Merge remote-tracking branch 'github/master' into feat/inspection-view-navigation 2026-10-14 10:21:03 -04:00
ajandClaude Opus 5.5 5180ce1506 feat(inspection): keep header zoom live while plate navigation is locked
Validation inspection disabled the whole plate header, so the zoom buttons
and Ctrl+Plus/Ctrl+Minus stopped working even though wheel zoom and fit stay
available. Group the plate navigation buttons into their own container so the
inspection scope can lock them while leaving the viewport-only zoom controls
and shortcuts live.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:20:57 -04:00
6 changed files with 52 additions and 9 deletions

No files matched your search

@@ -271,6 +271,35 @@ public class CuttingValidationPanelTests
Assert.False(view.ValidationOverlay.IsAnimating);
});
[Fact]
public void HeaderZoomStaysLiveWhilePlateNavigationIsLocked() => RunSta(() =>
{
using var editor = RapidEditor();
editor.Show();
editor.PlateManager.LoadAt(0);
editor.OpenValidationInspection();
var panel = editor.CuttingInspection!;
Pump(() => panel.ValidationReport != null && !panel.IsPlanning);
var view = editor.PlateView;
Button Zoom(string name) => Assert.IsType<Button>(editor.Controls.Find(name, true).Single());
Zoom("btnZoomToFit").PerformClick(); // The docked panel narrowed the canvas.
var fit = view.ViewScale;
Zoom("btnZoomIn").PerformClick();
Assert.True(view.ViewScale > fit);
var zoomed = view.ViewScale;
Zoom("btnZoomOut").PerformClick();
Assert.True(view.ViewScale < zoomed);
Zoom("btnZoomIn").PerformClick();
Zoom("btnZoomToFit").PerformClick();
Assert.Equal(fit, view.ViewScale, 3);
var next = Field<Button>(editor, "btnNextPlate");
next.PerformClick();
Assert.Equal(0, editor.PlateManager.CurrentIndex);
panel.RequestClose();
next.PerformClick();
Assert.Equal(1, editor.PlateManager.CurrentIndex);
});
private static void InvokeView(Control target, string name, params object[] args) => target.GetType()
.GetMethod(name, BindingFlags.NonPublic | BindingFlags.Instance)!.Invoke(target, args);
+2
View File
@@ -589,6 +589,8 @@ namespace OpenNest.Controls
InspectionCloseRequested();
else if (keyData == Keys.F)
ZoomToFit();
else if (keyData is (Keys.Control | Keys.Oemplus) or (Keys.Control | Keys.OemMinus))
return base.ProcessCmdKey(ref msg, keyData); // The host's zoom shortcuts stay live.
return true;
}
// Audited: DrawControl/PlateView, Action key handlers, EditNestForm,
+8 -2
View File
@@ -159,6 +159,12 @@ namespace OpenNest.Forms
btnNextPlate.Location = new Point(btnSize.Width * 2, 0);
btnLastPlate.Location = new Point(btnSize.Width * 3, 0);
// Inspection locks plate navigation as one container but leaves zoom live.
var plateButtons = new Panel { Size = new System.Drawing.Size(btnSize.Width * 4, btnSize.Height) };
plateButtons.Controls.AddRange(
new Control[] { btnFirstPlate, btnPreviousPlate, btnNextPlate, btnLastPlate });
ZoomControls = [zoomOutButton, zoomInButton, zoomToFitButton];
zoomOutButton.Location = new Point(btnSize.Width * 4 + 8, 0);
zoomInButton.Location = new Point(btnSize.Width * 5 + 8, 0);
zoomToFitButton.Location = new Point(btnSize.Width * 6 + 8, 0);
@@ -174,8 +180,7 @@ namespace OpenNest.Forms
navPanel.Controls.AddRange(
new Control[]
{
btnFirstPlate, btnPreviousPlate, btnNextPlate, btnLastPlate,
separator, zoomOutButton, zoomInButton, zoomToFitButton
plateButtons, separator, zoomOutButton, zoomInButton, zoomToFitButton
}
);
@@ -263,6 +268,7 @@ namespace OpenNest.Forms
public bool IsSidePanelVisible => !viewSplitContainer.Panel2Collapsed;
internal Control SidePanelHeader { get; private set; }
internal Control[] ZoomControls { get; private set; }
internal CuttingInspectionPanel CuttingInspection { get; private set; }
private bool disposeAfterCuttingInspection;
private bool closeAfterCuttingInspection;
+5
View File
@@ -89,6 +89,11 @@ namespace OpenNest.Forms
return true;
}
// Standard inspection navigation reaches the canvas/panel; menu shortcuts cannot mutate.
// The zoom shortcuts stay live with the header zoom buttons.
if (keyData == mnuViewZoomIn.ShortcutKeys)
ZoomIn_Click(this, EventArgs.Empty);
else if (keyData == mnuViewZoomOut.ShortcutKeys)
ZoomOut_Click(this, EventArgs.Empty);
if ((keyData & (Keys.Control | Keys.Alt)) != 0)
return true;
}
+5 -6
View File
@@ -13,8 +13,7 @@ internal sealed class ValidationInspectionScope : IDisposable
private readonly List<(Form Form, bool AllowDrop)> hosts = new();
private bool disposed;
private readonly PlateView view;
private readonly Control inspector;
private readonly Control header;
private readonly Control[] live;
private readonly Action close;
private Form closingHost;
private readonly bool previousInspection;
@@ -22,8 +21,8 @@ internal sealed class ValidationInspectionScope : IDisposable
public ValidationInspectionScope(EditNestForm editor, Control inspector, Control header, Action close)
{
view = editor.PlateView;
this.inspector = inspector;
this.header = header;
// The header zoom buttons only change the viewport, like the canvas wheel.
live = new[] { view, inspector, header }.Concat(editor.ZoomControls).Where(control => control != null).ToArray();
this.close = close;
previousInspection = view.InspectionOnly;
view.InspectionOnly = true;
@@ -43,9 +42,9 @@ internal sealed class ValidationInspectionScope : IDisposable
{
foreach (var child in parent.Controls.Cast<Control>())
{
if (ReferenceEquals(child, view) || ReferenceEquals(child, inspector) || ReferenceEquals(child, header))
if (live.Contains(child))
continue;
if (child.Contains(view) || child.Contains(inspector) || header != null && child.Contains(header))
if (live.Any(child.Contains))
DisableExceptCanvas(child);
else
{
+3 -1
View File
@@ -37,7 +37,9 @@ fresh verification and consent flow.
Finish or cancel active nesting/fill operations before validating. The panel is
exclusive with editing and other side-panel actions while it owns the nest. The
main plate view remains available for wheel zoom, middle-button pan, and **F**
or middle-button double-click to fit. Panel X, Cancel or Escape requests
or middle-button double-click to fit. The plate header's zoom buttons and the
**Ctrl+Plus**/**Ctrl+Minus** shortcuts also stay live; its plate navigation
buttons stay locked. Panel X, Cancel or Escape requests
cancellation and waits for the worker to stop before restoring editing controls.
Switching to Plan mode also waits for any validation worker; it does not promote
a live validation report into a plan check. Closing the editor or main window