Browse Source

Merge branch 'master' into supporter-pack

Krzysztof Krysiński 2 years ago
parent
commit
060b2aacb8
70 changed files with 865 additions and 406 deletions
  1. 48 0
      src/PixiEditor.ChangeableDocument/Changes/Root/Crop_Change.cs
  2. 8 2
      src/PixiEditor.ChangeableDocument/Changes/Selection/SelectEllipse_UpdateableChange.cs
  3. 6 2
      src/PixiEditor.ChangeableDocument/Changes/Selection/SelectLasso_UpdateableChange.cs
  4. 5 4
      src/PixiEditor.ChangeableDocument/Changes/Selection/SelectRectangle_UpdateableChange.cs
  5. 25 0
      src/PixiEditor.DrawingApi.Core/Numerics/VecI.cs
  6. 23 2
      src/PixiEditor.Extensions/UI/Translator.cs
  7. 11 1
      src/PixiEditor/Data/Localization/Languages/en.json
  8. 7 2
      src/PixiEditor/Helpers/ClipboardHelper.cs
  9. 4 0
      src/PixiEditor/Helpers/DocumentViewModelBuilder.cs
  10. 3 3
      src/PixiEditor/Helpers/EnumExtension.cs
  11. 22 0
      src/PixiEditor/Helpers/Extensions/EnumHelpers.cs
  12. BIN
      src/PixiEditor/Images/Add-reference.png
  13. BIN
      src/PixiEditor/Images/Commands/PixiEditor/Selection/CropToSelection.png
  14. BIN
      src/PixiEditor/Images/SocialMedia/SteamIcon.png
  15. 2 2
      src/PixiEditor/Models/Commands/Attributes/Commands/BasicAttribute.cs
  16. 1 1
      src/PixiEditor/Models/Commands/Attributes/Commands/CommandAttribute.cs
  17. 2 2
      src/PixiEditor/Models/Commands/Attributes/Commands/DebugAttribute.cs
  18. 2 2
      src/PixiEditor/Models/Commands/Attributes/Commands/FilterAttribute.cs
  19. 1 1
      src/PixiEditor/Models/Commands/Attributes/Commands/GroupAttribute.cs
  20. 2 2
      src/PixiEditor/Models/Commands/Attributes/Commands/InternalAttribute.cs
  21. 2 2
      src/PixiEditor/Models/Commands/Attributes/Evaluators/CanExecuteAttribute.cs
  22. 1 1
      src/PixiEditor/Models/Commands/Attributes/Evaluators/EvaluatorAttribute.cs
  23. 1 1
      src/PixiEditor/Models/Commands/Attributes/Evaluators/IconAttribute.cs
  24. 5 0
      src/PixiEditor/Models/Commands/Attributes/InternalNameAttribute.cs
  25. 68 44
      src/PixiEditor/Models/Controllers/ClipboardController.cs
  26. 3 2
      src/PixiEditor/Models/DataHolders/CrashReport.cs
  27. 0 72
      src/PixiEditor/Models/Dialogs/ImportFileDialog.cs
  28. 22 3
      src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs
  29. 3 2
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/BrightnessToolExecutor.cs
  30. 13 0
      src/PixiEditor/Models/Enums/RightClickMode.cs
  31. 6 5
      src/PixiEditor/Models/IO/Importer.cs
  32. 6 0
      src/PixiEditor/Models/Preferences/PreferencesSettings.cs
  33. 6 0
      src/PixiEditor/PixiEditor.csproj
  34. 52 14
      src/PixiEditor/Styles/ComboBoxDarkStyle.xaml
  35. 26 1
      src/PixiEditor/ViewModels/MainVM.cs
  36. 29 0
      src/PixiEditor/ViewModels/MainVmEnum.cs
  37. 11 0
      src/PixiEditor/ViewModels/SubViewModels/Main/ColorsViewModel.cs
  38. 4 4
      src/PixiEditor/ViewModels/SubViewModels/Main/DebugViewModel.cs
  39. 12 34
      src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs
  40. 115 12
      src/PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs
  41. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs
  42. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Main/MiscViewModel.cs
  43. 8 0
      src/PixiEditor/ViewModels/SubViewModels/Main/SelectionViewModel.cs
  44. 39 4
      src/PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs
  45. 5 0
      src/PixiEditor/ViewModels/SubViewModels/Tools/ShapeTool.cs
  46. 16 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/ToolViewModel.cs
  47. 3 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/BrightnessToolViewModel.cs
  48. 3 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/ColorPickerToolViewModel.cs
  49. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/EllipseToolViewModel.cs
  50. 3 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/EraserToolViewModel.cs
  51. 6 2
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/FloodFillToolViewModel.cs
  52. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/LassoToolViewModel.cs
  53. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/LineToolViewModel.cs
  54. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/MagicWandToolViewModel.cs
  55. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/MoveToolViewModel.cs
  56. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/PenToolViewModel.cs
  57. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/RectangleToolViewModel.cs
  58. 2 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/SelectToolViewModel.cs
  59. 8 3
      src/PixiEditor/Views/Dialogs/AboutPopup.xaml
  60. 4 1
      src/PixiEditor/Views/Dialogs/HelloTherePopup.xaml
  61. 0 52
      src/PixiEditor/Views/Dialogs/ImportFilePopup.xaml
  62. 0 48
      src/PixiEditor/Views/Dialogs/ImportFilePopup.xaml.cs
  63. 12 3
      src/PixiEditor/Views/Dialogs/SettingsWindow.xaml
  64. 18 4
      src/PixiEditor/Views/MainWindow.xaml
  65. 26 0
      src/PixiEditor/Views/MainWindow.xaml.cs
  66. 13 13
      src/PixiEditor/Views/UserControls/AnchorPointPicker.xaml
  67. 10 9
      src/PixiEditor/Views/UserControls/Layers/FolderControl.xaml
  68. 1 1
      src/PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml
  69. 97 29
      src/PixiEditorGen/CommandNameListGenerator.cs
  70. 25 0
      src/PixiEditorGen/Extensions.cs

+ 48 - 0
src/PixiEditor.ChangeableDocument/Changes/Root/Crop_Change.cs

@@ -0,0 +1,48 @@
+using PixiEditor.ChangeableDocument.ChangeInfos.Root;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changes.Root;
+
+internal class Crop_Change : ResizeBasedChangeBase
+{
+    private RectI rect;
+    
+    [GenerateMakeChangeAction]
+    public Crop_Change(RectI rect)
+    {
+        this.rect = rect;
+    }
+
+    public override bool InitializeAndValidate(Document target)
+    {
+        return base.InitializeAndValidate(target) && rect is { Width: > 0, Height: > 0 };
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        if (_originalSize == rect.Size)
+        {
+            ignoreInUndo = true;
+            return new None();
+        }
+
+        target.Size = rect.Size;
+        target.VerticalSymmetryAxisX = Math.Clamp(_originalVerAxisX - rect.Pos.X, 0, rect.Size.X);
+        target.HorizontalSymmetryAxisY = Math.Clamp(_originalHorAxisY - rect.Pos.Y, 0, rect.Size.Y);
+
+        target.ForEveryMember((member) =>
+        {
+            if (member is Layer layer)
+            {
+                Resize(layer.LayerImage, layer.GuidValue, rect.Size, rect.Pos * -1, deletedChunks);
+            }
+            if (member.Mask is null)
+                return;
+
+            Resize(member.Mask, member.GuidValue, rect.Size, rect.Pos * -1, deletedMaskChunks);
+        });
+        
+        ignoreInUndo = false;
+        return new Size_ChangeInfo(rect.Size, target.VerticalSymmetryAxisX, target.HorizontalSymmetryAxisY);
+    }
+}

+ 8 - 2
src/PixiEditor.ChangeableDocument/Changes/Selection/SelectEllipse_UpdateableChange.cs

@@ -7,6 +7,7 @@ namespace PixiEditor.ChangeableDocument.Changes.Selection;
 internal class SelectEllipse_UpdateableChange : UpdateableChange
 {
     private RectI borders;
+    private VectorPath? documentConstraint;
     private readonly SelectionMode mode;
     private VectorPath? originalPath;
 
@@ -26,6 +27,8 @@ internal class SelectEllipse_UpdateableChange : UpdateableChange
     public override bool InitializeAndValidate(Document target)
     {
         originalPath = new VectorPath(target.Selection.SelectionPath);
+        documentConstraint = new VectorPath();
+        documentConstraint.AddRect(new RectI(VecI.Zero, target.Size));
         return true;
     }
 
@@ -35,11 +38,13 @@ internal class SelectEllipse_UpdateableChange : UpdateableChange
         if (!borders.IsZeroArea)
             ellipsePath.AddOval(borders);
 
+        using var inConstraint = ellipsePath.Op(documentConstraint!, VectorPathOp.Intersect);
+
         var toDispose = target.Selection.SelectionPath;
         if (mode == SelectionMode.New)
-            target.Selection.SelectionPath = new(ellipsePath);
+            target.Selection.SelectionPath = new(inConstraint);
         else
-            target.Selection.SelectionPath = originalPath!.Op(ellipsePath, mode.ToVectorPathOp());
+            target.Selection.SelectionPath = originalPath!.Op(inConstraint, mode.ToVectorPathOp());
         toDispose.Dispose();
 
         return new Selection_ChangeInfo(new VectorPath(target.Selection.SelectionPath));
@@ -67,5 +72,6 @@ internal class SelectEllipse_UpdateableChange : UpdateableChange
     public override void Dispose()
     {
         originalPath?.Dispose();
+        documentConstraint?.Dispose();
     }
 }

+ 6 - 2
src/PixiEditor.ChangeableDocument/Changes/Selection/SelectLasso_UpdateableChange.cs

@@ -5,6 +5,8 @@ using PixiEditor.DrawingApi.Core.Surface.Vector;
 namespace PixiEditor.ChangeableDocument.Changes.Selection;
 internal class SelectLasso_UpdateableChange : UpdateableChange
 {
+    private RectI constraint;
+    private VecI initialPoint;
     private VectorPath? originalPath;
     private VectorPath path = new() { FillType = PathFillType.EvenOdd };
     private readonly SelectionMode mode;
@@ -12,18 +14,20 @@ internal class SelectLasso_UpdateableChange : UpdateableChange
     [GenerateUpdateableChangeActions]
     public SelectLasso_UpdateableChange(VecI point, SelectionMode mode)
     {
-        path.MoveTo(point);
+        initialPoint = point;
         this.mode = mode;
     }
 
     [UpdateChangeMethod]
     public void Update(VecI point)
     {
-        path.LineTo(point);
+        path.LineTo(point.KeepInside(constraint));
     }
 
     public override bool InitializeAndValidate(Document target)
     {
+        constraint = new RectI(VecI.Zero, target.Size);
+        path.MoveTo(initialPoint.KeepInside(constraint));
         originalPath = new VectorPath(target.Selection.SelectionPath);
         return true;
     }

+ 5 - 4
src/PixiEditor.ChangeableDocument/Changes/Selection/SelectRectangle_UpdateableChange.cs

@@ -33,10 +33,11 @@ internal class SelectRectangle_UpdateableChange : UpdateableChange
         using var rectPath = new VectorPath() { FillType = PathFillType.EvenOdd };
         if (!rect.IsZeroArea)
         {
-            rectPath.MoveTo(rect.TopLeft);
-            rectPath.LineTo(rect.TopRight);
-            rectPath.LineTo(rect.BottomRight);
-            rectPath.LineTo(rect.BottomLeft);
+            var constrained = rect.Intersect(new RectI(VecI.Zero, target.Size));
+            rectPath.MoveTo(constrained.TopLeft);
+            rectPath.LineTo(constrained.TopRight);
+            rectPath.LineTo(constrained.BottomRight);
+            rectPath.LineTo(constrained.BottomLeft);
             rectPath.Close();
         }
 

+ 25 - 0
src/PixiEditor.DrawingApi.Core/Numerics/VecI.cs

@@ -16,6 +16,7 @@ public struct VecI : IEquatable<VecI>
 
     public static VecI Zero { get; } = new(0, 0);
     public static VecI One { get; } = new(1, 1);
+    public static VecI NegativeOne { get; } = new(-1, -1);
 
     public VecI(int x, int y)
     {
@@ -67,6 +68,30 @@ public struct VecI : IEquatable<VecI>
     {
         return new(X, 2 * lineY - Y);
     }
+
+    public VecI KeepInside(RectI rect)
+    {
+        return new VecI(Math.Clamp(X, rect.Left, rect.Right), Math.Clamp(Y, rect.Top, rect.Bottom));
+    }
+
+    public byte[] ToByteArray()
+    {
+        var data = new byte[sizeof(int) * 2];
+
+        BitConverter.TryWriteBytes(data, X);
+        BitConverter.TryWriteBytes(data.AsSpan(4), Y);
+
+        return data;
+    }
+
+    public static VecI FromBytes(ReadOnlySpan<byte> value)
+    {
+        var x = BitConverter.ToInt32(value);
+        var y = BitConverter.ToInt32(value[4..]);
+
+        return new VecI(x, y);
+    }
+    
     public static VecI operator +(VecI a, VecI b)
     {
         return new VecI(a.X + b.X, a.Y + b.Y);

+ 23 - 2
src/PixiEditor.Extensions/UI/Translator.cs

@@ -23,6 +23,12 @@ public class Translator : UIElement
         typeof(Translator),
         new FrameworkPropertyMetadata(default(LocalizedString), FrameworkPropertyMetadataOptions.AffectsRender, LocalizedStringPropertyChangedCallback));
 
+    public static readonly DependencyProperty EnumProperty = DependencyProperty.RegisterAttached(
+        "Enum",
+        typeof(object),
+        typeof(Translator),
+        new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, EnumPropertyChangedCallback));
+
     public static readonly DependencyProperty TooltipKeyProperty = DependencyProperty.RegisterAttached(
         "TooltipKey", typeof(string), typeof(Translator), 
         new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.AffectsRender, TooltipKeyPropertyChangedCallback));
