فهرست منبع

Lazy document loading wip

Krzysztof Krysiński 6 ماه پیش
والد
کامیت
f79047fc85

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

@@ -918,5 +918,6 @@
   "AUTOSAVE_SETTINGS_PERIOD": "Autosave period",
   "AUTOSAVE_SETTINGS_PERIOD": "Autosave period",
   "AUTOSAVE_ENABLED": "Autosave enabled",
   "AUTOSAVE_ENABLED": "Autosave enabled",
   "MINUTE_UNIVERSAL": "min",
   "MINUTE_UNIVERSAL": "min",
-  "AUTOSAVE_SETTINGS_SAVE_USER_FILE": "Autosave to selected file"
+  "AUTOSAVE_SETTINGS_SAVE_USER_FILE": "Autosave to selected file",
+  "LOAD_LAZY_FILE_MESSAGE": "To improve startup time, PixiEditor didn't load this file. Click the button below to load it.",
 }
 }

+ 4 - 0
src/PixiEditor/PixiEditor.csproj

@@ -147,6 +147,10 @@
       <DependentUpon>ColorPropertyView.axaml</DependentUpon>
       <DependentUpon>ColorPropertyView.axaml</DependentUpon>
       <SubType>Code</SubType>
       <SubType>Code</SubType>
     </Compile>
     </Compile>
+    <Compile Update="Views\Dock\LazyDocumentTemplate.axaml.cs">
+      <DependentUpon>LazyDocumentTemplate.axaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>

+ 1 - 0
src/PixiEditor/ViewLocator.cs

@@ -15,6 +15,7 @@ public class ViewLocator : IDataTemplate
     public static Dictionary<Type, Type> ViewBindingsMap = new Dictionary<Type, Type>()
     public static Dictionary<Type, Type> ViewBindingsMap = new Dictionary<Type, Type>()
     {
     {
         [typeof(ViewportWindowViewModel)] = typeof(DocumentTemplate),
         [typeof(ViewportWindowViewModel)] = typeof(DocumentTemplate),
+        [typeof(LazyViewportWindowViewModel)] = typeof(LazyDocumentTemplate),
         [typeof(LayersDockViewModel)] = typeof(LayersManager),
         [typeof(LayersDockViewModel)] = typeof(LayersManager),
     };
     };
 
 

+ 6 - 6
src/PixiEditor/ViewModels/Dock/LayoutManager.cs

@@ -139,11 +139,11 @@ internal class LayoutManager
         registeredDockables.Remove(dockable);
         registeredDockables.Remove(dockable);
     }
     }
 
 
