Krzysztof Krysiński 6 miesięcy temu
rodzic
commit
38fafbf454

+ 1 - 1
src/PixiEditor.Extensions.CommonApi/UserPreferences/PreferencesConstants.cs

@@ -26,5 +26,5 @@ public static class PreferencesConstants
     public const bool AutosaveToDocumentPathDefault = false;
 
     public const string LastCrashFile = "LastCrashFile";
-    /*public const string UnsavedNextSessionFiles = "UnsavedNextSessionFiles";*/
+    public const string NextSessionFiles = "NextSessionFiles";
 }

+ 6 - 0
src/PixiEditor.UI.Common/Accents/Base.axaml

@@ -96,6 +96,9 @@
             <Color x:Key="VerticalSnapAxisColor">#5fad65</Color>
             <Color x:Key="SnapPointPreviewColor">#68abdf</Color>
 
+            <Color x:Key="AutosaveDotColor">white</Color>
+            <Color x:Key="UnsavedDotColor">white</Color>
+
             <system:Double x:Key="ThemeDisabledOpacity">0.4</system:Double>
 
             <SolidColorBrush x:Key="ThemeForegroundLowBrush" Color="gray"></SolidColorBrush>
@@ -205,6 +208,9 @@
             <SolidColorBrush x:Key="DockApplicationAccentForegroundBrush" Color="{DynamicResource ThemeForegroundColor}"/>
             <SolidColorBrush x:Key="DockApplicationAccentBrushIndicator" Color="{DynamicResource AccentColor}"/>
 
+            <SolidColorBrush x:Key="UnsavedDotBrush" Color="{DynamicResource UnsavedDotColor}"/>
+            <SolidColorBrush x:Key="AutosaveDotBrush" Color="{DynamicResource AutosaveDotColor}"/>
+
             <SolidColorBrush x:Key="DockThemeBorderLowBrush" Color="{DynamicResource ThemeBorderMidColor}" />
             <SolidColorBrush x:Key="DockThemeBackgroundBrush" Color="{DynamicResource ThemeBackgroundColor}" />
             <SolidColorBrush x:Key="DockThemeAccentBrush" Color="{DynamicResource ThemeAccentColor}" />

+ 7 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -912,5 +912,11 @@
   "UNEXPECTED_SHUTDOWN": "Unexpected shutdown",
   "UNEXPECTED_SHUTDOWN_MSG": "PixiEditor was unexpectedly shut down. We've loaded latest autosave of your files.",
   "OK": "OK",
-  "OPEN_AUTOSAVES": "Browse Autosaves"
+  "OPEN_AUTOSAVES": "Browse Autosaves",
+  "AUTOSAVE_SETTINGS_HEADER": "Autosave",
+  "AUTOSAVE_SETTINGS_SAVE_STATE": "Reopen last files on startup",
+  "AUTOSAVE_SETTINGS_PERIOD": "Autosave period",
+  "AUTOSAVE_ENABLED": "Autosave enabled",
+  "MINUTE_UNIVERSAL": "min",
+  "AUTOSAVE_SETTINGS_SAVE_USER_FILE": "Autosave to selected file"
 }

+ 6 - 0
src/PixiEditor/Helpers/ThemeResources.cs

@@ -35,4 +35,10 @@ public static class ThemeResources
 
     public static SolidColorBrush ThemeControlLowBrush =>
         ResourceLoader.GetResource<SolidColorBrush>("ThemeControlLowBrush", Application.Current.ActualThemeVariant);
+
+    public static IBrush AutosaveDotBrush =>
+        ResourceLoader.GetResource<IBrush>("AutosaveDotBrush", Application.Current.ActualThemeVariant);
+
+    public static IBrush UnsavedDotBrush =>
+        ResourceLoader.GetResource<IBrush>("UnsavedDotBrush", Application.Current.ActualThemeVariant);
 }

+ 2 - 1
src/PixiEditor/Models/DocumentModels/Autosave/AutosaveHistoryType.cs

@@ -3,5 +3,6 @@
 internal enum AutosaveHistoryType
 {
     Periodic,
-    OnClose
+    OnClose,
+    Crash
 }

+ 13 - 0
src/PixiEditor/Models/DocumentModels/Autosave/SessionFile.cs

@@ -0,0 +1,13 @@
+namespace PixiEditor.Models.DocumentModels.Autosave;
+
+public struct SessionFile
+{
+    public string? OriginalFilePath { get; set; }
+    public string? AutosaveFilePath { get; set; }
+
+    public SessionFile(string? originalFilePath, string? autosaveFilePath)
+    {
+        OriginalFilePath = originalFilePath;
+        AutosaveFilePath = autosaveFilePath;
+    }
+}

+ 8 - 0
src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs

@@ -214,6 +214,9 @@ internal class DocumentUpdater
             case ProcessingColorSpace_ChangeInfo info:
                 ProcessProcessingColorSpace(info);
                 break;
+            case MarkAsAutosaved_PassthroughAction info:
+                MarkAsAutosaved(info);
+                break;
         }
     }
 
@@ -796,4 +799,9 @@ internal class DocumentUpdater
     {
         doc.SetProcessingColorSpace(info.NewColorSpace);
     }
+
+    private void MarkAsAutosaved(MarkAsAutosaved_PassthroughAction info)
+    {
+        doc.InternalMarkSaveState(info.Type);
+    }
 }

+ 1 - 1
src/PixiEditor/Models/DocumentPassthroughActions/MarkAsAutosaved_PassthroughAction.cs

@@ -11,4 +11,4 @@ internal enum DocumentMarkType
     UnAutosaved
 }
 
-internal record class MarkAsSavedAutosaved_PassthroughAction(DocumentMarkType Type) : IChangeInfo, IAction;
+internal record class MarkAsAutosaved_PassthroughAction(DocumentMarkType Type) : IChangeInfo, IAction;

+ 18 - 34
src/PixiEditor/Models/ExceptionHandling/CrashReport.cs

@@ -308,6 +308,7 @@ internal class CrashReport : IDisposable
             .AppendLine($"  Size: {document.SizeBindable}")
             .AppendLine($"  Layer Count: {FormatObject(document.StructureHelper.GetAllLayers().Count)}")
             .AppendLine($"  Has all changes saved: {document.AllChangesSaved}")
+            .AppendLine($"  Has all changes autosaved: {document.AllChangesAutosaved}")
             .AppendLine($"  Horizontal Symmetry Enabled: {document.HorizontalSymmetryAxisEnabledBindable}")
             .AppendLine($"  Horizontal Symmetry Value: {FormatObject(document.HorizontalSymmetryAxisYBindable)}")
             .AppendLine($"  Vertical Symmetry Enabled: {document.VerticalSymmetryAxisEnabledBindable}")