@@ -52,8 +58,7 @@ public class Translator : UIElement
 
         dependencyObject.SetValue(FrameworkElement.ToolTipProperty, newLocalizedString.Value);
     }
-
-
+    
     private static void TooltipKeyPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
     {
         d.SetValue(FrameworkElement.ToolTipProperty, new LocalizedString(GetTooltipKey(d)).Value);
@@ -114,6 +119,17 @@ public class Translator : UIElement
         d.SetValue(KeyProperty, ((LocalizedString)e.NewValue).Key);
     }
 
+    private static void EnumPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        if (e.NewValue == null)
+        {
+            d.SetValue(KeyProperty, null);
+            return;
+        }
+        
+        d.SetValue(KeyProperty, EnumHelpers.GetDescription(e.NewValue));
+    }
+
     private static void UpdateKey(DependencyObject d, string key)
     {
         var parameters = GetLocalizedString(d).Parameters;
@@ -190,6 +206,11 @@ public class Translator : UIElement
         element.SetValue(LocalizedStringProperty, value);
     }
 
+    public static void SetEnum(DependencyObject element, object value)
+    {
+        element.SetValue(EnumProperty, value);
+    }
+
     public static LocalizedString GetLocalizedString(DependencyObject element)
     {
         return (LocalizedString)element.GetValue(LocalizedStringProperty);

+ 11 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -48,6 +48,7 @@
   "OPEN_WEBSITE": "Open website",
   "REPOSITORY": "Repository",
   "OPEN_REPOSITORY": "Open repository",
+  "OPEN_DOCUMENTATION": "Open documentation",
   "LICENSE": "License",
   "OPEN_LICENSE": "Open license",
   "THIRD_PARTY_LICENSES": "Third party licenses",
@@ -576,5 +577,14 @@
 
   "NEWS": "News",
   "HIDE_NEWS_PANEL": "Hide News panel in startup window",
-  "FAILED_FETCH_NEWS": "Failed to fetch news"
+  "FAILED_FETCH_NEWS": "Failed to fetch news",
+  
+  "CROP_TO_SELECTION": "Crop to selection",
+  "CROP_TO_SELECTION_DESCRIPTIVE": "Crop image to selection",
+  "SHOW_CONTEXT_MENU": "Show context menu",
+  "ERASE": "Erase",
+  "USE_SECONDARY_COLOR": "Use secondary color",
+  "RIGHT_CLICK_MODE": "Right click mode",
+  "ADD_PRIMARY_COLOR_TO_PALETTE": "Add primary color to palette",
+  "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Add primary color to current palette"
 }

+ 7 - 2
src/PixiEditor/Helpers/ClipboardHelper.cs

@@ -1,8 +1,9 @@
 using System.Windows;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.Helpers;
 
-class ClipboardHelper
+internal static class ClipboardHelper
 {
     public static bool TrySetDataObject(DataObject obj, bool copy)
     {
@@ -41,4 +42,8 @@ class ClipboardHelper
             return false;
         }
     }
-}
+    
+    public static VecI GetVecI(this DataObject data, string format) => VecI.FromBytes((byte[])data.GetData(format));
+
+    public static void SetVecI(this DataObject data, string format, VecI value) => data.SetData(format, value.ToByteArray());
+}

+ 4 - 0
src/PixiEditor/Helpers/DocumentViewModelBuilder.cs

@@ -28,6 +28,8 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
 
         return this;
     }
+
+    public DocumentViewModelBuilder WithSize(VecI size) => WithSize(size.X, size.Y);
     
     public DocumentViewModelBuilder WithSwatches(IEnumerable<PaletteColor> swatches)
     {
@@ -208,6 +210,8 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
             Height = height;
             return this;
         }
+
+        public LayerBuilder WithSize(VecI size) => WithSize(size.X, size.Y);
         
         public LayerBuilder WithRect(int width, int height, int offsetX, int offsetY)
         {

+ 3 - 3
src/PixiEditor/Helpers/EnumExtension.cs

@@ -12,10 +12,10 @@ public class EnumExtension : MarkupExtension
         EnumType = enumType ?? throw new ArgumentNullException(nameof(enumType));
     }
 
-    public Type EnumType
+    private Type EnumType
     {
-        get { return _enumType; }
-        private set
+        get => _enumType;
+        init
         {
             if (_enumType == value)
                 return;

+ 22 - 0
src/PixiEditor/Helpers/Extensions/EnumHelpers.cs

@@ -27,4 +27,26 @@ internal static class EnumHelpers
 
         return description;
     }
+    
+    public static string GetDescription(object enumValue)
+    {
+        if (!enumValue.GetType().IsEnum)
+        {
+            throw new ArgumentException("enumValue must be a enum", nameof(enumValue));
+        }
+        
+        var description = enumValue.ToString();
+        var fieldInfo = enumValue.GetType().GetField(enumValue.ToString());
+
+        if (fieldInfo != null)
+        {
+            var attrs = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), true);
+            if (attrs != null && attrs.Length > 0)
+            {
+                description = ((DescriptionAttribute)attrs[0]).Description;
+            }
+        }
+
+        return description;
+    }
 }

BIN
src/PixiEditor/Images/Add-reference.png


BIN
src/PixiEditor/Images/Commands/PixiEditor/Selection/CropToSelection.png


BIN
src/PixiEditor/Images/SocialMedia/SteamIcon.png


+ 2 - 2
src/PixiEditor/Models/Commands/Attributes/Commands/BasicAttribute.cs

@@ -15,7 +15,7 @@ internal partial class Command
         /// <param name="internalName">The internal name of the command</param>
         /// <param name="displayName">A short description which is displayed in the the top menu, e.g. "Save as...". Accepts localized key</param>
         /// <param name="descriptiveName">A description which is displayed in the search bar, e.g. "Save image as new". Leave empty to hide it from the search bar. Accepts localized key</param>
-        public BasicAttribute(string internalName, string displayName, string descriptiveName)
+        public BasicAttribute([InternalName] string internalName, string displayName, string descriptiveName)
             : this(internalName, null, displayName, descriptiveName)
         {
         }
@@ -27,7 +27,7 @@ internal partial class Command
         /// <param name="parameter">The parameter that will be passed to the first argument of the method</param>
         /// <param name="displayName">A short description which is displayed in the the top menu, e.g. "Save as...". Accepts localized key</param>
         /// <param name="description">A description which is displayed in the search bar, e.g. "Save image as new". Leave empty to hide it from the search bar. Accepts localized key</param>
