Browse Source

Merge branch 'master' into palettes

flabbet 3 years ago
parent
commit
099d945d6b
76 changed files with 1105 additions and 1763 deletions
  1. 0 19
      PixiEditor/Exceptions/ArrayLengthMismatchException.cs
  2. 0 106
      PixiEditor/Helpers/Behaviours/AllowableCharactersTextBoxBehavior.cs
  3. 8 1
      PixiEditor/Helpers/Behaviours/ClearFocusOnClickBehavior.cs
  4. 0 72
      PixiEditor/Helpers/Behaviours/HintTextBehavior.cs
  5. 1 9
      PixiEditor/Helpers/Behaviours/TextBoxFocusBehavior.cs
  6. 0 15
      PixiEditor/Helpers/Converters/BoolToBrushConverter.cs
  7. 0 34
      PixiEditor/Helpers/Converters/BrushTuple.cs
  8. 19 19
      PixiEditor/Helpers/Converters/EnumToStringConverter.cs
  9. 0 27
      PixiEditor/Helpers/Converters/FinalIsVisibleToVisiblityConverter.cs
  10. 0 27
      PixiEditor/Helpers/Converters/LayerToFinalOpacityConverter.cs
  11. 1 1
      PixiEditor/Helpers/Extensions/Int32RectHelper.cs
  12. 0 11
      PixiEditor/Helpers/Extensions/PixiParserHelper.cs
  13. 1 1
      PixiEditor/Helpers/Extensions/SKRectIHelper.cs
  14. 0 15
      PixiEditor/Helpers/Validators/SizeValidationRule.cs
  15. 0 21
      PixiEditor/Models/Controllers/BitmapChangedEventArgs.cs
  16. 1 1
      PixiEditor/Models/Controllers/BitmapOperationsUtility.cs
  17. 4 2
      PixiEditor/Models/Controllers/Shortcuts/ShortcutController.cs
  18. 1 23
      PixiEditor/Models/DataHolders/BitmapPixelChanges.cs
  19. 7 5
      PixiEditor/Models/DataHolders/CrashReport.cs
  20. 10 6
      PixiEditor/Models/DataHolders/Document/Document.Layers.cs
  21. 2 0
      PixiEditor/Models/DataHolders/Document/Document.Operations.cs
  22. 2 2
      PixiEditor/Models/DataHolders/Document/Document.cs
  23. 0 104
      PixiEditor/Models/DataHolders/ObservableCollection.cs
  24. 598 577
      PixiEditor/Models/DataHolders/RangeObservableCollection.cs
  25. 115 0
      PixiEditor/Models/DataHolders/WpfObservableRangeCollection.cs
  26. 1 17
      PixiEditor/Models/Dialogs/ConfirmationDialog.cs
  27. 2 2
      PixiEditor/Models/Dialogs/ResizeDocumentDialog.cs
  28. 0 8
      PixiEditor/Models/Enums/CapType.cs
  29. 0 27
      PixiEditor/Models/ImageManipulation/BitmapUtils.cs
  30. 0 99
      PixiEditor/Models/ImageManipulation/Morphology.cs
  31. 0 31
      PixiEditor/Models/ImageManipulation/Transform.cs
  32. 12 12
      PixiEditor/Models/Layers/LayerStructure.cs
  33. 0 19
      PixiEditor/Models/Position/MousePositionConverter.cs
  34. 1 1
      PixiEditor/PixiEditor.csproj
  35. 2 2
      PixiEditor/Properties/AssemblyInfo.cs
  36. 3 2
      PixiEditor/Styles/AvalonDock/Themes/Generic.xaml
  37. 1 0
      PixiEditor/Styles/ImageCheckBoxStyle.xaml
  38. 1 0
      PixiEditor/Styles/ListSwitchButtonStyle.xaml
  39. 14 0
      PixiEditor/Styles/ThemeStyle.xaml
  40. 1 0
      PixiEditor/ViewModels/CrashReportViewModel.cs
  41. 0 41
      PixiEditor/ViewModels/MenuButtonViewModel.cs
  42. 64 24
      PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs
  43. 17 7
      PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs
  44. 6 0
      PixiEditor/ViewModels/SubViewModels/Main/UndoViewModel.cs
  45. 1 1
      PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs
  46. 23 0
      PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/ToolsSettings.cs
  47. 2 0
      PixiEditor/ViewModels/SubViewModels/UserPreferences/SettingsViewModel.cs
  48. 4 1
      PixiEditor/ViewModels/ViewModelMain.cs
  49. 1 1
      PixiEditor/Views/Dialogs/CrashReportDialog.xaml
  50. 1 0
      PixiEditor/Views/Dialogs/ResizeCanvasPopup.xaml
  51. 1 0
      PixiEditor/Views/Dialogs/ResizeDocumentPopup.xaml
  52. 10 3
      PixiEditor/Views/Dialogs/SettingsWindow.xaml
  53. 4 10
      PixiEditor/Views/MainWindow.xaml
  54. 1 1
      PixiEditor/Views/UserControls/EditableTextBlock.xaml
  55. 8 7
      PixiEditor/Views/UserControls/Layers/LayersManager.xaml.cs
  56. 4 4
      PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml
  57. 0 57
      PixiEditor/Views/UserControls/MenuButton.xaml
  58. 0 46
      PixiEditor/Views/UserControls/MenuButton.xaml.cs
  59. 1 1
      PixiEditor/Views/UserControls/NumberInput.xaml
  60. 27 1
      PixiEditor/Views/UserControls/NumberInput.xaml.cs
  61. 0 16
      PixiEditor/Views/UserControls/Rotatebox.xaml
  62. 0 71
      PixiEditor/Views/UserControls/Rotatebox.xaml.cs
  63. 0 1
      PixiEditor/Views/UserControls/SizeInput.xaml
  64. 13 0
      PixiEditor/Views/UserControls/SizeInput.xaml.cs
  65. 5 0
      PixiEditor/Views/UserControls/SizePicker.xaml.cs
  66. 5 5
      PixiEditorTests/ModelsTests/ControllersTests/MockedSinglePixelPenTool.cs
  67. 40 32
      PixiEditorTests/ModelsTests/ControllersTests/UndoManagerTests.cs
  68. 1 24
      PixiEditorTests/ModelsTests/DataHoldersTests/BitmapPixelChangesTests.cs
  69. 12 13
      PixiEditorTests/ModelsTests/DataHoldersTests/DocumentLayersTests.cs
  70. 18 17
      PixiEditorTests/ModelsTests/DataHoldersTests/LayerStructureTests.cs
  71. 2 2
      PixiEditorTests/ModelsTests/DataHoldersTests/SelectionTests.cs
  72. 2 0
      PixiEditorTests/ModelsTests/DataHoldersTests/SurfaceTests.cs
  73. 0 39
      PixiEditorTests/ModelsTests/ImageManipulationTests/TransformTests.cs
  74. 5 1
      PixiEditorTests/ModelsTests/LayersTests/LayersTestHelper.cs
  75. 3 2
      PixiEditorTests/ModelsTests/PositionTests/CoordinatesTests.cs
  76. 16 17
      PixiEditorTests/ModelsTests/UndoTests/StorageBasedChangeTests.cs

+ 0 - 19
PixiEditor/Exceptions/ArrayLengthMismatchException.cs

@@ -1,19 +0,0 @@
-using System;
-
-namespace PixiEditor.Exceptions
-{
-    public class ArrayLengthMismatchException : Exception
-    {
-        public const string DefaultMessage = "First array length doesn't match second array length";
-
-        public ArrayLengthMismatchException()
-            : base(DefaultMessage)
-        {
-        }
-
-        public ArrayLengthMismatchException(string message)
-            : base(message)
-        {
-        }
-    }
-}

+ 0 - 106
PixiEditor/Helpers/Behaviours/AllowableCharactersTextBoxBehavior.cs

@@ -1,106 +0,0 @@
-using System;
-using System.Text.RegularExpressions;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Input;
-using System.Windows.Interactivity;
-
-namespace PixiEditor.Helpers.Behaviours
-{
-    public class AllowableCharactersTextBoxBehavior : Behavior<TextBox>
-    {
-        public static readonly DependencyProperty RegularExpressionProperty =
-            DependencyProperty.Register(
-                "RegularExpression",
-                typeof(string),
-                typeof(AllowableCharactersTextBoxBehavior),
-                new FrameworkPropertyMetadata(".*"));
-
-        public static readonly DependencyProperty MaxLengthProperty =
-            DependencyProperty.Register(
-                "MaxLength",
-                typeof(int),
-                typeof(AllowableCharactersTextBoxBehavior),
-                new FrameworkPropertyMetadata(int.MinValue));
-
-        public string RegularExpression
-        {
-            get => (string)GetValue(RegularExpressionProperty);
-            set => SetValue(RegularExpressionProperty, value);
-        }
-
-        public int MaxLength
-        {
-            get => (int)GetValue(MaxLengthProperty);
-            set => SetValue(MaxLengthProperty, value);
-        }
-
-        protected override void OnAttached()
-        {
-            base.OnAttached();
-            AssociatedObject.PreviewTextInput += OnPreviewTextInput;
-            DataObject.AddPastingHandler(AssociatedObject, OnPaste);
-        }
-
-        protected override void OnDetaching()
-        {
-            base.OnDetaching();
-            AssociatedObject.PreviewTextInput -= OnPreviewTextInput;
-            DataObject.RemovePastingHandler(AssociatedObject, OnPaste);
-        }
-
-        private void OnPaste(object sender, DataObjectPastingEventArgs e)
-        {
-            if (e.DataObject.GetDataPresent(DataFormats.Text))
-            {
-                string text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));
-
-                if (!IsValid(text, true))
-                {
-                    e.CancelCommand();
-                }
-            }
-            else
-            {
-                e.CancelCommand();
-            }
-        }
-
-        private void OnPreviewTextInput(object sender, TextCompositionEventArgs e)
-        {
-            e.Handled = !IsValid(e.Text, false);
-        }
-
-        private bool IsValid(string newText, bool paste)
-        {
-            return !ExceedsMaxLength(newText, paste) && Regex.IsMatch(newText, RegularExpression);
-        }
-
-        private bool ExceedsMaxLength(string newText, bool paste)
-        {
-            if (MaxLength == 0)
-            {
-                return false;
-            }
-
-            return LengthOfModifiedText(newText, paste) > MaxLength;
-        }
-
-        private int LengthOfModifiedText(string newText, bool paste)
-        {
-            int countOfSelectedChars = AssociatedObject.SelectedText.Length;
-            int caretIndex = AssociatedObject.CaretIndex;
-            string text = AssociatedObject.Text;
-
-            if (countOfSelectedChars > 0 || paste)
-            {
-                text = text.Remove(caretIndex, countOfSelectedChars);
-                return text.Length + newText.Length;
-            }
-
-            bool insert = Keyboard.IsKeyToggled(Key.Insert);
-
-            return insert && caretIndex < text.Length ? text.Length : text.Length + newText.Length;
-        }
-    }
-}

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

@@ -1,5 +1,6 @@
 using PixiEditor.Models.Controllers.Shortcuts;
 using PixiEditor.Models.Controllers.Shortcuts;
 using System.Windows;
 using System.Windows;
+using System.Windows.Input;
 using System.Windows.Interactivity;
 using System.Windows.Interactivity;
 
 
 namespace PixiEditor.Helpers.Behaviours
 namespace PixiEditor.Helpers.Behaviours