@@ -470,20 +471,15 @@ internal class CrashReport : IDisposable
                     .Where(x =>
                         x.FullName.StartsWith("Documents") &&
                         x.FullName.EndsWith(".pixi"))
-                    .Select(entry => new RecoveredPixi(null, null, entry, null)));
+                    .Select(entry => new RecoveredPixi(null, null, entry)));
 
             return recoveredDocuments;
         }
 
         foreach (var doc in sessionInfo.OpenedDocuments)
         {
-            ZipArchiveEntry? autosaved = null;
-            if (doc.AutosavePath != null)
-            {
-                autosaved = ZipFile.GetEntry($"Autosave/{Path.GetFileName(doc.AutosavePath)}");
-            }
-
-            recoveredDocuments.Add(new RecoveredPixi(doc.OriginalPath, doc.AutosavePath, ZipFile.GetEntry($"Documents/{doc.ZipName}"), autosaved));
+            recoveredDocuments.Add(new RecoveredPixi(doc.OriginalPath, doc.AutosavePath,
+                ZipFile.GetEntry($"Documents/{doc.ZipName}")));
         }
 
         return recoveredDocuments;
@@ -575,23 +571,11 @@ internal class CrashReport : IDisposable
 
                 using Stream documentStream = archive.CreateEntry($"Documents/{nameInZip}").Open();
                 documentStream.Write(serialized);
+                document.AutosaveViewModel.Autosave(AutosaveHistoryType.Crash);
 
                 originalPaths.Add(new CrashedFileInfo(nameInZip, document.FullFilePath,
                     document.AutosaveViewModel.LastAutosavedPath));
-            }
-            catch { }
 
-            try
-            {
-                if (document.AutosaveViewModel.LastAutosavedPath != null)
-                {
-                    using var file = File.OpenRead(document.AutosaveViewModel.LastAutosavedPath);
-                    using var entry = archive
-                        .CreateEntry($"Autosave/{Path.GetFileName(document.AutosaveViewModel.LastAutosavedPath)}")
-                        .Open();
-
-                    file.CopyTo(entry);
-                }
             }
             catch { }
 
@@ -638,7 +622,6 @@ internal class CrashReport : IDisposable
         public string? AutosavePath { get; }
 
         public ZipArchiveEntry RecoveredEntry { get; }
-        public ZipArchiveEntry? AutosaveEntry { get; }
 
         public byte[] GetRecoveredBytes()
         {
@@ -650,22 +633,23 @@ internal class CrashReport : IDisposable
             return buffer;
         }
 
-        public byte[] GetAutosaveBytes()
-        {
-            var buffer = new byte[AutosaveEntry.Length];
-            using var stream = AutosaveEntry.Open();
-
-            stream.ReadExactly(buffer);
-
-            return buffer;
-        }
-
-        public RecoveredPixi(string? originalPath, string? autosavePath, ZipArchiveEntry recoveredEntry, ZipArchiveEntry? autosaveEntry)
+        public RecoveredPixi(string? originalPath, string? autosavePath, ZipArchiveEntry recoveredEntry)
         {
             OriginalPath = originalPath;
             AutosavePath = autosavePath;
             RecoveredEntry = recoveredEntry;
-            AutosaveEntry = autosaveEntry;
+        }
+
+        public byte[] TryGetAutoSaveBytes()
+        {
+            if (AutosavePath == null)
+                return [];
+
+            string autosavePixiFile = AutosavePath;
+            if (!File.Exists(autosavePixiFile))
+                return [];
+
+            return File.ReadAllBytes(autosavePixiFile);
         }
     }
 }

+ 2 - 0
src/PixiEditor/Models/Handlers/IDocument.cs

@@ -16,6 +16,7 @@ using PixiEditor.Models.Rendering;
 using PixiEditor.Models.Structures;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
+using PixiEditor.Models.DocumentPassthroughActions;
 
 namespace PixiEditor.Models.Handlers;
 
@@ -66,4 +67,5 @@ internal interface IDocument : IHandler
     public void UpdateSavedState();
 
     internal void InternalRaiseLayersChanged(LayersChangedEventArgs e);
+    internal void InternalMarkSaveState(DocumentMarkType type);
 }

+ 4 - 2
src/PixiEditor/Styles/Templates/DocumentTabTemplate.axaml

@@ -21,7 +21,7 @@
                 Converter={StaticResource ConditionToSizeConverter}, ConverterParameter=20}" />
                 <ColumnDefinition Width="*" />
                 <ColumnDefinition
-                    Width="{Binding !IsSaved,
+                    Width="{Binding ShowUnsavedDot,
                 Converter={StaticResource ConditionToSizeConverter}, ConverterParameter=8}" />
                 <ColumnDefinition
                     Width="{Binding ShowCloseButton,
@@ -41,7 +41,9 @@
                            Foreground="{DynamicResource ThemeForegroundBrush}"
                            VerticalAlignment="Center"
                            Text="{Binding $parent[ContentControl].Tag.Title, FallbackValue=TITLE}" />
-                <Ellipse Grid.Column="2" Width="5" Height="5" Fill="White" IsVisible="{Binding !IsSaved}" />
+                <Ellipse Grid.Column="2" Width="5" Height="5"
+                         Fill="{Binding SavedStateColor}"
+                         IsVisible="{Binding ShowUnsavedDot}" />
                 <Button Grid.Column="3" Classes="CloseTabButton"
                         Height="16" Width="16" Margin="5, 0, 0, 0"
                         VerticalAlignment="Center"

+ 29 - 6
src/PixiEditor/ViewModels/Dock/DocumentTabCustomizationSettings.cs

@@ -1,18 +1,41 @@
+using Avalonia.Media;
 using PixiDocks.Core.Docking;
+using PixiEditor.Helpers;
 
 namespace PixiEditor.ViewModels.Dock;
 
 public class DocumentTabCustomizationSettings : TabCustomizationSettings
 {
-    private bool isSaved;
-    public bool IsSaved
+    private SavedState savedState;
+    public SavedState SavedState
     {
-        get => isSaved;
-        set => SetField(ref isSaved, value);
+        get => savedState;
+        set
+        {
+            SetField(ref savedState, value);
+            OnPropertyChanged(nameof(ShowUnsavedDot));
+            OnPropertyChanged(nameof(SavedStateColor));
+        }
     }
 
-    public DocumentTabCustomizationSettings(object? icon = null, bool showCloseButton = false, bool isSaved = true) : base(icon, showCloseButton)
+    public bool ShowUnsavedDot => SavedState != SavedState.Saved;
+    public IBrush SavedStateColor => SavedState switch
     {
-        IsSaved = isSaved;
+        SavedState.Saved => Brushes.Transparent,
+        SavedState.Autosaved => ThemeResources.AutosaveDotBrush,
+        SavedState.Unsaved => ThemeResources.UnsavedDotBrush,
+        _ => Brushes.Transparent
+    };
+
+    public DocumentTabCustomizationSettings(object? icon = null, bool showCloseButton = false, SavedState savedState = SavedState.Saved) : base(icon, showCloseButton)
+    {
+        SavedState = savedState;
     }
 }
