From e2a27df7ef9541167f0e5ec04438fa4a315c4b1a Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Sat, 14 Nov 2026 18:56:38 -0500 Subject: [PATCH] feat(avalonia): open a new nest on startup and convert the old nest template Once the main window opens, the Avalonia app runs the shared start-up steps as the WinForms main window does on load: a legacy nest template (NestTemplatePath) becomes the nest defaults file, and Tools > Options > Create a new nest on startup (on unless turned off) opens a new nest, which until now that checkbox only saved. A failed template conversion shows the WinForms "Nest Template" warning through the queue the load warnings use; the new nest opens only once every queued warning has closed. File > New's "Nest Defaults" warning now goes through the same queue, so it never stacks on a load warning, and the queue stops once the window closes. New beside WinForms, which takes no nest paths on its command line and converts synchronously: no new nest is added when a nest is already open by then (named on the command line, or opened or dropped while the template converted), start- up stops when the window closes (checked again just before the nest number is taken) or asks about unsaved nests meanwhile, and --snapshot runs neither step, so an unattended render changes no settings and takes no nest number. --- OpenNest.Avalonia/App.axaml.cs | 3 + OpenNest.Avalonia/MainWindow.axaml.cs | 90 +++++++++++++++++++++++---- 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/OpenNest.Avalonia/App.axaml.cs b/OpenNest.Avalonia/App.axaml.cs index 096952bd..c7ed6fce 100644 --- a/OpenNest.Avalonia/App.axaml.cs +++ b/OpenNest.Avalonia/App.axaml.cs @@ -31,7 +31,10 @@ namespace OpenNest.Avalonia window.SaveSnapshotAndClose(args[snapshot + 1], snapshot + 2 < args.Length && int.TryParse(args[snapshot + 2], out var plate) ? plate : 1); else + { window.RememberPlacement(); + window.RunStartupOnOpen(); + } } base.OnFrameworkInitializationCompleted(); diff --git a/OpenNest.Avalonia/MainWindow.axaml.cs b/OpenNest.Avalonia/MainWindow.axaml.cs index 588ff6ed..98e28c03 100644 --- a/OpenNest.Avalonia/MainWindow.axaml.cs +++ b/OpenNest.Avalonia/MainWindow.axaml.cs @@ -39,10 +39,12 @@ namespace OpenNest.Avalonia private bool writingFile; // True while Exit asks about unsaved nests, so a second close request is ignored. private bool askingToExit; - // Load warnings shown one dialog at a time, once the window is open. - private readonly Queue<(string Name, IReadOnlyList Warnings)> pendingWarnings = - new(); + // Load and start-up warnings shown one dialog at a time, once the window is open. + private readonly Queue<(string Title, string Message)> pendingWarnings = new(); private bool showingWarnings; + private Task warningsShown = Task.CompletedTask; + // The window is closing (its close went ahead) or closed. + private bool closed; public MainWindow() { @@ -64,6 +66,7 @@ namespace OpenNest.Avalonia FillStrategyOptions.ApplySaved(userSettings); ColorSchemeRegistry.Applied += OnColorSchemeApplied; Closed += (_, _) => ColorSchemeRegistry.Applied -= OnColorSchemeApplied; + Closed += (_, _) => closed = true; tabs.Changed += (_, _) => ShowActiveTab(); KeyBindings.Add(new KeyBinding { @@ -138,7 +141,8 @@ namespace OpenNest.Avalonia ? "1 load warning" : $"{warnings.Count} load warnings"; ShowNotice($"{tab.Name}: {count}"); - pendingWarnings.Enqueue((tab.Name, warnings)); + pendingWarnings.Enqueue(("Nest Load Warnings", + $"{tab.Name}:\n\n" + string.Join(Environment.NewLine, warnings))); if (IsVisible && !snapshotMode) _ = ShowLoadWarningsAsync(); } @@ -178,7 +182,11 @@ namespace OpenNest.Avalonia } AddView(tabs.Add(new Document { Nest = created.Nest })); if (created.Warning != null) - await ConfirmDialog.ShowAsync(this, "Nest Defaults", created.Warning); + { + // Through the queue, so it never stacks on a load warning already showing. + pendingWarnings.Enqueue(("Nest Defaults", created.Warning)); + await ShowLoadWarningsAsync(); + } } private void AddView(NestTab tab) @@ -201,6 +209,56 @@ namespace OpenNest.Avalonia WindowPlacementBinding.Remember( this, WindowPlacement.MainWindow, Data.UserSettingsFile.DefaultPath); + /// + /// Once the window opens, converts a legacy nest template into the nest defaults and + /// opens a new nest when Tools > Options asks for one, as the WinForms main window does + /// on load. No new nest beside nests already open (from the command line, or opened or + /// dropped while the template converted). Not used for --snapshot, which must neither + /// change settings nor take a nest number. + /// + public void RunStartupOnOpen() => Opened += async (_, _) => await RunStartupAsync(); + + // The window is closing, or asking about unsaved nests before exit: start-up stops. + private bool StartupInterrupted => closed || closeConfirmed || askingToExit; + + private async Task RunStartupAsync() + { + DesktopStartupResult startup; + try + { + // Reads the template's nest and writes the defaults file: off the UI thread. + startup = await Task.Run(() => DesktopStartup.Run( + Data.UserSettingsFile.DefaultPath, Data.NestDefaults.DefaultPath, + Engine.BestFit.BestFitCacheStore.Shared)); + } + catch (Exception ex) + { + ShowNotice( + $"Start-up could not finish: {ex.Message} Use File > New to start a nest."); + return; + } + // On the UI thread, as the window's other user-settings writers are. + if (startup.ClearTemplateSetting) + DesktopStartup.ClearTemplateSetting(Data.UserSettingsFile.DefaultPath); + if (StartupInterrupted) + return; + if (startup.TemplateWarning != null) + { + pendingWarnings.Enqueue(("Nest Template", startup.TemplateWarning)); + // Waits for every queued warning, including ones already showing. + await ShowLoadWarningsAsync(); + } + if (!startup.CreateNewNest) + return; + // The machines load first, so the check below is close to the nest-number request. + await MachinesLoaded; + // Closed meanwhile (a window closed in code takes its dialogs with it), or nests + // opened from the command line, or opened or dropped meanwhile: no new nest. + if (StartupInterrupted || tabs.Tabs.Count > 0) + return; + await NewNestAsync(); + } + /// Runs Auto Nest once the window opens, for --auto-nest. public void AutoNestOnOpen() => Opened += (_, _) => autoNest = RunAutoNestAsync(); @@ -359,6 +417,8 @@ namespace OpenNest.Avalonia { if (closeConfirmed || (!tabs.AnyModified && !writingFile)) { + // Set before Avalonia closes the owned dialogs, whose awaiters then resume. + closed = true; nesting?.Cancel(); SaveAllViewToggles(); return; @@ -558,17 +618,23 @@ namespace OpenNest.Avalonia private static readonly FilePickerFileType NestFiles = new("Nest files") { Patterns = new[] { "*.nest" } }; - // Shows the queued load warnings one dialog at a time, as WinForms does after each open. - private async Task ShowLoadWarningsAsync() + /// Shows the queued warnings one at a time; finishes when the queue is empty, + /// also when called while they are already showing. + private Task ShowLoadWarningsAsync() + { + if (!showingWarnings) + warningsShown = ShowQueuedWarningsAsync(); + return warningsShown; + } + + private async Task ShowQueuedWarningsAsync() { - if (showingWarnings) - return; showingWarnings = true; try { - while (pendingWarnings.TryDequeue(out var item)) - await ConfirmDialog.ShowAsync(this, "Nest Load Warnings", - $"{item.Name}:\n\n" + string.Join(Environment.NewLine, item.Warnings)); + // None once the window has closed: a dialog needs a live owner. + while (!closed && pendingWarnings.TryDequeue(out var item)) + await ConfirmDialog.ShowAsync(this, item.Title, item.Message); } finally {