Browse Source

Undo/Redo, Duplicate layer

Equbuxu 3 years ago
parent
commit
5663ee9cfa

+ 4 - 3
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -95,7 +95,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     /// <param name="guid">The <see cref="StructureMember.GuidValue"/> of the member</param>
     /// <exception cref="ArgumentException">Thrown if the member could not be found</exception>
     /// <exception cref="InvalidCastException">Thrown if the member is not of type <typeparamref name="T"/></exception>
-    public T FindMemberOrThrow<T>(Guid guid) where T : StructureMember => (T)FindMember(guid)!;
+    public T FindMemberOrThrow<T>(Guid guid) where T : StructureMember => (T?)FindMember(guid) ?? throw new ArgumentException($"Could not find member with guid '{guid}'");
 
     /// <summary>
     /// Finds the member with the <paramref name="guid"/> or returns null if not found
@@ -133,7 +133,8 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     /// <param name="member">The member</param>
     /// <typeparam name="T">The type of the <see cref="StructureMember"/></typeparam>
     /// <returns>True if the member could be found and is of type <typeparamref name="T"/>, otherwise false</returns>
-    public bool TryFindMember<T>(Guid guid, [NotNullWhen(true)] out T? member) where T : IReadOnlyStructureMember
+    public bool TryFindMember<T>(Guid guid, [NotNullWhen(true)] out T? member) 
+        where T : IReadOnlyStructureMember
     {
         if (!TryFindMember(guid, out var structureMember) || structureMember is not T cast)
         {
@@ -142,7 +143,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
         }
 
         member = cast;
-        return false;
+        return true;
     }
 
     /// <summary>

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/StructureMember.cs

@@ -12,7 +12,7 @@ internal abstract class StructureMember : IChangeable, IReadOnlyStructureMember,
     public bool ClipToMemberBelow { get; set; } = false;
     public string Name { get; set; } = "Unnamed";
     public BlendMode BlendMode { get; set; } = BlendMode.Normal;
-    public Guid GuidValue { get; init; }
+    public Guid GuidValue { get; set; }
     public ChunkyImage? Mask { get; set; } = null;
     public bool MaskIsVisible { get; set; } = true;
     IReadOnlyChunkyImage? IReadOnlyStructureMember.Mask => Mask;

+ 48 - 0
src/PixiEditor.ChangeableDocument/Changes/Structure/DuplicateLayer_Change.cs

@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
+
+namespace PixiEditor.ChangeableDocument.Changes.Structure;
+internal class DuplicateLayer_Change : Change
+{
+    private readonly Guid layerGuid;
+    private Guid duplicateGuid;
+
+    [GenerateMakeChangeAction]
+    public DuplicateLayer_Change(Guid layerGuid)
+    {
+        this.layerGuid = layerGuid;
+    }
+    public override OneOf<Success, Error> InitializeAndValidate(Document target)
+    {
+        if (!target.TryFindMember<Layer>(layerGuid, out Layer? layer))
+            return new Error();
+        return new Success();
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        (Layer existingLayer, Folder parent) = ((Layer, Folder))target.FindChildAndParentOrThrow(layerGuid);
+
+        Layer clone = existingLayer.Clone();
+        duplicateGuid = Guid.NewGuid();
+        clone.GuidValue = duplicateGuid;
+
+        int index = parent.Children.IndexOf(existingLayer);
+        parent.Children = parent.Children.Insert(index, clone);
+
+        ignoreInUndo = false;
+        return CreateLayer_ChangeInfo.FromLayer(parent.GuidValue, index, clone);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        var (member, parent) = target.FindChildAndParentOrThrow(duplicateGuid);
+        parent.Children = parent.Children.Remove(member);
+        member.Dispose();
+        return new DeleteStructureMember_ChangeInfo(duplicateGuid, parent.GuidValue);
+    }
+}

+ 2 - 0
src/PixiEditor.ChangeableDocument/DocumentChangeTracker.cs

@@ -12,6 +12,8 @@ public class DocumentChangeTracker : IDisposable
     private bool disposed = false;
     private bool running = false;
     public IReadOnlyDocument Document => document;
