Explorar o código

Merge pull request #352 from PixiEditor/v0.1.7.1-fixes

Fixes
Krzysztof Krysiński %!s(int64=3) %!d(string=hai) anos
pai
achega
0f6a398443
Modificáronse 44 ficheiros con 328 adicións e 179 borrados
  1. 1 1
      PixiEditor/Helpers/Behaviours/ClearFocusOnClickBehavior.cs
  2. 2 2
      PixiEditor/Helpers/Behaviours/GlobalShortcutFocusBehavior.cs
  3. 1 1
      PixiEditor/Helpers/Behaviours/TextBoxFocusBehavior.cs
  4. 4 4
      PixiEditor/Helpers/Extensions/ParserHelpers.cs
  5. 1 0
      PixiEditor/Models/Controllers/BitmapManager.cs
  6. 59 54
      PixiEditor/Models/Controllers/ClipboardController.cs
  7. 23 2
      PixiEditor/Models/Controllers/Shortcuts/ShortcutController.cs
  8. 6 1
      PixiEditor/Models/Controllers/SurfaceRenderer.cs
  9. 2 0
      PixiEditor/Models/DataHolders/Document/Document.Constructors.cs
  10. 30 13
      PixiEditor/Models/DataHolders/Document/Document.Layers.cs
  11. 16 9
      PixiEditor/Models/DataHolders/Document/Document.Operations.cs
  12. 1 5
      PixiEditor/Models/DataHolders/Document/Document.Preview.cs
  13. 14 6
      PixiEditor/Models/DataHolders/Document/Document.cs
  14. 20 0
      PixiEditor/Models/DataHolders/PixelSize.cs
  15. 2 2
      PixiEditor/Models/DataHolders/Selection.cs
  16. 5 1
      PixiEditor/Models/IO/Exporter.cs
  17. 5 9
      PixiEditor/Models/ImageManipulation/BitmapUtils.cs
  18. 11 6
      PixiEditor/Models/Layers/Layer.cs
  19. 4 4
      PixiEditor/Models/Layers/LayerHelper.cs
  20. 16 0
      PixiEditor/Models/Tools/BitmapOperationTool.cs
  21. 4 2
      PixiEditor/Models/Tools/Tools/CircleTool.cs
  22. 4 2
      PixiEditor/Models/Tools/Tools/LineTool.cs
  23. 1 1
      PixiEditor/Models/Tools/Tools/MagicWandTool.cs
  24. 5 2
      PixiEditor/Models/Tools/Tools/RectangleTool.cs
  25. 1 3
      PixiEditor/Models/Undo/StorageBasedChange.cs
  26. 2 1
      PixiEditor/ViewModels/SubViewModels/Main/ClipboardViewModel.cs
  27. 1 0
      PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs
  28. 4 2
      PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs
  29. 5 1
      PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs
  30. 3 0
      PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs
  31. 1 0
      PixiEditor/ViewModels/SubViewModels/Main/WindowViewModel.cs
  32. 1 1
      PixiEditor/ViewModels/ViewModelMain.cs
  33. 2 0
      PixiEditor/Views/MainWindow.xaml.cs
  34. 2 2
      PixiEditor/Views/UserControls/EditableTextBlock.xaml.cs
  35. 1 1
      PixiEditor/Views/UserControls/Layers/LayerGroupControl.xaml.cs
  36. 1 1
      PixiEditor/Views/UserControls/Layers/LayersManager.xaml
  37. 4 0
      PixiEditor/Views/UserControls/Layers/LayersManager.xaml.cs
  38. 1 1
      PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml.cs
  39. 25 3
      PixiEditor/Views/UserControls/PlainLayerView.xaml.cs
  40. 2 2
      PixiEditorTests/ModelsTests/ControllersTests/ShortcutControllerTests.cs
  41. 23 23
      PixiEditorTests/ModelsTests/DataHoldersTests/LayerStructureTests.cs
  42. 4 4
      PixiEditorTests/ModelsTests/DataHoldersTests/SelectionTests.cs
  43. 2 2
      PixiEditorTests/ModelsTests/UndoTests/StorageBasedChangeTests.cs
  44. 6 5
      azure-pipelines.yml

+ 1 - 1
PixiEditor/Helpers/Behaviours/ClearFocusOnClickBehavior.cs

@@ -20,7 +20,7 @@ namespace PixiEditor.Helpers.Behaviours
         private void AssociatedObject_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
         {
             AssociatedObject.Focus();
-            ShortcutController.BlockShortcutExecution = false;
+            ShortcutController.UnblockShortcutExecutionAll();
         }
     }
 }

+ 2 - 2
PixiEditor/Helpers/Behaviours/GlobalShortcutFocusBehavior.cs

@@ -27,12 +27,12 @@ namespace PixiEditor.Helpers.Behaviours
 
         private void AssociatedObject_LostKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
         {
-            ShortcutController.BlockShortcutExecution = false;
+            ShortcutController.UnblockShortcutExecution("GlobalShortcutFocusBehavior");
         }
 
         private void AssociatedObject_GotKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
         {
-            ShortcutController.BlockShortcutExecution = true;
+            ShortcutController.BlockShortcutExection("GlobalShortcutFocusBehavior");
         }
     }
 }

+ 1 - 1
PixiEditor/Helpers/Behaviours/TextBoxFocusBehavior.cs

@@ -85,7 +85,6 @@ namespace PixiEditor.Helpers.Behaviours
             }
 
             FocusManager.SetFocusedElement(scope, parent);
-            Keyboard.ClearFocus();
         }
 
         private void AssociatedObjectGotKeyboardFocus(
@@ -108,6 +107,7 @@ namespace PixiEditor.Helpers.Behaviours
         {
             if (DeselectOnFocusLoss)
                 AssociatedObject.Select(0, 0);
+            RemoveFocus();
         }
 
         private void AssociatedObjectPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)

+ 4 - 4
PixiEditor/Helpers/Extensions/ParserHelpers.cs

@@ -35,19 +35,19 @@ namespace PixiEditor.Helpers.Extensions
             WpfObservableRangeCollection<Layer> layers = new();
             foreach (SerializableLayer slayer in document)
             {
-                layers.Add(slayer.ToLayer());
+                layers.Add(slayer.ToLayer(document.Width, document.Height));
             }
 
             return layers;
         }
 
-        public static Layer ToLayer(this SerializableLayer layer)
+        public static Layer ToLayer(this SerializableLayer layer, int maxWidth, int maxHeight)
         {
-            return new Layer(layer.Name, new Surface(layer.ToSKImage()))
+            return new Layer(layer.Name, new Surface(layer.ToSKImage()), maxWidth, maxHeight)
             {
                 Opacity = layer.Opacity,
                 IsVisible = layer.IsVisible,
-                Offset = new(layer.OffsetX, layer.OffsetY, 0, 0)
+                Offset = new(layer.OffsetX, layer.OffsetY, 0, 0),
             };
         }
 

+ 1 - 0
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -34,6 +34,7 @@ namespace PixiEditor.Models.Controllers
                 activeDocument?.UpdatePreviewImage();
                 Document oldDoc = activeDocument;
                 activeDocument = value;
+                activeDocument?.UpdatePreviewImage();
                 RaisePropertyChanged(nameof(ActiveDocument));
                 ActiveWindow = value;
                 DocumentChanged?.Invoke(this, new DocumentChangedEventArgs(value, oldDoc));

+ 59 - 54
PixiEditor/Models/Controllers/ClipboardController.cs

@@ -15,6 +15,7 @@ using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.IO;
 using System.Linq;
+using System.Runtime.InteropServices;
 using System.Windows;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
@@ -111,34 +112,66 @@ namespace PixiEditor.Models.Controllers
         /// <summary>
         ///     Pastes image from clipboard into new layer.
         /// </summary>