+
+public enum SavedState
+{
+    Saved,
+    Autosaved,
+    Unsaved
+}

+ 28 - 8
src/PixiEditor/ViewModels/Document/AutosaveDocumentViewModel.cs

@@ -11,6 +11,7 @@ namespace PixiEditor.ViewModels.Document;
 internal class AutosaveDocumentViewModel : ObservableObject
 {
     private AutosaveStateData? autosaveStateData;
+
     public AutosaveStateData? AutosaveStateData
     {
         get => autosaveStateData;
@@ -18,6 +19,7 @@ internal class AutosaveDocumentViewModel : ObservableObject
     }
 
     private bool currentDocumentAutosaveEnabled = true;
+
     public bool CurrentDocumentAutosaveEnabled
     {
         get => currentDocumentAutosaveEnabled;
@@ -38,9 +40,16 @@ internal class AutosaveDocumentViewModel : ObservableObject
 
     public string LastAutosavedPath { get; set; }
 
-    private static bool SaveUserFileEnabled => IPreferences.Current!.GetPreference(PreferencesConstants.AutosaveToDocumentPath, PreferencesConstants.AutosaveToDocumentPathDefault);
-    private static double AutosavePeriod => IPreferences.Current!.GetPreference(PreferencesConstants.AutosavePeriodMinutes, PreferencesConstants.AutosavePeriodDefault);
-    private static bool AutosaveEnabledGlobally => IPreferences.Current!.GetPreference(PreferencesConstants.AutosaveEnabled, PreferencesConstants.AutosaveEnabledDefault);
+    private static bool SaveUserFileEnabled => IPreferences.Current!.GetPreference(
+        PreferencesConstants.AutosaveToDocumentPath, PreferencesConstants.AutosaveToDocumentPathDefault);
+
+    private static double AutosavePeriod =>
+        IPreferences.Current!.GetPreference(PreferencesConstants.AutosavePeriodMinutes,
+            PreferencesConstants.AutosavePeriodDefault);
+
+    private static bool AutosaveEnabledGlobally =>
+        IPreferences.Current!.GetPreference(PreferencesConstants.AutosaveEnabled,
+            PreferencesConstants.AutosaveEnabledDefault);
 
     public AutosaveDocumentViewModel(DocumentViewModel document, DocumentInternalParts internals)
     {
@@ -75,12 +84,12 @@ internal class AutosaveDocumentViewModel : ObservableObject
         AutosaveStateData = autosaver.State;
     }
 
-    public bool AutosaveOnClose()
+    public bool Autosave(AutosaveHistoryType type)
     {
         if (Document.AllChangesSaved)
         {
             AddAutosaveHistoryEntry(
-                AutosaveHistoryType.OnClose,
+                type,
                 AutosaveHistoryResult.NothingToSave);
             return true;
         }
@@ -92,7 +101,10 @@ internal class AutosaveDocumentViewModel : ObservableObject
             ExportConfig config = new ExportConfig(Document.SizeBindable);
             bool success = Exporter.TrySave(Document, filePath, config, null) == SaveResult.Success;
             if (success)
-                AddAutosaveHistoryEntry(AutosaveHistoryType.OnClose, AutosaveHistoryResult.SavedBackup);
+            {
+                AddAutosaveHistoryEntry(type, AutosaveHistoryResult.SavedBackup);
+                LastAutosavedPath = filePath;
+            }
 
             return success;
         }
@@ -102,16 +114,24 @@ internal class AutosaveDocumentViewModel : ObservableObject
         }
     }
 
+    public bool AutosaveOnClose()
+    {
+        return Autosave(AutosaveHistoryType.OnClose);
+    }
+
     public void AddAutosaveHistoryEntry(AutosaveHistoryType type, AutosaveHistoryResult result)
     {
-        List<AutosaveHistorySession>? historySessions = IPreferences.Current!.GetLocalPreference<List<AutosaveHistorySession>>(PreferencesConstants.AutosaveHistory);
+        List<AutosaveHistorySession>? historySessions =
+            IPreferences.Current!.GetLocalPreference<List<AutosaveHistorySession>>(PreferencesConstants
+                .AutosaveHistory);
         if (historySessions is null)
             historySessions = new();
 
         AutosaveHistorySession currentSession;
         if (historySessions.Count == 0 || historySessions[^1].SessionGuid != ViewModelMain.Current.CurrentSessionId)
         {
-            currentSession = new AutosaveHistorySession(ViewModelMain.Current.CurrentSessionId, ViewModelMain.Current.LaunchDateTime);
+            currentSession = new AutosaveHistorySession(ViewModelMain.Current.CurrentSessionId,
+                ViewModelMain.Current.LaunchDateTime);
             historySessions.Add(currentSession);
         }
         else

+ 35 - 5
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -107,6 +107,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     }
 
     private Guid? lastChangeOnSave = null;
+    private Guid? lastChangeOnAutosave = null;
 
     public bool AllChangesSaved
     {
@@ -116,6 +117,14 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         }
     }
 
+    public bool AllChangesAutosaved
+    {
+        get
+        {
+            return Internals.Tracker.LastChangeGuid == lastChangeOnAutosave;
+        }
+    }
+
     public DateTime OpenedUTC { get; } = DateTime.UtcNow;
 
     private bool horizontalSymmetryAxisEnabled;