-    public void AddViewport(ViewportWindowViewModel viewportWindowViewModel)
+    public void AddViewport(IDockableContent viewport)
     {
     {
-        RegisterDockable(viewportWindowViewModel);
+        RegisterDockable(viewport);
         DockableArea? documentsArea = TryFindArea("DocumentArea");
         DockableArea? documentsArea = TryFindArea("DocumentArea");
-        IDockable dockable = DockContext.CreateDockable(viewportWindowViewModel);
+        IDockable dockable = DockContext.CreateDockable(viewport);
         if (documentsArea != null)
         if (documentsArea != null)
         {
         {
             documentsArea.AddDockable(dockable);
             documentsArea.AddDockable(dockable);
@@ -169,17 +169,17 @@ internal class LayoutManager
         return result;
         return result;
     }
     }
 
 
-    public void RemoveViewport(ViewportWindowViewModel viewportWindowViewModel)
+    public void RemoveViewport(IDockableContent content)
     {
     {
         foreach (var element in ActiveLayout.Root)
         foreach (var element in ActiveLayout.Root)
         {
         {
             if (element is IDockableHost dockableHost)
             if (element is IDockableHost dockableHost)
             {
             {
-                var dockable = dockableHost.Dockables.FirstOrDefault(x => x.Id == viewportWindowViewModel.Id);
+                var dockable = dockableHost.Dockables.FirstOrDefault(x => x.Id == content.Id);
                 if (dockable != null)
                 if (dockable != null)
                 {
                 {
                     dockableHost?.RemoveDockable(dockable);
                     dockableHost?.RemoveDockable(dockable);
-                    UnregisterDockable(viewportWindowViewModel);
+                    UnregisterDockable(content);
                     return;
                     return;
                 }
                 }
             }
             }

+ 10 - 0
src/PixiEditor/ViewModels/Document/DocumentManagerViewModel.cs

@@ -2,6 +2,7 @@
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Avalonia.Input;
 using Avalonia.Input;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.Helpers;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Commands.Attributes.Evaluators;
 using PixiEditor.Models.Commands.Attributes.Evaluators;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Dialogs;
@@ -18,6 +19,7 @@ namespace PixiEditor.ViewModels.Document;
 internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocumentManagerHandler
 internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocumentManagerHandler
 {
 {
     public ObservableCollection<DocumentViewModel> Documents { get; } = new ObservableCollection<DocumentViewModel>();
     public ObservableCollection<DocumentViewModel> Documents { get; } = new ObservableCollection<DocumentViewModel>();
+    public ObservableCollection<LazyDocumentViewModel> LazyDocuments { get; } = new ObservableCollection<LazyDocumentViewModel>();
     public event EventHandler<DocumentChangedEventArgs>? ActiveDocumentChanged;
     public event EventHandler<DocumentChangedEventArgs>? ActiveDocumentChanged;
 
 
     private DocumentViewModel? activeDocument;
     private DocumentViewModel? activeDocument;
@@ -261,6 +263,14 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
         ActiveDocument.Operations.UseSrgbProcessing();
         ActiveDocument.Operations.UseSrgbProcessing();
     }
     }
 
 
+    [Command.Internal("PixiEditor.Document.LoadLazyDocument")]
+    public void LoadLazyDocument(LazyDocumentViewModel lazyDocument)
+    {
+        Owner.FileSubViewModel.LoadLazyDocument(lazyDocument);
+        LazyDocuments.Remove(lazyDocument);
+        Owner.WindowSubViewModel.CloseViewportForLazyDocument(lazyDocument);
+    }
+
     [Evaluator.CanExecute("PixiEditor.DocumentUsesSrgbBlending", nameof(ActiveDocument),
     [Evaluator.CanExecute("PixiEditor.DocumentUsesSrgbBlending", nameof(ActiveDocument),
         nameof(ActiveDocument.UsesSrgbBlending))]
         nameof(ActiveDocument.UsesSrgbBlending))]
     public bool DocumentUsesSrgbBlending() => ActiveDocument?.UsesSrgbBlending ?? false;
     public bool DocumentUsesSrgbBlending() => ActiveDocument?.UsesSrgbBlending ?? false;

+ 49 - 0
src/PixiEditor/ViewModels/Document/LazyDocumentViewModel.cs

@@ -0,0 +1,49 @@
+using PixiEditor.Helpers;
+
+namespace PixiEditor.ViewModels.Document;
+
+internal class LazyDocumentViewModel : PixiObservableObject
+{
+    private string path;
+    private bool associatePath;
+    private Guid tempFileGuid;
+    private string? originalPath;
+
+    public string Path
+    {
+        get => path;
+        set => SetProperty(ref path, value);
+    }
+
+    public bool AssociatePath
+    {
+        get => associatePath;
+        set => SetProperty(ref associatePath, value);
+    }
+
+    public Guid TempFileGuid
+    {
+        get => tempFileGuid;
+        set => SetProperty(ref tempFileGuid, value);
+    }
+
+    public string? OriginalPath
+    {
+        get => originalPath;
+        set => SetProperty(ref originalPath, value);
+    }
+
+    public string FileName => System.IO.Path.GetFileName(Path);
+
+    public LazyDocumentViewModel(string path, bool associatePath)
+    {
+        Path = path;
+        AssociatePath = associatePath;
+    }
+
+    public void SetTempFileGuidAndLastSavedPath(Guid tempGuid, string? originalPath)
+    {
+        TempFileGuid = tempGuid;
+        OriginalPath = originalPath;
+    }
+}

+ 62 - 17
src/PixiEditor/ViewModels/SubViewModels/FileViewModel.cs

@@ -261,6 +261,17 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
         return null;
         return null;
     }
     }
 
 
+    public LazyDocumentViewModel OpenFromPathLazy(string path, bool associatePath = true)
+    {
+        if (MakeExistingDocumentActiveIfOpened(path))
+            return null;
+
+        LazyDocumentViewModel lazyDoc = new LazyDocumentViewModel(path, associatePath);
+        AddLazyDocumentToTheSystem(lazyDoc);
+
+        return lazyDoc;
+    }
+
     private bool IsCustomFormat(string path)
     private bool IsCustomFormat(string path)
     {
     {
         string extension = Path.GetExtension(path);
         string extension = Path.GetExtension(path);
@@ -489,6 +500,13 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
         return doc;
         return doc;
     }
     }
 
 
+    public void AddLazyDocumentToTheSystem(LazyDocumentViewModel doc)
+    {
+        Owner.DocumentManagerSubViewModel.LazyDocuments.Add(doc);
+        Owner.WindowSubViewModel.CreateNewViewport(doc);
+        Owner.WindowSubViewModel.MakeDocumentViewportActive(doc);
+    }
+
     private void AddDocumentViewModelToTheSystem(DocumentViewModel doc)
     private void AddDocumentViewModelToTheSystem(DocumentViewModel doc)
     {
     {
         Owner.DocumentManagerSubViewModel.Documents.Add(doc);
         Owner.DocumentManagerSubViewModel.Documents.Add(doc);
@@ -646,12 +664,6 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
     {
     {
         var preferences = Owner.Preferences;
         var preferences = Owner.Preferences;
 
 
-        // Todo sure, no session saving, but shouldn't we still load backups in case of unexpected shutdown?
-        // it probably should be handled elsewhere
-        if (!preferences.GetPreference<bool>(PreferencesConstants.SaveSessionStateEnabled,
-                PreferencesConstants.SaveSessionStateDefault))
-            return;
-
         var history =
         var history =
             preferences.GetLocalPreference<List<AutosaveHistorySession>>(PreferencesConstants.AutosaveHistory);
             preferences.GetLocalPreference<List<AutosaveHistorySession>>(PreferencesConstants.AutosaveHistory);
 
 
@@ -671,11 +683,16 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
             return;
             return;
         }
         }
 
 
+        if (!preferences.GetPreference<bool>(PreferencesConstants.SaveSessionStateEnabled,
+                PreferencesConstants.SaveSessionStateDefault))
+            return;
+
         var nextSessionFiles = preferences.GetLocalPreference<SessionFile[]>(PreferencesConstants.NextSessionFiles, []);
         var nextSessionFiles = preferences.GetLocalPreference<SessionFile[]>(PreferencesConstants.NextSessionFiles, []);
         List<List<AutosaveHistoryEntry>> perDocumentHistories = (
         List<List<AutosaveHistoryEntry>> perDocumentHistories = (
             from entry in lastSession.AutosaveEntries
             from entry in lastSession.AutosaveEntries
             where nextSessionFiles.Any(a =>
             where nextSessionFiles.Any(a =>
-                a.AutosaveFilePath == AutosaveHelper.GetAutosavePath(entry.TempFileGuid) || entry.Type != AutosaveHistoryType.OnClose)
+                a.AutosaveFilePath == AutosaveHelper.GetAutosavePath(entry.TempFileGuid) ||
+                entry.Type != AutosaveHistoryType.OnClose)
             group entry by entry.TempFileGuid
             group entry by entry.TempFileGuid
             into entryGroup
             into entryGroup
             select entryGroup.OrderBy(a => a.DateTime).ToList()
             select entryGroup.OrderBy(a => a.DateTime).ToList()
@@ -691,14 +708,14 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
                 if (lastEntry.Type != AutosaveHistoryType.OnClose)
                 if (lastEntry.Type != AutosaveHistoryType.OnClose)
                 {
                 {
                     // unexpected shutdown happened, this file wasn't saved on close, but we supposedly have a backup
                     // unexpected shutdown happened, this file wasn't saved on close, but we supposedly have a backup
-                    LoadNewest(lastEntry);
+                    LoadNewest(lastEntry, true);
                     toLoad.RemoveAll(a =>
                     toLoad.RemoveAll(a =>
                         a.AutosaveFilePath == AutosaveHelper.GetAutosavePath(lastEntry.TempFileGuid)
                         a.AutosaveFilePath == AutosaveHelper.GetAutosavePath(lastEntry.TempFileGuid)
                         || a.OriginalFilePath == lastEntry.OriginalPath);
                         || a.OriginalFilePath == lastEntry.OriginalPath);
                 }
                 }
-                else if (lastEntry.Result == AutosaveHistoryResult.SavedBackup)
+                else if (lastEntry.Result == AutosaveHistoryResult.SavedBackup || lastEntry.OriginalPath == null)
                 {
                 {
-                    LoadFromAutosave(lastEntry);
+                    LoadFromAutosave(lastEntry, true);
                     toLoad.RemoveAll(a =>
                     toLoad.RemoveAll(a =>
                         a.AutosaveFilePath == AutosaveHelper.GetAutosavePath(lastEntry.TempFileGuid)
                         a.AutosaveFilePath == AutosaveHelper.GetAutosavePath(lastEntry.TempFileGuid)
                         || a.OriginalFilePath == lastEntry.OriginalPath);
                         || a.OriginalFilePath == lastEntry.OriginalPath);
@@ -736,7 +753,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
             foreach (var backup in lastBackups)
             foreach (var backup in lastBackups)
             {
             {
                 AutosaveHistoryEntry lastEntry = backup[^1];
                 AutosaveHistoryEntry lastEntry = backup[^1];
-                LoadNewest(lastEntry);
+                LoadNewest(lastEntry, false);
             }
             }
 
 
             OptionsDialog<LocalizedString> dialog = new OptionsDialog<LocalizedString>("UNEXPECTED_SHUTDOWN",
             OptionsDialog<LocalizedString> dialog = new OptionsDialog<LocalizedString>("UNEXPECTED_SHUTDOWN",
@@ -754,7 +771,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
         }
         }
     }
     }
 
 
-    private void LoadNewest(AutosaveHistoryEntry lastEntry)
+    private void LoadNewest(AutosaveHistoryEntry lastEntry, bool lazy)
     {
     {
         bool loadFromUserFile = false;
         bool loadFromUserFile = false;
 
 
@@ -768,15 +785,22 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
 
 
         if (loadFromUserFile)
         if (loadFromUserFile)
         {
         {
-            OpenFromPath(lastEntry.OriginalPath);
+            if (lazy)
+            {
+                OpenFromPathLazy(lastEntry.OriginalPath);
+            }
+            else
+            {
+                OpenFromPath(lastEntry.OriginalPath);
+            }
         }
         }
         else
         else
         {
         {
-            LoadFromAutosave(lastEntry);
+            LoadFromAutosave(lastEntry, lazy);
         }
         }
     }
     }
 
 
-    private void LoadFromAutosave(AutosaveHistoryEntry entry)
+    private void LoadFromAutosave(AutosaveHistoryEntry entry, bool lazy)
     {
     {
         string path = AutosaveHelper.GetAutosavePath(entry.TempFileGuid);
         string path = AutosaveHelper.GetAutosavePath(entry.TempFileGuid);
         if (path == null || !File.Exists(path))
         if (path == null || !File.Exists(path))
@@ -785,8 +809,16 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
             return;
             return;
         }
         }
 
 
-        var document = OpenFromPath(path, false);
-        document.AutosaveViewModel.SetTempFileGuidAndLastSavedPath(entry.TempFileGuid, path);
+        if (lazy)
+        {
+            var lazyDoc = OpenFromPathLazy(path, false);
+            lazyDoc.SetTempFileGuidAndLastSavedPath(entry.TempFileGuid, entry.OriginalPath);
+        }
+        else
+        {
+            var document = OpenFromPath(path, false);
+            document.AutosaveViewModel.SetTempFileGuidAndLastSavedPath(entry.TempFileGuid, path);
+        }
     }
     }
 
 
     private List<RecentlyOpenedDocument> GetRecentlyOpenedDocuments()
     private List<RecentlyOpenedDocument> GetRecentlyOpenedDocuments()
@@ -803,4 +835,17 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
 
 
         return documents;
         return documents;
     }
     }
+
+    public void LoadLazyDocument(LazyDocumentViewModel lazyDocument)
+    {
+        var document = OpenFromPath(lazyDocument.Path, lazyDocument.AssociatePath);
+
+        if (document is null)
+        {
+            NoticeDialog.Show("FAILED_TO_OPEN_FILE", "ERROR");
+            return;
+        }
+
+        document.AutosaveViewModel.SetTempFileGuidAndLastSavedPath(lazyDocument.TempFileGuid, lazyDocument.Path);
+    }
 }
 }

+ 12 - 0
src/PixiEditor/ViewModels/SubViewModels/LayoutViewModel.cs

@@ -19,7 +19,9 @@ internal class LayoutViewModel : SubViewModel<ViewModelMain>
     {
     {
         LayoutManager = layoutManager;
         LayoutManager = layoutManager;
         owner.WindowSubViewModel.ViewportAdded += WindowSubViewModel_ViewportAdded;
         owner.WindowSubViewModel.ViewportAdded += WindowSubViewModel_ViewportAdded;
+        owner.WindowSubViewModel.LazyViewportAdded += WindowSubViewModel_LazyViewportAdded;
         owner.WindowSubViewModel.ViewportClosed += WindowSubViewModel_ViewportRemoved;
         owner.WindowSubViewModel.ViewportClosed += WindowSubViewModel_ViewportRemoved;
+        owner.WindowSubViewModel.LazyViewportRemoved += WindowSubViewModel_LazyViewportRemoved;
     }
     }
 
 
     private void WindowSubViewModel_ViewportAdded(ViewportWindowViewModel obj)
     private void WindowSubViewModel_ViewportAdded(ViewportWindowViewModel obj)
@@ -31,4 +33,14 @@ internal class LayoutViewModel : SubViewModel<ViewModelMain>
     {
     {
         LayoutManager.RemoveViewport(obj);
         LayoutManager.RemoveViewport(obj);
     }
     }
+
+    private void WindowSubViewModel_LazyViewportAdded(LazyViewportWindowViewModel obj)
+    {
+        LayoutManager.AddViewport(obj);
+    }
+
+    private void WindowSubViewModel_LazyViewportRemoved(LazyViewportWindowViewModel obj)
+    {
+        LayoutManager.RemoveViewport(obj);
+    }
 }
 }

