diff --git a/OpenNest.Avalonia/App.axaml.cs b/OpenNest.Avalonia/App.axaml.cs
index 5716015f..67322799 100644
--- a/OpenNest.Avalonia/App.axaml.cs
+++ b/OpenNest.Avalonia/App.axaml.cs
@@ -25,6 +25,8 @@ namespace OpenNest.Avalonia
if (snapshot >= 0 && snapshot + 1 < args.Length)
window.SaveSnapshotAndClose(args[snapshot + 1],
snapshot + 2 < args.Length && int.TryParse(args[snapshot + 2], out var plate) ? plate : 1);
+ else
+ window.RememberPlacement();
}
base.OnFrameworkInitializationCompleted();
diff --git a/OpenNest.Avalonia/MainWindow.axaml.cs b/OpenNest.Avalonia/MainWindow.axaml.cs
index 09de9848..9a32590e 100644
--- a/OpenNest.Avalonia/MainWindow.axaml.cs
+++ b/OpenNest.Avalonia/MainWindow.axaml.cs
@@ -74,6 +74,15 @@ namespace OpenNest.Avalonia
PlateList.SelectedIndex = nest.Plates.Count > 0 ? System.Math.Clamp(selectIndex, 0, nest.Plates.Count - 1) : -1;
}
+ ///
+ /// Restores the window's size, position and maximized state from the user settings the
+ /// WinForms app shares, and saves them on close. Not used for --snapshot, whose image
+ /// must not depend on the user's settings.
+ ///
+ public void RememberPlacement() =>
+ WindowPlacementBinding.Remember(
+ this, WindowPlacement.MainWindow, Data.UserSettingsFile.DefaultPath);
+
/// Runs Auto Nest once the window opens, for --auto-nest.
public void AutoNestOnOpen() => Opened += (_, _) => autoNest = RunAutoNestAsync();
diff --git a/OpenNest.Avalonia/WindowPlacementBinding.cs b/OpenNest.Avalonia/WindowPlacementBinding.cs
new file mode 100644
index 00000000..c75b4717
--- /dev/null
+++ b/OpenNest.Avalonia/WindowPlacementBinding.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Linq;
+using Avalonia;
+using Avalonia.Controls;
+using OpenNest.Data;
+using OpenNest.Desktop;
+
+namespace OpenNest.Avalonia
+{
+ ///
+ /// Restores a window's size, position and maximized state from the shared user-settings
+ /// file and saves them when it closes, under the same names the WinForms app uses.
+ ///
+ ///
+ /// Sizes are stored in screen pixels. Avalonia sets the client area, while WinForms stores
+ /// the outer window, so a size saved by one app opens a frame's width different in the
+ /// other; each app's own round trip is exact.
+ ///
+ internal sealed class WindowPlacementBinding
+ {
+ private readonly Window window;
+ private readonly string name;
+ private readonly string settingsPath;
+ private PixelPoint? normalPosition;
+ private (int Width, int Height)? normalSize;
+
+ private WindowPlacementBinding(Window window, string name, string settingsPath)
+ {
+ this.window = window;
+ this.name = name;
+ this.settingsPath = settingsPath;
+ }
+
+ /// Call before the window is shown.
+ public static void Remember(Window window, string name, string settingsPath)
+ {
+ var binding = new WindowPlacementBinding(window, name, settingsPath);
+ binding.Restore();
+ window.Opened += (_, _) => binding.Track();
+ window.PositionChanged += (_, _) => binding.Track();
+ window.PropertyChanged += (_, e) =>
+ {
+ if (e.Property == TopLevel.ClientSizeProperty
+ || e.Property == Window.WindowStateProperty)
+ binding.Track();
+ };
+ window.Closing += (_, _) => binding.Save();
+ }
+
+ private void Restore()
+ {
+ var saved = WindowPlacement.Read(UserSettingsFile.Read(settingsPath), name);
+ var screens = window.Screens?.All;
+ if (saved == null || screens == null)
+ return;
+
+ var areas = screens
+ .Select(s => s.WorkingArea)
+ .Select(a => new OpenNest.Desktop.PixelRect(a.X, a.Y, a.Width, a.Height))
+ .ToList();
+ var placement = saved.FitTo(areas);
+ var screen = placement.Location is { } at
+ ? screens.First(s => s.WorkingArea.Contains(new PixelPoint(at.X, at.Y)))
+ : screens.FirstOrDefault(s => s.IsPrimary) ?? screens.FirstOrDefault();
+
+ if (placement.Location is { } location)
+ {
+ window.WindowStartupLocation = WindowStartupLocation.Manual;
+ window.Position = new PixelPoint(location.X, location.Y);
+ }
+
+ if (placement.Size is { } size)
+ {
+ var scaling = screen?.Scaling ?? 1;
+ window.Width = size.Width / scaling;
+ window.Height = size.Height / scaling;
+ }
+
+ if (placement.Maximized)
+ window.WindowState = WindowState.Maximized;
+ }
+
+ /// Remembers the normal bounds, which maximizing keeps.
+ private void Track()
+ {
+ if (window.WindowState != WindowState.Normal || !window.IsVisible)
+ return;
+
+ normalPosition = window.Position;
+ var scaling = window.DesktopScaling;
+ normalSize = ((int)System.Math.Round(window.ClientSize.Width * scaling),
+ (int)System.Math.Round(window.ClientSize.Height * scaling));
+ }
+
+ private void Save()
+ {
+ Track();
+ var maximized = window.WindowState == WindowState.Maximized;
+ UserSettingsFile.Update(settingsPath, values =>
+ {
+ // A window never shown normal this session keeps the bounds saved before.
+ var previous = WindowPlacement.Read(values, name);
+ var placement = new WindowPlacement(
+ normalPosition is { } p ? (p.X, p.Y) : previous?.Location,
+ normalSize ?? previous?.Size,
+ maximized);
+ placement.WriteTo(values, name);
+ });
+ }
+ }
+}