@@ -8,8 +9,14 @@ namespace PixiEditor.Helpers.Behaviours
     {
     {
         protected override void OnAttached()
         protected override void OnAttached()
         {
         {
-            AssociatedObject.MouseDown += AssociatedObject_MouseDown;
             base.OnAttached();
             base.OnAttached();
+            AssociatedObject.MouseDown += AssociatedObject_MouseDown;
+            AssociatedObject.LostKeyboardFocus += AssociatedObject_LostKeyboardFocus;
+        }
+
+        private void AssociatedObject_LostKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
+        {
+            
         }
         }
 
 
         protected override void OnDetaching()
         protected override void OnDetaching()

+ 0 - 72
PixiEditor/Helpers/Behaviours/HintTextBehavior.cs

@@ -1,72 +0,0 @@
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Interactivity;
-using System.Windows.Media;
-
-namespace PixiEditor.Helpers.Behaviours
-{
-    internal class HintTextBehavior : Behavior<TextBox>
-    {
-        // Using a DependencyProperty as the backing store for Hint.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty HintProperty =
-            DependencyProperty.Register(
-                "Hint",
-                typeof(string),
-                typeof(HintTextBehavior),
-                new PropertyMetadata(string.Empty));
-
-        private Brush textColor;
-
-        public string Hint
-        {
-            get => (string)GetValue(HintProperty);
-            set => SetValue(HintProperty, value);
-        }
-
-        protected override void OnAttached()
-        {
-            base.OnAttached();
-            AssociatedObject.GotFocus += AssociatedObject_GotFocus;
-            AssociatedObject.LostFocus += AssociatedObject_LostFocus;
-            textColor = AssociatedObject.Foreground;
-            SetHint(true);
-        }
-
-        protected override void OnDetaching()
-        {
-            base.OnDetaching();
-            AssociatedObject.LostFocus -= AssociatedObject_LostFocus;
-            AssociatedObject.GotFocus -= AssociatedObject_GotFocus;
-        }
-
-        private void AssociatedObject_LostFocus(object sender, RoutedEventArgs e)
-        {
-            if (string.IsNullOrEmpty(AssociatedObject.Text))
-            {
-                SetHint(true);
-            }
-        }
-
-        private void AssociatedObject_GotFocus(object sender, RoutedEventArgs e)
-        {
-            if (AssociatedObject.Text == Hint)
-            {
-                SetHint(false);
-            }
-        }
-
-        private void SetHint(bool active)
-        {
-            if (active)
-            {
-                AssociatedObject.Foreground = (SolidColorBrush)new BrushConverter().ConvertFromString("#7B7B7B");
-                AssociatedObject.Text = Hint;
-            }
-            else
-            {
-                AssociatedObject.Text = string.Empty;
-                AssociatedObject.Foreground = textColor;
-            }
-        }
-    }
-}

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

@@ -76,15 +76,7 @@ namespace PixiEditor.Helpers.Behaviours
 
 
         private void RemoveFocus()
         private void RemoveFocus()
         {
         {
-            DependencyObject scope = FocusManager.GetFocusScope(AssociatedObject);
-            FrameworkElement parent = (FrameworkElement)AssociatedObject.Parent;
-
-            while (parent != null && parent is IInputElement element && !element.Focusable)
-            {
-                parent = (FrameworkElement)parent.Parent;
-            }
-
-            FocusManager.SetFocusedElement(scope, parent);
+            MainWindow.Current.mainGrid.Focus();
         }
         }
 
 
         private void AssociatedObjectGotKeyboardFocus(
         private void AssociatedObjectGotKeyboardFocus(

+ 0 - 15
PixiEditor/Helpers/Converters/BoolToBrushConverter.cs

@@ -1,15 +0,0 @@
-using System;
-using System.Globalization;
-
-namespace PixiEditor.Helpers.Converters
-{
-    public class BoolToBrushConverter
-        : SingleInstanceConverter<BoolToBrushConverter>
-    {
-        public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            BrushTuple tuple = (BrushTuple)parameter;
-            return (bool)value ? tuple.FirstBrush : tuple.SecondBrush;
-        }
-    }
-}

+ 0 - 34
PixiEditor/Helpers/Converters/BrushTuple.cs

@@ -1,34 +0,0 @@
-using System;
-using System.Runtime.CompilerServices;
-using System.Windows.Media;
-
-namespace PixiEditor.Helpers.Converters
-{
-    public class BrushTuple : NotifyableObject, ITuple
-    {
-        public object this[int index] => index switch
-        {
-            0 => FirstBrush,
-            1 => SecondBrush,
-            _ => throw new ArgumentOutOfRangeException(nameof(index))
-        };
-
-        private Brush item1;
-
-        public Brush FirstBrush
-        {
-            get => item1;
-            set => SetProperty(ref item1, value);
-        }
-
-        private Brush item2;
-
-        public Brush SecondBrush
-        {
-            get => item2;
-            set => SetProperty(ref item2, value);
-        }
-
-        public int Length => 2;
-    }
-}

+ 19 - 19
PixiEditor/Helpers/Converters/EnumToStringConverter.cs

@@ -3,27 +3,27 @@ using System;
 
 
 namespace PixiEditor.Helpers.Converters
 namespace PixiEditor.Helpers.Converters
 {
 {
-  internal class EnumToStringConverter : SingleInstanceConverter<EnumToStringConverter>
-  {
-    public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+    internal class EnumToStringConverter : SingleInstanceConverter<EnumToStringConverter>
     {
     {
-      try
-      {
-        var type = value.GetType();
-        if (type == typeof(SizeUnit))
+        public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
         {
         {
-          var valueCasted = (SizeUnit)value;
-          if (valueCasted == SizeUnit.Percentage)
-            return "%";
-          
-          return "px";
+            try
+            {
+                var type = value.GetType();
+                if (type == typeof(SizeUnit))
+                {
+                    var valueCasted = (SizeUnit)value;
+                    if (valueCasted == SizeUnit.Percentage)
+                        return "%";
+
+                    return "px";
+                }
+                return Enum.GetName((value.GetType()), value);
+            }
+            catch
+            {
+                return string.Empty;
+            }
         }
         }
-        return Enum.GetName((value.GetType()), value);
-      }
-      catch
-      {
-        return string.Empty;
-      }
     }
     }
-  }
 }
 }

+ 0 - 27
PixiEditor/Helpers/Converters/FinalIsVisibleToVisiblityConverter.cs

@@ -1,27 +0,0 @@
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.Layers;
-using PixiEditor.ViewModels;
-using System;
-using System.Globalization;
-using System.Windows;
-using System.Windows.Data;
-using System.Windows.Markup;
-
-namespace PixiEditor.Helpers.Converters
-{
-    public class FinalIsVisibleToVisiblityConverter
-        : SingleInstanceMultiValueConverter<FinalIsVisibleToVisiblityConverter>
-    {
-        public override object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
-        {
-            BitmapManager bitmapManager = ViewModelMain.Current?.BitmapManager;
-
-            return
-                (values[0] is not Layer layer ||
-                bitmapManager.ActiveDocument is null ||
-                bitmapManager.ActiveDocument.GetFinalLayerIsVisible(layer))
-                    ? Visibility.Visible
-                    : (object)Visibility.Collapsed;
-        }
-    }
-}

+ 0 - 27
PixiEditor/Helpers/Converters/LayerToFinalOpacityConverter.cs

@@ -1,27 +0,0 @@
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Layers.Utils;
-using PixiEditor.ViewModels;
-using System;
-using System.Globalization;
-
-namespace PixiEditor.Helpers.Converters
-{
-    public class LayerToFinalOpacityConverter
-        : SingleInstanceMultiValueConverter<LayerToFinalOpacityConverter>
-    {
-        public override object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
-        {
-            if (values.Length > 0 && values[0] is Layer layer && ViewModelMain.Current?.BitmapManager?.ActiveDocument != null)
-            {
-                return (double)LayerStructureUtils.GetFinalLayerOpacity(layer, ViewModelMain.Current.BitmapManager.ActiveDocument.LayerStructure);
-            }
-
-            return null;
-        }
-
-        public override object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
-        {
-            return null;
-        }
-    }
-}

+ 1 - 1
PixiEditor/Helpers/Extensions/Int32RectEx.cs → PixiEditor/Helpers/Extensions/Int32RectHelper.cs

@@ -4,7 +4,7 @@ using System.Windows;
 
 
 namespace PixiEditor.Helpers.Extensions
 namespace PixiEditor.Helpers.Extensions
 {
 {
-    public static class Int32RectEx
+    public static class Int32RectHelper
     {
     {
         public static Int32Rect Intersect(this Int32Rect rect, Int32Rect other)
         public static Int32Rect Intersect(this Int32Rect rect, Int32Rect other)
         {
         {

+ 0 - 11
PixiEditor/Helpers/Extensions/PixiParserHelper.cs

@@ -1,11 +0,0 @@
-using PixiEditor.Parser;
-using SkiaSharp;
-
-namespace PixiEditor.Helpers.Extensions
-{
-    public static class PixiParserHelper
-    {
-        public static SKRectI GetRect(this SerializableLayer layer) =>
-            SKRectI.Create(layer.OffsetX, layer.OffsetY, layer.Width, layer.Height);
-    }
-}

+ 1 - 1
PixiEditor/Helpers/Extensions/SKRectIEx.cs → PixiEditor/Helpers/Extensions/SKRectIHelper.cs

@@ -3,7 +3,7 @@ using System.Windows;
 
 
 namespace PixiEditor.Helpers.Extensions
 namespace PixiEditor.Helpers.Extensions
 {
 {
-    public static class SKRectIEx
+    public static class SKRectIHelper
     {
     {
         public static Int32Rect ToInt32Rect(this SKRectI rect)
         public static Int32Rect ToInt32Rect(this SKRectI rect)
         {
         {

+ 0 - 15
PixiEditor/Helpers/Validators/SizeValidationRule.cs

@@ -1,15 +0,0 @@
-using System.Globalization;
-using System.Windows.Controls;
-
-namespace PixiEditor.Helpers.Validators
-{
-    public class SizeValidationRule : ValidationRule
-    {
-        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
-        {
-            int i = int.Parse(((string)value).Split(' ')[0]);
-
-            return new ValidationResult(i > 0, null); // Size is greater than 0
-        }
-    }
-}

+ 0 - 21
PixiEditor/Models/Controllers/BitmapChangedEventArgs.cs

@@ -1,21 +0,0 @@
-using PixiEditor.Models.DataHolders;
-using System;
-
-namespace PixiEditor.Models.Controllers
-{
-    public class BitmapChangedEventArgs : EventArgs
-    {
-        public BitmapChangedEventArgs(BitmapPixelChanges pixelsChanged, BitmapPixelChanges oldPixelsValues, Guid changedLayerGuid)
-        {
-            PixelsChanged = pixelsChanged;
-            OldPixelsValues = oldPixelsValues;
-            ChangedLayerGuid = changedLayerGuid;
-        }
-
-        public BitmapPixelChanges PixelsChanged { get; set; }
-
-        public BitmapPixelChanges OldPixelsValues { get; set; }
-
-        public Guid ChangedLayerGuid { get; set; }
-    }
-}

+ 1 - 1
PixiEditor/Models/Controllers/BitmapOperationsUtility.cs

@@ -13,7 +13,7 @@ namespace PixiEditor.Models.Controllers
 {
 {
     public class BitmapOperationsUtility
     public class BitmapOperationsUtility
     {
     {
-        public event EventHandler<BitmapChangedEventArgs> BitmapChanged;
+        public event EventHandler BitmapChanged;
 
 
         public BitmapManager Manager { get; set; }
         public BitmapManager Manager { get; set; }
 
 

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

@@ -1,4 +1,6 @@
-using System;
+using PixiEditor.Models.Tools;
+using PixiEditor.Models.Tools.Tools;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Collections.ObjectModel;
 using System.Linq;
 using System.Linq;
@@ -22,7 +24,7 @@ namespace PixiEditor.Models.Controllers.Shortcuts
 
 
         public Shortcut LastShortcut { get; private set; }
         public Shortcut LastShortcut { get; private set; }
 
 
-        public const Key MoveViewportToolTransientChangeKey = Key.Space;
+        public Dictionary<Key, Tool> TransientShortcuts { get; set; } = new Dictionary<Key, Tool>();
 
 
         public static void BlockShortcutExection(string blocker)
         public static void BlockShortcutExection(string blocker)
         {
         {

+ 1 - 23
PixiEditor/Models/DataHolders/BitmapPixelChanges.cs

@@ -1,5 +1,4 @@
-using PixiEditor.Exceptions;
-using PixiEditor.Helpers.Extensions;
+using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
 using SkiaSharp;
 using SkiaSharp;
 using System;
 using System;
@@ -69,27 +68,6 @@ namespace PixiEditor.Models.DataHolders
             return CombineOverride(new[] { changes1, changes2 });
             return CombineOverride(new[] { changes1, changes2 });
         }
         }
 
 
-        /// <summary>
-        ///     Builds BitmapPixelChanges using 2 same-length enumerables of coordinates and colors.
-        /// </summary>
-        public static BitmapPixelChanges FromArrays(IEnumerable<Coordinates> coordinates, IEnumerable<SKColor> color)
-        {
-            Coordinates[] coordinateArray = coordinates.ToArray();
-            SKColor[] colorArray = color.ToArray();
-            if (coordinateArray.Length != colorArray.Length)
-            {
-                throw new ArrayLengthMismatchException();
-            }
-
-            Dictionary<Coordinates, SKColor> dict = new Dictionary<Coordinates, SKColor>();
-            for (int i = 0; i < coordinateArray.Length; i++)
-            {
-                dict.Add(coordinateArray[i], colorArray[i]);
-            }
-
-            return new BitmapPixelChanges(dict);
-        }
-
         public BitmapPixelChanges WithoutTransparentPixels()
         public BitmapPixelChanges WithoutTransparentPixels()
         {
         {
             return new BitmapPixelChanges(ChangedPixels.Where(x => x.Value.Alpha > 0).ToDictionary(y => y.Key, y => y.Value));
             return new BitmapPixelChanges(ChangedPixels.Where(x => x.Value.Alpha > 0).ToDictionary(y => y.Key, y => y.Value));

+ 7 - 5
PixiEditor/Models/DataHolders/CrashReport.cs

@@ -91,8 +91,9 @@ namespace PixiEditor.Models.DataHolders
 
 
         public int GetDocumentCount() => ZipFile.Entries.Where(x => x.FullName.EndsWith(".pixi")).Count();
         public int GetDocumentCount() => ZipFile.Entries.Where(x => x.FullName.EndsWith(".pixi")).Count();
 
 
-        public IEnumerable<Document> RecoverDocuments()
+        public List<Document> RecoverDocuments()
         {
         {
+            List<Document> documents = new();
             foreach (ZipArchiveEntry entry in ZipFile.Entries.Where(x => x.FullName.EndsWith(".pixi")))
             foreach (ZipArchiveEntry entry in ZipFile.Entries.Where(x => x.FullName.EndsWith(".pixi")))
             {
             {
                 using Stream stream = entry.Open();
                 using Stream stream = entry.Open();
@@ -109,8 +110,10 @@ namespace PixiEditor.Models.DataHolders
                     continue;
                     continue;
                 }
                 }
 
 
-                yield return document;
+                documents.Add(document);
             }
             }
+
+            return documents;
         }
         }
 
 
         public void Dispose()
         public void Dispose()
@@ -159,15 +162,14 @@ namespace PixiEditor.Models.DataHolders
                 try
                 try
                 {
                 {
                     string documentPath =
                     string documentPath =
-                        $"{(string.IsNullOrWhiteSpace(document.DocumentFilePath) ? "Unsaved" : Path.GetFileNameWithoutExtension(document.DocumentFilePath))}-{document.OpenedUTC}.pixi";
+                        $"{(string.IsNullOrWhiteSpace(document.DocumentFilePath) ? "Unsaved" : Path.GetFileNameWithoutExtension(document.DocumentFilePath))}-{document.OpenedUTC}.pixi".Replace(':', '_');
 
 
                     byte[] serialized = PixiParser.Serialize(document.ToSerializable());
                     byte[] serialized = PixiParser.Serialize(document.ToSerializable());
 
 
                     using Stream documentStream = archive.CreateEntry($"Documents/{documentPath}").Open();
                     using Stream documentStream = archive.CreateEntry($"Documents/{documentPath}").Open();
                     documentStream.Write(serialized);
                     documentStream.Write(serialized);
                 }
                 }
-                catch
-                { }
+                catch { }
             }
             }
         }
         }
 
 

+ 10 - 6
PixiEditor/Models/DataHolders/Document/Document.Layers.cs

@@ -492,6 +492,8 @@ namespace PixiEditor.Models.DataHolders
 
 
             UndoManager.SquashUndoChanges(2, "Undo merge layers", false);
             UndoManager.SquashUndoChanges(2, "Undo merge layers", false);
 
 
+            LayersChanged?.Invoke(this, new LayersChangedEventArgs(layer.GuidValue, LayerAction.Add));
+
             return layer;
             return layer;
         }
         }
 
 
@@ -535,13 +537,13 @@ namespace PixiEditor.Models.DataHolders
 
 
             var startGroup = LayerStructure.GetGroupByLayer(layerGuid);
             var startGroup = LayerStructure.GetGroupByLayer(layerGuid);
 
 
-            LayerStructure.PreMoveReassignBounds(new GroupData(startGroup?.GroupGuid), layerGuid);
+            LayerStructure.Unassign(new GroupData(startGroup?.GroupGuid), layerGuid);
 
 
             Layers.Move(Layers.IndexOf(Layers.First(x => x.GuidValue == layerGuid)), indexTo);
             Layers.Move(Layers.IndexOf(Layers.First(x => x.GuidValue == layerGuid)), indexTo);
 
 
             var newGroup = LayerStructure.GetGroupByLayer(layerAtOldIndex);
             var newGroup = LayerStructure.GetGroupByLayer(layerAtOldIndex);
 
 
-            LayerStructure.PostMoveReassignBounds(new GroupData(newGroup?.GroupGuid), layerGuid);
+            LayerStructure.Assign(new GroupData(newGroup?.GroupGuid), layerGuid);
 
 
             RaisePropertyChanged(nameof(LayerStructure));
             RaisePropertyChanged(nameof(LayerStructure));
         }
         }
@@ -591,6 +593,7 @@ namespace PixiEditor.Models.DataHolders
 
 
                 Layer layer = MergeLayers(layers, nameOfSecond, indexes[0]);
                 Layer layer = MergeLayers(layers, nameOfSecond, indexes[0]);
                 layer.ChangeGuid(mergedLayerGuid);
                 layer.ChangeGuid(mergedLayerGuid);
+                SetMainActiveLayer(Layers.IndexOf(layer));
             }
             }
         }
         }
 
 
@@ -679,7 +682,7 @@ namespace PixiEditor.Models.DataHolders
 
 
             LayerStructure.ReassignParent(group, referenceLayerGroup);
             LayerStructure.ReassignParent(group, referenceLayerGroup);
 
 
-            LayerStructure.PostMoveReassignBounds(new GroupData(group?.Parent?.GroupGuid), new GroupData(group?.GroupGuid));
+            LayerStructure.Assign(new GroupData(group?.Parent?.GroupGuid), new GroupData(group?.GroupGuid));
         }
         }
 
 
         private int CalculateNewIndex(int layerIndex, bool above, int oldIndex)
         private int CalculateNewIndex(int layerIndex, bool above, int oldIndex)
@@ -712,13 +715,13 @@ namespace PixiEditor.Models.DataHolders
 
 
             var startGroup = LayerStructure.GetGroupByLayer(layer);
             var startGroup = LayerStructure.GetGroupByLayer(layer);
 
 
-            LayerStructure.PreMoveReassignBounds(new GroupData(startGroup?.GroupGuid), layer);
+            LayerStructure.Unassign(new GroupData(startGroup?.GroupGuid), layer);
 
 
             Layers.Move(oldIndex, newIndex);
             Layers.Move(oldIndex, newIndex);
 
 
             var newFolder = LayerStructure.GetGroupByLayer(referenceLayer);
             var newFolder = LayerStructure.GetGroupByLayer(referenceLayer);
 
 
-            LayerStructure.PostMoveReassignBounds(new GroupData(newFolder?.GroupGuid), layer);
+            LayerStructure.Assign(new GroupData(newFolder?.GroupGuid), layer);
 
 
             if (Layers.IndexOf(ActiveLayer) == oldIndex)
             if (Layers.IndexOf(ActiveLayer) == oldIndex)
             {
             {
@@ -757,8 +760,9 @@ namespace PixiEditor.Models.DataHolders
 
 
                 if (layerGroup?.Parent != null && LayerStructure.GroupContainsOnlyLayer(layer.GuidValue, layerGroup))
                 if (layerGroup?.Parent != null && LayerStructure.GroupContainsOnlyLayer(layer.GuidValue, layerGroup))
                 {
                 {
-                    LayerStructure.PreMoveReassignBounds(new GroupData(layerGroup.Parent.GroupGuid), new GroupData(layerGroup.GroupGuid));
+                    LayerStructure.Unassign(new GroupData(layerGroup.Parent.GroupGuid), new GroupData(layerGroup.GroupGuid));
                 }
                 }
+
                 LayerStructure.AssignParent(Layers[index].GuidValue, null);
                 LayerStructure.AssignParent(Layers[index].GuidValue, null);
                 RemoveGroupsIfEmpty(layer, layerGroup);
                 RemoveGroupsIfEmpty(layer, layerGroup);
 
 

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

@@ -54,6 +54,8 @@ namespace PixiEditor.Models.DataHolders
                 ResizeCanvas(newOffsets, width, height);
                 ResizeCanvas(newOffsets, width, height);
             }
             }
 
 
+            if (oldWidth == Width && Height == oldHeight) return;
+
             DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
             DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
         }
         }
 
 

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

@@ -185,7 +185,7 @@ namespace PixiEditor.Models.DataHolders
 
 
             MoveOffsets(layersToCenter, emptyBounds, moveVector);
             MoveOffsets(layersToCenter, emptyBounds, moveVector);
 
 
-            List <Guid> guids = layersToCenter.Select(x => x.GuidValue).ToList();
+            List<Guid> guids = layersToCenter.Select(x => x.GuidValue).ToList();
             UndoManager.AddUndoChange(
             UndoManager.AddUndoChange(
                 new Change(
                 new Change(
                     MoveOffsetsProcess,
                     MoveOffsetsProcess,
@@ -208,7 +208,7 @@ namespace PixiEditor.Models.DataHolders
 
 
         private void SetAsActiveOnClick(object obj)
         private void SetAsActiveOnClick(object obj)
         {
         {
-            if (XamlAccesibleViewModel.BitmapManager.ActiveDocument != this)
+            if (XamlAccesibleViewModel?.BitmapManager?.ActiveDocument != this)
             {
             {
                 XamlAccesibleViewModel.BitmapManager.ActiveDocument = this;
                 XamlAccesibleViewModel.BitmapManager.ActiveDocument = this;
             }
             }

+ 0 - 104
PixiEditor/Models/DataHolders/ObservableCollection.cs

@@ -1,104 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Collections.Specialized;
-using System.Diagnostics;
-using System.Linq;
-using System.Reflection;
-using System.Windows.Data;
-
-namespace PixiEditor.Models.DataHolders
-{
-public class ObservableCollection<T> : RangeObservableCollection<T>
-{
-        public bool SuppressNotify { get; set; } = false;
-  DeferredEventsCollection _deferredEvents;
-
-  public ObservableCollection()
-  {
-  }
-
-  public ObservableCollection(IEnumerable<T> collection) : base(collection)
-  {
-  }
-
-  public ObservableCollection(List<T> list) : base(list)
-  {
-  }
-
-
-  /// <summary>
-  /// Raise CollectionChanged event to any listeners.
-  /// Properties/methods modifying this ObservableCollection will raise
-  /// a collection changed event through this virtual method.
-  /// </summary>
-  /// <remarks>
-  /// When overriding this method, either call its base implementation
-  /// or call <see cref="BlockReentrancy"/> to guard against reentrant collection changes.
-  /// </remarks>
-  protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
-  {
-            if (SuppressNotify) return;
-    var _deferredEvents = (ICollection<NotifyCollectionChangedEventArgs>) typeof(RangeObservableCollection<T>)
-      .GetField("_deferredEvents", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(this);
-    if (_deferredEvents != null)
-    {
-      _deferredEvents.Add(e);
-      return;
-    }
-
-    foreach (var handler in GetHandlers())
-      if (IsRange(e) && handler.Target is CollectionView cv)
-        cv.Refresh();
-      else
-        handler(this, e);
-  }
-
-  protected override IDisposable DeferEvents() => new DeferredEventsCollection(this);
-
-  bool IsRange(NotifyCollectionChangedEventArgs e) => e.NewItems?.Count > 1 || e.OldItems?.Count > 1;
-
-  IEnumerable<NotifyCollectionChangedEventHandler> GetHandlers()
-  {
-    var info = typeof(System.Collections.ObjectModel.ObservableCollection<T>).GetField(nameof(CollectionChanged),
-      BindingFlags.Instance | BindingFlags.NonPublic);
-    var @event = (MulticastDelegate) info.GetValue(this);
-    return @event?.GetInvocationList()
-             .Cast<NotifyCollectionChangedEventHandler>()
-             .Distinct()
-           ?? Enumerable.Empty<NotifyCollectionChangedEventHandler>();
-  }
-
-  class DeferredEventsCollection : List<NotifyCollectionChangedEventArgs>, IDisposable
-  {
-    private readonly ObservableCollection<T> _collection;
-
-    public DeferredEventsCollection(ObservableCollection<T> collection)
-    {
-      Debug.Assert(collection != null);
-      Debug.Assert(collection._deferredEvents == null);
-      _collection = collection;
-      _collection._deferredEvents = this;
-    }
-
-    public void Dispose()
-    {
-      _collection._deferredEvents = null;
-
-      var handlers = _collection
-        .GetHandlers()
-        .ToLookup(h => h.Target is CollectionView);
-
-      foreach (var handler in handlers[false])
-      foreach (var e in this)
-        handler(_collection, e);
-
-      foreach (var cv in handlers[true]
-                 .Select(h => h.Target)
-                 .Cast<CollectionView>()
-                 .Distinct())
-        cv.Refresh();
-    }
-  }
-}
-}

+ 598 - 577
PixiEditor/Models/DataHolders/RangeObservableCollection.cs

@@ -9,658 +9,679 @@ using System.Linq;
 
 
 namespace PixiEditor.Models.DataHolders
 namespace PixiEditor.Models.DataHolders
 {
 {
-  // Licensed to the .NET Foundation under one or more agreements.
-  // The .NET Foundation licenses this file to you under the MIT license.
-  // See the LICENSE file in the project root for more information.
-  /// <summary>
-  /// Implementation of a dynamic data collection based on generic Collection&lt;T&gt;,
-  /// implementing INotifyCollectionChanged to notify listeners
-  /// when items get added, removed or the whole list is refreshed.
-  /// </summary>
-  public class RangeObservableCollection<T> : System.Collections.ObjectModel.ObservableCollection<T>
-  {
-    //------------------------------------------------------
-    //
-    //  Private Fields
-    //
-    //------------------------------------------------------
-
-    #region Private Fields    
-    [NonSerialized]
-    private DeferredEventsCollection? _deferredEvents;
-    #endregion Private Fields
-
-
-    //------------------------------------------------------
-    //
-    //  Constructors
-    //
-    //------------------------------------------------------
-
-    #region Constructors
-    /// <summary>
-    /// Initializes a new instance of ObservableCollection that is empty and has default initial capacity.
-    /// </summary>
-    public RangeObservableCollection() { }
+    // Licensed to the .NET Foundation under one or more agreements.
+    // The .NET Foundation licenses this file to you under the MIT license.
+    // See the LICENSE file in the project root for more information.
 
 
     /// <summary>
     /// <summary>
-    /// Initializes a new instance of the ObservableCollection class that contains
-    /// elements copied from the specified collection and has sufficient capacity
-    /// to accommodate the number of elements copied.
+    /// Implementation of a dynamic data collection based on generic Collection&lt;T&gt;,
+    /// implementing INotifyCollectionChanged to notify listeners
+    /// when items get added, removed or the whole list is refreshed.
     /// </summary>
     /// </summary>
-    /// <param name="collection">The collection whose elements are copied to the new list.</param>
-    /// <remarks>
-    /// The elements are copied onto the ObservableCollection in the
-    /// same order they are read by the enumerator of the collection.
-    /// </remarks>
-    /// <exception cref="ArgumentNullException"> collection is a null reference </exception>
-    public RangeObservableCollection(IEnumerable<T> collection) : base(collection) { }
+    public class RangeObservableCollection<T> : ObservableCollection<T>
+    {
+        //------------------------------------------------------
+        //
+        //  Private Fields
+        //
+        //------------------------------------------------------
+
+        #region Private Fields    
+        [NonSerialized]
+        private DeferredEventsCollection _deferredEvents;
+        #endregion Private Fields
+
+
+        //------------------------------------------------------
+        //
+        //  Constructors
+        //
+        //------------------------------------------------------
+
+        #region Constructors
+
+        /// <summary>
+        /// Initializes a new instance of ObservableCollection that is empty and has default initial capacity.
+        /// </summary>
+        public RangeObservableCollection() { }
+
+        /// <summary>
+        /// Initializes a new instance of the ObservableCollection class that contains
+        /// elements copied from the specified collection and has sufficient capacity
+        /// to accommodate the number of elements copied.
+        /// </summary>
+        /// <param name="collection">The collection whose elements are copied to the new list.</param>
+        /// <remarks>
+        /// The elements are copied onto the ObservableCollection in the
+        /// same order they are read by the enumerator of the collection.
+        /// </remarks>
+        /// <exception cref="ArgumentNullException"> collection is a null reference </exception>
+        public RangeObservableCollection(IEnumerable<T> collection) : base(collection) { }
+
+        /// <summary>
+        /// Initializes a new instance of the ObservableCollection class
+        /// that contains elements copied from the specified list
+        /// </summary>
+        /// <param name="list">The list whose elements are copied to the new list.</param>
+        /// <remarks>
+        /// The elements are copied onto the ObservableCollection in the
+        /// same order they are read by the enumerator of the list.
+        /// </remarks>
+        /// <exception cref="ArgumentNullException"> list is a null reference </exception>
+        public RangeObservableCollection(List<T> list) : base(list) { }
+
+        #endregion Constructors
+
+        //------------------------------------------------------
+        //
+        //  Public Properties
+        //
+        //------------------------------------------------------
+
+        #region Public Properties
+#pragma warning disable SA1306 // Field names should begin with lower-case letter
+        EqualityComparer<T> _Comparer;
+#pragma warning restore SA1306 // Field names should begin with lower-case letter
+        public EqualityComparer<T> Comparer
+        {
+            get => _Comparer ??= EqualityComparer<T>.Default;
+            private set => _Comparer = value;
+        }
 
 
-    /// <summary>
-    /// Initializes a new instance of the ObservableCollection class
-    /// that contains elements copied from the specified list
-    /// </summary>
-    /// <param name="list">The list whose elements are copied to the new list.</param>
-    /// <remarks>
-    /// The elements are copied onto the ObservableCollection in the
-    /// same order they are read by the enumerator of the list.
-    /// </remarks>
-    /// <exception cref="ArgumentNullException"> list is a null reference </exception>
-    public RangeObservableCollection(List<T> list) : base(list) { }
+        /// <summary>
+        /// Gets or sets a value indicating whether this collection acts as a <see cref="HashSet{T}"/>,
+        /// disallowing duplicate items, based on <see cref="Comparer"/>.
+        /// This might indeed consume background performance, but in the other hand,
+        /// it will pay off in UI performance as less required UI updates are required.
+        /// </summary>
+        public bool AllowDuplicates { get; set; } = true;
+
+        #endregion Public Properties
+
+        //------------------------------------------------------
+        //
+        //  Public Methods
+        //
+        //------------------------------------------------------
+
+        #region Public Methods
+
+        /// <summary>
+        /// Adds the elements of the specified collection to the end of the <see cref="ObservableCollection{T}"/>.
+        /// </summary>
+        /// <param name="collection">
+        /// The collection whose elements should be added to the end of the <see cref="ObservableCollection{T}"/>.
+        /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.
+        /// </param>
+        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
+        public void AddRange(IEnumerable<T> collection)
+        {
+            InsertRange(Count, collection);
+        }
 
 
-    #endregion Constructors
+        /// <summary>
+        /// Inserts the elements of a collection into the <see cref="ObservableCollection{T}"/> at the specified index.
+        /// </summary>
+        /// <param name="index">The zero-based index at which the new elements should be inserted.</param>
+        /// <param name="collection">The collection whose elements should be inserted into the List<T>.
+        /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.</param>                
+        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is not in the collection range.</exception>
+        public void InsertRange(int index, IEnumerable<T> collection)
+        {
+            if (collection == null)
+                throw new ArgumentNullException(nameof(collection));
+            if (index < 0)
+                throw new ArgumentOutOfRangeException(nameof(index));
+            if (index > Count)
+                throw new ArgumentOutOfRangeException(nameof(index));
+
+            if (!AllowDuplicates)
+            {
+                collection =
+                  collection
+                  .Distinct(Comparer)
+                  .Where(item => !Items.Contains(item, Comparer))
+                  .ToList();
+            }
+
+            if (collection is ICollection<T> countable)
+            {
+                if (countable.Count == 0)
+                    return;
+            }
+            else if (!collection.Any())
+            {
+                return;
+            }
 
 
-    //------------------------------------------------------
-    //
-    //  Public Properties
-    //
-    //------------------------------------------------------
+            CheckReentrancy();
 
 
-    #region Public Properties
-    EqualityComparer<T>? _Comparer;
-    public EqualityComparer<T> Comparer
-    {
-      get => _Comparer ??= EqualityComparer<T>.Default;
-      private set => _Comparer = value;
-    }
+            //expand the following couple of lines when adding more constructors.
+            var target = (List<T>)Items;
+            target.InsertRange(index, collection);
 
 
-    /// <summary>
-    /// Gets or sets a value indicating whether this collection acts as a <see cref="HashSet{T}"/>,
-    /// disallowing duplicate items, based on <see cref="Comparer"/>.
-    /// This might indeed consume background performance, but in the other hand,
-    /// it will pay off in UI performance as less required UI updates are required.
-    /// </summary>
-    public bool AllowDuplicates { get; set; } = true;
+            OnEssentialPropertiesChanged();
 
 
-    #endregion Public Properties
+            if (!(collection is IList list))
+                list = new List<T>(collection);
 
 
-    //------------------------------------------------------
-    //
-    //  Public Methods
-    //
-    //------------------------------------------------------
+            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list, index));
+        }
 
 
-    #region Public Methods
 
 
-    /// <summary>
-    /// Adds the elements of the specified collection to the end of the <see cref="System.Collections.ObjectModel.ObservableCollection{T}"/>.
-    /// </summary>
-    /// <param name="collection">
-    /// The collection whose elements should be added to the end of the <see cref="System.Collections.ObjectModel.ObservableCollection{T}"/>.
-    /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.
-    /// </param>
-    /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
-    public void AddRange(IEnumerable<T> collection)
-    {
-      InsertRange(Count, collection);
-    }
+        /// <summary> 
+        /// Removes the first occurence of each item in the specified collection from the <see cref="ObservableCollection{T}"/>.
+        /// </summary>
+        /// <param name="collection">The items to remove.</param>        
+        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
+        public void RemoveRange(IEnumerable<T> collection)
+        {
+            if (collection == null)
+                throw new ArgumentNullException(nameof(collection));
 
 
-    /// <summary>
-    /// Inserts the elements of a collection into the <see cref="System.Collections.ObjectModel.ObservableCollection{T}"/> at the specified index.
-    /// </summary>
-    /// <param name="index">The zero-based index at which the new elements should be inserted.</param>
-    /// <param name="collection">The collection whose elements should be inserted into the List<T>.
-    /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.</param>                
-    /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
-    /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is not in the collection range.</exception>
-    public void InsertRange(int index, IEnumerable<T> collection)
-    {
-      if (collection == null)
-        throw new ArgumentNullException(nameof(collection));
-      if (index < 0)
-        throw new ArgumentOutOfRangeException(nameof(index));
-      if (index > Count)
-        throw new ArgumentOutOfRangeException(nameof(index));
-
-      if (!AllowDuplicates)
-        collection =
-          collection
-          .Distinct(Comparer)
-          .Where(item => !Items.Contains(item, Comparer))
-          .ToList();
-
-      if (collection is ICollection<T> countable)
-      {
-        if (countable.Count == 0)
-          return;
-      }
-      else if (!collection.Any())
-        return;
-
-      CheckReentrancy();
-
-      //expand the following couple of lines when adding more constructors.
-      var target = (List<T>)Items;
-      target.InsertRange(index, collection);
-
-      OnEssentialPropertiesChanged();
-
-      if (!(collection is IList list))
-        list = new List<T>(collection);
-
-      OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list, index));
-    }
+            if (Count == 0)
+            {
+                return;
+            }
+            else if (collection is ICollection<T> countable)
+            {
+                if (countable.Count == 0)
+                {
+                    return;
+                }
+                else if (countable.Count == 1)
+                {
+                    using (IEnumerator<T> enumerator = countable.GetEnumerator())
+                    {
+                        enumerator.MoveNext();
+                        Remove(enumerator.Current);
+                        return;
+                    }
+                }
+            }
+            else if (!collection.Any())
+            {
+                return;
+            }
 
 
+            CheckReentrancy();
 
 
-    /// <summary> 
-    /// Removes the first occurence of each item in the specified collection from the <see cref="System.Collections.ObjectModel.ObservableCollection{T}"/>.
-    /// </summary>
-    /// <param name="collection">The items to remove.</param>        
-    /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
-    public void RemoveRange(IEnumerable<T> collection)
-    {
-      if (collection == null)
-        throw new ArgumentNullException(nameof(collection));
-
-      if (Count == 0)
-        return;
-      else if (collection is ICollection<T> countable)
-      {
-        if (countable.Count == 0)
-          return;
-        else if (countable.Count == 1)
-          using (IEnumerator<T> enumerator = countable.GetEnumerator())
-          {
-            enumerator.MoveNext();
-            Remove(enumerator.Current);
-            return;
-          }
-      }
-      else if (!collection.Any())
-        return;
-
-      CheckReentrancy();
-
-      var clusters = new Dictionary<int, List<T>>();
-      var lastIndex = -1;
-      List<T>? lastCluster = null;
-      foreach (T item in collection)
-      {
-        var index = IndexOf(item);
-        if (index < 0)
-          continue;
-
-        Items.RemoveAt(index);
-
-        if (lastIndex == index && lastCluster != null)
-          lastCluster.Add(item);
-        else
-          clusters[lastIndex = index] = lastCluster = new List<T> { item };
-      }
-
-      OnEssentialPropertiesChanged();
-
-      if (Count == 0)
-        OnCollectionReset();
-      else
-        foreach (KeyValuePair<int, List<T>> cluster in clusters)
-          OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster.Value, cluster.Key));
+            var clusters = new Dictionary<int, List<T>>();
+            var lastIndex = -1;
+            List<T> lastCluster = null;
+            foreach (T item in collection)
+            {
+                var index = IndexOf(item);
+                if (index < 0)
+                    continue;
 
 
-    }
+                Items.RemoveAt(index);
 
 
-    /// <summary>
-    /// Iterates over the collection and removes all items that satisfy the specified match.
-    /// </summary>
-    /// <remarks>The complexity is O(n).</remarks>
-    /// <param name="match"></param>
-    /// <returns>Returns the number of elements that where </returns>
-    /// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
-    public int RemoveAll(Predicate<T> match)
-    {
-      return RemoveAll(0, Count, match);
-    }
+                if (lastIndex == index && lastCluster != null)
+                    lastCluster.Add(item);
+                else
+                    clusters[lastIndex = index] = lastCluster = new List<T> { item };
+            }
 
 
-    /// <summary>
-    /// Iterates over the specified range within the collection and removes all items that satisfy the specified match.
-    /// </summary>
-    /// <remarks>The complexity is O(n).</remarks>
-    /// <param name="index">The index of where to start performing the search.</param>
-    /// <param name="count">The number of items to iterate on.</param>
-    /// <param name="match"></param>
-    /// <returns>Returns the number of elements that where </returns>
-    /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
-    /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception>
-    /// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
-    public int RemoveAll(int index, int count, Predicate<T> match)
-    {
-      if (index < 0)
-        throw new ArgumentOutOfRangeException(nameof(index));
-      if (count < 0)
-        throw new ArgumentOutOfRangeException(nameof(count));
-      if (index + count > Count)
-        throw new ArgumentOutOfRangeException(nameof(index));
-      if (match == null)
-        throw new ArgumentNullException(nameof(match));
-
-      if (Count == 0)
-        return 0;
-
-      List<T>? cluster = null;
-      var clusterIndex = -1;
-      var removedCount = 0;
-
-      using (BlockReentrancy())
-      using (DeferEvents())
-      {
-        for (var i = 0; i < count; i++, index++)
-        {
-          T item = Items[index];
-          if (match(item))
-          {
-            Items.RemoveAt(index);
-            removedCount++;
+            OnEssentialPropertiesChanged();
 
 
-            if (clusterIndex == index)
+            if (Count == 0)
             {
             {
-              Debug.Assert(cluster != null);
-              cluster!.Add(item);
+                OnCollectionReset();
             }
             }
             else
             else
             {
             {
-              cluster = new List<T> { item };
-              clusterIndex = index;
+                foreach (KeyValuePair<int, List<T>> cluster in clusters)
+                    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster.Value, cluster.Key));
             }
             }
 
 
-            index--;
-          }
-          else if (clusterIndex > -1)
-          {
-            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster, clusterIndex));
-            clusterIndex = -1;
-            cluster = null;
-          }
         }
         }
 
 
-        if (clusterIndex > -1)
-          OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster, clusterIndex));
-      }
+        /// <summary>
+        /// Iterates over the collection and removes all items that satisfy the specified match.
+        /// </summary>
+        /// <remarks>The complexity is O(n).</remarks>
+        /// <returns>Returns the number of elements that where </returns>
+        /// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
+        public int RemoveAll(Predicate<T> match)
+        {
+            return RemoveAll(0, Count, match);
+        }
 
 
-      if (removedCount > 0)
-        OnEssentialPropertiesChanged();
+        /// <summary>
+        /// Iterates over the specified range within the collection and removes all items that satisfy the specified match.
+        /// </summary>
+        /// <remarks>The complexity is O(n).</remarks>
+        /// <param name="index">The index of where to start performing the search.</param>
+        /// <param name="count">The number of items to iterate on.</param>
+        /// <returns>Returns the number of elements that where </returns>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception>
+        /// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
+        public int RemoveAll(int index, int count, Predicate<T> match)
+        {
+            if (index < 0)
+                throw new ArgumentOutOfRangeException(nameof(index));
+            if (count < 0)
+                throw new ArgumentOutOfRangeException(nameof(count));
+            if (index + count > Count)
+                throw new ArgumentOutOfRangeException(nameof(index));
+            if (match == null)
+                throw new ArgumentNullException(nameof(match));
+
+            if (Count == 0)
+                return 0;
+
+            List<T> cluster = null;
+            var clusterIndex = -1;
+            var removedCount = 0;
+
+            using (BlockReentrancy())
+            using (DeferEvents())
+            {
+                for (var i = 0; i < count; i++, index++)
+                {
+                    T item = Items[index];
+                    if (match(item))
+                    {
+                        Items.RemoveAt(index);
+                        removedCount++;
+
+                        if (clusterIndex == index)
+                        {
+                            Debug.Assert(cluster != null);
+                            cluster!.Add(item);
+                        }
+                        else
+                        {
+                            cluster = new List<T> { item };
+                            clusterIndex = index;
+                        }
+
+                        index--;
+                    }
+                    else if (clusterIndex > -1)
+                    {
+                        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster, clusterIndex));
+                        clusterIndex = -1;
+                        cluster = null;
+                    }
+                }
+
+                if (clusterIndex > -1)
+                    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster, clusterIndex));
+            }
 
 
-      return removedCount;
-    }
+            if (removedCount > 0)
+                OnEssentialPropertiesChanged();
 
 
-    /// <summary>
-    /// Removes a range of elements from the <see cref="System.Collections.ObjectModel.ObservableCollection{T}"/>>.
-    /// </summary>
-    /// <param name="index">The zero-based starting index of the range of elements to remove.</param>
-    /// <param name="count">The number of elements to remove.</param>
-    /// <exception cref="ArgumentOutOfRangeException">The specified range is exceeding the collection.</exception>
-    public void RemoveRange(int index, int count)
-    {
-      if (index < 0)
-        throw new ArgumentOutOfRangeException(nameof(index));
-      if (count < 0)
-        throw new ArgumentOutOfRangeException(nameof(count));
-      if (index + count > Count)
-        throw new ArgumentOutOfRangeException(nameof(index));
+            return removedCount;
+        }
 
 
-      if (count == 0)
-        return;
+        /// <summary>
+        /// Removes a range of elements from the <see cref="ObservableCollection{T}"/>>.
+        /// </summary>
+        /// <param name="index">The zero-based starting index of the range of elements to remove.</param>
+        /// <param name="count">The number of elements to remove.</param>
+        /// <exception cref="ArgumentOutOfRangeException">The specified range is exceeding the collection.</exception>
+        public void RemoveRange(int index, int count)
+        {
+            if (index < 0)
+                throw new ArgumentOutOfRangeException(nameof(index));
+            if (count < 0)
+                throw new ArgumentOutOfRangeException(nameof(count));
+            if (index + count > Count)
+                throw new ArgumentOutOfRangeException(nameof(index));
 
 
-      if (count == 1)
-      {
-        RemoveItem(index);
-        return;
-      }
+            if (count == 0)
+                return;
 
 
-      //Items will always be List<T>, see constructors
-      var items = (List<T>)Items;
-      List<T> removedItems = items.GetRange(index, count);
+            if (count == 1)
+            {
+                RemoveItem(index);
+                return;
+            }
 
 
-      CheckReentrancy();
+            //Items will always be List<T>, see constructors
+            var items = (List<T>)Items;
+            List<T> removedItems = items.GetRange(index, count);
 
 
-      items.RemoveRange(index, count);
+            CheckReentrancy();
 
 
-      OnEssentialPropertiesChanged();
+            items.RemoveRange(index, count);
 
 
-      if (Count == 0)
-        OnCollectionReset();
-      else
-        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, index));
-    }
+            OnEssentialPropertiesChanged();
 
 
-    /// <summary> 
-    /// Clears the current collection and replaces it with the specified collection,
-    /// using <see cref="Comparer"/>.
-    /// </summary>             
-    /// <param name="collection">The items to fill the collection with, after clearing it.</param>
-    /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
-    public void ReplaceRange(IEnumerable<T> collection)
-    {
-      ReplaceRange(0, Count, collection);
-    }
+            if (Count == 0)
+                OnCollectionReset();
+            else
+                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, index));
+        }
 
 
-    /// <summary>
-    /// Removes the specified range and inserts the specified collection in its position, leaving equal items in equal positions intact.
-    /// </summary>
-    /// <param name="index">The index of where to start the replacement.</param>
-    /// <param name="count">The number of items to be replaced.</param>
-    /// <param name="collection">The collection to insert in that location.</param>
-    /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
-    /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception>
-    /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
-    /// <exception cref="ArgumentNullException"><paramref name="comparer"/> is null.</exception>
-    public void ReplaceRange(int index, int count, IEnumerable<T> collection)
-    {
-      if (index < 0)
-        throw new ArgumentOutOfRangeException(nameof(index));
-      if (count < 0)
-        throw new ArgumentOutOfRangeException(nameof(count));
-      if (index + count > Count)
-        throw new ArgumentOutOfRangeException(nameof(index));
-
-      if (collection == null)
-        throw new ArgumentNullException(nameof(collection));
-
-      if (!AllowDuplicates)
-        collection =
-          collection
-          .Distinct(Comparer)
-          .ToList();
-
-      if (collection is ICollection<T> countable)
-      {
-        if (countable.Count == 0)
+        /// <summary> 
+        /// Clears the current collection and replaces it with the specified collection,
+        /// using <see cref="Comparer"/>.
+        /// </summary>             
+        /// <param name="collection">The items to fill the collection with, after clearing it.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
+        public void ReplaceRange(IEnumerable<T> collection)
         {
         {
-          RemoveRange(index, count);
-          return;
+            ReplaceRange(0, Count, collection);
         }
         }
-      }
-      else if (!collection.Any())
-      {
-        RemoveRange(index, count);
-        return;
-      }
-
-      if (index + count == 0)
-      {
-        InsertRange(0, collection);
-        return;
-      }
-
-      if (!(collection is IList<T> list))
-        list = new List<T>(collection);
-
-      using (BlockReentrancy())
-      using (DeferEvents())
-      {
-        var rangeCount = index + count;
-        var addedCount = list.Count;
-
-        var changesMade = false;
-        List<T>?
-          newCluster = null,
-          oldCluster = null;
-
-
-        int i = index;
-        for (; i < rangeCount && i - index < addedCount; i++)
+
+        /// <summary>
+        /// Removes the specified range and inserts the specified collection in its position, leaving equal items in equal positions intact.
+        /// </summary>
+        /// <param name="index">The index of where to start the replacement.</param>
+        /// <param name="count">The number of items to be replaced.</param>
+        /// <param name="collection">The collection to insert in that location.</param>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception>
+        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
+        /// <exception cref="ArgumentNullException"><paramref name="comparer"/> is null.</exception>
+        public void ReplaceRange(int index, int count, IEnumerable<T> collection)
         {
         {
-          //parallel position
-          T old = this[i], @new = list[i - index];
-          if (Comparer.Equals(old, @new))
-          {
-            OnRangeReplaced(i, newCluster!, oldCluster!);
-            continue;
-          }
-          else
-          {
-            Items[i] = @new;
-
-            if (newCluster == null)
+            if (index < 0)
+                throw new ArgumentOutOfRangeException(nameof(index));
+            if (count < 0)
+                throw new ArgumentOutOfRangeException(nameof(count));
+            if (index + count > Count)
+                throw new ArgumentOutOfRangeException(nameof(index));
+
+            if (collection == null)
+                throw new ArgumentNullException(nameof(collection));
+
+            if (!AllowDuplicates)
             {
             {
-              Debug.Assert(oldCluster == null);
-              newCluster = new List<T> { @new };
-              oldCluster = new List<T> { old };
+                collection =
+                  collection
+                  .Distinct(Comparer)
+                  .ToList();
             }
             }
-            else
+
+            if (collection is ICollection<T> countable)
+            {
+                if (countable.Count == 0)
+                {
+                    RemoveRange(index, count);
+                    return;
+                }
+            }
+            else if (!collection.Any())
             {
             {
-              newCluster.Add(@new);
-              oldCluster!.Add(old);
+                RemoveRange(index, count);
+                return;
             }
             }
 
 
-            changesMade = true;
-          }
-        }
+            if (index + count == 0)
+            {
+                InsertRange(0, collection);
+                return;
+            }
 
 
-        OnRangeReplaced(i, newCluster!, oldCluster!);
+            if (!(collection is IList<T> list))
+                list = new List<T>(collection);
 
 
-        //exceeding position
-        if (count != addedCount)
-        {
-          var items = (List<T>)Items;
-          if (count > addedCount)
-          {
-            var removedCount = rangeCount - addedCount;
-            T[] removed = new T[removedCount];
-            items.CopyTo(i, removed, 0, removed.Length);
-            items.RemoveRange(i, removedCount);
-            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed, i));
-          }
-          else
-          {
-            var k = i - index;
-            T[] added = new T[addedCount - k];
-            for (int j = k; j < addedCount; j++)
+            using (BlockReentrancy())
+            using (DeferEvents())
             {
             {
-              T @new = list[j];
-              added[j - k] = @new;
+                var rangeCount = index + count;
+                var addedCount = list.Count;
+
+                var changesMade = false;
+                List<T>
+                  newCluster = null,
+                  oldCluster = null;
+
+
+                int i = index;
+                for (; i < rangeCount && i - index < addedCount; i++)
+                {
+                    //parallel position
+                    T old = this[i], @new = list[i - index];
+                    if (Comparer.Equals(old, @new))
+                    {
+                        OnRangeReplaced(i, newCluster!, oldCluster!);
+                        continue;
+                    }
+                    else
+                    {
+                        Items[i] = @new;
+
+                        if (newCluster == null)
+                        {
+                            Debug.Assert(oldCluster == null);
+                            newCluster = new List<T> { @new };
+                            oldCluster = new List<T> { old };
+                        }
+                        else
+                        {
+                            newCluster.Add(@new);
+                            oldCluster!.Add(old);
+                        }
+
+                        changesMade = true;
+                    }
+                }
+
+                OnRangeReplaced(i, newCluster!, oldCluster!);
+
+                //exceeding position
+                if (count != addedCount)
+                {
+                    var items = (List<T>)Items;
+                    if (count > addedCount)
+                    {
+                        var removedCount = rangeCount - addedCount;
+                        T[] removed = new T[removedCount];
+                        items.CopyTo(i, removed, 0, removed.Length);
+                        items.RemoveRange(i, removedCount);
+                        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed, i));
+                    }
+                    else
+                    {
+                        var k = i - index;
+                        T[] added = new T[addedCount - k];
+                        for (int j = k; j < addedCount; j++)
+                        {
+                            T @new = list[j];
+                            added[j - k] = @new;
+                        }
+                        items.InsertRange(i, added);
+                        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, i));
+                    }
+
+                    OnEssentialPropertiesChanged();
+                }
+                else if (changesMade)
+                {
+                    OnIndexerPropertyChanged();
+                }
             }
             }