+ 40 - 0
src/PixiEditor/ViewModels/SubViewModels/LazyViewportWindowViewModel.cs

@@ -0,0 +1,40 @@
+using PixiDocks.Core.Docking;
+using PixiDocks.Core.Docking.Events;
+using PixiEditor.ViewModels.Dock;
+using PixiEditor.ViewModels.Document;
+
+namespace PixiEditor.ViewModels.SubViewModels;
+
+internal class LazyViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockableContent, IDockableCloseEvents,
+    IDockableSelectionEvents
+{
+    public string Id { get; } = Guid.NewGuid().ToString();
+    public string Title => Path.GetFileName(LazyDocument.OriginalPath ?? LazyDocument.Path);
+    public bool CanFloat { get; } = true;
+    public bool CanClose { get; } = true;
+    public TabCustomizationSettings TabCustomizationSettings { get; } = new DocumentTabCustomizationSettings();
+
+    public LazyDocumentViewModel LazyDocument { get; }
+
+    public LazyViewportWindowViewModel(WindowViewModel owner, LazyDocumentViewModel lazyDoc) : base(owner)
+    {
+        LazyDocument = lazyDoc;
+    }
+
+    void IDockableSelectionEvents.OnSelected()
+    {
+        Owner.ActiveWindow = this;
+        Owner.Owner.ShortcutController.OverwriteContext(this.GetType());
+    }
+
+    void IDockableSelectionEvents.OnDeselected()
+    {
+        Owner.Owner.ShortcutController.ClearContext(GetType());
+    }
+
+    async Task<bool> IDockableCloseEvents.OnClose()
+    {
+        Owner.OnLazyViewportWindowCloseButtonPressed(this);
+        return await Task.FromResult(true);
+    }
+}

