merge: show cut-plan status in a banner at the top of the panel

This commit is contained in:
aj committed 2026-10-16 10:12:10 -04:00
commit 274a1a1dea
2 files changed
+70 -32

No files matched your search

@@ -522,7 +522,7 @@ public class CuttingInspectionPanelTests
Assert.NotEmpty(plate.CutOffs);
Assert.Equal(plate.CutOffs.Count, plate.Parts.Count(p => p.BaseDrawing.IsCutOff));
Assert.Equal(plate.CutOffs.Count, panel.Proposal.Plates[0].Result.ProposedOrder.Count(p => p.IsCutOff));
Assert.False(Field<FlowLayoutPanel>(panel, "existingWorkBanner").Visible); // Staged lines are not "existing".
Assert.False(Field<Control>(panel, "existingWorkBanner").Visible); // Staged lines are not "existing".
Field<CheckBox>(panel, "autoCutOffsCheckBox").Checked = false;
PumpUntil(() => panel.Proposal != null && !panel.IsPlanning);
@@ -574,7 +574,7 @@ public class CuttingInspectionPanelTests
editor.OpenCuttingInspection();
var first = editor.CuttingInspection!;
PumpUntil(() => first.Proposal != null && !first.IsPlanning);
Assert.False(Field<FlowLayoutPanel>(first, "existingWorkBanner").Visible);
Assert.False(Field<Control>(first, "existingWorkBanner").Visible);
Field<Button>(first, "applyButton").PerformClick();
Assert.All(plate.Parts, part => Assert.True(part.HasManualLeadIns));
plate.CutOffs.Add(new CutOff(new Vector(60, 0), CutOffAxis.Vertical));
@@ -584,7 +584,7 @@ public class CuttingInspectionPanelTests
editor.OpenCuttingInspection();
var panel = editor.CuttingInspection!;
PumpUntil(() => panel.Proposal != null && !panel.IsPlanning);
var banner = Field<FlowLayoutPanel>(panel, "existingWorkBanner");
var banner = Field<Control>(panel, "existingWorkBanner");
Assert.True(banner.Visible);
Assert.Contains("ALREADY APPLIED: lead-ins on 2 parts and 1 cut-off on plate 1.",
Field<Label>(panel, "existingWorkLabel").Text);
@@ -675,7 +675,7 @@ public class CuttingInspectionPanelTests
protected override void OnLoad(EventArgs e) { }
}
private static T Field<T>(object target, string name) => Assert.IsType<T>(target.GetType()
private static T Field<T>(object target, string name) => Assert.IsAssignableFrom<T>(target.GetType()
.GetField(name, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(target));
private static void OpenAll(EditNestForm editor)
+66 -28
View File
@@ -48,10 +48,12 @@ internal sealed class CuttingInspectionPanel : UserControl
private readonly Label statusLabel = new()
{
AutoSize = true,
MaximumSize = new Size(440, 0),
Margin = new Padding(3, 10, 3, 3),
MaximumSize = new Size(410, 0),
Margin = Padding.Empty,
BackColor = Color.Transparent,
AccessibleName = "Cutting plan outcome"
};
private readonly StatusBanner statusBanner = new() { Dock = DockStyle.Fill, Margin = new Padding(3, 0, 3, 8) };
private readonly Label detailLabel = new()
{
AutoSize = true,
@@ -65,21 +67,12 @@ internal sealed class CuttingInspectionPanel : UserControl
private readonly Button nextButton = new() { Text = "Next", AutoSize = true };
private readonly CheckBox keepOrderCheckBox = new() { Text = "Keep current part order", AutoSize = true };
private readonly CheckBox autoCutOffsCheckBox = new() { AutoSize = true };
private readonly FlowLayoutPanel existingWorkBanner = new()
{
AutoSize = true,
FlowDirection = FlowDirection.TopDown,
WrapContents = false,
BackColor = Color.FromArgb(192, 0, 0),
Padding = new Padding(6),
Margin = new Padding(3, 3, 3, 6),
Visible = false
};
private readonly StatusBanner existingWorkBanner = new() { Margin = new Padding(3, 3, 3, 8) };
private readonly Label existingWorkLabel = new()
{
AutoSize = true,
MaximumSize = new Size(428, 0),
ForeColor = Color.White,
MaximumSize = new Size(410, 0),
Margin = new Padding(0, 0, 0, 6),
BackColor = Color.Transparent,
AccessibleName = "Existing lead-ins and cut-offs warning"
};
@@ -171,13 +164,17 @@ internal sealed class CuttingInspectionPanel : UserControl
cutOffRow.Controls.AddRange([autoCutOffsCheckBox, cutOffSettingsButton]);
existingWorkLabel.Font = new Font(Font, FontStyle.Bold);
existingWorkBanner.Controls.AddRange([existingWorkLabel, clearExistingButton]);
existingWorkBanner.SetTone(Tone.Bad);
header.Controls.AddRange([modeSelector, existingWorkBanner, settingsRow, keepOrderCheckBox, cutOffRow,
validationScopeLabel, buttons, acceptWarningsCheckBox, statusLabel, navigation, previewLabel, detailLabel]);
var layout = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 1, RowCount = 2 };
validationScopeLabel, buttons, acceptWarningsCheckBox, navigation, previewLabel, detailLabel]);
statusBanner.Controls.Add(statusLabel);
var layout = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 1, RowCount = 3 };
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
layout.Controls.Add(header, 0, 0);
layout.Controls.Add(warningGrid, 0, 1);
layout.Controls.Add(statusBanner, 0, 0);
layout.Controls.Add(header, 0, 1);
layout.Controls.Add(warningGrid, 0, 2);
Controls.Add(layout);
foreach (var name in new[] { "Plate", "Part", "With", "Issue" })
warningGrid.Columns.Add(new DataGridViewTextBoxColumn
@@ -203,19 +200,15 @@ internal sealed class CuttingInspectionPanel : UserControl
ShowSettings();
}
private enum Tone { Neutral, Good, Warning, Bad }
// Busy marks the slow part-order search, so it stands out while the operator waits.
private enum Tone { Neutral, Good, Warning, Bad, Busy }
/// <summary>Sets the headline and replaces the detail line (empty hides it).</summary>
/// <summary>Sets the headline banner and replaces the detail line (empty hides either).</summary>
private void SetStatus(Tone tone, string text, string detail = null)
{
statusLabel.Text = text;
statusLabel.ForeColor = tone switch
{
Tone.Good => Color.FromArgb(0, 128, 0),
Tone.Warning => Color.FromArgb(176, 96, 0),
Tone.Bad => Color.FromArgb(192, 0, 0),
_ => SystemColors.ControlText,
};
statusBanner.SetTone(tone);
statusBanner.Visible = !string.IsNullOrEmpty(text);
SetDetail(detail);
}
@@ -563,7 +556,7 @@ internal sealed class CuttingInspectionPanel : UserControl
var progress = new PostingProgress(context, value =>
{
if (!closed && !inspection.CloseRequested && inspection.IsRunning && generation == run)
SetStatus(Tone.Neutral, (plates.Length > 1 ? $"Planning plate {value.PlateNumber}: " : "Planning: ")
SetStatus(value.Phase == CuttingPlanPhase.Reordering ? Tone.Busy : Tone.Neutral, (plates.Length > 1 ? $"Planning plate {value.PlateNumber}: " : "Planning: ")
+ PhaseText(value.Phase) + "…");
});
var work = inspection.BeginPlanning(batch, BeforePlan, progress);
@@ -1116,6 +1109,51 @@ internal sealed class CuttingInspectionPanel : UserControl
return base.ProcessCmdKey(ref msg, keyData);
}
/// <summary>
/// One alert style for the panel: a pale tint with dark text and border in the same hue.
/// Busy alone is a saturated orange so the slow part-order search stands out.
/// </summary>
private sealed class StatusBanner : FlowLayoutPanel
{
private Color ink = SystemColors.ControlText;
public StatusBanner()
{
AutoSize = true;
AutoSizeMode = AutoSizeMode.GrowAndShrink;
FlowDirection = FlowDirection.TopDown;
WrapContents = false;
Padding = new Padding(12, 10, 12, 10);
Visible = false;
DoubleBuffered = true;
ResizeRedraw = true;
}
public void SetTone(Tone tone)
{
(BackColor, ink) = tone switch
{
Tone.Good => (Color.FromArgb(221, 240, 214), Color.FromArgb(28, 88, 28)),
Tone.Warning => (Color.FromArgb(252, 241, 200), Color.FromArgb(118, 78, 0)),
Tone.Bad => (Color.FromArgb(248, 215, 215), Color.FromArgb(132, 20, 20)),
Tone.Busy => (Color.FromArgb(255, 136, 0), Color.FromArgb(70, 30, 0)),
_ => (Color.FromArgb(236, 236, 236), Color.FromArgb(60, 60, 60)),
};
foreach (var label in Controls.OfType<Label>())
label.ForeColor = ink;
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
const int border = 2;
const ButtonBorderStyle style = ButtonBorderStyle.Solid;
ControlPaint.DrawBorder(e.Graphics, ClientRectangle, ink, border, style, ink, border, style,
ink, border, style, ink, border, style);
}
}
private sealed class PostingProgress(SynchronizationContext context, Action<CuttingPlanProgress> handler)
: IProgress<CuttingPlanProgress>
{