-        public BasicAttribute(string internalName, object parameter, string displayName, string description)
+        public BasicAttribute([InternalName] string internalName, object parameter, string displayName, string description)
             : base(internalName, displayName, description)
         {
             Parameter = parameter;

+ 1 - 1
src/PixiEditor/Models/Commands/Attributes/Commands/CommandAttribute.cs

@@ -38,7 +38,7 @@ internal partial class Command
         /// </summary>
         public string IconPath { get; set; }
 
-        protected CommandAttribute(string internalName, string displayName, string description)
+        protected CommandAttribute([InternalName] string internalName, string displayName, string description)
         {
             InternalName = internalName;
             DisplayName = displayName;

+ 2 - 2
src/PixiEditor/Models/Commands/Attributes/Commands/DebugAttribute.cs

@@ -4,11 +4,11 @@ internal partial class Command
 {
     internal class DebugAttribute : BasicAttribute
     {
-        public DebugAttribute(string internalName, string displayName, string descriptiveName) : base($"#DEBUG#{internalName}", displayName, descriptiveName)
+        public DebugAttribute([InternalName] string internalName, string displayName, string descriptiveName) : base($"#DEBUG#{internalName}", displayName, descriptiveName)
         {
         }
 
-        public DebugAttribute(string internalName, object parameter, string displayName, string description)
+        public DebugAttribute([InternalName] string internalName, object parameter, string displayName, string description)
             : base($"#DEBUG#{internalName}", parameter, displayName, description)
         {
         }

+ 2 - 2
src/PixiEditor/Models/Commands/Attributes/Commands/FilterAttribute.cs

@@ -9,11 +9,11 @@ internal partial class Command
     {
         public LocalizedString SearchTerm { get; }
         
-        public FilterAttribute(string internalName, string displayName, string searchTerm) : base(internalName, displayName, string.Empty)
+        public FilterAttribute([InternalName] string internalName, string displayName, string searchTerm) : base(internalName, displayName, string.Empty)
         {
             SearchTerm = searchTerm;
         }
         
-        public FilterAttribute(string internalName) : base(internalName, null, null) { }
+        public FilterAttribute([InternalName] string internalName) : base(internalName, null, null) { }
     }
 }

+ 1 - 1
src/PixiEditor/Models/Commands/Attributes/Commands/GroupAttribute.cs

@@ -15,7 +15,7 @@ internal partial class Command
         /// <summary>
         /// Groups all commands that start with the name <paramref name="internalName"/>
         /// </summary>
-        public GroupAttribute(string internalName, string displayName)
+        public GroupAttribute([InternalName] string internalName, string displayName)
         {
             InternalName = internalName;
             DisplayName = displayName;

+ 2 - 2
src/PixiEditor/Models/Commands/Attributes/Commands/InternalAttribute.cs

@@ -10,7 +10,7 @@ internal partial class Command
         /// <summary>
         /// A command that is not shown in the UI
         /// </summary>
-        public InternalAttribute(string name)
+        public InternalAttribute([InternalName] string name)
             : base(name, string.Empty, string.Empty)
         {
         }
@@ -18,7 +18,7 @@ internal partial class Command
         /// <summary>
         /// A command that is not shown in the UI
         /// </summary>
-        public InternalAttribute(string name, object parameter)
+        public InternalAttribute([InternalName] string name, object parameter)
             : base(name, parameter, string.Empty, string.Empty)
         {
         }

+ 2 - 2
src/PixiEditor/Models/Commands/Attributes/Evaluators/CanExecuteAttribute.cs

@@ -7,12 +7,12 @@ internal partial class Evaluator
     {
         public string[] NamesOfRequiredCanExecuteEvaluators { get; }
 
-        public CanExecuteAttribute(string name) : base(name)
+        public CanExecuteAttribute([InternalName] string name) : base(name)
         {
             NamesOfRequiredCanExecuteEvaluators = Array.Empty<string>();
         }
 
-        public CanExecuteAttribute(string name, params string[] requires) : base(name)
+        public CanExecuteAttribute([InternalName] string name, params string[] requires) : base(name)
         {
             NamesOfRequiredCanExecuteEvaluators = requires;
         }

+ 1 - 1
src/PixiEditor/Models/Commands/Attributes/Evaluators/EvaluatorAttribute.cs

@@ -7,7 +7,7 @@ internal static partial class Evaluator
     {
         public string Name { get; }
 
-        public EvaluatorAttribute(string name)
+        public EvaluatorAttribute([InternalName] string name)
         {
             Name = name;
         }

+ 1 - 1
src/PixiEditor/Models/Commands/Attributes/Evaluators/IconAttribute.cs

@@ -5,7 +5,7 @@ internal partial class Evaluator
     [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = true)]
     internal class IconAttribute : EvaluatorAttribute
     {
-        public IconAttribute(string name)
+        public IconAttribute([InternalName] string name)
             : base(name)
         { }
     }

+ 5 - 0
src/PixiEditor/Models/Commands/Attributes/InternalNameAttribute.cs

@@ -0,0 +1,5 @@
+namespace PixiEditor.Models.Commands.Attributes;
+
+[AttributeUsage(AttributeTargets.Parameter)]
+public class InternalNameAttribute : Attribute
+{ }

+ 68 - 44
src/PixiEditor/Models/Controllers/ClipboardController.cs

@@ -21,6 +21,8 @@ namespace PixiEditor.Models.Controllers;
 #nullable enable
 internal static class ClipboardController
 {
+    private const string PositionFormat = "PixiEditor.Position";
+    
     public static readonly string TempCopyFilePath = Path.Join(
         Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
         "PixiEditor",
@@ -42,7 +44,7 @@ internal static class ClipboardController
             NoticeDialog.Show("SELECTED_AREA_EMPTY", "NOTHING_TO_COPY");
             return;
         }
-        var (actuallySurface, _) = surface.AsT2;
+        var (actuallySurface, area) = surface.AsT2;
         DataObject data = new DataObject();
 
         using (ImgData pngData = actuallySurface.DrawingSurface.Snapshot().Encode())
@@ -64,6 +66,11 @@ internal static class ClipboardController
         data.SetData(DataFormats.Bitmap, finalBitmap, true); // Bitmap, no transparency
         data.SetImage(finalBitmap); // DIB format, no transparency
 
+        if (area.Size != document.SizeBindable && area.Pos != VecI.Zero)
+        {
+            data.SetVecI(PositionFormat, area.Pos);
+        }
+
         ClipboardHelper.TrySetDataObject(data, true);
     }
 
@@ -72,12 +79,20 @@ internal static class ClipboardController
     /// </summary>
     public static bool TryPaste(DocumentViewModel document, DataObject data, bool pasteAsNew = false)
     {
-        List<(string? name, Surface image)> images = GetImage(data);
+        List<DataImage> images = GetImage(data);
         if (images.Count == 0)
             return false;
 
         if (images.Count == 1)
         {
+            var dataImage = images[0];
+            var position = dataImage.position;
+
+            if (document.SizeBindable.X < position.X || document.SizeBindable.Y < position.Y)
+            {
+                position = VecI.Zero;
+            }
+            
             if (pasteAsNew)
             {
                 var guid = document.Operations.CreateStructureMember(StructureMemberType.Layer, "New Layer", false);
@@ -88,11 +103,11 @@ internal static class ClipboardController
                 }
                 
                 document.Operations.SetSelectedMember(guid.Value);
-                document.Operations.PasteImageWithTransform(images[0].image, VecI.Zero, guid.Value, false);
+                document.Operations.PasteImageWithTransform(dataImage.image, position, guid.Value, false);
             }
             else
             {
-                document.Operations.PasteImageWithTransform(images[0].image, VecI.Zero);
+                document.Operations.PasteImageWithTransform(dataImage.image, position);
             }
             
             return true;
@@ -108,72 +123,76 @@ internal static class ClipboardController
     public static bool TryPasteFromClipboard(DocumentViewModel document, bool pasteAsNew = false) =>
         TryPaste(document, ClipboardHelper.TryGetDataObject(), pasteAsNew);
 
-    public static List<(string? name, Surface image)> GetImagesFromClipboard() => GetImage(ClipboardHelper.TryGetDataObject());
+    public static List<DataImage> GetImagesFromClipboard() => GetImage(ClipboardHelper.TryGetDataObject());
 
     /// <summary>
     /// Gets images from clipboard, supported PNG, Dib and Bitmap.
     /// </summary>
-    public static List<(string? name, Surface image)> GetImage(DataObject? data)
+    public static List<DataImage> GetImage(DataObject? data)
     {
-        List<(string? name, Surface image)> surfaces = new();
+        List<DataImage> surfaces = new();
 
         if (data == null)
             return surfaces;
 
-        if (TryExtractSingleImage(data, out Surface? singleImage))
+        if (TryExtractSingleImage(data, out var singleImage))
         {
-            surfaces.Add((null, singleImage));
+            surfaces.Add(new DataImage(singleImage, data.GetVecI(PositionFormat)));
             return surfaces;
         }
-        else if (data.GetDataPresent(DataFormats.FileDrop))
+
+        if (!data.GetDataPresent(DataFormats.FileDrop))
         {
-            foreach (string? path in data.GetFileDropList())
+            return surfaces;
+        }
+
+        foreach (string? path in data.GetFileDropList())
+        {
+            if (path is null || !Importer.IsSupportedFile(path))
+                continue;
+            try
             {
-                if (path is null || !Importer.IsSupportedFile(path))
-                    continue;
-                try
-                {
-                    Surface imported;
+                Surface imported;
                     
-                    if (Path.GetExtension(path) == ".pixi")
-                    {
-                        using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
+                if (Path.GetExtension(path) == ".pixi")
+                {
+                    using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
                         
-                        try
+                    try
+                    {
+                        imported = Surface.Load(PixiParser.Deserialize(path).PreviewImage);
+                    }
+                    catch (InvalidFileException e)
+                    {
+                        // Check if it could be a old file
+                        if (!e.Message.StartsWith("Header"))
                         {
-                            imported = Surface.Load(PixiParser.Deserialize(path).PreviewImage);
+                            throw;
                         }
-                        catch (InvalidFileException e)
-                        {
-                            // Check if it could be a old file
-                            if (!e.Message.StartsWith("Header"))
-                            {
-                                throw;
-                            }
                             
-                            stream.Position = 0;
-                            using var bitmap = DepractedPixiParser.Deserialize(stream).RenderOldDocument();
-                            var size = new VecI(bitmap.Width, bitmap.Height);
-                            imported = new Surface(size);
-                            imported.DrawBytes(size, bitmap.Bytes, ColorType.RgbaF32, AlphaType.Premul);
+                        stream.Position = 0;
+                        using var bitmap = DepractedPixiParser.Deserialize(stream).RenderOldDocument();
+                        var size = new VecI(bitmap.Width, bitmap.Height);
+                        imported = new Surface(size);
+                        imported.DrawBytes(size, bitmap.Bytes, ColorType.RgbaF32, AlphaType.Premul);
                             
-                            System.Diagnostics.Debug.Write(imported.ToString());
-                        }
-                    }
-                    else
-                    {
-                        imported = Surface.Load(path);
+                        System.Diagnostics.Debug.Write(imported.ToString());
                     }
-
-                    string filename = Path.GetFullPath(path);
-                    surfaces.Add((filename, imported));
                 }
-                catch
+                else
                 {
-                    continue;
+                    imported = Surface.Load(path);
                 }
+
+                string filename = Path.GetFullPath(path);
+                surfaces.Add(new DataImage(filename, imported, data.GetVecI(PositionFormat)));
+            }
+            catch
+            {
+                continue;
             }
         }
+        
         return surfaces;
     }
 
@@ -259,4 +278,9 @@ internal static class ClipboardController
         result = null;
         return false;
     }
+
+    public record struct DataImage(string? name, Surface image, VecI position)
+    {
+        public DataImage(Surface image, VecI position) : this(null, image, position) { }
+    }
 }

+ 3 - 2
src/PixiEditor/Models/DataHolders/CrashReport.cs

@@ -16,10 +16,11 @@ internal class CrashReport : IDisposable
     public static CrashReport Generate(Exception exception)
     {
         StringBuilder builder = new();
-        DateTime currentTime = DateTime.Now;
+        DateTimeOffset currentTime = DateTimeOffset.Now;
 
         builder
-            .AppendLine($"PixiEditor {VersionHelpers.GetCurrentAssemblyVersionString(moreSpecific: true)} crashed on {currentTime:yyyy.MM.dd} at {currentTime:HH:mm:ss}\n")
+            .AppendLine($"PixiEditor {VersionHelpers.GetCurrentAssemblyVersionString(moreSpecific: true)} x{IntPtr.Size * 8} crashed on {currentTime:yyyy.MM.dd} at {currentTime:HH:mm:ss} {currentTime:zzz}")
+            .AppendLine($"Report: {Guid.NewGuid()}\n")
             .AppendLine("-----System Information----")
             .AppendLine("General:")
             .AppendLine($"  OS: {Environment.OSVersion.VersionString}")

+ 0 - 72
src/PixiEditor/Models/Dialogs/ImportFileDialog.cs

@@ -1,72 +0,0 @@
-using PixiEditor.Views;
-using PixiEditor.Views.Dialogs;
-
-namespace PixiEditor.Models.Dialogs;
-
-internal class ImportFileDialog : CustomDialog
-{
-    private int fileHeight;
-
-    private string filePath;
-    private int fileWidth;
-
-    public int FileWidth
-    {
-        get => fileWidth;
-        set
-        {
-            if (fileWidth != value)
-            {
-                fileWidth = value;
-                RaisePropertyChanged("Width");
-            }
-        }
-    }
-
-    public int FileHeight
-    {
-        get => fileHeight;
-        set
-        {
-            if (fileHeight != value)
-            {
-                fileHeight = value;
-                RaisePropertyChanged("FileHeight");
-            }
-        }
-    }
-
-    public string FilePath
-    {
-        get => filePath;
-        set
-        {
-            if (filePath != value)
-            {
-                filePath = value;
-                RaisePropertyChanged("FilePath");
-            }
-        }
-    }
-
-    public override bool ShowDialog()
-    {
-        ImportFilePopup popup = new ImportFilePopup
-        {
-            FilePath = FilePath
-        };
-
-        if (FileWidth != 0) popup.ImportWidth = FileWidth;
-        if (FileHeight != 0) popup.ImportHeight = FileHeight;
-        
-        popup.ShowDialog();
-        if (popup.DialogResult == true)
-        {
-            FileHeight = popup.ImportHeight;
-            FileWidth = popup.ImportWidth;
-            FilePath = popup.FilePath;
-        }
-
-        return (bool)popup.DialogResult;
-    }
-}

+ 22 - 3
src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -8,6 +8,7 @@ using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface.Vector;
 using PixiEditor.Extensions.Palettes;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 using PixiEditor.Models.DocumentPassthroughActions;
 using PixiEditor.Models.Enums;
@@ -106,7 +107,7 @@ internal class DocumentOperationsModule
     /// Pastes the <paramref name="images"/> as new layers
     /// </summary>
     /// <param name="images">The images to paste</param>
-    public void PasteImagesAsLayers(List<(string? name, Surface image)> images)
+    public void PasteImagesAsLayers(List<ClipboardController.DataImage> images)
     {
         if (Internals.ChangeController.IsChangeActive)
             return;
@@ -114,7 +115,7 @@ internal class DocumentOperationsModule
         RectI maxSize = new RectI(VecI.Zero, Document.SizeBindable);
         foreach (var imageWithName in images)
         {
-            maxSize = maxSize.Union(new RectI(VecI.Zero, imageWithName.image.Size));
+            maxSize = maxSize.Union(new RectI(imageWithName.position, imageWithName.image.Size));
         }
 
         if (maxSize.Size != Document.SizeBindable)
@@ -123,7 +124,7 @@ internal class DocumentOperationsModule
         foreach (var imageWithName in images)
         {
             var layerGuid = Internals.StructureHelper.CreateNewStructureMember(StructureMemberType.Layer, Path.GetFileName(imageWithName.name));
-            DrawImage(imageWithName.image, new ShapeCorners(new RectD(VecD.Zero, imageWithName.image.Size)), layerGuid, true, false, false);
+            DrawImage(imageWithName.image, new ShapeCorners(new RectD(imageWithName.position, imageWithName.image.Size)), layerGuid, true, false, false);
         }
         Internals.ActionAccumulator.AddFinishedActions();
     }
@@ -565,6 +566,24 @@ internal class DocumentOperationsModule
         Internals.ActionAccumulator.AddFinishedActions(new SelectionToMask_Action(member.GuidValue, mode));
     }
 
+    public void CropToSelection(bool clearSelection = true)
+    {
+        var bounds = Document.SelectionPathBindable.TightBounds;
+        if (Document.SelectionPathBindable.IsEmpty || bounds.Width <= 0 || bounds.Height <= 0)
+            return;
+
+        Internals.ActionAccumulator.AddActions(new Crop_Action((RectI)bounds));
+
+        if (clearSelection)
+        {
+            Internals.ActionAccumulator.AddFinishedActions(new ClearSelection_Action());
+        }
+        else
+        {
+            Internals.ActionAccumulator.AddFinishedActions();
+        }
+    }
+    
     public void InvertSelection()
     {
         var selection = Document.SelectionPathBindable;

+ 3 - 2
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/BrightnessToolExecutor.cs

@@ -1,4 +1,5 @@
-using ChunkyImageLib.DataHolders;
+using System.Windows.Input;
+using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Models.Enums;
 using PixiEditor.ViewModels.SubViewModels.Document;
@@ -27,7 +28,7 @@ internal class BrightnessToolExecutor : UpdateableChangeExecutor
         guidValue = member.GuidValue;
         repeat = tool.BrightnessMode == BrightnessMode.Repeat;
         toolSize = tool.ToolSize;
-        correctionFactor = tool.Darken ? -tool.CorrectionFactor : tool.CorrectionFactor;
+        correctionFactor = tool.Darken || tool.UsedWith == MouseButton.Right ? -tool.CorrectionFactor : tool.CorrectionFactor;
 
         ChangeBrightness_Action action = new(guidValue, controller!.LastPixelPosition, correctionFactor, toolSize, repeat);
         internals!.ActionAccumulator.AddActions(action);

+ 13 - 0
src/PixiEditor/Models/Enums/RightClickMode.cs

@@ -0,0 +1,13 @@
+using System.ComponentModel;
+
+namespace PixiEditor.Models.Enums;
+
+public enum RightClickMode
+{
+    [Description("USE_SECONDARY_COLOR")]
+    SecondaryColor,
+    [Description("SHOW_CONTEXT_MENU")]
+    ContextMenu,
+    [Description("ERASE")]
+    Erase
+}

+ 6 - 5
src/PixiEditor/Models/IO/Importer.cs

@@ -31,13 +31,14 @@ internal class Importer : NotifyableObject
     public static Surface ImportImage(string path, VecI size)
     {
         Surface original = Surface.Load(path);
-        if (original.Size != size)
+        if (original.Size == size || size == VecI.NegativeOne)
         {
-            Surface resized = original.ResizeNearestNeighbor(size);
-            original.Dispose();
-            return resized;
+            return original;
         }
-        return original;
+
+        Surface resized = original.ResizeNearestNeighbor(size);
+        original.Dispose();
+        return resized;
     }
 
     public static WriteableBitmap ImportWriteableBitmap(string path)

+ 6 - 0
src/PixiEditor/Models/Preferences/PreferencesSettings.cs

@@ -200,6 +200,12 @@ internal class PreferencesSettings : IPreferences
         if (!dict.ContainsKey(name)) return fallbackValue;
         var preference = dict[name];
         if (typeof(T) == preference.GetType()) return (T)preference;
+
+        if (typeof(T).IsEnum)
+        {
+            return (T)Enum.Parse(typeof(T), preference.ToString());
+        }
+        
         if (preference.GetType() == typeof(JArray))
         {
             return ((JArray)preference).ToObject<T>();

+ 6 - 0
src/PixiEditor/PixiEditor.csproj

@@ -155,6 +155,7 @@
 	</ItemGroup>
 
 	<ItemGroup>
+		<None Remove="Images\Add-reference.png" />
 		<None Remove="Images\AnchorDot.png" />
 		<None Remove="Images\Arrow-right.png" />
 		<None Remove="Images\Check-square.png" />
@@ -198,6 +199,7 @@
 		<None Remove="Images\SocialMedia\DonateIcon.png" />
 		<None Remove="Images\SocialMedia\GitHubIcon.png" />
 		<None Remove="Images\SocialMedia\RedditIcon.png" />
+		<None Remove="Images\SocialMedia\SteamIcon.png" />
 		<None Remove="Images\SocialMedia\WebsiteIcon.png" />
 		<None Remove="Images\SocialMedia\YouTubeIcon.png" />
 		<None Remove="Images\SymmetryHorizontal.png" />
@@ -248,6 +250,7 @@
 		</PackageReference>
 	</ItemGroup>
 	<ItemGroup>
+		<Resource Include="Images\Add-reference.png" />
 		<Resource Include="Images\AnchorDot.png" />
 		<Resource Include="Images\Arrow-right.png" />
 		<Resource Include="Images\Check-square.png" />
@@ -292,6 +295,7 @@
 		<Resource Include="Images\SocialMedia\DonateIcon.png" />
 		<Resource Include="Images\SocialMedia\GitHubIcon.png" />
 		<Resource Include="Images\SocialMedia\RedditIcon.png" />
+		<Resource Include="Images\SocialMedia\SteamIcon.png" />
 		<Resource Include="Images\SocialMedia\WebsiteIcon.png" />
 		<Resource Include="Images\SocialMedia\YouTubeIcon.png" />
 		<Resource Include="Images\SymmetryHorizontal.png" />
@@ -434,6 +438,8 @@
 		<Resource Include="Images\Commands\PixiEditor\Selection\InvertSelection.png" />
 		<None Remove="Images\LanguageFlags\zh.png" />
 		<Resource Include="Images\LanguageFlags\zh.png" />
+		<None Remove="Images\Commands\PixiEditor\Selection\CropToSelection.png" />
+		<Resource Include="Images\Commands\PixiEditor\Selection\CropToSelection.png" />
 		<None Remove="Images\LanguageFlags\hu.png" />
 		<Resource Include="Images\LanguageFlags\hu.png" />
 		<None Remove="Images\LanguageFlags\pt-br.png" />

+ 52 - 14
src/PixiEditor/Styles/ComboBoxDarkStyle.xaml

@@ -2,7 +2,8 @@
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                     xmlns:theme="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero2"
                     xmlns:controls="clr-namespace:PixiEditor.Views.UserControls"
-                    xmlns:sys="clr-namespace:System;assembly=mscorlib">
+                    xmlns:sys="clr-namespace:System;assembly=mscorlib"
+                    xmlns:views="clr-namespace:PixiEditor.Views">
 
     <SolidColorBrush x:Key="ComboBox.Static.Background" Color="#252525" />
     <SolidColorBrush x:Key="ComboBox.Static.Border" Color="#2F2F37" />
@@ -194,23 +195,18 @@
                     <Border Name="Bd" Background="{TemplateBinding Background}" BorderThickness="0,1">
                         <ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Stretch"
                                           VerticalAlignment="Center" Margin="4,2"/>
-                        
-                        <Border.Style>
-                            <Style TargetType="Border">
-                                <Setter Property="BorderBrush" Value="Transparent" />
-                                <Style.Triggers>
-                                    <Trigger Property="IsMouseOver" Value="True">
-                                        <Setter Property="BorderBrush" Value="{StaticResource AlmostLightModeAccentColor}" />
-                                    </Trigger>
-                                </Style.Triggers>
-                            </Style>
-
-                        </Border.Style>
                     </Border>
                     <ControlTemplate.Triggers>
-                        <Trigger Property="IsHighlighted" Value="True">
+                        <Trigger Property="IsMouseOver" Value="False">
+                            <Setter Property="BorderBrush" TargetName="Bd" Value="Transparent" />
+                        </Trigger>
+                        <Trigger Property="IsSelected" Value="True">
                             <Setter Property="Background" TargetName="Bd" Value="{StaticResource MainColor}" />
                         </Trigger>
+                        <Trigger Property="IsMouseOver" Value="True">
+                            <Setter Property="Background" TargetName="Bd" Value="{StaticResource MainColor}" />
+                            <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource AlmostLightModeAccentColor}" />
+                        </Trigger>
                         <Trigger Property="IsEnabled" Value="False">
                             <Setter Property="Foreground" Value="{StaticResource MainColor}" />
                         </Trigger>
@@ -235,4 +231,46 @@
             </Setter.Value>
         </Setter>
     </Style>
+    
+    <Style TargetType="ComboBox" x:Key="TranslatedEnum">
+        <Setter Property="ItemTemplate">
+            <Setter.Value>
+                <DataTemplate>
+                    <TextBlock views:Translator.Enum="{Binding}"/>
+                </DataTemplate>
+            </Setter.Value>
+        </Setter>
+        <Setter Property="ItemContainerStyle">
+            <Setter.Value>
+                <Style TargetType="{x:Type ComboBoxItem}">
+                    <Setter Property="Template">
+                        <Setter.Value>
+                            <ControlTemplate TargetType="{x:Type ComboBoxItem}">
+                                <Border x:Name="Bd" Background="{TemplateBinding Background}" BorderThickness="0,1">
+                                    <TextBlock views:Translator.Enum="{Binding}" />
+                                </Border>
+                                <ControlTemplate.Triggers>
+                                    <Trigger Property="IsMouseOver" Value="False">
+                                        <Setter Property="BorderBrush" TargetName="Bd" Value="Transparent" />
+                                    </Trigger>
+                                    <Trigger Property="IsSelected" Value="True">
+                                        <Setter Property="Background" TargetName="Bd" Value="{StaticResource MainColor}" />
+                                    </Trigger>
+                                    <Trigger Property="IsMouseOver" Value="True">
+                                        <Setter Property="Background" TargetName="Bd" Value="{StaticResource MainColor}" />
+                                        <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource AlmostLightModeAccentColor}" />
+                                    </Trigger>
+                                    <Trigger Property="IsEnabled" Value="False">
+                                        <Setter Property="Foreground" Value="{StaticResource MainColor}" />
+                                    </Trigger>
+                                </ControlTemplate.Triggers>
+                            </ControlTemplate>
+                        </Setter.Value>
+                    </Setter>
+                </Style>
+            </Setter.Value>
+        </Setter>
+        <Setter Property="Foreground" Value="White" />
+        <Setter Property="Template" Value="{DynamicResource DarkComboBox}" />
+    </Style>
 </ResourceDictionary>

+ 26 - 1
src/PixiEditor/ViewModels/MainVM.cs

@@ -1,10 +1,35 @@
 using System.Windows.Markup;
+using PixiEditor.ViewModels.SubViewModels;
 
 namespace PixiEditor.ViewModels;
 internal class MainVM : MarkupExtension
 {
+    private MainVmEnum? vm;
+    private static Dictionary<MainVmEnum, object> subVms = new();
+
     public override object ProvideValue(IServiceProvider serviceProvider)
     {
-        return ViewModelMain.Current;
+        return vm != null ? subVms[vm.Value] : ViewModelMain.Current;
+    }
+
+    static MainVM()
+    {
+        var type = typeof(ViewModelMain);
+        var vm = ViewModelMain.Current;
+        
+        foreach (var value in Enum.GetValues<MainVmEnum>())
+        {
+            subVms.Add(value, type.GetProperty(value.ToString().Replace("SVM", "SubViewModel").Replace("VM", "ViewModel"))?.GetValue(vm));
+        }
+    }
+    
+    public MainVM()
+    {
+        vm = null;
+    }
+
+    public MainVM(MainVmEnum vm)
+    {
+        this.vm = vm;
     }
 }

+ 29 - 0
src/PixiEditor/ViewModels/MainVmEnum.cs

@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.ViewModels;
+
+enum MainVmEnum
+{
+    FileSVM,
+    UpdateSVM,
+    ToolsSVM,
+    IoSVM,
+    LayersSVM,
+    ClipboardSVM,
+    UndoSVM,
+    SelectionSVM,
+    ViewportSVM,
+    ColorsSVM,
+    MiscSVM,
+    DiscordVM,
+    DebugSVM,
+    DocumentManagerSVM,
+    StylusSVM,
+    WindowSVM,
+    SearchSVM,
+    RegistrySVM
+}

+ 11 - 0
src/PixiEditor/ViewModels/SubViewModels/Main/ColorsViewModel.cs

@@ -334,6 +334,17 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>
         PrimaryColor = color.ToColor();
     }
 
+    [Command.Basic("PixIEditor.Colors.AddPrimaryToPalettes", "ADD_PRIMARY_COLOR_TO_PALETTE", "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE", CanExecute = "PixiEditor.HasDocument", IconPath = "CopyAdd.png")]
+    public void AddPrimaryColorToPalette()
+    {
+        var palette = Owner.DocumentManagerSubViewModel.ActiveDocument.Palette;
+
+        if (!palette.Contains(PrimaryColor))
+        {
+            palette.Add(PrimaryColor);
+        }
+    }
+
     [Command.Internal("PixiEditor.CloseContextMenu")]
     public void CloseContextMenu(System.Windows.Controls.ContextMenu menu)
     {

+ 4 - 4
src/PixiEditor/ViewModels/SubViewModels/Main/DebugViewModel.cs

@@ -82,22 +82,22 @@ internal class DebugViewModel : SubViewModel<ViewModelMain>
     }
     
 
-    [Command.Debug("PixiEditor.Debug.OpenLocalAppDataDirectory", @"%LocalAppData%\PixiEditor", "OPEN_LOCAL_APPDATA_DIR", "OPEN_LOCAL_APPDATA_DIR", IconPath = "Folder.png")]
-    [Command.Debug("PixiEditor.Debug.OpenCrashReportsDirectory", @"%LocalAppData%\PixiEditor\crash_logs", "OPEN_CRASH_REPORTS_DIR", "OPEN_CRASH_REPORTS_DIR", IconPath = "Folder.png")]
+    [Command.Debug("PixiEditor.Debug.OpenLocalAppDataDirectory", @"PixiEditor", "OPEN_LOCAL_APPDATA_DIR", "OPEN_LOCAL_APPDATA_DIR", IconPath = "Folder.png")]
+    [Command.Debug("PixiEditor.Debug.OpenCrashReportsDirectory", @"PixiEditor\crash_logs", "OPEN_CRASH_REPORTS_DIR", "OPEN_CRASH_REPORTS_DIR", IconPath = "Folder.png")]
     public static void OpenLocalAppDataFolder(string subDirectory)
     {
         var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), subDirectory);
         OpenFolder(path);
     }
 
-    [Command.Debug("PixiEditor.Debug.OpenRoamingAppDataDirectory", @"%AppData%\PixiEditor", "OPEN_ROAMING_APPDATA_DIR", "OPEN_ROAMING_APPDATA_DIR", IconPath = "Folder.png")]
+    [Command.Debug("PixiEditor.Debug.OpenRoamingAppDataDirectory", @"PixiEditor", "OPEN_ROAMING_APPDATA_DIR", "OPEN_ROAMING_APPDATA_DIR", IconPath = "Folder.png")]
     public static void OpenAppDataFolder(string subDirectory)
     {
         var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), subDirectory);
         OpenFolder(path);
     }
 
-    [Command.Debug("PixiEditor.Debug.OpenTempDirectory", @"%Temp%\PixiEditor", "OPEN_TEMP_DIR", "OPEN_TEMP_DIR", IconPath = "Folder.png")]
+    [Command.Debug("PixiEditor.Debug.OpenTempDirectory", @"PixiEditor", "OPEN_TEMP_DIR", "OPEN_TEMP_DIR", IconPath = "Folder.png")]
     public static void OpenTempFolder(string subDirectory)
     {
         var path = Path.Combine(Path.GetTempPath(), subDirectory);

+ 12 - 34
src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs

@@ -149,15 +149,15 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
     {
         var images = ClipboardController.GetImagesFromClipboard();
 
-        foreach (var (name, image) in images)
+        foreach (var dataImage in images)
         {
-            if (name == null)
+            if (File.Exists(dataImage.name))
             {
-                OpenRegularImage(image, null);
+                OpenRegularImage(dataImage.image, null);
                 continue;
             }
             
-            OpenFromPath(name, false);
+            OpenFromPath(dataImage.name, false);
         }
     }
 
@@ -228,24 +228,14 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
     /// </summary>
     private void OpenRegularImage(string path, bool associatePath)
     {
-        ImportFileDialog dialog = new ImportFileDialog();
+        var image = Importer.ImportImage(path, VecI.NegativeOne);
 
-        if (path != null && File.Exists(path))
-        {
-            dialog.FilePath = path;
-        }
-
-        if (!dialog.ShowDialog())
-        {
-            return;
-        }
-
-        DocumentViewModel doc = NewDocument(b => b
-            .WithSize(dialog.FileWidth, dialog.FileHeight)
+        var doc = NewDocument(b => b
+            .WithSize(image.Size)
             .WithLayer(l => l
                 .WithName("Image")
-                .WithSize(dialog.FileWidth, dialog.FileHeight)
-                .WithSurface(Importer.ImportImage(dialog.FilePath, new VecI(dialog.FileWidth, dialog.FileHeight)))));
+                .WithSize(image.Size)
+                .WithSurface(image)));
 
         if (associatePath)
         {
@@ -260,23 +250,11 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
     /// </summary>
     private void OpenRegularImage(Surface surface, string path)
     {
-        ImportFileDialog dialog = new ImportFileDialog( );
-
-        dialog.FileWidth = surface.Size.X;
-        dialog.FileHeight = surface.Size.Y;
-        
-        if (!dialog.ShowDialog())
-        {
-            return;
-        }
-
-        surface.ResizeNearestNeighbor(new VecI(dialog.FileWidth, dialog.FileHeight));
-            
         DocumentViewModel doc = NewDocument(b => b
-            .WithSize(dialog.FileWidth, dialog.FileHeight)
+            .WithSize(surface.Size)
             .WithLayer(l => l
                 .WithName("Image")
-                .WithSize(dialog.FileWidth, dialog.FileHeight)
+                .WithSize(surface.Size)
                 .WithSurface(surface)));
 
         if (path == null)
@@ -362,7 +340,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
     ///     Generates export dialog or saves directly if save data is known.
     /// </summary>
     /// <param name="parameter">CommandProperty.</param>
-    [Command.Basic("PixiEditor.File.Export", "EXPORT", "EXPORT_IMAGE", CanExecute = "PixiEditor.HasDocument", Key = Key.S, Modifiers = ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Shift)]
+    [Command.Basic("PixiEditor.File.Export", "EXPORT", "EXPORT_IMAGE", CanExecute = "PixiEditor.HasDocument", Key = Key.E, Modifiers = ModifierKeys.Control)]
     public void ExportFile()
     {
         DocumentViewModel doc = Owner.DocumentManagerSubViewModel.ActiveDocument;

+ 115 - 12
src/PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs

@@ -1,12 +1,12 @@
 using System.Windows;
 using System.Windows.Input;
-using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Commands;
 using PixiEditor.Models.Commands.Commands;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Enums;
 using PixiEditor.Models.Events;
 using PixiEditor.ViewModels.SubViewModels.Document;
 using PixiEditor.ViewModels.SubViewModels.Tools;
@@ -17,6 +17,12 @@ namespace PixiEditor.ViewModels.SubViewModels.Main;
 #nullable enable
 internal class IoViewModel : SubViewModel<ViewModelMain>
 {
+    private bool hadSwapped;
+    private int? previousEraseSize;
+    private bool hadSharedToolbar;
+    private bool? drawingWithRight;
+    private bool startedWithEraser;
+
     public RelayCommand MouseMoveCommand { get; set; }
     public RelayCommand MouseDownCommand { get; set; }
     public RelayCommand PreviewMouseMiddleButtonCommand { get; set; }
@@ -150,18 +156,67 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
 
     private void OnMouseDown(object? sender, MouseOnCanvasEventArgs args)
     {
-        if (args.Button == MouseButton.Left)
-        {
-            DocumentManagerViewModel docManager = Owner.DocumentManagerSubViewModel;
-            DocumentViewModel? activeDocument = docManager.ActiveDocument;
-            if (activeDocument == null)
-                return;
+        if (drawingWithRight != null || args.Button is not (MouseButton.Left or MouseButton.Right))
+            return;
+
+        if (args.Button == MouseButton.Right && !HandleRightMouseDown())
+            return;
+
+        var docManager = Owner.DocumentManagerSubViewModel;
+        var activeDocument = docManager.ActiveDocument;
+        if (activeDocument == null)
+            return;
+
+        drawingWithRight = args.Button == MouseButton.Right;
+        Owner.ToolsSubViewModel.UseToolEventInlet(args.PositionOnCanvas, args.Button);
+        activeDocument.EventInlet.OnCanvasLeftMouseButtonDown(args.PositionOnCanvas);
+    }
+
+    private bool HandleRightMouseDown()
+    {
+        var tools = Owner.ToolsSubViewModel;
+
+        startedWithEraser = tools.ActiveTool is EraserToolViewModel;
 
-            Owner.ToolsSubViewModel.LeftMouseButtonDownInlet(args.PositionOnCanvas);
-            activeDocument.EventInlet.OnCanvasLeftMouseButtonDown(args.PositionOnCanvas);
+        switch (tools.RightClickMode)
+        {
+            case RightClickMode.SecondaryColor when tools.ActiveTool.UsesColor:
+            case RightClickMode.Erase when tools.ActiveTool is ColorPickerToolViewModel:
+                Owner.ColorsSubViewModel.SwapColors(null);
+                hadSwapped = true;
+                return true;
+            case RightClickMode.Erase when tools.ActiveTool.IsErasable:
+            {
+                HandleRightMouseEraseDown(tools);
+                return true;
+            }
+            case RightClickMode.SecondaryColor when tools.ActiveTool is BrightnessToolViewModel:
+                return true;
+            case RightClickMode.ContextMenu:
+            default:
+                return false;
         }
     }
 
+    private void HandleRightMouseEraseDown(ToolsViewModel tools)
+    {
+        var currentToolSize = tools.ActiveTool.Toolbar.Settings.FirstOrDefault(x => x.Name == "ToolSize");
+        hadSharedToolbar = tools.EnableSharedToolbar;
+        if (currentToolSize != null)
+        {
+            tools.EnableSharedToolbar = false;
+            var toolSize = tools.GetTool<EraserToolViewModel>().Toolbar.Settings.First(x => x.Name == "ToolSize");
+            previousEraseSize = (int)toolSize.Value;
+            toolSize.Value = tools.ActiveTool is PenToolViewModel { PixelPerfectEnabled: true } ? 1 : currentToolSize.Value;
+        }
+        else
+        {
+            previousEraseSize = null;
+        }
+
+        tools.SetActiveTool<EraserToolViewModel>(true);
+    }
+    
     private void OnPreviewMiddleMouseButton(object sender)
     {
         Owner.ToolsSubViewModel.SetActiveTool<MoveViewportToolViewModel>(true);
@@ -177,15 +232,63 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
 
     private void OnMouseUp(object? sender, MouseButton button)
     {
+        bool toLeftRightClick = drawingWithRight == null ||
+                                (button == MouseButton.Left && drawingWithRight.Value) ||
+                                (button == MouseButton.Right && !drawingWithRight.Value);
+        
+        if (toLeftRightClick && button != MouseButton.Middle)
+            return;
+
         if (Owner.DocumentManagerSubViewModel.ActiveDocument is null)
             return;
-        if (button == MouseButton.Left)
+        var tools = Owner.ToolsSubViewModel;
+
+        var rightCanUp = (button == MouseButton.Right && tools.RightClickMode == RightClickMode.Erase || tools.RightClickMode == RightClickMode.SecondaryColor);
+        
+        if (button == MouseButton.Left || rightCanUp)
         {
             Owner.DocumentManagerSubViewModel.ActiveDocument.EventInlet.OnCanvasLeftMouseButtonUp();
         }
-        else if (button == MouseButton.Middle)
+        
+        drawingWithRight = null;
+
+        HandleRightMouseUp(button, tools);
+        
+        hadSwapped = false;
+    }
+
+    private void HandleRightMouseUp(MouseButton button, ToolsViewModel tools)
+    {
+        switch (button)
+        {
+            case MouseButton.Middle:
+                tools.RestorePreviousTool();
+                break;
+            case MouseButton.Right when hadSwapped && 
+                                        (tools.RightClickMode == RightClickMode.SecondaryColor || 
+                                         tools is { ActiveTool: ColorPickerToolViewModel, RightClickMode: RightClickMode.Erase }
+                                        ):
+
+                Owner.ColorsSubViewModel.SwapColors(null);
+                break;
+            case MouseButton.Right when tools.RightClickMode == RightClickMode.Erase:
+                HandleRightMouseEraseUp(tools);
+                break;
+        }
+    }
+
+    private void HandleRightMouseEraseUp(ToolsViewModel tools)
+    {
+        if (startedWithEraser)
+        {
+            return;
+        }
+
+        tools.EnableSharedToolbar = hadSharedToolbar;
+        if (previousEraseSize != null)
         {
-            Owner.ToolsSubViewModel.RestorePreviousTool();
+            tools.GetTool<EraserToolViewModel>().Toolbar.Settings.First(x => x.Name == "ToolSize").Value = previousEraseSize.Value;
         }
+        tools.RestorePreviousTool();
     }
 }

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

@@ -328,7 +328,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
     public bool ReferenceLayerDoesntExist() => 
         Owner.DocumentManagerSubViewModel.ActiveDocument is not null && Owner.DocumentManagerSubViewModel.ActiveDocument.ReferenceLayerViewModel.ReferenceBitmap is null;
 
-    [Command.Basic("PixiEditor.Layer.ImportReferenceLayer", "ADD_REFERENCE_LAYER", "ADD_REFERENCE_LAYER", CanExecute = "PixiEditor.Layer.ReferenceLayerDoesntExist")]
+    [Command.Basic("PixiEditor.Layer.ImportReferenceLayer", "ADD_REFERENCE_LAYER", "ADD_REFERENCE_LAYER", CanExecute = "PixiEditor.Layer.ReferenceLayerDoesntExist", IconPath = "Add-reference.png")]
     public void ImportReferenceLayer()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Main/MiscViewModel.cs

@@ -15,7 +15,7 @@ internal class MiscViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Internal("PixiEditor.Links.OpenHyperlink")]
-    [Command.Basic("PixiEditor.Links.OpenDocumentation", "https://pixieditor.net/docs/introduction", "DOCUMENTATION", "Open Documentation", IconPath = "Globe.png")]
+    [Command.Basic("PixiEditor.Links.OpenDocumentation", "https://pixieditor.net/docs/introduction", "DOCUMENTATION", "OPEN_DOCUMENTATION", IconPath = "Globe.png")]
     [Command.Basic("PixiEditor.Links.OpenWebsite", "https://pixieditor.net", "WEBSITE", "OPEN_WEBSITE", IconPath = "Globe.png")]
     [Command.Basic("PixiEditor.Links.OpenRepository", "https://github.com/PixiEditor/PixiEditor", "REPOSITORY", "OPEN_REPOSITORY", IconPath = "Globe.png")]
     [Command.Basic("PixiEditor.Links.OpenLicense", "https://github.com/PixiEditor/PixiEditor/blob/master/LICENSE", "LICENSE", "OPEN_LICENSE", IconPath = "Globe.png")]

+ 8 - 0
src/PixiEditor/ViewModels/SubViewModels/Main/SelectionViewModel.cs

@@ -75,6 +75,14 @@ internal class SelectionViewModel : SubViewModel<ViewModelMain>
         Owner.DocumentManagerSubViewModel.ActiveDocument?.Operations.SelectionToMask(mode);
     }
 
+    [Command.Basic("PixiEditor.Selection.CropToSelection", "CROP_TO_SELECTION", "CROP_TO_SELECTION_DESCRIPTIVE", CanExecute = "PixiEditor.Selection.IsNotEmpty")]
+    public void CropToSelection()
+    {
+        var document = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        
+        document!.Operations.CropToSelection();
+    }
+
     [Evaluator.CanExecute("PixiEditor.Selection.CanNudgeSelectedObject")]
     public bool CanNudgeSelectedObject(int[] dist) => Owner.DocumentManagerSubViewModel.ActiveDocument?.UpdateableChangeActive == true;
 }

+ 39 - 4
src/PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs

@@ -5,6 +5,7 @@ using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Extensions.Common.UserPreferences;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Controllers;
+using PixiEditor.Models.Enums;
 using PixiEditor.Models.Events;
 using PixiEditor.ViewModels.SubViewModels.Document;
 using PixiEditor.ViewModels.SubViewModels.Tools;
@@ -16,10 +17,39 @@ namespace PixiEditor.ViewModels.SubViewModels.Main;
 [Command.Group("PixiEditor.Tools", "TOOLS")]
 internal class ToolsViewModel : SubViewModel<ViewModelMain>
 {
+    private RightClickMode rightClickMode;
+    
     public ZoomToolViewModel? ZoomTool => GetTool<ZoomToolViewModel>();
 
     public ToolViewModel? LastActionTool { get; private set; }
 
+    public RightClickMode RightClickMode
+    {
+        get => rightClickMode;
+        set
+        {
+            if (SetProperty(ref rightClickMode, value))
+            {
+                IPreferences.Current.UpdatePreference(nameof(RightClickMode), value);
+            }
+        }
+    }
+
+    public bool EnableSharedToolbar
+    {
+        get => IPreferences.Current.GetPreference<bool>(nameof(EnableSharedToolbar));
+        set
+        {
+            if (EnableSharedToolbar == value)
+            {
+                return;
+            }
+
+            IPreferences.Current.UpdatePreference(nameof(EnableSharedToolbar), value);
+            RaisePropertyChanged(nameof(EnableSharedToolbar));
+        }
+    }
+
     private Cursor? toolCursor;
     public Cursor? ToolCursor
     {
@@ -56,7 +86,9 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>
 
     public ToolsViewModel(ViewModelMain owner)
         : base(owner)
-    { }
+    {
+        rightClickMode = IPreferences.Current.GetPreference<RightClickMode>(nameof(RightClickMode));
+    }
 
     public void SetupTools(IServiceProvider services)
     {
@@ -112,7 +144,7 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>
             tool.Toolbar.GenerateSettings();
 
         if (ActiveTool != null) ActiveTool.IsTransient = false;
-        bool shareToolbar = IPreferences.Current.GetPreference<bool>("EnableSharedToolbar");
+        bool shareToolbar = EnableSharedToolbar;
         if (ActiveTool is not null)
         {
             ActiveTool.IsActive = false;
@@ -227,9 +259,12 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>
         ShortcutController.UnblockShortcutExecution("ShortcutDown");
     }
 
-    public void LeftMouseButtonDownInlet(VecD canvasPos)
+    public void UseToolEventInlet(VecD canvasPos, MouseButton button)
     {
-        ActiveTool?.OnLeftMouseButtonDown(canvasPos);
+        if (ActiveTool == null) return;
+
+        ActiveTool.UsedWith = button;
+        ActiveTool.UseTool(canvasPos);
     }
 
     public void ConvertedKeyDownInlet(FilteredKeyEventArgs args)

+ 5 - 0
src/PixiEditor/ViewModels/SubViewModels/Tools/ShapeTool.cs

@@ -7,6 +7,11 @@ namespace PixiEditor.ViewModels.SubViewModels.Tools;
 internal abstract class ShapeTool : ToolViewModel
 {
     public override BrushShape BrushShape => BrushShape.Pixel;
+
+    public override bool UsesColor => true;
+
+    public override bool IsErasable => true;
+
     public ShapeTool()
     {
         Cursor = Cursors.Cross;

+ 16 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/ToolViewModel.cs

@@ -30,6 +30,21 @@ internal abstract class ToolViewModel : NotifyableObject
 
     public abstract LocalizedString Tooltip { get; }
 
+    /// <summary>
+    /// Determines if secondary color should be used if right click mode is set to secondary color
+    /// </summary>
+    public virtual bool UsesColor => false;
+
+    /// <summary>
+    /// Determines if PixiEditor should switch to the Eraser when right click mode is set to erase
+    /// </summary>
+    public virtual bool IsErasable => false;
+
+    /// <summary>
+    /// The mouse button that is being used with the tool
+    /// </summary>
+    public MouseButton UsedWith { get; set; }
+
     private LocalizedString actionDisplay = string.Empty;
     public LocalizedString ActionDisplay
     {
@@ -67,7 +82,7 @@ internal abstract class ToolViewModel : NotifyableObject
     }
 
     public virtual void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown) { }
-    public virtual void OnLeftMouseButtonDown(VecD pos) { }
+    public virtual void UseTool(VecD pos) { }
     public virtual void OnSelected() 
     {
         ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TryStopToolLinkedExecutor();

+ 3 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/BrightnessToolViewModel.cs

@@ -23,6 +23,8 @@ internal class BrightnessToolViewModel : ToolViewModel
         Toolbar = ToolbarFactory.Create<BrightnessToolViewModel, BasicToolbar>(this);
     }
 
+    public override bool IsErasable => true;
+
     public override LocalizedString Tooltip => new LocalizedString("BRIGHTNESS_TOOL_TOOLTIP", Shortcut);
 
     public override BrushShape BrushShape => BrushShape.Circle;
@@ -52,7 +54,7 @@ internal class BrightnessToolViewModel : ToolViewModel
         }
     }
 
-    public override void OnLeftMouseButtonDown(VecD pos)
+    public override void UseTool(VecD pos)
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseBrightnessTool();
     }

+ 3 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/ColorPickerToolViewModel.cs

@@ -22,6 +22,8 @@ internal class ColorPickerToolViewModel : ToolViewModel
     
     public override bool HideHighlight => true;
 
+    public override bool UsesColor => true;
+
     public override string ToolNameLocalizationKey => "COLOR_PICKER_TOOL";
     public override BrushShape BrushShape => BrushShape.Pixel;
 
@@ -149,7 +151,7 @@ internal class ColorPickerToolViewModel : ToolViewModel
         referenceLayer.RaiseShowHighestChanged();
     }
 
-    public override void OnLeftMouseButtonDown(VecD pos)
+    public override void UseTool(VecD pos)
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseColorPickerTool();
     }

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/EllipseToolViewModel.cs

@@ -35,7 +35,7 @@ internal class EllipseToolViewModel : ShapeTool
         }
     }
 
-    public override void OnLeftMouseButtonDown(VecD pos)
+    public override void UseTool(VecD pos)
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseEllipseTool();
     }