+ 40 - 0
src/PixiEditor/ViewModels/SubViewModels/WindowViewModel.cs

@@ -26,10 +26,14 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
     private CommandController commandController;
     private CommandController commandController;
     public RelayCommand<string> ShowAvalonDockWindowCommand { get; set; }
     public RelayCommand<string> ShowAvalonDockWindowCommand { get; set; }
     public ObservableCollection<ViewportWindowViewModel> Viewports { get; } = new();
     public ObservableCollection<ViewportWindowViewModel> Viewports { get; } = new();
+    public ObservableCollection<LazyViewportWindowViewModel> LazyViewports { get; } = new();
     public event EventHandler<ViewportWindowViewModel>? ActiveViewportChanged;
     public event EventHandler<ViewportWindowViewModel>? ActiveViewportChanged;
     public event Action<ViewportWindowViewModel> ViewportAdded;
     public event Action<ViewportWindowViewModel> ViewportAdded;
     public event Action<ViewportWindowViewModel> ViewportClosed;
     public event Action<ViewportWindowViewModel> ViewportClosed;
 
 
+    public event Action<LazyViewportWindowViewModel> LazyViewportAdded;
+    public event Action<LazyViewportWindowViewModel> LazyViewportRemoved;
+
     private object? activeWindow;
     private object? activeWindow;
 
 
     public object? ActiveWindow
     public object? ActiveWindow
