Ver Fonte

Finished menu items

Krzysztof Krysiński há 1 ano atrás
pai
commit
b857b2feab

+ 2 - 0
src/PixiEditor.AvaloniaUI/Models/Commands/Attributes/Commands/GroupAttribute.cs

@@ -11,6 +11,8 @@ internal partial class Command
 
         public LocalizedString DisplayName { get; }
 
+        public string? IsVisibleMenuProperty { get; set; }
+
         /// <summary>
         /// Groups all commands that start with the name <paramref name="internalName"/>
         /// </summary>

+ 17 - 14
src/PixiEditor.AvaloniaUI/Models/Commands/CommandController.cs

@@ -82,15 +82,15 @@ internal class CommandController
         }
     }
 
-    private static List<(string internalName, LocalizedString displayName)> FindCommandGroups(IEnumerable<Type> typesToSearchForAttributes)
+    private static List<CommandAttribute.GroupAttribute> FindCommandGroups(IEnumerable<Type> typesToSearchForAttributes)
     {
-        List<(string internalName, LocalizedString displayName)> result = new();
+        List<CommandAttribute.GroupAttribute> result = new();
 
         foreach (var type in typesToSearchForAttributes)
         {
             foreach (var group in type.GetCustomAttributes<CommandAttribute.GroupAttribute>())
             {
-                result.Add((group.InternalName, group.DisplayName));
+                result.Add(group);
             }
         }
 
@@ -120,13 +120,13 @@ internal class CommandController
         }
         catch (JsonException)
         {
-            File.Move(shortcutFile.Path, $"{shortcutFile.Path}.corrupted", true);
+            File.Move(shortcutFile.Path, $"{shortcutFile.Path}.corrupted", true);  // TODO: platform dependent
             shortcutFile = new ShortcutFile(ShortcutsPath, this);
             template = shortcutFile.LoadTemplate();
             NoticeDialog.Show("SHORTCUTS_CORRUPTED", "SHORTCUTS_CORRUPTED_TITLE");
         }
         var compiledCommandList = new CommandNameList();
-        List<(string internalName, LocalizedString displayName)> commandGroupsData = FindCommandGroups(compiledCommandList.Groups);
+        List<CommandAttribute.GroupAttribute> commandGroupsData = FindCommandGroups(compiledCommandList.Groups);
         OneToManyDictionary<string, Command> commands = new(); // internal name of the corr. group -> command in that group
 
         LoadEvaluators(serviceProvider, compiledCommandList);
@@ -137,15 +137,18 @@ internal class CommandController
 
         foreach (var (groupInternalName, storedCommands) in commands)
         {
-            var groupData = commandGroupsData.FirstOrDefault(group => group.internalName == groupInternalName);
-            if (groupData == default || groupData.internalName == "PixiEditor.Links")
+            var groupData = commandGroupsData.FirstOrDefault(group => group.InternalName == groupInternalName);
+            if (groupData == default || groupData.InternalName == "PixiEditor.Links")
             {
                 miscList.AddRange(storedCommands);
                 continue;
             }
 
-            LocalizedString groupDisplayName = groupData.displayName;
-            CommandGroups.Add(new CommandGroup(groupDisplayName, storedCommands));
+            LocalizedString groupDisplayName = groupData.DisplayName;
+            CommandGroups.Add(new CommandGroup(groupDisplayName, storedCommands)
+            {
+                IsVisibleProperty = groupData.IsVisibleMenuProperty
+            } );
         }
         
         CommandGroups.Add(new CommandGroup("MISC", miscList));
@@ -173,7 +176,7 @@ internal class CommandController
         }
     }
 