@@ -539,19 +548,40 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
     public void MarkAsSaved()
     {
-        lastChangeOnSave = Internals.Tracker.LastChangeGuid;
-        OnPropertyChanged(nameof(AllChangesSaved));
+        Internals.ActionAccumulator.AddActions(new MarkAsAutosaved_PassthroughAction(DocumentMarkType.Saved));
     }
 
     public void MarkAsAutosaved()
     {
-        Internals.ActionAccumulator.AddActions(new MarkAsSavedAutosaved_PassthroughAction(DocumentMarkType.Autosaved));
+        Internals.ActionAccumulator.AddActions(new MarkAsAutosaved_PassthroughAction(DocumentMarkType.Autosaved));
     }
 
     public void MarkAsUnsaved()
     {
-        lastChangeOnSave = Guid.NewGuid();
-        OnPropertyChanged(nameof(AllChangesSaved));
+        Internals.ActionAccumulator.AddActions(new MarkAsAutosaved_PassthroughAction(DocumentMarkType.Unsaved));
+    }
+
+    public void InternalMarkSaveState(DocumentMarkType type)
+    {
+        switch (type)
+        {
+            case DocumentMarkType.Saved:
+                lastChangeOnSave = Internals.Tracker.LastChangeGuid;
+                OnPropertyChanged(nameof(AllChangesSaved));
+                break;
+            case DocumentMarkType.Unsaved:
+                lastChangeOnSave = Guid.NewGuid();
+                OnPropertyChanged(nameof(AllChangesSaved));
+                break;
+            case DocumentMarkType.Autosaved:
+                lastChangeOnAutosave = Internals.Tracker.LastChangeGuid;
+                OnPropertyChanged(nameof(AllChangesAutosaved));
+                break;
+            case DocumentMarkType.UnAutosaved:
+                lastChangeOnAutosave = Guid.NewGuid();
+                OnPropertyChanged(nameof(AllChangesAutosaved));
+                break;
+        }
     }
 
     public OneOf<Error, Surface> TryRenderWholeImage(KeyFrameTime frameTime, VecI renderSize)

+ 53 - 45
src/PixiEditor/ViewModels/SubViewModels/FileViewModel.cs

@@ -416,7 +416,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
                     try
                     {
                         OpenRecoveredDotPixi(document.OriginalPath, document.AutosavePath,
-                            AutosaveHelper.GetAutosaveGuid(document.AutosavePath), document.GetAutosaveBytes());
+                            AutosaveHelper.GetAutosaveGuid(document.AutosavePath), document.TryGetAutoSaveBytes());
                     }
                     catch (Exception veryDeepE)
                     {
@@ -664,20 +664,24 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
         if (lastSession.AutosaveEntries.Count == 0)
             return;
 
+        bool shutdownWasUnexpected = lastSession.AutosaveEntries.All(a => a.Type != AutosaveHistoryType.OnClose);
+        if (shutdownWasUnexpected)
+        {
+            LoadFromUnexpectedShutdown(lastSession);
+            return;
+        }
+
+        var nextSessionFiles = preferences.GetLocalPreference<SessionFile[]>(PreferencesConstants.NextSessionFiles, []);
         List<List<AutosaveHistoryEntry>> perDocumentHistories = (
             from entry in lastSession.AutosaveEntries
+            where nextSessionFiles.Any(a =>
+                a.AutosaveFilePath == AutosaveHelper.GetAutosavePath(entry.TempFileGuid) || entry.Type != AutosaveHistoryType.OnClose)
             group entry by entry.TempFileGuid
             into entryGroup
             select entryGroup.OrderBy(a => a.DateTime).ToList()
         ).ToList();
 
-        bool shutdownWasUnexpected = lastSession.AutosaveEntries.All(a => a.Type != AutosaveHistoryType.OnClose);
-        if (shutdownWasUnexpected)
-        {
-            LoadFromUnexpectedShutdown(lastSession);
-
-            return;
-        }
+        var toLoad = nextSessionFiles.ToList();
 
         foreach (var documentHistory in perDocumentHistories)
         {
@@ -687,26 +691,17 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
                 if (lastEntry.Type != AutosaveHistoryType.OnClose)
                 {
                     // unexpected shutdown happened, this file wasn't saved on close, but we supposedly have a backup
-                    LoadFromAutosave(lastEntry);
+                    LoadNewest(lastEntry);
+                    toLoad.RemoveAll(a =>
+                        a.AutosaveFilePath == AutosaveHelper.GetAutosavePath(lastEntry.TempFileGuid)
+                        || a.OriginalFilePath == lastEntry.OriginalPath);
                 }
-                else
+                else if (lastEntry.Result == AutosaveHistoryResult.SavedBackup)
                 {
-                    switch (lastEntry.Result)
-                    {
-                        case AutosaveHistoryResult.SavedBackup:
-                            LoadFromAutosave(lastEntry);
-                            break;
-                        case AutosaveHistoryResult.SavedUserFile:
-                        case AutosaveHistoryResult.NothingToSave:
-                            if (lastEntry.OriginalPath != null)
-                            {
-                                OpenFromPath(lastEntry.OriginalPath);
-                            }
-
-                            break;
-                        default:
-                            throw new ArgumentOutOfRangeException();
-                    }
+                    LoadFromAutosave(lastEntry);
+                    toLoad.RemoveAll(a =>
+                        a.AutosaveFilePath == AutosaveHelper.GetAutosavePath(lastEntry.TempFileGuid)
+                        || a.OriginalFilePath == lastEntry.OriginalPath);
                 }
             }
             catch (Exception e)
@@ -715,7 +710,16 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
             }
         }
 
+        foreach (var file in toLoad)
+        {
+            if (file.OriginalFilePath != null)
+            {
+                OpenFromPath(file.OriginalFilePath);
+            }
+        }
+
         Owner.AutosaveViewModel.CleanupAutosavedFilesAndHistory();
+        preferences.UpdateLocalPreference(PreferencesConstants.NextSessionFiles, Array.Empty<SessionFile>());
     }
 
     private void LoadFromUnexpectedShutdown(AutosaveHistorySession lastSession)
@@ -732,25 +736,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
             foreach (var backup in lastBackups)
             {
                 AutosaveHistoryEntry lastEntry = backup[^1];
-
-                bool loadFromUserFile = false;
-
-                if (lastEntry.OriginalPath != null && File.Exists(lastEntry.OriginalPath))
-                {
-                    DateTime saveFileWriteTime = File.GetLastWriteTime(lastEntry.OriginalPath);
-                    DateTime autosaveWriteTime = lastEntry.DateTime;
-
-                    loadFromUserFile = saveFileWriteTime > autosaveWriteTime;
-                }
-
-                if (loadFromUserFile)
-                {
-                    OpenFromPath(lastEntry.OriginalPath);
-                }
-                else
-                {
-                    LoadFromAutosave(lastEntry);
-                }
+                LoadNewest(lastEntry);
             }
 
             OptionsDialog<LocalizedString> dialog = new OptionsDialog<LocalizedString>("UNEXPECTED_SHUTDOWN",
@@ -768,6 +754,28 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
         }
     }
 