-        public static void PasteFromClipboard()
+        public static void PasteFromClipboard(Document document)
         {
-            IEnumerable<Layer> layers;
+            Layer[] layers;
             try
             {
-                layers = GetLayersFromClipboard();
+                layers = GetLayersFromClipboard(document).ToArray();
             }
             catch
             {
                 return;
             }
 
-            Document activeDocument = ViewModelMain.Current.BitmapManager.ActiveDocument;
-            int startIndex = activeDocument.Layers.Count;
+            int resizedCount = 0;
+
+            Guid[] guids = layers.Select(x => x.GuidValue).ToArray();
+
+            var undoArgs = new object[] { guids, document, new PixelSize(document.Width, document.Height) };
 
             foreach (var layer in layers)
             {
-                activeDocument.Layers.Add(layer);
+                document.Layers.Add(layer);
+
+                if (layer.Width > document.Width || layer.Height > document.Height)
+                {
+                    ResizeToLayer(document, layer);
+                    resizedCount++;
+                }
+            }
+
+            StorageBasedChange change = new StorageBasedChange(document, layers, false);
+
+            document.UndoManager.AddUndoChange(change.ToChange(RemoveLayersProcess, undoArgs,
+                RestoreLayersProcess, new object[] { document }, "Paste from clipboard"));
+        }
+
+        private static void RemoveLayersProcess(object[] parameters)
+        {
+            if (parameters.Length > 2 && parameters[1] is Document document && parameters[2] is PixelSize size) 
+            {
+                document.RemoveLayersProcess(parameters);
+                document.ResizeCanvas(size.Width, size.Height, Enums.AnchorPoint.Left | Enums.AnchorPoint.Top, false);
             }
+        }
 
-            activeDocument.UndoManager.AddUndoChange(
-                new Change(RemoveLayersProcess, new object[] { startIndex }, AddLayersProcess, new object[] { layers }) { DisposeProcess = DisposeProcess });
+        private static void RestoreLayersProcess(Layer[] layers, UndoLayer[] data, object[] parameters)
+        {
+            if (parameters.Length > 0 && parameters[0] is Document document)
+            {
+                document.RestoreLayersProcess(layers, data);
+                foreach (var layer in layers)
+                {
+                    ResizeToLayer(document, layer);
+                }
+            }
         }
 
         /// <summary>
         ///     Gets image from clipboard, supported PNG, Dib and Bitmap.
         /// </summary>
-        private static IEnumerable<Layer> GetLayersFromClipboard()
+        private static IEnumerable<Layer> GetLayersFromClipboard(Document document)
         {
             DataObject data = ClipboardHelper.TryGetDataObject();
             if (data == null)
@@ -172,7 +205,7 @@ namespace PixiEditor.Models.Controllers
             else */
             if (TryFromSingleImage(data, out Surface singleImage))
             {
-                yield return new Layer("Image", singleImage);
+                yield return new Layer("Image", singleImage, document.Width, document.Height);
             }
             else if (data.GetDataPresent(DataFormats.FileDrop))
             {
@@ -187,13 +220,13 @@ namespace PixiEditor.Models.Controllers
 
                     try
                     {
-                        layer = new(Path.GetFileName(path), Importer.ImportSurface(path));
+                        layer = new(Path.GetFileName(path), Importer.ImportSurface(path), document.Width, document.Height);
                     }
                     catch (CorruptedFileException)
                     {
                     }
 
-                    yield return layer ?? new($"Corrupt {path}");
+                    yield return layer ?? new($"Corrupt {path}", document.Width, document.Height);
                 }
             }
             else
@@ -208,18 +241,24 @@ namespace PixiEditor.Models.Controllers
             if (dao == null)
                 return false;
 
-            var files = dao.GetFileDropList();
-
-            if (files != null)
+            try
             {
-                foreach (var file in files)
+                var files = dao.GetFileDropList();
+                if (files != null)
                 {
-                    if (Importer.IsSupportedFile(file))
+                    foreach (var file in files)
                     {
-                        return true;
+                        if (Importer.IsSupportedFile(file))
+                        {
+                            return true;
+                        }
                     }
                 }
             }
+            catch(COMException)
+            {
+                return false;
+            }
 
             return dao.GetDataPresent("PNG") || dao.GetDataPresent(DataFormats.Dib) ||
                    dao.GetDataPresent(DataFormats.Bitmap) || dao.GetDataPresent(DataFormats.FileDrop) ||
@@ -303,43 +342,9 @@ namespace PixiEditor.Models.Controllers
             return false;
         }
 
-        private static void RemoveLayersProcess(object[] parameters)
+        private static void ResizeToLayer(Document document, Layer layer)
         {
-            if (parameters.Length == 0 || parameters[0] is not int i)
-            {
-                return;
-            }
-
-            Document document = ViewModelMain.Current.BitmapManager.ActiveDocument;
-
-            while (i < document.Layers.Count)
-            {
-                document.RemoveLayer(i, false);
-            }
-        }
-
-        private static void AddLayersProcess(object[] parameters)
-        {
-            if (parameters.Length == 0 || parameters[0] is not IEnumerable<Layer> layers)
-            {
-                return;
-            }
-
-            foreach (var layer in layers)
-            {
-                ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.Add(layer);
-            }
-        }
-
-        private static void DisposeProcess(object[] rev, object[] proc)
-        {
-            if (proc[0] is IEnumerable<Layer> layers)
-            {
-                foreach (var layer in layers)
-                {
-                    layer.LayerBitmap.Dispose();
-                }
-            }
+            document.ResizeCanvas(Math.Max(document.Width, layer.Width), Math.Max(document.Height, layer.Height), Enums.AnchorPoint.Left | Enums.AnchorPoint.Top, false);
         }
     }
 }

+ 23 - 2
PixiEditor/Models/Controllers/Shortcuts/ShortcutController.cs

@@ -1,6 +1,8 @@
 using System;
+using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Linq;
+using System.Windows.Documents;
 using System.Windows.Input;
 
 namespace PixiEditor.Models.Controllers.Shortcuts
@@ -12,7 +14,9 @@ namespace PixiEditor.Models.Controllers.Shortcuts
             ShortcutGroups = new ObservableCollection<ShortcutGroup>(shortcutGroups);
         }
 
-        public static bool BlockShortcutExecution { get; set; }
+        public static bool ShortcutExecutionBlocked => _shortcutExecutionBlockers.Count > 0;
+
+        private static List<string> _shortcutExecutionBlockers = new List<string>();
 
         public ObservableCollection<ShortcutGroup> ShortcutGroups { get; init; }
 
@@ -20,6 +24,23 @@ namespace PixiEditor.Models.Controllers.Shortcuts
 
         public const Key MoveViewportToolTransientChangeKey = Key.Space;
 
