Browse Source

Made set geometry interruptable change

flabbet 8 months ago
parent
commit
691a9305b0

+ 2 - 2
src/PixiEditor.ChangeableDocument.Gen/Helpers.cs

@@ -38,7 +38,7 @@ internal static class Helpers
     }
 
     public static Result<string> CreateStartUpdateChangeAction
-        (MethodInfo changeConstructorInfo, MethodInfo updateMethodInfo, ClassDeclarationSyntax containingClass)
+        (MethodInfo changeConstructorInfo, MethodInfo updateMethodInfo, ClassDeclarationSyntax containingClass, bool isCancelable)
     {
         string actionName = changeConstructorInfo.ContainingClass.Name.Split('_')[0] + "_Action";
         List<TypeWithName> constructorArgs = changeConstructorInfo.Arguments;
@@ -55,7 +55,7 @@ internal static class Helpers
         StringBuilder sb = new();
 
         sb.AppendLine("namespace PixiEditor.ChangeableDocument.Actions.Generated;");
-        sb.AppendLine($"public record class {actionName} : PixiEditor.ChangeableDocument.Actions.IStartOrUpdateChangeAction");
+        sb.AppendLine($"public record class {actionName} : PixiEditor.ChangeableDocument.Actions.IStartOrUpdateChangeAction" + (isCancelable ? ", PixiEditor.ChangeableDocument.Actions.ICancelableAction" : ""));
         sb.AppendLine("{");
         sb.Append($"public {actionName}");
         AppendArgumentList(sb, constructorArgs);

+ 11 - 8
src/PixiEditor.ChangeableDocument.Gen/UpdateableChangeActionGenerator.cs

@@ -12,7 +12,7 @@ public class UpdateableChangeActionGenerator : IIncrementalGenerator
     private static NamespacedType constructorAttributeType = new NamespacedType(ConstructorAttribute, AttributesNamespace);
     private static NamespacedType updateMethodAttributeType = new NamespacedType(UpdateMethodAttribute, AttributesNamespace);
 
-    private static Result<(IMethodSymbol, IMethodSymbol, ClassDeclarationSyntax)>? TransformSyntax
+    private static Result<(IMethodSymbol, IMethodSymbol, ClassDeclarationSyntax, bool)>? TransformSyntax
         (GeneratorSyntaxContext context, CancellationToken cancelToken)
     {
         ClassDeclarationSyntax containingClass;
@@ -33,9 +33,11 @@ public class UpdateableChangeActionGenerator : IIncrementalGenerator
         var classSymbol = (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(containingClass)!;
         if (!Helpers.IsInheritedFrom(classSymbol, new("UpdateableChange", "PixiEditor.ChangeableDocument.Changes")))
         {
-            return Result<(IMethodSymbol, IMethodSymbol, ClassDeclarationSyntax)>.Error
+            return Result<(IMethodSymbol, IMethodSymbol, ClassDeclarationSyntax, bool)>.Error
                 ("The GenerateUpdateableChangeActions and UpdateChangeMethodAttribute can only be used inside UpdateableChanges", containingClass.SyntaxTree, containingClass.Span);
         }
+        
+        bool isCancelable = Helpers.IsInheritedFrom(classSymbol, new("CancelableUpdateableChange", "PixiEditor.ChangeableDocument.Changes"));
 
         // here we are sure we are inside an updateable change, time to find the update method
         MethodDeclarationSyntax? methodSyntax = null;
@@ -43,7 +45,7 @@ public class UpdateableChangeActionGenerator : IIncrementalGenerator
         const string errorMessage = $"Update method isn't marked with {UpdateMethodAttribute}";
         if (!members.Any())
         {
-            return Result<(IMethodSymbol, IMethodSymbol, ClassDeclarationSyntax)>.Error
+            return Result<(IMethodSymbol, IMethodSymbol, ClassDeclarationSyntax, bool)>.Error
                 (errorMessage, containingClass.SyntaxTree, containingClass.Span);
         }
         foreach (var member in members)
@@ -59,7 +61,7 @@ public class UpdateableChangeActionGenerator : IIncrementalGenerator
         }
         if (methodSyntax is null)
         {
-            return Result<(IMethodSymbol, IMethodSymbol, ClassDeclarationSyntax)>.Error
+            return Result<(IMethodSymbol, IMethodSymbol, ClassDeclarationSyntax, bool)>.Error
                 (errorMessage, containingClass.SyntaxTree, containingClass.Span);
         }
 
@@ -68,23 +70,24 @@ public class UpdateableChangeActionGenerator : IIncrementalGenerator
         var constructorSymbol = context.SemanticModel.GetDeclaredSymbol(constructorSyntax, cancelToken);
         if (constructorSymbol is not IMethodSymbol || methodSymbol is not IMethodSymbol)
             return null;
-        return ((IMethodSymbol)constructorSymbol, (IMethodSymbol)methodSymbol, containingClass);
+        
+        return ((IMethodSymbol)constructorSymbol, (IMethodSymbol)methodSymbol, containingClass, isCancelable);
     }
 
     private static Result<(NamedSourceCode, NamedSourceCode)> GenerateActions
-        (Result<(IMethodSymbol, IMethodSymbol, ClassDeclarationSyntax)>? prevResult, CancellationToken cancelToken)
+        (Result<(IMethodSymbol, IMethodSymbol, ClassDeclarationSyntax, bool)>? prevResult, CancellationToken cancelToken)
     {
         if (prevResult!.Value.ErrorText is not null)
         {
             return Result<(NamedSourceCode, NamedSourceCode)>.Error
                 (prevResult.Value.ErrorText, prevResult.Value.SyntaxTree!, (TextSpan)prevResult.Value.Span!);
         }
-        var (constructor, update, containingClass) = prevResult.Value.Value;
+        var (constructor, update, containingClass, isCancelable) = prevResult.Value.Value;
 
         var constructorInfo = Helpers.ExtractMethodInfo(constructor!);
         var updateInfo = Helpers.ExtractMethodInfo(update!);
 
-        var maybeStartUpdateAction = Helpers.CreateStartUpdateChangeAction(constructorInfo, updateInfo, containingClass);
+        var maybeStartUpdateAction = Helpers.CreateStartUpdateChangeAction(constructorInfo, updateInfo, containingClass, isCancelable);
         if (maybeStartUpdateAction.ErrorText is not null)
         {
             return Result<(NamedSourceCode, NamedSourceCode)>.Error

+ 5 - 0
src/PixiEditor.ChangeableDocument/Actions/ICancelableAction.cs

@@ -0,0 +1,5 @@
+namespace PixiEditor.ChangeableDocument.Actions;
+
+public interface ICancelableAction : IAction
+{
+}

+ 6 - 0
src/PixiEditor.ChangeableDocument/Changes/InterruptableUpdateableChange.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.ChangeableDocument.Changes;
+
+internal abstract class InterruptableUpdateableChange : UpdateableChange
+{
+    
+}

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Vectors/SetShapeGeometry_UpdateableChange.cs

@@ -6,7 +6,7 @@ using Drawie.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Vectors;
 
-internal class SetShapeGeometry_UpdateableChange : UpdateableChange
+internal class SetShapeGeometry_UpdateableChange : InterruptableUpdateableChange
 {
     public Guid TargetId { get; set; }
     public ShapeVectorData Data { get; set; }

+ 71 - 16
src/PixiEditor.ChangeableDocument/DocumentChangeTracker.cs

@@ -14,6 +14,7 @@ public class DocumentChangeTracker : IDisposable
     public IReadOnlyDocument Document => document;
     public bool HasSavedUndo => undoStack.Any();
     public bool HasSavedRedo => redoStack.Any();
+
     public Guid? LastChangeGuid
     {
         get
@@ -93,16 +94,16 @@ public class DocumentChangeTracker : IDisposable
         if (activePacket.Count == 1 &&
             undoStack.Count > 0 &&
             (undoStack.Peek().source == ActionSource.Automated ||
-            (IsHomologous(undoStack.Peek()) &&
-            undoStack.Peek().changes[^1].IsMergeableWith(activePacket[0].change))))
+             (IsHomologous(undoStack.Peek()) &&
+              undoStack.Peek().changes[^1].IsMergeableWith(activePacket[0].change))))
         {
             undoStack.Peek().changes.Add(activePacket[0].change);
         }
         else
         {
             undoStack.Push(
-                (activePacket.Any(x => x.source == ActionSource.User) ? ActionSource.User : source,  
-                activePacket.Select(x => x.change).ToList()));
+                (activePacket.Any(x => x.source == ActionSource.User) ? ActionSource.User : source,
+                    activePacket.Select(x => x.change).ToList()));
         }
 
         activePacket = null;
@@ -115,6 +116,7 @@ public class DocumentChangeTracker : IDisposable
             if (!changes.changes[i].IsMergeableWith(changes.changes[i - 1]))
                 return false;
         }
+
         return true;
     }
 