@@ -118,6 +122,14 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
         ViewportAdded?.Invoke(newViewport);
         ViewportAdded?.Invoke(newViewport);
     }
     }
 
 
+    public void CreateNewViewport(LazyDocumentViewModel lazyDoc)
+    {
+        LazyViewportWindowViewModel newViewport = new LazyViewportWindowViewModel(this, lazyDoc);
+        LazyViewports.Add(newViewport);
+
+        LazyViewportAdded?.Invoke(newViewport);
+    }
+
     public void MakeDocumentViewportActive(DocumentViewModel? doc)
     public void MakeDocumentViewportActive(DocumentViewModel? doc)
     {
     {
         if (doc is null)
         if (doc is null)
@@ -130,6 +142,17 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
         ActiveWindow = Viewports.FirstOrDefault(viewport => viewport.Document == doc);
         ActiveWindow = Viewports.FirstOrDefault(viewport => viewport.Document == doc);
     }
     }
 
 
+    public void MakeDocumentViewportActive(LazyDocumentViewModel? doc)
+    {
+        if (doc is null)
+        {
+            ActiveWindow = null;
+            return;
+        }
+
+        ActiveWindow = LazyViewports.FirstOrDefault(viewport => viewport.LazyDocument == doc);
+    }
+
     public string CalculateViewportIndex(ViewportWindowViewModel viewport)
     public string CalculateViewportIndex(ViewportWindowViewModel viewport)
     {
     {
         ViewportWindowViewModel[] viewports = Viewports.Where(a => a.Document == viewport.Document).ToArray();
         ViewportWindowViewModel[] viewports = Viewports.Where(a => a.Document == viewport.Document).ToArray();
@@ -159,6 +182,13 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
         return true;
         return true;
     }
     }
 
 