-            items.InsertRange(i, added);
-            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, i));
-          }
-
-          OnEssentialPropertiesChanged();
-        }
-        else if (changesMade)
-        {
-          OnIndexerPropertyChanged();
         }
         }
-      }
-    }
 
 
-    #endregion Public Methods
+        #endregion Public Methods
 
 
 
 
-    //------------------------------------------------------
-    //
-    //  Protected Methods
-    //
-    //------------------------------------------------------
+        //------------------------------------------------------
+        //
+        //  Protected Methods
+        //
+        //------------------------------------------------------
 
 
-    #region Protected Methods
+        #region Protected Methods
 
 
-    /// <summary>
-    /// Called by base class Collection&lt;T&gt; when the list is being cleared;
-    /// raises a CollectionChanged event to any listeners.
-    /// </summary>
-    protected override void ClearItems()
-    {
-      if (Count == 0)
-        return;
+        /// <summary>
+        /// Called by base class Collection&lt;T&gt; when the list is being cleared;
+        /// raises a CollectionChanged event to any listeners.
+        /// </summary>
+        protected override void ClearItems()
+        {
+            if (Count == 0)
+                return;
 
 
-      CheckReentrancy();
-      base.ClearItems();
-      OnEssentialPropertiesChanged();
-      OnCollectionReset();
-    }
+            CheckReentrancy();
+            base.ClearItems();
+            OnEssentialPropertiesChanged();
+            OnCollectionReset();
+        }
 
 
-    /// <inheritdoc/>
-    protected override void InsertItem(int index, T item)
-    {
-      if (!AllowDuplicates && Items.Contains(item))
-        return;
+        /// <inheritdoc/>
+        protected override void InsertItem(int index, T item)
+        {
+            if (!AllowDuplicates && Items.Contains(item))
+                return;
 
 
-      base.InsertItem(index, item);
-    }
+            base.InsertItem(index, item);
+        }
 
 
-    /// <inheritdoc/>
-    protected override void SetItem(int index, T item)
-    {
-      if (AllowDuplicates)
-      {
-        if (Comparer.Equals(this[index], item))
-          return;
-      }
-      else
-        if (Items.Contains(item, Comparer))
-        return;
-
-      CheckReentrancy();
-      T oldItem = this[index];
-      base.SetItem(index, item);
-
-      OnIndexerPropertyChanged();
-      OnCollectionChanged(NotifyCollectionChangedAction.Replace, oldItem!, item!, index);
-    }
+        /// <inheritdoc/>
+        protected override void SetItem(int index, T item)
+        {
+            if (AllowDuplicates)
+            {
+                if (Comparer.Equals(this[index], item))
+                    return;
+            }
+            else if (Items.Contains(item, Comparer))
+            {
+                return;
+            }
 
 
-    /// <summary>
-    /// Raise CollectionChanged event to any listeners.
-    /// Properties/methods modifying this ObservableCollection will raise
-    /// a collection changed event through this virtual method.
-    /// </summary>
-    /// <remarks>
-    /// When overriding this method, either call its base implementation
-    /// or call <see cref="BlockReentrancy"/> to guard against reentrant collection changes.
-    /// </remarks>
-    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
-    {
-      if (_deferredEvents != null)
-      {
-        _deferredEvents.Add(e);
-        return;
-      }
-      base.OnCollectionChanged(e);
-    }
+            CheckReentrancy();
+            T oldItem = this[index];
+            base.SetItem(index, item);
+
+            OnIndexerPropertyChanged();
+            OnCollectionChanged(NotifyCollectionChangedAction.Replace, oldItem!, item!, index);
+        }
 
 
-    protected virtual IDisposable DeferEvents() => new DeferredEventsCollection(this);
+        /// <summary>
+        /// Raise CollectionChanged event to any listeners.
+        /// Properties/methods modifying this ObservableCollection will raise
+        /// a collection changed event through this virtual method.
+        /// </summary>
+        /// <remarks>
+        /// When overriding this method, either call its base implementation
+        /// or call <see cref="BlockReentrancy"/> to guard against reentrant collection changes.
+        /// </remarks>
+        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
+        {
+            if (_deferredEvents != null)
+            {
+                _deferredEvents.Add(e);
+                return;
+            }
+            base.OnCollectionChanged(e);
+        }
 
 
-    #endregion Protected Methods
+        protected virtual IDisposable DeferEvents() => new DeferredEventsCollection(this);
 
 
+        #endregion Protected Methods
 
 
-    //------------------------------------------------------
-    //
-    //  Private Methods
-    //
-    //------------------------------------------------------
 
 
-    #region Private Methods
+        //------------------------------------------------------
+        //
+        //  Private Methods
+        //
+        //------------------------------------------------------
 
 
-    /// <summary>
-    /// Helper to raise Count property and the Indexer property.
-    /// </summary>
-    void OnEssentialPropertiesChanged()
-    {
-      OnPropertyChanged(EventArgsCache.CountPropertyChanged);
-      OnIndexerPropertyChanged();
-    }
+        #region Private Methods
 
 
-    /// <summary>
-    /// /// Helper to raise a PropertyChanged event for the Indexer property
-    /// /// </summary>
-    void OnIndexerPropertyChanged() =>
-     OnPropertyChanged(EventArgsCache.IndexerPropertyChanged);
+        /// <summary>
+        /// Helper to raise Count property and the Indexer property.
+        /// </summary>
+        void OnEssentialPropertiesChanged()
+        {
+            OnPropertyChanged(EventArgsCache.CountPropertyChanged);
+            OnIndexerPropertyChanged();
+        }
 
 
-    /// <summary>
-    /// Helper to raise CollectionChanged event to any listeners
-    /// </summary>
-    void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) =>
-     OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));
+        /// <summary>
+        /// /// Helper to raise a PropertyChanged event for the Indexer property
+        /// /// </summary>
+        void OnIndexerPropertyChanged() =>
+         OnPropertyChanged(EventArgsCache.IndexerPropertyChanged);
+
+        /// <summary>
+        /// Helper to raise CollectionChanged event to any listeners
+        /// </summary>
+        void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) =>
+         OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));
+
+        /// <summary>
+        /// Helper to raise CollectionChanged event with action == Reset to any listeners
+        /// </summary>
+        void OnCollectionReset() =>
+         OnCollectionChanged(EventArgsCache.ResetCollectionChanged);
+
+        /// <summary>
+        /// Helper to raise event for clustered action and clear cluster.
+        /// </summary>
+        /// <param name="followingItemIndex">The index of the item following the replacement block.</param>
+        //TODO should have really been a local method inside ReplaceRange(int index, int count, IEnumerable<T> collection, IEqualityComparer<T> comparer),
+        //move when supported language version updated.
+        void OnRangeReplaced(int followingItemIndex, ICollection<T> newCluster, ICollection<T> oldCluster)
+        {
+            if (oldCluster == null || oldCluster.Count == 0)
+            {
+                Debug.Assert(newCluster == null || newCluster.Count == 0);
+                return;
+            }
 
 
-    /// <summary>
-    /// Helper to raise CollectionChanged event with action == Reset to any listeners
-    /// </summary>
-    void OnCollectionReset() =>
-     OnCollectionChanged(EventArgsCache.ResetCollectionChanged);
+            OnCollectionChanged(
+              new NotifyCollectionChangedEventArgs(
+                NotifyCollectionChangedAction.Replace,
+                new List<T>(newCluster),
+                new List<T>(oldCluster),
+                followingItemIndex - oldCluster.Count));
 
 
-    /// <summary>
-    /// Helper to raise event for clustered action and clear cluster.
-    /// </summary>
-    /// <param name="followingItemIndex">The index of the item following the replacement block.</param>
-    /// <param name="newCluster"></param>
-    /// <param name="oldCluster"></param>
-    //TODO should have really been a local method inside ReplaceRange(int index, int count, IEnumerable<T> collection, IEqualityComparer<T> comparer),
-    //move when supported language version updated.
-    void OnRangeReplaced(int followingItemIndex, ICollection<T> newCluster, ICollection<T> oldCluster)
-    {
-      if (oldCluster == null || oldCluster.Count == 0)
-      {
-        Debug.Assert(newCluster == null || newCluster.Count == 0);
-        return;
-      }
-
-      OnCollectionChanged(
-        new NotifyCollectionChangedEventArgs(
-          NotifyCollectionChangedAction.Replace,
-          new List<T>(newCluster),
-          new List<T>(oldCluster),
-          followingItemIndex - oldCluster.Count));
-
-      oldCluster.Clear();
-      newCluster.Clear();
-    }
+            oldCluster.Clear();
+            newCluster.Clear();
+        }
 
 
-    #endregion Private Methods
+        #endregion Private Methods
 
 
-    //------------------------------------------------------
-    //
-    //  Private Types
-    //
-    //------------------------------------------------------
+        //------------------------------------------------------
+        //
+        //  Private Types
+        //
+        //------------------------------------------------------
 
 
-    #region Private Types
-    sealed class DeferredEventsCollection : List<NotifyCollectionChangedEventArgs>, IDisposable
-    {
-      readonly RangeObservableCollection<T> _collection;
-      public DeferredEventsCollection(RangeObservableCollection<T> collection)
-      {
-        Debug.Assert(collection != null);
-        Debug.Assert(collection._deferredEvents == null);
-        _collection = collection;
-        _collection._deferredEvents = this;
-      }
-
-      public void Dispose()
-      {
-        _collection._deferredEvents = null;
-        foreach (var args in this)
-          _collection.OnCollectionChanged(args);
-      }
-    }
+        #region Private Types
+        sealed class DeferredEventsCollection : List<NotifyCollectionChangedEventArgs>, IDisposable
+        {
+            readonly RangeObservableCollection<T> _collection;
+            public DeferredEventsCollection(RangeObservableCollection<T> collection)
+            {
+                Debug.Assert(collection != null);
+                Debug.Assert(collection._deferredEvents == null);
+                _collection = collection;
+                _collection._deferredEvents = this;
+            }
 
 
-    #endregion Private Types
+            public void Dispose()
+            {
+                _collection._deferredEvents = null;
+                foreach (var args in this)
+                    _collection.OnCollectionChanged(args);
+            }
+        }
 
 
-  }
+        #endregion Private Types
+
+    }
 
 
-  /// <remarks>
-  /// To be kept outside <see cref="System.Collections.ObjectModel.ObservableCollection{T}"/>, since otherwise, a new instance will be created for each generic type used.
-  /// </remarks>
-  internal static class EventArgsCache
-  {
-    internal static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs("Count");
-    internal static readonly PropertyChangedEventArgs IndexerPropertyChanged = new PropertyChangedEventArgs("Item[]");
-    internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
-  }
-}
+    /// <remarks>
+    /// To be kept outside <see cref="ObservableCollection{T}"/>, since otherwise, a new instance will be created for each generic type used.
+    /// </remarks>
+#pragma warning disable SA1402 // File may only contain a single type
+    internal static class EventArgsCache
+#pragma warning restore SA1402 // File may only contain a single type
+    {
+        internal static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs("Count");
+        internal static readonly PropertyChangedEventArgs IndexerPropertyChanged = new PropertyChangedEventArgs("Item[]");
+        internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
+    }
+}