+ 3 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/EraserToolViewModel.cs

@@ -21,12 +21,14 @@ internal class EraserToolViewModel : ToolViewModel
     [Settings.Inherited]
     public int ToolSize => GetValue<int>();
 
+    public override bool IsErasable => true;
+
     public override string ToolNameLocalizationKey => "ERASER_TOOL";
     public override BrushShape BrushShape => BrushShape.Circle;
 
     public override LocalizedString Tooltip => new LocalizedString("ERASER_TOOL_TOOLTIP", Shortcut);
 
-    public override void OnLeftMouseButtonDown(VecD pos)
+    public override void UseTool(VecD pos)
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseEraserTool();
     }

+ 6 - 2
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/FloodFillToolViewModel.cs

@@ -16,7 +16,11 @@ internal class FloodFillToolViewModel : ToolViewModel
     public override string ToolNameLocalizationKey => "FLOOD_FILL_TOOL";
     public override BrushShape BrushShape => BrushShape.Pixel;
 
-    public override LocalizedString Tooltip => new LocalizedString("FLOOD_FILL_TOOL_TOOLTIP", Shortcut);
+    public override LocalizedString Tooltip => new("FLOOD_FILL_TOOL_TOOLTIP", Shortcut);
+
+    public override bool UsesColor => true;
+
+    public override bool IsErasable => true;
 
     public bool ConsiderAllLayers { get; private set; }
 
