Browse Source

Merge changes manually, Delete changes, improved selection and layer merging

Equbuxu 3 years ago
parent
commit
ef9b7c5ff5

+ 4 - 2
src/ChangeableDocument/Actions/Structure/CreateStructureMember_Action.cs

@@ -4,19 +4,21 @@ namespace ChangeableDocument.Actions.Structure;
 
 
 public record class CreateStructureMember_Action : IMakeChangeAction
 public record class CreateStructureMember_Action : IMakeChangeAction
 {
 {
-    public CreateStructureMember_Action(Guid parentGuid, int index, StructureMemberType type)
+    public CreateStructureMember_Action(Guid parentGuid, Guid newGuid, int index, StructureMemberType type)
     {
     {
         ParentGuid = parentGuid;
         ParentGuid = parentGuid;
+        NewGuid = newGuid;
         Index = index;
         Index = index;
         Type = type;
         Type = type;
     }
     }
 
 
     public Guid ParentGuid { get; init; }
     public Guid ParentGuid { get; init; }
+    public Guid NewGuid { get; init; }
     public int Index { get; init; }
     public int Index { get; init; }
     public StructureMemberType Type { get; init; }
     public StructureMemberType Type { get; init; }
 
 
     Change IMakeChangeAction.CreateCorrespondingChange()
     Change IMakeChangeAction.CreateCorrespondingChange()
     {
     {
-        return new CreateStructureMember_Change(ParentGuid, Index, Type);
+        return new CreateStructureMember_Change(ParentGuid, NewGuid, Index, Type);
     }
     }
 }
 }

+ 6 - 0
src/ChangeableDocument/Actions/Undo/DeleteRecordedChanges_Action.cs

@@ -0,0 +1,6 @@
+namespace ChangeableDocument.Actions.Undo
+{
+    public record class DeleteRecordedChanges_Action : IAction
+    {
+    }
+}

+ 12 - 0
src/ChangeableDocument/Actions/Undo/MergeLatestChanges_Action.cs

@@ -0,0 +1,12 @@
+namespace ChangeableDocument.Actions.Undo
+{
+    public record class MergeLatestChanges_Action : IAction
+    {
+        public MergeLatestChanges_Action(int count)
+        {
+            Count = count;
+        }
+
+        public int Count { get; }
+    }
+}

+ 3 - 5
src/ChangeableDocument/Changes/CreateStructureMember_Change.cs

@@ -11,17 +11,15 @@ namespace ChangeableDocument.Changes
         private int parentFolderIndex;
         private int parentFolderIndex;
         private StructureMemberType type;
         private StructureMemberType type;
 
 
-        public CreateStructureMember_Change(Guid parentFolder, int parentFolderIndex, StructureMemberType type)
+        public CreateStructureMember_Change(Guid parentFolder, Guid newGuid, int parentFolderIndex, StructureMemberType type)
         {
         {
             this.parentFolderGuid = parentFolder;
             this.parentFolderGuid = parentFolder;
             this.parentFolderIndex = parentFolderIndex;
             this.parentFolderIndex = parentFolderIndex;
             this.type = type;
             this.type = type;
+            newMemberGuid = newGuid;
         }
         }
 
 
-        public override void Initialize(Document target)
-        {
-            newMemberGuid = Guid.NewGuid();
-        }
+        public override void Initialize(Document target) { }
 
 
         public override IChangeInfo Apply(Document document, out bool ignoreInUndo)
         public override IChangeInfo Apply(Document document, out bool ignoreInUndo)
         {
         {

+ 48 - 0
src/ChangeableDocument/DocumentChangeTracker.cs

@@ -70,6 +70,48 @@ namespace ChangeableDocument
             return changeInfos;
             return changeInfos;
         }
         }
 
 
