Browse Source

Add tracking to most commands

CPKreuz 1 year ago
parent
commit
f822fc8623

+ 11 - 1
src/PixiEditor/Models/Commands/CommandController.cs

@@ -292,7 +292,7 @@ internal class CommandController
 
                         var command =
                             new Command.BasicCommand(
-                                _ => searchHandler.OpenSearchWindow($":{searchTerm}:"),
+                                ExecuteFilter,
                                 CanExecuteEvaluator.AlwaysTrue)
                             {
                                 InternalName = menu.InternalName,
@@ -306,6 +306,16 @@ internal class CommandController
                         Commands.Add(command);
 
                         AddCommandToCommandsCollection(command, commandGroupsData, commands);
+
+                        void ExecuteFilter(object o)
+                        {
+                            if (attribute.AnalyticsTrack && o is CommandExecutionContext c)
+                            {
+                                Analytics.SendCommand(menu.InternalName, c.SourceInfo);
+                            }
+                            
+                            searchHandler.OpenSearchWindow($":{searchTerm}:");
+                        }
                     }
                 }
             }

+ 18 - 18
src/PixiEditor/ViewModels/Document/DocumentManagerViewModel.cs

@@ -63,19 +63,19 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
     public bool DocumentNotNull() => ActiveDocument != null;
 
     [Command.Basic("PixiEditor.Document.ClipCanvas", "CLIP_CANVAS", "CLIP_CANVAS", CanExecute = "PixiEditor.HasDocument",
-        Icon = PixiPerfectIcons.Crop, MenuItemPath = "IMAGE/CLIP_CANVAS", MenuItemOrder = 2)]
+        Icon = PixiPerfectIcons.Crop, MenuItemPath = "IMAGE/CLIP_CANVAS", MenuItemOrder = 2, AnalyticsTrack = true)]
     public void ClipCanvas() => ActiveDocument?.Operations.ClipCanvas();
 
     [Command.Basic("PixiEditor.Document.FlipImageHorizontal", FlipType.Horizontal, "FLIP_IMG_HORIZONTALLY", "FLIP_IMG_HORIZONTALLY", CanExecute = "PixiEditor.HasDocument",
-        MenuItemPath = "IMAGE/FLIP/FLIP_IMG_HORIZONTALLY", MenuItemOrder = 14, Icon = PixiPerfectIcons.XFlip)]
+        MenuItemPath = "IMAGE/FLIP/FLIP_IMG_HORIZONTALLY", MenuItemOrder = 14, Icon = PixiPerfectIcons.XFlip, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Document.FlipImageVertical", FlipType.Vertical, "FLIP_IMG_VERTICALLY", "FLIP_IMG_VERTICALLY", CanExecute = "PixiEditor.HasDocument",
-        MenuItemPath = "IMAGE/FLIP/FLIP_IMG_VERTICALLY", MenuItemOrder = 15, Icon = PixiPerfectIcons.YFlip)]
+        MenuItemPath = "IMAGE/FLIP/FLIP_IMG_VERTICALLY", MenuItemOrder = 15, Icon = PixiPerfectIcons.YFlip, AnalyticsTrack = true)]
     public void FlipImage(FlipType type) => ActiveDocument?.Operations.FlipImage(type, activeDocument.AnimationDataViewModel.ActiveFrameBindable);
 
     [Command.Basic("PixiEditor.Document.FlipLayersHorizontal", FlipType.Horizontal, "FLIP_LAYERS_HORIZONTALLY", "FLIP_LAYERS_HORIZONTALLY", CanExecute = "PixiEditor.HasDocument",
-        MenuItemPath = "IMAGE/FLIP/FLIP_LAYERS_HORIZONTALLY", MenuItemOrder = 16, Icon = PixiPerfectIcons.XSelectedFlip)]
+        MenuItemPath = "IMAGE/FLIP/FLIP_LAYERS_HORIZONTALLY", MenuItemOrder = 16, Icon = PixiPerfectIcons.XSelectedFlip, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Document.FlipLayersVertical", FlipType.Vertical, "FLIP_LAYERS_VERTICALLY", "FLIP_LAYERS_VERTICALLY", CanExecute = "PixiEditor.HasDocument",
-        MenuItemPath = "IMAGE/FLIP/FLIP_LAYERS_VERTICALLY", MenuItemOrder = 17, Icon = PixiPerfectIcons.YSelectedFlip)]
+        MenuItemPath = "IMAGE/FLIP/FLIP_LAYERS_VERTICALLY", MenuItemOrder = 17, Icon = PixiPerfectIcons.YSelectedFlip, AnalyticsTrack = true)]
     public void FlipLayers(FlipType type)
     {
         if (ActiveDocument?.SelectedStructureMember == null)
@@ -86,24 +86,24 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
 
     [Command.Basic("PixiEditor.Document.Rotate90Deg", "ROT_IMG_90",
         "ROT_IMG_90", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D90,
-        MenuItemPath = "IMAGE/ROTATION/ROT_IMG_90_D", MenuItemOrder = 8, Icon = PixiPerfectIcons.RotateImage90)]
+        MenuItemPath = "IMAGE/ROTATION/ROT_IMG_90_D", MenuItemOrder = 8, Icon = PixiPerfectIcons.RotateImage90, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Document.Rotate180Deg", "ROT_IMG_180",
         "ROT_IMG_180", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D180,
-        MenuItemPath = "IMAGE/ROTATION/ROT_IMG_180_D", MenuItemOrder = 9, Icon = PixiPerfectIcons.RotateImage180)]
+        MenuItemPath = "IMAGE/ROTATION/ROT_IMG_180_D", MenuItemOrder = 9, Icon = PixiPerfectIcons.RotateImage180, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Document.Rotate270Deg", "ROT_IMG_-90",
         "ROT_IMG_-90", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D270,
-        MenuItemPath = "IMAGE/ROTATION/ROT_IMG_-90_D", MenuItemOrder = 10, Icon = PixiPerfectIcons.RotateImageMinus90)]
+        MenuItemPath = "IMAGE/ROTATION/ROT_IMG_-90_D", MenuItemOrder = 10, Icon = PixiPerfectIcons.RotateImageMinus90, AnalyticsTrack = true)]
     public void RotateImage(RotationAngle angle) => ActiveDocument?.Operations.RotateImage(angle);
 
     [Command.Basic("PixiEditor.Document.Rotate90DegLayers", "ROT_LAYERS_90",
         "ROT_LAYERS_90", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D90,
-        MenuItemPath = "IMAGE/ROTATION/ROT_LAYERS_90_D", MenuItemOrder = 11, Icon = PixiPerfectIcons.RotateFile90)]
+        MenuItemPath = "IMAGE/ROTATION/ROT_LAYERS_90_D", MenuItemOrder = 11, Icon = PixiPerfectIcons.RotateFile90, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Document.Rotate180DegLayers", "ROT_LAYERS_180",
         "ROT_LAYERS_180", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D180,
-        MenuItemPath = "IMAGE/ROTATION/ROT_LAYERS_180_D", MenuItemOrder = 12, Icon = PixiPerfectIcons.RotateFile180)]
+        MenuItemPath = "IMAGE/ROTATION/ROT_LAYERS_180_D", MenuItemOrder = 12, Icon = PixiPerfectIcons.RotateFile180, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Document.Rotate270DegLayers", "ROT_LAYERS_-90",
         "ROT_LAYERS_-90", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D270,
-        MenuItemPath = "IMAGE/ROTATION/ROT_LAYERS_-90_D", MenuItemOrder = 13, Icon = PixiPerfectIcons.RotateFileMinus90)]
+        MenuItemPath = "IMAGE/ROTATION/ROT_LAYERS_-90_D", MenuItemOrder = 13, Icon = PixiPerfectIcons.RotateFileMinus90, AnalyticsTrack = true)]
     public void RotateLayers(RotationAngle angle)
     {
         if (ActiveDocument?.SelectedStructureMember == null)
@@ -113,7 +113,7 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
     }
 
     [Command.Basic("PixiEditor.Document.ToggleVerticalSymmetryAxis", "TOGGLE_VERT_SYMMETRY_AXIS", "TOGGLE_VERT_SYMMETRY_AXIS", CanExecute = "PixiEditor.HasDocument", 
-        Icon = PixiPerfectIcons.YSymmetry)]
+        Icon = PixiPerfectIcons.YSymmetry, AnalyticsTrack = true)]
     public void ToggleVerticalSymmetryAxis()
     {
         if (ActiveDocument is null)
@@ -122,7 +122,7 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
     }
 
     [Command.Basic("PixiEditor.Document.ToggleHorizontalSymmetryAxis", "TOGGLE_HOR_SYMMETRY_AXIS", "TOGGLE_HOR_SYMMETRY_AXIS", CanExecute = "PixiEditor.HasDocument", 
-        Icon = PixiPerfectIcons.XSymmetry)]
+        Icon = PixiPerfectIcons.XSymmetry, AnalyticsTrack = true)]
     public void ToggleHorizontalSymmetryAxis()
     {
         if (ActiveDocument is null)
@@ -138,7 +138,7 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
         ActiveDocument.EventInlet.OnSymmetryDragged(info);
     }
 
