Browse Source

Combine layers operation

Equbuxu 3 years ago
parent
commit
e20302834f

+ 21 - 0
src/ChangeableDocument/Actions/Drawing/CombineStructureMembersOnto_Action.cs

@@ -0,0 +1,21 @@
+using ChangeableDocument.Changes;
+using ChangeableDocument.Changes.Drawing;
+
+namespace ChangeableDocument.Actions.Drawing
+{
+    public record class CombineStructureMembersOnto_Action : IMakeChangeAction
+    {
+        public CombineStructureMembersOnto_Action(Guid targetLayer, HashSet<Guid> membersToCombine)
+        {
+            TargetLayer = targetLayer;
+            MembersToCombine = membersToCombine;
+        }
+
+        public Guid TargetLayer { get; }
+        public HashSet<Guid> MembersToCombine { get; }
+        IChange IMakeChangeAction.CreateCorrespondingChange()
+        {
+            return new CombineStructureMembersOnto_Change(MembersToCombine, TargetLayer);
+        }
+    }
+}

+ 100 - 0
src/ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs

@@ -0,0 +1,100 @@
+using ChangeableDocument.Changeables;
+using ChangeableDocument.ChangeInfos;
+using ChangeableDocument.Rendering;
+using ChunkyImageLib;
+using ChunkyImageLib.DataHolders;
+
+namespace ChangeableDocument.Changes.Drawing
+{
+    internal class CombineStructureMembersOnto_Change : IChange
+    {
+        private HashSet<Guid> membersToMerge;
+
+        private HashSet<Guid> layersToCombine = new();
+
+        private Guid targetLayer;
+        private CommittedChunkStorage? originalChunks;
+
+        public CombineStructureMembersOnto_Change(HashSet<Guid> membersToMerge, Guid targetLayer)
+        {
+            this.membersToMerge = new HashSet<Guid>(membersToMerge);
+            this.targetLayer = targetLayer;
+        }
+
+        public void Initialize(Document target)
+        {
+            foreach (Guid guid in membersToMerge)
+            {
+                var member = target.FindMemberOrThrow(guid);
+                if (member is Layer layer)
+                    layersToCombine.Add(layer.GuidValue);
+                else if (member is Folder innerFolder)
+                    AddChildren(innerFolder, layersToCombine);
+            }
+        }
+
+        private void AddChildren(Folder folder, HashSet<Guid> collection)
+        {
+            foreach (var child in folder.Children)
+            {
+                if (child is Layer layer)
+                    collection.Add(layer.GuidValue);
+                else if (child is Folder innerFolder)
+                    AddChildren(innerFolder, collection);
+            }
+        }
+
+        public IChangeInfo? Apply(Document target)
+        {
+            Layer toDrawOn = (Layer)target.FindMemberOrThrow(targetLayer);
+
+            var chunksToCombine = new HashSet<Vector2i>();
+            foreach (var guid in layersToCombine)
+            {
+                var layer = (Layer)target.FindMemberOrThrow(guid);
+                chunksToCombine.UnionWith(layer.LayerImage.FindAllChunks());
+            }
+
+            toDrawOn.LayerImage.Clear();
+            foreach (var chunk in chunksToCombine)
+            {
+                using var combined = ChunkRenderer.RenderSpecificLayers(chunk, target.StructureRoot, layersToCombine);
+                toDrawOn.LayerImage.DrawImage(chunk * ChunkyImage.ChunkSize, combined.Surface);
+            }
+            var affectedChunks = toDrawOn.LayerImage.FindAffectedChunks();
+            originalChunks = new CommittedChunkStorage(toDrawOn.LayerImage, affectedChunks);
+            toDrawOn.LayerImage.CommitChanges();
+
+            return new LayerImageChunks_ChangeInfo()
+            {
+                LayerGuid = targetLayer,
+                Chunks = affectedChunks
+            };
+        }
+
+        public IChangeInfo? Revert(Document target)
+        {
+            Layer toDrawOn = (Layer)target.FindMemberOrThrow(targetLayer);
+            if (originalChunks == null)
+                throw new Exception("No saved chunks to restore");
+
+            originalChunks.ApplyChunksToImage(toDrawOn.LayerImage);
+            var affectedChunks = toDrawOn.LayerImage.FindAffectedChunks();
+            toDrawOn.LayerImage.CommitChanges();
+
+            originalChunks.Dispose();
+            originalChunks = null;
+
+            return new LayerImageChunks_ChangeInfo()
+            {
+                LayerGuid = targetLayer,
+                Chunks = affectedChunks
+            };
+        }
+
+        public void Dispose()
+        {
+            originalChunks?.Dispose();
+        }
+    }
+}

+ 2 - 2
src/ChunkyImageLib/ChunkyImage.cs

@@ -76,13 +76,13 @@ namespace ChunkyImageLib
             EnqueueOperation(operation);
         }
 
-        internal void DrawImage(Vector2i pos, Surface image)
+        public void DrawImage(Vector2i pos, Surface image)
         {
             ImageOperation operation = new(pos, image);
             EnqueueOperation(operation);
         }
 