-    private void LoadTools(IServiceProvider serviceProvider, List<(string internalName, LocalizedString displayName)> commandGroupsData, OneToManyDictionary<string, Command> commands,
+    private void LoadTools(IServiceProvider serviceProvider, List<CommandAttribute.GroupAttribute> commandGroupsData, OneToManyDictionary<string, Command> commands,
         ShortcutsTemplate template)
     {
         IToolsHandler toolsHandler = serviceProvider.GetService<IToolsHandler>();
@@ -215,16 +218,16 @@ internal class CommandController
             .FirstOrDefault(x => x.Commands.Contains(internalName), new Shortcut(defaultShortcut, (List<string>)null))
             .KeyCombination;
 
-    private void AddCommandToCommandsCollection(Command command, List<(string internalName, LocalizedString displayName)> commandGroupsData, OneToManyDictionary<string, Command> commands)
+    private void AddCommandToCommandsCollection(Command command, List<CommandAttribute.GroupAttribute> commandGroupsData, OneToManyDictionary<string, Command> commands)
     {
-        (string internalName, string displayName) group = commandGroupsData.FirstOrDefault(x => command.InternalName.StartsWith(x.internalName));
+        var group = commandGroupsData.FirstOrDefault(x => command.InternalName.StartsWith(x.InternalName));
         if (group == default)
             commands.Add("", command);
         else
-            commands.Add(group.internalName, command);
+            commands.Add(group.InternalName, command);
     }
 
-    private void LoadCommands(IServiceProvider serviceProvider, CommandNameList compiledCommandList, List<(string internalName, LocalizedString displayName)> commandGroupsData, OneToManyDictionary<string, Command> commands, ShortcutsTemplate template)
+    private void LoadCommands(IServiceProvider serviceProvider, CommandNameList compiledCommandList, List<CommandAttribute.GroupAttribute> commandGroupsData, OneToManyDictionary<string, Command> commands, ShortcutsTemplate template)
     {
         foreach (var type in compiledCommandList.Commands)
         {

+ 3 - 0
src/PixiEditor.AvaloniaUI/Models/Commands/CommandGroup.cs

@@ -3,6 +3,7 @@ using System.Linq;
 using Avalonia.Input;
 using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.AvaloniaUI.Models.Commands.Commands;
+using PixiEditor.AvaloniaUI.Models.Commands.Evaluators;
 using PixiEditor.AvaloniaUI.Models.Input;
 using PixiEditor.Extensions.Common.Localization;
 
@@ -23,6 +24,8 @@ internal class CommandGroup : ObservableObject
 
     public bool HasAssignedShortcuts { get; set; }
 
+    public string? IsVisibleProperty { get; set; }
+
     public IEnumerable<Command> Commands => commands;
 
     public IEnumerable<Command> VisibleCommands => visibleCommands;

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/Commands/Evaluators/CanExecuteEvaluator.cs

@@ -7,7 +7,7 @@ internal class CanExecuteEvaluator : Evaluator<bool>
     public static CanExecuteEvaluator AlwaysTrue { get; } = new StaticValueEvaluator(true);
 
     public static CanExecuteEvaluator AlwaysFalse { get; } = new StaticValueEvaluator(false);
-    public string[]? DependentOn { get; set; }
+    public string[]? DependentOn { get; set; } // TODO: It is used in CanExecuteChanged event, but it's commented out because it might not impact performance
 
     private class StaticValueEvaluator : CanExecuteEvaluator
     {

+ 18 - 6
src/PixiEditor.AvaloniaUI/ViewModels/Menu/MenuBarViewModel.cs

@@ -2,7 +2,9 @@
 using System.Collections.ObjectModel;
 using System.Linq;
 using System.Windows.Input;
+using Avalonia;
 using Avalonia.Controls;
+using Avalonia.Data;
 using Microsoft.Extensions.DependencyInjection;
 using PixiEditor.AvaloniaUI.Models.Commands;
 using PixiEditor.AvaloniaUI.Models.Commands.XAML;
@@ -41,7 +43,7 @@ internal class MenuBarViewModel : PixiObservableObject
            BuildMenuEntry(command);
         }
 
-        BuildMenu(builders);
+        BuildMenu(controller, builders);
     }
 
     private int GetCategoryMultiplier(Command command)
@@ -55,16 +57,16 @@ internal class MenuBarViewModel : PixiObservableObject
         return argMenuItemPath.Split('/').Length > 1;
     }
 
-    private void BuildMenu(MenuItemBuilder[] builders)
+    private void BuildMenu(CommandController controller, MenuItemBuilder[] builders)
     {
-        BuildSimpleItems(menuItems);
+        BuildSimpleItems(controller, menuItems);
         foreach (var builder in builders)
         {
             builder.ModifyMenuTree(MenuEntries);
         }
     }
 
-    private void BuildSimpleItems(Dictionary<string, MenuTreeItem> root, MenuItem? parent = null)
+    private void BuildSimpleItems(CommandController controller, Dictionary<string, MenuTreeItem> root, MenuItem? parent = null)
     {
         string? lastSubCommand = null;
 
@@ -75,12 +77,22 @@ internal class MenuBarViewModel : PixiObservableObject
                 Header = new LocalizedString(item.Key),
             };
 
+            CommandGroup? group = controller.CommandGroups.FirstOrDefault(x => x.IsVisibleProperty != null && x.Commands.Contains(item.Value.Command));
+
+            if (group != null)
+            {
+                menuItem.Bind(Visual.IsVisibleProperty, new Binding(group.IsVisibleProperty)
+                {
+                    Source = ViewModelMain.Current,
+                });
+            }
+
             if (item.Value.Items.Count == 0)
             {
                 Models.Commands.XAML.Menu.SetCommand(menuItem, item.Value.Command.InternalName);
 
                 string internalName = item.Value.Command.InternalName;
-                internalName = internalName.Substring(0, internalName.LastIndexOf('.'));
+                internalName = internalName[..internalName.LastIndexOf('.')];
 
                 if (lastSubCommand != null && lastSubCommand != internalName)
                 {
@@ -108,7 +120,7 @@ internal class MenuBarViewModel : PixiObservableObject
                 {
                     MenuEntries.Add(menuItem);
                 }
-                BuildSimpleItems(item.Value.Items, menuItem);
+                BuildSimpleItems(controller, item.Value.Items, menuItem);
             }
         }
     }

+ 29 - 14
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/DebugViewModel.cs

@@ -10,6 +10,7 @@ using Avalonia.Platform.Storage;
 using Newtonsoft.Json;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
 using PixiEditor.AvaloniaUI.Models.Commands.Attributes.Commands;
+using PixiEditor.AvaloniaUI.Models.Commands.Attributes.Evaluators;
 using PixiEditor.AvaloniaUI.Models.Commands.Templates.Providers.Parsers;
 using PixiEditor.AvaloniaUI.Models.Dialogs;
 using PixiEditor.AvaloniaUI.Views;
@@ -21,7 +22,7 @@ using PixiEditor.OperatingSystem;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.SubViewModels;
 
-[Command.Group("PixiEditor.Debug", "DEBUG")]
+[Command.Group("PixiEditor.Debug", "DEBUG", IsVisibleMenuProperty = $"{nameof(ViewModelMain.DebugSubViewModel)}.{nameof(UseDebug)}")]
 internal class DebugViewModel : SubViewModel<ViewModelMain>
 {
     public static bool IsDebugBuild { get; set; }
@@ -29,6 +30,7 @@ internal class DebugViewModel : SubViewModel<ViewModelMain>
     public bool IsDebugModeEnabled { get; set; }
 
     private bool useDebug;
+
     public bool UseDebug
     {
         get => useDebug;
@@ -85,22 +87,26 @@ internal class DebugViewModel : SubViewModel<ViewModelMain>
     }
     
 
-    [Command.Debug("PixiEditor.Debug.OpenLocalAppDataDirectory", @"PixiEditor", "OPEN_LOCAL_APPDATA_DIR", "OPEN_LOCAL_APPDATA_DIR", IconPath = "Folder.png")]
-    [Command.Debug("PixiEditor.Debug.OpenCrashReportsDirectory", @"PixiEditor\crash_logs", "OPEN_CRASH_REPORTS_DIR", "OPEN_CRASH_REPORTS_DIR", IconPath = "Folder.png")]
+    [Command.Debug("PixiEditor.Debug.IO.OpenLocalAppDataDirectory", @"PixiEditor", "OPEN_LOCAL_APPDATA_DIR", "OPEN_LOCAL_APPDATA_DIR", IconPath = "Folder.png",
+        MenuItemPath = "DEBUG/OPEN_LOCAL_APPDATA_DIR", MenuItemOrder = 3)]
+    [Command.Debug("PixiEditor.Debug.IO.OpenCrashReportsDirectory", @"PixiEditor\crash_logs", "OPEN_CRASH_REPORTS_DIR", "OPEN_CRASH_REPORTS_DIR", IconPath = "Folder.png",
+        MenuItemPath = "DEBUG/OPEN_CRASH_REPORTS_DIR", MenuItemOrder = 4)]
     public static void OpenLocalAppDataFolder(string subDirectory)
     {
         var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), subDirectory);
         OpenFolder(path);
     }
 
