Browse Source

Live updating

Krzysztof Krysiński 1 month ago
parent
commit
656da1de65

+ 3 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/NestedDocumentNode.cs

@@ -18,6 +18,7 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 public class NestedDocumentNode : LayerNode, IInputDependentOutputs, ITransformableObject, IRasterizable,
     IVariableSampling
 {
+    public const string DocumentPropertyName = "Document";
     private DocumentReference? lastDocument;
     public InputProperty<DocumentReference> NestedDocument { get; }
 
@@ -44,7 +45,7 @@ public class NestedDocumentNode : LayerNode, IInputDependentOutputs, ITransforma
 
     public NestedDocumentNode()
     {
-        NestedDocument = CreateInput<DocumentReference>("Document", "DOCUMENT", null)
+        NestedDocument = CreateInput<DocumentReference>(DocumentPropertyName, "DOCUMENT", null)
             .NonOverridenChanged(DocumentChanged);
         NestedDocument.ConnectionChanged += NestedDocumentOnConnectionChanged;
         BilinearSampling = CreateInput<bool>("BilinearSampling", "BILINEAR_SAMPLING", false);
@@ -69,6 +70,7 @@ public class NestedDocumentNode : LayerNode, IInputDependentOutputs, ITransforma
 
     private void DocumentChanged(DocumentReference document)
     {
+        lastDocument = NestedDocument.Value;
         if (document?.DocumentInstance == null)
         {
             ClearOutputProperties();
@@ -197,7 +199,6 @@ public class NestedDocumentNode : LayerNode, IInputDependentOutputs, ITransforma
 
         if (Instance != lastDocument?.DocumentInstance)
         {
-            lastDocument = NestedDocument.Value;
             DocumentChanged(NestedDocument.Value);
         }
 

+ 8 - 0
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/UpdateProperty_Change.cs

@@ -229,4 +229,12 @@ internal class UpdatePropertyValue_Change : InterruptableUpdateableChange
         return other is UpdatePropertyValue_Change change && change._nodeId == _nodeId &&
                change._propertyName == _propertyName && _value == change._value;
     }
+
+    public override void Dispose()
+    {
+        if(previousValue is IDisposable disposable)
+        {
+            disposable.Dispose();
+        }
+    }
 }

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

@@ -1,12 +1,14 @@
 using System.Collections.ObjectModel;
 using System.Threading.Tasks;
 using Avalonia.Input;
+using Avalonia.Threading;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Commands.Attributes.Evaluators;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Handlers;
+using PixiEditor.Models.IO;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.ViewModels.SubViewModels;
 using PixiEditor.ViewModels.Tools.Tools;
@@ -62,6 +64,10 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
     public bool HasActiveDocument => ActiveDocument != null;
 
     public event Action<DocumentViewModel> DocumentAdded;
+
+    private Dictionary<string, FileSystemWatcher> documentPathListeners = new Dictionary<string, FileSystemWatcher>();
+    private HashSet<Guid> documentIdListeners = new HashSet<Guid>();
+
     public DocumentManagerViewModel(ViewModelMain owner) : base(owner)
     {
         owner.WindowSubViewModel.ActiveViewportChanged += (_, args) =>
@@ -302,6 +308,72 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
     public void Add(DocumentViewModel doc)
     {
         Documents.Add(doc);
+        ListenForReferenceChanges(doc.DocumentReferences);
         DocumentAdded?.Invoke(doc);
     }
+
+    private void ListenForReferenceChanges(List<(string originalPath, Guid refId)> docDocumentReferences)
+    {
+        foreach (var (originalPath, refId) in docDocumentReferences)
+        {
+            if (!string.IsNullOrEmpty(originalPath))
+            {
+                if (!documentPathListeners.ContainsKey(originalPath))
+                {
+                    try
+                    {
+                        var dirPath = System.IO.Path.GetDirectoryName(originalPath);
+                        FileSystemWatcher watcher = new FileSystemWatcher(dirPath);
+                        watcher.Filter = System.IO.Path.GetFileName(originalPath);
+
+                        watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
+                        watcher.IncludeSubdirectories = false;
+                        documentPathListeners[originalPath] = watcher;
+
+                        watcher.Changed += (s, e) => OnDocumentReferenceDocumentChanged(refId, e.FullPath);
+                        watcher.Renamed += (s, e) =>
+                        {
+                            OnDocumentReferenceDocumentChanged(refId, e.FullPath);
+                            watcher.EnableRaisingEvents = false;
+                            documentPathListeners.Remove(originalPath);
+
+                            documentPathListeners[e.FullPath] = watcher;
+                            watcher.Filter = System.IO.Path.GetFileName(e.FullPath);
+                            watcher.EnableRaisingEvents = true;
+                        };
+
+                        watcher.EnableRaisingEvents = true;
+                    }
+                    catch (Exception)
+                    {
+                        // Ignore errors with file watchers
+                    }
+                }
+            }
+            else
+            {
+                /*if (!documentIdListeners.Contains(refId))
+                {
+                    ResourceLocator.ListenForDocumentChanges(refId, OnDocumentReferenceDocumentChanged);
+                    documentIdListeners.Add(refId);
+                }*/
+            }
+        }
+    }
+
+    private void OnDocumentReferenceDocumentChanged(Guid referenceId, string fullPath)
+    {
+        Dispatcher.UIThread.Post(() =>
+        {
+            var loaded = Documents.FirstOrDefault(x => x.FullFilePath == fullPath) ??
+                Importer.ImportDocument(fullPath);
+            foreach (var doc in Documents)
+            {
+                if (doc.FullFilePath == fullPath)
+                    continue;
+
+                doc.UpdateDocumentReferences(referenceId, loaded);
+            }
+        });
+    }
 }

+ 31 - 0
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -33,11 +33,13 @@ using PixiEditor.Models.Serialization.Factories;
 using PixiEditor.Models.Structures;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
 using PixiEditor.Models.IO;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Skia;
 using PixiEditor.UI.Common.Localization;
+using PixiEditor.ViewModels.Document.Nodes;
 using PixiEditor.ViewModels.Document.Nodes.Workspace;
 using PixiEditor.ViewModels.Document.TransformOverlays;
 using PixiEditor.Views.Overlays.SymmetryOverlay;
@@ -214,6 +216,8 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     public bool UsesSrgbBlending { get; private set; }
     public AutosaveDocumentViewModel AutosaveViewModel { get; set; }
 
+    public List<(string originalPath, Guid refId)> DocumentReferences { get; } = new();
+
     private bool isDisposed = false;
 
     private DocumentViewModel()
@@ -485,6 +489,14 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
                 {
                     object value =
                         SerializationUtil.Deserialize(propertyValue.Value, config, allFactories, serializerData);
+                    if (value is DocumentReference docRef)
+                    {
+                        if (viewModel.DocumentReferences.All(x => x.refId != docRef.ReferenceId))
+                        {
+                            viewModel.DocumentReferences.Add((docRef.OriginalFilePath, docRef.ReferenceId));
+                        }
+                    }
+
                     acc.AddActions(new UpdatePropertyValue_Action(guid, propertyValue.Key, value),
                         new EndUpdatePropertyValue_Action());
                 }
@@ -1418,4 +1430,23 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     {
         Operations.ResizeImage(new VecI(width, height), ResamplingMethod.NearestNeighbor);
     }
+
+    public void UpdateDocumentReferences(Guid referenceId, DocumentViewModel newDoc)
+    {
+        var nestedNodes = NodeGraph.AllNodes.Where(x => x is NestedDocumentNodeViewModel)
+            .Cast<NestedDocumentNodeViewModel>();
+        using var changeBlock = Operations.StartChangeBlock();
+        foreach (var node in nestedNodes)
+        {
+            if(node.InputPropertyMap[NestedDocumentNode.DocumentPropertyName].Value is not DocumentReference docRef ||
+               docRef.ReferenceId != referenceId)
+                continue;
+
+            Internals.ActionAccumulator.AddActions(new UpdatePropertyValue_Action(node.Id,
+                NestedDocumentNode.DocumentPropertyName,
+                new DocumentReference(newDoc.FullFilePath, referenceId,
+                    newDoc.AccessInternalReadOnlyDocument().Clone())),
+                new EndUpdatePropertyValue_Action());
+        }
+    }
 }