+    public bool HasSavedUndo => undoStack.Any();
+    public bool HasSavedRedo => redoStack.Any();
 
     private UpdateableChange? activeUpdateableChange = null;
     private List<Change>? activePacket = null;

+ 25 - 0
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs

@@ -33,6 +33,10 @@ internal class DocumentViewModel : NotifyableObject
         }
     }
 
+    public bool UpdateableChangeActive => Helpers.ChangeController.IsChangeActive;
+    public bool HasSavedUndo => Helpers.Tracker.HasSavedUndo;
+    public bool HasSavedRedo => Helpers.Tracker.HasSavedRedo;
+
     public FolderViewModel StructureRoot { get; }
 
     public DocumentStructureViewModel StructureViewModel { get; }
@@ -243,6 +247,13 @@ internal class DocumentViewModel : NotifyableObject
         Helpers.StructureHelper.CreateNewStructureMember(type);
     }
 
+    public void DuplicateLayer(Guid guidValue)
+    {
+        if (Helpers.ChangeController.IsChangeActive)
+            return;
+        Helpers.ActionAccumulator.AddFinishedActions(new DuplicateLayer_Action(guidValue));
+    }
+
     public void DeleteStructureMember(Guid guidValue)
     {
         if (Helpers.ChangeController.IsChangeActive)
@@ -284,6 +295,20 @@ internal class DocumentViewModel : NotifyableObject
     public void UsePenTool() => Helpers.ChangeController.TryStartUpdateableChange<PenToolExecutor>();
     public void UseEllipseTool() => Helpers.ChangeController.TryStartUpdateableChange<EllipseToolExecutor>();
 
+    public void Undo()
+    {
+        if (Helpers.ChangeController.IsChangeActive)
+            return;
+        Helpers.ActionAccumulator.AddActions(new Undo_Action());
+    }
+
+    public void Redo()
+    {
+        if (Helpers.ChangeController.IsChangeActive)
+            return;
+        Helpers.ActionAccumulator.AddActions(new Redo_Action());
+    }
+
     public void MoveStructureMember(Guid memberToMove, Guid memberToMoveIntoOrNextTo, StructureMemberPlacement placement)
     {
         if (Helpers.ChangeController.IsChangeActive || memberToMove == memberToMoveIntoOrNextTo)

+ 8 - 12
src/PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs

@@ -131,24 +131,20 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         Owner.DocumentManagerSubViewModel.ActiveDocument?.OnOpacitySliderDragEnded();
     }
 
+    [Command.Basic("PixiEditor.Layer.DuplicateSelectedLayer", "Duplicate selected layer", "Duplicate selected layer", CanExecute = "PixiEditor.Layer.CanDuplicatedSelectedLayer")]
     public void DuplicateLayer(object parameter)
     {
-
+        var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
+        if (member is not LayerViewModel layerVM)
+            return;
+        member.Document.DuplicateLayer(member.GuidValue);
     }
 
+    [Evaluator.CanExecute("PixiEditor.Layer.CanDuplicatedSelectedLayer")]
     public bool CanDuplicateLayer(object property)
     {
-        return false;
-    }
-
-    public void RenameLayer(object parameter)
-    {
-
-    }
-
-    public bool CanRenameLayer(object parameter)
-    {
-        return false;
+        var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
+        return member is LayerViewModel;
     }
 
     private bool HasSelectedMember(bool above)

+ 16 - 35
src/PixiEditor/ViewModels/SubViewModels/Main/UndoViewModel.cs

@@ -8,14 +8,9 @@ namespace PixiEditor.ViewModels.SubViewModels.Main;
 [Command.Group("PixiEditor.Undo", "Undo")]
 internal class UndoViewModel : SubViewModel<ViewModelMain>
 {
-    public event EventHandler UndoRedoCalled;
-
     public UndoViewModel(ViewModelMain owner)
         : base(owner)
     {
-        //var result = Directory.CreateDirectory(StorageBasedChange.DefaultUndoChangeLocation);
-
-        //ClearUndoTempDirectory();
     }
 
     /// <summary>
@@ -25,14 +20,10 @@ internal class UndoViewModel : SubViewModel<ViewModelMain>
         IconPath = "E7A6", IconEvaluator = "PixiEditor.FontIcon")]
     public void Redo()
     {
-        UndoRedoCalled?.Invoke(this, EventArgs.Empty);
-
-        //sometimes CanRedo gets changed after UndoRedoCalled invoke, so check again (normally this is checked by the relaycommand)
-        if (CanRedo())
-        {
-            //Owner.BitmapManager.ActiveDocument.UndoManager.Redo();
-            //Owner.BitmapManager.ActiveDocument.ChangesSaved = false;
-        }
+        var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        if (doc is null || doc.UpdateableChangeActive || !doc.HasSavedRedo)
+            return;
+        doc.Redo();
     }
 
     /// <summary>