+ 115 - 0
PixiEditor/Models/DataHolders/WpfObservableRangeCollection.cs

@@ -0,0 +1,115 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Windows.Data;
+
+namespace PixiEditor.Models.DataHolders
+{
+    public class WpfObservableRangeCollection<T> : RangeObservableCollection<T>
+    {
+        public bool SuppressNotify { get; set; } = false;
+        DeferredEventsCollection _deferredEvents;
+
+        public WpfObservableRangeCollection()
+        {
+        }
+
+        public WpfObservableRangeCollection(IEnumerable<T> collection) : base(collection)
+        {
+        }
+
+        public WpfObservableRangeCollection(List<T> list) : base(list)
+        {
+        }
+
+
+        /// <summary>
+        /// Raise CollectionChanged event to any listeners.
+        /// Properties/methods modifying this ObservableCollection will raise
+        /// a collection changed event through this virtual method.
+        /// </summary>
+        /// <remarks>
+        /// When overriding this method, either call its base implementation
+        /// or call <see cref="BlockReentrancy"/> to guard against reentrant collection changes.
+        /// </remarks>
+        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
+        {
+            if (SuppressNotify) return;
+#pragma warning disable SA1312 // Variable names should begin with lower-case letter
+            var _deferredEvents = (ICollection<NotifyCollectionChangedEventArgs>)typeof(RangeObservableCollection<T>)
+#pragma warning restore SA1312 // Variable names should begin with lower-case letter
+              .GetField("_deferredEvents", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(this);
+            if (_deferredEvents != null)
+            {
+                _deferredEvents.Add(e);
+                return;
+            }
+
+            foreach (var handler in GetHandlers())
+            {
+                if (IsRange(e) && handler.Target is CollectionView cv)
+                    cv.Refresh();
+                else
+                    handler(this, e);
+            }
+        }
+
+        protected override IDisposable DeferEvents() => new DeferredEventsCollection(this);
+
+        bool IsRange(NotifyCollectionChangedEventArgs e) => e.NewItems?.Count > 1 || e.OldItems?.Count > 1;
+
+        IEnumerable<NotifyCollectionChangedEventHandler> GetHandlers()
+        {
+            var info = typeof(ObservableCollection<T>).GetField(
+                nameof(CollectionChanged),
+                BindingFlags.Instance | BindingFlags.NonPublic);
+            var @event = (MulticastDelegate)info.GetValue(this);
+            return @event?.GetInvocationList()
+                     .Cast<NotifyCollectionChangedEventHandler>()
+                     .Distinct()
+                   ?? Enumerable.Empty<NotifyCollectionChangedEventHandler>();
+        }
+
+        class DeferredEventsCollection : List<NotifyCollectionChangedEventArgs>, IDisposable
+        {
+            private readonly WpfObservableRangeCollection<T> _collection;
+
+            public DeferredEventsCollection(WpfObservableRangeCollection<T> collection)
+            {
+                Debug.Assert(collection != null);
+                Debug.Assert(collection._deferredEvents == null);
+                _collection = collection;
+                _collection._deferredEvents = this;
+            }
+
+            public void Dispose()
+            {
+                _collection._deferredEvents = null;
+
+                var handlers = _collection
+                  .GetHandlers()
+                  .ToLookup(h => h.Target is CollectionView);
+
+                foreach (var handler in handlers[false])
+                {
+                    foreach (var e in this)
+                    {
+                        handler(_collection, e);
+                    }
+                }
+
+                foreach (var cv in handlers[true]
+                           .Select(h => h.Target)
+                           .Cast<CollectionView>()
+                           .Distinct())
+                {
+                    cv.Refresh();
+                }
+            }
+        }
+    }
+}

+ 1 - 17
PixiEditor/Models/Dialogs/ConfirmationDialog.cs

@@ -1,26 +1,10 @@
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Enums;
 using PixiEditor.Views;
 using PixiEditor.Views;
-using System;
 
 
 namespace PixiEditor.Models.Dialogs
 namespace PixiEditor.Models.Dialogs
 {
 {
     public static class ConfirmationDialog
     public static class ConfirmationDialog
-    {
-        [Obsolete(message: "Use Show(message, title) instead.")]
-        public static ConfirmationType Show(string message)
-        {
-            ConfirmationPopup popup = new ConfirmationPopup
-            {
-                Body = message
-            };
-            if (popup.ShowDialog().GetValueOrDefault())
-            {
-                return popup.Result ? ConfirmationType.Yes : ConfirmationType.No;
-            }
-
-            return ConfirmationType.Canceled;
-        }
-
+    {
         public static ConfirmationType Show(string message, string title)
         public static ConfirmationType Show(string message, string title)
         {
         {
             ConfirmationPopup popup = new ConfirmationPopup
             ConfirmationPopup popup = new ConfirmationPopup

+ 2 - 2
PixiEditor/Models/Dialogs/ResizeDocumentDialog.cs

@@ -27,7 +27,7 @@ namespace PixiEditor.Models.Dialogs
                 if (width != value)
                 if (width != value)
                 {
                 {
                     width = value;
                     width = value;
-                    RaisePropertyChanged("Width");
+                    RaisePropertyChanged(nameof(Width));
                 }
                 }
             }
             }
         }
         }
@@ -40,7 +40,7 @@ namespace PixiEditor.Models.Dialogs
                 if (height != value)
                 if (height != value)
                 {
                 {
                     height = value;
                     height = value;
-                    RaisePropertyChanged("Height");
+                    RaisePropertyChanged(nameof(Height));
                 }
                 }
             }
             }
         }
         }

+ 0 - 8
PixiEditor/Models/Enums/CapType.cs

@@ -1,8 +0,0 @@
-namespace PixiEditor.Models.Enums
-{
-    public enum CapType
-    {
-        Square,
-        Round
-    }
-}

+ 0 - 27
PixiEditor/Models/ImageManipulation/BitmapUtils.cs

@@ -1,7 +1,6 @@
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers.Utils;
 using PixiEditor.Models.Layers.Utils;
-using PixiEditor.Models.Position;
 using PixiEditor.Parser;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Skia;
 using PixiEditor.Parser.Skia;
 using SkiaSharp;
 using SkiaSharp;
@@ -129,32 +128,6 @@ namespace PixiEditor.Models.ImageManipulation
                 maxPreviewHeight);
                 maxPreviewHeight);
         }
         }
 
 
-        public static Dictionary<Guid, SKColor[]> GetPixelsForSelection(Layer[] layers, Coordinates[] selection)
-        {
-            Dictionary<Guid, SKColor[]> result = new();
-
-            foreach (Layer layer in layers)
-            {
-                SKColor[] pixels = new SKColor[selection.Length];
-
-                for (int j = 0; j < pixels.Length; j++)
-                {
-                    Coordinates position = layer.GetRelativePosition(selection[j]);
-                    if (position.X < 0 || position.X > layer.Width - 1 || position.Y < 0 ||
-                        position.Y > layer.Height - 1)
-                    {
-                        continue;
-                    }
-
-                    var cl = layer.GetPixel(position.X, position.Y);
-                    pixels[j] = cl;
-                }
-                result[layer.GuidValue] = pixels;
-            }
-
-            return result;
-        }
-
         public static SKColor BlendColors(SKColor bottomColor, SKColor topColor)
         public static SKColor BlendColors(SKColor bottomColor, SKColor topColor)
         {
         {
             if ((topColor.Alpha < 255 && topColor.Alpha > 0))
             if ((topColor.Alpha < 255 && topColor.Alpha > 0))

+ 0 - 99
PixiEditor/Models/ImageManipulation/Morphology.cs

@@ -1,99 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using PixiEditor.Models.Position;
-
-namespace PixiEditor.Models.ImageManipulation
-{
-    public class Morphology
-    {
-        public static IEnumerable<Coordinates> ApplyDilation(Coordinates[] points, int kernelSize, int[,] mask)
-        {
-            int kernelDim = kernelSize;
-
-            // This is the offset of center pixel from border of the kernel
-            int kernelOffset = (kernelDim - 1) / 2;
-            int margin = kernelDim;
-
-            byte[,] byteImg = GetByteArrayForPoints(points, margin);
-            byte[,] outputArray = byteImg.Clone() as byte[,];
-            Coordinates offset = new Coordinates(points.Min(x => x.X) - margin, points.Min(x => x.Y) - margin);
-
-            int width = byteImg.GetLength(0);
-            int height = byteImg.GetLength(1);
-            for (int y = kernelOffset; y < height - kernelOffset; y++)
-            {
-                for (int x = kernelOffset; x < width - kernelOffset; x++)
-                {
-                    byte value = 0;
-
-                    // Apply dilation
-                    for (int ykernel = -kernelOffset; ykernel <= kernelOffset; ykernel++)
-                    {
-                        for (int xkernel = -kernelOffset; xkernel <= kernelOffset; xkernel++)
-                        {
-                            if (mask[xkernel + kernelOffset, ykernel + kernelOffset] == 1)
-                            {
-                                value = Math.Max(value, byteImg[x + xkernel, y + ykernel]);
-                            }
-                            else
-                            {
-                                continue;
-                            }
-                        }
-                    }
-
-                    // Write processed data into the second array
-                    outputArray[x, y] = value;
-                }
-            }
-
-            return ToCoordinates(outputArray, offset).Distinct();
-        }
-
-        private static IEnumerable<Coordinates> ToCoordinates(byte[,] byteArray, Coordinates offset)
-        {
-            List<Coordinates> output = new List<Coordinates>();
-            int width = byteArray.GetLength(0);
-
-            for (int y = 0; y < byteArray.GetLength(1); y++)
-            {
-                for (int x = 0; x < width; x++)
-                {
-                    if (byteArray[x, y] == 1)
-                    {
-                        output.Add(new Coordinates(x + offset.X, y + offset.Y));
-                    }
-                }
-            }
-
-            return output;
-        }
-
-        private static byte[,] GetByteArrayForPoints(Coordinates[] points, int margin)
-        {
-            Tuple<int, int> dimensions = GetDimensionsForPoints(points);
-            int minX = points.Min(x => x.X);
-            int minY = points.Min(x => x.Y);
-            byte[,] array = new byte[dimensions.Item1 + (margin * 2), dimensions.Item2 + (margin * 2)];
-
-            for (int y = 0; y < dimensions.Item2 + margin; y++)
-            {
-                for (int x = 0; x < dimensions.Item1 + margin; x++)
-                {
-                    Coordinates cords = new Coordinates(x + minX, y + minY);
-                    array[x + margin, y + margin] = points.Contains(cords) ? (byte)1 : (byte)0;
-                }
-            }
-
-            return array;
-        }
-
-        private static Tuple<int, int> GetDimensionsForPoints(Coordinates[] points)
-        {
-            int width = points.Max(x => x.X) - points.Min(x => x.X);
-            int height = points.Max(x => x.Y) - points.Min(x => x.Y);
-            return new Tuple<int, int>(width + 1, height + 1);
-        }
-    }
-}

+ 0 - 31
PixiEditor/Models/ImageManipulation/Transform.cs

@@ -1,31 +0,0 @@
-using PixiEditor.Models.Position;
-
-namespace PixiEditor.Models.ImageManipulation
-{
-    public static class Transform
-    {
-        /// <summary>
-        ///     Returns translation between two coordinates.
-        /// </summary>
-        /// <param name="from">Starting coordinate.</param>
-        /// <param name="to">New coordinate.</param>
-        /// <returns>Translation as coordinate.</returns>
-        public static Coordinates GetTranslation(Coordinates from, Coordinates to)
-        {
-            int translationX = to.X - from.X;
-            int translationY = to.Y - from.Y;
-            return new Coordinates(translationX, translationY);
-        }
-
-        public static Coordinates[] Translate(Coordinates[] points, Coordinates vector)
-        {
-            Coordinates[] translatedPoints = new Coordinates[points.Length];
-            for (int i = 0; i < translatedPoints.Length; i++)
-            {
-                translatedPoints[i] = new Coordinates(points[i].X + vector.X, points[i].Y + vector.Y);
-            }
-
-            return translatedPoints;
-        }
-    }
-}

+ 12 - 12
PixiEditor/Models/Layers/LayerStructure.cs

@@ -191,7 +191,7 @@ namespace PixiEditor.Models.Layers
                 return;
                 return;
             }
             }
 
 
-            PreMoveReassignBounds(parentGroup, group);
+            Unassign(parentGroup, group);
 
 
             List<Guid> layersInOrder = GetLayersInOrder(new GroupData(groupTopIndex, groupBottomIndex));
             List<Guid> layersInOrder = GetLayersInOrder(new GroupData(groupTopIndex, groupBottomIndex));
 
 
@@ -250,9 +250,9 @@ namespace PixiEditor.Models.Layers
         /// </summary>
         /// </summary>
         /// <param name="parentGroup">Parent group to reassign data in.</param>
         /// <param name="parentGroup">Parent group to reassign data in.</param>
         /// <param name="group">Group which data should be reassigned.</param>
         /// <param name="group">Group which data should be reassigned.</param>
