Quellcode durchsuchen

Implemented rest of main view models and added custom Menu and ContextMenu controls

CPKreuz vor 3 Jahren
Ursprung
Commit
5695a625e8
23 geänderte Dateien mit 451 neuen und 448 gelöschten Zeilen
  1. 36 0
      PixiEditor/Helpers/DesignCommandHelpers.cs
  2. 17 4
      PixiEditor/Models/Commands/CommandController.cs
  3. 2 33
      PixiEditor/Models/Commands/Commands/Command.cs
  4. 15 0
      PixiEditor/Models/Commands/Exceptions/CommandNotFoundException.cs
  5. 58 2
      PixiEditor/Models/Commands/XAML/Command.cs
  6. 48 0
      PixiEditor/Models/Commands/XAML/ContextMenu.cs
  7. 48 0
      PixiEditor/Models/Commands/XAML/Menu.cs
  8. 12 6
      PixiEditor/Models/Commands/XAML/ShortcutBinding.cs
  9. 17 30
      PixiEditor/ViewModels/SubViewModels/Main/ClipboardViewModel.cs
  10. 8 20
      PixiEditor/ViewModels/SubViewModels/Main/ColorsViewModel.cs
  11. 43 10
      PixiEditor/ViewModels/SubViewModels/Main/DebugViewModel.cs
  12. 13 26
      PixiEditor/ViewModels/SubViewModels/Main/DocumentViewModel.cs
  13. 16 67
      PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs
  14. 2 0
      PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs
  15. 9 47
      PixiEditor/ViewModels/SubViewModels/Main/MiscViewModel.cs
  16. 9 20
      PixiEditor/ViewModels/SubViewModels/Main/SelectionViewModel.cs
  17. 12 13
      PixiEditor/ViewModels/SubViewModels/Main/UndoViewModel.cs
  18. 3 4
      PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs
  19. 9 16
      PixiEditor/ViewModels/SubViewModels/Main/ViewportViewModel.cs
  20. 15 6
      PixiEditor/ViewModels/SubViewModels/Main/WindowViewModel.cs
  21. 2 78
      PixiEditor/ViewModels/ViewModelMain.cs
  22. 4 4
      PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs
  23. 53 62
      PixiEditor/Views/MainWindow.xaml

+ 36 - 0
PixiEditor/Helpers/DesignCommandHelpers.cs

@@ -0,0 +1,36 @@
+using PixiEditor.Models.Commands;
+using CommandAttribute = PixiEditor.Models.Commands.Attributes.Command;
+using System.Reflection;
+using PixiEditor.Models.Commands.Exceptions;
+
+namespace PixiEditor.Helpers
+{
+    /// <summary>
+    /// Helps with debugging when using XAML
+    /// </summary>
+    public static class DesignCommandHelpers
+    {
+        private static IEnumerable<CommandAttribute.CommandAttribute> _commands;
+
+        public static CommandAttribute.CommandAttribute GetCommandAttribute(string name)
+        {
+            if (_commands == null)
+            {
+                _commands = Assembly
+                    .GetAssembly(typeof(CommandController))
+                    .GetTypes()
+                    .SelectMany(x => x.GetMethods())
+                    .SelectMany(x => x.GetCustomAttributes<CommandAttribute.CommandAttribute>());
+            }
+
+            var command = _commands.SingleOrDefault(x => x.Name == name || x.Name == $"#DEBUG#{name}");
+
+            if (command == null)
+            {
+                throw new CommandNotFoundException(name);
+            }
+
+            return command;
+        }
+    }
+}

+ 17 - 4
PixiEditor/Models/Commands/CommandController.cs

@@ -7,6 +7,8 @@ namespace PixiEditor.Models.Commands
 {
     public class CommandController
     {
+        public static CommandController Current { get; private set; }
+
         public CommandCollection Commands { get; set; }
 
         public Dictionary<string, FactoryEvaluator> FactoryEvaluators { get; set; }
@@ -20,6 +22,8 @@ namespace PixiEditor.Models.Commands
             CanExecuteEvaluators = new();
 
             Init(services);
+
+            Current ??= this;
         }
 
         public void Init(IServiceProvider services)
@@ -64,9 +68,10 @@ namespace PixiEditor.Models.Commands
 
                     foreach (var attribute in commandAttrs)
                     {
-                        AddCommand(method, instanceType, attribute, (x, xCan) => new Command.BasicCommand
+                        AddCommand(method, instanceType, attribute, (isDebug, name, x, xCan) => new Command.BasicCommand
                         {
-                            Name = attribute.Name,
+                            Name = name,
+                            IsDebug = isDebug,
                             Display = attribute.Display,
                             Description = attribute.Description,
                             Methods = new(x, xCan),
@@ -117,7 +122,7 @@ namespace PixiEditor.Models.Commands
                 evaluators.Add(evaluator.Name, evaluator);
             }
 
-            void AddCommand<TAttr, TCommand>(MethodInfo method, object instance, TAttr attribute, Func<Action<object>, Predicate<object>, TCommand> commandFactory)
+            void AddCommand<TAttr, TCommand>(MethodInfo method, object instance, TAttr attribute, Func<bool, string, Action<object>, Predicate<object>, TCommand> commandFactory)
                 where TAttr : CommandAttribute.CommandAttribute
                 where TCommand : Command
             {if (method.GetParameters().Length > 1)
@@ -142,7 +147,15 @@ namespace PixiEditor.Models.Commands
                     action = x => method.Invoke(instance, null);
                 }
 
-                Commands.Add(commandFactory(action, x => CanExecuteEvaluators[attribute.CanExecute].Evaluate(x)));
+                string name = attribute.Name;
+                bool isDebug = attribute.Name.StartsWith("#DEBUG#");
+
+                if (attribute.Name.StartsWith("#DEBUG#"))
+                {
+                    name = name["#DEBUG#".Length..];
+                }
+
+                Commands.Add(commandFactory(isDebug, name, action, x => attribute.CanExecute == null || CanExecuteEvaluators[attribute.CanExecute].Evaluate(x)));
             }
         }
     }

+ 2 - 33
PixiEditor/Models/Commands/Commands/Command.cs

@@ -10,6 +10,8 @@ namespace PixiEditor.Models.Commands
     {
         private KeyCombination _shortcut;
 
+        public bool IsDebug { get; init; }
+
         public string Name { get; init; }
 
         public string Display { get; init; }
@@ -31,38 +33,5 @@ namespace PixiEditor.Models.Commands
         public void Execute() => Methods.Execute(GetParameter());
 
         public bool CanExecute() => Methods.CanExecute(GetParameter());
-
-        public ICommand GetICommand(bool useProvidedParameter) => new ProvidedICommand()
-        {
-            Command = this,
-            UseProvidedParameter = useProvidedParameter,
-        };
-
-        class ProvidedICommand : ICommand
-        {
-            public event EventHandler CanExecuteChanged
-            {
-                add => CommandManager.RequerySuggested += value;
-                remove => CommandManager.RequerySuggested -= value;
-            }
-
-            public Command Command { get; init; }
-
-            public bool UseProvidedParameter { get; init; }
-
-            public bool CanExecute(object parameter) => UseProvidedParameter ? Command.Methods.CanExecute(parameter) : Command.CanExecute();
-
-            public void Execute(object parameter)
-            {
-                if (UseProvidedParameter)
-                {
-                    Command.Methods.Execute(parameter);
-                }
-                else
-                {
-                    Command.Execute();
-                }
-            }
-        }
     }
 }

+ 15 - 0
PixiEditor/Models/Commands/Exceptions/CommandNotFoundException.cs

@@ -0,0 +1,15 @@
+namespace PixiEditor.Models.Commands.Exceptions;
+
+[Serializable]
+public class CommandNotFoundException : Exception
+{
+    public string CommandName { get; set; }
+
+    public CommandNotFoundException(string name) : this(name, null) { }
+
+    public CommandNotFoundException(string name, Exception inner) : base($"No command with the name '{name}' found", inner) { }
+
+    protected CommandNotFoundException(
+      System.Runtime.Serialization.SerializationInfo info,
+      System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+}

+ 58 - 2
PixiEditor/Models/Commands/XAML/Command.cs

@@ -1,4 +1,9 @@
-using PixiEditor.ViewModels;
+using Microsoft.Extensions.DependencyInjection;
+using PixiEditor.Helpers;
+using PixiEditor.ViewModels;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Input;
 using System.Windows.Markup;
 
 namespace PixiEditor.Models.Commands.XAML
@@ -17,12 +22,63 @@ namespace PixiEditor.Models.Commands.XAML
 
         public override object ProvideValue(IServiceProvider serviceProvider)
         {
+            try
+            {
+                if (DesignerProperties.GetIsInDesignMode(serviceProvider.GetService<IProvideValueTarget>().TargetObject as DependencyObject))
+                {
+                    var attribute = DesignCommandHelpers.GetCommandAttribute(Name);
+                    return GetICommand(
+                        new Commands.Command.BasicCommand()
+                        {
+                            Name = Name,
+                            Display = attribute.Display,
+                            Description = attribute.Description,
+                            DefaultShortcut = attribute.GetShortcut(),
+                            Shortcut = attribute.GetShortcut()
+                        }, false);
+                }
+            }
+            catch { }
+
             if (commandController == null)
             {
                 commandController = ViewModelMain.Current.CommandController;
             }
 
-            return commandController.Commands[Name].GetICommand(UseProvided);
+            return GetICommand(commandController.Commands[Name], UseProvided);
+        }
+
+        public static ICommand GetICommand(Commands.Command command, bool useProvidedParameter) => new ProvidedICommand()
+        {
+            Command = command,
+            UseProvidedParameter = useProvidedParameter,
+        };
+
+        class ProvidedICommand : ICommand
+        {
+            public event EventHandler CanExecuteChanged
+            {
+                add => CommandManager.RequerySuggested += value;
+                remove => CommandManager.RequerySuggested -= value;
+            }
+
+            public Commands.Command Command { get; init; }
+
+            public bool UseProvidedParameter { get; init; }
+
+            public bool CanExecute(object parameter) => UseProvidedParameter ? Command.Methods.CanExecute(parameter) : Command.CanExecute();
+
+            public void Execute(object parameter)
+            {
+                if (UseProvidedParameter)
+                {
+                    Command.Methods.Execute(parameter);
+                }
+                else
+                {
+                    Command.Execute();
+                }
+            }
         }
     }
 }