@@ -42,26 +33,10 @@ internal class UndoViewModel : SubViewModel<ViewModelMain>
         IconPath = "E7A7", IconEvaluator = "PixiEditor.FontIcon")]
     public void Undo()
     {
-        UndoRedoCalled?.Invoke(this, EventArgs.Empty);
-
-        //sometimes CanUndo gets changed after UndoRedoCalled invoke, so check again (normally this is checked by the relaycommand)
-        if (CanUndo())
-        {
-            //Owner.BitmapManager.ActiveDocument.UndoManager.Undo();
-            //Owner.BitmapManager.ActiveDocument.ChangesSaved = false;
-        }
-    }
-
-    /// <summary>
-    /// Removes all files from %tmp%/PixiEditor/UndoStack/.
-    /// </summary>
-    public void ClearUndoTempDirectory()
-    {
-        /*DirectoryInfo dirInfo = new DirectoryInfo(StorageBasedChange.DefaultUndoChangeLocation);
-        foreach (FileInfo file in dirInfo.GetFiles())
-        {
-            file.Delete();
-        }*/
+        var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        if (doc is null || doc.UpdateableChangeActive || !doc.HasSavedUndo)
+            return;
+        doc.Undo();
     }
 
     /// <summary>
@@ -72,7 +47,10 @@ internal class UndoViewModel : SubViewModel<ViewModelMain>
     [Evaluator.CanExecute("PixiEditor.Undo.CanUndo")]
     public bool CanUndo()
     {
-        return false;
+        var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        if (doc is null)
+            return false;
+        return !doc.UpdateableChangeActive && doc.HasSavedUndo;
     }
 
     /// <summary>
@@ -83,6 +61,9 @@ internal class UndoViewModel : SubViewModel<ViewModelMain>
     [Evaluator.CanExecute("PixiEditor.Undo.CanRedo")]
     public bool CanRedo()
     {
-        return false;
+        var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        if (doc is null)
+            return false;
+        return !doc.UpdateableChangeActive && doc.HasSavedRedo;
     }
 }

+ 1 - 5
src/PixiEditor/Views/UserControls/Layers/LayerControl.xaml

@@ -67,11 +67,7 @@
         </Grid>
         <Border.ContextMenu>
             <ContextMenu>
-                <MenuItem Header="Duplicate"
-                                         Command="{Binding PlacementTarget.Tag.LayerCommandsViewModel.DuplicateLayerCommand,
-                                            RelativeSource={RelativeSource AncestorType=ContextMenu}}"
-                          CommandParameter="{Binding PlacementTarget.Tag.ContainerIndex, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
-                </MenuItem>
+                <MenuItem Header="Duplicate" Command="{cmds:Command PixiEditor.Layer.DuplicateSelectedLayer}"/>
                 <MenuItem Header="Delete" Command="{cmds:Command PixiEditor.Layer.DeleteAllSelected}"/>
                 <MenuItem Header="Rename" Click="RenameMenuItem_Click"/>
                 <MenuItem Header="Move upwards" Command="{cmds:Command PixiEditor.Layer.MoveSelectedMemberUpwards}"/>