-        public void PreMoveReassignBounds(GroupData parentGroup, GroupData group)
+        public void Unassign(GroupData parentGroup, GroupData group)
         {
         {
-            PreMoveReassignBounds(GetGroupByGuid(parentGroup.GroupGuid), GetGroupByGuid(group.GroupGuid));
+            Unassign(GetGroupByGuid(parentGroup.GroupGuid), GetGroupByGuid(group.GroupGuid));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -260,7 +260,7 @@ namespace PixiEditor.Models.Layers
         /// </summary>
         /// </summary>
         /// <param name="parentGroup">Parent group to reassign data in.</param>
         /// <param name="parentGroup">Parent group to reassign data in.</param>
         /// <param name="layer">Layer which data should be reassigned.</param>
         /// <param name="layer">Layer which data should be reassigned.</param>
-        public void PreMoveReassignBounds(GroupData parentGroup, Guid layer)
+        public void Unassign(GroupData parentGroup, Guid layer)
         {
         {
             PreMoveReassignBounds(GetGroupByGuid(parentGroup.GroupGuid), layer);
             PreMoveReassignBounds(GetGroupByGuid(parentGroup.GroupGuid), layer);
         }
         }
@@ -270,9 +270,9 @@ namespace PixiEditor.Models.Layers
         /// </summary>
         /// </summary>
         /// <param name="parentGroup">Parent group to reassign data in.</param>
         /// <param name="parentGroup">Parent group to reassign data in.</param>
         /// <param name="layerGuid">Group which data should be reassigned.</param>
         /// <param name="layerGuid">Group which data should be reassigned.</param>
-        public void PostMoveReassignBounds(GroupData parentGroup, Guid layerGuid)
+        public void Assign(GroupData parentGroup, Guid layerGuid)
         {
         {
-            PostMoveReassignBounds(GetGroupByGuid(parentGroup.GroupGuid), layerGuid);
+            Assign(GetGroupByGuid(parentGroup.GroupGuid), layerGuid);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -280,9 +280,9 @@ namespace PixiEditor.Models.Layers
         /// </summary>
         /// </summary>
         /// <param name="parentGroup">Parent group to reassign data in.</param>
         /// <param name="parentGroup">Parent group to reassign data in.</param>
         /// <param name="group">Group which data should be reassigned.</param>
         /// <param name="group">Group which data should be reassigned.</param>
-        public void PostMoveReassignBounds(GroupData parentGroup, GroupData group)
+        public void Assign(GroupData parentGroup, GroupData group)
         {
         {
-            PostMoveReassignBounds(GetGroupByGuid(parentGroup?.GroupGuid), GetGroupByGuid(group.GroupGuid));
+            Assign(GetGroupByGuid(parentGroup?.GroupGuid), GetGroupByGuid(group.GroupGuid));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -450,7 +450,7 @@ namespace PixiEditor.Models.Layers
             }
             }
         }
         }
 
 
-        private void PreMoveReassignBounds(GuidStructureItem parentGroup, GuidStructureItem group)
+        private void Unassign(GuidStructureItem parentGroup, GuidStructureItem group)
         {
         {
             if (parentGroup != null)
             if (parentGroup != null)
             {
             {
@@ -481,7 +481,7 @@ namespace PixiEditor.Models.Layers
             }
             }
         }
         }
 
 
-        private void PostMoveReassignBounds(GuidStructureItem parentGroup, Guid layerGuid)
+        private void Assign(GuidStructureItem parentGroup, Guid layerGuid)
         {
         {
             if (parentGroup != null)
             if (parentGroup != null)
             {
             {
@@ -529,7 +529,7 @@ namespace PixiEditor.Models.Layers
             }
             }
         }
         }
 
 
-        private void PostMoveReassignBounds(GuidStructureItem parentGroup, GuidStructureItem group)
+        private void Assign(GuidStructureItem parentGroup, GuidStructureItem group)
         {
         {
             if (parentGroup != null)
             if (parentGroup != null)
             {
             {
@@ -572,7 +572,7 @@ namespace PixiEditor.Models.Layers
                 PreMoveReassignBounds(currentParent, layer);
                 PreMoveReassignBounds(currentParent, layer);
             }
             }
 
 
-            PostMoveReassignBounds(parent, layer);
+            Assign(parent, layer);
 
 
             LayerStructureChanged?.Invoke(this, new LayerStructureChangedEventArgs(layer));
             LayerStructureChanged?.Invoke(this, new LayerStructureChangedEventArgs(layer));
         }
         }

+ 0 - 19
PixiEditor/Models/Position/MousePositionConverter.cs

@@ -1,19 +0,0 @@
-using System.Drawing;
-using System.Runtime.InteropServices;
-
-namespace PixiEditor.Models.Position
-{
-    public static class MousePositionConverter
-    {
-        public static Coordinates CurrentCoordinates { get; set; }
-
-        public static Point GetCursorPosition()
-        {
-            GetCursorPos(out Point point);
-            return point;
-        }
-
-        [DllImport("user32.dll")]
-        private static extern bool GetCursorPos(out Point point);
-    }
-}

+ 1 - 1
PixiEditor/PixiEditor.csproj

@@ -201,7 +201,7 @@
 		</PackageReference>
 		</PackageReference>
 		<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
 		<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
 		<PackageReference Include="PixiEditor.ColorPicker" Version="3.2.0" />
 		<PackageReference Include="PixiEditor.ColorPicker" Version="3.2.0" />
-		<PackageReference Include="PixiEditor.Parser" Version="2.0.0" />
+		<PackageReference Include="PixiEditor.Parser" Version="2.0.0.1" />
 		<PackageReference Include="PixiEditor.Parser.Skia" Version="2.0.0.1" />
 		<PackageReference Include="PixiEditor.Parser.Skia" Version="2.0.0.1" />
 		<PackageReference Include="SkiaSharp" Version="2.80.3" />
 		<PackageReference Include="SkiaSharp" Version="2.80.3" />
 		<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
 		<PackageReference Include="System.Drawing.Common" Version="6.0.0" />

+ 2 - 2
PixiEditor/Properties/AssemblyInfo.cs

@@ -50,5 +50,5 @@ using System.Windows;
 // You can specify all the values or you can default the Build and Revision Numbers
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.1.7.2")]
-[assembly: AssemblyFileVersion("0.1.7.2")]
+[assembly: AssemblyVersion("0.1.8.0")]
+[assembly: AssemblyFileVersion("0.1.8.0")]

+ 3 - 2
PixiEditor/Styles/AvalonDock/Themes/Generic.xaml

@@ -162,8 +162,9 @@
 	<Style x:Key="PixiEditorDockThemeButtonStyle" TargetType="Button">
 	<Style x:Key="PixiEditorDockThemeButtonStyle" TargetType="Button">
 		<Setter Property="Background" Value="Transparent" />
 		<Setter Property="Background" Value="Transparent" />
 		<Setter Property="BorderThickness" Value="1" />
 		<Setter Property="BorderThickness" Value="1" />
-		<Setter Property="BorderBrush" Value="Transparent" />
-		<Setter Property="Padding" Value="0" />
+        <Setter Property="BorderBrush" Value="Transparent" />
+        <Setter Property="Focusable" Value="False" />
+        <Setter Property="Padding" Value="0" />
 		<Setter Property="Template">
 		<Setter Property="Template">
 			<Setter.Value>
 			<Setter.Value>
 				<ControlTemplate TargetType="Button">
 				<ControlTemplate TargetType="Button">

+ 1 - 0
PixiEditor/Styles/ImageCheckBoxStyle.xaml

@@ -2,6 +2,7 @@
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                     xmlns:local="clr-namespace:PixiEditor.Styles">
                     xmlns:local="clr-namespace:PixiEditor.Styles">
     <Style TargetType="{x:Type CheckBox}" x:Key="ImageCheckBox">
     <Style TargetType="{x:Type CheckBox}" x:Key="ImageCheckBox">
+        <Setter Property="Focusable" Value="False"/>
         <Setter Property="Template">
         <Setter Property="Template">
             <Setter.Value>
             <Setter.Value>
                 <ControlTemplate TargetType="{x:Type CheckBox}">
                 <ControlTemplate TargetType="{x:Type CheckBox}">

+ 1 - 0
PixiEditor/Styles/ListSwitchButtonStyle.xaml

@@ -7,6 +7,7 @@
         <Setter Property="BorderThickness" Value="1"/>
         <Setter Property="BorderThickness" Value="1"/>
         <Setter Property="FontSize" Value="12"/>
         <Setter Property="FontSize" Value="12"/>
         <Setter Property="Cursor" Value="Hand"/>
         <Setter Property="Cursor" Value="Hand"/>
+        <Setter Property="Focusable" Value="False"/>
         <Setter Property="Padding" Value="2, 0"/>
         <Setter Property="Padding" Value="2, 0"/>
         <Setter Property="Foreground" Value="White"/>
         <Setter Property="Foreground" Value="White"/>
         <Setter Property="Template">
         <Setter Property="Template">

+ 14 - 0
PixiEditor/Styles/ThemeStyle.xaml

@@ -13,9 +13,14 @@
         <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
         <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
     </Style>
     </Style>
 
 
+    <Style TargetType="ToggleButton">
+        <Setter Property="Focusable" Value="False"/>
+    </Style>
+    
     <Style TargetType="Button" x:Key="BaseDarkButton">
     <Style TargetType="Button" x:Key="BaseDarkButton">
         <Setter Property="Background" Value="#404040" />
         <Setter Property="Background" Value="#404040" />
         <Setter Property="Foreground" Value="White" />
         <Setter Property="Foreground" Value="White" />
+        <Setter Property="Focusable" Value="False" />
         <Setter Property="FontSize" Value="15" />
         <Setter Property="FontSize" Value="15" />
         <Setter Property="SnapsToDevicePixels" Value="True" />
         <Setter Property="SnapsToDevicePixels" Value="True" />
         <Setter Property="Template">
         <Setter Property="Template">
@@ -60,6 +65,7 @@
     <Style TargetType="Button" x:Key="DarkRoundButton" BasedOn="{StaticResource BaseDarkButton}">
     <Style TargetType="Button" x:Key="DarkRoundButton" BasedOn="{StaticResource BaseDarkButton}">
         <Setter Property="OverridesDefaultStyle" Value="True" />
         <Setter Property="OverridesDefaultStyle" Value="True" />
         <Setter Property="Background" Value="#303030" />
         <Setter Property="Background" Value="#303030" />
+        <Setter Property="Focusable" Value="False" />
         <Setter Property="Height" Value="28"/>
         <Setter Property="Height" Value="28"/>
         <Setter Property="Width" Value="70"/>
         <Setter Property="Width" Value="70"/>
         <Setter Property="Template">
         <Setter Property="Template">
@@ -124,6 +130,7 @@
 
 
     <Style TargetType="Button" x:Key="ImageButtonStyle">
     <Style TargetType="Button" x:Key="ImageButtonStyle">
         <Setter Property="OverridesDefaultStyle" Value="True" />
         <Setter Property="OverridesDefaultStyle" Value="True" />
+        <Setter Property="Focusable" Value="False" />
         <Setter Property="Template">
         <Setter Property="Template">
             <Setter.Value>
             <Setter.Value>
                 <ControlTemplate TargetType="Button">
                 <ControlTemplate TargetType="Button">
@@ -145,6 +152,7 @@
            BasedOn="{StaticResource BaseDarkButton}">
            BasedOn="{StaticResource BaseDarkButton}">
         <Setter Property="TextBlock.FontFamily" Value="Segoe MDL2 Assets"/>
         <Setter Property="TextBlock.FontFamily" Value="Segoe MDL2 Assets"/>
         <Setter Property="TextBlock.FontSize" Value="15"/>
         <Setter Property="TextBlock.FontSize" Value="15"/>
+        <Setter Property="Focusable" Value="False" />
         <Setter Property="TextBlock.Width" Value="30"/>
         <Setter Property="TextBlock.Width" Value="30"/>
 
 
         <Style.Triggers>
         <Style.Triggers>
@@ -160,6 +168,10 @@
         </Style.Triggers>
         </Style.Triggers>
     </Style>
     </Style>
 
 
+    <Style TargetType="CheckBox">
+        <Setter Property="Focusable" Value="False"/>
+    </Style>
+
 
 
     <Style TargetType="TextBox" x:Key="DarkTextBoxStyle">
     <Style TargetType="TextBox" x:Key="DarkTextBoxStyle">
         <Setter Property="BorderThickness" Value="1" />
         <Setter Property="BorderThickness" Value="1" />
@@ -198,6 +210,7 @@
 
 
     <Style TargetType="Button" x:Key="OpacityButtonStyle">
     <Style TargetType="Button" x:Key="OpacityButtonStyle">
         <Setter Property="OverridesDefaultStyle" Value="True" />
         <Setter Property="OverridesDefaultStyle" Value="True" />
+        <Setter Property="Focusable" Value="False" />
         <Setter Property="Template">
         <Setter Property="Template">
             <Setter.Value>
             <Setter.Value>
                 <ControlTemplate TargetType="Button">
                 <ControlTemplate TargetType="Button">
@@ -224,6 +237,7 @@
 
 
     <Style TargetType="Button" x:Key="ToolButtonStyle">
     <Style TargetType="Button" x:Key="ToolButtonStyle">
         <Setter Property="Height" Value="32" />
         <Setter Property="Height" Value="32" />
+        <Setter Property="Focusable" Value="False" />
         <Setter Property="Width" Value="32" />
         <Setter Property="Width" Value="32" />
         <Setter Property="VerticalAlignment" Value="Top" />
         <Setter Property="VerticalAlignment" Value="Top" />
         <Setter Property="HorizontalAlignment" Value="Center" />
         <Setter Property="HorizontalAlignment" Value="Center" />

+ 1 - 0
PixiEditor/ViewModels/CrashReportViewModel.cs

@@ -2,6 +2,7 @@
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Views.Dialogs;
 using PixiEditor.Views.Dialogs;
 using System.Diagnostics;
 using System.Diagnostics;
+using System.Linq;
 using System.Windows;
 using System.Windows;
 
 
 namespace PixiEditor.ViewModels
 namespace PixiEditor.ViewModels

+ 0 - 41
PixiEditor/ViewModels/MenuButtonViewModel.cs

@@ -1,41 +0,0 @@
-using System.Windows;
-using PixiEditor.Helpers;
-
-namespace PixiEditor.ViewModels
-{
-    internal class MenuButtonViewModel : ViewModelBase
-    {
-        private Visibility listViewVisibility;
-
-        public MenuButtonViewModel()
-        {
-            OpenListViewCommand = new RelayCommand(OpenListView);
-            CloseListViewCommand = new RelayCommand(CloseListView);
-            ListViewVisibility = Visibility.Hidden;
-        }
-
-        public RelayCommand OpenListViewCommand { get; set; }
-
-        public RelayCommand CloseListViewCommand { get; set; }
-
-        public Visibility ListViewVisibility
-        {
-            get => listViewVisibility;
-            set
-            {
-                listViewVisibility = value;
-                RaisePropertyChanged("ListViewVisibility");
-            }
-        }
-
-        private void OpenListView(object parameter)
-        {
-            ListViewVisibility = ListViewVisibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden;
-        }
-
-        private void CloseListView(object parameter)
-        {
-            ListViewVisibility = Visibility.Hidden;
-        }
-    }
-}

+ 64 - 24
PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs

@@ -1,8 +1,10 @@
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Controllers.Shortcuts;
 using PixiEditor.Models.Controllers.Shortcuts;
+using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools.Tools;
 using PixiEditor.Models.Tools.Tools;
 using System;
 using System;
+using System.Windows;
 using System.Windows.Input;
 using System.Windows.Input;
 
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 namespace PixiEditor.ViewModels.SubViewModels.Main
@@ -17,10 +19,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
         public RelayCommand MouseUpCommand { get; set; }
         public RelayCommand MouseUpCommand { get; set; }
 
 
-        public RelayCommand KeyDownCommand { get; set; }
-
-        public RelayCommand KeyUpCommand { get; set; }
-
         private bool restoreToolOnKeyUp = false;
         private bool restoreToolOnKeyUp = false;
 
 
         private MouseInputFilter filter = new();
         private MouseInputFilter filter = new();
@@ -33,17 +31,42 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             MouseUpCommand = new RelayCommand(filter.MouseUp);
             MouseUpCommand = new RelayCommand(filter.MouseUp);
             PreviewMouseMiddleButtonCommand = new RelayCommand(OnPreviewMiddleMouseButton);
             PreviewMouseMiddleButtonCommand = new RelayCommand(OnPreviewMiddleMouseButton);
             GlobalMouseHook.OnMouseUp += filter.MouseUp;
             GlobalMouseHook.OnMouseUp += filter.MouseUp;
-            KeyDownCommand = new RelayCommand(OnKeyDown);
-            KeyUpCommand = new RelayCommand(OnKeyUp);
+
+            InputManager.Current.PreProcessInput += Current_PreProcessInput;
 
 
             filter.OnMouseDown += OnMouseDown;
             filter.OnMouseDown += OnMouseDown;
             filter.OnMouseMove += OnMouseMove;
             filter.OnMouseMove += OnMouseMove;
             filter.OnMouseUp += OnMouseUp;
             filter.OnMouseUp += OnMouseUp;
         }
         }
 
 
-        private void OnKeyDown(object parameter)
+        private void Current_PreProcessInput(object sender, PreProcessInputEventArgs e)
+        {
+            if (e != null && e.StagingItem != null && e.StagingItem.Input != null)
+            {
+                InputEventArgs inputEvent = e.StagingItem.Input;
+
+                if (inputEvent is KeyboardEventArgs)
+                {
+                    KeyboardEventArgs k = inputEvent as KeyboardEventArgs;
+                    RoutedEvent r = k.RoutedEvent;
+                    KeyEventArgs keyEvent = k as KeyEventArgs;
+
+                    if (keyEvent != null && keyEvent?.InputSource?.RootVisual != MainWindow.Current) return;
+                    if (r == Keyboard.KeyDownEvent)
+                    {
+                        OnKeyDown(keyEvent);
+                    }
+
+                    if (r == Keyboard.KeyUpEvent)
+                    {
+                        OnKeyUp(keyEvent);
+                    }
+                }
+            }
+        }
+
+        private void OnKeyDown(KeyEventArgs args)
         {
         {
-            KeyEventArgs args = (KeyEventArgs)parameter;
             var key = args.Key;
             var key = args.Key;
             if (key == Key.System)
             if (key == Key.System)
                 key = args.SystemKey;
                 key = args.SystemKey;
@@ -55,8 +78,23 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 Owner.BitmapManager.InputTarget.OnKeyDown(key);
                 Owner.BitmapManager.InputTarget.OnKeyDown(key);
             }
             }
 
 
-            if (args.Key == ShortcutController.MoveViewportToolTransientChangeKey)
-                ChangeMoveViewportToolState(true);
+            HandleTransientKey(args, true);
+        }
+
+        private void HandleTransientKey(KeyEventArgs args, bool state)
+        {
+            var controller = Owner.ShortcutController;
+
+            Key finalKey = args.Key;
+            if (finalKey == Key.System)
+            {
+                finalKey = args.SystemKey;
+            }
+
+            if (controller.TransientShortcuts.ContainsKey(finalKey))
+            {
+                ChangeToolState(controller.TransientShortcuts[finalKey].GetType(), state);
+            }
         }
         }
 
 
         private void ProcessShortcutDown(bool isRepeat, Key key)
         private void ProcessShortcutDown(bool isRepeat, Key key)
@@ -71,9 +109,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Owner.ShortcutController.KeyPressed(key, Keyboard.Modifiers);
             Owner.ShortcutController.KeyPressed(key, Keyboard.Modifiers);
         }
         }
 
 
-        private void OnKeyUp(object parameter)
+        private void OnKeyUp(KeyEventArgs args)
         {
         {
-            KeyEventArgs args = (KeyEventArgs)parameter;
             var key = args.Key;
             var key = args.Key;
             if (key == Key.System)
             if (key == Key.System)
                 key = args.SystemKey;
                 key = args.SystemKey;
@@ -83,10 +120,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             if (Owner.BitmapManager.ActiveDocument != null)
             if (Owner.BitmapManager.ActiveDocument != null)
                 Owner.BitmapManager.InputTarget.OnKeyUp(key);
                 Owner.BitmapManager.InputTarget.OnKeyUp(key);
 
 
-            if (args.Key == ShortcutController.MoveViewportToolTransientChangeKey)
-            {
-                ChangeMoveViewportToolState(false);     
-            }
+            HandleTransientKey(args, false);
         }
         }
 
 
         private void ProcessShortcutUp(Key key)
         private void ProcessShortcutUp(Key key)
@@ -115,21 +149,27 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
         private void OnPreviewMiddleMouseButton(object sender)
         private void OnPreviewMiddleMouseButton(object sender)
         {
         {
-            ChangeMoveViewportToolState(true);
+            ChangeToolState<MoveViewportTool>(true);
+        }
+
+        private void ChangeToolState<T>(bool setOn)
+            where T : Tool
+        {
+            ChangeToolState(typeof(T), setOn);
         }
         }
 
 
-        void ChangeMoveViewportToolState(bool setOn)
+        private void ChangeToolState(Type type, bool setOn)
         {
         {
             if (setOn)
             if (setOn)
             {
             {
-                var moveViewportToolIsActive = Owner.ToolsSubViewModel.ActiveTool is MoveViewportTool;
-                if (!moveViewportToolIsActive)
+                var transientToolIsActive = Owner.ToolsSubViewModel.ActiveTool.GetType() == type;
+                if (!transientToolIsActive)
                 {
                 {
-                    Owner.ToolsSubViewModel.SetActiveTool<MoveViewportTool>();
-                    Owner.ToolsSubViewModel.MoveToolIsTransient = true;
+                    Owner.ToolsSubViewModel.SetActiveTool(type);
+                    Owner.ToolsSubViewModel.ActiveToolIsTransient = true;
                 }
                 }
             }
             }
-            else if (Owner.ToolsSubViewModel.LastActionTool != null && Owner.ToolsSubViewModel.MoveToolIsTransient)
+            else if (Owner.ToolsSubViewModel.LastActionTool != null && Owner.ToolsSubViewModel.ActiveToolIsTransient)
             {
             {
                 Owner.ToolsSubViewModel.SetActiveTool(Owner.ToolsSubViewModel.LastActionTool);
                 Owner.ToolsSubViewModel.SetActiveTool(Owner.ToolsSubViewModel.LastActionTool);
                 restoreToolOnKeyUp = false;
                 restoreToolOnKeyUp = false;
@@ -155,7 +195,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
             }
             else if (button == MouseButton.Middle)
             else if (button == MouseButton.Middle)
             {
             {
-                ChangeMoveViewportToolState(false);
+                ChangeToolState<MoveViewportTool>(false);
             }
             }
         }
         }
     }
     }

+ 17 - 7
PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs

@@ -5,6 +5,7 @@ using PixiEditor.Models.Events;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools.Tools;
 using PixiEditor.Models.Tools.Tools;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
+using PixiEditor.Models.UserPreferences;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
@@ -23,7 +24,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
         public Tool LastActionTool { get; private set; }
         public Tool LastActionTool { get; private set; }
 
 
-        public bool MoveToolIsTransient { get; set; }
+        public bool ActiveToolIsTransient { get; set; }
 
 
         public Cursor ToolCursor
         public Cursor ToolCursor
         {
         {
@@ -56,7 +57,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
             }
         }
         }
 
 
-        public IEnumerable<Tool> ToolSet { get; private set; }
+        public List<Tool> ToolSet { get; private set; }
 
 
         public event EventHandler<SelectedToolEventArgs> SelectedToolChanged;
         public event EventHandler<SelectedToolEventArgs> SelectedToolChanged;
 
 