+        public static void BlockShortcutExection(string blocker)
+        {
+            if (_shortcutExecutionBlockers.Contains(blocker)) return;
+            _shortcutExecutionBlockers.Add(blocker);
+        }
+
+        public static void UnblockShortcutExecution(string blocker)
+        {
+            if (!_shortcutExecutionBlockers.Contains(blocker)) return;
+            _shortcutExecutionBlockers.Remove(blocker);
+        }
+
+        public static void UnblockShortcutExecutionAll()
+        {
+            _shortcutExecutionBlockers.Clear();
+        }
+
         public Shortcut GetToolShortcut<T>()
         {
             return GetToolShortcut(typeof(T));
@@ -43,7 +64,7 @@ namespace PixiEditor.Models.Controllers.Shortcuts
 
         public void KeyPressed(Key key, ModifierKeys modifiers)
         {
-            if (!BlockShortcutExecution)
+            if (!ShortcutExecutionBlocked)
             {
                 Shortcut[] shortcuts = ShortcutGroups.SelectMany(x => x.Shortcuts).ToList().FindAll(x => x.ShortcutKey == key).ToArray();
                 if (shortcuts.Length < 1)

+ 6 - 1
PixiEditor/Models/Controllers/SurfaceRenderer.cs

@@ -28,11 +28,16 @@ namespace PixiEditor.Models.Controllers
         }
 
         public void Draw(Surface otherSurface, byte opacity)
+        {
+            Draw(otherSurface, opacity, new SKRectI(0, 0, otherSurface.Width, otherSurface.Height));
+        }
+
+        public void Draw(Surface otherSurface, byte opacity, SKRectI drawRect)
         {
             BackingSurface.Canvas.Clear();
             FinalBitmap.Lock();
             BlendingPaint.Color = new SKColor(255, 255, 255, opacity);
-            using (var snapshot = otherSurface.SkiaSurface.Snapshot())
+            using (var snapshot = otherSurface.SkiaSurface.Snapshot(drawRect))
                 BackingSurface.Canvas.DrawImage(snapshot, new SKRect(0, 0, FinalBitmap.PixelWidth, FinalBitmap.PixelHeight), HighQualityResizePaint);
             FinalBitmap.AddDirtyRect(new Int32Rect(0, 0, FinalBitmap.PixelWidth, FinalBitmap.PixelHeight));
             FinalBitmap.Unlock();

+ 2 - 0
PixiEditor/Models/DataHolders/Document/Document.Constructors.cs

@@ -1,5 +1,6 @@
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
 using PixiEditor.ViewModels;
 using System;
 using System.Linq;
@@ -31,6 +32,7 @@ namespace PixiEditor.Models.DataHolders
             LayerStructure.LayerStructureChanged += LayerStructure_LayerStructureChanged;
             DocumentSizeChanged += (sender, args) =>
             {
+                ActiveSelection = new Selection(Array.Empty<Coordinates>(), new PixelSize(args.NewWidth, args.NewHeight));
                 Renderer.Resize(args.NewWidth, args.NewHeight);
                 GeneratePreviewLayer();
             };

+ 30 - 13
PixiEditor/Models/DataHolders/Document/Document.Layers.cs

@@ -12,6 +12,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text.RegularExpressions;
 using System.Windows;
+using Windows.Graphics;
 
 namespace PixiEditor.Models.DataHolders
 {
@@ -221,9 +222,7 @@ namespace PixiEditor.Models.DataHolders
             if (width <= 0 || height <= 0)
                 throw new ArgumentException("Dimensions must be greater than 0");
 
-            layer = bitmap == null ? new Layer(name, width, height) : new Layer(name, bitmap);
-            layer.MaxHeight = Height;
-            layer.MaxWidth = Width;
+            layer = bitmap == null ? new Layer(name, width, height, Width, Height) : new Layer(name, bitmap, Width, Height);
 
             Layers.Add(layer);
 
@@ -441,7 +440,7 @@ namespace PixiEditor.Models.DataHolders
 
             var groupParent = LayerStructure.GetGroupByLayer(layersToMerge[^1].GuidValue);
 
-            Layer placeholderLayer = new("_placeholder");
+            Layer placeholderLayer = new("_placeholder", Width, Height);
             Layers.Insert(index, placeholderLayer);
             LayerStructure.AssignParent(placeholderLayer.GuidValue, groupParent?.GroupGuid);
 
@@ -449,6 +448,8 @@ namespace PixiEditor.Models.DataHolders
             {
                 Layer firstLayer = mergedLayer;
                 Layer secondLayer = layersToMerge[i + 1];
+                firstLayer.ClipCanvas();
+                secondLayer.ClipCanvas();
                 mergedLayer = firstLayer.MergeWith(secondLayer, name, Width, Height);
                 RemoveLayer(layersToMerge[i], false);
             }
@@ -471,7 +472,7 @@ namespace PixiEditor.Models.DataHolders
                 throw new ArgumentException("Not enough layers were provided to merge. Minimum amount is 2");
             }
 
-            IEnumerable<Layer> undoArgs = layersToMerge;
+            Layer[] undoArgs = layersToMerge;
 
             var oldLayerStructure = LayerStructure.CloneGroups();
 
@@ -602,7 +603,7 @@ namespace PixiEditor.Models.DataHolders
                 for (int i = 0; i < layers.Length; i++)
                 {
                     Layer layer = layers[i];
-                    layer.IsActive = true;
+                    layer.IsActive = data[i].IsActive;
                     Layers.Insert(data[i].LayerIndex, layer);
                 }
 
@@ -614,20 +615,36 @@ namespace PixiEditor.Models.DataHolders
         /// <summary>
         ///     Moves offsets of layers by specified vector.
         /// </summary>
-        private void MoveOffsets(IEnumerable<Layer> layers, Coordinates moveVector)
+        private void MoveOffsets(IList<Layer> layers, IList<Int32Rect> bounds, Coordinates moveVector)
         {
-            foreach (Layer layer in layers)
+            for (int i = 0; i < layers.Count; i++)
             {
+                Layer layer = layers[i];
+                Int32Rect bound = bounds[i];
                 Thickness offset = layer.Offset;
                 layer.Offset = new Thickness(offset.Left + moveVector.X, offset.Top + moveVector.Y, 0, 0);
+                if (!bound.IsEmpty && layer.Bounds != bound)
+                {
+                    layer.DynamicResizeAbsolute(bound);
+                }
+                else
+                {
+                    layer.ClipCanvas();
+                }
             }
         }
 
         private void MoveOffsetsProcess(object[] arguments)
         {
-            if (arguments.Length > 0 && arguments[0] is IEnumerable<Layer> layers && arguments[1] is Coordinates vector)
+            if (arguments.Length > 0 && arguments[0] is List<Guid> guids && arguments[1] is List<Int32Rect> bounds && arguments[2] is Coordinates vector)
             {
-                MoveOffsets(layers, vector);
+                List<Layer> layers = new List<Layer>(guids.Count);
+                foreach (Guid guid in guids)
+                {
+                    layers.Add(Layers.First(x => x.GuidValue == guid));
+                }
+
+                MoveOffsets(layers, bounds, vector);
             }
             else
             {
@@ -712,7 +729,7 @@ namespace PixiEditor.Models.DataHolders
             Renderer.ForceRerender();
         }
 
-        private void RestoreLayersProcess(Layer[] layers, UndoLayer[] layersData)
+        public void RestoreLayersProcess(Layer[] layers, UndoLayer[] layersData)
         {
             for (int i = 0; i < layers.Length; i++)
             {
@@ -726,7 +743,7 @@ namespace PixiEditor.Models.DataHolders
             }
         }
 
-        private void RemoveLayerProcess(object[] parameters)
+        public void RemoveLayerProcess(object[] parameters)
         {
             if (parameters is { Length: > 0 } && parameters[0] is Guid layerGuid)
             {
@@ -837,7 +854,7 @@ namespace PixiEditor.Models.DataHolders
             return sucess;
         }
 
-        private void RemoveLayersProcess(object[] parameters)
+        public void RemoveLayersProcess(object[] parameters)
         {
             if (parameters != null && parameters.Length > 0 && parameters[0] is IEnumerable<Guid> layerGuids)
             {

+ 16 - 9
PixiEditor/Models/DataHolders/Document/Document.Operations.cs

@@ -23,7 +23,7 @@ namespace PixiEditor.Models.DataHolders
         ///     Point that will act as "starting position" of resizing. Use pipe to connect horizontal and
         ///     vertical.
         /// </param>
-        public void ResizeCanvas(int width, int height, AnchorPoint anchor)
+        public void ResizeCanvas(int width, int height, AnchorPoint anchor, bool addToUndo = true)
         {
             int oldWidth = Width;
             int oldHeight = Height;
@@ -37,16 +37,23 @@ namespace PixiEditor.Models.DataHolders
             object[] processArgs = { newOffsets, width, height };
             object[] reverseProcessArgs = { Width, Height };
 
-            StorageBasedChange change = new(this, Layers);
+            if (addToUndo) 
+            { 
+                StorageBasedChange change = new(this, Layers);
+                ResizeCanvas(newOffsets, width, height);
 
-            ResizeCanvas(newOffsets, width, height);
+                UndoManager.AddUndoChange(change.ToChange(
+                    RestoreDocumentLayersProcess,
+                    reverseProcessArgs,
+                    ResizeCanvasProcess,
+                    processArgs,
+                    "Resize canvas"));
+            }
+            else
+            {
+                ResizeCanvas(newOffsets, width, height);
+            }
 
-            UndoManager.AddUndoChange(change.ToChange(
-                RestoreDocumentLayersProcess,
-                reverseProcessArgs,
-                ResizeCanvasProcess,
-                processArgs,
-                "Resize canvas"));
             DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
         }
 

+ 1 - 5
PixiEditor/Models/DataHolders/Document/Document.Preview.cs

@@ -43,11 +43,7 @@ namespace PixiEditor.Models.DataHolders
 
         public void GeneratePreviewLayer()
         {
-            PreviewLayer = new Layer("_previewLayer")
-            {
-                MaxWidth = Width,
-                MaxHeight = Height
-            };
+            PreviewLayer = new Layer("_previewLayer", Width, Height);
         }
     }
 }

+ 14 - 6
PixiEditor/Models/DataHolders/Document/Document.cs

@@ -61,7 +61,7 @@ namespace PixiEditor.Models.DataHolders
             }
         }
 
-        private Selection selection = new Selection(Array.Empty<Coordinates>());
+        private Selection selection;
 
         public Selection ActiveSelection
         {
@@ -156,9 +156,13 @@ namespace PixiEditor.Models.DataHolders
         /// </summary>
         public void CenterContent()
         {
-            var layersToCenter = Layers.Where(x => x.IsActive && LayerStructureUtils.GetFinalLayerIsVisible(x, LayerStructure));
-            if (!layersToCenter.Any())
+            var layersToCenter = Layers.Where(x => x.IsActive && LayerStructureUtils.GetFinalLayerIsVisible(x, LayerStructure)).ToList();
+            if (layersToCenter.Count == 0)
+            {
                 return;
+            }
+
+            List<Int32Rect> oldBounds = layersToCenter.Select(x => x.Bounds).ToList();
 
             DoubleCoords? maybePoints = ClipLayersAndGetEdgePoints(layersToCenter);
             if (maybePoints == null)
@@ -176,13 +180,17 @@ namespace PixiEditor.Models.DataHolders
                 new Coordinates(Width, Height));
             Coordinates moveVector = new Coordinates(documentCenter.X - contentCenter.X, documentCenter.Y - contentCenter.Y);
 
-            MoveOffsets(layersToCenter, moveVector);
+            List<Int32Rect> emptyBounds = Enumerable.Repeat(Int32Rect.Empty, layersToCenter.Count).ToList();
+
+            MoveOffsets(layersToCenter, emptyBounds, moveVector);
+
+            List <Guid> guids = layersToCenter.Select(x => x.GuidValue).ToList();
             UndoManager.AddUndoChange(
                 new Change(
                     MoveOffsetsProcess,
-                    new object[] { layersToCenter, new Coordinates(-moveVector.X, -moveVector.Y) },
+                    new object[] { guids, oldBounds, new Coordinates(-moveVector.X, -moveVector.Y) },
                     MoveOffsetsProcess,
-                    new object[] { layersToCenter, moveVector },
+                    new object[] { guids, emptyBounds, moveVector },
                     "Center content"));
         }
 

+ 20 - 0
PixiEditor/Models/DataHolders/PixelSize.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.Models.DataHolders
+{
+    public struct PixelSize
+    {
+        public int Width { get; set; }
+        public int Height { get; set; }
+
+        public PixelSize(int width, int height)
+        {
+            Width = width;
+            Height = height;
+        }
+    }
+}

+ 2 - 2
PixiEditor/Models/DataHolders/Selection.cs

@@ -17,10 +17,10 @@ namespace PixiEditor.Models.DataHolders
         private readonly SKColor selectionBlue;
         private Layer selectionLayer;
 
-        public Selection(Coordinates[] selectedPoints)
+        public Selection(Coordinates[] selectedPoints, PixelSize maxSize)
         {
             SelectedPoints = new ObservableCollection<Coordinates>(selectedPoints);
-            SelectionLayer = new Layer("_selectionLayer");
+            SelectionLayer = new Layer("_selectionLayer", maxSize.Width, maxSize.Height);
             selectionBlue = new SKColor(142, 202, 255, 255);
         }
 

+ 5 - 1
PixiEditor/Models/IO/Exporter.cs

@@ -56,10 +56,14 @@ namespace PixiEditor.Models.IO
                 var bitmap = document.Renderer.FinalBitmap;
                 SaveAs(encodersFactory[chosenFormat](), path, bitmap.PixelWidth, bitmap.PixelHeight, bitmap);
             }
-            else
+            else if(Directory.Exists(Path.GetDirectoryName(path)))
             {
                 Parser.PixiParser.Serialize(ParserHelpers.ToSerializable(document), path);
             }
+            else
+            {
+                SaveAsEditableFileWithDialog(document, out path);
+            }
 
             return path;
         }

+ 5 - 9
PixiEditor/Models/ImageManipulation/BitmapUtils.cs

@@ -185,10 +185,7 @@ namespace PixiEditor.Models.ImageManipulation
                 throw new ArgumentException("There were not the same amount of bitmaps and offsets", nameof(layerBitmaps));
             }
 