-    [Command.Internal("PixiEditor.Document.StartDragSymmetry", CanExecute = "PixiEditor.HasDocument")]
+    [Command.Internal("PixiEditor.Document.StartDragSymmetry", CanExecute = "PixiEditor.HasDocument", AnalyticsTrack = true)]
     public void StartDragSymmetry(SymmetryAxisDirection dir)
     {
         if (ActiveDocument is null)
@@ -159,7 +159,7 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
         CanExecute = "PixiEditor.Selection.IsNotEmpty", Key = Key.Delete,
         ShortcutContext = typeof(ViewportWindowViewModel),
         Icon = PixiPerfectIcons.Eraser,
-        MenuItemPath = "EDIT/DELETE_SELECTED_PIXELS", MenuItemOrder = 6)]
+        MenuItemPath = "EDIT/DELETE_SELECTED_PIXELS", MenuItemOrder = 6, AnalyticsTrack = true)]
     public void DeletePixels()
     {
         Owner.DocumentManagerSubViewModel.ActiveDocument?.Operations.DeleteSelectedPixels(activeDocument.AnimationDataViewModel.ActiveFrameBindable);
@@ -167,9 +167,9 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
 
 
     [Command.Basic("PixiEditor.Document.ResizeDocument", false, "RESIZE_DOCUMENT", "RESIZE_DOCUMENT", CanExecute = "PixiEditor.HasDocument", Key = Key.I, Modifiers = KeyModifiers.Control | KeyModifiers.Shift,
-        Icon = PixiPerfectIcons.Resize, MenuItemPath = "IMAGE/RESIZE_IMAGE", MenuItemOrder = 0)]
+        Icon = PixiPerfectIcons.Resize, MenuItemPath = "IMAGE/RESIZE_IMAGE", MenuItemOrder = 0, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Document.ResizeCanvas", true, "RESIZE_CANVAS", "RESIZE_CANVAS", CanExecute = "PixiEditor.HasDocument", Key = Key.C, Modifiers = KeyModifiers.Control | KeyModifiers.Shift,
-        Icon = PixiPerfectIcons.CanvasResize, MenuItemPath = "IMAGE/RESIZE_CANVAS", MenuItemOrder = 1)]
+        Icon = PixiPerfectIcons.CanvasResize, MenuItemPath = "IMAGE/RESIZE_CANVAS", MenuItemOrder = 1, AnalyticsTrack = true)]
     public async Task OpenResizePopup(bool canvas)
     {
         DocumentViewModel? doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -195,7 +195,7 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
     }
 
     [Command.Basic("PixiEditor.Document.CenterContent", "CENTER_CONTENT", "CENTER_CONTENT", CanExecute = "PixiEditor.HasDocument",
-        Icon = PixiPerfectIcons.Center, MenuItemPath = "IMAGE/CENTER_CONTENT", MenuItemOrder = 3)]
+        Icon = PixiPerfectIcons.Center, MenuItemPath = "IMAGE/CENTER_CONTENT", MenuItemOrder = 3, AnalyticsTrack = true)]
     public void CenterContent()
     {
         if(ActiveDocument?.SelectedStructureMember == null)

+ 3 - 3
src/PixiEditor/ViewModels/SubViewModels/AnimationsViewModel.cs

@@ -17,8 +17,8 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
     {
     }
     
-    [Command.Basic("PixiEditor.Animation.CreateRasterKeyFrame", "Create Raster Key Frame", "Create a raster key frame", Parameter = false)]
-    [Command.Basic("PixiEditor.Animation.DuplicateRasterKeyFrame", "Duplicate Raster Key Frame", "Duplicate a raster key frame", Parameter = true)]
+    [Command.Basic("PixiEditor.Animation.CreateRasterKeyFrame", "Create Raster Key Frame", "Create a raster key frame", Parameter = false, AnalyticsTrack = true)]
+    [Command.Basic("PixiEditor.Animation.DuplicateRasterKeyFrame", "Duplicate Raster Key Frame", "Duplicate a raster key frame", Parameter = true, AnalyticsTrack = true)]
     public void CreateRasterKeyFrame(bool duplicate)
     {
         var activeDocument = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -49,7 +49,7 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
     }
     
     [Command.Basic("PixiEditor.Animation.DeleteKeyFrames", "DELETE_KEY_FRAMES", "DELETE_KEY_FRAMES_DESCRIPTIVE",
-        ShortcutContext = typeof(TimelineDockViewModel), Key = Key.Delete)]
+        ShortcutContext = typeof(TimelineDockViewModel), Key = Key.Delete, AnalyticsTrack = true)]
     public void DeleteKeyFrames()
     {
         var activeDocument = Owner.DocumentManagerSubViewModel.ActiveDocument;

+ 12 - 12
src/PixiEditor/ViewModels/SubViewModels/ClipboardViewModel.cs

@@ -32,7 +32,7 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Clipboard.Cut", "CUT", "CUT_DESCRIPTIVE", CanExecute = "PixiEditor.Selection.IsNotEmpty", Key = Key.X, Modifiers = KeyModifiers.Control,
-        MenuItemPath = "EDIT/CUT", MenuItemOrder = 2, Icon = PixiPerfectIcons.Scissors)]
+        MenuItemPath = "EDIT/CUT", MenuItemOrder = 2, Icon = PixiPerfectIcons.Scissors, AnalyticsTrack = true)]
     public async Task Cut()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -43,9 +43,9 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Clipboard.Paste", false, "PASTE", "PASTE_DESCRIPTIVE", CanExecute = "PixiEditor.Clipboard.CanPaste", Key = Key.V, Modifiers = KeyModifiers.Shift,
-        MenuItemPath = "EDIT/PASTE", MenuItemOrder = 4, Icon = PixiPerfectIcons.Paste)]
+        MenuItemPath = "EDIT/PASTE", MenuItemOrder = 4, Icon = PixiPerfectIcons.Paste, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Clipboard.PasteAsNewLayer", true, "PASTE_AS_NEW_LAYER", "PASTE_AS_NEW_LAYER_DESCRIPTIVE", CanExecute = "PixiEditor.Clipboard.CanPaste", Key = Key.V, Modifiers = KeyModifiers.Control,
-        Icon = PixiPerfectIcons.PasteAsNewLayer)]
+        Icon = PixiPerfectIcons.PasteAsNewLayer, AnalyticsTrack = true)]
     public void Paste(bool pasteAsNewLayer)
     {
         if (Owner.DocumentManagerSubViewModel.ActiveDocument is null) 
@@ -54,7 +54,7 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
     }
     
     [Command.Basic("PixiEditor.Clipboard.PasteReferenceLayer", "PASTE_REFERENCE_LAYER", "PASTE_REFERENCE_LAYER_DESCRIPTIVE", CanExecute = "PixiEditor.Clipboard.CanPaste",
-        Icon = PixiPerfectIcons.PasteReferenceLayer)]
+        Icon = PixiPerfectIcons.PasteReferenceLayer, AnalyticsTrack = true)]
     public async Task PasteReferenceLayer(IDataObject data)
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -90,8 +90,8 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
             new VecI(bitmap.Size.X, bitmap.Size.Y));
     }
 
-    [Command.Basic("PixiEditor.Clipboard.PasteColor", false, "PASTE_COLOR", "PASTE_COLOR_DESCRIPTIVE", CanExecute = "PixiEditor.Clipboard.CanPasteColor", IconEvaluator = "PixiEditor.Clipboard.PasteColorIcon")]
-    [Command.Basic("PixiEditor.Clipboard.PasteColorAsSecondary", true, "PASTE_COLOR_SECONDARY", "PASTE_COLOR_SECONDARY_DESCRIPTIVE", CanExecute = "PixiEditor.Clipboard.CanPasteColor", IconEvaluator = "PixiEditor.Clipboard.PasteColorIcon")]
+    [Command.Basic("PixiEditor.Clipboard.PasteColor", false, "PASTE_COLOR", "PASTE_COLOR_DESCRIPTIVE", CanExecute = "PixiEditor.Clipboard.CanPasteColor", IconEvaluator = "PixiEditor.Clipboard.PasteColorIcon", AnalyticsTrack = true)]
+    [Command.Basic("PixiEditor.Clipboard.PasteColorAsSecondary", true, "PASTE_COLOR_SECONDARY", "PASTE_COLOR_SECONDARY_DESCRIPTIVE", CanExecute = "PixiEditor.Clipboard.CanPasteColor", IconEvaluator = "PixiEditor.Clipboard.PasteColorIcon", AnalyticsTrack = true)]
     public async Task PasteColor(bool secondary)
     {
         if (!ColorHelper.ParseAnyFormat((await ClipboardController.Clipboard.GetTextAsync())?.Trim() ?? string.Empty, out var result))
@@ -110,7 +110,7 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Clipboard.Copy", "COPY", "COPY_DESCRIPTIVE", CanExecute = "PixiEditor.Selection.IsNotEmpty", Key = Key.C, Modifiers = KeyModifiers.Control,
-        MenuItemPath = "EDIT/COPY", MenuItemOrder = 3, Icon = PixiPerfectIcons.Copy)]
+        MenuItemPath = "EDIT/COPY", MenuItemOrder = 3, Icon = PixiPerfectIcons.Copy, AnalyticsTrack = true)]
     public async Task Copy()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -119,11 +119,11 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
         await ClipboardController.CopyToClipboard(doc);
     }
 
