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); + }); + } + } +}