+ 48 - 0
PixiEditor/Models/Commands/XAML/ContextMenu.cs

@@ -0,0 +1,48 @@
+using PixiEditor.Helpers;
+using PixiEditor.Models.DataHolders;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace PixiEditor.Models.Commands.XAML
+{
+    public class ContextMenu : System.Windows.Controls.ContextMenu
+    {
+        public static readonly DependencyProperty CommandNameProperty =
+            DependencyProperty.RegisterAttached(
+                "Command",
+                typeof(string),
+                typeof(ContextMenu),
+                new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, CommandChanged)
+            );
+
+        public static string GetCommand(UIElement target) => (string)target.GetValue(CommandNameProperty);
+
+        public static void SetCommand(UIElement target, string value) => target.SetValue(CommandNameProperty, value);
+
+        public static void CommandChanged(object sender, DependencyPropertyChangedEventArgs e)
+        {
+            if (e.NewValue is not string value || sender is not MenuItem item)
+            {
+                throw new InvalidOperationException($"{nameof(ContextMenu)}.Command only works for MenuItem's");
+            }
+
+            if (DesignerProperties.GetIsInDesignMode(sender as DependencyObject))
+            {
+                HandleDesignMode(item, value);
+                return;
+            }
+
+            var command = CommandController.Current.Commands[value];
+
+            item.Command = Command.GetICommand(command, false);
+            item.SetBinding(MenuItem.InputGestureTextProperty, ShortcutBinding.GetBinding(command));
+        }
+
+        private static void HandleDesignMode(MenuItem item, string name)
+        {
+            var command = DesignCommandHelpers.GetCommandAttribute(name);
+            item.InputGestureText = new KeyCombination(command.Key, command.Modifiers).ToString();
+        }
+    }
+}

+ 48 - 0
PixiEditor/Models/Commands/XAML/Menu.cs

@@ -0,0 +1,48 @@
+using PixiEditor.Helpers;
+using PixiEditor.Models.DataHolders;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace PixiEditor.Models.Commands.XAML
+{
+    public class Menu : System.Windows.Controls.Menu
+    {
+        public static readonly DependencyProperty CommandNameProperty =
+            DependencyProperty.RegisterAttached(
+                "Command",
+                typeof(string),
+                typeof(Menu),
+                new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, CommandChanged)
+            );
+
+        public static string GetCommand(UIElement target) => (string)target.GetValue(CommandNameProperty);
+
+        public static void SetCommand(UIElement target, string value) => target.SetValue(CommandNameProperty, value);
+
+        public static void CommandChanged(object sender, DependencyPropertyChangedEventArgs e)
+        {
+            if (e.NewValue is not string value || sender is not MenuItem item)
+            {
+                throw new InvalidOperationException($"{nameof(Menu)}.Command only works for MenuItem's");
+            }
+
+            if (DesignerProperties.GetIsInDesignMode(sender as DependencyObject))
+            {
+                HandleDesignMode(item, value);
+                return;
+            }
+
+            var command = CommandController.Current.Commands[value];
+
+            item.Command = Command.GetICommand(command, false);
+            item.SetBinding(MenuItem.InputGestureTextProperty, ShortcutBinding.GetBinding(command));
+        }
+
+        private static void HandleDesignMode(MenuItem item, string name)
+        {
+            var command = DesignCommandHelpers.GetCommandAttribute(name);
+            item.InputGestureText = new KeyCombination(command.Key, command.Modifiers).ToString();
+        }
+    }
+}

+ 12 - 6
PixiEditor/Models/Commands/XAML/ShortcutBinding.cs

@@ -1,9 +1,9 @@
-using PixiEditor.ViewModels;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using PixiEditor.Helpers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.ViewModels;
+using System.ComponentModel;
+using System.Windows;
 using System.Windows.Data;
 using System.Windows.Markup;
 using ActualCommand = PixiEditor.Models.Commands.Command;