@@ -69,7 +70,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
         public void SetupTools(IServiceProvider services)
         public void SetupTools(IServiceProvider services)
         {
         {
-            ToolSet = services.GetServices<Tool>();
+            ToolSet = services.GetServices<Tool>().ToList();
             SetActiveTool<PenTool>();
             SetActiveTool<PenTool>();
 
 
             Owner.BitmapManager.BitmapOperations.BitmapChanged += (_, _) => TriggerCacheOutdated();
             Owner.BitmapManager.BitmapOperations.BitmapChanged += (_, _) => TriggerCacheOutdated();
@@ -92,11 +93,16 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
         public void SetActiveTool(Tool tool)
         public void SetActiveTool(Tool tool)
         {
         {
-            MoveToolIsTransient = false;
+            if (ActiveTool == tool) return;
+            ActiveToolIsTransient = false;
+            bool shareToolbar = IPreferences.Current.GetPreference<bool>("EnableSharedToolbar");
             if (ActiveTool != null)
             if (ActiveTool != null)
             {
             {
                 activeTool.IsActive = false;
                 activeTool.IsActive = false;
-                ActiveTool.Toolbar.SaveToolbarSettings();
+                if (shareToolbar)
+                {
+                    ActiveTool.Toolbar.SaveToolbarSettings();
+                }
             }
             }
 
 
             LastActionTool = ActiveTool;
             LastActionTool = ActiveTool;
@@ -104,7 +110,10 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
             ActiveTool = tool;
             ActiveTool = tool;
 
 
-            ActiveTool.Toolbar.LoadSharedSettings();
+            if (shareToolbar)
+            {
+                ActiveTool.Toolbar.LoadSharedSettings();
+            }
 
 
             if (LastActionTool != ActiveTool)
             if (LastActionTool != ActiveTool)
                 SelectedToolChanged?.Invoke(this, new SelectedToolEventArgs(LastActionTool, ActiveTool));
                 SelectedToolChanged?.Invoke(this, new SelectedToolEventArgs(LastActionTool, ActiveTool));
@@ -187,8 +196,9 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
             }
         }
         }
 
 
-        private void SetActiveTool(Type toolType)
+        public void SetActiveTool(Type toolType)
         {
         {
+            if (!typeof(Tool).IsAssignableFrom(toolType)) { throw new ArgumentException($"'{toolType}' does not inherit from {typeof(Tool)}"); }
             Tool foundTool = ToolSet.First(x => x.GetType() == toolType);
             Tool foundTool = ToolSet.First(x => x.GetType() == toolType);
             SetActiveTool(foundTool);
             SetActiveTool(foundTool);
         }
         }

+ 6 - 0
PixiEditor/ViewModels/SubViewModels/Main/UndoViewModel.cs

@@ -34,7 +34,10 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
             //sometimes CanRedo gets changed after UndoRedoCalled invoke, so check again (normally this is checked by the relaycommand)
             //sometimes CanRedo gets changed after UndoRedoCalled invoke, so check again (normally this is checked by the relaycommand)
             if (CanRedo(null))
             if (CanRedo(null))
+            {
                 Owner.BitmapManager.ActiveDocument.UndoManager.Redo();
                 Owner.BitmapManager.ActiveDocument.UndoManager.Redo();
+                Owner.BitmapManager.ActiveDocument.ChangesSaved = false;
+            }
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -47,7 +50,10 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
             //sometimes CanUndo gets changed after UndoRedoCalled invoke, so check again (normally this is checked by the relaycommand)
             //sometimes CanUndo gets changed after UndoRedoCalled invoke, so check again (normally this is checked by the relaycommand)
             if (CanUndo(null))
             if (CanUndo(null))
+            {
                 Owner.BitmapManager.ActiveDocument.UndoManager.Undo();
                 Owner.BitmapManager.ActiveDocument.UndoManager.Undo();
+                Owner.BitmapManager.ActiveDocument.ChangesSaved = false;
+            }
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 1 - 1
PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs

@@ -101,7 +101,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 if (updateZipExists || updateExeExists)
                 if (updateZipExists || updateExeExists)
                 {
                 {
                     ViewModelMain.Current.UpdateSubViewModel.UpdateReadyToInstall = true;
                     ViewModelMain.Current.UpdateSubViewModel.UpdateReadyToInstall = true;
-                    var result = ConfirmationDialog.Show("Update is ready to install. Do you want to install it now?");
+                    var result = ConfirmationDialog.Show("Update is ready to be installed. Do you want to install it now?", "New update");
                     if (result == Models.Enums.ConfirmationType.Yes)
                     if (result == Models.Enums.ConfirmationType.Yes)
                     {
                     {
                         if (updateZipExists && File.Exists(updaterPath))
                         if (updateZipExists && File.Exists(updaterPath))

+ 23 - 0
PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/ToolsSettings.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.ViewModels.SubViewModels.UserPreferences.Settings
+{
+    public class ToolsSettings : SettingsGroup
+    {
+        private bool enableSharedToolbar = GetPreference(nameof(EnableSharedToolbar), false);
+
+        public bool EnableSharedToolbar
+        {
+            get => enableSharedToolbar;
+            set
+            {
+                enableSharedToolbar = value;
+                RaiseAndUpdatePreference(nameof(EnableSharedToolbar), value);
+            }
+        }
+    }
+}

+ 2 - 0
PixiEditor/ViewModels/SubViewModels/UserPreferences/SettingsViewModel.cs

@@ -6,6 +6,8 @@ namespace PixiEditor.ViewModels.SubViewModels.UserPreferences
     {
     {
         public GeneralSettings General { get; set; } = new GeneralSettings();
         public GeneralSettings General { get; set; } = new GeneralSettings();
 
 
+        public ToolsSettings Tools { get; set; } = new ToolsSettings();
+
         public FileSettings File { get; set; } = new FileSettings();
         public FileSettings File { get; set; } = new FileSettings();
 
 
         public UpdateSettings Update { get; set; } = new UpdateSettings();
         public UpdateSettings Update { get; set; } = new UpdateSettings();

+ 4 - 1
PixiEditor/ViewModels/ViewModelMain.cs

@@ -230,6 +230,9 @@ namespace PixiEditor.ViewModels
                         "Misc",
                         "Misc",
                         new Shortcut(Key.F1, MiscSubViewModel.OpenShortcutWindowCommand, "Open shortcuts window", true)));
                         new Shortcut(Key.F1, MiscSubViewModel.OpenShortcutWindowCommand, "Open shortcuts window", true)));
 
 
+            ShortcutController.TransientShortcuts[Key.Space] = ToolsSubViewModel.ToolSet.First(x => x is MoveViewportTool);
+            ShortcutController.TransientShortcuts[Key.LeftAlt] = ToolsSubViewModel.ToolSet.First(x => x is ColorPickerTool);
+
             BitmapManager.PrimaryColor = ColorsSubViewModel.PrimaryColor;
             BitmapManager.PrimaryColor = ColorsSubViewModel.PrimaryColor;
 
 
             ToolsSubViewModel?.SetupToolsTooltipShortcuts(services);
             ToolsSubViewModel?.SetupToolsTooltipShortcuts(services);
@@ -380,7 +383,7 @@ namespace PixiEditor.ViewModels
             BitmapManager.ActiveDocument.CenterViewportTrigger.Execute(this, new Size(BitmapManager.ActiveDocument.Width, BitmapManager.ActiveDocument.Height));
             BitmapManager.ActiveDocument.CenterViewportTrigger.Execute(this, new Size(BitmapManager.ActiveDocument.Width, BitmapManager.ActiveDocument.Height));
         }
         }
 
 
-        private void BitmapUtility_BitmapChanged(object sender, BitmapChangedEventArgs e)
+        private void BitmapUtility_BitmapChanged(object sender, EventArgs e)
         {
         {
             BitmapManager.ActiveDocument.ChangesSaved = false;
             BitmapManager.ActiveDocument.ChangesSaved = false;
             if (ToolsSubViewModel.ActiveTool is BitmapOperationTool)
             if (ToolsSubViewModel.ActiveTool is BitmapOperationTool)

+ 1 - 1
PixiEditor/Views/Dialogs/CrashReportDialog.xaml

@@ -33,7 +33,7 @@
             <StackPanel>
             <StackPanel>
                 <Grid Background="{StaticResource MainColor}">
                 <Grid Background="{StaticResource MainColor}">
                     <StackPanel Margin="7" VerticalAlignment="Center">
                     <StackPanel Margin="7" VerticalAlignment="Center">
-                        <TextBlock Text="{Binding DocumentCount, StringFormat={}{0} file(s) can be recovered}"
+                        <TextBlock Text="{Binding DocumentCount, StringFormat={}{0} file(s) might be recoverable}"
                        d:Text="2 file(s) can be recovered"/>
                        d:Text="2 file(s) can be recovered"/>
                         <TextBlock TextWrapping="Wrap">You can help the developers fix this bug by sending a crash report that was generated (you will still be able to recover the files).</TextBlock>
                         <TextBlock TextWrapping="Wrap">You can help the developers fix this bug by sending a crash report that was generated (you will still be able to recover the files).</TextBlock>
                     </StackPanel>
                     </StackPanel>

+ 1 - 0
PixiEditor/Views/Dialogs/ResizeCanvasPopup.xaml

@@ -42,6 +42,7 @@
                               Width="240"
                               Width="240"
                               Height="170"
                               Height="170"
                               x:Name="sizePicker"
                               x:Name="sizePicker"
+                              Focusable="True"
                               ChosenHeight="{Binding NewAbsoluteHeight, Mode=TwoWay, ElementName=window}"
                               ChosenHeight="{Binding NewAbsoluteHeight, Mode=TwoWay, ElementName=window}"
                               ChosenWidth="{Binding NewAbsoluteWidth, Mode=TwoWay, ElementName=window}" 
                               ChosenWidth="{Binding NewAbsoluteWidth, Mode=TwoWay, ElementName=window}" 
                               ChosenPercentageSize="{Binding NewPercentageSize, Mode=TwoWay, ElementName=window}"
                               ChosenPercentageSize="{Binding NewPercentageSize, Mode=TwoWay, ElementName=window}"

+ 1 - 0
PixiEditor/Views/Dialogs/ResizeDocumentPopup.xaml

@@ -37,6 +37,7 @@
         <local:SizePicker HorizontalAlignment="Center" Width="240" Height="180" Margin="0,30,0,0"
         <local:SizePicker HorizontalAlignment="Center" Width="240" Height="180" Margin="0,30,0,0"
             x:Name="sizePicker"
             x:Name="sizePicker"
             PreserveAspectRatio="True"
             PreserveAspectRatio="True"
+                          Focusable="True"
             ChosenHeight="{Binding NewAbsoluteHeight, Mode=TwoWay, ElementName=window}"
             ChosenHeight="{Binding NewAbsoluteHeight, Mode=TwoWay, ElementName=window}"
             ChosenWidth="{Binding NewAbsoluteWidth, Mode=TwoWay, ElementName=window}" 
             ChosenWidth="{Binding NewAbsoluteWidth, Mode=TwoWay, ElementName=window}" 
             ChosenPercentageSize="{Binding NewPercentageSize, Mode=TwoWay, ElementName=window}"
             ChosenPercentageSize="{Binding NewPercentageSize, Mode=TwoWay, ElementName=window}"

+ 10 - 3
PixiEditor/Views/Dialogs/SettingsWindow.xaml

@@ -76,6 +76,8 @@
                     <RowDefinition Height="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=Tag}"/>
                     <RowDefinition Height="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=Tag}"/>
                     <RowDefinition Height="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=Tag}"/>
                     <RowDefinition Height="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=Tag}"/>
                     <RowDefinition Height="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=Tag}"/>
                     <RowDefinition Height="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=Tag}"/>
+                    <RowDefinition Height="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=Tag}"/>
+                    <RowDefinition Height="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=Tag}"/>
                 </Grid.RowDefinitions>
                 </Grid.RowDefinitions>
 
 
                 <Label Grid.Row="0" Grid.ColumnSpan="2" Style="{StaticResource SettingsHeader}">Misc</Label>
                 <Label Grid.Row="0" Grid.ColumnSpan="2" Style="{StaticResource SettingsHeader}">Misc</Label>
@@ -106,13 +108,18 @@
                                  Size="{Binding SettingsSubViewModel.File.DefaultNewFileHeight, Mode=TwoWay}" 
                                  Size="{Binding SettingsSubViewModel.File.DefaultNewFileHeight, Mode=TwoWay}" 
                                  Width="70" Height="21" MaxSize="9999" HorizontalAlignment="Left"/>
                                  Width="70" Height="21" MaxSize="9999" HorizontalAlignment="Left"/>
 
 
-                <Label Grid.Row="7" Grid.ColumnSpan="2" Style="{StaticResource SettingsHeader}">Automatic updates</Label>
+                <Label Grid.Row="7" Grid.ColumnSpan="2" Style="{StaticResource SettingsHeader}">Tools</Label>
 
 
                 <CheckBox Grid.Row="8" Grid.Column="1" VerticalAlignment="Center"
                 <CheckBox Grid.Row="8" Grid.Column="1" VerticalAlignment="Center"
+                    IsChecked="{Binding SettingsSubViewModel.Tools.EnableSharedToolbar}">Enable shared toolbar</CheckBox>
+                
+                <Label Grid.Row="9" Grid.ColumnSpan="2" Style="{StaticResource SettingsHeader}">Automatic updates</Label>
+
+                <CheckBox Grid.Row="10" Grid.Column="1" VerticalAlignment="Center"
                     IsChecked="{Binding SettingsSubViewModel.Update.CheckUpdatesOnStartup}">Check updates on startup</CheckBox>
                     IsChecked="{Binding SettingsSubViewModel.Update.CheckUpdatesOnStartup}">Check updates on startup</CheckBox>
 
 
-                <Label Grid.Row="9" Grid.Column="1" Style="{StaticResource SettingsText}">Update stream</Label>
-                <ComboBox Grid.Row="9" Grid.Column="2" VerticalAlignment="Center"
+                <Label Grid.Row="11" Grid.Column="1" Style="{StaticResource SettingsText}">Update stream</Label>
+                <ComboBox Grid.Row="11" Grid.Column="2" VerticalAlignment="Center"
                     Width="110" Height="22" HorizontalAlignment="Left"
                     Width="110" Height="22" HorizontalAlignment="Left"
                     ItemsSource="{Binding SettingsSubViewModel.Update.UpdateChannels}"
                     ItemsSource="{Binding SettingsSubViewModel.Update.UpdateChannels}"
                     SelectedValue="{Binding SettingsSubViewModel.Update.UpdateChannelName}"/>
                     SelectedValue="{Binding SettingsSubViewModel.Update.UpdateChannelName}"/>

+ 4 - 10
PixiEditor/Views/MainWindow.xaml

@@ -51,12 +51,6 @@
     </Window.CommandBindings>
     </Window.CommandBindings>
 
 
     <i:Interaction.Triggers>
     <i:Interaction.Triggers>
-        <i:EventTrigger EventName="KeyDown">
-            <cmd:EventToCommand Command="{Binding IoSubViewModel.KeyDownCommand}" PassEventArgsToCommand="True" />
-        </i:EventTrigger>
-        <i:EventTrigger EventName="KeyUp">
-            <cmd:EventToCommand Command="{Binding IoSubViewModel.KeyUpCommand}" PassEventArgsToCommand="True"/>
-        </i:EventTrigger>
         <i:EventTrigger EventName="ContentRendered">
         <i:EventTrigger EventName="ContentRendered">
             <i:InvokeCommandAction Command="{Binding OnStartupCommand}" />
             <i:InvokeCommandAction Command="{Binding OnStartupCommand}" />
         </i:EventTrigger>
         </i:EventTrigger>
@@ -206,7 +200,7 @@
             <Button Command="{Binding UndoSubViewModel.RedoCommand}" ToolTip="Redo"
             <Button Command="{Binding UndoSubViewModel.RedoCommand}" ToolTip="Redo"
                     Style="{StaticResource ToolSettingsGlyphButton}" Content="&#xE7A6;"/>
                     Style="{StaticResource ToolSettingsGlyphButton}" Content="&#xE7A6;"/>
             <ToggleButton Width="30" BorderThickness="0"
             <ToggleButton Width="30" BorderThickness="0"
-                          ToolTip="Pen Mode"
+                          ToolTip="Pen Mode" Focusable="False"
                           IsChecked="{Binding StylusSubViewModel.IsPenModeEnabled}">
                           IsChecked="{Binding StylusSubViewModel.IsPenModeEnabled}">
                 <ToggleButton.Style>
                 <ToggleButton.Style>
                     <Style TargetType="ToggleButton">
                     <Style TargetType="ToggleButton">
@@ -214,9 +208,9 @@
                             <Setter.Value>
                             <Setter.Value>
                                 <ControlTemplate TargetType="ToggleButton">
                                 <ControlTemplate TargetType="ToggleButton">
                                     <Border BorderBrush="{TemplateBinding BorderBrush}" 
                                     <Border BorderBrush="{TemplateBinding BorderBrush}" 
-                                            Background="{TemplateBinding Background}">
+                                            Background="{TemplateBinding Background}" Focusable="False">
                                         <ContentPresenter HorizontalAlignment="Center"
                                         <ContentPresenter HorizontalAlignment="Center"
-                                              VerticalAlignment="Center"/>
+                                              VerticalAlignment="Center" Focusable="False"/>
                                     </Border>
                                     </Border>
                                 </ControlTemplate>
                                 </ControlTemplate>
                             </Setter.Value>
                             </Setter.Value>
@@ -226,7 +220,7 @@
                                 <Setter Property="Background" Value="Transparent"/>
                                 <Setter Property="Background" Value="Transparent"/>
                             </Trigger>
                             </Trigger>
                             <Trigger Property="IsMouseOver" Value="True">
                             <Trigger Property="IsMouseOver" Value="True">
-                                <Setter Property="Background" Value="#606060"/>
+                                <Setter Property="Background" Value="#404040"/>
                             </Trigger>
                             </Trigger>
                             <Trigger Property="IsChecked" Value="True">
                             <Trigger Property="IsChecked" Value="True">
                                 <Setter Property="Background" Value="#707070"/>
                                 <Setter Property="Background" Value="#707070"/>

+ 1 - 1
PixiEditor/Views/UserControls/EditableTextBlock.xaml

@@ -5,7 +5,7 @@
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours"
              xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours"
              mc:Ignorable="d"
              mc:Ignorable="d"
-             d:DesignHeight="60" d:DesignWidth="100">
+             d:DesignHeight="60" d:DesignWidth="100" Focusable="True">
     <Grid>
     <Grid>
         <TextBlock Foreground="Snow" MouseLeftButtonDown="TextBlock_MouseDown"
         <TextBlock Foreground="Snow" MouseLeftButtonDown="TextBlock_MouseDown"
                    TextTrimming="CharacterEllipsis" Name="textBlock"
                    TextTrimming="CharacterEllipsis" Name="textBlock"

+ 8 - 7
PixiEditor/Views/UserControls/Layers/LayersManager.xaml.cs

@@ -74,6 +74,7 @@ namespace PixiEditor.Views.UserControls.Layers
         public LayersManager()
         public LayersManager()
         {
         {
             InitializeComponent();
             InitializeComponent();
+            numberInput.OnScrollAction = () => NumberInput_LostFocus(null, null);
         }
         }
 
 
         private static void LayerTreeRootChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
         private static void LayerTreeRootChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
@@ -84,7 +85,7 @@ namespace PixiEditor.Views.UserControls.Layers
             manager.CachedLayerTreeRoot = newRoot;
             manager.CachedLayerTreeRoot = newRoot;
             return;
             return;
             //layer tree caching goes after than and disabled for now
             //layer tree caching goes after than and disabled for now
-
+            /*
             if (manager.CachedLayerTreeRoot == null || newRoot == null)
             if (manager.CachedLayerTreeRoot == null || newRoot == null)
             {
             {
                 manager.CachedLayerTreeRoot = newRoot;
                 manager.CachedLayerTreeRoot = newRoot;
@@ -94,7 +95,7 @@ namespace PixiEditor.Views.UserControls.Layers
             if (object.ReferenceEquals(manager.CachedLayerTreeRoot, newRoot))
             if (object.ReferenceEquals(manager.CachedLayerTreeRoot, newRoot))
                 return;
                 return;
 
 
-            UpdateCachedTree(manager.CachedLayerTreeRoot, newRoot);
+            UpdateCachedTree(manager.CachedLayerTreeRoot, newRoot);*/
         }
         }
 
 
         private static void UpdateCachedTree(IList<IHasGuid> tree, IList<IHasGuid> newTree)
         private static void UpdateCachedTree(IList<IHasGuid> tree, IList<IHasGuid> newTree)
@@ -285,7 +286,8 @@ namespace PixiEditor.Views.UserControls.Layers
 
 
         private void LayerStructureItemContainer_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
         private void LayerStructureItemContainer_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
         {
         {
-            if (sender is LayerStructureItemContainer container && e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
+            if (sender is LayerStructureItemContainer container
+                && e.LeftButton == System.Windows.Input.MouseButtonState.Pressed && !container.Layer.IsRenaming)
             {
             {
                 Dispatcher.InvokeAsync(() => DragDrop.DoDragDrop(container, container, DragDropEffects.Move));
                 Dispatcher.InvokeAsync(() => DragDrop.DoDragDrop(container, container, DragDropEffects.Move));
             }
             }
@@ -332,7 +334,8 @@ namespace PixiEditor.Views.UserControls.Layers
 
 
         private void LayerGroup_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
         private void LayerGroup_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
         {
         {
-            if (sender is LayerGroupControl container && e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
+            if (sender is LayerGroupControl container && e.LeftButton == System.Windows.Input.MouseButtonState.Pressed
+                && !container.GroupData.IsRenaming)
             {
             {
                 Dispatcher.InvokeAsync(() => DragDrop.DoDragDrop(container, container, DragDropEffects.Move));
                 Dispatcher.InvokeAsync(() => DragDrop.DoDragDrop(container, container, DragDropEffects.Move));
             }
             }
@@ -346,7 +349,6 @@ namespace PixiEditor.Views.UserControls.Layers
 
 
             if (item is Layer || item is LayerStructureItemContainer)
             if (item is Layer || item is LayerStructureItemContainer)
             {
             {
-
                 Layer layer = null;
                 Layer layer = null;
 
 
                 if (item is Layer lr)
                 if (item is Layer lr)
@@ -370,7 +372,6 @@ namespace PixiEditor.Views.UserControls.Layers
             }
             }
 
 
             ShortcutController.UnblockShortcutExecutionAll();
             ShortcutController.UnblockShortcutExecutionAll();
-            MoveFocus(new System.Windows.Input.TraversalRequest(System.Windows.Input.FocusNavigationDirection.Next));
         }
         }
 
 
         private void HandleLayerOpacityChange(float val, Layer layer)
         private void HandleLayerOpacityChange(float val, Layer layer)
@@ -462,4 +463,4 @@ namespace PixiEditor.Views.UserControls.Layers
             SelectedItem = sender;
             SelectedItem = sender;
         }
         }
     }
     }
-}
+}

+ 4 - 4
PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml

@@ -51,7 +51,7 @@
                         </Button.Background>
                         </Button.Background>
                     </Button>
                     </Button>
                 </StackPanel>
                 </StackPanel>
-                <CheckBox Panel.ZIndex="10" Name="visibilityCheckbox" Grid.Column="1" Margin="0,0,5,0" Height="16" HorizontalAlignment="Right">
+                <CheckBox Focusable="False" Panel.ZIndex="10" Name="visibilityCheckbox" Grid.Column="1" Margin="0,0,5,0" Height="16" HorizontalAlignment="Right">
                     <CheckBox.Triggers>
                     <CheckBox.Triggers>
                         <EventTrigger RoutedEvent="CheckBox.Checked">
                         <EventTrigger RoutedEvent="CheckBox.Checked">
                             <BeginStoryboard>
                             <BeginStoryboard>
@@ -73,13 +73,13 @@
                     </CheckBox.Triggers>
                     </CheckBox.Triggers>
                     <CheckBox.Template>
                     <CheckBox.Template>
                         <ControlTemplate TargetType="{x:Type CheckBox}">
                         <ControlTemplate TargetType="{x:Type CheckBox}">
-                            <StackPanel Orientation="Horizontal">
-                                <Image Width="14" Cursor="Hand" x:Name="checkboxImage" Source="/Images/ChevronDown.png">
+                            <StackPanel Orientation="Horizontal" Focusable="False">
+                                <Image Focusable="False" Width="14" Cursor="Hand" x:Name="checkboxImage" Source="/Images/ChevronDown.png">
                                     <Image.RenderTransform>
                                     <Image.RenderTransform>
                                         <RotateTransform Angle="0"/>
                                         <RotateTransform Angle="0"/>
                                     </Image.RenderTransform>
                                     </Image.RenderTransform>
                                 </Image>
                                 </Image>
-                                <ContentPresenter/>
+                                <ContentPresenter Focusable="False"/>
                             </StackPanel>
                             </StackPanel>
                             <ControlTemplate.Triggers>
                             <ControlTemplate.Triggers>
                                 <Trigger Property="IsChecked" Value="True">
                                 <Trigger Property="IsChecked" Value="True">

+ 0 - 57
PixiEditor/Views/UserControls/MenuButton.xaml

@@ -1,57 +0,0 @@
-<UserControl x:Class="PixiEditor.Views.MenuButton"
-             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-             xmlns:local="clr-namespace:PixiEditor.Views"
-             xmlns:vm="clr-namespace:PixiEditor.ViewModels"
-             xmlns:helpers="clr-namespace:PixiEditor.Helpers"
-             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
-             mc:Ignorable="d"
-             d:DesignHeight="40" d:DesignWidth="80" x:Name="menuButton"
-             DataContext="{DynamicResource MenuButtonViewModel}">
-    <UserControl.Resources>
-        <vm:MenuButtonViewModel x:Key="MenuButtonViewModel" />
-        <Style TargetType="ListViewItem">
-            <Style.Resources>
-                <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Transparent" />
-                <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
-
-            </Style.Resources>
-        </Style>
-    </UserControl.Resources>
-
-    <StackPanel Name="MainStackPanel">
-        <Button Content="{Binding ElementName=menuButton,Path=Text}" Style="{StaticResource MenuButton}"
-                HorizontalAlignment="Left" Command="{Binding OpenListViewCommand}" />
-        <ListView Visibility="{Binding ListViewVisibility}" Style="{StaticResource MenuListViewStyle}">
-            <ListView.ItemContainerStyle>
-                <Style TargetType="{x:Type ListViewItem}">
-                    <Setter Property="Background" Value="Transparent" />
-                    <Setter Property="VerticalContentAlignment"
-                            Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
-                    <Setter Property="Template">
-                        <Setter.Value>
-                            <ControlTemplate TargetType="{x:Type ListViewItem}">
-                                <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}"
-                                        BorderThickness="{TemplateBinding BorderThickness}" Background="Transparent"
-                                        Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
-                                    <ContentPresenter
-                                        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
-                                        VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
-                                </Border>
-                                <ControlTemplate.Triggers>
-                                    <Trigger Property="IsMouseOver" Value="True">
-                                        <Setter Property="BorderThickness" Value="1" />
-                                        <Setter Property="BorderBrush" Value="Transparent" />
-                                    </Trigger>
-                                </ControlTemplate.Triggers>
-                            </ControlTemplate>
-                        </Setter.Value>
-                    </Setter>
-                </Style>
-            </ListView.ItemContainerStyle>
-            <ContentPresenter Content="{Binding Item, ElementName=menuButton}" />
-        </ListView>
-    </StackPanel>
-</UserControl>

+ 0 - 46
PixiEditor/Views/UserControls/MenuButton.xaml.cs

@@ -1,46 +0,0 @@
-using PixiEditor.ViewModels;
-using System.Windows;
-using System.Windows.Controls;
-
-namespace PixiEditor.Views
-{
-    /// <summary>
-    ///     Interaction logic for MenuButton.xaml
-    /// </summary>
-    public partial class MenuButton : UserControl
-    {
-        public static readonly DependencyProperty MenuButtonTextProperty =
-            DependencyProperty.Register("Text", typeof(string), typeof(MenuButton),
-                new UIPropertyMetadata(string.Empty));
-
-        // Using a DependencyProperty as the backing store for Item.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty ItemProperty =
-            DependencyProperty.Register("Item", typeof(object), typeof(MenuButton), new PropertyMetadata(null));
-
-        private readonly MenuButtonViewModel dc = new MenuButtonViewModel();
-
-        public MenuButton()
-        {
-            InitializeComponent();
-            DataContext = dc;
-        }
-
-        public string Text
-        {
-            get => (string)GetValue(MenuButtonTextProperty);
-            set => SetValue(MenuButtonTextProperty, value);
-        }
-
-
-        public object Item
-        {
-            get => GetValue(ItemProperty);
-            set => SetValue(ItemProperty, value);
-        }
-
-        protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e)
-        {
-            dc.CloseListViewCommand.Execute(null);
-        }
-    }
-}

+ 1 - 1
PixiEditor/Views/UserControls/NumberInput.xaml

@@ -6,7 +6,7 @@
              xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
              xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
              xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours"
              xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours"
              mc:Ignorable="d"
              mc:Ignorable="d"
-             d:DesignHeight="20" d:DesignWidth="40" x:Name="numberInput" Focusable="True">
+             d:DesignHeight="20" d:DesignWidth="40" x:Name="numberInput" Focusable="True" FocusVisualStyle="{x:Null}">
     <TextBox TextAlignment="Center" Style="{StaticResource DarkTextBoxStyle}" Focusable="True"
     <TextBox TextAlignment="Center" Style="{StaticResource DarkTextBoxStyle}" Focusable="True"
              InputScope="Number" MouseWheel="TextBox_MouseWheel"
              InputScope="Number" MouseWheel="TextBox_MouseWheel"
              PreviewTextInput="TextBox_PreviewTextInput" Text="{Binding ElementName=numberInput, Path=Value}" Padding="0" VerticalContentAlignment="Center">
              PreviewTextInput="TextBox_PreviewTextInput" Text="{Binding ElementName=numberInput, Path=Value}" Padding="0" VerticalContentAlignment="Center">

+ 27 - 1
PixiEditor/Views/UserControls/NumberInput.xaml.cs

@@ -38,6 +38,30 @@ namespace PixiEditor.Views
 
 
         private readonly Regex regex = new Regex("^[.][0-9]+$|^[0-9]*[.]{0,1}[0-9]*$", RegexOptions.Compiled);
         private readonly Regex regex = new Regex("^[.][0-9]+$|^[0-9]*[.]{0,1}[0-9]*$", RegexOptions.Compiled);
 
 
