diff --git a/OpenNest.Avalonia/MainWindow.axaml b/OpenNest.Avalonia/MainWindow.axaml
index effc42f7..1ae9afaa 100644
--- a/OpenNest.Avalonia/MainWindow.axaml
+++ b/OpenNest.Avalonia/MainWindow.axaml
@@ -59,6 +59,9 @@
diff --git a/OpenNest.Avalonia/MainWindow.axaml.cs b/OpenNest.Avalonia/MainWindow.axaml.cs
index f2eb6c51..4f967e05 100644
--- a/OpenNest.Avalonia/MainWindow.axaml.cs
+++ b/OpenNest.Avalonia/MainWindow.axaml.cs
@@ -95,6 +95,16 @@ namespace OpenNest.Avalonia
});
KeyBindings.Add(new KeyBinding { Gesture = new KeyGesture(Key.PageUp), Command = new Command(() => StepPlate(-1)) });
KeyBindings.Add(new KeyBinding { Gesture = new KeyGesture(Key.PageDown), Command = new Command(() => StepPlate(1)) });
+ KeyBindings.Add(new KeyBinding
+ {
+ Gesture = new KeyGesture(Key.F5),
+ Command = new Command(() => OnSetOffsetIncrement(null, null)),
+ });
+ KeyBindings.Add(new KeyBinding
+ {
+ Gesture = new KeyGesture(Key.F6),
+ Command = new Command(() => OnSetRotationIncrement(null, null)),
+ });
// Ctrl+Tab before focus navigation takes the Tab key.
AddHandler(KeyDownEvent, OnPreviewKeyDown, RoutingStrategies.Tunnel);
// .nest files dropped anywhere on the window, the nest view included, open in tabs.
@@ -295,6 +305,8 @@ namespace OpenNest.Avalonia
SaveButton.IsEnabled = view != null;
SaveAsButton.IsEnabled = view != null;
CloseAllMenuItem.IsEnabled = view != null;
+ SetOffsetIncrementMenuItem.IsEnabled = view != null;
+ SetRotationIncrementMenuItem.IsEnabled = view != null;
UpdateExportCommands(view != null);
Title = view == null
? "OpenNest (Avalonia prototype)"
@@ -849,6 +861,37 @@ namespace OpenNest.Avalonia
private void StepPlate(int step) => ActiveView?.StepPlate(step);
+ // Tools > Set Offset Increment (F5), as WinForms: how far an arrow key nudges the active
+ // nest's selection, or the calculated step (selection size plus part spacing). Each nest
+ // keeps its own increments for the session, as each WinForms nest window does.
+ private async void OnSetOffsetIncrement(object sender, RoutedEventArgs e)
+ {
+ if (ActiveView is not { } view)
+ return;
+ var edits = view.PlateView.Edits;
+ var result = await SetValueDialog.ShowAsync(this, "Set Offset Increment",
+ edits.OffsetIncrement,
+ optionText: "Calculated distance (selection size + part spacing)",
+ optionChecked: edits.CalculatedOffset);
+ if (result == null)
+ return;
+ edits.OffsetIncrement = result.Value;
+ edits.CalculatedOffset = result.OptionChecked;
+ }
+
+ // Tools > Set Rotation Increment (F6): the Shift+wheel turn, at most 360 degrees.
+ private async void OnSetRotationIncrement(object sender, RoutedEventArgs e)
+ {
+ if (ActiveView is not { } view)
+ return;
+ var edits = view.PlateView.Edits;
+ var result = await SetValueDialog.ShowAsync(this, "Set Rotation Increment",
+ edits.RotateIncrementDegrees, maximum: 360);
+ if (result == null)
+ return;
+ edits.RotateIncrementDegrees = result.Value;
+ }
+
// Tools > Options. Settings for features the app does not have yet (Database mode, the
// update feed) are still saved, for the WinForms app and later milestones.
private async void OnOptions(object sender, RoutedEventArgs e) =>
diff --git a/OpenNest.Avalonia/SetValueDialog.cs b/OpenNest.Avalonia/SetValueDialog.cs
new file mode 100644
index 00000000..1231fe19
--- /dev/null
+++ b/OpenNest.Avalonia/SetValueDialog.cs
@@ -0,0 +1,116 @@
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Layout;
+
+namespace OpenNest.Avalonia
+{
+ ///
+ /// One number to set, as the WinForms SetValueForm: an "Increment :" box from 0 to a maximum
+ /// shown with four decimals, an optional check box that disables the box, and a Set button
+ /// that is also Enter. Esc or closing the window changes nothing.
+ ///
+ internal sealed class SetValueDialog : Window
+ {
+ public const double DefaultMaximum = 999999;
+
+ /// What the user set: the value and the option check box.
+ public sealed record Result(double Value, bool OptionChecked);
+
+ private readonly NumericUpDown value;
+ private readonly CheckBox option;
+ // The last value the box held, which an emptied box keeps, as the WinForms box does.
+ private double lastValid;
+ private readonly double maximum;
+
+ private SetValueDialog(
+ string title, double initialValue, double maximum, string optionText,
+ bool optionChecked)
+ {
+ Title = title;
+ // Wide enough for the option's text on one line, as the WinForms form widens.
+ MinWidth = 300;
+ SizeToContent = SizeToContent.WidthAndHeight;
+ CanResize = false;
+ ShowInTaskbar = false;
+ WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ this.maximum = maximum;
+ lastValid = System.Math.Clamp(initialValue, 0, maximum);
+
+ value = new NumericUpDown
+ {
+ Name = "Value",
+ Minimum = 0,
+ Maximum = (decimal)maximum,
+ Increment = 1,
+ // A typed value out of range goes to the nearest limit, as in WinForms, rather
+ // than being refused.
+ ClipValueToMinMax = true,
+ FormatString = "0.0000",
+ Value = (decimal)lastValid,
+ HorizontalAlignment = HorizontalAlignment.Stretch,
+ };
+ value.ValueChanged += (_, e) =>
+ {
+ if (e.NewValue is { } v)
+ lastValid = (double)v;
+ };
+ var set = new Button { Name = "Set", Content = "Set", IsDefault = true };
+ set.Click += (_, _) => Close(new Result(Current, option.IsChecked == true));
+
+ var row = new Grid
+ {
+ ColumnDefinitions = new ColumnDefinitions("Auto,*,Auto"),
+ ColumnSpacing = 8,
+ };
+ var label = new TextBlock
+ {
+ Text = "Increment :",
+ VerticalAlignment = VerticalAlignment.Center,
+ };
+ row.Children.Add(label);
+ Grid.SetColumn(value, 1);
+ row.Children.Add(value);
+ Grid.SetColumn(set, 2);
+ row.Children.Add(set);
+
+ option = new CheckBox
+ {
+ Name = "Option",
+ Content = optionText,
+ IsChecked = optionChecked,
+ IsVisible = !string.IsNullOrEmpty(optionText),
+ };
+ value.IsEnabled = !optionChecked;
+ option.IsCheckedChanged += (_, _) => value.IsEnabled = option.IsChecked != true;
+
+ var panel = new StackPanel { Margin = new Thickness(12), Spacing = 10 };
+ panel.Children.Add(row);
+ panel.Children.Add(option);
+ Content = panel;
+
+ AddHandler(KeyDownEvent, (_, e) =>
+ {
+ if (e.Key != Key.Escape)
+ return;
+ e.Handled = true;
+ Close(null);
+ }, RoutingStrategies.Tunnel);
+ Opened += (_, _) => value.Focus();
+ }
+
+ // A value out of range is taken to the nearest limit, as the WinForms box does.
+ private double Current => System.Math.Clamp(lastValid, 0, maximum);
+
+ ///
+ /// The value and option the user set, or null when they closed the dialog without Set.
+ ///
+ public static Task ShowAsync(
+ Window owner, string title, double value, double maximum = DefaultMaximum,
+ string optionText = null, bool optionChecked = false) =>
+ new SetValueDialog(title, value, maximum, optionText, optionChecked)
+ .ShowDialog(owner);
+ }
+}