+    private void LoadNewest(AutosaveHistoryEntry lastEntry)
+    {
+        bool loadFromUserFile = false;
+
+        if (lastEntry.OriginalPath != null && File.Exists(lastEntry.OriginalPath))
+        {
+            DateTime saveFileWriteTime = File.GetLastWriteTime(lastEntry.OriginalPath);
+            DateTime autosaveWriteTime = lastEntry.DateTime;
+
+            loadFromUserFile = saveFileWriteTime > autosaveWriteTime;
+        }
+
+        if (loadFromUserFile)
+        {
+            OpenFromPath(lastEntry.OriginalPath);
+        }
+        else
+        {
+            LoadFromAutosave(lastEntry);
+        }
+    }
+
     private void LoadFromAutosave(AutosaveHistoryEntry entry)
     {
         string path = AutosaveHelper.GetAutosavePath(entry.TempFileGuid);

+ 31 - 8
src/PixiEditor/ViewModels/SubViewModels/ViewportWindowViewModel.cs

@@ -11,20 +11,23 @@ using PixiEditor.Views.Visuals;
 
 namespace PixiEditor.ViewModels.SubViewModels;
 #nullable enable
-internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockableContent, IDockableCloseEvents, IDockableSelectionEvents
+internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockableContent, IDockableCloseEvents,
+    IDockableSelectionEvents
 {
     public DocumentViewModel Document { get; }
     public ExecutionTrigger<VecI> CenterViewportTrigger { get; } = new ExecutionTrigger<VecI>();
     public ExecutionTrigger<double> ZoomViewportTrigger { get; } = new ExecutionTrigger<double>();
 
-    
+
     public string Index => _index;
 
     public string Id => id;
     public string Title => $"{Document.FileName}{Index}";
     public bool CanFloat => true;
     public bool CanClose => true;
-    public DocumentTabCustomizationSettings TabCustomizationSettings { get; } = new DocumentTabCustomizationSettings(showCloseButton: true);
+
+    public DocumentTabCustomizationSettings TabCustomizationSettings { get; } =
+        new DocumentTabCustomizationSettings(showCloseButton: true);
 
     TabCustomizationSettings IDockableContent.TabCustomizationSettings => TabCustomizationSettings;
 
@@ -44,7 +47,7 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
             OnPropertyChanged(nameof(FlipX));
         }
     }
-    
+
     private bool _flipY;
 
     public bool FlipY
@@ -56,7 +59,7 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
             OnPropertyChanged(nameof(FlipY));
         }
     }
-    
+
     public string RenderOutputName
     {
         get => renderOutputName;
@@ -68,7 +71,7 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
     }
 
     private ViewportColorChannels _channels = ViewportColorChannels.Default;
-    
+
     public ViewportColorChannels Channels
     {
         get => _channels;
@@ -102,7 +105,8 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         Document = document;
         Document.SizeChanged += DocumentOnSizeChanged;
         Document.PropertyChanged += DocumentOnPropertyChanged;
-        previewPainterControl = new PreviewPainterControl(Document.PreviewPainter, Document.AnimationDataViewModel.ActiveFrameTime.Frame);
+        previewPainterControl = new PreviewPainterControl(Document.PreviewPainter,
+            Document.AnimationDataViewModel.ActiveFrameTime.Frame);
         TabCustomizationSettings.Icon = previewPainterControl;
     }
 
@@ -119,7 +123,11 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         }
         else if (e.PropertyName == nameof(DocumentViewModel.AllChangesSaved))
         {
-            TabCustomizationSettings.IsSaved = Document.AllChangesSaved;
+            TabCustomizationSettings.SavedState = GetSaveState(Document);
+        }
+        else if (e.PropertyName == nameof(DocumentViewModel.AllChangesAutosaved))
+        {
+            TabCustomizationSettings.SavedState = GetSaveState(Document);
         }
     }
 
@@ -152,6 +160,21 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         return _closeRequested;
     }
 
+    private static SavedState GetSaveState(DocumentViewModel document)
+    {
+        if (document.AllChangesSaved)
+        {
+            return SavedState.Saved;
+        }
+
+        if (document.AllChangesAutosaved)
+        {
+            return SavedState.Autosaved;
+        }
+
+        return SavedState.Unsaved;
+    }
+
     void IDockableSelectionEvents.OnSelected()
     {
         Owner.ActiveWindow = this;

+ 4 - 4
src/PixiEditor/ViewModels/UserPreferences/Settings/FileSettings.cs

@@ -62,11 +62,11 @@ internal class FileSettings : SettingsGroup
         set => RaiseAndUpdatePreference(ref autosaveEnabled, value);
     }
 
-    private bool saveSessionEnabled = GetPreference(PreferencesConstants.SaveSessionStateEnabled, PreferencesConstants.SaveSessionStateDefault);
-    public bool SaveSessionEnabled
+    private bool saveSessionStateEnabled = GetPreference(PreferencesConstants.SaveSessionStateEnabled, PreferencesConstants.SaveSessionStateDefault);
+    public bool SaveSessionStateEnabled
     {
-        get => saveSessionEnabled;
-        set => RaiseAndUpdatePreference(ref saveSessionEnabled, value);
+        get => saveSessionStateEnabled;
+        set => RaiseAndUpdatePreference(ref saveSessionStateEnabled, value);
     }
 
     private double autosavePeriodMinutes = GetPreference(PreferencesConstants.AutosavePeriodMinutes, PreferencesConstants.AutosavePeriodDefault);

+ 8 - 0
src/PixiEditor/ViewModels/ViewModelMain.cs

@@ -15,6 +15,7 @@ using PixiEditor.Models.Config;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.DocumentModels;
+using PixiEditor.Models.DocumentModels.Autosave;
 using PixiEditor.Models.Files;
 using PixiEditor.Models.Handlers;
 using PixiEditor.OperatingSystem;
@@ -244,6 +245,12 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
             return;
 
         document.AutosaveViewModel.AutosaveOnClose();
+
+        List<SessionFile> sessionFiles = IPreferences.Current.GetLocalPreference<SessionFile[]>(PreferencesConstants.NextSessionFiles)?.ToList() ?? new();
+        sessionFiles.RemoveAll(x => x.OriginalFilePath == document.FullFilePath || x.AutosaveFilePath == document.AutosaveViewModel.LastAutosavedPath);
+        sessionFiles.Add(new SessionFile(document.FullFilePath, document.AutosaveViewModel.LastAutosavedPath));
+
+        IPreferences.Current.UpdateLocalPreference(PreferencesConstants.NextSessionFiles, sessionFiles.ToArray());
     }
 
     /// <summary>
@@ -294,6 +301,7 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
 
             WindowSubViewModel.CloseViewportsForDocument(document);
             document.Dispose();