-        internal void ClearRegion(Vector2i pos, Vector2i size)
+        public void ClearRegion(Vector2i pos, Vector2i size)
         {
             ClearRegionOperation operation = new(pos, size);
             EnqueueOperation(operation);

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

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

+ 26 - 0
src/PixiEditorPrototype/ViewModels/DocumentViewModel.cs

@@ -1,5 +1,6 @@
 using ChangeableDocument;
 using ChangeableDocument.Actions.Document;
+using ChangeableDocument.Actions.Drawing;
 using ChangeableDocument.Actions.Drawing.Rectangle;
 using ChangeableDocument.Actions.Drawing.Selection;
 using ChangeableDocument.Actions.Structure;
@@ -7,6 +8,8 @@ using ChangeableDocument.Actions.Undo;
 using ChunkyImageLib.DataHolders;
 using PixiEditorPrototype.Models;
 using SkiaSharp;
+using System;
+using System.Collections.Generic;
 using System.ComponentModel;
 using System.Windows;
 using System.Windows.Input;
@@ -48,6 +51,7 @@ namespace PixiEditorPrototype.ViewModels
         public RelayCommand? ChangeSelectedItemCommand { get; }
         public RelayCommand? ChangeActiveToolCommand { get; }
         public RelayCommand? ResizeCanvasCommand { get; }
+        public RelayCommand? CombineCommand { get; }
 
         public RelayCommand? MouseDownCommand { get; }
         public RelayCommand? MouseMoveCommand { get; }
@@ -86,6 +90,7 @@ namespace PixiEditorPrototype.ViewModels
             ChangeSelectedItemCommand = new RelayCommand(ChangeSelectedItem);
             ChangeActiveToolCommand = new RelayCommand(ChangeActiveTool);
             ResizeCanvasCommand = new RelayCommand(ResizeCanvas);
+            CombineCommand = new RelayCommand(Combine);
 
             MouseDownCommand = new RelayCommand(MouseDown);
             MouseMoveCommand = new RelayCommand(MouseMove);
@@ -201,11 +206,32 @@ namespace PixiEditorPrototype.ViewModels
         {
             ActionAccumulator.AddAction(new ResizeCanvas_Action(new(ResizeWidth, ResizeHeight)));
         }
+
         private void ChangeSelectedItem(object? param)
         {
             SelectedStructureMember = (StructureMemberViewModel?)((RoutedPropertyChangedEventArgs<object>?)param)?.NewValue;
         }
 
+        private void Combine(object? param)
+        {
+            if (SelectedStructureMember == null)
+                return;
+            HashSet<Guid> selected = new();
+            AddSelectedMembers(StructureRoot, selected);
+            ActionAccumulator.AddAction(new CombineStructureMembersOnto_Action(SelectedStructureMember.GuidValue, selected));
+        }
+
+        private void AddSelectedMembers(FolderViewModel folder, HashSet<Guid> collection)
+        {
+            foreach (var child in folder.Children)
+            {
+                if (child.IsSelected)
+                    collection.Add(child.GuidValue);
+                if (child is FolderViewModel innerFolder)
+                    AddSelectedMembers(innerFolder, collection);
+            }
+        }
+
         private void ChangeActiveTool(object? param)
         {
             if (param == null)

+ 2 - 0
src/PixiEditorPrototype/ViewModels/StructureMemberViewModel.cs

@@ -23,6 +23,8 @@ namespace PixiEditorPrototype.ViewModels
             set => Document.ActionAccumulator.AddAction(new SetStructureMemberVisibility_Action(value, member.GuidValue));
         }
 
+        public bool IsSelected { get; set; }
+
         public float Opacity
         {
             get => member.Opacity;

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

@@ -22,8 +22,10 @@
                     <Button Margin="5" Command="{Binding CreateNewFolderCommand}" Width="80">New Folder</Button>
                     <Button Margin="5" Command="{Binding DeleteStructureMemberCommand}" Width="80">Delete</Button>
                 </StackPanel>
-                <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" HorizontalAlignment="Center">
+                <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"
                             Value="{Binding SelectedStructureMember.Opacity, Mode=OneWay}">
                         <i:Interaction.Behaviors>
                             <behaviors:SliderUpdateBehavior
@@ -32,7 +34,7 @@
                                 ValueFromSlider="{Binding ElementName=opacitySlider, Path=Value}"/>
                         </i:Interaction.Behaviors>
                     </Slider>
-                    <TextBlock Text="{Binding SelectedStructureMember.Opacity, StringFormat=N2}" TextAlignment="Center" d:Text="1.00" Width="30"></TextBlock>
+                    <TextBlock Text="{Binding SelectedStructureMember.Opacity, StringFormat=N2}" VerticalAlignment="Center" TextAlignment="Center" d:Text="1.00" Width="30"></TextBlock>
                 </StackPanel>
                 <TreeView ItemsSource="{Binding StructureRoot.Children}">
                     <TreeView.ItemsPanel>
@@ -59,6 +61,7 @@
                     <TreeView.Resources>
                         <HierarchicalDataTemplate DataType="{x:Type vm:FolderViewModel}" ItemsSource="{Binding Children}">
                             <StackPanel Orientation="Horizontal" Width="150">
+                                <CheckBox VerticalAlignment="Center" Margin="3" IsChecked="{Binding IsSelected}"/>
                                 <StackPanel>
                                     <Button Width="18" Command="{Binding MoveUpCommand}">^</Button>
                                     <Button Width="18" Command="{Binding MoveDownCommand}">v</Button>
@@ -71,6 +74,7 @@
                         </HierarchicalDataTemplate>
                         <DataTemplate DataType="{x:Type vm:LayerViewModel}">
                             <StackPanel Orientation="Horizontal" Width="150">
+                                <CheckBox VerticalAlignment="Center" Margin="3" IsChecked="{Binding IsSelected}"/>
                                 <StackPanel>
                                     <Button Width="18" Command="{Binding MoveUpCommand}">^</Button>
                                     <Button Width="18" Command="{Binding MoveDownCommand}">v</Button>