@@ -39,7 +43,7 @@ internal class FloodFillToolViewModel : ToolViewModel
         }
     }
 
-    public override void OnLeftMouseButtonDown(VecD pos)
+    public override void UseTool(VecD pos)
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseFloodFillTool();
     }

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/LassoToolViewModel.cs

@@ -50,7 +50,7 @@ internal class LassoToolViewModel : ToolViewModel
     [Settings.Enum("MODE_LABEL")]
     public SelectionMode SelectMode => GetValue<SelectionMode>();
     
-    public override void OnLeftMouseButtonDown(VecD pos)
+    public override void UseTool(VecD pos)
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseLassoTool();
     }

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/LineToolViewModel.cs

@@ -41,7 +41,7 @@ internal class LineToolViewModel : ShapeTool
         }
     }
 
-    public override void OnLeftMouseButtonDown(VecD pos)
+    public override void UseTool(VecD pos)
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseLineTool();
     }

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/MagicWandToolViewModel.cs

@@ -30,7 +30,7 @@ internal class MagicWandToolViewModel : ToolViewModel
         ActionDisplay = "MAGIC_WAND_ACTION_DISPLAY";
     }
     
-    public override void OnLeftMouseButtonDown(VecD pos)
+    public override void UseTool(VecD pos)
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseMagicWandTool();
     }

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/MoveToolViewModel.cs

