merge: integrate inspection-view navigation lock with live header zoom

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-16 06:46:12 -04:00
commit f05533f69c
6 files changed
+52 -9

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
@@ -601,6 +601,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