-            using Surface previewSurface = new Surface(maxPreviewWidth, maxPreviewHeight);
-            return previewSurface.ToWriteableBitmap();
-            /*
-            WriteableBitmap previewBitmap = BitmapFactory.New(width, height);
+            using Surface previewSurface = new Surface(width, height);
 
             var layerBitmapsEnumerator = layerBitmaps.GetEnumerator();
             var offsetsXEnumerator = offsetsX.GetEnumerator();
@@ -199,19 +196,18 @@ namespace PixiEditor.Models.ImageManipulation
                 offsetsXEnumerator.MoveNext();
                 offsetsYEnumerator.MoveNext();
 
-                var bitmap = layerBitmapsEnumerator.Current;
+                var bitmap = layerBitmapsEnumerator.Current.SkiaSurface.Snapshot();
                 var offsetX = offsetsXEnumerator.Current;
                 var offsetY = offsetsYEnumerator.Current;
 
-                previewBitmap.Blit(
-                    new Rect(offsetX, offsetY, bitmap.Width, bitmap.Height),
+                previewSurface.SkiaSurface.Canvas.DrawImage(
                     bitmap,
-                    new Rect(0, 0, bitmap.Width, bitmap.Height));
+                    offsetX, offsetY, Surface.BlendingPaint);
             }
 
             int newWidth = width >= height ? maxPreviewWidth : (int)Math.Ceiling(width / ((float)height / maxPreviewHeight));
             int newHeight = height > width ? maxPreviewHeight : (int)Math.Ceiling(height / ((float)width / maxPreviewWidth));
-            return previewBitmap.Redesize(newWidth, newHeight, WriteableBitmapExtensions.Interpolation.NearestNeighbor);*/
+            return previewSurface.ResizeNearestNeighbor(newWidth, newHeight).ToWriteableBitmap();
         }
     }
 }

+ 11 - 6
PixiEditor/Models/Layers/Layer.cs

@@ -31,32 +31,38 @@ namespace PixiEditor.Models.Layers
 
         private string layerHighlightColor = "#666666";
 
-        public Layer(string name)
+        public Layer(string name, int maxWidth, int maxHeight)
         {
             Name = name;
             LayerBitmap = new Surface(1, 1);
             IsReset = true;
             Width = 1;
             Height = 1;
+            MaxWidth = maxWidth;
+            MaxHeight = maxHeight;
             GuidValue = Guid.NewGuid();
         }
 
-        public Layer(string name, int width, int height)
+        public Layer(string name, int width, int height, int maxWidth, int maxHeight)
         {
             Name = name;
             LayerBitmap = new Surface(width, height);
             IsReset = true;
             Width = width;
             Height = height;
+            MaxWidth = maxWidth;
+            MaxHeight = maxHeight;
             GuidValue = Guid.NewGuid();
         }
 
-        public Layer(string name, Surface layerBitmap)
+        public Layer(string name, Surface layerBitmap, int maxWidth, int maxHeight)
         {
             Name = name;
             LayerBitmap = layerBitmap;
             Width = layerBitmap.Width;
             Height = layerBitmap.Height;
+            MaxWidth = maxWidth;
+            MaxHeight = maxHeight;
             GuidValue = Guid.NewGuid();
         }
 
@@ -209,6 +215,7 @@ namespace PixiEditor.Models.Layers
         public bool IsReset { get; private set; }
 
         public Int32Rect TightBounds => GetContentDimensions();
+        public Int32Rect Bounds => new Int32Rect(OffsetX, OffsetY, Width, Height);
 
         public event EventHandler<Int32Rect> LayerBitmapChanged;
 
@@ -245,12 +252,10 @@ namespace PixiEditor.Models.Layers
         /// </summary>
         public Layer Clone(bool generateNewGuid = false)
         {
-            return new Layer(Name, new Surface(LayerBitmap))
+            return new Layer(Name, new Surface(LayerBitmap), MaxWidth, MaxHeight)
             {
                 IsVisible = IsVisible,
                 Offset = Offset,
-                MaxHeight = MaxHeight,
-                MaxWidth = MaxWidth,
                 Opacity = Opacity,
                 IsActive = IsActive,
                 IsRenaming = IsRenaming,

+ 4 - 4
PixiEditor/Models/Layers/LayerHelper.cs

@@ -57,7 +57,7 @@ namespace PixiEditor.Models.Layers
             }
         }
 