-    [Command.Basic("PixiEditor.Clipboard.CopyPrimaryColorAsHex", CopyColor.PrimaryHEX, "COPY_COLOR_HEX", "COPY_COLOR_HEX_DESCRIPTIVE", IconEvaluator = "PixiEditor.Clipboard.CopyColorIcon")]
-    [Command.Basic("PixiEditor.Clipboard.CopyPrimaryColorAsRgb", CopyColor.PrimaryRGB, "COPY_COLOR_RGB", "COPY_COLOR_RGB_DESCRIPTIVE", IconEvaluator = "PixiEditor.Clipboard.CopyColorIcon")]
-    [Command.Basic("PixiEditor.Clipboard.CopySecondaryColorAsHex", CopyColor.SecondaryHEX, "COPY_COLOR_SECONDARY_HEX", "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE", IconEvaluator = "PixiEditor.Clipboard.CopyColorIcon")]
-    [Command.Basic("PixiEditor.Clipboard.CopySecondaryColorAsRgb", CopyColor.SecondardRGB, "COPY_COLOR_SECONDARY_RGB", "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE", IconEvaluator = "PixiEditor.Clipboard.CopyColorIcon")]
-    [Command.Filter("PixiEditor.Clipboard.CopyColorToClipboard", "COPY_COLOR_TO_CLIPBOARD", "COPY_COLOR", Key = Key.C, Modifiers = KeyModifiers.Shift)]
+    [Command.Basic("PixiEditor.Clipboard.CopyPrimaryColorAsHex", CopyColor.PrimaryHEX, "COPY_COLOR_HEX", "COPY_COLOR_HEX_DESCRIPTIVE", IconEvaluator = "PixiEditor.Clipboard.CopyColorIcon", AnalyticsTrack = true)]
+    [Command.Basic("PixiEditor.Clipboard.CopyPrimaryColorAsRgb", CopyColor.PrimaryRGB, "COPY_COLOR_RGB", "COPY_COLOR_RGB_DESCRIPTIVE", IconEvaluator = "PixiEditor.Clipboard.CopyColorIcon", AnalyticsTrack = true)]
+    [Command.Basic("PixiEditor.Clipboard.CopySecondaryColorAsHex", CopyColor.SecondaryHEX, "COPY_COLOR_SECONDARY_HEX", "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE", IconEvaluator = "PixiEditor.Clipboard.CopyColorIcon", AnalyticsTrack = true)]
+    [Command.Basic("PixiEditor.Clipboard.CopySecondaryColorAsRgb", CopyColor.SecondardRGB, "COPY_COLOR_SECONDARY_RGB", "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE", IconEvaluator = "PixiEditor.Clipboard.CopyColorIcon", AnalyticsTrack = true)]
+    [Command.Filter("PixiEditor.Clipboard.CopyColorToClipboard", "COPY_COLOR_TO_CLIPBOARD", "COPY_COLOR", Key = Key.C, Modifiers = KeyModifiers.Shift, AnalyticsTrack = true)]
     public async Task CopyColorAsHex(CopyColor color)
     {
         var targetColor = color switch

+ 16 - 16
src/PixiEditor/ViewModels/SubViewModels/ColorsViewModel.cs

@@ -115,8 +115,8 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>, IColorsHandler
         doc.Operations.ReplaceColor(colors.oldColor, colors.newColor, doc.AnimationDataViewModel.ActiveFrameBindable);
     }
 
-    [Commands_Command.Basic("PixiEditor.Colors.ReplaceSecondaryByPrimaryColor", false, "REPLACE_SECONDARY_BY_PRIMARY", "REPLACE_SECONDARY_BY_PRIMARY", IconEvaluator = "PixiEditor.Colors.ReplaceColorIcon")]
-    [Commands_Command.Basic("PixiEditor.Colors.ReplacePrimaryBySecondaryColor", true, "REPLACE_PRIMARY_BY_SECONDARY", "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE", IconEvaluator = "PixiEditor.Colors.ReplaceColorIcon")]
+    [Commands_Command.Basic("PixiEditor.Colors.ReplaceSecondaryByPrimaryColor", false, "REPLACE_SECONDARY_BY_PRIMARY", "REPLACE_SECONDARY_BY_PRIMARY", IconEvaluator = "PixiEditor.Colors.ReplaceColorIcon", AnalyticsTrack = true)]
+    [Commands_Command.Basic("PixiEditor.Colors.ReplacePrimaryBySecondaryColor", true, "REPLACE_PRIMARY_BY_SECONDARY", "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE", IconEvaluator = "PixiEditor.Colors.ReplaceColorIcon", AnalyticsTrack = true)]
     public void ReplaceColors(bool replacePrimary)
     {
         PaletteColor oldColor = replacePrimary ? PrimaryColor.ToPaletteColor() : SecondaryColor.ToPaletteColor();
@@ -164,7 +164,7 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>, IColorsHandler
     }
 
     [Commands_Command.Basic("PixiEditor.Colors.OpenPaletteBrowser", "OPEN_PALETTE_BROWSER", "OPEN_PALETTE_BROWSER", CanExecute = "PixiEditor.HasDocument", 
-        Icon = PixiPerfectIcons.Database, MenuItemPath = "VIEW/OPEN_PALETTE_BROWSER", MenuItemOrder = 3)]
+        Icon = PixiPerfectIcons.Database, MenuItemPath = "VIEW/OPEN_PALETTE_BROWSER", MenuItemOrder = 3, AnalyticsTrack = true)]
     public void OpenPalettesBrowser() 
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -237,7 +237,7 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>, IColorsHandler
         return paletteColors is not null && Owner.DocumentIsNotNull(paletteColors) && paletteColors.Count > 0;
     }
 
-    [Commands_Command.Internal("PixiEditor.Colors.ImportPalette", CanExecute = "PixiEditor.Colors.CanImportPalette")]
+    [Commands_Command.Internal("PixiEditor.Colors.ImportPalette", CanExecute = "PixiEditor.Colors.CanImportPalette", AnalyticsTrack = true)]
     public async Task ImportPalette(List<PaletteColor> palette)
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -299,16 +299,16 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>, IColorsHandler
         return ColorSearchResult.GetIcon(color);
     }
 