+        private List<Change> PopLatestChanges(int count)
+        {
+            if (redoStack.Count > 0)
+                throw new Exception("There are changes in the redo stack");
+            List<Change> popped = new();
+            while (count > 0)
+            {
+                if (undoStack.Count == 0)
+                    return popped;
+                var packet = undoStack.Peek();
+                var change = packet[^1];
+                packet.RemoveAt(packet.Count - 1);
+                popped.Add(change);
+                if (packet.Count == 0)
+                    undoStack.Pop();
+                count--;
+            }
+            popped.Reverse();
+            return popped;
+        }
+
+        private void MergeLatestChanges(int count)
+        {
+            if (redoStack.Count > 0)
+                throw new Exception("There are changes in the redo stack");
+            var packet = PopLatestChanges(count);
+            if (packet.Count > 0)
+                undoStack.Push(packet);
+        }
+
+        private void DeleteAllChanges()
+        {
+            foreach (var changesToDispose in redoStack)
+                foreach (var changeToDispose in changesToDispose)
+                    changeToDispose.Dispose();
+            foreach (var changesToDispose in undoStack)
+                foreach (var changeToDispose in changesToDispose)
+                    changeToDispose.Dispose();
+            redoStack.Clear();
+            undoStack.Clear();
+        }
+
         private IChangeInfo? ProcessMakeChangeAction(IMakeChangeAction act)
         private IChangeInfo? ProcessMakeChangeAction(IMakeChangeAction act)
         {
         {
             if (activeChange != null)
             if (activeChange != null)
@@ -133,6 +175,12 @@ namespace ChangeableDocument
                     case Redo_Action act:
                     case Redo_Action act:
                         changeInfos.AddRange(Redo());
                         changeInfos.AddRange(Redo());
                         break;
                         break;
+                    case MergeLatestChanges_Action act:
+                        MergeLatestChanges(act.Count);
+                        break;
+                    case DeleteRecordedChanges_Action:
+                        DeleteAllChanges();
+                        break;
                     default:
                     default:
                         throw new Exception("Unknown action type");
                         throw new Exception("Unknown action type");
                 }
                 }

+ 3 - 3
src/PixiEditorPrototype/Models/DocumentStructureHelper.cs

@@ -19,13 +19,13 @@ namespace PixiEditorPrototype.Models
             if (doc.SelectedStructureMember == null)
             if (doc.SelectedStructureMember == null)
             {
             {
                 //put member on top
                 //put member on top
-                doc.ActionAccumulator.AddAction(new CreateStructureMember_Action(doc.StructureRoot.GuidValue, doc.StructureRoot.Children.Count, type));
+                doc.ActionAccumulator.AddAction(new CreateStructureMember_Action(doc.StructureRoot.GuidValue, Guid.NewGuid(), doc.StructureRoot.Children.Count, type));
                 return;
                 return;
             }
             }
             if (doc.SelectedStructureMember is FolderViewModel folder)
             if (doc.SelectedStructureMember is FolderViewModel folder)
             {
             {
                 //put member inside folder on top
                 //put member inside folder on top
-                doc.ActionAccumulator.AddAction(new CreateStructureMember_Action(folder.GuidValue, folder.Children.Count, type));
+                doc.ActionAccumulator.AddAction(new CreateStructureMember_Action(folder.GuidValue, Guid.NewGuid(), folder.Children.Count, type));
                 return;
                 return;
             }
             }
             if (doc.SelectedStructureMember is LayerViewModel layer)
             if (doc.SelectedStructureMember is LayerViewModel layer)
@@ -35,7 +35,7 @@ namespace PixiEditorPrototype.Models
                 if (path.Count < 2)
                 if (path.Count < 2)
                     throw new Exception("Couldn't find a path to the selected member");
                     throw new Exception("Couldn't find a path to the selected member");
                 var parent = (FolderViewModel)path[1];
                 var parent = (FolderViewModel)path[1];
-                doc.ActionAccumulator.AddAction(new CreateStructureMember_Action(parent.GuidValue, parent.Children.IndexOf(layer) + 1, type));
+                doc.ActionAccumulator.AddAction(new CreateStructureMember_Action(parent.GuidValue, Guid.NewGuid(), parent.Children.IndexOf(layer) + 1, type));
                 return;
                 return;
             }
             }
             throw new Exception("Unknown member type: " + type.ToString());
             throw new Exception("Unknown member type: " + type.ToString());

+ 28 - 3
src/PixiEditorPrototype/ViewModels/DocumentViewModel.cs

@@ -3,6 +3,7 @@ using ChangeableDocument.Actions.Document;
 using ChangeableDocument.Actions.Drawing;
 using ChangeableDocument.Actions.Drawing;
 using ChangeableDocument.Actions.Drawing.Rectangle;
 using ChangeableDocument.Actions.Drawing.Rectangle;
 using ChangeableDocument.Actions.Drawing.Selection;
 using ChangeableDocument.Actions.Drawing.Selection;
+using ChangeableDocument.Actions.Properties;
 using ChangeableDocument.Actions.Structure;
 using ChangeableDocument.Actions.Structure;
 using ChangeableDocument.Actions.Undo;
 using ChangeableDocument.Actions.Undo;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