+    public void OnLazyViewportWindowCloseButtonPressed(LazyViewportWindowViewModel viewport)
+    {
+        LazyViewports.Remove(viewport);
+        LazyViewportRemoved?.Invoke(viewport);
+        Owner.CloseLazyDocument(viewport.LazyDocument);
+    }
+
     public void CloseViewportsForDocument(DocumentViewModel document)
     public void CloseViewportsForDocument(DocumentViewModel document)
     {
     {
         var viewports = Viewports.Where(vp => vp.Document == document).ToArray();
         var viewports = Viewports.Where(vp => vp.Document == document).ToArray();
@@ -169,6 +199,16 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
         }
         }
     }
     }
 
 
+    public void CloseViewportForLazyDocument(LazyDocumentViewModel lazyDoc)
+    {
+        var viewport = LazyViewports.FirstOrDefault(vp => vp.LazyDocument == lazyDoc);
+        if (viewport is not null)
+        {
+            LazyViewports.Remove(viewport);
+            LazyViewportRemoved?.Invoke(viewport);
+        }
+    }
+
     [Commands_Command.Basic("PixiEditor.Window.OpenSettingsWindow", "OPEN_SETTINGS", "OPEN_SETTINGS_DESCRIPTIVE",
     [Commands_Command.Basic("PixiEditor.Window.OpenSettingsWindow", "OPEN_SETTINGS", "OPEN_SETTINGS_DESCRIPTIVE",
         Key = Key.OemComma, Modifiers = KeyModifiers.Control,
         Key = Key.OemComma, Modifiers = KeyModifiers.Control,
         MenuItemPath = "EDIT/SETTINGS", MenuItemOrder = 16, Icon = PixiPerfectIcons.Settings, AnalyticsTrack = true)]
         MenuItemPath = "EDIT/SETTINGS", MenuItemOrder = 16, Icon = PixiPerfectIcons.Settings, AnalyticsTrack = true)]