-    [Commands_Command.Basic("PixiEditor.Colors.SelectFirstPaletteColor", "SELECT_COLOR_1", "SELECT_COLOR_1_DESCRIPTIVE", Key = Key.D1, Parameter = 0, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.FirstPaletteColorIcon")]
-    [Commands_Command.Basic("PixiEditor.Colors.SelectSecondPaletteColor", "SELECT_COLOR_2", "SELECT_COLOR_2_DESCRIPTIVE", Key = Key.D2, Parameter = 1, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.SecondPaletteColorIcon")]
-    [Commands_Command.Basic("PixiEditor.Colors.SelectThirdPaletteColor", "SELECT_COLOR_3", "SELECT_COLOR_3_DESCRIPTIVE", Key = Key.D3, Parameter = 2, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.ThirdPaletteColorIcon")]
-    [Commands_Command.Basic("PixiEditor.Colors.SelectFourthPaletteColor", "SELECT_COLOR_4", "SELECT_COLOR_4_DESCRIPTIVE", Key = Key.D4, Parameter = 3, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.FourthPaletteColorIcon")]
-    [Commands_Command.Basic("PixiEditor.Colors.SelectFifthPaletteColor", "SELECT_COLOR_5", "SELECT_COLOR_5_DESCRIPTIVE", Key = Key.D5, Parameter = 4, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.FifthPaletteColorIcon")]
-    [Commands_Command.Basic("PixiEditor.Colors.SelectSixthPaletteColor", "SELECT_COLOR_6", "SELECT_COLOR_6_DESCRIPTIVE", Key = Key.D6, Parameter = 5, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.SixthPaletteColorIcon")]
-    [Commands_Command.Basic("PixiEditor.Colors.SelectSeventhPaletteColor", "SELECT_COLOR_7", "SELECT_COLOR_7_DESCRIPTIVE", Key = Key.D7, Parameter = 6, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.SeventhPaletteColorIcon")]
-    [Commands_Command.Basic("PixiEditor.Colors.SelectEighthPaletteColor", "SELECT_COLOR_8", "SELECT_COLOR_8_DESCRIPTIVE", Key = Key.D8, Parameter = 7, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.EighthPaletteColorIcon")]
-    [Commands_Command.Basic("PixiEditor.Colors.SelectNinthPaletteColor", "SELECT_COLOR_9", "SELECT_COLOR_9_DESCRIPTIVE", Key = Key.D9, Parameter = 8, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.NinthPaletteColorIcon")]
-    [Commands_Command.Basic("PixiEditor.Colors.SelectTenthPaletteColor", "SELECT_COLOR_10", "SELECT_COLOR_10_DESCRIPTIVE", Key = Key.D0, Parameter = 9, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.TenthPaletteColorIcon")]
+    [Commands_Command.Basic("PixiEditor.Colors.SelectFirstPaletteColor", "SELECT_COLOR_1", "SELECT_COLOR_1_DESCRIPTIVE", Key = Key.D1, Parameter = 0, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.FirstPaletteColorIcon", AnalyticsTrack = true)]
+    [Commands_Command.Basic("PixiEditor.Colors.SelectSecondPaletteColor", "SELECT_COLOR_2", "SELECT_COLOR_2_DESCRIPTIVE", Key = Key.D2, Parameter = 1, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.SecondPaletteColorIcon", AnalyticsTrack = true)]
+    [Commands_Command.Basic("PixiEditor.Colors.SelectThirdPaletteColor", "SELECT_COLOR_3", "SELECT_COLOR_3_DESCRIPTIVE", Key = Key.D3, Parameter = 2, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.ThirdPaletteColorIcon", AnalyticsTrack = true)]
+    [Commands_Command.Basic("PixiEditor.Colors.SelectFourthPaletteColor", "SELECT_COLOR_4", "SELECT_COLOR_4_DESCRIPTIVE", Key = Key.D4, Parameter = 3, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.FourthPaletteColorIcon", AnalyticsTrack = true)]
+    [Commands_Command.Basic("PixiEditor.Colors.SelectFifthPaletteColor", "SELECT_COLOR_5", "SELECT_COLOR_5_DESCRIPTIVE", Key = Key.D5, Parameter = 4, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.FifthPaletteColorIcon", AnalyticsTrack = true)]
+    [Commands_Command.Basic("PixiEditor.Colors.SelectSixthPaletteColor", "SELECT_COLOR_6", "SELECT_COLOR_6_DESCRIPTIVE", Key = Key.D6, Parameter = 5, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.SixthPaletteColorIcon", AnalyticsTrack = true)]
+    [Commands_Command.Basic("PixiEditor.Colors.SelectSeventhPaletteColor", "SELECT_COLOR_7", "SELECT_COLOR_7_DESCRIPTIVE", Key = Key.D7, Parameter = 6, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.SeventhPaletteColorIcon", AnalyticsTrack = true)]
+    [Commands_Command.Basic("PixiEditor.Colors.SelectEighthPaletteColor", "SELECT_COLOR_8", "SELECT_COLOR_8_DESCRIPTIVE", Key = Key.D8, Parameter = 7, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.EighthPaletteColorIcon", AnalyticsTrack = true)]
+    [Commands_Command.Basic("PixiEditor.Colors.SelectNinthPaletteColor", "SELECT_COLOR_9", "SELECT_COLOR_9_DESCRIPTIVE", Key = Key.D9, Parameter = 8, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.NinthPaletteColorIcon", AnalyticsTrack = true)]
+    [Commands_Command.Basic("PixiEditor.Colors.SelectTenthPaletteColor", "SELECT_COLOR_10", "SELECT_COLOR_10_DESCRIPTIVE", Key = Key.D0, Parameter = 9, CanExecute = "PixiEditor.Colors.CanSelectPaletteColor", IconEvaluator = "PixiEditor.Colors.TenthPaletteColorIcon", AnalyticsTrack = true)]
     public void SelectPaletteColor(int index)
     {
         var document = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -320,7 +320,7 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>, IColorsHandler
     }
 
     [Commands_Command.Basic("PixiEditor.Colors.Swap", "SWAP_COLORS", "SWAP_COLORS_DESCRIPTIVE", Key = Key.X,
-        Icon = PixiPerfectIcons.ColorsSwap)]
+        Icon = PixiPerfectIcons.ColorsSwap, AnalyticsTrack = true)]
     public void SwapColors(object parameter)
     {
         (PrimaryColor, SecondaryColor) = (SecondaryColor, PrimaryColor);
@@ -356,7 +356,7 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>, IColorsHandler
     }
 
     [Commands_Command.Basic("PixIEditor.Colors.AddPrimaryToPalettes", "ADD_PRIMARY_COLOR_TO_PALETTE", "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE", CanExecute = "PixiEditor.HasDocument", 
-        Icon = PixiPerfectIcons.CopyAdd)]
+        Icon = PixiPerfectIcons.CopyAdd, AnalyticsTrack = true)]
     public void AddPrimaryColorToPalette()
     {
         var palette = Owner.DocumentManagerSubViewModel.ActiveDocument.Palette;

+ 16 - 16
src/PixiEditor/ViewModels/SubViewModels/DebugViewModel.cs

@@ -90,9 +90,9 @@ internal class DebugViewModel : SubViewModel<ViewModelMain>
     
 
     [Command.Debug("PixiEditor.Debug.IO.OpenLocalAppDataDirectory", @"PixiEditor", "OPEN_LOCAL_APPDATA_DIR", "OPEN_LOCAL_APPDATA_DIR",
-        MenuItemPath = "DEBUG/OPEN_LOCAL_APPDATA_DIR", MenuItemOrder = 3, Icon = PixiPerfectIcons.Folder)]
+        MenuItemPath = "DEBUG/OPEN_LOCAL_APPDATA_DIR", MenuItemOrder = 3, Icon = PixiPerfectIcons.Folder, AnalyticsTrack = true)]
     [Command.Debug("PixiEditor.Debug.IO.OpenCrashReportsDirectory", @"PixiEditor\crash_logs", "OPEN_CRASH_REPORTS_DIR", "OPEN_CRASH_REPORTS_DIR",
-        MenuItemPath = "DEBUG/OPEN_CRASH_REPORTS_DIR", MenuItemOrder = 4, Icon = PixiPerfectIcons.Folder)]
+        MenuItemPath = "DEBUG/OPEN_CRASH_REPORTS_DIR", MenuItemOrder = 4, Icon = PixiPerfectIcons.Folder, AnalyticsTrack = true)]
     public static void OpenLocalAppDataFolder(string subDirectory)
     {
         var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), subDirectory);
@@ -108,14 +108,14 @@ internal class DebugViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Debug("PixiEditor.Debug.IO.OpenTempDirectory", @"PixiEditor", "OPEN_TEMP_DIR", "OPEN_TEMP_DIR", Icon = PixiPerfectIcons.Folder,
-        MenuItemPath = "DEBUG/OPEN_TEMP_DIR", MenuItemOrder = 6)]
+        MenuItemPath = "DEBUG/OPEN_TEMP_DIR", MenuItemOrder = 6, AnalyticsTrack = true)]
     public static void OpenTempFolder(string subDirectory)
     {
         var path = Path.Combine(Path.GetTempPath(), subDirectory);
         OpenFolder(path);
     }
 
-    [Command.Debug("PixiEditor.Debug.DumpAllCommands", "DUMP_ALL_COMMANDS", "DUMP_ALL_COMMANDS_DESCRIPTIVE")]
+    [Command.Debug("PixiEditor.Debug.DumpAllCommands", "DUMP_ALL_COMMANDS", "DUMP_ALL_COMMANDS_DESCRIPTIVE", AnalyticsTrack = true)]
     public async Task DumpAllCommands()
     {
         await Application.Current.ForDesktopMainWindowAsync(async desktop =>
@@ -141,7 +141,7 @@ internal class DebugViewModel : SubViewModel<ViewModelMain>
         });
     }
     
-    [Command.Debug("PixiEditor.Debug.GenerateKeysTemplate", "GENERATE_KEY_BINDINGS_TEMPLATE", "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE")]
+    [Command.Debug("PixiEditor.Debug.GenerateKeysTemplate", "GENERATE_KEY_BINDINGS_TEMPLATE", "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE", AnalyticsTrack = true)]
     public async Task GenerateKeysTemplate()
     {
         await Application.Current.ForDesktopMainWindowAsync(async desktop =>
@@ -180,7 +180,7 @@ internal class DebugViewModel : SubViewModel<ViewModelMain>
         });
     }
 
-    [Command.Debug("PixiEditor.Debug.ValidateShortcutMap", "VALIDATE_SHORTCUT_MAP", "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE")]
+    [Command.Debug("PixiEditor.Debug.ValidateShortcutMap", "VALIDATE_SHORTCUT_MAP", "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE", AnalyticsTrack = true)]
     public async Task ValidateShortcutMap()
     {
         await Application.Current.ForDesktopMainWindowAsync(async desktop =>
@@ -216,7 +216,7 @@ internal class DebugViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Debug("PixiEditor.Debug.ClearRecentDocument", "CLEAR_RECENT_DOCUMENTS", "CLEAR_RECENTLY_OPENED_DOCUMENTS",
-        MenuItemPath = "DEBUG/DELETE/CLEAR_RECENT_DOCUMENTS")]
+        MenuItemPath = "DEBUG/DELETE/CLEAR_RECENT_DOCUMENTS", AnalyticsTrack = true)]
     public void ClearRecentDocuments()
     {
         Owner.FileSubViewModel.RecentlyOpened.Clear();
@@ -224,21 +224,21 @@ internal class DebugViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Debug("PixiEditor.Debug.OpenCommandDebugWindow", "OPEN_CMD_DEBUG_WINDOW", "OPEN_CMD_DEBUG_WINDOW",
-        MenuItemPath = "DEBUG/OPEN_COMMAND_DEBUG_WINDOW", MenuItemOrder = 0)]
+        MenuItemPath = "DEBUG/OPEN_COMMAND_DEBUG_WINDOW", MenuItemOrder = 0, AnalyticsTrack = true)]
     public void OpenCommandDebugWindow()
     {
         new CommandDebugPopup().Show();
     }
 
     [Command.Debug("PixiEditor.Debug.OpenPointerDebugWindow", "Open pointer debug window", "Open pointer debug window",
-        MenuItemPath = "DEBUG/Open pointer debug window", MenuItemOrder = 1)]
+        MenuItemPath = "DEBUG/Open pointer debug window", MenuItemOrder = 1, AnalyticsTrack = true)]
     public void OpenPointerDebugWindow()
     {
         new PointerDebugPopup().Show();
     }
 
     [Command.Debug("PixiEditor.Debug.OpenLocalizationDebugWindow", "OPEN_LOCALIZATION_DEBUG_WINDOW", "OPEN_LOCALIZATION_DEBUG_WINDOW",
-        MenuItemPath = "DEBUG/OPEN_LOCALIZATION_DEBUG_WINDOW", MenuItemOrder = 2)]
+        MenuItemPath = "DEBUG/OPEN_LOCALIZATION_DEBUG_WINDOW", MenuItemOrder = 2, AnalyticsTrack = true)]
     public void OpenLocalizationDebugWindow()
     {
         if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
@@ -250,7 +250,7 @@ internal class DebugViewModel : SubViewModel<ViewModelMain>
 
     }
 
-    [Command.Internal("PixiEditor.Debug.SetLanguageFromFilePicker")]
+    [Command.Internal("PixiEditor.Debug.SetLanguageFromFilePicker", AnalyticsTrack = true)]
     public async Task SetLanguageFromFilePicker()
     {
         await Application.Current.ForDesktopMainWindowAsync(async desktop =>
@@ -276,22 +276,22 @@ internal class DebugViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Debug("PixiEditor.Debug.IO.OpenInstallDirectory", "OPEN_INSTALLATION_DIR", "OPEN_INSTALLATION_DIR", Icon = PixiPerfectIcons.Folder,
-        MenuItemPath = "DEBUG/OPEN_INSTALLATION_DIR", MenuItemOrder = 8)]
+        MenuItemPath = "DEBUG/OPEN_INSTALLATION_DIR", MenuItemOrder = 8, AnalyticsTrack = true)]
     public static void OpenInstallLocation()
     {
         IOperatingSystem.Current.OpenFolder(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
     }
 
     [Command.Debug("PixiEditor.Debug.Crash", "CRASH", "CRASH_APP",
-        MenuItemPath = "DEBUG/CRASH", MenuItemOrder = 9)]
+        MenuItemPath = "DEBUG/CRASH", MenuItemOrder = 9, AnalyticsTrack = true)]
     public static void Crash() => throw new InvalidOperationException("User requested to crash :c");
 
     [Command.Debug("PixiEditor.Debug.DeleteUserPreferences", @"%appdata%\PixiEditor\user_preferences.json", "DELETE_USR_PREFS", "DELETE_USR_PREFS",
-        MenuItemPath = "DEBUG/DELETE/USER_PREFS", MenuItemOrder = 10)]
+        MenuItemPath = "DEBUG/DELETE/USER_PREFS", MenuItemOrder = 10, AnalyticsTrack = true)]
     [Command.Debug("PixiEditor.Debug.DeleteShortcutFile", @"%appdata%\PixiEditor\shortcuts.json", "DELETE_SHORTCUT_FILE", "DELETE_SHORTCUT_FILE",
-        MenuItemPath = "DEBUG/DELETE/SHORTCUT_FILE", MenuItemOrder = 11)]
+        MenuItemPath = "DEBUG/DELETE/SHORTCUT_FILE", MenuItemOrder = 11, AnalyticsTrack = true)]
     [Command.Debug("PixiEditor.Debug.DeleteEditorData", @"%localappdata%\PixiEditor\editor_data.json", "DELETE_EDITOR_DATA", "DELETE_EDITOR_DATA",
-        MenuItemPath = "DEBUG/DELETE/EDITOR_DATA", MenuItemOrder = 12)]
+        MenuItemPath = "DEBUG/DELETE/EDITOR_DATA", MenuItemOrder = 12, AnalyticsTrack = true)]
     public static async Task DeleteFile(string path)
     {
         if (MainWindow.Current is null)

+ 6 - 6
src/PixiEditor/ViewModels/SubViewModels/FileViewModel.cs

@@ -125,7 +125,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
         }
     }
 
-    [Command.Internal("PixiEditor.File.OpenRecent")]
+    [Command.Internal("PixiEditor.File.OpenRecent", AnalyticsTrack = true)]
     public void OpenRecent(string parameter)
     {
         string path = parameter;
@@ -141,7 +141,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.File.Open", "OPEN", "OPEN_FILE", Key = Key.O, Modifiers = KeyModifiers.Control,
-        MenuItemPath = "FILE/OPEN_FILE", MenuItemOrder = 1, Icon = PixiPerfectIcons.FileText)]
+        MenuItemPath = "FILE/OPEN_FILE", MenuItemOrder = 1, Icon = PixiPerfectIcons.FileText, AnalyticsTrack = true)]
     public async Task OpenFromOpenFileDialog()
     {
         var filter = SupportedFilesHelper.BuildOpenFilter();
@@ -159,7 +159,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.File.OpenFileFromClipboard", "OPEN_FILE_FROM_CLIPBOARD",
-        "OPEN_FILE_FROM_CLIPBOARD_DESCRIPTIVE", CanExecute = "PixiEditor.Clipboard.HasImageInClipboard")]
+        "OPEN_FILE_FROM_CLIPBOARD_DESCRIPTIVE", CanExecute = "PixiEditor.Clipboard.HasImageInClipboard", AnalyticsTrack = true)]
     public async Task OpenFromClipboard()
     {
         var images = await ClipboardController.GetImagesFromClipboard();
@@ -345,10 +345,10 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
 
     [Command.Basic("PixiEditor.File.Save", false, "SAVE", "SAVE_IMAGE", CanExecute = "PixiEditor.HasDocument",
         Key = Key.S, Modifiers = KeyModifiers.Control, Icon = PixiPerfectIcons.Save,
-        MenuItemPath = "FILE/SAVE_PIXI", MenuItemOrder = 3)]
+        MenuItemPath = "FILE/SAVE_PIXI", MenuItemOrder = 3, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.File.SaveAsNew", true, "SAVE_AS", "SAVE_IMAGE_AS", CanExecute = "PixiEditor.HasDocument",
         Key = Key.S, Modifiers = KeyModifiers.Control | KeyModifiers.Shift, Icon = PixiPerfectIcons.Save,
-        MenuItemPath = "FILE/SAVE_AS_PIXI", MenuItemOrder = 4)]
+        MenuItemPath = "FILE/SAVE_AS_PIXI", MenuItemOrder = 4, AnalyticsTrack = true)]
     public async Task<bool> SaveActiveDocument(bool asNew)
     {
         DocumentViewModel doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -397,7 +397,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
     /// <param name="parameter">CommandProperty.</param>
     [Command.Basic("PixiEditor.File.Export", "EXPORT", "EXPORT_IMAGE", CanExecute = "PixiEditor.HasDocument",
         Key = Key.E, Modifiers = KeyModifiers.Control,
-        MenuItemPath = "FILE/EXPORT_IMG", MenuItemOrder = 5, Icon = PixiPerfectIcons.Image)]
+        MenuItemPath = "FILE/EXPORT_IMG", MenuItemOrder = 5, Icon = PixiPerfectIcons.Image, AnalyticsTrack = true)]
     public async Task ExportFile()
     {
         try

+ 23 - 23
src/PixiEditor/ViewModels/SubViewModels/LayersViewModel.cs

@@ -59,7 +59,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         "LAYER_DELETE_SELECTED_DESCRIPTIVE", 
         CanExecute = "PixiEditor.Layer.CanDeleteSelected", Key = Key.Delete, 
         ShortcutContext = typeof(LayersDockViewModel),
-        Icon = PixiPerfectIcons.Trash)]
+        Icon = PixiPerfectIcons.Trash, AnalyticsTrack = true)]
     public void DeleteSelected()
     {
         var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
@@ -103,7 +103,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Layer.DeleteAllSelected", "LAYER_DELETE_ALL_SELECTED", "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.HasSelectedMembers", 
-        Icon = PixiPerfectIcons.Trash)]
+        Icon = PixiPerfectIcons.Trash, AnalyticsTrack = true)]
     public void DeleteAllSelected()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -115,7 +115,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Layer.NewFolder", "NEW_FOLDER", "CREATE_NEW_FOLDER", CanExecute = "PixiEditor.Layer.CanCreateNewMember",