-        public static Layer MergeWith(this Layer thisLayer, Layer otherLayer, string newName, Vector documentsSize)
+        public static Layer MergeWith(this Layer thisLayer, Layer otherLayer, string newName, PixelSize documentSize)
         {
             Int32Rect thisRect = new(thisLayer.OffsetX, thisLayer.OffsetY, thisLayer.Width, thisLayer.Height);
             Int32Rect otherRect = new(otherLayer.OffsetX, otherLayer.OffsetY, otherLayer.Width, otherLayer.Height);
@@ -66,9 +66,9 @@ namespace PixiEditor.Models.Layers
 
             Surface mergedBitmap = BitmapUtils.CombineLayers(combined, new Layer[] { thisLayer, otherLayer });
 
-            Layer mergedLayer = new Layer(newName, mergedBitmap)
+            Layer mergedLayer = new Layer(newName, mergedBitmap, documentSize.Width, documentSize.Height)
             {
-                Offset = new Thickness(combined.X, combined.Y, 0, 0)
+                Offset = new Thickness(combined.X, combined.Y, 0, 0),
             };
 
             return mergedLayer;
@@ -76,7 +76,7 @@ namespace PixiEditor.Models.Layers
 
         public static Layer MergeWith(this Layer thisLayer, Layer otherLayer, string newName, int documentWidth, int documentHeight)
         {
-            return MergeWith(thisLayer, otherLayer, newName, new Vector(documentWidth, documentHeight));
+            return MergeWith(thisLayer, otherLayer, newName, new PixelSize(documentWidth, documentHeight));
         }
     }
 }

+ 16 - 0
PixiEditor/Models/Tools/BitmapOperationTool.cs

@@ -19,6 +19,9 @@ namespace PixiEditor.Models.Tools
 
         public bool UseDocumentRectForUndo { get; set; } = false;
 
+        private SKRectI _rectReportedByTool;
+        private bool _customRectReported = false;
+
         private StorageBasedChange _change;
 
         public abstract void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color);
@@ -51,6 +54,12 @@ namespace PixiEditor.Models.Tools
             _change = null;
         }
 
+        protected void ReportCustomSessionRect(SKRectI rect)
+        {
+            _rectReportedByTool = rect;
+            _customRectReported = true;
+        }
+
         private void InitializeStorageBasedChange(SKRectI toolSessionRect)
         {
             Document doc = ViewModels.ViewModelMain.Current.BitmapManager.ActiveDocument;
@@ -72,6 +81,13 @@ namespace PixiEditor.Models.Tools
                 finalRect = SKRectI.Create(0, 0, doc.Width, doc.Height);
             }
 
+            if (_customRectReported)
+            {
+                _customRectReported = false;
+                finalRect = _rectReportedByTool;
+                _rectReportedByTool = SKRectI.Empty;
+            }
+
             _change = new StorageBasedChange(doc, new[] { new LayerChunk(doc.ActiveLayer, finalRect) });
         }
     }

+ 4 - 2
PixiEditor/Models/Tools/Tools/CircleTool.cs

@@ -41,10 +41,11 @@ namespace PixiEditor.Models.Tools.Tools
                 CoordinatesHelper.GetSquareCoordiantes(recordedMouseMovement) :
                 (recordedMouseMovement[0], recordedMouseMovement[^1]);
 
-            DrawEllipseFromCoordinates(previewLayer, start, end, color, fill, thickness, hasFillColor);
+            var dirtyRect = DrawEllipseFromCoordinates(previewLayer, start, end, color, fill, thickness, hasFillColor);
+            ReportCustomSessionRect(SKRectI.Create(dirtyRect.X, dirtyRect.Y, dirtyRect.Width, dirtyRect.Height));
         }
 
-        public static void DrawEllipseFromCoordinates(Layer layer, Coordinates first, Coordinates second,
+        public static Int32Rect DrawEllipseFromCoordinates(Layer layer, Coordinates first, Coordinates second,
             SKColor color, SKColor fillColor, int thickness, bool hasFillColor)
         {
             DoubleCoords corners = CalculateCoordinatesForShapeRotation(first, second);
@@ -71,6 +72,7 @@ namespace PixiEditor.Models.Tools.Tools
             }
 
             layer.InvokeLayerBitmapChange(dirtyRect);
+            return dirtyRect;
         }
 
         public static void DrawEllipseFill(Layer layer, SKColor color, List<Coordinates> outlineCoordinates)

+ 4 - 2
PixiEditor/Models/Tools/Tools/LineTool.cs

@@ -45,10 +45,11 @@ namespace PixiEditor.Models.Tools.Tools
             if (Session.IsShiftDown)
                 (start, end) = CoordinatesHelper.GetSquareOrLineCoordinates(recordedMouseMovement);
 
-            DrawLine(previewLayer, start, end, color, thickness, SKBlendMode.Src);
+            var dirtyRect = DrawLine(previewLayer, start, end, color, thickness, SKBlendMode.Src);
+            ReportCustomSessionRect(SKRectI.Create(dirtyRect.X, dirtyRect.Y, dirtyRect.Width, dirtyRect.Height));
         }
 
-        public void DrawLine(
+        public Int32Rect DrawLine(
             Layer layer, Coordinates start, Coordinates end, SKColor color, int thickness, SKBlendMode blendMode,
             SKStrokeCap strokeCap = SKStrokeCap.Butt)
         {
@@ -90,6 +91,7 @@ namespace PixiEditor.Models.Tools.Tools
             }
 
             layer.InvokeLayerBitmapChange(dirtyRect);
+            return dirtyRect;
         }
 
         private void DrawBresenhamLine(Layer layer, int x1, int y1, int x2, int y2, SKPaint paint)

+ 1 - 1
PixiEditor/Models/Tools/Tools/MagicWandTool.cs

@@ -88,7 +88,7 @@ namespace PixiEditor.Models.Tools.Tools
             cachedDocument ??= new Layer("_CombinedLayers", BitmapUtils.CombineLayers(
                 new Int32Rect(0, 0, document.Width, document.Height),
                 document.Layers,
-                document.LayerStructure));
+                document.LayerStructure), document.Width, document.Height);
         }
     }
 }

+ 5 - 2
PixiEditor/Models/Tools/Tools/RectangleTool.cs

@@ -38,10 +38,11 @@ namespace PixiEditor.Models.Tools.Tools
                 var temp = Toolbar.GetSetting<ColorSetting>("FillColor").Value;
                 fillColor = new SKColor(temp.R, temp.G, temp.B, temp.A);
             }
-            CreateRectangle(previewLayer, color, fillColor, recordedMouseMovement, thickness);
+            var dirtyRect = CreateRectangle(previewLayer, color, fillColor, recordedMouseMovement, thickness);
+            ReportCustomSessionRect(SKRectI.Create(dirtyRect.X, dirtyRect.Y, dirtyRect.Width, dirtyRect.Height));
         }
 
-        private void CreateRectangle(Layer layer, SKColor color, SKColor? fillColor, IReadOnlyList<Coordinates> coordinates, int thickness)
+        private Int32Rect CreateRectangle(Layer layer, SKColor color, SKColor? fillColor, IReadOnlyList<Coordinates> coordinates, int thickness)
         {
             var (start, end) = Session.IsShiftDown ? CoordinatesHelper.GetSquareCoordiantes(coordinates) : (coordinates[0], coordinates[^1]);
 
@@ -75,7 +76,9 @@ namespace PixiEditor.Models.Tools.Tools
                 paint.Color = color;
                 layer.LayerBitmap.SkiaSurface.Canvas.DrawRect(x, y, w, h, paint);
             }
+
             layer.InvokeLayerBitmapChange(dirtyRect);
+            return dirtyRect;
         }
     }
 }

+ 1 - 3
PixiEditor/Models/Undo/StorageBasedChange.cs