@@ -22,6 +22,12 @@ namespace PixiEditor.Models.Commands.XAML
 
         public override object ProvideValue(IServiceProvider serviceProvider)
         {
+            if (DesignerProperties.GetIsInDesignMode(serviceProvider.GetService<IProvideValueTarget>().TargetObject as DependencyObject))
+            {
+                var attribute = DesignCommandHelpers.GetCommandAttribute(Name);
+                return new KeyCombination(attribute.Key, attribute.Modifiers).ToString();
+            }
+
             if (commandController == null)
             {
                 commandController = ViewModelMain.Current.CommandController;

+ 17 - 30
PixiEditor/ViewModels/SubViewModels/Main/ClipboardViewModel.cs

@@ -1,62 +1,49 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Windows.Media;
-using PixiEditor.Helpers;
+using PixiEditor.Models.Commands.Attributes;
 using PixiEditor.Models.Controllers;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Position;
+using System.Windows.Input;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
     public class ClipboardViewModel : SubViewModel<ViewModelMain>
     {
-        public RelayCommand CopyCommand { get; set; }
-
-        public RelayCommand DuplicateCommand { get; set; }
-
-        public RelayCommand CutCommand { get; set; }
-
-        public RelayCommand PasteCommand { get; set; }
-
         public ClipboardViewModel(ViewModelMain owner)
             : base(owner)
         {
-            CopyCommand = new RelayCommand(Copy);
-            DuplicateCommand = new RelayCommand(Duplicate, Owner.SelectionSubViewModel.SelectionIsNotEmpty);
-            CutCommand = new RelayCommand(Cut, Owner.SelectionSubViewModel.SelectionIsNotEmpty);
-            PasteCommand = new RelayCommand(Paste, CanPaste);
         }
 
-        public void Duplicate(object parameter)
+        [Command.Basic("PixiEditor.Clipboard.Duplicate", "Duplicate", "Duplicate selected area/layer", CanExecute = "PixiEditor.HasDocument", Key = Key.J, Modifiers = ModifierKeys.Control)]
+        public void Duplicate()
         {
-            Copy(null);
-            Paste(null);
+            Copy();
+            Paste();
         }
 
-        public void Cut(object parameter)
+        [Command.Basic("PixiEditor.Clipboard.Cut", "Cut", "Cut selected area/layer", CanExecute = "PixiEditor.HasDocument", Key = Key.X, Modifiers = ModifierKeys.Control)]
+        public void Cut()
         {
-            Copy(null);
+            Copy();
             Owner.BitmapManager.BitmapOperations.DeletePixels(
                 new[] { Owner.BitmapManager.ActiveDocument.ActiveLayer },
                 Owner.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints.ToArray());
         }
 
-        public void Paste(object parameter)
+        [Command.Basic("PixiEditor.Clipboard.Paste", "Paste", "Paste from clipboard", CanExecute = "PixiEditor.Clipboard.CanPaste", Key = Key.V, Modifiers = ModifierKeys.Control)]
+        public void Paste()
         {
             if (Owner.BitmapManager.ActiveDocument == null) return;
             ClipboardController.PasteFromClipboard(Owner.BitmapManager.ActiveDocument);
         }
 
-        private bool CanPaste(object property)
+        [Command.Basic("PixiEditor.Clipboard.Copy", "Copy", "Copy to clipboard", CanExecute = "PixiEditor.HasDocument", Key = Key.C, Modifiers = ModifierKeys.Control)]
+        public void Copy()
         {
-            return Owner.DocumentIsNotNull(null) && ClipboardController.IsImageInClipboard();
+            ClipboardController.CopyToClipboard(Owner.BitmapManager.ActiveDocument);
         }
 
-        private void Copy(object parameter)
+        [Evaluator.CanExecute("PixiEditor.Clipboard.CanPaste")]
+        public bool CanPaste()
         {
-            ClipboardController.CopyToClipboard(Owner.BitmapManager.ActiveDocument);
+            return Owner.DocumentIsNotNull(null) && ClipboardController.IsImageInClipboard();
         }
     }
 }

+ 8 - 20
PixiEditor/ViewModels/SubViewModels/Main/ColorsViewModel.cs

@@ -1,17 +1,11 @@
-using PixiEditor.Helpers;
+using PixiEditor.Models.Commands.Attributes;
 using SkiaSharp;
-using System;
+using System.Windows.Input;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
     public class ColorsViewModel : SubViewModel<ViewModelMain>
     {
-        public RelayCommand SwapColorsCommand { get; set; }
-
-        public RelayCommand SelectColorCommand { get; set; }
-
-        public RelayCommand RemoveSwatchCommand { get; set; }
-
         private SKColor primaryColor = SKColors.Black;
 
         public SKColor PrimaryColor // Primary color, hooked with left mouse button
@@ -46,11 +40,9 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         public ColorsViewModel(ViewModelMain owner)
             : base(owner)
         {
-            SelectColorCommand = new RelayCommand(SelectColor);
-            RemoveSwatchCommand = new RelayCommand(RemoveSwatch);
-            SwapColorsCommand = new RelayCommand(SwapColors);
         }
 
+        [Command.Basic("PixiEditor.Colors.Swap", "Swap colors", "Swap primary and secondary colors", Key = Key.X)]
         public void SwapColors(object parameter)
         {
             var tmp = PrimaryColor;
@@ -66,23 +58,19 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
 
-        private void RemoveSwatch(object parameter)
+        [Command.Basic("PixiEditor.Colors.RemoveSwatch", "", "")]
+        public void RemoveSwatch(SKColor color)
         {
-            if (!(parameter is SKColor))
-            {
-                throw new ArgumentException();
-            }
-
-            SKColor color = (SKColor)parameter;
             if (Owner.BitmapManager.ActiveDocument.Swatches.Contains(color))
             {
                 Owner.BitmapManager.ActiveDocument.Swatches.Remove(color);
             }
         }
 
-        private void SelectColor(object parameter)
+        [Command.Basic("PixiEditor.Colors.SelectColor", "", "")]
+        public void SelectColor(SKColor color)
         {
-            PrimaryColor = parameter as SKColor? ?? throw new ArgumentException();
+            PrimaryColor = color;
         }
     }
 }

+ 43 - 10
PixiEditor/ViewModels/SubViewModels/Main/DebugViewModel.cs

@@ -1,5 +1,9 @@
 using PixiEditor.Helpers;
+using PixiEditor.Models.Commands.Attributes;
+using PixiEditor.Models.Dialogs;
+using PixiEditor.Models.UserPreferences;
 using System;
+using System.Diagnostics;
 using System.IO;
 using System.Reflection;
 
@@ -7,28 +11,57 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 {
     public class DebugViewModel : SubViewModel<ViewModelMain>
     {
-        public RelayCommand OpenFolderCommand { get; set; }
+        public bool IsDebugBuild { get; set; }
 
-        public RelayCommand OpenInstallLocationCommand { get; set; }
+        public bool IsDebugModeEnabaled { get; set; }
 
-        public RelayCommand CrashCommand { get; set; }
+        public bool UseDebug { get; set; }
 
-        public DebugViewModel(ViewModelMain owner)
+        public DebugViewModel(ViewModelMain owner, IPreferences preferences)
             : base(owner)
         {
-            OpenFolderCommand = new RelayCommand(OpenFolder);
-            OpenInstallLocationCommand = new RelayCommand(OpenInstallLocation);
-            CrashCommand = new RelayCommand(_ => throw new InvalidOperationException("Debug Crash"));
+            SetDebug();
+            preferences.AddCallback<bool>("IsDebugModeEnabled", UpdateDebugMode);
+            UpdateDebugMode(preferences.GetPreference<bool>("IsDebugModeEnabled"));
         }
 
-        public static void OpenFolder(object parameter)
+        [Command.Basic("#DEBUG#PixiEditor.Debug.OpenTempDirectory", "%Temp%/PixiEditor", "Open Temp Directory", "Open Temp Directory")]
+        [Command.Basic("#DEBUG#PixiEditor.Debug.OpenLocalAppDataDirectory", "%LocalAppData%/PixiEditor", "Open Local AppData Directory", "Open Local AppData Directory")]
+        [Command.Basic("#DEBUG#PixiEditor.Debug.OpenRoamingAppDataDirectory", "%AppData%/PixiEditor", "Open Roaming AppData Directory", "Open Roaming AppData Directory")]
+        public static void OpenFolder(string path)
         {
-            ProcessHelpers.ShellExecuteEV(parameter as string);
+            ProcessHelpers.ShellExecuteEV(path);
         }
 
-        public static void OpenInstallLocation(object parameter)
+        [Command.Basic("#DEBUG#PixiEditor.Debug.OpenInstallDirectory", "Open Installation Directory", "Open Installation Directory")]
+        public static void OpenInstallLocation()
         {
             ProcessHelpers.ShellExecuteEV(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
         }
+
+        [Command.Basic("#DEBUG#PixiEditor.Debug.Crash", "Crash", "Crash Application")]
+        public static void Crash() => throw new InvalidOperationException("User requested to crash :c");
+
+        [Command.Basic("#DEBUG#PixiEditor.Debug.DeleteUserPreferences", @"%appdata%\PixiEditor\user_preferences.json", "Delete User Preferences (Roaming)", "Delete User Preferences (Roaming AppData)")]
+        [Command.Basic("#DEBUG#PixiEditor.Debug.DeleteEditorData", @"%localappdata%\PixiEditor\editor_data.json", "Delete Editor Data (Local)", "Delete Editor Data (Local AppData)")]
+        public static void DeleteFile(string path)
+        {
+            string file = Environment.ExpandEnvironmentVariables(path);
+            if (!File.Exists(file))
+            {
+                NoticeDialog.Show($"File {path} does not exist\n(Full Path: {file})", "File not found");
+                return;
+            }
+
+            if (ConfirmationDialog.Show($"Are you sure you want to delete {path}?\nThis data will be lost for all installations.\n(Full Path: {file})", "Are you sure?") == Models.Enums.ConfirmationType.Yes)
+            {
+                File.Delete(file);
+            }
+        }
+
+        [Conditional("DEBUG")]
+        private void SetDebug() => IsDebugBuild = true;
+
+        private void UpdateDebugMode(bool setting) => UseDebug = IsDebugBuild || IsDebugModeEnabaled;
     }
 }

+ 13 - 26
PixiEditor/ViewModels/SubViewModels/Main/DocumentViewModel.cs

@@ -1,8 +1,8 @@
-using PixiEditor.Helpers;
+using PixiEditor.Models.Commands.Attributes;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Enums;
-using System.Linq;
+using System.Windows.Input;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
@@ -11,26 +11,9 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         public const string ConfirmationDialogTitle = "Unsaved changes";
         public const string ConfirmationDialogMessage = "The document has been modified. Do you want to save changes?";
 
-        public RelayCommand CenterContentCommand { get; set; }
-
-        public RelayCommand ClipCanvasCommand { get; set; }
-
-        public RelayCommand DeletePixelsCommand { get; set; }
-
-        public RelayCommand OpenResizePopupCommand { get; set; }
-
-        public RelayCommand RotateToRightCommand { get; set; }
-        public RelayCommand FlipCommand { get; set; }
-
         public DocumentViewModel(ViewModelMain owner)
             : base(owner)
         {
-            CenterContentCommand = new RelayCommand(CenterContent, Owner.DocumentIsNotNull);
-            ClipCanvasCommand = new RelayCommand(ClipCanvas, Owner.DocumentIsNotNull);
-            DeletePixelsCommand = new RelayCommand(DeletePixels, Owner.SelectionSubViewModel.SelectionIsNotEmpty);
-            OpenResizePopupCommand = new RelayCommand(OpenResizePopup, Owner.DocumentIsNotNull);
-            RotateToRightCommand = new RelayCommand(RotateDocument, Owner.DocumentIsNotNull);
-            FlipCommand = new RelayCommand(FlipDocument, Owner.DocumentIsNotNull);
         }
 
         public void FlipDocument(object parameter)
@@ -53,7 +36,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
 
-        public void ClipCanvas(object parameter)
+        [Command.Basic("PixiEditor.Document.ClipCanvas", "Clip Canvas", "Clip Canvas", CanExecute = "PixiEditor.HasDocument")]
+        public void ClipCanvas()
         {
             Owner.BitmapManager.ActiveDocument?.ClipCanvas();
         }
@@ -78,7 +62,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Owner.BitmapManager.CloseDocument(document);
         }
 
-        private void DeletePixels(object parameter)
+        [Command.Basic("PixiEditor.Document.DeletePixels", "Delete pixels", "Delete selected pixels", CanExecute = "PixiEditor.Selection.IsNotEmpty", Key = Key.Delete)]
+        public void DeletePixels()
         {
             var doc = Owner.BitmapManager.ActiveDocument;
             Owner.BitmapManager.BitmapOperations.DeletePixels(
@@ -86,16 +71,17 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 doc.ActiveSelection.SelectedPoints.ToArray());
         }
 
-        private void OpenResizePopup(object parameter)
+        [Command.Basic("PixiEditor.Document.ResizeDocument", false, "Resize Document", "Resize Document", CanExecute = "PixiEditor.HasDocument", Key = Key.I, Modifiers = ModifierKeys.Control | ModifierKeys.Shift)]
+        [Command.Basic("PixiEditor.Document.ResizeCanvas", true, "Resize Canvas", "Resize Canvas", CanExecute = "PixiEditor.HasDocument", Key = Key.C, Modifiers = ModifierKeys.Control | ModifierKeys.Shift)]
+        public void OpenResizePopup(bool canvas)
         {
-            bool isCanvasDialog = (string)parameter == "canvas";
             ResizeDocumentDialog dialog = new ResizeDocumentDialog(
                 Owner.BitmapManager.ActiveDocument.Width,
                 Owner.BitmapManager.ActiveDocument.Height,
-                isCanvasDialog);
+                canvas);
             if (dialog.ShowDialog())
             {
-                if (isCanvasDialog)
+                if (canvas)
                 {
                     Owner.BitmapManager.ActiveDocument.ResizeCanvas(dialog.Width, dialog.Height, dialog.ResizeAnchor);
                 }
@@ -106,7 +92,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
 
-        private void CenterContent(object property)
+        [Command.Basic("PixiEditor.Document.CenterContent", "Center Content", "Center Content", CanExecute = "PixiEditor.HasDocument")]
+        public void CenterContent()
         {
             Owner.BitmapManager.ActiveDocument.CenterContent();
         }

+ 16 - 67
PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs

@@ -2,22 +2,17 @@
 using Newtonsoft.Json.Linq;
 using PixiEditor.Exceptions;
 using PixiEditor.Helpers;
-using PixiEditor.Models;
+using PixiEditor.Models.Commands.Attributes;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.IO;
 using PixiEditor.Models.UserPreferences;
 using PixiEditor.Parser;
 using PixiEditor.Views.Dialogs;
-using System;
-using System.Collections.Generic;
-using System.Drawing.Imaging;
 using System.IO;
-using System.Linq;
 using System.Windows;
-using System.Windows.Media.Imaging;
-using PixiEditor.Models.Commands.Attributes;
 using System.Windows.Input;
+using System.Windows.Media.Imaging;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
@@ -25,18 +20,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
     {
         private bool hasRecent;
 
-        public RelayCommand OpenNewFilePopupCommand { get; set; }
-
-        public RelayCommand SaveDocumentCommand { get; set; }
-
-        public RelayCommand OpenFileCommand { get; set; }
-
-        public RelayCommand ExportFileCommand { get; set; } // Command that is used to save file
-
-        public RelayCommand OpenRecentCommand { get; set; }
-
-        public RelayCommand RemoveRecentlyOpenedCommand { get; set; }
-
         public bool HasRecent
         {
             get => hasRecent;
@@ -52,12 +35,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         public FileViewModel(ViewModelMain owner)
             : base(owner)
         {
-            OpenNewFilePopupCommand = new RelayCommand(OpenNewFilePopup);
-            //SaveDocumentCommand = new RelayCommand(SaveDocument, Owner.DocumentIsNotNull);
-            OpenFileCommand = new RelayCommand(Open);
-            ExportFileCommand = new RelayCommand(ExportFile, CanSave);
-            OpenRecentCommand = new RelayCommand(OpenRecent);
-            RemoveRecentlyOpenedCommand = new RelayCommand(RemoveRecentlyOpened);
             Owner.OnStartupEvent += Owner_OnStartupEvent;
             RecentlyOpened = new RecentlyOpenedCollection(GetRecentlyOpenedDocuments());
 
@@ -69,29 +46,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             IPreferences.Current.AddCallback("MaxOpenedRecently", UpdateMaxRecentlyOpened);
         }
 
-        public void OpenRecent(object parameter)
-        {
-            string path = (string)parameter;
-
-            foreach (Document document in Owner.BitmapManager.Documents)
-            {
-                if (document.DocumentFilePath == path)
-                {
-                    Owner.BitmapManager.ActiveDocument = document;
-                    return;
-                }
-            }
-
-            if (!File.Exists(path))
-            {
-                NoticeDialog.Show("The file does not exist", "Failed to open the file");
-                RecentlyOpened.Remove(path);
-                return;
-            }
-
-            Open((string)parameter);
-        }
-
         public void RemoveRecentlyOpened(object parameter)
         {
             if (RecentlyOpened.Contains((string)parameter))
@@ -104,7 +58,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         ///     Generates new Layer and sets it as active one.
         /// </summary>
         /// <param name="parameter">CommandParameter.</param>
-        public void OpenNewFilePopup(object parameter)
+        [Command.Basic("PixiEditor.File.New", "New image", "Create new image", Key = Key.N, Modifiers = ModifierKeys.Control)]
+        public void OpenNewFilePopup()
         {
             NewFileDialog newFile = new NewFileDialog();
             if (newFile.ShowDialog())
@@ -154,13 +109,15 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
 
-        public void OpenAny()
-        {
-            Open((object)null);
-        }
-
+        [Command.Basic("PixiEditor.File.Open", "Open", "Open image", Key = Key.O, Modifiers = ModifierKeys.Control)]
         public void Open(string path)
         {
+            if (path == null)
+            {
+                Open();
+                return;
+            }
+
             try
             {
                 if (path.EndsWith(".pixi"))
@@ -200,8 +157,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 }
             }
         }
-                
-        private void Open(object property)
+
+        public void Open()
         {
             var filter = SupportedFilesHelper.BuildOpenFilter();
 
@@ -241,6 +198,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         }
 
         [Command.Basic("PixiEditor.File.Save", false, "Save", "Save image", CanExecute = "PixiEditor.HasDocument", Key = Key.S, Modifiers = ModifierKeys.Control)]
+        [Command.Basic("PixiEditor.File.SaveAsNew", true, "Save as...", "Save image as new", CanExecute = "PixiEditor.HasDocument", Key = Key.S, Modifiers = ModifierKeys.Control | ModifierKeys.Shift)]
         public void SaveDocument(bool asNew)
         {
             if (asNew || string.IsNullOrEmpty(Owner.BitmapManager.ActiveDocument.DocumentFilePath)) 
@@ -257,23 +215,14 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         ///     Generates export dialog or saves directly if save data is known.
         /// </summary>
         /// <param name="parameter">CommandProperty.</param>
-        private void ExportFile(object parameter)
+        [Command.Basic("PixiEditor.File.Export", "Export", "Export image", CanExecute = "PixiEditor.HasDocument", Key = Key.S, Modifiers = ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Shift)]
+        public void ExportFile()
         {
             ViewModelMain.Current.ActionDisplay = "";
             WriteableBitmap bitmap = Owner.BitmapManager.ActiveDocument.Renderer.FinalBitmap;
             Exporter.Export(bitmap, new Size(bitmap.PixelWidth, bitmap.PixelHeight));
         }
 
-        /// <summary>
-        ///     Returns true if file save is possible.
-        /// </summary>
-        /// <param name="property">CommandProperty.</param>
-        /// <returns>True if active document is not null.</returns>
-        private bool CanSave(object property)
-        {
-            return Owner.BitmapManager.ActiveDocument != null;
-        }
-
         private void UpdateMaxRecentlyOpened(object parameter)
         {
             int newAmount = (int)parameter;

+ 2 - 0
PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs

@@ -1,4 +1,5 @@
 using PixiEditor.Helpers;
+using PixiEditor.Models.Commands.Attributes;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Layers;
 using PixiEditor.Views.UserControls.Layers;
@@ -201,6 +202,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             return Owner.BitmapManager.ActiveDocument?.Layers.Count(x => x.IsActive) > 0;
         }
 
+        [Command.Basic("PixiEditor.Layer.New", "New Layer", "Create new layer", CanExecute = "PixiEditor.HasDocument", Key = Key.N, Modifiers = ModifierKeys.Control | ModifierKeys.Shift)]
         public void NewLayer(object parameter)
         {
             GuidStructureItem control = GetGroupFromParameter(parameter);

+ 9 - 47
PixiEditor/ViewModels/SubViewModels/Main/MiscViewModel.cs

@@ -1,61 +1,23 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using PixiEditor.Helpers;
-using PixiEditor.Views.Dialogs;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Commands.Attributes;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
     public class MiscViewModel : SubViewModel<ViewModelMain>
     {
-        public RelayCommand OpenHyperlinkCommand { get; set; }
-
-        public RelayCommand OpenSettingsWindowCommand { get; set; }
-
-        public RelayCommand OpenShortcutWindowCommand { get; set; }
-
-        public RelayCommand OpenHelloThereWindowCommand { get; set; }
-
-        public ShortcutPopup ShortcutPopup { get; set; }
-
         public MiscViewModel(ViewModelMain owner)
             : base(owner)
         {
-            OpenHyperlinkCommand = new RelayCommand(OpenHyperlink);
-            OpenSettingsWindowCommand = new RelayCommand(OpenSettingsWindow);
-            OpenShortcutWindowCommand = new RelayCommand(OpenShortcutWindow);
-            OpenHelloThereWindowCommand = new RelayCommand(OpenHelloThereWindow);
-
-            ShortcutPopup = new ShortcutPopup(owner.ShortcutController);
-        }
-
-        private void OpenSettingsWindow(object parameter)
-        {
-            SettingsWindow settings = new SettingsWindow();
-            settings.Show();
-        }
-
-        private void OpenHyperlink(object parameter)
-        {
-            if (parameter is not string s)
-            {
-                return;
-            }
-
-            ProcessHelpers.ShellExecute(s);
-        }
-
-        private void OpenShortcutWindow(object parameter)
-        {
-            ShortcutPopup.Show();
         }
 
-        private void OpenHelloThereWindow(object parameter)
+        [Command.Basic("PixiEditor.Links.OpenDocumentation", "https://pixieditor.net/docs/introduction", "Documentation", "Open Documentation")]
+        [Command.Basic("PixiEditor.Links.OpenWebsite", "https://pixieditor.net", "Website", "Open Website")]
+        [Command.Basic("PixiEditor.Links.OpenRepository", "https://github.com/PixiEditor/PixiEditor", "Repository", "Open Repository")]
+        [Command.Basic("PixiEditor.Links.OpenLicense", "https://github.com/PixiEditor/PixiEditor/blob/master/LICENSE", "License", "Open License")]
+        [Command.Basic("PixiEditor.Links.OpenOtherLicenses", "https://pixieditor.net/docs/Third-party-licenses", "Third Party Licenses", "Open Third Party Licenses")]
+        public static void OpenHyperlink(string url)
         {
-            new HelloTherePopup(Owner.FileSubViewModel).Show();
+            ProcessHelpers.ShellExecute(url);
         }
     }
 }

+ 9 - 20
PixiEditor/ViewModels/SubViewModels/Main/SelectionViewModel.cs

@@ -1,32 +1,24 @@
-using System;
-using System.Collections.Generic;
-using PixiEditor.Helpers;
-using PixiEditor.Models.DataHolders;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Commands.Attributes;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools.Tools;
+using System.Windows.Input;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
     public class SelectionViewModel : SubViewModel<ViewModelMain>
     {
-        public RelayCommand DeselectCommand { get; set; }
-
-        public RelayCommand SelectAllCommand { get; set; }
-
         private readonly SelectTool selectTool;
 
         public SelectionViewModel(ViewModelMain owner)
             : base(owner)
         {
-            DeselectCommand = new RelayCommand(Deselect, SelectionIsNotEmpty);
-            SelectAllCommand = new RelayCommand(SelectAll, CanSelectAll);
-
             selectTool = new SelectTool(Owner.BitmapManager);
         }
 
-        public void SelectAll(object parameter)
+        [Command.Basic("PixiEditor.Selection.SelectAll", "Select all", "Select everything", CanExecute = "PixiEditor.HasDocument", Key = Key.A, Modifiers = ModifierKeys.Control)]
+        public void SelectAll()
         {
             var oldSelection = new List<Coordinates>(Owner.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints);
 
@@ -34,7 +26,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             SelectionHelpers.AddSelectionUndoStep(Owner.BitmapManager.ActiveDocument, oldSelection, SelectionType.New);
         }
 
-        public void Deselect(object parameter)
+        [Command.Basic("PixiEditor.Selection.Clear", "Clear selection", "Clear selection", CanExecute = "PixiEditor.Selection.IsNotEmpty", Key = Key.D, Modifiers = ModifierKeys.Control)]
+        public void Deselect()
         {
             var oldSelection = new List<Coordinates>(Owner.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints);
 
@@ -43,15 +36,11 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             SelectionHelpers.AddSelectionUndoStep(Owner.BitmapManager.ActiveDocument, oldSelection, SelectionType.New);
         }
 
-        public bool SelectionIsNotEmpty(object property)
+        [Evaluator.CanExecute("PixiEditor.Selection.IsNotEmpty")]
+        public bool SelectionIsNotEmpty()
         {
             var selectedPoints = Owner.BitmapManager.ActiveDocument?.ActiveSelection.SelectedPoints;
             return selectedPoints != null && selectedPoints.Count > 0;
         }
-
-        private bool CanSelectAll(object property)
-        {
-            return Owner.BitmapManager.ActiveDocument != null && Owner.BitmapManager.ActiveDocument.Layers.Count > 0;
-        }
     }
 }

+ 12 - 13
PixiEditor/ViewModels/SubViewModels/Main/UndoViewModel.cs

@@ -2,23 +2,18 @@
 using PixiEditor.Models.Undo;
 using System;
 using System.IO;
+using PixiEditor.Models.Commands.Attributes;
+using System.Windows.Input;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
     public class UndoViewModel : SubViewModel<ViewModelMain>
     {
-        public RelayCommand UndoCommand { get; set; }
-
-        public RelayCommand RedoCommand { get; set; }
-
         public event EventHandler UndoRedoCalled;
 
         public UndoViewModel(ViewModelMain owner)
             : base(owner)
         {
-            UndoCommand = new RelayCommand(Undo, CanUndo);
-            RedoCommand = new RelayCommand(Redo, CanRedo);
-
             var result = Directory.CreateDirectory(StorageBasedChange.DefaultUndoChangeLocation);
 
             //ClearUndoTempDirectory();
@@ -28,12 +23,13 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         ///     Redo last action.
         /// </summary>
         /// <param name="parameter">CommandProperty.</param>
-        public void Redo(object parameter)
+        [Command.Basic("PixiEditor.Undo.Redo", "Redo", "Redo next step", CanExecute = "PixiEditor.Undo.CanRedo", Key = Key.Y, Modifiers = ModifierKeys.Control)]
+        public void Redo()
         {
             UndoRedoCalled?.Invoke(this, EventArgs.Empty);
 
             //sometimes CanRedo gets changed after UndoRedoCalled invoke, so check again (normally this is checked by the relaycommand)
-            if (CanRedo(null))
+            if (CanRedo())
             {
                 Owner.BitmapManager.ActiveDocument.UndoManager.Redo();
                 Owner.BitmapManager.ActiveDocument.ChangesSaved = false;
@@ -44,12 +40,13 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         ///     Undo last action.
         /// </summary>
         /// <param name="parameter">CommandParameter.</param>
-        public void Undo(object parameter)
+        [Command.Basic("PixiEditor.Undo.Undo", "Undo", "Undo previous step", CanExecute = "PixiEditor.Undo.CanUndo", Key = Key.Z, Modifiers = ModifierKeys.Control)]
+        public void Undo()
         {
             UndoRedoCalled?.Invoke(this, EventArgs.Empty);
 
             //sometimes CanUndo gets changed after UndoRedoCalled invoke, so check again (normally this is checked by the relaycommand)
-            if (CanUndo(null))
+            if (CanUndo())
             {
                 Owner.BitmapManager.ActiveDocument.UndoManager.Undo();
                 Owner.BitmapManager.ActiveDocument.ChangesSaved = false;
@@ -73,7 +70,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         /// </summary>
         /// <param name="property">CommandParameter.</param>
         /// <returns>True if can undo.</returns>
-        private bool CanUndo(object property)
+        [Evaluator.CanExecute("PixiEditor.Undo.CanUndo")]
+        public bool CanUndo()
         {
             return Owner.BitmapManager.ActiveDocument?.UndoManager.CanUndo ?? false;
         }
@@ -83,7 +81,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         /// </summary>
         /// <param name="property">CommandProperty.</param>
         /// <returns>True if can redo.</returns>
-        private bool CanRedo(object property)
+        [Evaluator.CanExecute("PixiEditor.Undo.CanRedo")]
+        public bool CanRedo()
         {
             return Owner.BitmapManager.ActiveDocument?.UndoManager.CanRedo ?? false;
         }

+ 3 - 4
PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs

@@ -6,6 +6,7 @@ using System.Linq;
 using System.Threading.Tasks;
 using System.Windows;
 using PixiEditor.Helpers;
+using PixiEditor.Models.Commands.Attributes;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Processes;
 using PixiEditor.Models.UserPreferences;
@@ -21,8 +22,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public UpdateChannel[] UpdateChannels { get; } = new UpdateChannel[2];
 
-        public RelayCommand RestartApplicationCommand { get; set; }
-
         private string versionText;
 
         public string VersionText
@@ -53,7 +52,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             : base(owner)
         {
             Owner.OnStartupEvent += Owner_OnStartupEvent;
-            RestartApplicationCommand = new RelayCommand(RestartApplication);
             IPreferences.Current.AddCallback<string>("UpdateChannel", (val) => UpdateChecker.Channel = GetUpdateChannel(val));
             InitUpdateChecker();
         }
@@ -154,7 +152,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Application.Current.Shutdown();
         }
 
-        private static void RestartApplication(object parameter)
+        [Command.Basic("PixiEditor.Restart", "", "")]
+        public static void RestartApplication()
         {
             try
             {

+ 9 - 16
PixiEditor/ViewModels/SubViewModels/Main/ViewportViewModel.cs

@@ -1,40 +1,33 @@
-using PixiEditor.Helpers;
+using PixiEditor.Models.Commands.Attributes;
+using System.Windows.Input;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
     public class ViewportViewModel : SubViewModel<ViewModelMain>
     {
-        public RelayCommand ZoomCommand { get; set; }
-
-        public RelayCommand ToggleGridLinesCommand { get; set; }
-
-        private bool gridLinesEnabled = false;
+        private bool gridLinesEnabled;
 
         public bool GridLinesEnabled
         {
             get => gridLinesEnabled;
-            set
-            {
-                gridLinesEnabled = value;
-                RaisePropertyChanged(nameof(GridLinesEnabled));
-            }
+            set => SetProperty(ref gridLinesEnabled, value);
         }
 
         public ViewportViewModel(ViewModelMain owner)
             : base(owner)
         {
-            ZoomCommand = new RelayCommand(ZoomViewport);
-            ToggleGridLinesCommand = new RelayCommand(ToggleGridLines);
         }
 
-        private void ToggleGridLines(object parameter)
+        [Command.Basic("PixiEditor.View.ToggleGrid", "Toggle gridlines", "Toggle gridlines", Key = Key.OemTilde, Modifiers = ModifierKeys.Control)]
+        public void ToggleGridLines()
         {
             GridLinesEnabled = !GridLinesEnabled;
         }
 
-        private void ZoomViewport(object parameter)
+        [Command.Basic("PixiEditor.View.ZoomIn", 1, "Zoom in", "Zoom in", CanExecute = "PixiEditor.HasDocument", Key = Key.OemPlus)]
+        [Command.Basic("PixiEditor.View.Zoomout", -1, "Zoom out", "Zoom out", CanExecute = "PixiEditor.HasDocument", Key = Key.OemMinus)]
+        public void ZoomViewport(double zoom)
         {
-            double zoom = (int)parameter;
             if (Owner.BitmapManager.ActiveDocument is not null)
             {
                 Owner.BitmapManager.ActiveDocument.ZoomViewportTrigger.Execute(this, zoom);

+ 15 - 6
PixiEditor/ViewModels/SubViewModels/Main/WindowViewModel.cs

@@ -1,11 +1,11 @@
 using AvalonDock.Layout;
 using GalaSoft.MvvmLight.CommandWpf;
-using System.Collections.Generic;
-using System.Linq;
+using PixiEditor.Models.Commands.Attributes;
+using PixiEditor.Views.Dialogs;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
-    public class WindowViewModel : SubViewModel<ViewModelMain>, ISettableOwner<ViewModelMain>
+    public class WindowViewModel : SubViewModel<ViewModelMain>
     {
         public RelayCommand<string> ShowAvalonDockWindowCommand { get; set; }
 
@@ -20,12 +20,21 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             ShowAvalonDockWindowCommand = new(ShowAvalonDockWindow);
         }
 
-        public void SetOwner(ViewModelMain owner)
+        [Command.Basic("PixiEditor.Settings.Open", "Open Settings", "Open Settings Window")]
+        public static void OpenSettingsWindow()
         {
-            Owner = owner;
+            SettingsWindow settings = new SettingsWindow();
+            settings.Show();
         }
 
-        private void ShowAvalonDockWindow(string id)
+        [Command.Basic("PixiEditor.Window.OpenStartupWindow", "Open Settings", "Open Settings Window")]
+        public void OpenHelloThereWindow()
+        {
+            new HelloTherePopup(Owner.FileSubViewModel).Show();
+        }
+
+        [Command.Basic("PixiEditor.Window.OpenNavigationWindow", "navigation", "Open Navigation Window", "Open Navigation Window")]
+        public static void ShowAvalonDockWindow(string id)
         {
             if (MainWindow.Current?.LayoutRoot?.Manager?.Layout == null) return;
             var anchorables = new List<LayoutAnchorable>(MainWindow.Current.LayoutRoot.Manager.Layout

+ 2 - 78
PixiEditor/ViewModels/ViewModelMain.cs

@@ -111,16 +111,6 @@ namespace PixiEditor.ViewModels
             }
         }
 
-        public bool IsDebug
-        {
-            get =>
-#if DEBUG
-                true;
-#else
-                false;
-#endif
-        }
-
         public ViewModelMain(IServiceProvider serviceProvider)
         {
             Current = this;
@@ -156,70 +146,15 @@ namespace PixiEditor.ViewModels
             DocumentSubViewModel = services.GetService<DocumentViewModel>();
             DiscordViewModel = services.GetService<DiscordViewModel>();
             UpdateSubViewModel = services.GetService<UpdateViewModel>();
+            DebugSubViewModel = services.GetService<DebugViewModel>();
 
             WindowSubViewModel = services.GetService<WindowViewModel>();
             StylusSubViewModel = services.GetService<StylusViewModel>();
 
-            AddDebugOnlyViewModels();
-            AddReleaseOnlyViewModels();
-
-            ShortcutController = new ShortcutController(
-                    new ShortcutGroup(
-                        "Tools",
-                        CreateToolShortcut<PenTool>(Key.B, "Pen"),
-                        CreateToolShortcut<EraserTool>(Key.E, "Eraser"),
-                        CreateToolShortcut<ColorPickerTool>(Key.O, "Color picker"),
-                        CreateToolShortcut<RectangleTool>(Key.R, "Rectangle"),
-                        CreateToolShortcut<CircleTool>(Key.C, "Ellipse"),
-                        CreateToolShortcut<LineTool>(Key.L, "Line"),
-                        CreateToolShortcut<FloodFillTool>(Key.G, "Flood fill"),
-                        CreateToolShortcut<BrightnessTool>(Key.U, "Brightness"),
-                        CreateToolShortcut<MoveTool>(Key.V, "Move selection"),
-                        CreateToolShortcut<SelectTool>(Key.M, "Select"),
-                        CreateToolShortcut<ZoomTool>(Key.Z, "Zoom"),
-                        CreateToolShortcut<MoveViewportTool>(Key.H, "Move viewport"),
-                        CreateToolShortcut<MagicWandTool>(Key.W, "Magic wand"),
-                        new Shortcut(Key.OemPlus, ViewportSubViewModel.ZoomCommand, "Zoom in", 1),
-                        new Shortcut(Key.OemMinus, ViewportSubViewModel.ZoomCommand, "Zoom out", -1),
-                        new Shortcut(Key.OemOpenBrackets, ToolsSubViewModel.ChangeToolSizeCommand, "Decrease tool size", -1),
-                        new Shortcut(Key.OemCloseBrackets, ToolsSubViewModel.ChangeToolSizeCommand, "Increase tool size", 1)),
-                    new ShortcutGroup(
-                        "Editor",
-                        new Shortcut(Key.X, ColorsSubViewModel.SwapColorsCommand, "Swap primary and secondary colors"),
-                        new Shortcut(Key.Y, UndoSubViewModel.RedoCommand, "Redo", modifier: ModifierKeys.Control),
-                        new Shortcut(Key.Z, UndoSubViewModel.UndoCommand, "Undo", modifier: ModifierKeys.Control),
-                        new Shortcut(Key.D, SelectionSubViewModel.DeselectCommand, "Clear selection", modifier: ModifierKeys.Control),
-                        new Shortcut(Key.A, SelectionSubViewModel.SelectAllCommand, "Select all", modifier: ModifierKeys.Control),
-                        new Shortcut(Key.C, ClipboardSubViewModel.CopyCommand, "Copy", modifier: ModifierKeys.Control),
-                        new Shortcut(Key.V, ClipboardSubViewModel.PasteCommand, "Paste", modifier: ModifierKeys.Control),
-                        new Shortcut(Key.J, ClipboardSubViewModel.DuplicateCommand, "Duplicate", modifier: ModifierKeys.Control),
-                        new Shortcut(Key.X, ClipboardSubViewModel.CutCommand, "Cut", modifier: ModifierKeys.Control),
-                        new Shortcut(Key.Delete, DocumentSubViewModel.DeletePixelsCommand, "Clear selected area"),
-                        new Shortcut(Key.I, DocumentSubViewModel.OpenResizePopupCommand, "Resize image", modifier: ModifierKeys.Control | ModifierKeys.Shift),
-                        new Shortcut(Key.C, DocumentSubViewModel.OpenResizePopupCommand, "Resize canvas", "canvas", ModifierKeys.Control | ModifierKeys.Shift),
-                        new Shortcut(Key.F11, SystemCommands.MaximizeWindowCommand, "Maximize window")),
-                    new ShortcutGroup(
-                        "File",
-                        new Shortcut(Key.O, FileSubViewModel.OpenFileCommand, "Open image", modifier: ModifierKeys.Control),
-                        new Shortcut(Key.S, FileSubViewModel.ExportFileCommand, "Export image", modifier: ModifierKeys.Control | ModifierKeys.Shift | ModifierKeys.Alt),
-                        new Shortcut(Key.S, FileSubViewModel.SaveDocumentCommand, "Save", modifier: ModifierKeys.Control),
-                        new Shortcut(Key.S, FileSubViewModel.SaveDocumentCommand, "Save as new", "AsNew", ModifierKeys.Control | ModifierKeys.Shift),
-                        new Shortcut(Key.N, FileSubViewModel.OpenNewFilePopupCommand, "Create new image", modifier: ModifierKeys.Control)),
-                    new ShortcutGroup(
-                        "Layers",
-                        new Shortcut(Key.F2, LayersSubViewModel.RenameLayerCommand, "Rename active layer", BitmapManager.ActiveDocument?.ActiveLayerGuid)),
-                    new ShortcutGroup(
-                        "View",
-                        new Shortcut(Key.OemTilde, ViewportSubViewModel.ToggleGridLinesCommand, "Toggle gridlines", modifier: ModifierKeys.Control)));
+            ShortcutController = new ShortcutController();
 
             MiscSubViewModel = services.GetService<MiscViewModel>();
 
-            // Add F1 shortcut after MiscSubViewModel is constructed
-            ShortcutController.ShortcutGroups.Add(
-                    new ShortcutGroup(
-                        "Misc",
-                        new Shortcut(Key.F1, MiscSubViewModel.OpenShortcutWindowCommand, "Open shortcuts window", true)));
-
             ShortcutController.TransientShortcuts[Key.Space] = ToolsSubViewModel.ToolSet.First(x => x is MoveViewportTool);
             ShortcutController.TransientShortcuts[Key.LeftAlt] = ToolsSubViewModel.ToolSet.First(x => x is ColorPickerTool);
 
@@ -278,17 +213,6 @@ namespace PixiEditor.ViewModels
             if (!OverrideActionDisplay) RaisePropertyChanged(nameof(ActionDisplay));
         }
 
-        [Conditional("DEBUG")]
-        private void AddDebugOnlyViewModels()
-        {
-            DebugSubViewModel = new DebugViewModel(this);
-        }
-
-        [Conditional("RELEASE")]
-        private void AddReleaseOnlyViewModels()
-        {
-        }
-
         private Shortcut CreateToolShortcut<T>(Key key, ModifierKeys modifier = ModifierKeys.None)
             where T : Tool
         {

+ 4 - 4
PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs

@@ -34,7 +34,7 @@ namespace PixiEditor.Views.Dialogs
 
         public RelayCommand OpenRecentCommand { get; set; }
 
-        public RelayCommand OpenHyperlinkCommand { get => FileViewModel.Owner.MiscSubViewModel.OpenHyperlinkCommand; }
+        public RelayCommand OpenHyperlinkCommand { get => null; }
 
         public RelayCommand OpenInExplorerCommand { get; set; }
 
@@ -94,21 +94,21 @@ namespace PixiEditor.Views.Dialogs
         {
             Application.Current.MainWindow.Activate();
             Close();
-            FileViewModel.OpenAny();
+            FileViewModel.Open();
         }
 
         private void OpenNewFile(object parameter)
         {
             Application.Current.MainWindow.Activate();
             Close();
-            FileViewModel.OpenNewFilePopup(parameter);
+            FileViewModel.OpenNewFilePopup();
         }
 
         private void OpenRecent(object parameter)
         {
             Application.Current.MainWindow.Activate();
             Close();
-            FileViewModel.OpenRecent(parameter);
+            FileViewModel.Open(parameter as string);
         }
 
         private void OpenInExplorer(object parameter)

+ 53 - 62
PixiEditor/Views/MainWindow.xaml

@@ -78,18 +78,18 @@
         <DockPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Background="{StaticResource MainColor}">
             <Image DockPanel.Dock="Left" HorizontalAlignment="Left" VerticalAlignment="Top"
                    Source="/Images/PixiEditorLogo.png" Width="20" Height="20" Margin="5,5,0,0" />
-            <Menu WindowChrome.IsHitTestVisibleInChrome="True" Margin="10, 4, 0, 0" DockPanel.Dock="Left"
+            <cmds:Menu WindowChrome.IsHitTestVisibleInChrome="True" Margin="10, 4, 0, 0" DockPanel.Dock="Left"
                   HorizontalAlignment="Left" VerticalAlignment="Top" Background="Transparent" IsMainMenu="True">
                 <Menu.Resources>
                     <Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource menuItemStyle}" />
                 </Menu.Resources>
                 <MenuItem Header="_File">
-                    <MenuItem InputGestureText="CTRL+N" Header="_New" Command="{Binding FileSubViewModel.OpenNewFilePopupCommand}" />
-                    <MenuItem Header="_Open" InputGestureText="Ctrl+O" Command="{Binding FileSubViewModel.OpenFileCommand}" />
+                    <MenuItem Header="_New" cmds:Menu.Command="PixiEditor.File.New" />
+                    <MenuItem Header="_Open" cmds:Menu.Command="PixiEditor.File.Open" />
                     <MenuItem Header="_Recent" ItemsSource="{Binding FileSubViewModel.RecentlyOpened}" x:Name="recentItemMenu" IsEnabled="{Binding FileSubViewModel.HasRecent}">
                         <MenuItem.ItemContainerStyle>
-                            <Style TargetType="MenuItem">
-                                <Setter Property="Command" Value="{Binding ElementName=recentItemMenu, Path=DataContext.FileSubViewModel.OpenRecentCommand}"/>
+                            <Style TargetType="MenuItem" BasedOn="{StaticResource PixiEditorDockThemeMenuItemStyle}">
+                                <Setter Property="Command" Value="{cmds:Command PixiEditor.File.Open, UseProvided=True}"/>
                                 <Setter Property="CommandParameter" Value="{Binding FilePath}"/>
                             </Style>
                         </MenuItem.ItemContainerStyle>
@@ -99,39 +99,35 @@
                             </DataTemplate>
                         </MenuItem.ItemTemplate>
                     </MenuItem>
-                    <MenuItem Header="_Save" InputGestureText="{cmds:ShortcutBinding PixiEditor.File.Save}" Command="{cmds:Command PixiEditor.File.Save}" />
-                    <MenuItem Header="_Save As..." InputGestureText="Ctrl+Shift+S"
-                              Command="{Binding FileSubViewModel.SaveDocumentCommand}" CommandParameter="AsNew" />
-                    <MenuItem Header="_Export" InputGestureText="Ctrl+Shift+Alt+S" Command="{Binding FileSubViewModel.ExportFileCommand}" />
+                    <MenuItem Header="_Save" cmds:Menu.Command="PixiEditor.File.Save" />
+                    <MenuItem Header="_Save As..." cmds:Menu.Command="PixiEditor.File.SaveAsNew" />
+                    <MenuItem Header="_Export" cmds:Menu.Command="PixiEditor.File.Export" />
                     <Separator />
                     <MenuItem Header="_Exit" Command="{x:Static SystemCommands.CloseWindowCommand}" />
                 </MenuItem>
                 <MenuItem Header="_Edit">
-                    <MenuItem Header="_Undo" InputGestureText="Ctrl+Z" Command="{Binding UndoSubViewModel.UndoCommand}" />
-                    <MenuItem Header="_Redo" InputGestureText="Ctrl+Y" Command="{Binding UndoSubViewModel.RedoCommand}" />
+                    <MenuItem Header="_Undo" cmds:Menu.Command="PixiEditor.Undo.Undo" />
+                    <MenuItem Header="_Redo" cmds:Menu.Command="PixiEditor.Undo.Redo" />
                     <Separator />
-                    <MenuItem Header="_Cut" Command="{Binding ClipboardSubViewModel.CutCommand}" InputGestureText="Ctrl+X" />
-                    <MenuItem Header="_Copy" Command="{Binding ClipboardSubViewModel.CopyCommand}" InputGestureText="Ctrl+C" />
-                    <MenuItem Header="_Paste" Command="{Binding ClipboardSubViewModel.PasteCommand}" InputGestureText="Ctrl+V" />
-                    <MenuItem Header="_Duplicate" Command="{Binding ClipboardSubViewModel.DuplicateCommand}" InputGestureText="Ctrl+J" />
+                    <MenuItem Header="_Cut" cmds:Menu.Command="PixiEditor.Clipboard.Cut" />
+                    <MenuItem Header="_Copy" cmds:Menu.Command="PixiEditor.Clipboard.Copy" />
+                    <MenuItem Header="_Paste" cmds:Menu.Command="PixiEditor.Clipboard.Paste" />
+                    <MenuItem Header="_Duplicate" cmds:Menu.Command="PixiEditor.Clipboard.Duplicate" />
                     <Separator />
-                    <MenuItem Header="_Delete Selected" Command="{Binding DocumentSubViewModel.DeletePixelsCommand}"
-                              InputGestureText="Delete" />
+                    <MenuItem Header="_Delete Selected" cmds:Menu.Command="PixiEditor.Document.DeletePixels" />
                     <Separator />
-                    <MenuItem Header="_Settings" Command="{Binding MiscSubViewModel.OpenSettingsWindowCommand}" />
+                    <MenuItem Header="_Settings" cmds:Menu.Command="PixiEditor.Settings.Open" />
                 </MenuItem>
                 <MenuItem Header="_Select">
-                    <MenuItem Header="_Select All" Command="{Binding SelectionSubViewModel.SelectAllCommand}" InputGestureText="Ctrl+A" />
-                    <MenuItem Header="_Deselect" Command="{Binding SelectionSubViewModel.DeselectCommand}" InputGestureText="Ctrl+D" />
+                    <MenuItem Header="_Select All" cmds:Menu.Command="PixiEditor.Selection.SelectAll" />
+                    <MenuItem Header="_Deselect" cmds:Menu.Command="PixiEditor.Selection.Clear" />
                 </MenuItem>
                 <MenuItem Header="_Image">
-                    <MenuItem Header="Resize _Image..." Command="{Binding DocumentSubViewModel.OpenResizePopupCommand}"
-                              InputGestureText="Ctrl+Shift+I" />
-                    <MenuItem Header="_Resize Canvas..." Command="{Binding DocumentSubViewModel.OpenResizePopupCommand}"
-                              CommandParameter="canvas" InputGestureText="Ctrl+Shift+C" />
-                    <MenuItem Header="_Clip Canvas" Command="{Binding DocumentSubViewModel.ClipCanvasCommand}" />
+                    <MenuItem Header="Resize _Image..." cmds:Menu.Command="PixiEditor.Document.ResizeDocument" />
+                    <MenuItem Header="_Resize Canvas..." cmds:Menu.Command="PixiEditor.Document.ResizeCanvas" />
+                    <MenuItem Header="_Clip Canvas" cmds:Menu.Command="PixiEditor.Document.ClipCanvas" />
                     <Separator/>
-                    <MenuItem Header="_Center Content" Command="{Binding DocumentSubViewModel.CenterContentCommand}" />
+                    <MenuItem Header="_Center Content" cmds:Menu.Command="PixiEditor.Document.CenterContent" />
                     <!--<Separator/>
                     <MenuItem Header="_Rotate to right 90&#186;" Command="{Binding DocumentSubViewModel.RotateToRightCommand}">
                         <MenuItem.CommandParameter>
@@ -150,35 +146,31 @@
                 </MenuItem>
                 <MenuItem Header="_View">
                     <MenuItem Header="_Show Grid Lines" IsChecked="{Binding ViewportSubViewModel.GridLinesEnabled, Mode=TwoWay}"
-                              IsCheckable="True" InputGestureText="Ctrl+`"/>
-                    <MenuItem Header="Open _Startup Window" ToolTip="Hello there!"
-                              Command="{Binding MiscSubViewModel.OpenHelloThereWindowCommand}"/>
-                    <MenuItem Header="Open _Navigation Window"
-                              Command="{Binding WindowSubViewModel.ShowAvalonDockWindowCommand}" CommandParameter="navigation"/>
+                              IsCheckable="True" InputGestureText="{cmds:ShortcutBinding PixiEditor.View.ToggleGrid}"/>
+                    <MenuItem Header="Open _Startup Window" ToolTip="Hello there!" cmds:Menu.Command="PixiEditor.Window.OpenStartupWindow"/>
+                    <MenuItem Header="Open _Navigation Window" cmds:Menu.Command="PixiEditor.Window.OpenNavigationWindow"/>
                 </MenuItem>
                 <MenuItem Header="_Help">
-                    <MenuItem Header="_Documentation" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
-                              CommandParameter="https://pixieditor.net/docs/introduction"/>
-                    <MenuItem Header="_Website" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
-                              CommandParameter="https://pixieditor.net"/>
-                    <MenuItem Header="_Repository" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
-                              CommandParameter="https://github.com/PixiEditor/PixiEditor"/>
-                    <MenuItem Header="_Shortcuts" Command="{Binding MiscSubViewModel.OpenShortcutWindowCommand}"/>
+                    <MenuItem Header="_Documentation" cmds:Menu.Command="PixiEditor.Links.OpenDocumentation" />
+                    <MenuItem Header="_Website" cmds:Menu.Command="PixiEditor.Links.OpenWebsite" />
+                    <MenuItem Header="_Repository" cmds:Menu.Command="PixiEditor.Links.OpenRepository" />
                     <Separator/>
-                    <MenuItem Header="_License" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
-                              CommandParameter="https://github.com/PixiEditor/PixiEditor/blob/master/LICENSE"/>
-                    <MenuItem Header="_Third Party Licenses" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
-                              CommandParameter="https://pixieditor.net/docs/Third-party-licenses"/>
+                    <MenuItem Header="_License" cmds:Menu.Command="PixiEditor.Links.OpenLicense" />
+                    <MenuItem Header="_Third Party Licenses" cmds:Menu.Command="PixiEditor.Links.OpenOtherLicenses" />
                 </MenuItem>
-                <MenuItem Header="_Debug" Visibility="{Binding IsDebug, Converter={StaticResource BoolToVisibilityConverter}}">
-                    <MenuItem Header="Open _Local App Data" Command="{Binding DebugSubViewModel.OpenFolderCommand}"
-                              CommandParameter="%LocalAppData%/PixiEditor"/>
-                    <MenuItem Header="Open _Roaming App Data" Command="{Binding DebugSubViewModel.OpenFolderCommand}"
-                              CommandParameter="%AppData%/PixiEditor"/>
-                    <MenuItem Header="Open _Install Location"  Command="{Binding DebugSubViewModel.OpenInstallLocationCommand}"/>
-                    <MenuItem Header="_Crash"  Command="{Binding DebugSubViewModel.CrashCommand}"/>
+                <MenuItem Header="_Debug" Visibility="{Binding DebugSubViewModel.UseDebug, Converter={StaticResource BoolToVisibilityConverter}}">
+                    <MenuItem Header="Open _Local App Data" cmds:Menu.Command="PixiEditor.Debug.OpenLocalAppDataDirectory" />
+                    <MenuItem Header="Open _Roaming App Data" cmds:Menu.Command="PixiEditor.Debug.OpenRoamingAppDataDirectory" />
+                    <MenuItem Header="Open _Temp App Data" cmds:Menu.Command="PixiEditor.Debug.OpenTempDirectory" />
+                    <MenuItem Header="Open _Install Location" cmds:Menu.Command="PixiEditor.Debug.OpenInstallDirectory" />
+                    <Separator/>
+                    <MenuItem Header="_Crash" cmds:Menu.Command="PixiEditor.Debug.Crash" />
+                    <MenuItem Header="Delete">
+                        <MenuItem Header="User Preferences (Roaming)" cmds:Menu.Command="PixiEditor.Debug.DeleteUserPreferences" />
+                        <MenuItem Header="Editor Data (Local)" cmds:Menu.Command="PixiEditor.Debug.DeleteEditorData" />
+                    </MenuItem>
                 </MenuItem>
-            </Menu>
+            </cmds:Menu>
             <StackPanel DockPanel.Dock="Right" VerticalAlignment="Top" Orientation="Horizontal" Margin="0,-5,-5,0"
                         HorizontalAlignment="Right" WindowChrome.IsHitTestVisibleInChrome="True">
                 <Button Style="{StaticResource MinimizeButtonStyle}" WindowChrome.IsHitTestVisibleInChrome="True"
@@ -197,10 +189,9 @@
         </DockPanel>
         <StackPanel Background="{StaticResource MainColor}" Orientation="Horizontal" Grid.ColumnSpan="3" Grid.Column="0"
                      Grid.Row="1">
-            <Button Margin="1,0,0,0" Command="{Binding UndoSubViewModel.UndoCommand}"
-                    ToolTip="Undo"
+            <Button Margin="1,0,0,0" Command="{cmds:Command PixiEditor.Undo.Undo}" ToolTip="Undo"
                     Style="{StaticResource ToolSettingsGlyphButton}" Content="&#xE7A7;"/>
-            <Button Command="{Binding UndoSubViewModel.RedoCommand}" ToolTip="Redo"
+            <Button Command="{cmds:Command PixiEditor.Undo.Redo}" ToolTip="Redo"
                     Style="{StaticResource ToolSettingsGlyphButton}" Content="&#xE7A6;"/>
             <ToggleButton Width="30" BorderThickness="0"
                           ToolTip="Pen Mode" Focusable="False"
@@ -302,14 +293,14 @@
                                             </i:EventTrigger>
                                         </i:Interaction.Triggers>
                                         <usercontrols:DrawingViewPort.ContextMenu>
-                                            <ContextMenu>
-                                                <MenuItem Header="_Select All" Command="{Binding XamlAccesibleViewModel.SelectionSubViewModel.SelectAllCommand}" InputGestureText="Ctrl+A" />
-                                                <MenuItem Header="_Deselect" Command="{Binding XamlAccesibleViewModel.SelectionSubViewModel.DeselectCommand}" InputGestureText="Ctrl+D" />
+                                            <cmds:ContextMenu>
+                                                <MenuItem Header="_Select All" cmds:ContextMenu.Command="PixiEditor.Selection.SelectAll" />
+                                                <MenuItem Header="_Deselect" cmds:ContextMenu.Command="PixiEditor.Selection.Clear" />
                                                 <Separator/>
-                                                <MenuItem Header="_Cut" Command="{Binding XamlAccesibleViewModel.ClipboardSubViewModel.CutCommand}" InputGestureText="Ctrl+X" />
-                                                <MenuItem Header="_Copy" Command="{Binding XamlAccesibleViewModel.ClipboardSubViewModel.CopyCommand}" InputGestureText="Ctrl+C" />
-                                                <MenuItem Header="_Paste" Command="{Binding XamlAccesibleViewModel.ClipboardSubViewModel.PasteCommand}" InputGestureText="Ctrl+V" />
-                                            </ContextMenu>
+                                                <MenuItem Header="_Cut" cmds:ContextMenu.Command="PixiEditor.Clipboard.Cut" />
+                                                <MenuItem Header="_Copy" cmds:ContextMenu.Command="PixiEditor.Clipboard.Copy" />
+                                                <MenuItem Header="_Paste" cmds:ContextMenu.Command="PixiEditor.Clipboard.Paste" />
+                                            </cmds:ContextMenu>
                                         </usercontrols:DrawingViewPort.ContextMenu>
                                     </usercontrols:DrawingViewPort>
                                 </DataTemplate>
@@ -347,7 +338,7 @@
                                                                  CanClose="False" CanAutoHide="False"
                                                                  CanDockAsTabbedDocument="False" CanFloat="True">
                                         <usercontrols:SwatchesView
-                                            SelectSwatchCommand="{Binding ColorsSubViewModel.SelectColorCommand}" RemoveSwatchCommand="{Binding ColorsSubViewModel.RemoveSwatchCommand}"
+                                            SelectSwatchCommand="{cmds:Command PixiEditor.Colors.SelectColor, UseProvided=True}" RemoveSwatchCommand="{cmds:Command PixiEditor.Colors.RemoveSwatch, UseProvided=True}"
                                             Swatches="{Binding BitmapManager.ActiveDocument.Swatches}"/>
                                     </avalondock:LayoutAnchorable>
                                 </LayoutAnchorablePane>
@@ -434,7 +425,7 @@
                        Grid.Column="3" Orientation="Horizontal">
                 <Button Style="{StaticResource BaseDarkButton}" 
                     Visibility="{Binding UpdateSubViewModel.UpdateReadyToInstall, Converter={StaticResource BoolToVisibilityConverter}, FallbackValue=Hidden}" FontSize="14" Height="20" 
-                    Command="{Binding UpdateSubViewModel.RestartApplicationCommand}">Restart</Button>
+                    Command="{cmds:Command PixiEditor.Restart}">Restart</Button>
                 <TextBlock VerticalAlignment="Center" Padding="10" HorizontalAlignment="Right"
                        Foreground="White" FontSize="14"  Text="{Binding UpdateSubViewModel.VersionText}" />
             </StackPanel>