-    [Command.Debug("PixiEditor.Debug.OpenRoamingAppDataDirectory", @"PixiEditor", "OPEN_ROAMING_APPDATA_DIR", "OPEN_ROAMING_APPDATA_DIR", IconPath = "Folder.png")]
+    [Command.Debug("PixiEditor.Debug.IO.OpenRoamingAppDataDirectory", @"PixiEditor", "OPEN_ROAMING_APPDATA_DIR", "OPEN_ROAMING_APPDATA_DIR", IconPath = "Folder.png",
+        MenuItemPath = "DEBUG/OPEN_ROAMING_APPDATA_DIR", MenuItemOrder = 5)]
     public static void OpenAppDataFolder(string subDirectory)
     {
         var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), subDirectory);
         OpenFolder(path);
     }
 
-    [Command.Debug("PixiEditor.Debug.OpenTempDirectory", @"PixiEditor", "OPEN_TEMP_DIR", "OPEN_TEMP_DIR", IconPath = "Folder.png")]
+    [Command.Debug("PixiEditor.Debug.IO.OpenTempDirectory", @"PixiEditor", "OPEN_TEMP_DIR", "OPEN_TEMP_DIR", IconPath = "Folder.png",
+        MenuItemPath = "DEBUG/OPEN_TEMP_DIR", MenuItemOrder = 6)]
     public static void OpenTempFolder(string subDirectory)
     {
         var path = Path.Combine(Path.GetTempPath(), subDirectory);
@@ -207,26 +213,30 @@ internal class DebugViewModel : SubViewModel<ViewModelMain>
         });
     }
 
