diff --git a/OpenNest.WinForms.Tests/Actions/ActionCutOffHintTests.cs b/OpenNest.WinForms.Tests/Actions/ActionCutOffHintTests.cs
new file mode 100644
index 00000000..7d78942d
--- /dev/null
+++ b/OpenNest.WinForms.Tests/Actions/ActionCutOffHintTests.cs
@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Windows.Forms;
+using OpenNest.Actions;
+using OpenNest.Controls;
+using OpenNest.Help;
+
+namespace OpenNest.WinForms.Tests.Actions;
+
+/// The manual cut-off tool says how to turn its line and where its help is.
+public class ActionCutOffHintTests
+{
+ private static void PressKey(PlateView view, Keys key) =>
+ typeof(Control).GetMethod("OnKeyDown", BindingFlags.Instance | BindingFlags.NonPublic)!
+ .Invoke(view, new object[] { new KeyEventArgs(key) });
+
+ [Fact]
+ public void StatusNamesTheAxisAndSpaceTurnsIt() => StaTestThread.Run(() =>
+ {
+ using var view = new PlateView { Plate = new Plate(48, 120) };
+
+ view.SetAction(typeof(ActionCutOff));
+ Assert.Equal(
+ "Sheet Cut-Off: click to add a vertical line, Space turns it horizontal, Esc finishes, F1 for help",
+ view.Status);
+ Assert.Equal(HelpTopics.SheetCutOff, view.ActionHelpTopic);
+
+ PressKey(view, Keys.Space);
+ Assert.StartsWith("Sheet Cut-Off: click to add a horizontal line, Space turns it vertical", view.Status);
+
+ view.SetAction(typeof(ActionSelect));
+ Assert.Null(view.ActionHelpTopic);
+ }, TimeSpan.FromMinutes(1), "The STA test did not complete.");
+}
diff --git a/OpenNest/Actions/Action.cs b/OpenNest/Actions/Action.cs
index 1d5562a4..5679e3a4 100644
--- a/OpenNest/Actions/Action.cs
+++ b/OpenNest/Actions/Action.cs
@@ -33,6 +33,12 @@ namespace OpenNest.Actions
///
public virtual bool ResumeOnEscape => true;
+ /// Status bar guidance shown after the action's name, or null for the name alone.
+ public virtual string Hint => null;
+
+ /// The help topic F1 opens while this action is active, or null for the general help.
+ public virtual string HelpTopic => null;
+
public virtual void OnPlateChanged() { }
public virtual void ConnectEvents() { }
diff --git a/OpenNest/Actions/ActionCutOff.cs b/OpenNest/Actions/ActionCutOff.cs
index f9a71a50..556f6588 100644
--- a/OpenNest/Actions/ActionCutOff.cs
+++ b/OpenNest/Actions/ActionCutOff.cs
@@ -6,6 +6,7 @@ using System.Windows.Forms;
using OpenNest.CNC;
using OpenNest.Controls;
using OpenNest.Geometry;
+using OpenNest.Help;
namespace OpenNest.Actions
{
@@ -50,6 +51,17 @@ namespace OpenNest.Actions
public override bool IsBusy() => false;
+ public override string Hint =>
+ $"click to add a {AxisName(lockedAxis)} line, Space turns it {AxisName(OtherAxis(lockedAxis))}, Esc finishes, F1 for help";
+
+ public override string HelpTopic => HelpTopics.SheetCutOff;
+
+ private static CutOffAxis OtherAxis(CutOffAxis axis) =>
+ axis == CutOffAxis.Vertical ? CutOffAxis.Horizontal : CutOffAxis.Vertical;
+
+ private static string AxisName(CutOffAxis axis) =>
+ axis == CutOffAxis.Vertical ? "vertical" : "horizontal";
+
private void OnMouseMove(object sender, MouseEventArgs e)
{
var pt = plateView.CurrentPoint;
@@ -78,8 +90,8 @@ namespace OpenNest.Actions
{
if (e.KeyCode == Keys.Space)
{
- lockedAxis =
- lockedAxis == CutOffAxis.Vertical ? CutOffAxis.Horizontal : CutOffAxis.Vertical;
+ lockedAxis = OtherAxis(lockedAxis);
+ plateView.RefreshActionStatus();
if (previewCutOff != null)
{
@@ -126,6 +138,22 @@ namespace OpenNest.Actions
e.Graphics.DrawLine(pen, pt1, pt2);
}
}
+
+ DrawRotateTag(e.Graphics);
+ }
+
+ // The status bar is easy to miss, so the cursor says how to turn the line.
+ private void DrawRotateTag(Graphics g)
+ {
+ var text = $"Space: turn {AxisName(OtherAxis(lockedAxis))}";
+ var cursor = plateView.PointWorldToGraph(plateView.CurrentPoint);
+ var size = g.MeasureString(text, plateView.Font);
+ var rect = new RectangleF(cursor.X + 16f, cursor.Y + 16f, size.Width + 6, size.Height + 4);
+
+ using (var background = new SolidBrush(Color.FromArgb(230, Color.White)))
+ g.FillRectangle(background, rect);
+ g.DrawRectangle(Pens.DimGray, rect.X, rect.Y, rect.Width, rect.Height);
+ g.DrawString(text, plateView.Font, Brushes.Black, rect.X + 3, rect.Y + 2);
}
}
}
diff --git a/OpenNest/Controls/ActionManager.cs b/OpenNest/Controls/ActionManager.cs
index 2bb8b64a..3d24c856 100644
--- a/OpenNest/Controls/ActionManager.cs
+++ b/OpenNest/Controls/ActionManager.cs
@@ -40,7 +40,7 @@ namespace OpenNest.Controls
}
currentAction = action;
- view.Status = GetDisplayName(type);
+ RefreshStatus();
}
public void SetAction(Type type, params object[] args)
@@ -63,7 +63,7 @@ namespace OpenNest.Controls
return;
currentAction = action;
- view.Status = GetDisplayName(type);
+ RefreshStatus();
}
public void ProcessEscapeKey()
@@ -98,7 +98,7 @@ namespace OpenNest.Controls
action.ConnectEvents();
currentAction = action;
- view.Status = GetDisplayName(action.GetType());
+ RefreshStatus();
}
public void OnPlateChanged()
@@ -119,6 +119,14 @@ namespace OpenNest.Controls
}
}
+ /// Shows the current action's name and hint in the status bar.
+ public void RefreshStatus()
+ {
+ var name = GetDisplayName(currentAction.GetType());
+ var hint = currentAction.Hint;
+ view.Status = string.IsNullOrEmpty(hint) ? name : $"{name}: {hint}";
+ }
+
private string GetDisplayName(Type type)
{
var attributes = type.GetCustomAttributes(true);
diff --git a/OpenNest/Controls/PlateView.cs b/OpenNest/Controls/PlateView.cs
index d50a858c..7542bac2 100644
--- a/OpenNest/Controls/PlateView.cs
+++ b/OpenNest/Controls/PlateView.cs
@@ -866,6 +866,12 @@ namespace OpenNest.Controls
public void SetAction(Type type, params object[] args) =>
actionManager.SetAction(type, args);
+ /// Shows the current action's name and hint again after the hint changed.
+ public void RefreshActionStatus() => actionManager.RefreshStatus();
+
+ /// The help topic for the current action, or null when it has none.
+ public string ActionHelpTopic => actionManager.CurrentAction?.HelpTopic;
+
public void AlignSelected(AlignType alignType) => selection.AlignSelected(alignType);
public void AlignSelected(AlignType alignType, LayoutPart fixedPart) =>
diff --git a/OpenNest/Forms/MainForm.cs b/OpenNest/Forms/MainForm.cs
index 9e02ac13..7a76dca7 100644
--- a/OpenNest/Forms/MainForm.cs
+++ b/OpenNest/Forms/MainForm.cs
@@ -99,6 +99,12 @@ namespace OpenNest.Forms
}
if (ProcessUndoKeys(keyData))
return true;
+ // F1 opens the active tool's topic before the Help menu's general help.
+ if (keyData == Keys.F1 && activeForm?.PlateView?.ActionHelpTopic is { } topic)
+ {
+ HelpTopics.Show(this, topic);
+ return true;
+ }
return base.ProcessCmdKey(ref msg, keyData);
}
diff --git a/OpenNest/Help/HelpTopics.cs b/OpenNest/Help/HelpTopics.cs
index 40a51603..2e75a304 100644
--- a/OpenNest/Help/HelpTopics.cs
+++ b/OpenNest/Help/HelpTopics.cs
@@ -19,6 +19,7 @@ namespace OpenNest.Help
public const string AutoNest = "auto-nest";
public const string Post = "post";
public const string SupportGrid = "support-grid";
+ public const string SheetCutOff = "sheet-cutoff";
/// Overrides the help folder, e.g. a local opennest-help site/ build.
public const string DirectoryVariable = "OPENNEST_HELP_DIR";