@@ -124,9 +126,11 @@ public class DocumentChangeTracker : IDisposable
             return new List<IChangeInfo>();
         if (activePacket is not null || activeUpdateableChange is not null)
         {
-            Trace.WriteLine("Attempted to undo while there is an active updateable change or an unfinished undo packet");
+            Trace.WriteLine(
+                "Attempted to undo while there is an active updateable change or an unfinished undo packet");
             return new List<IChangeInfo>();
         }
+
         List<IChangeInfo> changeInfos = new();
         var changePacket = undoStack.Pop();
 
@@ -148,9 +152,11 @@ public class DocumentChangeTracker : IDisposable
             return new List<IChangeInfo>();
         if (activePacket is not null || activeUpdateableChange is not null)
         {
-            Trace.WriteLine("Attempted to redo while there is an active updateable change or an unfinished undo packet");
+            Trace.WriteLine(
+                "Attempted to redo while there is an active updateable change or an unfinished undo packet");
             return new List<IChangeInfo>();
         }
+
         List<IChangeInfo> changeInfos = new();
         var changePacket = redoStack.Pop();
 
@@ -170,9 +176,11 @@ public class DocumentChangeTracker : IDisposable
     {
         if (activeUpdateableChange is not null || activePacket is not null)
         {
-            Trace.WriteLine("Attempted to delete all changes while there is an active updateable change or an unfinished undo packet");
+            Trace.WriteLine(
+                "Attempted to delete all changes while there is an active updateable change or an unfinished undo packet");
             return;
         }
+
         foreach (var changesToDispose in redoStack)
         {
             foreach (var changeToDispose in changesToDispose.changes)
@@ -196,6 +204,7 @@ public class DocumentChangeTracker : IDisposable
             Trace.WriteLine($"Attempted to execute make change action {act} while {activeUpdateableChange} is active");
             return new None();
         }
+
         var change = act.CreateCorrespondingChange();
         var validationResult = change.InitializeAndValidate(document);
         if (!validationResult)
@@ -217,25 +226,67 @@ public class DocumentChangeTracker : IDisposable
     {
         if (activeUpdateableChange is null)
         {
-            var newChange = act.CreateCorrespondingChange();
-            var validationResult = newChange.InitializeAndValidate(document);
-            if (!validationResult)
+            if (CreateUpdateableChange(act, out var processStartOrUpdateChangeAction))
             {
-                Trace.WriteLine($"Change {newChange} failed validation");
-                newChange.Dispose();
-                return new None();
+                return processStartOrUpdateChangeAction;
             }
-            activeUpdateableChange = newChange;
         }
         else if (!act.IsChangeTypeMatching(activeUpdateableChange))
         {
-            Trace.WriteLine($"Tried to start or update a change using action {act} while a change of type {activeUpdateableChange} is active");
+            if (activeUpdateableChange is InterruptableUpdateableChange)
+            {
+                var applyInfo = activeUpdateableChange.Apply(document, false, out bool ignoreInUndo);
+                if (!ignoreInUndo)
+                    AddToUndo(activeUpdateableChange, ActionSource.User);
+                else
+                    activeUpdateableChange.Dispose();
+
+                activeUpdateableChange = null;
+
+                List<IChangeInfo> changeInfos = new();
+                applyInfo.Switch(
+                    static (None _) => { },
+                    (IChangeInfo info) => changeInfos.Add(info),
+                    (List<IChangeInfo> infos) => changeInfos.AddRange(infos));
+
+                if (CreateUpdateableChange(act, out var processStartOrUpdateChangeAction))
+                {
+                    processStartOrUpdateChangeAction.Switch(
+                        static (None _) => { },
+                        (IChangeInfo info) => changeInfos.Add(info),
+                        (List<IChangeInfo> infos) => changeInfos.AddRange(infos));
+                }
+
+                return changeInfos;
+            }
+
+            Trace.WriteLine(
+                $"Tried to start or update a change using action {act} while a change of type {activeUpdateableChange} is active");
             return new None();
         }
+
         act.UpdateCorrespodingChange(activeUpdateableChange);
         return activeUpdateableChange.ApplyTemporarily(document);
     }
 
+    private bool CreateUpdateableChange(IStartOrUpdateChangeAction act,
+        out OneOf<None, IChangeInfo, List<IChangeInfo>> processStartOrUpdateChangeAction)
+    {
+        var newChange = act.CreateCorrespondingChange();
+        var validationResult = newChange.InitializeAndValidate(document);
+        if (!validationResult)
+        {
+            Trace.WriteLine($"Change {newChange} failed validation");
+            newChange.Dispose();
+            processStartOrUpdateChangeAction = new None();
+            return true;
+        }
+
+        activeUpdateableChange = newChange;
+        processStartOrUpdateChangeAction = new None();
+        return false;
+    }
+
     private OneOf<None, IChangeInfo, List<IChangeInfo>> ProcessEndChangeAction(IEndChangeAction act)
     {
         if (activeUpdateableChange is null)
@@ -243,9 +294,11 @@ public class DocumentChangeTracker : IDisposable
             Trace.WriteLine($"Attempted to end a change using action {act} while no changes are active");
             return new None();
         }
+
         if (!act.IsChangeTypeMatching(activeUpdateableChange))
         {
-            Trace.WriteLine($"Trying to end a change with an action {act} while change {activeUpdateableChange} is active");
+            Trace.WriteLine(
+                $"Trying to end a change with an action {act} while change {activeUpdateableChange} is active");
             return new None();
         }
 
@@ -261,6 +314,7 @@ public class DocumentChangeTracker : IDisposable
     private List<IChangeInfo?> ProcessActionList(IReadOnlyList<(ActionSource, IAction)> actions)
     {
         List<IChangeInfo?> changeInfos = new();
+
         void AddInfo(OneOf<None, IChangeInfo, List<IChangeInfo>> info) =>
             info.Switch(
                 static (None _) => { },
@@ -298,6 +352,7 @@ public class DocumentChangeTracker : IDisposable
                     break;
             }
         }
+
         return changeInfos;
     }
 