+
+        public int Decimals
+        {
+            get { return (int)GetValue(DecimalsProperty); }
+            set { SetValue(DecimalsProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for Precision.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty DecimalsProperty =
+            DependencyProperty.Register("Decimals", typeof(int), typeof(NumberInput), new PropertyMetadata(2));
+
+
+        public Action OnScrollAction
+        {
+            get { return (Action)GetValue(OnScrollActionProperty); }
+            set { SetValue(OnScrollActionProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for OnScrollAction.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty OnScrollActionProperty =
+            DependencyProperty.Register("OnScrollAction", typeof(Action), typeof(NumberInput), new PropertyMetadata(null));
+
+
+
         public NumberInput()
         public NumberInput()
         {
         {
             InitializeComponent();
             InitializeComponent();
@@ -64,7 +88,7 @@ namespace PixiEditor.Views
         private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
         private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
         {
         {
             NumberInput input = (NumberInput)d;
             NumberInput input = (NumberInput)d;
-            input.Value = Math.Clamp((float)e.NewValue, input.Min, input.Max);
+            input.Value = (float)Math.Round(Math.Clamp((float)e.NewValue, input.Min, input.Max), input.Decimals);
         }
         }
 
 
         private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
         private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
@@ -89,6 +113,8 @@ namespace PixiEditor.Views
             {
             {
                 Value += step;
                 Value += step;
             }
             }
+
+            OnScrollAction?.Invoke();
         }
         }
     }
     }
 }
 }

+ 0 - 16
PixiEditor/Views/UserControls/Rotatebox.xaml

@@ -1,16 +0,0 @@
-<UserControl x:Class="PixiEditor.Views.Rotatebox"
-             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
-             xmlns:local="clr-namespace:PixiEditor.Views"
-             mc:Ignorable="d" 
-             d:DesignHeight="100" d:DesignWidth="160" x:Name="uc">
-    <StackPanel Orientation="Vertical" RenderTransformOrigin="0.5, 0.5">
-    <Image Name="knob" Source="../Images/AnchorDot.png" RenderTransformOrigin="0.5,0.5" Width="20" Height="20"/>
-        <Border Width="120" Height="60" BorderThickness="0.3" BorderBrush="DeepSkyBlue" CornerRadius="1"/>
-        <StackPanel.RenderTransform>
-            <RotateTransform Angle="{Binding Path=Angle, ElementName=uc}"/>
-        </StackPanel.RenderTransform>
-    </StackPanel>
-</UserControl>

+ 0 - 71
PixiEditor/Views/UserControls/Rotatebox.xaml.cs

@@ -1,71 +0,0 @@
-using System;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Input;
-
-namespace PixiEditor.Views
-{
-    /// <summary>
-    /// Interaction logic for Rotatebox.xaml
-    /// </summary>
-    public partial class Rotatebox : UserControl
-    {
-        private double _height = 0, _width = 0;
-        private float _offset = 90;
-
-        public Rotatebox()
-        {
-            InitializeComponent();
-            MouseLeftButtonDown += OnMouseLeftButtonDown;
-            MouseUp += OnMouseUp;
-            MouseMove += OnMouseMove;
-        }
-
-        // Using a DependencyProperty backing store for Angle.
-        public static readonly DependencyProperty AngleProperty =
-            DependencyProperty.Register("Angle", typeof(double), typeof(Rotatebox), new UIPropertyMetadata(0.0));
-
-        public double Angle
-        {
-            get { return (double)GetValue(AngleProperty); }
-            set { SetValue(AngleProperty, value); }
-        }
-
-
-        private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
-        {
-            Mouse.Capture(this);
-            _width = ActualWidth;
-            _height = ActualHeight;
-        }
-
-        private void OnMouseUp(object sender, MouseButtonEventArgs e)
-        {
-            Mouse.Capture(null);
-        }
-
-        private void OnMouseMove(object sender, MouseEventArgs e)
-        {
-            if (Equals(Mouse.Captured, this))
-            {
-                // Get the current mouse position relative to the control
-                Point currentLocation = Mouse.GetPosition(this);
-
-                // We want to rotate around the center of the knob, not the top corner
-                Point knobCenter = new Point(_width / 2, _height / 2);
-
-                // Calculate an angle
-                double radians = Math.Atan((currentLocation.Y - knobCenter.Y) /
-                                           (currentLocation.X - knobCenter.X));
-                Angle = radians * 180 / Math.PI + _offset;
-
-                // Apply a 180 degree shift when X is negative so that we can rotate
-                // all of the way around
-                if (currentLocation.X - knobCenter.X < 0)
-                {
-                    Angle += 180;
-                }
-            }
-        }
-    }
-}

+ 0 - 1
PixiEditor/Views/UserControls/SizeInput.xaml

@@ -6,7 +6,6 @@
              xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
              xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
              xmlns:behaviors="clr-namespace:PixiEditor.Helpers.Behaviours"
              xmlns:behaviors="clr-namespace:PixiEditor.Helpers.Behaviours"
              xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
              xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
-             xmlns:validators="clr-namespace:PixiEditor.Helpers.Validators"
              mc:Ignorable="d" Foreground="White" Focusable="True"
              mc:Ignorable="d" Foreground="White" Focusable="True"
              d:DesignHeight="30" d:DesignWidth="160" Name="uc">
              d:DesignHeight="30" d:DesignWidth="160" Name="uc">
 
 

+ 13 - 0
PixiEditor/Views/UserControls/SizeInput.xaml.cs

@@ -1,4 +1,5 @@
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Enums;
+using System;
 using System.Windows;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Input;
@@ -22,6 +23,16 @@ namespace PixiEditor.Views
         public static readonly DependencyProperty UnitProperty =
         public static readonly DependencyProperty UnitProperty =
             DependencyProperty.Register(nameof(Unit), typeof(SizeUnit), typeof(SizeInput), new PropertyMetadata(SizeUnit.Pixel));
             DependencyProperty.Register(nameof(Unit), typeof(SizeUnit), typeof(SizeInput), new PropertyMetadata(SizeUnit.Pixel));
 
 
+        public Action OnScrollAction
+        {
+            get { return (Action)GetValue(OnScrollActionProperty); }
+            set { SetValue(OnScrollActionProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for OnScrollAction.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty OnScrollActionProperty =
+            DependencyProperty.Register("OnScrollAction", typeof(Action), typeof(SizeInput), new PropertyMetadata(null));
+
         public SizeInput()
         public SizeInput()
         {
         {
             InitializeComponent();
             InitializeComponent();
@@ -119,6 +130,8 @@ namespace PixiEditor.Views
             {
             {
                 Size += step;
                 Size += step;
             }
             }
+
+            OnScrollAction?.Invoke();
         }
         }
     }
     }
 }
 }

+ 5 - 0
PixiEditor/Views/UserControls/SizePicker.xaml.cs

@@ -85,7 +85,12 @@ namespace PixiEditor.Views
             WidthLostFocusCommand = new(WidthLostFocus);
             WidthLostFocusCommand = new(WidthLostFocus);
             HeightLostFocusCommand = new(HeightLostFocus);
             HeightLostFocusCommand = new(HeightLostFocus);
             PercentageLostFocusCommand = new(PercentageLostFocus);
             PercentageLostFocusCommand = new(PercentageLostFocus);
+
             InitializeComponent();
             InitializeComponent();
+
+            WidthPicker.OnScrollAction = () => OnSizeUpdate(true);
+            HeightPicker.OnScrollAction = () => OnSizeUpdate(false);
+            PercentageSizePicker.OnScrollAction = () => PercentageLostFocus(null);
         }
         }
 
 
         public void FocusWidthPicker()
         public void FocusWidthPicker()

+ 5 - 5
PixiEditorTests/ModelsTests/ControllersTests/MockedSinglePixelPenTool.cs

@@ -1,10 +1,9 @@
-using System.Collections.Generic;
-using System.Windows.Media;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Layers;
+using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools;
 using SkiaSharp;
 using SkiaSharp;
+using System;
+using System.Collections.Generic;
 
 
 namespace PixiEditorTests.ModelsTests.ControllersTests
 namespace PixiEditorTests.ModelsTests.ControllersTests
 {
 {
@@ -15,8 +14,9 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public override void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement,
         public override void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement,
             SKColor color)
             SKColor color)
         {
         {
+            if (recordedMouseMovement == null || activeLayer == null)
+                throw new ArgumentException("Parameter is null");
             activeLayer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(recordedMouseMovement[0].ToSKPoint(), color);
             activeLayer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(recordedMouseMovement[0].ToSKPoint(), color);
-
         }
         }
     }
     }
 }
 }

+ 40 - 32
PixiEditorTests/ModelsTests/ControllersTests/UndoManagerTests.cs

@@ -1,5 +1,4 @@
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Controllers;
-using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Undo;
 using PixiEditor.Models.Undo;
 using Xunit;
 using Xunit;
 
 
@@ -20,7 +19,7 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestSetRoot()
         public void TestSetRoot()
         {
         {
             PrepareUndoManagerForTest();
             PrepareUndoManagerForTest();
-            UndoManager undoManager = new UndoManager(this);
+            using UndoManager undoManager = new UndoManager(this);
             Assert.Equal(this, undoManager.MainRoot);
             Assert.Equal(this, undoManager.MainRoot);
         }
         }
 
 
@@ -28,9 +27,10 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestAddToUndoStack()
         public void TestAddToUndoStack()
         {
         {
             PrepareUndoManagerForTest();
             PrepareUndoManagerForTest();
-            UndoManager undoManager = new UndoManager(this);
+            using UndoManager undoManager = new UndoManager(this);
 
 
-            undoManager.AddUndoChange(new Change("ExampleProperty", ExampleProperty, ExampleProperty));
+            using var change = new Change("ExampleProperty", ExampleProperty, ExampleProperty);
+            undoManager.AddUndoChange(change);
             Assert.True(undoManager.UndoStack.Count == 1);
             Assert.True(undoManager.UndoStack.Count == 1);
             Assert.True((int)undoManager.UndoStack.Peek().OldValue == ExampleProperty);
             Assert.True((int)undoManager.UndoStack.Peek().OldValue == ExampleProperty);
         }
         }
@@ -39,9 +39,10 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestThatUndoAddsToRedoStack()
         public void TestThatUndoAddsToRedoStack()
         {
         {
             PrepareUndoManagerForTest();
             PrepareUndoManagerForTest();
-            UndoManager undoManager = new UndoManager(this);
+            using UndoManager undoManager = new UndoManager(this);
 
 
-            undoManager.AddUndoChange(new Change("ExampleProperty", ExampleProperty, ExampleProperty));
+            using var change = new Change("ExampleProperty", ExampleProperty, ExampleProperty);
+            undoManager.AddUndoChange(change);
             undoManager.Undo();
             undoManager.Undo();
             Assert.True(undoManager.RedoStack.Count == 1);
             Assert.True(undoManager.RedoStack.Count == 1);
         }
         }