-        Icon = PixiPerfectIcons.FolderPlus)]
+        Icon = PixiPerfectIcons.FolderPlus, AnalyticsTrack = true)]
     public void NewFolder()
     {
         if (Owner.DocumentManagerSubViewModel.ActiveDocument is not { } doc)
@@ -124,7 +124,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Layer.NewLayer", "NEW_LAYER", "CREATE_NEW_LAYER", CanExecute = "PixiEditor.Layer.CanCreateNewMember", Key = Key.N, Modifiers = KeyModifiers.Control | KeyModifiers.Shift, 
-        Icon = PixiPerfectIcons.FilePlus)]
+        Icon = PixiPerfectIcons.FilePlus, AnalyticsTrack = true)]
     public void NewLayer()
     {
         if (Owner.DocumentManagerSubViewModel.ActiveDocument is not { } doc)
@@ -138,7 +138,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         return Owner.DocumentManagerSubViewModel.ActiveDocument is { UpdateableChangeActive: false };
     }
 
-    [Command.Internal("PixiEditor.Layer.ToggleLockTransparency", CanExecute = "PixiEditor.Layer.SelectedMemberIsLayer")]
+    [Command.Internal("PixiEditor.Layer.ToggleLockTransparency", CanExecute = "PixiEditor.Layer.SelectedMemberIsLayer", AnalyticsTrack = true)]
     public void ToggleLockTransparency()
     {
         var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
@@ -160,13 +160,13 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         Owner.DocumentManagerSubViewModel.ActiveDocument?.EventInlet.OnOpacitySliderDragged((float)value);
     }
 
-    [Command.Internal("PixiEditor.Layer.OpacitySliderDragEnded")]
+    [Command.Internal("PixiEditor.Layer.OpacitySliderDragEnded", AnalyticsTrack = true)]
     public void OpacitySliderDragEnded()
     {
         Owner.DocumentManagerSubViewModel.ActiveDocument?.EventInlet.OnOpacitySliderDragEnded();
     }
 
-    [Command.Internal("PixiEditor.Layer.OpacitySliderSet")]
+    [Command.Internal("PixiEditor.Layer.OpacitySliderSet", AnalyticsTrack = true)]
     public void OpacitySliderSet(double value)
     {
         var document = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -178,7 +178,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Layer.DuplicateSelectedLayer", "DUPLICATE_SELECTED_LAYER", "DUPLICATE_SELECTED_LAYER", CanExecute = "PixiEditor.Layer.SelectedMemberIsLayer",
-        Icon = PixiPerfectIcons.DuplicateFile, MenuItemPath = "EDIT/DUPLICATE", MenuItemOrder = 5)]
+        Icon = PixiPerfectIcons.DuplicateFile, MenuItemPath = "EDIT/DUPLICATE", MenuItemOrder = 5, AnalyticsTrack = true)]
     public void DuplicateLayer()
     {
         var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
@@ -240,7 +240,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
     public bool ActiveLayerHasNoMask() => !Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember?.HasMaskBindable ?? false;
 
     [Command.Basic("PixiEditor.Layer.CreateMask", "CREATE_MASK", "CREATE_MASK", CanExecute = "PixiEditor.Layer.ActiveLayerHasNoMask", 
-        Icon = PixiPerfectIcons.CreateMask)]
+        Icon = PixiPerfectIcons.CreateMask, AnalyticsTrack = true)]
     public void CreateMask()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -250,7 +250,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         doc!.Operations.CreateMask(member);
     }
 