@@ -12,6 +13,7 @@ using SkiaSharp;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.ComponentModel;
+using System.Linq;
 using System.Windows;
 using System.Windows;
 using System.Windows.Input;
 using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Media;
@@ -54,6 +56,7 @@ namespace PixiEditorPrototype.ViewModels
         public RelayCommand? ChangeActiveToolCommand { get; }
         public RelayCommand? ChangeActiveToolCommand { get; }
         public RelayCommand? ResizeCanvasCommand { get; }
         public RelayCommand? ResizeCanvasCommand { get; }
         public RelayCommand? CombineCommand { get; }
         public RelayCommand? CombineCommand { get; }
+        public RelayCommand? ClearHistoryCommand { get; }
 
 
         public RelayCommand? MouseDownCommand { get; }
         public RelayCommand? MouseDownCommand { get; }
         public RelayCommand? MouseMoveCommand { get; }
         public RelayCommand? MouseMoveCommand { get; }
@@ -93,6 +96,7 @@ namespace PixiEditorPrototype.ViewModels
             ChangeActiveToolCommand = new RelayCommand(ChangeActiveTool);
             ChangeActiveToolCommand = new RelayCommand(ChangeActiveTool);
             ResizeCanvasCommand = new RelayCommand(ResizeCanvas);
             ResizeCanvasCommand = new RelayCommand(ResizeCanvas);
             CombineCommand = new RelayCommand(Combine);
             CombineCommand = new RelayCommand(Combine);
+            ClearHistoryCommand = new RelayCommand(ClearHistory);
 
 
             MouseDownCommand = new RelayCommand(MouseDown);
             MouseDownCommand = new RelayCommand(MouseDown);
             MouseMoveCommand = new RelayCommand(MouseMove);
             MouseMoveCommand = new RelayCommand(MouseMove);