-    [Command.Debug("PixiEditor.Debug.ClearRecentDocument", "CLEAR_RECENT_DOCUMENTS", "CLEAR_RECENTLY_OPENED_DOCUMENTS")]
+    [Command.Debug("PixiEditor.Debug.ClearRecentDocument", "CLEAR_RECENT_DOCUMENTS", "CLEAR_RECENTLY_OPENED_DOCUMENTS",
+        MenuItemPath = "DEBUG/DELETE/CLEAR_RECENT_DOCUMENTS")]
     public void ClearRecentDocuments()
     {
         Owner.FileSubViewModel.RecentlyOpened.Clear();
         IPreferences.Current.UpdateLocalPreference(PreferencesConstants.RecentlyOpened, Array.Empty<object>());
     }
 
-    [Command.Debug("PixiEditor.Debug.OpenCommandDebugWindow", "OPEN_CMD_DEBUG_WINDOW", "OPEN_CMD_DEBUG_WINDOW")]
+    [Command.Debug("PixiEditor.Debug.OpenCommandDebugWindow", "OPEN_CMD_DEBUG_WINDOW", "OPEN_CMD_DEBUG_WINDOW",
+        MenuItemPath = "DEBUG/OPEN_COMMAND_DEBUG_WINDOW", MenuItemOrder = 0)]
     public void OpenCommandDebugWindow()
     {
         new CommandDebugPopup().Show();
     }
 
-    [Command.Debug("PixiEditor.Debug.OpenPointerDebugWindow", "Open pointer debug window", "Open pointer debug window")]
+    [Command.Debug("PixiEditor.Debug.OpenPointerDebugWindow", "Open pointer debug window", "Open pointer debug window",
+        MenuItemPath = "DEBUG/Open pointer debug window", MenuItemOrder = 1)]
     public void OpenPointerDebugWindow()
     {
         new PointerDebugPopup().Show();
     }
 
-    [Command.Debug("PixiEditor.Debug.OpenLocalizationDebugWindow", "OPEN_LOCALIZATION_DEBUG_WINDOW", "OPEN_LOCALIZATION_DEBUG_WINDOW")]
+    [Command.Debug("PixiEditor.Debug.OpenLocalizationDebugWindow", "OPEN_LOCALIZATION_DEBUG_WINDOW", "OPEN_LOCALIZATION_DEBUG_WINDOW",
+        MenuItemPath = "DEBUG/OPEN_LOCALIZATION_DEBUG_WINDOW", MenuItemOrder = 2)]
     public void OpenLocalizationDebugWindow()
     {
         if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
@@ -263,18 +273,23 @@ internal class DebugViewModel : SubViewModel<ViewModelMain>
         });
     }
 