-    [Command.Basic("PixiEditor.Layer.DeleteMask", "DELETE_MASK", "DELETE_MASK", CanExecute = "PixiEditor.Layer.ActiveLayerHasMask", Icon = PixiPerfectIcons.Trash)]
+    [Command.Basic("PixiEditor.Layer.DeleteMask", "DELETE_MASK", "DELETE_MASK", CanExecute = "PixiEditor.Layer.ActiveLayerHasMask", Icon = PixiPerfectIcons.Trash, AnalyticsTrack = true)]
     public void DeleteMask()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -261,7 +261,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Layer.ToggleMask", "TOGGLE_MASK", "TOGGLE_MASK", CanExecute = "PixiEditor.Layer.ActiveLayerHasMask",
-        Icon = PixiPerfectIcons.ToggleMask)]
+        Icon = PixiPerfectIcons.ToggleMask, AnalyticsTrack = true)]
     public void ToggleMask()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -272,7 +272,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         member.MaskIsVisibleBindable = !member.MaskIsVisibleBindable;
     }
     
-    [Command.Basic("PixiEditor.Layer.ApplyMask", "APPLY_MASK", "APPLY_MASK", CanExecute = "PixiEditor.Layer.ActiveLayerHasMask")]
+    [Command.Basic("PixiEditor.Layer.ApplyMask", "APPLY_MASK", "APPLY_MASK", CanExecute = "PixiEditor.Layer.ActiveLayerHasMask", AnalyticsTrack = true)]
     public void ApplyMask()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -284,7 +284,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Layer.ToggleVisible", "TOGGLE_VISIBILITY", "TOGGLE_VISIBILITY", CanExecute = "PixiEditor.HasDocument",
-        Icon = PixiPerfectIcons.ToggleLayerVisible)]
+        Icon = PixiPerfectIcons.ToggleLayerVisible, AnalyticsTrack = true)]
     public void ToggleVisible()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -300,12 +300,12 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
     [Evaluator.CanExecute("PixiEditor.Layer.HasMemberBelow")]
     public bool HasMemberBelow(object property) => HasSelectedMember(false);
 
-    [Command.Basic("PixiEditor.Layer.MoveSelectedMemberUpwards", "MOVE_MEMBER_UP", "MOVE_MEMBER_UP_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.HasMemberAbove")]
+    [Command.Basic("PixiEditor.Layer.MoveSelectedMemberUpwards", "MOVE_MEMBER_UP", "MOVE_MEMBER_UP_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.HasMemberAbove", AnalyticsTrack = true)]
     public void MoveSelectedMemberUpwards() => MoveSelectedMember(true);
-    [Command.Basic("PixiEditor.Layer.MoveSelectedMemberDownwards", "MOVE_MEMBER_DOWN", "MOVE_MEMBER_DOWN_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.HasMemberBelow")]
+    [Command.Basic("PixiEditor.Layer.MoveSelectedMemberDownwards", "MOVE_MEMBER_DOWN", "MOVE_MEMBER_DOWN_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.HasMemberBelow", AnalyticsTrack = true)]
     public void MoveSelectedMemberDownwards() => MoveSelectedMember(false);
 
-    [Command.Basic("PixiEditor.Layer.MergeSelected", "MERGE_ALL_SELECTED_LAYERS", "MERGE_ALL_SELECTED_LAYERS", CanExecute = "PixiEditor.Layer.HasMultipleSelectedMembers")]
+    [Command.Basic("PixiEditor.Layer.MergeSelected", "MERGE_ALL_SELECTED_LAYERS", "MERGE_ALL_SELECTED_LAYERS", CanExecute = "PixiEditor.Layer.HasMultipleSelectedMembers", AnalyticsTrack = true)]
     public void MergeSelected()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -335,11 +335,11 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         doc.Operations.MergeStructureMembers(new List<Guid> { member.Id, above ? nextMergeableMember.Id : previousMergeableMember.Id });
     }
 
-    [Command.Basic("PixiEditor.Layer.MergeWithAbove", "MERGE_WITH_ABOVE", "MERGE_WITH_ABOVE_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.HasMemberAbove")]
+    [Command.Basic("PixiEditor.Layer.MergeWithAbove", "MERGE_WITH_ABOVE", "MERGE_WITH_ABOVE_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.HasMemberAbove", AnalyticsTrack = true)]
     public void MergeWithAbove() => MergeSelectedWith(true);
 
     [Command.Basic("PixiEditor.Layer.MergeWithBelow", "MERGE_WITH_BELOW", "MERGE_WITH_BELOW_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.HasMemberBelow", 
-        Icon = PixiPerfectIcons.Merge)]
+        Icon = PixiPerfectIcons.Merge, AnalyticsTrack = true)]
     public void MergeWithBelow() => MergeSelectedWith(false);
 
     [Evaluator.CanExecute("PixiEditor.Layer.ReferenceLayerExists")]
@@ -349,7 +349,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         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",
-        Icon = PixiPerfectIcons.AddReference)]
+        Icon = PixiPerfectIcons.AddReference, AnalyticsTrack = true)]
     public async Task ImportReferenceLayer()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -400,7 +400,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         return null;
     }
 
-    [Command.Basic("PixiEditor.Layer.DeleteReferenceLayer", "DELETE_REFERENCE_LAYER", "DELETE_REFERENCE_LAYER", CanExecute = "PixiEditor.Layer.ReferenceLayerExists", Icon = PixiPerfectIcons.Trash)]
+    [Command.Basic("PixiEditor.Layer.DeleteReferenceLayer", "DELETE_REFERENCE_LAYER", "DELETE_REFERENCE_LAYER", CanExecute = "PixiEditor.Layer.ReferenceLayerExists", Icon = PixiPerfectIcons.Trash, AnalyticsTrack = true)]
     public void DeleteReferenceLayer()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -411,7 +411,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Layer.TransformReferenceLayer", "TRANSFORM_REFERENCE_LAYER", "TRANSFORM_REFERENCE_LAYER", CanExecute = "PixiEditor.Layer.ReferenceLayerExists", 
-        Icon = PixiPerfectIcons.Crop)]
+        Icon = PixiPerfectIcons.Crop, AnalyticsTrack = true)]
     public void TransformReferenceLayer()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -421,7 +421,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         doc.Operations.TransformReferenceLayer();
     }
 
-    [Command.Basic("PixiEditor.Layer.ToggleReferenceLayerTopMost", "TOGGLE_REFERENCE_LAYER_POS", "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.ReferenceLayerExists", IconEvaluator = "PixiEditor.Layer.ToggleReferenceLayerTopMostIcon")]
+    [Command.Basic("PixiEditor.Layer.ToggleReferenceLayerTopMost", "TOGGLE_REFERENCE_LAYER_POS", "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.ReferenceLayerExists", IconEvaluator = "PixiEditor.Layer.ToggleReferenceLayerTopMostIcon", AnalyticsTrack = true)]
     public void ToggleReferenceLayerTopMost()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -432,7 +432,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Layer.ResetReferenceLayerPosition", "RESET_REFERENCE_LAYER_POS", "RESET_REFERENCE_LAYER_POS", CanExecute = "PixiEditor.Layer.ReferenceLayerExists", 
-        Icon = PixiPerfectIcons.Reset)]
+        Icon = PixiPerfectIcons.Reset, AnalyticsTrack = true)]
     public void ResetReferenceLayerPosition()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;

+ 5 - 5
src/PixiEditor/ViewModels/SubViewModels/MiscViewModel.cs

@@ -16,15 +16,15 @@ internal class MiscViewModel : SubViewModel<ViewModelMain>
 
     [Command.Internal("PixiEditor.Links.OpenHyperlink")]
     [Command.Basic("PixiEditor.Links.OpenDocumentation", "https://pixieditor.net/docs/introduction", "DOCUMENTATION", "OPEN_DOCUMENTATION", Icon = PixiPerfectIcons.Globe,
-        MenuItemPath = "HELP/DOCUMENTATION", MenuItemOrder = 0)]
+        MenuItemPath = "HELP/DOCUMENTATION", MenuItemOrder = 0, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Links.OpenWebsite", "https://pixieditor.net", "WEBSITE", "OPEN_WEBSITE", Icon = PixiPerfectIcons.Globe,
-        MenuItemPath = "HELP/WEBSITE", MenuItemOrder = 1)]
+        MenuItemPath = "HELP/WEBSITE", MenuItemOrder = 1, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Links.OpenRepository", "https://github.com/PixiEditor/PixiEditor", "REPOSITORY", "OPEN_REPOSITORY", Icon = PixiPerfectIcons.Globe,
-        MenuItemPath = "HELP/REPOSITORY", MenuItemOrder = 2)]
+        MenuItemPath = "HELP/REPOSITORY", MenuItemOrder = 2, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Links.OpenLicense", "LICENSE", "LICENSE", "OPEN_LICENSE", Icon = PixiPerfectIcons.Globe,
-        MenuItemPath = "HELP/LICENSE", MenuItemOrder = 3)]
+        MenuItemPath = "HELP/LICENSE", MenuItemOrder = 3, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Links.OpenOtherLicenses", "Third Party Licenses", "THIRD_PARTY_LICENSES", "OPEN_THIRD_PARTY_LICENSES", Icon = PixiPerfectIcons.Globe,
-        MenuItemPath = "HELP/THIRD_PARTY_LICENSES", MenuItemOrder = 4)]
+        MenuItemPath = "HELP/THIRD_PARTY_LICENSES", MenuItemOrder = 4, AnalyticsTrack = true)]
     public static void OpenUri(string uri)
     {
         try

+ 4 - 3
src/PixiEditor/ViewModels/SubViewModels/NodeGraphManagerViewModel.cs

@@ -15,7 +15,7 @@ internal class NodeGraphManagerViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.NodeGraph.DeleteSelectedNodes", "DELETE_NODES", "DELETE_NODES_DESCRIPTIVE", 
-        Key = Key.Delete, ShortcutContext = typeof(NodeGraphDockViewModel))]
+        Key = Key.Delete, ShortcutContext = typeof(NodeGraphDockViewModel), AnalyticsTrack = true)]
     public void DeleteSelectedNodes()
     {
         Guid[] selectedNodes = Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.AllNodes
@@ -27,7 +27,8 @@ internal class NodeGraphManagerViewModel : SubViewModel<ViewModelMain>
         Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.RemoveNodes(selectedNodes);
     }
 