@@ -153,6 +157,8 @@ namespace PixiEditorPrototype.ViewModels
             }
             }
             else if (activeTool == Tool.Select)
             else if (activeTool == Tool.Select)
             {
             {
+                if (!startedSelectingRect)
+                    ActionAccumulator.AddAction(new ClearSelection_Action());
                 startedSelectingRect = true;
                 startedSelectingRect = true;
                 ActionAccumulator.AddAction(new SelectRectangle_Action(
                 ActionAccumulator.AddAction(new SelectRectangle_Action(
                         new(mouseDownCanvasX, mouseDownCanvasY),
                         new(mouseDownCanvasX, mouseDownCanvasY),
@@ -180,6 +186,7 @@ namespace PixiEditorPrototype.ViewModels
             {
             {
                 startedSelectingRect = false;
                 startedSelectingRect = false;
                 ActionAccumulator.AddAction(new EndSelectRectangle_Action());
                 ActionAccumulator.AddAction(new EndSelectRectangle_Action());
+                ActionAccumulator.AddAction(new MergeLatestChanges_Action(2));
             }
             }
         }
         }
 
 
@@ -218,12 +225,30 @@ namespace PixiEditorPrototype.ViewModels
         {
         {
             if (SelectedStructureMember == null)
             if (SelectedStructureMember == null)
                 return;
                 return;
-            HashSet<Guid> selected = new();
+            List<Guid> selected = new();
             AddSelectedMembers(StructureRoot, selected);
             AddSelectedMembers(StructureRoot, selected);
-            ActionAccumulator.AddAction(new CombineStructureMembersOnto_Action(SelectedStructureMember.GuidValue, selected));
+            if (selected.Count < 2)
+                return;
+
+            var (child, parent) = StructureHelper.FindChildAndParentOrThrow(selected[0]);
+            int index = parent.Children.IndexOf(child);
+            Guid newGuid = Guid.NewGuid();
+
+            //make a new layer, put combined image onto it, delete layers that were merged
+            ActionAccumulator.AddAction(new CreateStructureMember_Action(parent.GuidValue, newGuid, index, StructureMemberType.Layer));
+            ActionAccumulator.AddAction(new SetStructureMemberName_Action(child.Name + "-comb", newGuid));
+            ActionAccumulator.AddAction(new CombineStructureMembersOnto_Action(newGuid, selected.ToHashSet()));
+            foreach (var member in selected)
+                ActionAccumulator.AddAction(new DeleteStructureMember_Action(member));
+            ActionAccumulator.AddAction(new MergeLatestChanges_Action(3 + selected.Count));
+        }
+
+        private void ClearHistory(object? param)
+        {
+            ActionAccumulator.AddAction(new DeleteRecordedChanges_Action());
         }
         }
 
 
-        private void AddSelectedMembers(FolderViewModel folder, HashSet<Guid> collection)
+        private void AddSelectedMembers(FolderViewModel folder, List<Guid> collection)
         {
         {
             foreach (var child in folder.Children)
             foreach (var child in folder.Children)
             {
             {

+ 7 - 6
src/PixiEditorPrototype/Views/DocumentView.xaml

@@ -22,10 +22,11 @@
                     <Button Margin="5" Command="{Binding CreateNewFolderCommand}" Width="80">New Folder</Button>
                     <Button Margin="5" Command="{Binding CreateNewFolderCommand}" Width="80">New Folder</Button>
                     <Button Margin="5" Command="{Binding DeleteStructureMemberCommand}" Width="80">Delete</Button>
                     <Button Margin="5" Command="{Binding DeleteStructureMemberCommand}" Width="80">Delete</Button>
                 </StackPanel>
                 </StackPanel>
-                <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" HorizontalAlignment="Stretch" Margin="0,0,0,5">
-                    <Button Width="80" Margin="5,0" Command="{Binding CombineCommand}">Combine</Button>
-                    <Slider Width="100" Minimum="0" Maximum="1" SmallChange="0.01" LargeChange="0.1" IsSnapToTickEnabled="True" TickFrequency="0.01" x:Name="opacitySlider"
-                            VerticalAlignment="Center"
+                <DockPanel DockPanel.Dock="Top" HorizontalAlignment="Stretch" Margin="0,0,0,5">
+                    <Button Width="80" Margin="5,0" Command="{Binding CombineCommand}">Merge</Button>
+                    <TextBlock Text="{Binding SelectedStructureMember.Opacity, StringFormat=N2}" Margin="5,0" DockPanel.Dock="Right" VerticalAlignment="Center" TextAlignment="Center" d:Text="1.00" Width="30"></TextBlock>
+                    <Slider Minimum="0" Maximum="1" SmallChange="0.01" LargeChange="0.1" IsSnapToTickEnabled="True" TickFrequency="0.01" x:Name="opacitySlider"
+                            VerticalAlignment="Center" HorizontalAlignment="Stretch"
                             Value="{Binding SelectedStructureMember.Opacity, Mode=OneWay}">
                             Value="{Binding SelectedStructureMember.Opacity, Mode=OneWay}">
                         <i:Interaction.Behaviors>
                         <i:Interaction.Behaviors>
                             <behaviors:SliderUpdateBehavior
                             <behaviors:SliderUpdateBehavior
@@ -34,8 +35,7 @@
                                 ValueFromSlider="{Binding ElementName=opacitySlider, Path=Value}"/>
                                 ValueFromSlider="{Binding ElementName=opacitySlider, Path=Value}"/>
                         </i:Interaction.Behaviors>
                         </i:Interaction.Behaviors>
                     </Slider>
                     </Slider>
-                    <TextBlock Text="{Binding SelectedStructureMember.Opacity, StringFormat=N2}" VerticalAlignment="Center" TextAlignment="Center" d:Text="1.00" Width="30"></TextBlock>
-                </StackPanel>
+                </DockPanel>
                 <TreeView ItemsSource="{Binding StructureRoot.Children}">
                 <TreeView ItemsSource="{Binding StructureRoot.Children}">
                     <TreeView.ItemsPanel>
                     <TreeView.ItemsPanel>
                         <ItemsPanelTemplate>
                         <ItemsPanelTemplate>
@@ -95,6 +95,7 @@
                     <Button Width="50" Margin="5" Command="{Binding UndoCommand}">Undo</Button>
                     <Button Width="50" Margin="5" Command="{Binding UndoCommand}">Undo</Button>
                     <Button Width="50" Margin="5" Command="{Binding RedoCommand}">Redo</Button>
                     <Button Width="50" Margin="5" Command="{Binding RedoCommand}">Redo</Button>
                     <Button Width="100" Margin="5" Command="{Binding ClearSelectionCommand}">Clear selection</Button>
                     <Button Width="100" Margin="5" Command="{Binding ClearSelectionCommand}">Clear selection</Button>
+                    <Button Width="120" Margin="5" Command="{Binding ClearHistoryCommand}">Clear undo history</Button>
                 </StackPanel>
                 </StackPanel>
                 <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right">
                 <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right">
                     <TextBox Width="30" Margin="5" Text="{Binding ResizeWidth}"/>
                     <TextBox Width="30" Margin="5" Text="{Binding ResizeWidth}"/>