+            document.AutosaveViewModel.OnDocumentClosed();
 
             return true;
         }

+ 0 - 2
src/PixiEditor/Views/MainWindow.axaml.cs

@@ -102,8 +102,6 @@ internal partial class MainWindow : Window
 
         fileVM.OpenFromReport(report, out showMissingFilesDialog);
 
-        showMissingFilesDialog = documents.Count != i;
-
         return window;
 
         MainWindow GetMainWindow(Guid? analyticsSession)

+ 81 - 48
src/PixiEditor/Views/Windows/Settings/SettingsWindow.axaml

@@ -16,6 +16,7 @@
     xmlns:preferences="clr-namespace:PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;assembly=PixiEditor.Extensions.CommonApi"
     xmlns:dialogs="clr-namespace:PixiEditor.Views.Dialogs"
     xmlns:settings="clr-namespace:PixiEditor.Views.Windows.Settings"
+    xmlns:localization="clr-namespace:PixiEditor.Extensions.Common.Localization;assembly=PixiEditor.Extensions"
     mc:Ignorable="d"
     x:Class="PixiEditor.Views.Windows.Settings.SettingsWindow"
     Name="window"
@@ -27,7 +28,7 @@
     ui:Translator.Key="SETTINGS">
 
     <Window.Resources>
-        <vm:SettingsWindowViewModel x:Key="SettingsWindowViewModel"/>
+        <vm:SettingsWindowViewModel x:Key="SettingsWindowViewModel" />
     </Window.Resources>
 
     <DockPanel>
@@ -39,7 +40,8 @@
                      SelectedIndex="{Binding CurrentPage}">
                 <ListBox.ItemTemplate>
                     <DataTemplate>
-                        <TextBlock Classes="h5" Foreground="{DynamicResource ThemeForegroundLowBrush}" Text="{Binding Path=Name.Value}" VerticalAlignment="Center">
+                        <TextBlock Classes="h5" Foreground="{DynamicResource ThemeForegroundLowBrush}"
+                                   Text="{Binding Path=Name.Value}" VerticalAlignment="Center">
                             <TextBlock.Styles>
                                 <Style Selector="ListBoxItem:selected TextBlock">
                                     <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
@@ -53,11 +55,12 @@
                 <Grid>
                     <Grid.Styles>
                         <Style Selector=":is(Control).leftOffset">
-                            <Setter Property="Margin" Value="20, 0, 0, 0"/>
+                            <Setter Property="Margin" Value="20, 0, 0, 0" />
                         </Style>
                     </Grid.Styles>
                     <!--Background="{StaticResource AccentColor}"-->
-                    <controls:FixedSizeStackPanel Orientation="Vertical" ChildSize="32" VerticalChildrenAlignment="Center" Margin="12">
+                    <controls:FixedSizeStackPanel Orientation="Vertical" ChildSize="32"
+                                                  VerticalChildrenAlignment="Center" Margin="12">
                         <controls:FixedSizeStackPanel.IsVisible>
                             <Binding Path="CurrentPage" Converter="{converters:IsEqualConverter}">
                                 <Binding.ConverterParameter>
@@ -66,7 +69,7 @@
                             </Binding>
                         </controls:FixedSizeStackPanel.IsVisible>
 
-                        <TextBlock ui:Translator.Key="LANGUAGE" Classes="h5"/>
+                        <TextBlock ui:Translator.Key="LANGUAGE" Classes="h5" />
                         <ComboBox Classes="leftOffset" Width="200" HorizontalAlignment="Left"
                                   ItemsSource="{Binding SettingsSubViewModel.General.AvailableLanguages}"
                                   SelectedItem="{Binding SettingsSubViewModel.General.SelectedLanguage, Mode=TwoWay}">
@@ -78,52 +81,78 @@
                                             Margin="3, 0"
                                             VerticalAlignment="Center"
                                             Source="{Binding IconFullPath, Converter={converters:ImagePathToBitmapConverter}}" />
-                                        <TextBlock VerticalAlignment="Center" Text="{Binding Name}"/>
+                                        <TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
                                     </StackPanel>
                                 </DataTemplate>
                             </ComboBox.ItemTemplate>
                         </ComboBox>
 
-                        <TextBlock ui:Translator.Key="MISC" Classes="h5"/>
+                        <TextBlock ui:Translator.Key="MISC" Classes="h5" />
 
                         <CheckBox Classes="leftOffset" ui:Translator.Key="SHOW_STARTUP_WINDOW"
-                                  IsChecked="{Binding SettingsSubViewModel.File.ShowStartupWindow}"/>
+                                  IsChecked="{Binding SettingsSubViewModel.File.ShowStartupWindow}" />
 
                         <CheckBox Classes="leftOffset" ui:Translator.Key="DISABLE_NEWS_PANEL"
-                                  IsChecked="{Binding SettingsSubViewModel.File.DisableNewsPanel}"/>
+                                  IsChecked="{Binding SettingsSubViewModel.File.DisableNewsPanel}" />
 
                         <CheckBox Classes="leftOffset" ui:Translator.Key="SHOW_IMAGE_PREVIEW_TASKBAR"
-                                  IsChecked="{Binding SettingsSubViewModel.General.ImagePreviewInTaskbar}"/>
+                                  IsChecked="{Binding SettingsSubViewModel.General.ImagePreviewInTaskbar}" />
 
                         <StackPanel Classes="leftOffset" Orientation="Horizontal">
                             <Label
                                 ui:Translator.Key="RECENT_FILE_LENGTH"
-                                ui:Translator.TooltipKey="RECENT_FILE_LENGTH_TOOLTIP"/>
+                                ui:Translator.TooltipKey="RECENT_FILE_LENGTH_TOOLTIP" />
                             <input:NumberInput Min="0" FontSize="12" HorizontalAlignment="Left"
-                                           Value="{Binding SettingsSubViewModel.File.MaxOpenedRecently, Mode=TwoWay}" Width="40"/>
+                                               Value="{Binding SettingsSubViewModel.File.MaxOpenedRecently, Mode=TwoWay}"
+                                               Width="40" />
                         </StackPanel>
 