-    [Command.Debug("PixiEditor.NodeGraph.CreateNodeFrameAroundEverything", "Create node frame", "Create node frame")]
+    // TODO: Remove this
+    [Command.Debug("PixiEditor.NodeGraph.CreateNodeFrameAroundEverything", "Create node frame", "Create node frame", AnalyticsTrack = true)]
     public void CreateNodeFrameAroundEverything()
     {
         Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.CreateNodeFrameAroundEverything();
@@ -52,7 +53,7 @@ internal class NodeGraphManagerViewModel : SubViewModel<ViewModelMain>
         Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.SetNodePosition(args.node, args.newPos);
     }
 
-    [Command.Internal("PixiEditor.NodeGraph.UpdateValue")]
+    [Command.Internal("PixiEditor.NodeGraph.UpdateValue", AnalyticsTrack = true)]
     public void UpdatePropertyValue((INodeHandler node, string property, object value) args)
     {
         Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.UpdatePropertyValue(args.node, args.property,

+ 2 - 2
src/PixiEditor/ViewModels/SubViewModels/SearchViewModel.cs

@@ -36,8 +36,8 @@ internal class SearchViewModel : SubViewModel<ViewModelMain>, ISearchHandler
     [Evaluator.CanExecute("PixiEditor.Search.CanOpenSearchWindow")]
     public bool CanToggleSearchWindow() => !ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Busy ?? true;
 
-    [Command.Basic("PixiEditor.Search.Toggle", "", "COMMAND_SEARCH", "OPEN_COMMAND_SEARCH", Key = Key.K, Modifiers = KeyModifiers.Control, CanExecute = "PixiEditor.Search.CanOpenSearchWindow")]
-    [Command.Basic("PixiEditor.Search.BrowseDirectory", "./", "BROWSE_DIRECTORY", "BROWSE_DIRECTORY", Key = Key.F, Modifiers = KeyModifiers.Control, CanExecute = "PixiEditor.Search.CanOpenSearchWindow")]
+    [Command.Basic("PixiEditor.Search.Toggle", "", "COMMAND_SEARCH", "OPEN_COMMAND_SEARCH", Key = Key.K, Modifiers = KeyModifiers.Control, CanExecute = "PixiEditor.Search.CanOpenSearchWindow", AnalyticsTrack = true)]
+    [Command.Basic("PixiEditor.Search.BrowseDirectory", "./", "BROWSE_DIRECTORY", "BROWSE_DIRECTORY", Key = Key.F, Modifiers = KeyModifiers.Control, CanExecute = "PixiEditor.Search.CanOpenSearchWindow", AnalyticsTrack = true)]
     public void ToggleSearchWindow(string searchTerm)
     {
         SelectAll = true;

+ 10 - 10
src/PixiEditor/ViewModels/SubViewModels/SelectionViewModel.cs

@@ -18,7 +18,7 @@ internal class SelectionViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Selection.SelectAll", "SELECT_ALL", "SELECT_ALL_DESCRIPTIVE", CanExecute = "PixiEditor.HasDocument", Key = Key.A, Modifiers = KeyModifiers.Control,
-        MenuItemPath = "SELECT/SELECT_ALL", MenuItemOrder = 8, Icon = PixiPerfectIcons.SelectAll)]
+        MenuItemPath = "SELECT/SELECT_ALL", MenuItemOrder = 8, Icon = PixiPerfectIcons.SelectAll, AnalyticsTrack = true)]
     public void SelectAll()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -28,7 +28,7 @@ internal class SelectionViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Selection.Clear", "CLEAR_SELECTION", "CLEAR_SELECTION", CanExecute = "PixiEditor.Selection.IsNotEmpty", Key = Key.D, Modifiers = KeyModifiers.Control,
-        MenuItemPath = "SELECT/DESELECT", MenuItemOrder = 9, Icon = PixiPerfectIcons.Deselect)]
+        MenuItemPath = "SELECT/DESELECT", MenuItemOrder = 9, Icon = PixiPerfectIcons.Deselect, AnalyticsTrack = true)]
     public void ClearSelection()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -38,7 +38,7 @@ internal class SelectionViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Selection.InvertSelection", "INVERT_SELECTION", "INVERT_SELECTION_DESCRIPTIVE", CanExecute = "PixiEditor.Selection.IsNotEmpty", Key = Key.I, Modifiers = KeyModifiers.Control,
-        MenuItemPath = "SELECT/INVERT", MenuItemOrder = 10, Icon = PixiPerfectIcons.Invert)]
+        MenuItemPath = "SELECT/INVERT", MenuItemOrder = 10, Icon = PixiPerfectIcons.Invert, AnalyticsTrack = true)]
     public void InvertSelection()
     {
         Owner.DocumentManagerSubViewModel.ActiveDocument?.Operations.InvertSelection();
@@ -57,7 +57,7 @@ internal class SelectionViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Selection.TransformArea", "TRANSFORM_SELECTED_AREA", "TRANSFORM_SELECTED_AREA", CanExecute = "PixiEditor.Selection.IsNotEmpty", 
-        Key = Key.T, Modifiers = KeyModifiers.Control)]
+        Key = Key.T, Modifiers = KeyModifiers.Control, AnalyticsTrack = true)]
     public void TransformSelectedArea()
     {
         Owner.DocumentManagerSubViewModel.ActiveDocument?.Operations.TransformSelectedArea(false);
@@ -74,14 +74,14 @@ internal class SelectionViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Selection.NewToMask", SelectionMode.New, "MASK_FROM_SELECTION", "MASK_FROM_SELECTION_DESCRIPTIVE", CanExecute = "PixiEditor.Selection.IsNotEmpty",
-        MenuItemPath = "SELECT/SELECTION_TO_MASK/TO_NEW_MASK", MenuItemOrder = 12, Icon = PixiPerfectIcons.NewMask)]
+        MenuItemPath = "SELECT/SELECTION_TO_MASK/TO_NEW_MASK", MenuItemOrder = 12, Icon = PixiPerfectIcons.NewMask, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Selection.AddToMask", SelectionMode.Add, "ADD_SELECTION_TO_MASK", "ADD_SELECTION_TO_MASK", CanExecute = "PixiEditor.Selection.IsNotEmpty",
-        MenuItemPath = "SELECT/SELECTION_TO_MASK/ADD_TO_MASK", MenuItemOrder = 13, Icon = PixiPerfectIcons.AddToMask)]
+        MenuItemPath = "SELECT/SELECTION_TO_MASK/ADD_TO_MASK", MenuItemOrder = 13, Icon = PixiPerfectIcons.AddToMask, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Selection.SubtractFromMask", SelectionMode.Subtract, "SUBTRACT_SELECTION_FROM_MASK", "SUBTRACT_SELECTION_FROM_MASK", CanExecute = "PixiEditor.Selection.IsNotEmptyAndHasMask",
-        MenuItemPath = "SELECT/SELECTION_TO_MASK/SUBTRACT_FROM_MASK", MenuItemOrder = 14, Icon = PixiPerfectIcons.Subtract)]
+        MenuItemPath = "SELECT/SELECTION_TO_MASK/SUBTRACT_FROM_MASK", MenuItemOrder = 14, Icon = PixiPerfectIcons.Subtract, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Selection.IntersectSelectionMask", SelectionMode.Intersect, "INTERSECT_SELECTION_MASK", "INTERSECT_SELECTION_MASK", CanExecute = "PixiEditor.Selection.IsNotEmptyAndHasMask",
-        MenuItemPath = "SELECT/SELECTION_TO_MASK/INTERSECT_WITH_MASK", MenuItemOrder = 15, Icon = PixiPerfectIcons.Intersect)]
-    [Command.Filter("PixiEditor.Selection.ToMaskMenu", "SELECTION_TO_MASK", "SELECTION_TO_MASK", Key = Key.M, Modifiers = KeyModifiers.Control)]
+        MenuItemPath = "SELECT/SELECTION_TO_MASK/INTERSECT_WITH_MASK", MenuItemOrder = 15, Icon = PixiPerfectIcons.Intersect, AnalyticsTrack = true)]
+    [Command.Filter("PixiEditor.Selection.ToMaskMenu", "SELECTION_TO_MASK", "SELECTION_TO_MASK", Key = Key.M, Modifiers = KeyModifiers.Control, AnalyticsTrack = true)]
     public void SelectionToMask(SelectionMode mode)
     {
         if (Owner.DocumentManagerSubViewModel.ActiveDocument is null)
@@ -91,7 +91,7 @@ internal class SelectionViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.Selection.CropToSelection", "CROP_TO_SELECTION", "CROP_TO_SELECTION_DESCRIPTIVE", CanExecute = "PixiEditor.Selection.IsNotEmpty",
-        MenuItemPath = "SELECT/CROP_TO_SELECTION", MenuItemOrder = 11, Icon = PixiPerfectIcons.CropToSelection)]
+        MenuItemPath = "SELECT/CROP_TO_SELECTION", MenuItemOrder = 11, Icon = PixiPerfectIcons.CropToSelection, AnalyticsTrack = true)]
     public void CropToSelection()
     {
         var document = Owner.DocumentManagerSubViewModel.ActiveDocument;

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/StylusViewModel.cs

@@ -48,7 +48,7 @@ internal class StylusViewModel : SubViewModel<ViewModelMain>
         UpdateUseTouchGesture();
     }
 