@@ -45,7 +45,7 @@ internal class MoveToolViewModel : ToolViewModel
         }
     }
 
-    public override void OnLeftMouseButtonDown(VecD pos)
+    public override void UseTool(VecD pos)
     {
         ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseShiftLayerTool();
     }

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/PenToolViewModel.cs

@@ -43,7 +43,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools
             ActionDisplay = new LocalizedString("PEN_TOOL_ACTION_DISPLAY", Shortcut);
         }
 
-        public override void OnLeftMouseButtonDown(VecD pos)
+        public override void UseTool(VecD pos)
         {
             ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UsePenTool();
         }

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/RectangleToolViewModel.cs

@@ -35,7 +35,7 @@ internal class RectangleToolViewModel : ShapeTool
         }
     }
 
-    public override void OnLeftMouseButtonDown(VecD pos)
+    public override void UseTool(VecD pos)
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseRectangleTool();
     }

+ 2 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/SelectToolViewModel.cs

@@ -16,6 +16,7 @@ internal class SelectToolViewModel : ToolViewModel
 {
     private string defaultActionDisplay = "SELECT_TOOL_ACTION_DISPLAY_DEFAULT";
     public override string ToolNameLocalizationKey => "SELECT_TOOL_NAME";
+
     public SelectToolViewModel()
     {
         ActionDisplay = defaultActionDisplay;
@@ -55,7 +56,7 @@ internal class SelectToolViewModel : ToolViewModel
 
     public override LocalizedString Tooltip => new LocalizedString("SELECT_TOOL_TOOLTIP", Shortcut);
 
-    public override void OnLeftMouseButtonDown(VecD pos)
+    public override void UseTool(VecD pos)
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseSelectTool();
     }

+ 8 - 3
src/PixiEditor/Views/Dialogs/AboutPopup.xaml

@@ -14,7 +14,9 @@
         mc:Ignorable="d" WindowStyle="None"
         ui:Translator.Key="ABOUT"
         WindowStartupLocation="CenterScreen"
-        Height="510" Width="400" Name="aboutPopup" MinWidth="100" MinHeight="100"
+        Name="aboutPopup"
+        Width="440" Height="510"
+        MaxWidth="440" MaxHeight="510"
         FlowDirection="{helpers:Localization FlowDirection}">
     <Window.CommandBindings>
         <CommandBinding Command="{x:Static SystemCommands.CloseWindowCommand}" CanExecute="CommandBinding_CanExecute"
@@ -23,7 +25,7 @@
     
     <WindowChrome.WindowChrome>
         <WindowChrome CaptionHeight="32"  GlassFrameThickness="0.1"
-                      ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
+                      ResizeBorderThickness="0" />
     </WindowChrome.WindowChrome>
 
     <DockPanel Background="{StaticResource AccentColor}" Focusable="True">
@@ -75,7 +77,7 @@
                 </Ellipse>
                 <Label Style="{StaticResource SettingsText}" Margin="10 0 0 0" FontSize="14">
                     <Hyperlink Command="{cmds:Command PixiEditor.Links.OpenHyperlink, UseProvided=True}" CommandParameter="https://github.com/CPKreuz" Style="{StaticResource SettingsLink}">
-                        <Run Text="Philip Kreuz (CPK)"/>
+                        <Run Text="Philip Kreuz (cpk)"/>
                         <Run Text="" FontFamily="{StaticResource Feather}"/>
                     </Hyperlink>
                 </Label>
@@ -125,6 +127,9 @@
                     <Button Command="{cmds:Command PixiEditor.Links.OpenHyperlink, UseProvided=True}" CommandParameter="https://reddit.com/r/PixiEditor"
                             Style="{StaticResource SocialMediaButton}" Tag="#FF4500" ui:Translator.TooltipKey="REDDIT"
                             Content="/Images/SocialMedia/RedditIcon.png"/>
+                    <Button Command="{cmds:Command PixiEditor.Links.OpenHyperlink, UseProvided=True}" CommandParameter="https://store.steampowered.com/app/2218560"
+                            Style="{StaticResource SocialMediaButton}" Tag="#00adee" views:Translator.TooltipKey="STEAM"
+                            Content="/Images/SocialMedia/SteamIcon.png"/>
                     <Button Command="{cmds:Command PixiEditor.Links.OpenHyperlink, UseProvided=True}" CommandParameter="https://github.com/PixiEditor/PixiEditor"
                             Style="{StaticResource SocialMediaButton}" Tag="Black" ui:Translator.TooltipKey="GITHUB"
                             Content="/Images/SocialMedia/GithubIcon.png"/>

+ 4 - 1
src/PixiEditor/Views/Dialogs/HelloTherePopup.xaml

@@ -252,11 +252,14 @@
                             Style="{StaticResource SocialMediaButton}" Tag="#e3002d"
                             Content="/Images/SocialMedia/WebsiteIcon.png"/>
                     <Button Command="{cmds:Command PixiEditor.Links.OpenHyperlink, UseProvided=True}" CommandParameter="https://discord.gg/tzkQFDkqQS"
-                            Style="{StaticResource SocialMediaButton}" Tag="#7289DA" ui:Translator.TooltipKey="DISCORD"
+                            Style="{StaticResource SocialMediaButton}" Tag="#5865F2" views:Translator.TooltipKey="DISCORD"
                             Content="/Images/SocialMedia/DiscordIcon.png"/>
                     <Button Command="{cmds:Command PixiEditor.Links.OpenHyperlink, UseProvided=True}" CommandParameter="https://reddit.com/r/PixiEditor"
                             Style="{StaticResource SocialMediaButton}" Tag="#FF4500" ui:Translator.TooltipKey="REDDIT"
                             Content="/Images/SocialMedia/RedditIcon.png"/>
+                    <Button Command="{cmds:Command PixiEditor.Links.OpenHyperlink, UseProvided=True}" CommandParameter="https://store.steampowered.com/app/2218560"
+                            Style="{StaticResource SocialMediaButton}" Tag="#00adee" views:Translator.TooltipKey="STEAM"
+                            Content="/Images/SocialMedia/SteamIcon.png"/>
                     <Button Command="{cmds:Command PixiEditor.Links.OpenHyperlink, UseProvided=True}" CommandParameter="https://github.com/PixiEditor/PixiEditor"
                             Style="{StaticResource SocialMediaButton}" Tag="Black" ui:Translator.TooltipKey="GITHUB"
                             Content="/Images/SocialMedia/GithubIcon.png"/>

+ 0 - 52
src/PixiEditor/Views/Dialogs/ImportFilePopup.xaml

@@ -1,52 +0,0 @@
-<Window x:Class="PixiEditor.Views.Dialogs.ImportFilePopup"
-        x:ClassModifier="internal"
-        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-        xmlns:local="clr-namespace:PixiEditor.Views"
-        xmlns:dial="clr-namespace:PixiEditor.Views.Dialogs"
-        xmlns:vm="clr-namespace:PixiEditor.ViewModels"
-        xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours" 
-        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
-        xmlns:userControls="clr-namespace:PixiEditor.Views.UserControls"
-        xmlns:helpers="clr-namespace:PixiEditor.Helpers"
-        xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
-        mc:Ignorable="d" BorderBrush="Black" BorderThickness="1"
-        ShowInTaskbar="False" 
-        MinHeight="250" MinWidth="300" Height="250" Width="300" 
-        WindowStyle="None" 
-        WindowStartupLocation="CenterScreen" 
-        Name="importFilePopup"
-        ui:Translator.Key="IMPORT_FILE_TITLE"
-        DataContext="{DynamicResource ImportFilePopupViewModel}"
-        FlowDirection="{helpers:Localization FlowDirection}">
-    <Window.Resources>
-        <vm:ImportFilePopupViewModel x:Key="ImportFilePopupViewModel" />
-    </Window.Resources>
-    <WindowChrome.WindowChrome>
-        <WindowChrome CaptionHeight="32"  GlassFrameThickness="0.1"
-                      ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
-    </WindowChrome.WindowChrome>
-
-    <Window.CommandBindings>
-        <CommandBinding Command="{x:Static SystemCommands.CloseWindowCommand}" CanExecute="CommandBinding_CanExecute"
-                        Executed="CommandBinding_Executed_Close" />
-    </Window.CommandBindings>
-
-    <DockPanel Background="{StaticResource AccentColor}">
-        <i:Interaction.Behaviors>
-            <behaviours:ClearFocusOnClickBehavior/>
-        </i:Interaction.Behaviors>
-
-        <dial:DialogTitleBar DockPanel.Dock="Top"
-            TitleKey="IMPORT_FILE_TITLE" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"/>
-        <Button DockPanel.Dock="Bottom" Width="70" HorizontalAlignment="Center" IsDefault="True"
-                    Margin="15" Style="{StaticResource DarkRoundButton}" Content="Import" Command="{Binding OkCommand}"
-                    CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
-        <userControls:SizePicker x:Name="sizePicker" Width="230" Height="125" Margin="0,30,0,0"
-                                  ChosenWidth="{Binding ImportWidth, Mode=TwoWay}"
-                                  ChosenHeight="{Binding ImportHeight, Mode=TwoWay}" />
-
-    </DockPanel>
-</Window>

+ 0 - 48
src/PixiEditor/Views/Dialogs/ImportFilePopup.xaml.cs

@@ -1,48 +0,0 @@
-using System.Windows;
-using System.Windows.Input;
-using PixiEditor.ViewModels;
-
-namespace PixiEditor.Views.Dialogs;
-
-internal partial class ImportFilePopup : Window
-{
-    private readonly ImportFilePopupViewModel dc = new ImportFilePopupViewModel();
-
-    public ImportFilePopup()
-    {
-        InitializeComponent();
-        DataContext = dc;
-        Loaded += (_, _) => sizePicker.FocusWidthPicker();
-    }
-
-
-    public int ImportHeight
-    {
-        get => dc.ImportHeight;
-        set => dc.ImportHeight = value;
-    }
-
-
-    public int ImportWidth
-    {
-        get => dc.ImportWidth;
-        set => dc.ImportWidth = value;
-    }
-
-
-    public string FilePath
-    {
-        get => dc.FilePath;
-        set => dc.FilePath = value;
-    }
-
-    private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
-    {
-        e.CanExecute = true;
-    }
-
-    private void CommandBinding_Executed_Close(object sender, ExecutedRoutedEventArgs e)
-    {
-        SystemCommands.CloseWindow(this);
-    }
-}

+ 12 - 3
src/PixiEditor/Views/Dialogs/SettingsWindow.xaml

@@ -6,7 +6,7 @@
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:local="clr-namespace:PixiEditor.Views.Dialogs" 
         xmlns:sys="clr-namespace:System;assembly=System.Runtime"
-        xmlns:viewmodels="clr-namespace:PixiEditor.ViewModels" 
+        xmlns:vm="clr-namespace:PixiEditor.ViewModels" 
         xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters" 
         xmlns:views="clr-namespace:PixiEditor.Views" 
         xmlns:i="http://schemas.microsoft.com/xaml/behaviors" 
@@ -18,6 +18,7 @@
         xmlns:settingGroups="clr-namespace:PixiEditor.Views.Dialogs.SettingGroups"
         xmlns:helpers="clr-namespace:PixiEditor.Helpers"
         xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+        xmlns:enums="clr-namespace:PixiEditor.Models.Enums"
         mc:Ignorable="d"
         Name="window" 
         Height="688" Width="780"
@@ -29,7 +30,7 @@
         FlowDirection="{helpers:Localization FlowDirection}"
         ui:Translator.Key="SETTINGS">
     <Window.Resources>
-        <viewmodels:SettingsWindowViewModel x:Key="SettingsWindowViewModel"/>
+        <vm:SettingsWindowViewModel x:Key="SettingsWindowViewModel"/>
         <BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
     </Window.Resources>
 
@@ -147,7 +148,15 @@
                                  Height="21" MaxSize="9999" HorizontalAlignment="Left"/>
                 </StackPanel>
 
-                <Label Style="{StaticResource SettingsHeader}" d:Content="Tools" ui:Translator.Key="TOOLS"/>
+                <Label Style="{StaticResource SettingsHeader}" d:Content="Tools" views:Translator.Key="TOOLS"/>
+                
+                <StackPanel Margin="27 0" Orientation="Horizontal">
+                    <Label Style="{StaticResource SettingsText}" Margin="0,0,7,0"
+                           views:Translator.Key="RIGHT_CLICK_MODE"/>
+                    <ComboBox SelectedItem="{Binding RightClickMode, Source={vm:MainVM ToolsSVM}, Mode=TwoWay}"
+                              ItemsSource="{helpers:Enum enums:RightClickMode}"
+                              Width="160" Style="{StaticResource TranslatedEnum}"/>
+                </StackPanel>
 
                 <CheckBox VerticalAlignment="Center" Margin="27 5"
                     IsChecked="{Binding SettingsSubViewModel.Tools.EnableSharedToolbar}" d:Content="Enable shared toolbar" ui:Translator.Key="ENABLE_SHARED_TOOLBAR"/>

+ 18 - 4
src/PixiEditor/Views/MainWindow.xaml

@@ -259,6 +259,9 @@
                         <MenuItem
                             ui1:Translator.Key="INVERT"
                             cmds:Menu.Command="PixiEditor.Selection.InvertSelection" />
+                        <MenuItem
+                            views:Translator.Key="CROP_TO_SELECTION"
+                            cmds:Menu.Command="PixiEditor.Selection.CropToSelection" />
                         <Separator/>
                         <MenuItem ui1:Translator.Key="SELECTION_TO_MASK">
                             <MenuItem
@@ -631,6 +634,7 @@
                                             StylusOutOfRangeCommand="{cmds:Command PixiEditor.Stylus.StylusOutOfRange, UseProvided=True}"
                                             FlipX="{Binding FlipX, Mode=TwoWay}"
                                             FlipY="{Binding FlipY, Mode=TwoWay}"
+                                            ContextMenuOpening="Viewport_OnContextMenuOpening"
                                             Stylus.IsTapFeedbackEnabled="False" 
                                             Stylus.IsTouchFeedbackEnabled="False"
                                             Document="{Binding Document}">
@@ -816,7 +820,7 @@
                 <Grid Cursor="Arrow">
                     <Grid.RowDefinitions>
                         <RowDefinition/>
-                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="30"/>
                     </Grid.RowDefinitions>
 
                     <ScrollViewer VerticalScrollBarVisibility="Auto">
@@ -851,9 +855,19 @@
                             </ItemsControl.ItemTemplate>
                         </ItemsControl>
                     </ScrollViewer>
-                    <Border Background="{Binding ColorsSubViewModel.PrimaryColor, Mode=TwoWay, Converter={converters:GenericColorToMediaColorConverter}}"
-                            BorderBrush="Gray" BorderThickness="1" CornerRadius="0,0,5,5"
-                            Grid.Row="1" Height="30"/>
+                    <Grid Grid.Row="1">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="0.4*"/>
+                        </Grid.ColumnDefinitions>
+                        
+                        <Border Background="{Binding ColorsSubViewModel.PrimaryColor, Mode=TwoWay, Converter={converters:GenericColorToMediaColorConverter}}"
+                                BorderBrush="Gray" BorderThickness="1" CornerRadius="0,0,5,5"
+                                Grid.ColumnSpan="2"/>
+                        <Border Background="{Binding ColorsSubViewModel.SecondaryColor, Mode=TwoWay, Converter={converters:GenericColorToMediaColorConverter}}"
+                                Margin="0,1,1,1" CornerRadius="0,0,5,0"
+                                Grid.Column="1"/>
+                    </Grid>
                 </Grid>
             </Border>
 

+ 26 - 0
src/PixiEditor/Views/MainWindow.xaml.cs

@@ -15,9 +15,12 @@ using PixiEditor.Extensions.UI;
 using PixiEditor.Helpers;
 using PixiEditor.Models.AppExtensions;
 using PixiEditor.Models.Controllers;
+using PixiEditor.Models.Enums;
 using PixiEditor.Models.IO;
 using PixiEditor.Platform;
 using PixiEditor.ViewModels.SubViewModels.Document;
+using PixiEditor.ViewModels.SubViewModels.Tools;
+using PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 
 namespace PixiEditor.Views;
 
@@ -271,4 +274,27 @@ internal partial class MainWindow : Window
             e.Handled = true;
         }
     }