@@ -50,9 +51,10 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestUndo()
         public void TestUndo()
         {
         {
             PrepareUndoManagerForTest();
             PrepareUndoManagerForTest();
-            UndoManager undoManager = new UndoManager(this);
+            using UndoManager undoManager = new UndoManager(this);
 
 
-            undoManager.AddUndoChange(new Change("ExampleProperty", ExampleProperty, 55));
+            using var change = new Change("ExampleProperty", ExampleProperty, 55);
+            undoManager.AddUndoChange(change);
             ExampleProperty = 55;
             ExampleProperty = 55;
             undoManager.Undo();
             undoManager.Undo();
             Assert.True((int)undoManager.RedoStack.Peek().OldValue == ExampleProperty);
             Assert.True((int)undoManager.RedoStack.Peek().OldValue == ExampleProperty);
@@ -62,9 +64,10 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestThatRedoAddsToUndoStack()
         public void TestThatRedoAddsToUndoStack()
         {
         {
             PrepareUndoManagerForTest();
             PrepareUndoManagerForTest();
-            UndoManager undoManager = new UndoManager(this);
+            using UndoManager undoManager = new UndoManager(this);
 
 
-            undoManager.AddUndoChange(new Change("ExampleProperty", ExampleProperty, ExampleProperty));
+            using var change = new Change("ExampleProperty", ExampleProperty, ExampleProperty);
+            undoManager.AddUndoChange(change);
             undoManager.Undo();
             undoManager.Undo();
             undoManager.Redo();
             undoManager.Redo();
             Assert.True(undoManager.UndoStack.Count == 1);
             Assert.True(undoManager.UndoStack.Count == 1);
@@ -74,10 +77,11 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestRedo()
         public void TestRedo()
         {
         {
             PrepareUndoManagerForTest();
             PrepareUndoManagerForTest();
-            UndoManager undoManager = new UndoManager(this);
+            using UndoManager undoManager = new UndoManager(this);
 
 
             ExampleProperty = 55;
             ExampleProperty = 55;
-            undoManager.AddUndoChange(new Change("ExampleProperty", 1, ExampleProperty));
+            using var change = new Change("ExampleProperty", 1, ExampleProperty);
+            undoManager.AddUndoChange(change);
             undoManager.Undo();
             undoManager.Undo();
             undoManager.Redo();
             undoManager.Redo();
             Assert.True((int)undoManager.UndoStack.Peek().NewValue == ExampleProperty);
             Assert.True((int)undoManager.UndoStack.Peek().NewValue == ExampleProperty);
@@ -87,12 +91,13 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestThatUndoManagerUndoAndRedoWithCustomRootCorrectly()
         public void TestThatUndoManagerUndoAndRedoWithCustomRootCorrectly()
         {
         {
             PrepareUndoManagerForTest();
             PrepareUndoManagerForTest();
-            UndoManager undoManager = new UndoManager(this);
+            using UndoManager undoManager = new UndoManager(this);
 
 
             TestPropertyClass testProp = new TestPropertyClass();
             TestPropertyClass testProp = new TestPropertyClass();
             int newVal = 5;
             int newVal = 5;
             testProp.IntProperty = newVal;
             testProp.IntProperty = newVal;
-            undoManager.AddUndoChange(new Change("IntProperty", 0, newVal, root: testProp));
+            using var change = new Change("IntProperty", 0, newVal, root: testProp);
+            undoManager.AddUndoChange(change);
             Assert.Equal(newVal, testProp.IntProperty);
             Assert.Equal(newVal, testProp.IntProperty);
 
 
             undoManager.Undo();
             undoManager.Undo();
@@ -108,16 +113,15 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestThatMixedProcessOfUndoAndRedoWorks()
         public void TestThatMixedProcessOfUndoAndRedoWorks()
         {
         {
             PrepareUndoManagerForTest();
             PrepareUndoManagerForTest();
-            UndoManager undoManager = new UndoManager(this);
+            using UndoManager undoManager = new UndoManager(this);
 
 
             int newVal = 5;
             int newVal = 5;
-
-            undoManager.AddUndoChange(
-                new Change(
+            using var change = new Change(
                     "ExampleProperty",
                     "ExampleProperty",
                     ReverseProcess,
                     ReverseProcess,
                     new object[] { ExampleProperty },
                     new object[] { ExampleProperty },
-                    newVal));
+                    newVal);
+            undoManager.AddUndoChange(change);
 
 
             ExampleProperty = newVal;
             ExampleProperty = newVal;
 
 
@@ -136,14 +140,15 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestThatProcessBasedUndoAndRedoWorks()
         public void TestThatProcessBasedUndoAndRedoWorks()
         {
         {
             PrepareUndoManagerForTest();
             PrepareUndoManagerForTest();
-            UndoManager undoManager = new UndoManager(this);
+            using UndoManager undoManager = new UndoManager(this);
 
 
             int newVal = 5;
             int newVal = 5;
-            undoManager.AddUndoChange(new Change(
+            using var change = new Change(
                 ReverseProcess,
                 ReverseProcess,
                 new object[] { ExampleProperty },
                 new object[] { ExampleProperty },
                 ReverseProcess,
                 ReverseProcess,
-                new object[] { newVal }));
+                new object[] { newVal });
+            undoManager.AddUndoChange(change);
 
 
             ExampleProperty = newVal;
             ExampleProperty = newVal;
 
 
@@ -162,11 +167,11 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestThatNestedPropertyUndoWorks()
         public void TestThatNestedPropertyUndoWorks()
         {
         {
             PrepareUndoManagerForTest();
             PrepareUndoManagerForTest();
-            UndoManager undoManager = new UndoManager(this);
+            using UndoManager undoManager = new UndoManager(this);
 
 
             int newVal = 5;
             int newVal = 5;
-
-            undoManager.AddUndoChange(new Change("TestPropClass.IntProperty", TestPropClass.IntProperty, newVal));
+            using var change = new Change("TestPropClass.IntProperty", TestPropClass.IntProperty, newVal);
+            undoManager.AddUndoChange(change);
 
 
             TestPropClass.IntProperty = newVal;
             TestPropClass.IntProperty = newVal;
 
 
@@ -185,9 +190,10 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestThatFindRootProcessWorks()
         public void TestThatFindRootProcessWorks()
         {
         {
             PrepareUndoManagerForTest();
             PrepareUndoManagerForTest();
-            UndoManager undoManager = new UndoManager(this);
+            using UndoManager undoManager = new UndoManager(this);
 
 
-            undoManager.AddUndoChange(new Change("IntProperty", 0, 5, FindRootProcess, null));
+            using var change1 = new Change("IntProperty", 0, 5, FindRootProcess, null);
+            undoManager.AddUndoChange(change1);
 
 
             Change change = undoManager.UndoStack.Peek();
             Change change = undoManager.UndoStack.Peek();
 
 
@@ -198,9 +204,10 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestThatUndoForFindRootProcessWorks()
         public void TestThatUndoForFindRootProcessWorks()
         {
         {
             PrepareUndoManagerForTest();
             PrepareUndoManagerForTest();
-            UndoManager undoManager = new UndoManager(this);
+            using UndoManager undoManager = new UndoManager(this);
 
 
-            undoManager.AddUndoChange(new Change("IntProperty", 0, 5, FindRootProcess, null));
+            using var change = new Change("IntProperty", 0, 5, FindRootProcess, null);
+            undoManager.AddUndoChange(change);
 
 
             TestPropClass.IntProperty = 5;
             TestPropClass.IntProperty = 5;
 
 
@@ -213,9 +220,10 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestThatUndoAndRedoForFindRootProcessWorks()
         public void TestThatUndoAndRedoForFindRootProcessWorks()
         {
         {
             PrepareUndoManagerForTest();
             PrepareUndoManagerForTest();
-            UndoManager undoManager = new UndoManager(this);
+            using UndoManager undoManager = new UndoManager(this);
 
 
-            undoManager.AddUndoChange(new Change("IntProperty", 0, 5, FindRootProcess, null));
+            using var change = new Change("IntProperty", 0, 5, FindRootProcess, null);
+            undoManager.AddUndoChange(change);
 
 
             TestPropClass.IntProperty = 5;
             TestPropClass.IntProperty = 5;
 
 
@@ -244,4 +252,4 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             TestPropClass = new TestPropertyClass { IntProperty = 0 };
             TestPropClass = new TestPropertyClass { IntProperty = 0 };
         }
         }
     }
     }
-}
+}

+ 1 - 24
PixiEditorTests/ModelsTests/DataHoldersTests/BitmapPixelChangesTests.cs

@@ -1,5 +1,4 @@
-using PixiEditor.Exceptions;
-using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
 using SkiaSharp;
 using SkiaSharp;
 using Xunit;
 using Xunit;
@@ -32,27 +31,5 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
             Assert.Equal(SKColors.Red, output.ChangedPixels[new Coordinates(0, 0)]);
             Assert.Equal(SKColors.Red, output.ChangedPixels[new Coordinates(0, 0)]);
             Assert.Equal(SKColors.Lime, output.ChangedPixels[new Coordinates(1, 0)]);
             Assert.Equal(SKColors.Lime, output.ChangedPixels[new Coordinates(1, 0)]);
         }
         }
-
-        [Fact]
-        public void TestThatFromArraysThrowsError()
-        {
-            Assert.Throws<ArrayLengthMismatchException>(
-                () => BitmapPixelChanges.FromArrays(new[] { new Coordinates(0, 0) }, new[] { SKColors.Red, SKColors.Lime }));
-        }
-
-        [Fact]
-        public void TestThatFormArraysWorks()
-        {
-            Coordinates[] coordinatesArray = { new Coordinates(0, 0), new Coordinates(2, 3), new Coordinates(5, 5) };
-            SKColor[] colorsArray = { SKColors.Red, SKColors.Lime, SKColors.Blue };
-            BitmapPixelChanges result = BitmapPixelChanges.FromArrays(coordinatesArray, colorsArray);
-            for (int i = 0; i < coordinatesArray.Length; i++)
-            {
-                Coordinates cords = coordinatesArray[i];
-                Assert.Equal(colorsArray[i], result.ChangedPixels[cords]);
-            }
-
-            Assert.False(result.WasBuiltAsSingleColored);
-        }
     }
     }
 }
 }

+ 12 - 13
PixiEditorTests/ModelsTests/DataHoldersTests/DocumentLayersTests.cs

@@ -1,6 +1,5 @@
-using System;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.ViewModels.SubViewModels.Main;
+using PixiEditor.Models.DataHolders;
+using System;
 using Xunit;
 using Xunit;
 
 
 namespace PixiEditorTests.ModelsTests.DataHoldersTests
 namespace PixiEditorTests.ModelsTests.DataHoldersTests
@@ -11,7 +10,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatToggleLayerDoesNotToggleLastLayer()
         public void TestThatToggleLayerDoesNotToggleLastLayer()
         {
         {
-            Document doc = new (5, 5);
+            using Document doc = new(5, 5);
             doc.AddNewLayer("layer");
             doc.AddNewLayer("layer");
             bool isActive = doc.Layers[^1].IsActive;
             bool isActive = doc.Layers[^1].IsActive;
             doc.ToggleLayer(0);
             doc.ToggleLayer(0);
@@ -21,7 +20,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatToggleLayerTogglesLayer()
         public void TestThatToggleLayerTogglesLayer()
         {
         {
-            Document doc = new (5, 5);
+            using Document doc = new(5, 5);
             doc.AddNewLayer("layer");
             doc.AddNewLayer("layer");
             doc.AddNewLayer("layer 1");
             doc.AddNewLayer("layer 1");
             doc.Layers[0].IsActive = true;
             doc.Layers[0].IsActive = true;
@@ -35,7 +34,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatToggleLayerDoesNothingOnNonExistingIndex()
         public void TestThatToggleLayerDoesNothingOnNonExistingIndex()
         {
         {
-            Document document = new Document(5, 5);
+            using Document document = new Document(5, 5);
             document.AddNewLayer("test");
             document.AddNewLayer("test");
             document.ToggleLayer(1);
             document.ToggleLayer(1);
             document.ToggleLayer(-1);
             document.ToggleLayer(-1);
@@ -48,7 +47,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [InlineData(1, 1)]
         [InlineData(1, 1)]
         public void TestThatSelectLayersRangeSelectsRange(int startIndex, int endIndex)
         public void TestThatSelectLayersRangeSelectsRange(int startIndex, int endIndex)
         {
         {
-            Document document = new Document(5, 5);
+            using Document document = new Document(5, 5);
 
 
             document.AddNewLayer("1");
             document.AddNewLayer("1");
             document.AddNewLayer("2");
             document.AddNewLayer("2");
@@ -62,7 +61,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
             {
             {
                 Assert.Equal(
                 Assert.Equal(
                     i >= Math.Min(startIndex, endIndex)
                     i >= Math.Min(startIndex, endIndex)
-                    && i <= Math.Max(startIndex, endIndex), 
+                    && i <= Math.Max(startIndex, endIndex),
                     document.Layers[i].IsActive);
                     document.Layers[i].IsActive);
             }
             }
         }
         }
@@ -73,7 +72,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [InlineData(2)]
         [InlineData(2)]
         public void TestThatDeselectAllExceptDeselectsAllExceptLayer(int index)
         public void TestThatDeselectAllExceptDeselectsAllExceptLayer(int index)
         {
         {
-            Document document = new Document(5, 5);
+            using Document document = new Document(5, 5);
 
 
             document.AddNewLayer("1");
             document.AddNewLayer("1");
             document.AddNewLayer("2");
             document.AddNewLayer("2");
@@ -94,7 +93,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatUpdateLayersColorMakesOnlyOneLayerMainColorAndOtherSecondary()
         public void TestThatUpdateLayersColorMakesOnlyOneLayerMainColorAndOtherSecondary()
         {
         {
-            Document document = new Document(1, 1);
+            using Document document = new Document(1, 1);
 
 
             document.AddNewLayer("1");
             document.AddNewLayer("1");
             document.AddNewLayer("2");
             document.AddNewLayer("2");
@@ -114,7 +113,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatUpdateLayersColorMakesLayerMainColorAndRestNonActiveReturnsTransparent()
         public void TestThatUpdateLayersColorMakesLayerMainColorAndRestNonActiveReturnsTransparent()
         {
         {
-            Document document = new Document(1, 1);
+            using Document document = new Document(1, 1);
 
 
             document.AddNewLayer("1");
             document.AddNewLayer("1");
             document.AddNewLayer("2");
             document.AddNewLayer("2");
@@ -134,7 +133,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatSetNextSelectedLayerAsActiveSelectsFirstAvailableLayer()
         public void TestThatSetNextSelectedLayerAsActiveSelectsFirstAvailableLayer()
         {
         {
-            Document document = new Document(1, 1);
+            using Document document = new Document(1, 1);
 
 
             document.AddNewLayer("1");
             document.AddNewLayer("1");
             document.AddNewLayer("2");
             document.AddNewLayer("2");
@@ -151,4 +150,4 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
             Assert.Equal(document.Layers[0].GuidValue, document.ActiveLayerGuid);
             Assert.Equal(document.Layers[0].GuidValue, document.ActiveLayerGuid);
         }
         }
     }
     }
-}
+}

+ 18 - 17
PixiEditorTests/ModelsTests/DataHoldersTests/LayerStructureTests.cs

@@ -10,7 +10,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatAddNewGroupAddsNewGroup()
         public void TestThatAddNewGroupAddsNewGroup()
         {
         {
-            Document doc = new Document(1, 1);
+            using Document doc = new Document(1, 1);
             doc.Layers.Add(new("_testLayer", 1, 1));
             doc.Layers.Add(new("_testLayer", 1, 1));
             var testLayer = doc.Layers[^1];
             var testLayer = doc.Layers[^1];
             doc.LayerStructure.AddNewGroup("test", testLayer.GuidValue);
             doc.LayerStructure.AddNewGroup("test", testLayer.GuidValue);
@@ -23,7 +23,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatAddNewGroupAddsNewGroupAsASubgroup()
         public void TestThatAddNewGroupAddsNewGroupAsASubgroup()
         {
         {
-            Document doc = new Document(1, 1);
+            using Document doc = new Document(1, 1);
             doc.Layers.Add(new("_testLayer", 1, 1));
             doc.Layers.Add(new("_testLayer", 1, 1));
             var testLayer = doc.Layers[^1];
             var testLayer = doc.Layers[^1];
             doc.LayerStructure.AddNewGroup("test", testLayer.GuidValue);
             doc.LayerStructure.AddNewGroup("test", testLayer.GuidValue);
@@ -40,7 +40,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatMoveGroupMovesSwapsLayerPlacesWithOtherGroup()
         public void TestThatMoveGroupMovesSwapsLayerPlacesWithOtherGroup()
         {
         {
-            Document doc = new Document(1, 1);
+            using Document doc = new Document(1, 1);
             doc.Layers.Add(new Layer("_testLayer", 1, 1));
             doc.Layers.Add(new Layer("_testLayer", 1, 1));
             doc.Layers.Add(new Layer("_testLayer1", 1, 1));
             doc.Layers.Add(new Layer("_testLayer1", 1, 1));
             var testLayer = doc.Layers[0];
             var testLayer = doc.Layers[0];
@@ -60,7 +60,8 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatIsChildOfDetectsNestedGroupCorrectly()
         public void TestThatIsChildOfDetectsNestedGroupCorrectly()
         {
         {
-            LayerStructure ls = new LayerStructure(new Document(1, 1));
+            using var doc = new Document(1, 1);
+            LayerStructure ls = new LayerStructure(doc);
             Layer testLayer = new Layer("tst", 1, 1);
             Layer testLayer = new Layer("tst", 1, 1);
             ls.Groups.Add(new GuidStructureItem("group 1", testLayer.GuidValue));
             ls.Groups.Add(new GuidStructureItem("group 1", testLayer.GuidValue));
             ls.Groups[0].Subgroups.Add(new GuidStructureItem("group 1 nested", testLayer.GuidValue));
             ls.Groups[0].Subgroups.Add(new GuidStructureItem("group 1 nested", testLayer.GuidValue));
@@ -72,7 +73,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatIsChildOfDetectsNestedLayersCorrectly()
         public void TestThatIsChildOfDetectsNestedLayersCorrectly()
         {
         {
-            var doc = new Document(1, 1);
+            using var doc = new Document(1, 1);
             doc.Layers.Add(new Layer("tst", 1, 1));
             doc.Layers.Add(new Layer("tst", 1, 1));
             Guid testLayerGuid = doc.Layers[0].GuidValue;
             Guid testLayerGuid = doc.Layers[0].GuidValue;
             LayerStructure ls = new LayerStructure(doc);
             LayerStructure ls = new LayerStructure(doc);
@@ -86,7 +87,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatGroupContainsOnlyLayerDetectsOnlySingleLayerCorrectly()
         public void TestThatGroupContainsOnlyLayerDetectsOnlySingleLayerCorrectly()
         {
         {
-            var doc = new Document(1, 1);
+            using var doc = new Document(1, 1);
             doc.Layers.Add(new Layer("layer", 1, 1));
             doc.Layers.Add(new Layer("layer", 1, 1));
             var guid = doc.Layers[0].GuidValue;
             var guid = doc.Layers[0].GuidValue;
             doc.LayerStructure.AddNewGroup("layer group", guid);
             doc.LayerStructure.AddNewGroup("layer group", guid);
@@ -96,7 +97,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatGroupContainsOnlyLayerDetectsOnlySingleLayerThatIsNested()
         public void TestThatGroupContainsOnlyLayerDetectsOnlySingleLayerThatIsNested()
         {
         {
-            var doc = new Document(1, 1);
+            using var doc = new Document(1, 1);
             doc.Layers.Add(new Layer("layer", 1, 1));
             doc.Layers.Add(new Layer("layer", 1, 1));
             var guid = doc.Layers[0].GuidValue;
             var guid = doc.Layers[0].GuidValue;
             doc.LayerStructure.AddNewGroup("layer group", guid);
             doc.LayerStructure.AddNewGroup("layer group", guid);
@@ -108,7 +109,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatCloneReturnsSameLayerStructure()
         public void TestThatCloneReturnsSameLayerStructure()
         {
         {
-            Document doc = new(1, 1);
+            using Document doc = new(1, 1);
             doc.Layers.Add(new("Test", 1, 1));
             doc.Layers.Add(new("Test", 1, 1));
             doc.Layers.Add(new("Test2", 1, 1));
             doc.Layers.Add(new("Test2", 1, 1));
             LayerStructure structure = new(doc);
             LayerStructure structure = new(doc);
@@ -124,7 +125,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatGetGroupByGuidReturnsNullForNonExistingGroup()
         public void TestThatGetGroupByGuidReturnsNullForNonExistingGroup()
         {
         {
-            Document doc = new(1, 1);
+            using Document doc = new(1, 1);
             doc.Layers.Add(new("Test", 1, 1));
             doc.Layers.Add(new("Test", 1, 1));
 
 
             Assert.Null(doc.LayerStructure.GetGroupByGuid(null));
             Assert.Null(doc.LayerStructure.GetGroupByGuid(null));
@@ -134,7 +135,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatGetGroupByGuidReturnsGroupCorrectly()
         public void TestThatGetGroupByGuidReturnsGroupCorrectly()
         {
         {
-            Document doc = new(1, 1);
+            using Document doc = new(1, 1);
             doc.Layers.Add(new("Test", 1, 1));
             doc.Layers.Add(new("Test", 1, 1));
             var group = doc.LayerStructure.AddNewGroup("Test group", doc.Layers[0].GuidValue);
             var group = doc.LayerStructure.AddNewGroup("Test group", doc.Layers[0].GuidValue);
 
 
@@ -144,12 +145,12 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatPreMoveReassignBoundsMakesNestedGroupEmptyAndRemovesItAndParent()
         public void TestThatPreMoveReassignBoundsMakesNestedGroupEmptyAndRemovesItAndParent()
         {
         {
-            Document doc = new(1, 1);
+            using Document doc = new(1, 1);
             doc.Layers.Add(new("Test", 1, 1));
             doc.Layers.Add(new("Test", 1, 1));
             doc.LayerStructure.AddNewGroup("Test group", doc.Layers[0].GuidValue);
             doc.LayerStructure.AddNewGroup("Test group", doc.Layers[0].GuidValue);
             var group1 = doc.LayerStructure.AddNewGroup("Test group nested", doc.Layers[0].GuidValue);
             var group1 = doc.LayerStructure.AddNewGroup("Test group nested", doc.Layers[0].GuidValue);
 
 
-            doc.LayerStructure.PreMoveReassignBounds(new GroupData(group1.GroupGuid), doc.Layers[0].GuidValue);
+            doc.LayerStructure.Unassign(new GroupData(group1.GroupGuid), doc.Layers[0].GuidValue);
 
 
             Assert.Empty(doc.LayerStructure.Groups);
             Assert.Empty(doc.LayerStructure.Groups);
         }
         }
@@ -157,7 +158,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatPostMoveReassignBoundsAssignsNewLayerToGroup()
         public void TestThatPostMoveReassignBoundsAssignsNewLayerToGroup()
         {
         {
-            Document doc = new(1, 1);
+            using Document doc = new(1, 1);
             doc.Layers.Add(new("Test", 1, 1));
             doc.Layers.Add(new("Test", 1, 1));
             doc.LayerStructure.AddNewGroup("Test group", doc.Layers[0].GuidValue);
             doc.LayerStructure.AddNewGroup("Test group", doc.Layers[0].GuidValue);
             var group1 = doc.LayerStructure.AddNewGroup("Test group nested", doc.Layers[0].GuidValue);
             var group1 = doc.LayerStructure.AddNewGroup("Test group nested", doc.Layers[0].GuidValue);
@@ -167,7 +168,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
             var firstLayer = doc.Layers[0];
             var firstLayer = doc.Layers[0];
             var layer = doc.Layers[^1];
             var layer = doc.Layers[^1];
 
 
-            doc.LayerStructure.PostMoveReassignBounds(new GroupData(group1.GroupGuid), layer.GuidValue);
+            doc.LayerStructure.Assign(new GroupData(group1.GroupGuid), layer.GuidValue);
 
 
             Assert.Single(doc.LayerStructure.Groups);
             Assert.Single(doc.LayerStructure.Groups);
             Assert.Single(doc.LayerStructure.Groups[0].Subgroups);
             Assert.Single(doc.LayerStructure.Groups[0].Subgroups);
@@ -180,7 +181,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatAssignParentAssignsParent()
         public void TestThatAssignParentAssignsParent()
         {
         {
-            Document doc = new(1, 1);
+            using Document doc = new(1, 1);
             doc.Layers.Add(new Layer("Test", 1, 1));
             doc.Layers.Add(new Layer("Test", 1, 1));
 
 
             var firstLayer = doc.Layers[0];
             var firstLayer = doc.Layers[0];
@@ -200,7 +201,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatAssignParentDeAssignsParentOnNull()
         public void TestThatAssignParentDeAssignsParentOnNull()
         {
         {
-            Document doc = new(1, 1);
+            using Document doc = new(1, 1);
             doc.Layers.Add(new Layer("Test", 1, 1));
             doc.Layers.Add(new Layer("Test", 1, 1));
 
 
             var firstLayer = doc.Layers[0];
             var firstLayer = doc.Layers[0];
@@ -221,7 +222,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatGetGroupLayersReturnsAllLayersInGroup()
         public void TestThatGetGroupLayersReturnsAllLayersInGroup()
         {
         {
-            Document doc = new(1, 1);
+            using Document doc = new(1, 1);
             doc.Layers.Add(new Layer("Test", 1, 1));
             doc.Layers.Add(new Layer("Test", 1, 1));
             doc.Layers.Add(new Layer("Test 1", 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 2", 1, 1));

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

@@ -13,7 +13,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatSetSelectionNewSetsCorrectSelection()
         public void TestThatSetSelectionNewSetsCorrectSelection()
         {
         {
-            Selection selection = new Selection(Array.Empty<Coordinates>(), new (10, 10));
+            Selection selection = new Selection(Array.Empty<Coordinates>(), new(10, 10));
             Coordinates[] points = { new Coordinates(0, 0), new Coordinates(1, 1) };
             Coordinates[] points = { new Coordinates(0, 0), new Coordinates(1, 1) };
 
 
             selection.SetSelection(points, SelectionType.New);
             selection.SetSelection(points, SelectionType.New);
@@ -62,7 +62,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         [Fact]
         public void TestThatUndoWorks()
         public void TestThatUndoWorks()
         {
         {
-            Document document = new Document(10, 10);
+            using Document document = new Document(10, 10);
 
 
             IEnumerable<Coordinates> oldSelection = new List<Coordinates>(document.ActiveSelection.SelectedPoints);
             IEnumerable<Coordinates> oldSelection = new List<Coordinates>(document.ActiveSelection.SelectedPoints);
 
 

+ 2 - 0
PixiEditorTests/ModelsTests/DataHoldersTests/SurfaceTests.cs

@@ -4,7 +4,9 @@ using Xunit;
 
 
 namespace PixiEditorTests.ModelsTests.DataHoldersTests
 namespace PixiEditorTests.ModelsTests.DataHoldersTests
 {
 {
+#pragma warning disable CA1001 // Types that own disposable fields should be disposable
     public class SurfaceTests
     public class SurfaceTests
+#pragma warning restore CA1001 // Types that own disposable fields should be disposable
     {
     {
         SKColor redColor = new SKColor(254, 2, 3);
         SKColor redColor = new SKColor(254, 2, 3);
         SKColor greenColor = new SKColor(6, 224, 3);
         SKColor greenColor = new SKColor(6, 224, 3);

+ 0 - 39
PixiEditorTests/ModelsTests/ImageManipulationTests/TransformTests.cs

@@ -1,39 +0,0 @@
-using PixiEditor.Models.ImageManipulation;
-using PixiEditor.Models.Position;
-using Xunit;
-
-namespace PixiEditorTests.ModelsTests.ImageManipulationTests
-{
-    public class TransformTests
-    {
-        [Theory]
-        [InlineData(0, 0, 1, 1, 1, 1)]
-        [InlineData(1, 1, 0, 0, -1, -1)]
-        [InlineData(5, 5, 4, 6, -1, 1)]
-        [InlineData(-15, -15, -16, -16, -1, -1)]
-        [InlineData(150, 150, 1150, 1150, 1000, 1000)]
-        public void TestGetTranslation(int x1, int y1, int x2, int y2, int expectedX, int expectedY)
-        {
-            Coordinates translation = Transform.GetTranslation(new Coordinates(x1, y1), new Coordinates(x2, y2));
-            Assert.Equal(new Coordinates(expectedX, expectedY), translation);
-        }
-
-        [Theory]
-        [InlineData(0, 0)]
-        [InlineData(1, 1)]
-        [InlineData(5, 2)]
-        [InlineData(50, 150)]
-        [InlineData(-5, -52)]
-        public void TestTranslate(int vectorX, int vectorY)
-        {
-            Coordinates[] points = { new Coordinates(0, 0), new Coordinates(5, 5), new Coordinates(15, 2) };
-            Coordinates[] translatedCords = Transform.Translate(points, new Coordinates(vectorX, vectorY));
-
-            for (int i = 0; i < points.Length; i++)
-            {
-                Assert.Equal(points[i].X + vectorX, translatedCords[i].X);
-                Assert.Equal(points[i].Y + vectorY, translatedCords[i].Y);
-            }
-        }
-    }
-}

+ 5 - 1
PixiEditorTests/ModelsTests/LayersTests/LayersTestHelper.cs

@@ -7,6 +7,9 @@ namespace PixiEditorTests.ModelsTests.LayersTests
     {
     {
         public static void LayersAreEqual(Layer expected, Layer actual)
         public static void LayersAreEqual(Layer expected, Layer actual)
         {
         {
+            Assert.NotNull(actual);
+            Assert.NotNull(expected);
+#pragma warning disable CA1062 // Validate arguments of public methods
             Assert.Equal(expected.Name, actual.Name);
             Assert.Equal(expected.Name, actual.Name);
             Assert.Equal(expected.Offset, actual.Offset);
             Assert.Equal(expected.Offset, actual.Offset);
             Assert.Equal(expected.Width, actual.Width);
             Assert.Equal(expected.Width, actual.Width);
@@ -17,6 +20,7 @@ namespace PixiEditorTests.ModelsTests.LayersTests
             Assert.Equal(expected.IsVisible, actual.IsVisible);
             Assert.Equal(expected.IsVisible, actual.IsVisible);
             Assert.Equal(expected.IsRenaming, actual.IsRenaming);
             Assert.Equal(expected.IsRenaming, actual.IsRenaming);
             Assert.Equal(expected.ConvertBitmapToBytes(), actual.ConvertBitmapToBytes());
             Assert.Equal(expected.ConvertBitmapToBytes(), actual.ConvertBitmapToBytes());
+#pragma warning restore CA1062 // Validate arguments of public methods
         }
         }
     }
     }
-}
+}

+ 3 - 2
PixiEditorTests/ModelsTests/PositionTests/CoordinatesTests.cs

@@ -1,4 +1,5 @@
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
+using System.Globalization;
 using Xunit;
 using Xunit;
 
 
 namespace PixiEditorTests.ModelsTests.PositionTests
 namespace PixiEditorTests.ModelsTests.PositionTests
@@ -10,7 +11,7 @@ namespace PixiEditorTests.ModelsTests.PositionTests
         {
         {
             Coordinates cords = new Coordinates(5, 5);
             Coordinates cords = new Coordinates(5, 5);
 
 
-            Assert.Equal("5, 5", cords.ToString());
+            Assert.Equal("5, 5", cords.ToString(CultureInfo.InvariantCulture));
         }
         }
 
 
         [Fact]
         [Fact]
@@ -22,4 +23,4 @@ namespace PixiEditorTests.ModelsTests.PositionTests
             Assert.True(cords != cords2);
             Assert.True(cords != cords2);
         }
         }
     }
     }
-}
+}

+ 16 - 17
PixiEditorTests/ModelsTests/UndoTests/StorageBasedChangeTests.cs

@@ -5,7 +5,6 @@ using PixiEditor.Models.Undo;
 using PixiEditorTests.ModelsTests.LayersTests;
 using PixiEditorTests.ModelsTests.LayersTests;
 using SkiaSharp;
 using SkiaSharp;
 using System;
 using System;
-using System.Collections.ObjectModel;
 using System.IO;
 using System.IO;
 using Xunit;
 using Xunit;
 
 
@@ -23,11 +22,11 @@ namespace PixiEditorTests.ModelsTests.UndoTests
             }
             }
         }
         }
 
 
-        public Document GenerateTestDocument()
+        public static Document GenerateTestDocument()
         {
         {
-            Document testDocument = new Document(10, 10);
-            Surface testBitmap = new Surface(10, 10);
-            Surface testBitmap2 = new Surface(5, 8);
+            using Document testDocument = new Document(10, 10);
+            using Surface testBitmap = new Surface(10, 10);
+            using Surface testBitmap2 = new Surface(5, 8);
             testBitmap.SetSRGBPixel(0, 0, SKColors.Black);
             testBitmap.SetSRGBPixel(0, 0, SKColors.Black);
             testBitmap2.SetSRGBPixel(4, 4, SKColors.Blue);
             testBitmap2.SetSRGBPixel(4, 4, SKColors.Blue);
             Random random = new Random();
             Random random = new Random();
@@ -42,9 +41,9 @@ namespace PixiEditorTests.ModelsTests.UndoTests
         [Fact]
         [Fact]
         public void TestThatConstructorGeneratesUndoLayersProperly()
         public void TestThatConstructorGeneratesUndoLayersProperly()
         {
         {
-            Document testDocument = GenerateTestDocument();
+            using Document testDocument = GenerateTestDocument();
 
 
-            StorageBasedChange change = new StorageBasedChange(testDocument, testDocument.Layers, UndoStoreLocation);
+            using StorageBasedChange change = new StorageBasedChange(testDocument, testDocument.Layers, UndoStoreLocation);
 
 
             Assert.Equal(testDocument.Layers.Count, change.StoredLayers.Length);
             Assert.Equal(testDocument.Layers.Count, change.StoredLayers.Length);
 
 
@@ -69,9 +68,9 @@ namespace PixiEditorTests.ModelsTests.UndoTests
         [Fact]
         [Fact]
         public void TestThatSaveLayersOnDeviceSavesLayers()
         public void TestThatSaveLayersOnDeviceSavesLayers()
         {
         {
-            Document document = GenerateTestDocument();
+            using Document document = GenerateTestDocument();
 
 
-            StorageBasedChange change = new StorageBasedChange(document, document.Layers, UndoStoreLocation);
+            using StorageBasedChange change = new StorageBasedChange(document, document.Layers, UndoStoreLocation);
 
 
             foreach (var layer in change.StoredLayers)
             foreach (var layer in change.StoredLayers)
             {
             {
@@ -83,9 +82,9 @@ namespace PixiEditorTests.ModelsTests.UndoTests
         [Fact]
         [Fact]
         public void TestThatLoadLayersFromDeviceLoadsLayers()
         public void TestThatLoadLayersFromDeviceLoadsLayers()
         {
         {
-            Document document = GenerateTestDocument();
+            using Document document = GenerateTestDocument();
 
 
-            StorageBasedChange change = new StorageBasedChange(document, document.Layers, UndoStoreLocation);
+            using StorageBasedChange change = new StorageBasedChange(document, document.Layers, UndoStoreLocation);
 
 
             Layer[] layers = change.LoadLayersFromDevice();
             Layer[] layers = change.LoadLayersFromDevice();
 
 
@@ -101,9 +100,9 @@ namespace PixiEditorTests.ModelsTests.UndoTests
         [Fact]
         [Fact]
         public void TestThatUndoInvokesLoadFromDeviceAndExecutesProcess()
         public void TestThatUndoInvokesLoadFromDeviceAndExecutesProcess()
         {
         {
-            Document document = GenerateTestDocument();
+            using Document document = GenerateTestDocument();
 
 
-            StorageBasedChange change = new StorageBasedChange(document, document.Layers, UndoStoreLocation);
+            using StorageBasedChange change = new StorageBasedChange(document, document.Layers, UndoStoreLocation);
             bool undoInvoked = false;
             bool undoInvoked = false;
 
 
             Action<Layer[], UndoLayer[]> testUndoProcess = (layers, data) =>
             Action<Layer[], UndoLayer[]> testUndoProcess = (layers, data) =>
@@ -120,7 +119,7 @@ namespace PixiEditorTests.ModelsTests.UndoTests
             Action<object[]> testRedoProcess = parameters => { };
             Action<object[]> testRedoProcess = parameters => { };
 
 
             Change undoChange = change.ToChange(testUndoProcess, testRedoProcess, null);
             Change undoChange = change.ToChange(testUndoProcess, testRedoProcess, null);
-            UndoManager manager = new UndoManager(this);
+            using UndoManager manager = new UndoManager(this);
 
 
             manager.AddUndoChange(undoChange);
             manager.AddUndoChange(undoChange);
             manager.Undo();
             manager.Undo();
@@ -131,9 +130,9 @@ namespace PixiEditorTests.ModelsTests.UndoTests
         [Fact]
         [Fact]
         public void TestThatRedoInvokesSaveToDeviceAndExecutesProcess()
         public void TestThatRedoInvokesSaveToDeviceAndExecutesProcess()
         {
         {
-            Document document = GenerateTestDocument();
+            using Document document = GenerateTestDocument();
 
 
-            StorageBasedChange change = new StorageBasedChange(document, document.Layers, UndoStoreLocation);
+            using StorageBasedChange change = new StorageBasedChange(document, document.Layers, UndoStoreLocation);
             bool redoInvoked = false;
             bool redoInvoked = false;
 
 
             Action<Layer[], UndoLayer[]> testUndoProcess = (layers, data) => { };
             Action<Layer[], UndoLayer[]> testUndoProcess = (layers, data) => { };
@@ -152,7 +151,7 @@ namespace PixiEditorTests.ModelsTests.UndoTests
             };
             };
 
 
             Change undoChange = change.ToChange(testUndoProcess, testRedoProcess, new object[] { 2 });
             Change undoChange = change.ToChange(testUndoProcess, testRedoProcess, new object[] { 2 });
-            UndoManager manager = new UndoManager(this);
+            using UndoManager manager = new UndoManager(this);
 
 
             manager.AddUndoChange(undoChange);
             manager.AddUndoChange(undoChange);
             manager.Undo();
             manager.Undo();