@@ -153,14 +153,12 @@ namespace PixiEditor.Models.Undo
             {
                 UndoLayer storedLayer = StoredLayers[i];
                 var bitmap = Importer.LoadFromGZippedBytes(storedLayer.StoredPngLayerName);
-                layers[i] = new Layer(storedLayer.Name, bitmap)
+                layers[i] = new Layer(storedLayer.Name, bitmap, storedLayer.MaxWidth, storedLayer.MaxHeight)
                 {
                     Width = storedLayer.Width,
                     Height = storedLayer.Height,
                     Offset = new Thickness(storedLayer.OffsetX, storedLayer.OffsetY, 0, 0),
                     Opacity = storedLayer.Opacity,
-                    MaxWidth = storedLayer.MaxWidth,
-                    MaxHeight = storedLayer.MaxHeight,
                     IsVisible = storedLayer.IsVisible,
                     IsActive = storedLayer.IsActive,
                     LayerHighlightColor = storedLayer.LayerHighlightColor

+ 2 - 1
PixiEditor/ViewModels/SubViewModels/Main/ClipboardViewModel.cs

@@ -45,7 +45,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public void Paste(object parameter)
         {
-            ClipboardController.PasteFromClipboard();
+            if (Owner.BitmapManager.ActiveDocument == null) return;
+            ClipboardController.PasteFromClipboard(Owner.BitmapManager.ActiveDocument);
         }
 
         private bool CanPaste(object property)

+ 1 - 0
PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs

@@ -148,6 +148,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 Owner.BitmapManager.ActiveDocument.AddNewLayer(
                     "Image",
                     Importer.ImportImage(dialog.FilePath, dialog.FileWidth, dialog.FileHeight));
+                Owner.BitmapManager.ActiveDocument.UpdatePreviewImage();
             }
         }
 

+ 4 - 2
PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs

@@ -65,7 +65,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 Owner.ShortcutController.LastShortcut.Command == Owner.ToolsSubViewModel.SelectToolCommand)
             {
                 restoreToolOnKeyUp = true;
-                ShortcutController.BlockShortcutExecution = true;
+                ShortcutController.BlockShortcutExection("ShortcutDown");
             }
 
             Owner.ShortcutController.KeyPressed(key, Keyboard.Modifiers);
@@ -96,7 +96,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             {
                 restoreToolOnKeyUp = false;
                 Owner.ToolsSubViewModel.SetActiveTool(Owner.ToolsSubViewModel.LastActionTool);
-                ShortcutController.BlockShortcutExecution = false;
+                ShortcutController.UnblockShortcutExecution("ShortcutDown");
             }
         }
 
@@ -132,6 +132,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             else if (Owner.ToolsSubViewModel.LastActionTool != null && Owner.ToolsSubViewModel.MoveToolIsTransient)
             {
                 Owner.ToolsSubViewModel.SetActiveTool(Owner.ToolsSubViewModel.LastActionTool);
+                restoreToolOnKeyUp = false;
+                ShortcutController.UnblockShortcutExecution("ShortcutDown");
             }
         }
 

+ 5 - 1
PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs

@@ -217,7 +217,11 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             if (doc.Layers.Count > 1)
             {
                 doc.MoveLayerInStructure(doc.Layers[^1].GuidValue, lastActiveLayerGuid, true);
-                Guid? parent = parameter is Layer or LayerStructureItemContainer ? activeLayerParent?.GroupGuid : activeLayerParent.Parent?.GroupGuid;
+                Guid? parent = null;
+                if (activeLayerParent != null)
+                {
+                    parent = parameter is Layer or LayerStructureItemContainer ? activeLayerParent?.GroupGuid : activeLayerParent.Parent?.GroupGuid;
+                }
                 doc.LayerStructure.AssignParent(doc.ActiveLayerGuid, parent);
                 doc.AddLayerStructureToUndo(oldGroups);
                 doc.UndoManager.SquashUndoChanges(3, "Add New Layer");

+ 3 - 0
PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs

@@ -96,6 +96,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             if (ActiveTool != null)
             {
                 activeTool.IsActive = false;
+                ActiveTool.Toolbar.SaveToolbarSettings();
             }
 
             LastActionTool = ActiveTool;
@@ -103,6 +104,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
             ActiveTool = tool;
 
+            ActiveTool.Toolbar.LoadSharedSettings();
+
             if (LastActionTool != ActiveTool)
                 SelectedToolChanged?.Invoke(this, new SelectedToolEventArgs(LastActionTool, ActiveTool));
 

+ 1 - 0
PixiEditor/ViewModels/SubViewModels/Main/WindowViewModel.cs

@@ -27,6 +27,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         private void ShowAvalonDockWindow(string id)
         {
+            if (MainWindow.Current?.LayoutRoot?.Manager?.Layout == null) return;
             var anchorables = new List<LayoutAnchorable>(MainWindow.Current.LayoutRoot.Manager.Layout
                     .Descendents()
                     .OfType<LayoutAnchorable>());

+ 1 - 1
PixiEditor/ViewModels/ViewModelMain.cs

@@ -362,7 +362,7 @@ namespace PixiEditor.ViewModels
 
         private void ActiveDocument_DocumentSizeChanged(object sender, DocumentSizeChangedEventArgs e)
         {
-            BitmapManager.ActiveDocument.ActiveSelection = new Selection(Array.Empty<Coordinates>());
+            BitmapManager.ActiveDocument.ActiveSelection = new Selection(Array.Empty<Coordinates>(), new PixelSize(e.NewWidth, e.NewHeight));
             BitmapManager.ActiveDocument.ChangesSaved = false;
             BitmapManager.ActiveDocument.CenterViewportTrigger.Execute(this, new Size(BitmapManager.ActiveDocument.Width, BitmapManager.ActiveDocument.Height));
         }

+ 2 - 0
PixiEditor/Views/MainWindow.xaml.cs

@@ -64,6 +64,8 @@ namespace PixiEditor
                 }
             });
 
+            Current = this;
+
             OnReleaseBuild();
         }
 

+ 2 - 2
PixiEditor/Views/UserControls/EditableTextBlock.xaml.cs