+
+    private void Viewport_OnContextMenuOpening(object sender, ContextMenuEventArgs e)
+    {
+        var tools = DataContext.ToolsSubViewModel;
+
+        var superSpecialBrightnessTool = tools.RightClickMode == RightClickMode.SecondaryColor && tools.ActiveTool is BrightnessToolViewModel;
+        var superSpecialColorPicker = tools.RightClickMode == RightClickMode.Erase && tools.ActiveTool is ColorPickerToolViewModel;
+
+        if (superSpecialBrightnessTool || superSpecialColorPicker)
+        {
+            e.Handled = true;
+            return;
+        }
+
+        var useContextMenu = DataContext.ToolsSubViewModel.RightClickMode == RightClickMode.ContextMenu;
+        var usesErase = tools.RightClickMode == RightClickMode.Erase && tools.ActiveTool.IsErasable;
+        var usesSecondaryColor = tools.RightClickMode == RightClickMode.SecondaryColor && tools.ActiveTool.UsesColor;
+
+        if (!useContextMenu && (usesErase || usesSecondaryColor))
+        {
+            e.Handled = true;
+        }
+    }
 }

+ 13 - 13
src/PixiEditor/Views/UserControls/AnchorPointPicker.xaml

@@ -19,63 +19,63 @@
             <ColumnDefinition Width="26" />
             <ColumnDefinition Width="26" />
         </Grid.ColumnDefinitions>
-        <ToggleButton IsChecked="True" Checked="ToggleButton_Checked" Click="ToggleButton_Click" Margin="0.25"
-                      Style="{DynamicResource AnchorPointToggleButtonStyle}" ui:Translator.TooltipKey="TOP_LEFT" Grid.Row="0"
+        <ToggleButton IsChecked="True" Checked="ToggleButton_Checked" PreviewMouseLeftButtonDown="ToggleButton_Click" Margin="0.25"
+                      Style="{DynamicResource AnchorPointToggleButtonStyle}" views:Translator.TooltipKey="TOP_LEFT" Grid.Row="0"
                       Grid.Column="0" BorderBrush="Black">
             <ToggleButton.Background>
                 <ImageBrush ImageSource="../../Images/AnchorDot.png" />
             </ToggleButton.Background>
         </ToggleButton>
-        <ToggleButton Checked="ToggleButton_Checked" Click="ToggleButton_Click" Margin="0.25"
-                      Style="{DynamicResource AnchorPointToggleButtonStyle}" Grid.Row="0" ui:Translator.TooltipKey="TOP_CENTER"
+        <ToggleButton Checked="ToggleButton_Checked" PreviewMouseLeftButtonDown="ToggleButton_Click" Margin="0.25"
+                      Style="{DynamicResource AnchorPointToggleButtonStyle}" Grid.Row="0" views:Translator.TooltipKey="TOP_CENTER"
                       Grid.Column="1" BorderBrush="Black">
             <ToggleButton.Background>
                 <ImageBrush ImageSource="../../Images/AnchorDot.png" />
             </ToggleButton.Background>
         </ToggleButton>
-        <ToggleButton Checked="ToggleButton_Checked" Click="ToggleButton_Click" Margin="0.25"
-                      Style="{DynamicResource AnchorPointToggleButtonStyle}" ui:Translator.TooltipKey="TOP_RIGHT" Grid.Row="0"
+        <ToggleButton Checked="ToggleButton_Checked" PreviewMouseLeftButtonDown="ToggleButton_Click" Margin="0.25"
+                      Style="{DynamicResource AnchorPointToggleButtonStyle}" views:Translator.TooltipKey="TOP_RIGHT" Grid.Row="0"
                       Grid.Column="2" BorderBrush="Black">
             <ToggleButton.Background>
                 <ImageBrush ImageSource="../../Images/AnchorDot.png" />
             </ToggleButton.Background>
         </ToggleButton>
-        <ToggleButton Checked="ToggleButton_Checked" Click="ToggleButton_Click" Margin="0.25"
-                      Style="{DynamicResource AnchorPointToggleButtonStyle}" Grid.Row="1" ui:Translator.TooltipKey="MIDDLE_LEFT"
+        <ToggleButton Checked="ToggleButton_Checked" PreviewMouseLeftButtonDown="ToggleButton_Click" Margin="0.25"
+                      Style="{DynamicResource AnchorPointToggleButtonStyle}" Grid.Row="1" views:Translator.TooltipKey="MIDDLE_LEFT"
                       Grid.Column="0" BorderBrush="Black">
             <ToggleButton.Background>
                 <ImageBrush ImageSource="../../Images/AnchorDot.png" />
             </ToggleButton.Background>
         </ToggleButton>
-        <ToggleButton Checked="ToggleButton_Checked" Click="ToggleButton_Click" Margin="0.25"
+        <ToggleButton Checked="ToggleButton_Checked" PreviewMouseLeftButtonDown="ToggleButton_Click" Margin="0.25"
                       Style="{DynamicResource AnchorPointToggleButtonStyle}" Grid.Row="1" Grid.Column="1"
                       ui:Translator.TooltipKey="MIDDLE_CENTER" BorderBrush="Black">
             <ToggleButton.Background>
                 <ImageBrush ImageSource="../../Images/AnchorDot.png" />
             </ToggleButton.Background>
         </ToggleButton>
-        <ToggleButton Checked="ToggleButton_Checked" Click="ToggleButton_Click" Margin="0.25"
+        <ToggleButton Checked="ToggleButton_Checked" PreviewMouseLeftButtonDown="ToggleButton_Click" Margin="0.25"
                       Style="{DynamicResource AnchorPointToggleButtonStyle}" Grid.Row="1" Grid.Column="2"
                       ui:Translator.TooltipKey="MIDDLE_RIGHT" BorderBrush="Black">
             <ToggleButton.Background>
                 <ImageBrush ImageSource="../../Images/AnchorDot.png" />
             </ToggleButton.Background>
         </ToggleButton>
-        <ToggleButton Checked="ToggleButton_Checked" Click="ToggleButton_Click" Margin="0.25"
+        <ToggleButton Checked="ToggleButton_Checked" PreviewMouseLeftButtonDown="ToggleButton_Click" Margin="0.25"
                       Style="{DynamicResource AnchorPointToggleButtonStyle}" Grid.Row="2" Grid.Column="0"
                       ui:Translator.TooltipKey="BOTTOM_LEFT" BorderBrush="Black">
             <ToggleButton.Background>
                 <ImageBrush ImageSource="../../Images/AnchorDot.png" />
             </ToggleButton.Background>
         </ToggleButton>