+ 37 - 3
src/PixiEditor/ViewModels/ViewModelMain.cs

@@ -93,6 +93,7 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
     public DateTime LaunchDateTime { get; } = DateTime.Now;
     public DateTime LaunchDateTime { get; } = DateTime.Now;
 
 
     public event Action<DocumentViewModel> BeforeDocumentClosed;
     public event Action<DocumentViewModel> BeforeDocumentClosed;
+    public event Action<LazyDocumentViewModel> LazyDocumentClosed;
 
 
     public ViewModelMain()
     public ViewModelMain()
     {
     {
@@ -170,6 +171,7 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
 
 
         DocumentManagerSubViewModel.ActiveDocumentChanged += OnActiveDocumentChanged;
         DocumentManagerSubViewModel.ActiveDocumentChanged += OnActiveDocumentChanged;
         BeforeDocumentClosed += OnBeforeDocumentClosed;
         BeforeDocumentClosed += OnBeforeDocumentClosed;
+        LazyDocumentClosed += OnLazyDocumentClosed;
     }
     }
 
 
     public bool DocumentIsNotNull(object property)
     public bool DocumentIsNotNull(object property)
@@ -185,6 +187,7 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
     [RelayCommand]
     [RelayCommand]
     public async Task CloseWindow()
     public async Task CloseWindow()
     {
     {
+        ResetNextSessionFiles();
         UserWantsToClose = await DisposeAllDocumentsWithSaveConfirmation();
         UserWantsToClose = await DisposeAllDocumentsWithSaveConfirmation();
 
 
         if (UserWantsToClose)
         if (UserWantsToClose)
@@ -197,6 +200,11 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
         }
         }
     }
     }
 
 
+    public void ResetNextSessionFiles()
+    {
+        IPreferences.Current.UpdateLocalPreference(PreferencesConstants.NextSessionFiles, Array.Empty<SessionFile>());
+    }
+
     private void ToolsSubViewModel_SelectedToolChanged(object sender, SelectedToolEventArgs e)
     private void ToolsSubViewModel_SelectedToolChanged(object sender, SelectedToolEventArgs e)
     {
     {
         if (e.OldTool != null)
         if (e.OldTool != null)
@@ -236,6 +244,12 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
             }
             }
         }
         }
 
 
+        foreach (var lazyDoc in DocumentManagerSubViewModel.LazyDocuments)
+        {
+            CloseLazyDocument(lazyDoc);
+            WindowSubViewModel.CloseViewportForLazyDocument(lazyDoc);
+        }
+
         return true;
         return true;
     }
     }
 
 
@@ -246,13 +260,33 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
 
 
         document.AutosaveViewModel.AutosaveOnClose();
         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);
+        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));
         sessionFiles.Add(new SessionFile(document.FullFilePath, document.AutosaveViewModel.LastAutosavedPath));
 
 
         IPreferences.Current.UpdateLocalPreference(PreferencesConstants.NextSessionFiles, sessionFiles.ToArray());
         IPreferences.Current.UpdateLocalPreference(PreferencesConstants.NextSessionFiles, sessionFiles.ToArray());
     }
     }
 
 