+                        <TextBlock Classes="h5" ui:Translator.Key="AUTOSAVE_SETTINGS_HEADER" />
+
+                        <CheckBox Classes="leftOffset"
+                                  VerticalAlignment="Center" ui:Translator.Key="AUTOSAVE_SETTINGS_SAVE_STATE"
+                                  IsChecked="{Binding SettingsSubViewModel.File.SaveSessionStateEnabled, Mode=TwoWay}" />
+
+                        <CheckBox Classes="leftOffset" ui:Translator.Key="AUTOSAVE_ENABLED"
+                                  IsChecked="{Binding SettingsSubViewModel.File.AutosaveEnabled, Mode=TwoWay}" />
+
+                        <StackPanel Classes="leftOffset" Orientation="Horizontal">
+                            <Label ui:Translator.Key="AUTOSAVE_SETTINGS_PERIOD" />
+                            <input:NumberInput Min="0.1" FontSize="12" HorizontalAlignment="Left"
+                                               IsEnabled="{Binding SettingsSubViewModel.File.AutosaveEnabled}"
+                                               Value="{Binding SettingsSubViewModel.File.AutosavePeriodMinutes, Mode=TwoWay}"
+                                               Width="55" />
+                            <Label ui:Translator.Key="MINUTE_UNIVERSAL" />
+                        </StackPanel>
+
+                        <CheckBox Classes="leftOffset"
+                                  VerticalAlignment="Center" ui:Translator.Key="AUTOSAVE_SETTINGS_SAVE_USER_FILE"
+                                  IsEnabled="{Binding SettingsSubViewModel.File.AutosaveEnabled}"
+                                  IsChecked="{Binding SettingsSubViewModel.File.AutosaveToDocumentPath}" />
+
                         <TextBlock
                             Classes="h5"
-                            d:Content="Default new file size"
-                            ui:Translator.Key="DEFAULT_NEW_SIZE"/>
+                            ui:Translator.Key="DEFAULT_NEW_SIZE" />
 
-                        <StackPanel Orientation="Horizontal"  Classes="leftOffset">
-                            <Label d:Content="Width" ui:Translator.Key="WIDTH"/>
+                        <StackPanel Orientation="Horizontal" Classes="leftOffset">
+                            <Label d:Content="Width" ui:Translator.Key="WIDTH" />
                             <input:SizeInput
-                                         Size="{Binding SettingsSubViewModel.File.DefaultNewFileWidth, Mode=TwoWay}" MaxSize="9999" HorizontalAlignment="Left"/>
+                                Size="{Binding SettingsSubViewModel.File.DefaultNewFileWidth, Mode=TwoWay}"
+                                MaxSize="9999" HorizontalAlignment="Left" />
                         </StackPanel>
 
                         <StackPanel Orientation="Horizontal" Classes="leftOffset">
-                            <Label d:Content="Height" ui:Translator.Key="HEIGHT"/>
+                            <Label d:Content="Height" ui:Translator.Key="HEIGHT" />
                             <input:SizeInput
-                                         Size="{Binding SettingsSubViewModel.File.DefaultNewFileHeight, Mode=TwoWay}" MaxSize="9999" HorizontalAlignment="Left"/>
+                                Size="{Binding SettingsSubViewModel.File.DefaultNewFileHeight, Mode=TwoWay}"
+                                MaxSize="9999" HorizontalAlignment="Left" />
                         </StackPanel>
 
                         <TextBlock d:Content="Tools" ui:Translator.Key="TOOLS" Classes="h5" />
 
                         <StackPanel Orientation="Horizontal" Classes="leftOffset">
-                            <Label Target="rightClickModeComboBox" ui:Translator.Key="RIGHT_CLICK_MODE" VerticalAlignment="Center"/>
+                            <Label Target="rightClickModeComboBox" ui:Translator.Key="RIGHT_CLICK_MODE"
+                                   VerticalAlignment="Center" />
                             <ComboBox SelectedItem="{Binding RightClickMode, Source={vm:MainVM ToolsSVM}, Mode=TwoWay}"
                                       Name="rightClickModeComboBox"
                                       ItemsSource="{markupExtensions:Enum preferences:RightClickMode}"
@@ -131,7 +160,8 @@
                                       VerticalAlignment="Center">
                                 <ComboBox.ItemTemplate>
                                     <DataTemplate>
-                                        <TextBlock ui:Translator.Key="{Binding Converter={converters:EnumToLocalizedStringConverter}}"/>
+                                        <TextBlock
+                                            ui:Translator.Key="{Binding Converter={converters:EnumToLocalizedStringConverter}}" />
                                     </DataTemplate>
                                 </ComboBox.ItemTemplate>
                             </ComboBox>
@@ -140,38 +170,40 @@
 
                         <CheckBox Classes="leftOffset"
                                   IsChecked="{Binding SettingsSubViewModel.Tools.EnableSharedToolbar}"
-                                  ui:Translator.Key="ENABLE_SHARED_TOOLBAR"/>
+                                  ui:Translator.Key="ENABLE_SHARED_TOOLBAR" />
 
-                        <TextBlock ui:Translator.Key="AUTOMATIC_UPDATES" Classes="h5"/>
+                        <TextBlock ui:Translator.Key="AUTOMATIC_UPDATES" Classes="h5" />
 
                         <CheckBox
                             VerticalAlignment="Center"
                             IsEnabled="{Binding Path=ShowUpdateTab}"
                             IsChecked="{Binding SettingsSubViewModel.Update.CheckUpdatesOnStartup}"
                             ui:Translator.Key="CHECK_FOR_UPDATES"
-                            Classes="leftOffset"/>
+                            Classes="leftOffset" />
 
                         <StackPanel Orientation="Horizontal" Classes="leftOffset">
-                            <Label Target="updateStreamComboBox" ui:Translator.Key="UPDATE_STREAM" VerticalAlignment="Center"/>
+                            <Label Target="updateStreamComboBox" ui:Translator.Key="UPDATE_STREAM"
+                                   VerticalAlignment="Center" />
                             <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Left">
                                 <ComboBox Width="110"
                                           Name="updateStreamComboBox"
                                           VerticalAlignment="Center"
                                           IsEnabled="{Binding Path=ShowUpdateTab}"
                                           ItemsSource="{Binding SettingsSubViewModel.Update.UpdateChannels}"
-                                          SelectedValue="{Binding SettingsSubViewModel.Update.UpdateChannelName}"/>
+                                          SelectedValue="{Binding SettingsSubViewModel.Update.UpdateChannelName}" />
                                 <Image Cursor="Help" Source="/Images/Commands/PixiEditor/Links/OpenDocumentation.png"
                                        VerticalAlignment="Center"
                                        ToolTip.ShowDelay="0"
                                        IsVisible="{Binding !ShowUpdateTab}"
-                                       ui:Translator.TooltipKey="UPDATE_CHANNEL_HELP_TOOLTIP"/>
+                                       ui:Translator.TooltipKey="UPDATE_CHANNEL_HELP_TOOLTIP" />
                                 <!-- ToolTipService.InitialShowDelay="0"-->
                             </StackPanel>
                         </StackPanel>
 