-        <ToggleButton Checked="ToggleButton_Checked" Click="ToggleButton_Click" Margin="0.25"
+        <ToggleButton Checked="ToggleButton_Checked" PreviewMouseLeftButtonDown="ToggleButton_Click" Margin="0.25"
                       Style="{DynamicResource AnchorPointToggleButtonStyle}" Grid.Row="2" Grid.Column="1"
                       ui:Translator.TooltipKey="BOTTOM_CENTER" BorderBrush="Black">
             <ToggleButton.Background>
                 <ImageBrush ImageSource="../../Images/AnchorDot.png" />
             </ToggleButton.Background>
         </ToggleButton>
-        <ToggleButton Checked="ToggleButton_Checked" Click="ToggleButton_Click" Margin="0.25"
+        <ToggleButton Checked="ToggleButton_Checked" PreviewMouseLeftButtonDown="ToggleButton_Click" Margin="0.25"
                       Style="{DynamicResource AnchorPointToggleButtonStyle}" Grid.Row="2" Grid.Column="2"
                       ui:Translator.TooltipKey="BOTTOM_RIGHT" BorderBrush="Black">
             <ToggleButton.Background>

+ 10 - 9
src/PixiEditor/Views/UserControls/Layers/FolderControl.xaml

@@ -94,8 +94,14 @@
                             </Grid>
                         </Border>
                         <StackPanel Orientation="Vertical"  Margin="3,0,5,0">
+                            <userControls1:EditableTextBlock
+                                x:Name="editableTextBlock"
+                                d:Text="New Folder" FontSize="14"
+                                VerticalAlignment="Center"
+                                Text="{Binding Folder.NameBindable, ElementName=folderControl, Mode=TwoWay}" />
+                            
                             <StackPanel Orientation="Horizontal">
-                                <TextBlock d:Text="100" Foreground="White">
+                                <TextBlock d:Text="100" Foreground="White" FontSize="11">
                                     <TextBlock.Text>
                                         <Binding ElementName="folderControl" Path="Folder.OpacityBindable" Converter="{converters:MultiplyConverter}" StringFormat="N0">
                                             <Binding.ConverterParameter>
@@ -104,19 +110,14 @@
                                         </Binding>
                                     </TextBlock.Text>
                                 </TextBlock>
-                                <TextBlock Foreground="White">%</TextBlock>
+                                <TextBlock Foreground="White" FontSize="11">%</TextBlock>
                                 <TextBlock 
                                 Margin="5,0,0,0" 
                                 d:Text="Normal" 
-                                Foreground="White" 
+                                Foreground="White"
+                                FontSize="11"
                                 Text="{Binding Folder.BlendModeBindable, ElementName=folderControl, Converter={converters:BlendModeToStringConverter}}"/>
                             </StackPanel>
-                            <userControls1:EditableTextBlock
-                            x:Name="editableTextBlock"
-                            d:Text="New Folder"
-                            FontSize="12"
-                            VerticalAlignment="Center"
-                            Text="{Binding Folder.NameBindable, ElementName=folderControl, Mode=TwoWay}" />
                         </StackPanel>
                     </StackPanel>
                     <Image Source="/Images/Folder.png" Height="20" Margin="0,0,10,0" HorizontalAlignment="Right"/>

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

@@ -67,7 +67,7 @@
                     Panel.ZIndex="5">
                     <Grid Cursor="Hand" Visibility="{Binding ElementName=visibilityCheckbox, Path=IsChecked, Converter={InverseBoolToVisibilityConverter}}" Background="Transparent">
                         <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" DockPanel.Dock="Left">
-                            <Image Margin="5 0 5 0" Width="20" Source="/Images/Layer-add.png"
+                            <Image Margin="5 0 5 0" Width="20" Source="/Images/Add-reference.png"
                                Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={converters:NullToVisibilityConverter}}"/>
 
                             <TextBlock IsEnabled="{Binding ElementName=uc, Path=IsEnabled}" 

+ 97 - 29
src/PixiEditorGen/CommandNameListGenerator.cs

@@ -1,4 +1,5 @@
 using System.Collections.Immutable;
+using System.Text;
 using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.CSharp;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -14,11 +15,15 @@ public class CommandNameListGenerator : IIncrementalGenerator
 
     private const string Groups = "PixiEditor.Models.Commands.Attributes.Commands.Command.GroupAttribute";
 
+    private const string InternalNameAttribute = "PixiEditor.Models.Commands.Attributes.InternalNameAttribute";
+
+    private static DiagnosticDescriptor commandDuplicate = new("Pixi01", "Command/Evaluator duplicate", "{0} with name '{1}' is defined multiple times", "PixiEditor.Commands", DiagnosticSeverity.Error, true);
+    
     public void Initialize(IncrementalGeneratorInitializationContext context)
     {
-        var commandList = CreateSyntaxProvider<Command>(context, Commands).Where(x => x != null);
-        var evaluatorList = CreateSyntaxProvider<Command>(context, Evaluators).Where(x => x != null);
-        var groupList = CreateSyntaxProvider<Group>(context, Groups).Where(x => x != null);
+        var commandList = CreateSyntaxProvider<CommandMethod>(context, Commands).Where(x => x != null);
+        var evaluatorList = CreateSyntaxProvider<CommandMethod>(context, Evaluators).Where(x => x != null);
+        var groupList = CreateSyntaxProvider<GroupType>(context, Groups).Where(x => x != null);
 
         context.RegisterSourceOutput(commandList.Collect(), (context, commands) => AddSource(context, commands, "Commands"));
         context.RegisterSourceOutput(evaluatorList.Collect(), (context, evaluators) => AddSource(context, evaluators, "Evaluators"));
@@ -30,7 +35,7 @@ public class CommandNameListGenerator : IIncrementalGenerator
         return context.SyntaxProvider.CreateSyntaxProvider(
             (x, token) =>
             {
-                if (typeof(T) == typeof(Command))
+                if (typeof(T) == typeof(CommandMethod))
                 {
                     return x is MethodDeclarationSyntax method && method.AttributeLists.Count > 0;
                 }
@@ -42,21 +47,22 @@ public class CommandNameListGenerator : IIncrementalGenerator
             {
                 var member = (MemberDeclarationSyntax)context.Node;
 
-                if (!HasCommandAttribute(member, context, cancelToken, className))
+                var attributes = GetCommandAttributes(member, context, cancelToken, className);
+                if (attributes.Count == 0)
                     return null;
 
                 var symbol = context.SemanticModel.GetDeclaredSymbol(member, cancelToken);
 
-                if (symbol is IMethodSymbol methodSymbol && typeof(T) == typeof(Command))
+                if (symbol is IMethodSymbol methodSymbol && typeof(T) == typeof(CommandMethod))
                 {
                     if (methodSymbol.ReceiverType == null)
                         return null;
 
-                    return (T)(object)new Command(methodSymbol);
+                    return (T)(object)new CommandMethod(attributes, methodSymbol);
                 }
-                else if (symbol is ITypeSymbol typeSymbol && typeof(T) == typeof(Group))
+                else if (symbol is ITypeSymbol typeSymbol && typeof(T) == typeof(GroupType))
                 {
-                    return (T)(object)new Group(typeSymbol);
+                    return (T)(object)new GroupType(typeSymbol);
                 }
                 else
                 {
@@ -65,10 +71,13 @@ public class CommandNameListGenerator : IIncrementalGenerator
             });
     }
 
-    private void AddSource(SourceProductionContext context, ImmutableArray<Command> methodNames, string name)
+    private void AddSource(SourceProductionContext context, ImmutableArray<CommandMethod> methodNames, string name)
     {
-        List<string> createdClasses = new List<string>();
-        SyntaxList<StatementSyntax> statements = new SyntaxList<StatementSyntax>();
+        if (ReportDuplicateDefinitions(context, methodNames, name))
+            return;
+        
+        var createdClasses = new List<string>();
+        var statements = new SyntaxList<StatementSyntax>();
 
         foreach (var methodName in methodNames)
         {
@@ -104,7 +113,26 @@ public class CommandNameListGenerator : IIncrementalGenerator
         context.AddSource($"CommandNameList+{name}", nspace.NormalizeWhitespace().ToFullString());
     }
 
-    private void AddGroupsSource(SourceProductionContext context, ImmutableArray<Group> groups)
+    private bool ReportDuplicateDefinitions(SourceProductionContext context, ImmutableArray<CommandMethod> methodNames, string name)
+    {
+        var hasDuplicate = false;
+        var allAttributes = methodNames.SelectMany(x => x.Attributes).ToArray();
+        
+        foreach (var attribute in allAttributes)
+        {
+            if (!allAttributes.Any(x => x != attribute && x.InternalName == attribute.InternalName))
+            {
+                continue;
+            }
+
+            context.ReportDiagnostic(Diagnostic.Create(commandDuplicate, attribute.InternalNameArgument?.GetLocation(), name.TrimEnd('s'), attribute.InternalName));
+            hasDuplicate = true;
+        }
+
+        return hasDuplicate;
+    }
+
+    private void AddGroupsSource(SourceProductionContext context, ImmutableArray<GroupType> groups)
     {
         SyntaxList<StatementSyntax> statements = new SyntaxList<StatementSyntax>();
 
@@ -133,24 +161,48 @@ public class CommandNameListGenerator : IIncrementalGenerator
         context.AddSource("CommandNameList+Groups", nspace.NormalizeWhitespace().ToFullString());
     }
 
-    private static bool HasCommandAttribute(MemberDeclarationSyntax declaration, GeneratorSyntaxContext context, CancellationToken token, string commandAttributeStart)
+    private static List<CommandAttribute> GetCommandAttributes(MemberDeclarationSyntax declaration, GeneratorSyntaxContext context, CancellationToken token, string commandAttributeStart)
     {
-        foreach (var attrList in declaration.AttributeLists)
+        var list = new List<CommandAttribute>();
+        
+        foreach (var attribute in declaration.AttributeLists.SelectMany(attrList => attrList.Attributes))
         {
-            foreach (var attribute in attrList.Attributes)
-            {
-                token.ThrowIfCancellationRequested();
-                var symbol = context.SemanticModel.GetSymbolInfo(attribute, token);
-                if (symbol.Symbol is not IMethodSymbol methodSymbol)
-                    continue;
-                if (!methodSymbol.ContainingType.ToDisplayString()
+            token.ThrowIfCancellationRequested();
+            var symbol = context.SemanticModel.GetSymbolInfo(attribute, token);
+            if (symbol.Symbol is not IMethodSymbol methodSymbol)
+                continue;
+            if (!methodSymbol.ContainingType.ToDisplayString()
                     .StartsWith(commandAttributeStart))
-                    continue;
-                return true;
+                continue;
+
+            var target = -1;
+                
+            for (var i = 0; i < methodSymbol.Parameters.Length; i++)
+            {
+                var parameter = methodSymbol.Parameters[i];
+                if (parameter.GetAttributes().Any(x => x.AttributeClass?.ToDisplayString() == InternalNameAttribute))
+                {
+                    target = i;
+                    break;
+                }
+            }
+
+            if (target != -1)
+            {
+                var argument = attribute.ArgumentList?.Arguments[target];
+
+                if (argument?.Expression is LiteralExpressionSyntax literal)
+                {
+                    list.Add(new CommandAttribute(argument, literal.Token.ValueText));
+                }
+                else
+                {
+                    list.Add(new CommandAttribute(argument, null));
+                }
             }
         }
 
-        return false;
+        return list;
     }
 
     class CommandMember<TSelf> where TSelf : CommandMember<TSelf>
@@ -163,22 +215,38 @@ public class CommandNameListGenerator : IIncrementalGenerator
         }
     }
 
-    class Command : CommandMember<Command>
+    class CommandMethod : CommandMember<CommandMethod>
     {
         public string MethodName { get; }
 
         public string[] ParameterTypeNames { get; }
+        
+        public List<CommandAttribute> Attributes { get; }
 
-        public Command(IMethodSymbol symbol) : base(symbol.ContainingType.ToDisplayString())
+        public CommandMethod(List<CommandAttribute> attributes, IMethodSymbol symbol) : base(symbol.ContainingType.ToDisplayString())
         {
+            Attributes = attributes;
             MethodName = symbol.Name;
             ParameterTypeNames = symbol.Parameters.Select(x => $"typeof({x.Type.ToDisplayString()})").ToArray();
         }
     }
 
-    class Group : CommandMember<Group>
+    class CommandAttribute
+    {
+        public string? InternalName { get; }
+
+        public AttributeArgumentSyntax? InternalNameArgument { get; }
+        
+        public CommandAttribute(AttributeArgumentSyntax? internalNameArgument, string? internalName)
+        {
+            InternalNameArgument = internalNameArgument;
+            InternalName = internalName;
+        }
+    }
+
+    class GroupType : CommandMember<GroupType>
     {
-        public Group(ITypeSymbol symbol) : base(symbol.ToDisplayString())
+        public GroupType(ITypeSymbol symbol) : base(symbol.ToDisplayString())
         { }
     }
 }

+ 25 - 0
src/PixiEditorGen/Extensions.cs

@@ -0,0 +1,25 @@
+using System.Text;
+using Microsoft.CodeAnalysis;
+
+namespace PixiEditorGen;
+
+internal static class Extensions
+{
+    public static bool AssignableTo(this INamedTypeSymbol? type, INamedTypeSymbol target)
+    {
+        while (true)
+        {
+            if (type == null)
+            {
+                return false;
+            }
+
+            if (type.Name == target.Name)
+            {
+                return true;
+            }
+
+            type = type.BaseType;
+        }
+    }
+}