+    private void OnLazyDocumentClosed(LazyDocumentViewModel document)
+    {
+        List<SessionFile> sessionFiles = IPreferences.Current
+            .GetLocalPreference<SessionFile[]>(PreferencesConstants.NextSessionFiles)?.ToList() ?? new();
+        sessionFiles.RemoveAll(x =>
+            x.OriginalFilePath == document.OriginalPath ||
+            x.AutosaveFilePath == document.Path);
+        sessionFiles.Add(new SessionFile(document.OriginalPath, document.Path));
+
+        IPreferences.Current.UpdateLocalPreference(PreferencesConstants.NextSessionFiles, sessionFiles.ToArray());
+    }
+
+    internal void CloseLazyDocument(LazyDocumentViewModel document)
+    {
+        LazyDocumentClosed?.Invoke(document);
+    }
+
     /// <summary>
     /// <summary>
     /// Disposes the active document after showing the unsaved changes confirmation dialog.
     /// Disposes the active document after showing the unsaved changes confirmation dialog.
     /// </summary>
     /// </summary>
@@ -296,7 +330,7 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
                 if (DocumentManagerSubViewModel.Documents.Count > 0)
                 if (DocumentManagerSubViewModel.Documents.Count > 0)
                     WindowSubViewModel.MakeDocumentViewportActive(DocumentManagerSubViewModel.Documents.Last());
                     WindowSubViewModel.MakeDocumentViewportActive(DocumentManagerSubViewModel.Documents.Last());
                 else
                 else
-                    WindowSubViewModel.MakeDocumentViewportActive(null);
+                    WindowSubViewModel.MakeDocumentViewportActive((DocumentViewModel)null);
             }
             }
 
 
             WindowSubViewModel.CloseViewportsForDocument(document);
             WindowSubViewModel.CloseViewportsForDocument(document);

+ 27 - 0
src/PixiEditor/Views/Dock/LazyDocumentTemplate.axaml

@@ -0,0 +1,27 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+             xmlns:xaml="clr-namespace:PixiEditor.Models.Commands.XAML"
+             xmlns:viewModels1="clr-namespace:PixiEditor.ViewModels"
+             xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+             xmlns:palettes1="clr-namespace:PixiEditor.Views.Palettes"
+             xmlns:viewportControls="clr-namespace:PixiEditor.Views.Main.ViewportControls"
+             xmlns:subViewModels="clr-namespace:PixiEditor.ViewModels.SubViewModels"
+             xmlns:document="clr-namespace:PixiEditor.ViewModels.Document"
+             xmlns:palettes="clr-namespace:PixiEditor.Extensions.CommonApi.Palettes;assembly=PixiEditor.Extensions.CommonApi"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:DataType="subViewModels:LazyViewportWindowViewModel"
+             x:Class="PixiEditor.Views.Dock.LazyDocumentTemplate">
+    <Design.DataContext>
+        <subViewModels:LazyViewportWindowViewModel />
+    </Design.DataContext>
+
+    <StackPanel Spacing="5" HorizontalAlignment="Center" VerticalAlignment="Center">
+        <TextBlock ui:Translator.Key="LOAD_LAZY_FILE_MESSAGE" />
+        <TextBlock Text="{Binding LazyDocument.Path}" />
+        <Button ui:Translator.Key="OPEN" Command="{xaml:Command PixiEditor.Document.LoadLazyDocument, UseProvided=True}"
+                CommandParameter="{Binding LazyDocument}"/>
+    </StackPanel>
+</UserControl>

+ 12 - 0
src/PixiEditor/Views/Dock/LazyDocumentTemplate.axaml.cs

@@ -0,0 +1,12 @@
+using Avalonia.Controls;
+
+namespace PixiEditor.Views.Dock;
+
+public partial class LazyDocumentTemplate : UserControl
+{
+    public LazyDocumentTemplate()
+    {
+        InitializeComponent();
+    }
+}
+