+ 9 - 2
src/PixiEditor/Models/Controllers/ClipboardController.cs

@@ -363,9 +363,16 @@ internal static class ClipboardController
                 {
                     foreach (var file in storageFiles)
                     {
-                        if (Importer.IsSupportedFile(file.Path.LocalPath))
+                        try
                         {
-                            return file.Path.LocalPath;
+                            if (Importer.IsSupportedFile(file.Path.LocalPath))
+                            {
+                                return file.Path.LocalPath;
+                            }
+                        }
+                        catch (UriFormatException)
+                        {
+                            continue;
                         }
                     }
                 }

+ 22 - 24
src/PixiEditor/ViewModels/Nodes/NodePropertyViewModel.cs

@@ -2,6 +2,7 @@
 using Avalonia;
 using Avalonia.Media;
 using Avalonia.Styling;
+using Avalonia.Threading;
 using Drawie.Backend.Core.Shaders.Generation;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.Events;
@@ -20,7 +21,7 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
     private bool isInput;
     private bool isFunc;
     private IBrush socketBrush;
-    
+
     private ObservableCollection<INodePropertyHandler> connectedInputs = new();
     private INodePropertyHandler? connectedOutput;
 
@@ -29,22 +30,17 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
         get => displayName;
         set => SetProperty(ref displayName, value);
     }