-    [Command.Debug("PixiEditor.Debug.OpenInstallDirectory", "OPEN_INSTALLATION_DIR", "OPEN_INSTALLATION_DIR", IconPath = "Folder.png")]
+    [Command.Debug("PixiEditor.Debug.IO.OpenInstallDirectory", "OPEN_INSTALLATION_DIR", "OPEN_INSTALLATION_DIR", IconPath = "Folder.png",
+        MenuItemPath = "DEBUG/OPEN_INSTALLATION_DIR", MenuItemOrder = 8)]
     public static void OpenInstallLocation()
     {
         IOperatingSystem.Current.OpenFolder(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
     }
 
-    [Command.Debug("PixiEditor.Debug.Crash", "CRASH", "CRASH_APP")]
+    [Command.Debug("PixiEditor.Debug.Crash", "CRASH", "CRASH_APP",
+        MenuItemPath = "DEBUG/CRASH", MenuItemOrder = 9)]
     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")]
-    [Command.Debug("PixiEditor.Debug.DeleteShortcutFile", @"%appdata%\PixiEditor\shortcuts.json", "DELETE_SHORTCUT_FILE", "DELETE_SHORTCUT_FILE")]
-    [Command.Debug("PixiEditor.Debug.DeleteEditorData", @"%localappdata%\PixiEditor\editor_data.json", "DELETE_EDITOR_DATA", "DELETE_EDITOR_DATA")]
+    [Command.Debug("PixiEditor.Debug.DeleteUserPreferences", @"%appdata%\PixiEditor\user_preferences.json", "DELETE_USR_PREFS", "DELETE_USR_PREFS",
+        MenuItemPath = "DEBUG/DELETE/USER_PREFS", MenuItemOrder = 10)]
+    [Command.Debug("PixiEditor.Debug.DeleteShortcutFile", @"%appdata%\PixiEditor\shortcuts.json", "DELETE_SHORTCUT_FILE", "DELETE_SHORTCUT_FILE",
+        MenuItemPath = "DEBUG/DELETE/SHORTCUT_FILE", MenuItemOrder = 11)]
+    [Command.Debug("PixiEditor.Debug.DeleteEditorData", @"%localappdata%\PixiEditor\editor_data.json", "DELETE_EDITOR_DATA", "DELETE_EDITOR_DATA",
+        MenuItemPath = "DEBUG/DELETE/EDITOR_DATA", MenuItemOrder = 12)]
     public static async Task DeleteFile(string path)
     {
         if (MainWindow.Current is null)

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

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

+ 2 - 1
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/WindowViewModel.cs

@@ -180,7 +180,8 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
         PalettesBrowser.Open();
     }
         
-    [Command.Basic("PixiEditor.Window.OpenAboutWindow", "OPEN_ABOUT_WINDOW", "OPEN_ABOUT_WINDOW")]
+    [Command.Basic("PixiEditor.Window.OpenAboutWindow", "OPEN_ABOUT_WINDOW", "OPEN_ABOUT_WINDOW",
+        MenuItemPath = "HELP/ABOUT", MenuItemOrder = 5)]
     public void OpenAboutWindow()
     {
         new AboutPopup().Show();

+ 3 - 5
src/PixiEditorGen/CommandNameListGenerator.cs

@@ -38,12 +38,10 @@ public class CommandNameListGenerator : IIncrementalGenerator
             {
                 if (typeof(T) == typeof(CommandMethod))
                 {
-                    return x is MethodDeclarationSyntax method && method.AttributeLists.Count > 0;
-                }
-                else
-                {
-                    return x is TypeDeclarationSyntax type && type.AttributeLists.Count > 0;
+                    return x is MethodDeclarationSyntax { AttributeLists.Count: > 0 };
                 }
+
+                return x is TypeDeclarationSyntax { AttributeLists.Count: > 0 };
             }, (context, cancelToken) =>
             {
                 var member = (MemberDeclarationSyntax)context.Node;