-                        <TextBlock ui:Translator.Key="DEBUG" Classes="h5"/>
+                        <TextBlock ui:Translator.Key="DEBUG" Classes="h5" />
                         <CheckBox Classes="leftOffset"
-                            IsChecked="{Binding SettingsSubViewModel.General.IsDebugModeEnabled}" ui:Translator.Key="ENABLE_DEBUG_MODE" d:Content="Enable Debug Mode"/>
+                                  IsChecked="{Binding SettingsSubViewModel.General.IsDebugModeEnabled}"
+                                  ui:Translator.Key="ENABLE_DEBUG_MODE" d:Content="Enable Debug Mode" />
                         <!--<Label Classes="{StaticResource SettingsText}" VerticalAlignment="Center">
                             <ui1:Hyperlink Command="{cmds:Command PixiEditor.Debug.OpenCrashReportsDirectory}" Style="{StaticResource SettingsLink}">
                                 <Run ui:Translator.Key="OPEN_CRASH_REPORTS_DIR" d:Text="Open crash reports directory"/>
@@ -189,20 +221,21 @@
                             </Binding>
                         </StackPanel.IsVisible>
 
-                        <controls:FixedSizeStackPanel ChildSize="32" Orientation="Vertical" VerticalChildrenAlignment="Center">
-                            <TextBlock ui:Translator.Key="DISCORD_RICH_PRESENCE" Classes="h5"/>
+                        <controls:FixedSizeStackPanel ChildSize="32" Orientation="Vertical"
+                                                      VerticalChildrenAlignment="Center">
+                            <TextBlock ui:Translator.Key="DISCORD_RICH_PRESENCE" Classes="h5" />
 
                             <CheckBox IsChecked="{Binding SettingsSubViewModel.Discord.EnableRichPresence}"
-                                      ui:Translator.Key="ENABLED"/>
+                                      ui:Translator.Key="ENABLED" />
                             <CheckBox IsEnabled="{Binding SettingsSubViewModel.Discord.EnableRichPresence}"
                                       IsChecked="{Binding SettingsSubViewModel.Discord.ShowDocumentName}"
-                                      ui:Translator.Key="SHOW_IMAGE_NAME"/>
+                                      ui:Translator.Key="SHOW_IMAGE_NAME" />
                             <CheckBox IsEnabled="{Binding SettingsSubViewModel.Discord.EnableRichPresence}"
                                       IsChecked="{Binding SettingsSubViewModel.Discord.ShowDocumentSize}"
-                                      ui:Translator.Key="SHOW_IMAGE_SIZE"/>
+                                      ui:Translator.Key="SHOW_IMAGE_SIZE" />
                             <CheckBox IsEnabled="{Binding SettingsSubViewModel.Discord.EnableRichPresence}"
                                       IsChecked="{Binding SettingsSubViewModel.Discord.ShowLayerCount}"
-                                      ui:Translator.Key="SHOW_LAYER_COUNT" d:Content="Show layer count"/>
+                                      ui:Translator.Key="SHOW_LAYER_COUNT" d:Content="Show layer count" />
                         </controls:FixedSizeStackPanel>
 
                         <settings:DiscordRichPresencePreview
@@ -210,10 +243,11 @@
                             Width="280"
                             State="{Binding SettingsSubViewModel.Discord.StatePreview}"
                             Detail="{Binding SettingsSubViewModel.Discord.DetailPreview}"
-                            IsPlaying="{Binding SettingsSubViewModel.Discord.EnableRichPresence}"/>
+                            IsPlaying="{Binding SettingsSubViewModel.Discord.EnableRichPresence}" />
                     </StackPanel>
 
-                    <Grid Margin="12" Height="{Binding ElementName=window, Path=Height, Converter={converters:SubtractConverter}, ConverterParameter=50}">
+                    <Grid Margin="12"
+                          Height="{Binding ElementName=window, Path=Height, Converter={converters:SubtractConverter}, ConverterParameter=50}">
                         <Grid.IsVisible>
                             <Binding Path="CurrentPage" Converter="{converters:IsEqualConverter}">
                                 <Binding.ConverterParameter>
@@ -222,9 +256,9 @@
                             </Binding>
                         </Grid.IsVisible>
                         <Grid.RowDefinitions>
-                            <RowDefinition Height="Auto"/>
-                            <RowDefinition Height="Auto"/>
-                            <RowDefinition/>
+                            <RowDefinition Height="Auto" />
+                            <RowDefinition Height="Auto" />
+                            <RowDefinition />
                         </Grid.RowDefinitions>
                         <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
                             <!--<StackPanel.Resources>
@@ -237,28 +271,27 @@
                                 </Style>
                             </StackPanel.Resources>-->
                             <Button Command="{cmds:Command PixiEditor.Shortcuts.Export}"
-                                    d:Content="Export" ui:Translator.Key="EXPORT"/>
+                                    d:Content="Export" ui:Translator.Key="EXPORT" />
                             <Button Command="{cmds:Command PixiEditor.Shortcuts.Import}"
-                                    d:Content="Import" ui:Translator.Key="IMPORT"/>
+                                    d:Content="Import" ui:Translator.Key="IMPORT" />
                             <Button Command="{cmds:Command PixiEditor.Shortcuts.OpenTemplatePopup}"
-                                    d:Content="Shortcut Templates" ui:Translator.Key="SHORTCUT_TEMPLATES"/>
+                                    d:Content="Shortcut Templates" ui:Translator.Key="SHORTCUT_TEMPLATES" />
                             <Button Command="{cmds:Command PixiEditor.Shortcuts.Reset}"
-                                    d:Content="Reset all" ui:Translator.Key="RESET_ALL"/>
+                                    d:Content="Reset all" ui:Translator.Key="RESET_ALL" />
                         </StackPanel>
                         <TextBox Grid.Row="1"
                                  Text="{Binding SearchTerm, Mode=TwoWay}">
                             <!--Styles="{StaticResource DarkTextBoxStyle}"-->
                             <i:Interaction.Behaviors>
-                                <behaviours:GlobalShortcutFocusBehavior/>
+                                <behaviours:GlobalShortcutFocusBehavior />
                             </i:Interaction.Behaviors>
                         </TextBox>
 
-                        <settings:ShortcutsBinder Grid.Row="2"/>
+                        <settings:ShortcutsBinder Grid.Row="2" />
                     </Grid>
                 </Grid>
             </Border>
         </DockPanel>
     </DockPanel>
 
-</dialogs:PixiEditorPopup>
-
+</dialogs:PixiEditorPopup>