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";