@@ -62,7 +62,7 @@ namespace PixiEditor.Views
 
         public void EnableEditing()
         {
-            ShortcutController.BlockShortcutExecution = true;
+            ShortcutController.BlockShortcutExection("EditableTextBlock");
             TextBlockVisibility = Visibility.Hidden;
             IsEditing = true;
             Dispatcher.BeginInvoke(
@@ -78,7 +78,7 @@ namespace PixiEditor.Views
         public void DisableEditing()
         {
             TextBlockVisibility = Visibility.Visible;
-            ShortcutController.BlockShortcutExecution = false;
+            ShortcutController.UnblockShortcutExecution("EditableTextBlock");
             IsEditing = false;
         }
 

+ 1 - 1
PixiEditor/Views/UserControls/Layers/LayerGroupControl.xaml.cs

@@ -201,7 +201,7 @@ namespace PixiEditor.Views.UserControls.Layers
         private void MoveGroupWithTempLayer(bool above, Models.DataHolders.Document document, Guid group, int indexOfReferenceLayer, bool putItInside) // ¯\_(ツ)_/¯
         {
             // The trick here is to insert a temp layer, assign group to it, then delete it.
-            Layer tempLayer = new("_temp");
+            Layer tempLayer = new("_temp", document.Width, document.Height);
             document.Layers.Insert(indexOfReferenceLayer, tempLayer);
 
             Guid? refGuid = putItInside ? GroupData?.GroupGuid : GroupData?.Parent?.GroupGuid;

+ 1 - 1
PixiEditor/Views/UserControls/Layers/LayersManager.xaml

@@ -45,7 +45,7 @@
                     </Button.Background>
                 </Button>
             </StackPanel>
-            <StackPanel Orientation="Horizontal" DockPanel.Dock="Right" Margin="0,0,10,0" HorizontalAlignment="Right">
+            <StackPanel Orientation="Horizontal" DockPanel.Dock="Right" Margin="0,0,10,0" HorizontalAlignment="Right" Focusable="True">
                 <Label Content="Opacity" Foreground="White" VerticalAlignment="Center"/>
                 <vws:NumberInput
                         Min="0" Max="100"

+ 4 - 0
PixiEditor/Views/UserControls/Layers/LayersManager.xaml.cs

@@ -1,4 +1,5 @@
 using PixiEditor.Models.Controllers;
+using PixiEditor.Models.Controllers.Shortcuts;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Undo;
@@ -367,6 +368,9 @@ namespace PixiEditor.Views.UserControls.Layers
             {
                 HandleGroupOpacityChange(groupControl.GroupData, val);
             }
+
+            ShortcutController.UnblockShortcutExecutionAll();
+            MoveFocus(new System.Windows.Input.TraversalRequest(System.Windows.Input.FocusNavigationDirection.Next));
         }
 
         private void HandleLayerOpacityChange(float val, Layer layer)

+ 1 - 1
PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml.cs

@@ -37,7 +37,7 @@ namespace PixiEditor.Views.UserControls.Layers
             if (path != null)
             {
                 var bitmap = Importer.ImportSurface(path);
-                Layer = new Layer("_Reference Layer", bitmap);
+                Layer = new Layer("_Reference Layer", bitmap, bitmap.Width, bitmap.Height);
             }
         }
 

+ 25 - 3
PixiEditor/Views/UserControls/PlainLayerView.xaml.cs

@@ -1,5 +1,6 @@
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Layers;
+using SkiaSharp;
 using System;
 using System.Windows;
 using System.Windows.Controls;
@@ -21,6 +22,8 @@ namespace PixiEditor.Views.UserControls
         private int prevLayerWidth = -1;
         private int prevLayerHeight = -1;
 
+        private Int32Rect _cachedTightBounds;
+
         public PlainLayerView()
         {
             InitializeComponent();
@@ -49,14 +52,17 @@ namespace PixiEditor.Views.UserControls
                     view.TargetLayer = null;
                     return;
                 }
+
                 layer.LayerBitmapChanged += view.OnLayerBitmapChanged;
-                view.Resize(layer.Width, layer.Height);
+                view._cachedTightBounds = GetTightBounds(layer);
+
+                view.Resize(view._cachedTightBounds.Width, view._cachedTightBounds.Height);
             }
         }
 
         private void Update()
         {
-            renderer.Draw(TargetLayer.LayerBitmap, (byte)(TargetLayer.Opacity * 255));
+            renderer.Draw(TargetLayer.LayerBitmap, (byte)(TargetLayer.Opacity * 255), SKRectI.Create(_cachedTightBounds.X, _cachedTightBounds.Y, _cachedTightBounds.Width, _cachedTightBounds.Height));
         }
 
         private void OnControlSizeChanged(object sender, SizeChangedEventArgs e)
@@ -96,16 +102,32 @@ namespace PixiEditor.Views.UserControls
 
         private void OnLayerBitmapChanged(object sender, Int32Rect e)
         {
-            if (TargetLayer.Width != prevLayerWidth || TargetLayer.Height != prevLayerHeight)
+            if (TargetLayer.Width != prevLayerWidth || TargetLayer.Height != prevLayerHeight 
+                || TargetLayer.OffsetX != _cachedTightBounds.X
+                || TargetLayer.OffsetY != _cachedTightBounds.Y)
             {
                 ResizeWithOptimized(RenderSize);
                 prevLayerWidth = TargetLayer.Width;
                 prevLayerHeight = TargetLayer.Height;
+                _cachedTightBounds = GetTightBounds(TargetLayer);
             }
             else
             {
                 Update();
             }
         }
+
+        private static Int32Rect GetTightBounds(Layer targetLayer)
+        {
+            //var tightBounds = targetLayer.TightBounds;
+            //if (tightBounds.IsEmpty)
+            //{
+            //    tightBounds = new Int32Rect(0, 0, targetLayer.Width, targetLayer.Height);
+            //}
+
+            var tightBounds = new Int32Rect(0, 0, targetLayer.Width, targetLayer.Height);
+
+            return tightBounds;
+        }
     }
 }

+ 2 - 2
PixiEditorTests/ModelsTests/ControllersTests/ShortcutControllerTests.cs

@@ -42,7 +42,7 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             RelayCommand shortcutCommand = new RelayCommand(arg => { result = (int)arg; });
 
             ShortcutController controller = GenerateStandardShortcutController(Key.A, ModifierKeys.None, shortcutCommand);
-            ShortcutController.BlockShortcutExecution = true;
+            ShortcutController.BlockShortcutExection("Test");
 
             controller.KeyPressed(Key.A, ModifierKeys.None);
             Assert.Equal(-1, result);
@@ -75,7 +75,7 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         {
             ShortcutController controller = new ShortcutController();
             controller.ShortcutGroups.Add(new ShortcutGroup(string.Empty, new Shortcut(shortcutKey, shortcutCommand, 0, modifiers)));
-            ShortcutController.BlockShortcutExecution = false;
+            ShortcutController.UnblockShortcutExecutionAll();
             return controller;
         }
     }

+ 23 - 23
PixiEditorTests/ModelsTests/DataHoldersTests/LayerStructureTests.cs