-    [Command.Basic("PixiEditor.Stylus.TogglePenMode", "TOGGLE_PEN_MODE", "TOGGLE_PEN_MODE", Icon = PixiPerfectIcons.Edit)]
+    [Command.Basic("PixiEditor.Stylus.TogglePenMode", "TOGGLE_PEN_MODE", "TOGGLE_PEN_MODE", Icon = PixiPerfectIcons.Edit, AnalyticsTrack = true)]
     public void TogglePenMode()
     {
         IsPenModeEnabled = !IsPenModeEnabled;

+ 3 - 3
src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs

@@ -120,7 +120,7 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         SetActiveTool(typeof(T), transient);
     }
 
-    [Command.Basic("PixiEditor.Tools.ApplyTransform", "APPLY_TRANSFORM", "", Key = Key.Enter)]
+    [Command.Basic("PixiEditor.Tools.ApplyTransform", "APPLY_TRANSFORM", "", Key = Key.Enter, AnalyticsTrack = true)]
     public void ApplyTransform()
     {
         DocumentViewModel? doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -203,8 +203,8 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         SetActiveTool(tool.GetType(), false);
     }
 
-    [Command.Basic("PixiEditor.Tools.IncreaseSize", 1, "INCREASE_TOOL_SIZE", "INCREASE_TOOL_SIZE", CanExecute = "PixiEditor.Tools.CanChangeToolSize", Key = Key.OemCloseBrackets)]
-    [Command.Basic("PixiEditor.Tools.DecreaseSize", -1, "DECREASE_TOOL_SIZE", "DECREASE_TOOL_SIZE", CanExecute = "PixiEditor.Tools.CanChangeToolSize", Key = Key.OemOpenBrackets)]
+    [Command.Basic("PixiEditor.Tools.IncreaseSize", 1, "INCREASE_TOOL_SIZE", "INCREASE_TOOL_SIZE", CanExecute = "PixiEditor.Tools.CanChangeToolSize", Key = Key.OemCloseBrackets, AnalyticsTrack = true)]
+    [Command.Basic("PixiEditor.Tools.DecreaseSize", -1, "DECREASE_TOOL_SIZE", "DECREASE_TOOL_SIZE", CanExecute = "PixiEditor.Tools.CanChangeToolSize", Key = Key.OemOpenBrackets, AnalyticsTrack = true)]
     public void ChangeToolSize(int increment)
     {
         if (ActiveTool?.Toolbar is not BasicToolbar toolbar)

+ 2 - 2
src/PixiEditor/ViewModels/SubViewModels/UndoViewModel.cs

@@ -17,7 +17,7 @@ internal class UndoViewModel : SubViewModel<ViewModelMain>
     ///     Redo last action.
     /// </summary>
     [Command.Basic("PixiEditor.Undo.Redo", "REDO", "REDO_DESCRIPTIVE", CanExecute = "PixiEditor.Undo.CanRedo", Key = Key.Y, Modifiers = KeyModifiers.Control,
-        Icon = PixiPerfectIcons.Redo, MenuItemPath = "EDIT/REDO", MenuItemOrder = 1)]
+        Icon = PixiPerfectIcons.Redo, MenuItemPath = "EDIT/REDO", MenuItemOrder = 1, AnalyticsTrack = true)]
     public void Redo()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -30,7 +30,7 @@ internal class UndoViewModel : SubViewModel<ViewModelMain>
     ///     Undo last action.
     /// </summary>
     [Command.Basic("PixiEditor.Undo.Undo", "UNDO", "UNDO_DESCRIPTIVE", CanExecute = "PixiEditor.Undo.CanUndo", Key = Key.Z, Modifiers = KeyModifiers.Control,
-        Icon = PixiPerfectIcons.Undo, MenuItemPath = "EDIT/UNDO", MenuItemOrder = 0)]
+        Icon = PixiPerfectIcons.Undo, MenuItemPath = "EDIT/UNDO", MenuItemOrder = 0, AnalyticsTrack = true)]
     public void Undo()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;

+ 2 - 2
src/PixiEditor/ViewModels/SubViewModels/ViewOptionsViewModel.cs

@@ -27,9 +27,9 @@ internal class ViewOptionsViewModel : SubViewModel<ViewModelMain>
     }
 
     [Command.Basic("PixiEditor.View.ZoomIn", 1, "ZOOM_IN", "ZOOM_IN", CanExecute = "PixiEditor.HasDocument", Key = Key.OemPlus,
-        Icon = PixiPerfectIcons.ZoomIn)]
+        Icon = PixiPerfectIcons.ZoomIn, AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.View.Zoomout", -1, "ZOOM_OUT", "ZOOM_OUT", CanExecute = "PixiEditor.HasDocument", Key = Key.OemMinus,
-        Icon = PixiPerfectIcons.ZoomOut)]
+        Icon = PixiPerfectIcons.ZoomOut, AnalyticsTrack = true)]
     public void ZoomViewport(double zoom)
     {
         ViewportWindowViewModel? viewport = Owner.WindowSubViewModel.ActiveWindow as ViewportWindowViewModel;

+ 8 - 8
src/PixiEditor/ViewModels/SubViewModels/WindowViewModel.cs

@@ -56,7 +56,7 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
 
     [Commands_Command.Basic("PixiEditor.Window.CreateNewViewport", "NEW_WINDOW_FOR_IMG", "NEW_WINDOW_FOR_IMG",
         Icon = PixiPerfectIcons.PlusSquare, CanExecute = "PixiEditor.HasDocument",
-        MenuItemPath = "VIEW/NEW_WINDOW_FOR_IMG", MenuItemOrder = 0)]
+        MenuItemPath = "VIEW/NEW_WINDOW_FOR_IMG", MenuItemOrder = 0, AnalyticsTrack = true)]
     public void CreateNewViewport()
     {
         var doc = ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument;
@@ -66,7 +66,7 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
     }
     
     [Commands_Command.Basic("PixiEditor.Window.CenterActiveViewport", "CENTER_ACTIVE_VIEWPORT", "CENTER_ACTIVE_VIEWPORT", CanExecute = "PixiEditor.HasDocument",
-        Icon = PixiPerfectIcons.Center)]
+        Icon = PixiPerfectIcons.Center, AnalyticsTrack = true)]
     public void CenterCurrentViewport()
     {
         if (ActiveWindow is ViewportWindowViewModel viewport)
@@ -74,7 +74,7 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
     }
     
     [Commands_Command.Basic("PixiEditor.Window.FlipHorizontally", "FLIP_VIEWPORT_HORIZONTALLY", "FLIP_VIEWPORT_HORIZONTALLY", CanExecute = "PixiEditor.HasDocument",
-        Icon = PixiPerfectIcons.YFlip)]
+        Icon = PixiPerfectIcons.YFlip, AnalyticsTrack = true)]
     public void FlipViewportHorizontally()
     {
         if (ActiveWindow is ViewportWindowViewModel viewport)
@@ -84,7 +84,7 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
     }
     
     [Commands_Command.Basic("PixiEditor.Window.FlipVertically", "FLIP_VIEWPORT_VERTICALLY", "FLIP_VIEWPORT_VERTICALLY", CanExecute = "PixiEditor.HasDocument",
-        Icon = PixiPerfectIcons.XFlip)]
+        Icon = PixiPerfectIcons.XFlip, AnalyticsTrack = true)]
     public void FlipViewportVertically()
     {
         if (ActiveWindow is ViewportWindowViewModel viewport)
@@ -157,7 +157,7 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
     }
 
     [Commands_Command.Basic("PixiEditor.Window.OpenSettingsWindow", "OPEN_SETTINGS", "OPEN_SETTINGS_DESCRIPTIVE", Key = Key.OemComma, Modifiers = KeyModifiers.Control,
-        MenuItemPath = "EDIT/SETTINGS", MenuItemOrder = 16, Icon = PixiPerfectIcons.Settings)]
+        MenuItemPath = "EDIT/SETTINGS", MenuItemOrder = 16, Icon = PixiPerfectIcons.Settings, AnalyticsTrack = true)]
     public static void OpenSettingsWindow(int page)
     {
         if (page < 0)
@@ -170,14 +170,14 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
     }
 
     [Commands_Command.Basic("PixiEditor.Window.OpenStartupWindow", "OPEN_STARTUP_WINDOW", "OPEN_STARTUP_WINDOW",
-        Icon = PixiPerfectIcons.Home, MenuItemPath = "VIEW/OPEN_STARTUP_WINDOW", MenuItemOrder = 1)]
+        Icon = PixiPerfectIcons.Home, MenuItemPath = "VIEW/OPEN_STARTUP_WINDOW", MenuItemOrder = 1, AnalyticsTrack = true)]
     public void OpenHelloThereWindow()
     {
         new HelloTherePopup(Owner.FileSubViewModel).Show(MainWindow.Current);
     }
 
     [Commands_Command.Basic("PixiEditor.Window.OpenShortcutWindow", "OPEN_SHORTCUT_WINDOW", "OPEN_SHORTCUT_WINDOW", Key = Key.F1,
-        Icon = PixiPerfectIcons.Book, MenuItemPath = "VIEW/OPEN_SHORTCUT_WINDOW", MenuItemOrder = 2)]
+        Icon = PixiPerfectIcons.Book, MenuItemPath = "VIEW/OPEN_SHORTCUT_WINDOW", MenuItemOrder = 2, AnalyticsTrack = true)]
     public void ShowShortcutWindow()
     {
         ShortcutsPopup.Show();
@@ -185,7 +185,7 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
     }
         
     [Commands_Command.Basic("PixiEditor.Window.OpenAboutWindow", "OPEN_ABOUT_WINDOW", "OPEN_ABOUT_WINDOW",
-        Icon = PixiPerfectIcons.Info, MenuItemPath = "HELP/ABOUT", MenuItemOrder = 5)]
+        Icon = PixiPerfectIcons.Info, MenuItemPath = "HELP/ABOUT", MenuItemOrder = 5, AnalyticsTrack = true)]
     public void OpenAboutWindow()
     {
         new AboutPopup().Show();