-    
+
     public object? Value
     {
         get => _value;
         set
         {
-            var oldValue = _value;
-            
-            if (SetProperty(ref _value, value))
-            {
-                ViewModelMain.Current.NodeGraphManager.UpdatePropertyValue((node, PropertyName, value));
-                ValueChanged?.Invoke(this, new NodePropertyValueChangedArgs(oldValue, value));
-            }
+            ViewModelMain.Current.NodeGraphManager.UpdatePropertyValue((node, PropertyName, value));
+            //ValueChanged?.Invoke(this, new NodePropertyValueChangedArgs(oldValue, value));
         }
     }
-    
+
     public bool IsInput
     {
         get => isInput;
@@ -103,13 +99,13 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
         get => propertyName;
         set => SetProperty(ref propertyName, value);
     }
-    
+
     public IBrush SocketBrush
     {
         get => socketBrush;
         set => SetProperty(ref socketBrush, value);
     }
-    
+
     public Type PropertyType { get; }
 
     public NodePropertyViewModel(INodeHandler node, Type propertyType)
@@ -123,17 +119,19 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
             targetType = propertyType.GetMethod("Invoke").ReturnType;
         }
 
-        if (Application.Current.Styles.TryGetResource($"{targetType.Name}SocketBrush", App.Current.ActualThemeVariant, out object brush))
+        if (Application.Current.Styles.TryGetResource($"{targetType.Name}SocketBrush", App.Current.ActualThemeVariant,
+                out object brush))
         {
             if (brush is IBrush brushValue)
             {
                 SocketBrush = brushValue;
             }
         }