@@ -11,7 +11,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         public void TestThatAddNewGroupAddsNewGroup()
         {
             Document doc = new Document(1, 1);
-            doc.Layers.Add(new("_testLayer"));
+            doc.Layers.Add(new("_testLayer", 1, 1));
             var testLayer = doc.Layers[^1];
             doc.LayerStructure.AddNewGroup("test", testLayer.GuidValue);
 
@@ -24,7 +24,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         public void TestThatAddNewGroupAddsNewGroupAsASubgroup()
         {
             Document doc = new Document(1, 1);
-            doc.Layers.Add(new("_testLayer"));
+            doc.Layers.Add(new("_testLayer", 1, 1));
             var testLayer = doc.Layers[^1];
             doc.LayerStructure.AddNewGroup("test", testLayer.GuidValue);
             doc.LayerStructure.AddNewGroup("test1", testLayer.GuidValue);
@@ -41,8 +41,8 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         public void TestThatMoveGroupMovesSwapsLayerPlacesWithOtherGroup()
         {
             Document doc = new Document(1, 1);
-            doc.Layers.Add(new Layer("_testLayer"));
-            doc.Layers.Add(new Layer("_testLayer1"));
+            doc.Layers.Add(new Layer("_testLayer", 1, 1));
+            doc.Layers.Add(new Layer("_testLayer1", 1, 1));
             var testLayer = doc.Layers[0];
             var testLayer1 = doc.Layers[^1];
             doc.LayerStructure.AddNewGroup("test", testLayer.GuidValue);
@@ -61,7 +61,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         public void TestThatIsChildOfDetectsNestedGroupCorrectly()
         {
             LayerStructure ls = new LayerStructure(new Document(1, 1));
-            Layer testLayer = new Layer("tst");
+            Layer testLayer = new Layer("tst", 1, 1);
             ls.Groups.Add(new GuidStructureItem("group 1", testLayer.GuidValue));
             ls.Groups[0].Subgroups.Add(new GuidStructureItem("group 1 nested", testLayer.GuidValue));
 
@@ -73,7 +73,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         public void TestThatIsChildOfDetectsNestedLayersCorrectly()
         {
             var doc = new Document(1, 1);
-            doc.Layers.Add(new Layer("tst"));
+            doc.Layers.Add(new Layer("tst", 1, 1));
             Guid testLayerGuid = doc.Layers[0].GuidValue;
             LayerStructure ls = new LayerStructure(doc);
             ls.AddNewGroup("Test group", testLayerGuid);
@@ -87,7 +87,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         public void TestThatGroupContainsOnlyLayerDetectsOnlySingleLayerCorrectly()
         {
             var doc = new Document(1, 1);
-            doc.Layers.Add(new Layer("layer"));
+            doc.Layers.Add(new Layer("layer", 1, 1));
             var guid = doc.Layers[0].GuidValue;
             doc.LayerStructure.AddNewGroup("layer group", guid);
             Assert.True(LayerStructure.GroupContainsOnlyLayer(guid, doc.LayerStructure.Groups[0]));
@@ -97,7 +97,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         public void TestThatGroupContainsOnlyLayerDetectsOnlySingleLayerThatIsNested()
         {
             var doc = new Document(1, 1);
-            doc.Layers.Add(new Layer("layer"));
+            doc.Layers.Add(new Layer("layer", 1, 1));
             var guid = doc.Layers[0].GuidValue;
             doc.LayerStructure.AddNewGroup("layer group", guid);
             doc.LayerStructure.AddNewGroup("layer group nested", guid);
@@ -109,8 +109,8 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         public void TestThatCloneReturnsSameLayerStructure()
         {
             Document doc = new(1, 1);
-            doc.Layers.Add(new("Test"));
-            doc.Layers.Add(new("Test2"));
+            doc.Layers.Add(new("Test", 1, 1));
+            doc.Layers.Add(new("Test2", 1, 1));
             LayerStructure structure = new(doc);
             structure.AddNewGroup("Test group", doc.Layers[0].GuidValue);
 
@@ -125,7 +125,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         public void TestThatGetGroupByGuidReturnsNullForNonExistingGroup()
         {
             Document doc = new(1, 1);
-            doc.Layers.Add(new("Test"));
+            doc.Layers.Add(new("Test", 1, 1));
 
             Assert.Null(doc.LayerStructure.GetGroupByGuid(null));
             Assert.Null(doc.LayerStructure.GetGroupByGuid(Guid.NewGuid()));
@@ -135,7 +135,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         public void TestThatGetGroupByGuidReturnsGroupCorrectly()
         {
             Document doc = new(1, 1);
-            doc.Layers.Add(new("Test"));
+            doc.Layers.Add(new("Test", 1, 1));
             var group = doc.LayerStructure.AddNewGroup("Test group", doc.Layers[0].GuidValue);
 
             Assert.Equal(group.GroupGuid, doc.LayerStructure.GetGroupByGuid(group.GroupGuid).GroupGuid);
@@ -145,7 +145,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         public void TestThatPreMoveReassignBoundsMakesNestedGroupEmptyAndRemovesItAndParent()
         {
             Document doc = new(1, 1);
-            doc.Layers.Add(new("Test"));
+            doc.Layers.Add(new("Test", 1, 1));
             doc.LayerStructure.AddNewGroup("Test group", doc.Layers[0].GuidValue);
             var group1 = doc.LayerStructure.AddNewGroup("Test group nested", doc.Layers[0].GuidValue);
 
@@ -158,11 +158,11 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         public void TestThatPostMoveReassignBoundsAssignsNewLayerToGroup()
         {
             Document doc = new(1, 1);
-            doc.Layers.Add(new("Test"));
+            doc.Layers.Add(new("Test", 1, 1));
             doc.LayerStructure.AddNewGroup("Test group", doc.Layers[0].GuidValue);
             var group1 = doc.LayerStructure.AddNewGroup("Test group nested", doc.Layers[0].GuidValue);
 
-            doc.Layers.Add(new("Test 1"));
+            doc.Layers.Add(new("Test 1", 1, 1));
 
             var firstLayer = doc.Layers[0];
             var layer = doc.Layers[^1];
@@ -181,13 +181,13 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         public void TestThatAssignParentAssignsParent()
         {
             Document doc = new(1, 1);
-            doc.Layers.Add(new Layer("Test"));
+            doc.Layers.Add(new Layer("Test", 1, 1));
 
             var firstLayer = doc.Layers[0];
 
             doc.LayerStructure.AddNewGroup("Test group", doc.Layers[0].GuidValue);
 
-            doc.Layers.Add(new Layer("Test 1"));
+            doc.Layers.Add(new Layer("Test 1", 1, 1));
 
             var layer = doc.Layers[^1];
 
@@ -201,13 +201,13 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         public void TestThatAssignParentDeAssignsParentOnNull()
         {
             Document doc = new(1, 1);
-            doc.Layers.Add(new Layer("Test"));
+            doc.Layers.Add(new Layer("Test", 1, 1));
 
             var firstLayer = doc.Layers[0];
 
             doc.LayerStructure.AddNewGroup("Test group", doc.Layers[0].GuidValue);
 
-            doc.Layers.Add(new Layer("Test 1"));
+            doc.Layers.Add(new Layer("Test 1", 1, 1));
 
             var layer = doc.Layers[^1];
 
@@ -222,10 +222,10 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         public void TestThatGetGroupLayersReturnsAllLayersInGroup()
         {
             Document doc = new(1, 1);
-            doc.Layers.Add(new Layer("Test"));
-            doc.Layers.Add(new Layer("Test 1"));
-            doc.Layers.Add(new Layer("Test 2"));
-            doc.Layers.Add(new Layer("Test 3"));
+            doc.Layers.Add(new Layer("Test", 1, 1));
+            doc.Layers.Add(new Layer("Test 1", 1, 1));
+            doc.Layers.Add(new Layer("Test 2", 1, 1));
+            doc.Layers.Add(new Layer("Test 3", 1, 1));
             doc.LayerStructure.AddNewGroup("Test group", doc.Layers[0].GuidValue);
 
             doc.LayerStructure.AssignParent(doc.Layers[1].GuidValue, doc.LayerStructure.Groups[0].GroupGuid);

+ 4 - 4
PixiEditorTests/ModelsTests/DataHoldersTests/SelectionTests.cs

@@ -13,7 +13,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         public void TestThatSetSelectionNewSetsCorrectSelection()
         {
-            Selection selection = new Selection(Array.Empty<Coordinates>());
+            Selection selection = new Selection(Array.Empty<Coordinates>(), new (10, 10));
             Coordinates[] points = { new Coordinates(0, 0), new Coordinates(1, 1) };
 
             selection.SetSelection(points, SelectionType.New);
@@ -25,7 +25,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         public void TestThatSetSelectionAddSetsCorrectSelection()
         {
-            Selection selection = new Selection(Array.Empty<Coordinates>());
+            Selection selection = new Selection(Array.Empty<Coordinates>(), new PixelSize(10, 10));
             Coordinates[] points = { new Coordinates(0, 0), new Coordinates(1, 1) };
             Coordinates[] points2 = { new Coordinates(2, 4), new Coordinates(5, 7) };
 
@@ -38,7 +38,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         public void TestThatSetSelectionSubtractSetsCorrectSelection()
         {
-            Selection selection = new Selection(Array.Empty<Coordinates>());
+            Selection selection = new Selection(Array.Empty<Coordinates>(), new PixelSize(10, 10));
             Coordinates[] points = { new Coordinates(0, 0), new Coordinates(1, 1) };
             Coordinates[] points2 = { new Coordinates(1, 1) };
 
@@ -51,7 +51,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         public void TestClearWorks()
         {
-            Selection selection = new Selection(new[] { new Coordinates(0, 0), new Coordinates(5, 7) });
+            Selection selection = new Selection(new[] { new Coordinates(0, 0), new Coordinates(5, 7) }, new PixelSize(10, 10));
             selection.Clear();
 
             Assert.Empty(selection.SelectedPoints);

+ 2 - 2
PixiEditorTests/ModelsTests/UndoTests/StorageBasedChangeTests.cs

@@ -33,8 +33,8 @@ namespace PixiEditorTests.ModelsTests.UndoTests
             Random random = new Random();
             testDocument.Layers = new WpfObservableRangeCollection<Layer>()
             {
-                new Layer("Test layer" + random.Next(int.MinValue, int.MaxValue), testBitmap),
-                new Layer("Test layer 2" + random.Next(int.MinValue, int.MaxValue), testBitmap2) { Offset = new System.Windows.Thickness(2, 3, 0, 0) }
+                new Layer("Test layer" + random.Next(int.MinValue, int.MaxValue), testBitmap, testDocument.Width, testDocument.Height),
+                new Layer("Test layer 2" + random.Next(int.MinValue, int.MaxValue), testBitmap2, testDocument.Width, testDocument.Height) { Offset = new System.Windows.Thickness(2, 3, 0, 0) }
             };
             return testDocument;
         }

+ 6 - 5
azure-pipelines.yml

@@ -49,8 +49,9 @@ steps:
     workingDirectory: 'PixiEditorTests\'
   displayName: Collect code coverage
 
-- task: CmdLine@2
-  continueOnError: true
-  inputs:
-    script: codecov -f .\PixiEditorTests\PixiEditor_coverage.xml -t $(CODECOV_TOKEN)
-  displayName: Upload to Codecov.io
+  # Disiabled, because there is a problem with .NET 6 and OpenCover.Console.exe
+#- task: CmdLine@2
+#  continueOnError: true
+#  inputs:
+#    script: codecov -f .\PixiEditorTests\PixiEditor_coverage.xml -t $(CODECOV_TOKEN)
+#  displayName: Upload to Codecov.io