-        
-        if(SocketBrush == null)
+
+        if (SocketBrush == null)
         {
-            if(Application.Current.Styles.TryGetResource($"DefaultSocketBrush", App.Current.ActualThemeVariant, out object defaultBrush))
+            if (Application.Current.Styles.TryGetResource($"DefaultSocketBrush", App.Current.ActualThemeVariant,
+                    out object defaultBrush))
             {
                 if (defaultBrush is IBrush defaultBrushValue)
                 {
@@ -146,7 +144,7 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
     public static NodePropertyViewModel? CreateFromType(Type type, INodeHandler node)
     {
         Type propertyType = type;
-        
+
         if (type.IsAssignableTo(typeof(Delegate)))
         {
             propertyType = type.GetMethod("Invoke").ReturnType;
@@ -156,9 +154,9 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
         {
             propertyType = type.GetMethod("Invoke").ReturnType.BaseType.GenericTypeArguments[0];
         }
-        
+
         string name = $"{propertyType.Name}PropertyViewModel";
-        
+
         Type viewModelType = Type.GetType($"PixiEditor.ViewModels.Nodes.Properties.{name}");
         if (viewModelType == null)
         {
@@ -166,13 +164,13 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
             {
                 return new GenericEnumPropertyViewModel(node, type, propertyType);
             }
-            
+
             return new GenericPropertyViewModel(node, type);
         }
-        
+
         return (NodePropertyViewModel)Activator.CreateInstance(viewModelType, node, type);
     }
-    
+
     public event NodePropertyValueChanged? ValueChanged;
 
     public void InternalSetValue(object? value)
@@ -201,12 +199,12 @@ internal abstract class NodePropertyViewModel<T> : NodePropertyViewModel
 
             if (base.Value is T value)
                 return value;
-            
+
             return default;
         }
         set => base.Value = value;
     }
-    
+
     public NodePropertyViewModel(NodeViewModel node, Type valueType) : base(node, valueType)
     {
     }