Browse Source

Command controller optimization and general startup optimizations

CPKreuz 2 years ago
parent
commit
d448a43e24

+ 32 - 0
src/PixiEditor.sln

@@ -36,6 +36,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.DrawingApi.Core"
 EndProject
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.DrawingApi.Skia", "PixiEditor.DrawingApi.Skia\PixiEditor.DrawingApi.Skia.csproj", "{98040E8A-F08E-45F8-956F-6480C8272049}"
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.DrawingApi.Skia", "PixiEditor.DrawingApi.Skia\PixiEditor.DrawingApi.Skia.csproj", "{98040E8A-F08E-45F8-956F-6480C8272049}"
 EndProject
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditorGen", "PixiEditorGen\PixiEditorGen.csproj", "{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}"
+EndProject
 Global
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Debug|Any CPU = Debug|Any CPU
@@ -456,6 +458,36 @@ Global
 		{98040E8A-F08E-45F8-956F-6480C8272049}.Release|x64.Build.0 = Release|Any CPU
 		{98040E8A-F08E-45F8-956F-6480C8272049}.Release|x64.Build.0 = Release|Any CPU
 		{98040E8A-F08E-45F8-956F-6480C8272049}.Release|x86.ActiveCfg = Release|Any CPU
 		{98040E8A-F08E-45F8-956F-6480C8272049}.Release|x86.ActiveCfg = Release|Any CPU
 		{98040E8A-F08E-45F8-956F-6480C8272049}.Release|x86.Build.0 = Release|Any CPU
 		{98040E8A-F08E-45F8-956F-6480C8272049}.Release|x86.Build.0 = Release|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Debug|x64.Build.0 = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Debug|x86.Build.0 = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Dev Release|Any CPU.ActiveCfg = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Dev Release|Any CPU.Build.0 = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Dev Release|x64.ActiveCfg = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Dev Release|x64.Build.0 = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Dev Release|x86.ActiveCfg = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Dev Release|x86.Build.0 = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.MSIX Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.MSIX Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.MSIX Debug|x64.ActiveCfg = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.MSIX Debug|x64.Build.0 = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.MSIX Debug|x86.ActiveCfg = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.MSIX Debug|x86.Build.0 = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.MSIX|Any CPU.ActiveCfg = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.MSIX|Any CPU.Build.0 = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.MSIX|x64.ActiveCfg = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.MSIX|x64.Build.0 = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.MSIX|x86.ActiveCfg = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.MSIX|x86.Build.0 = Debug|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Release|Any CPU.Build.0 = Release|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Release|x64.ActiveCfg = Release|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Release|x64.Build.0 = Release|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Release|x86.ActiveCfg = Release|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE

+ 177 - 139
src/PixiEditor/Models/Commands/CommandController.cs

@@ -3,8 +3,6 @@ using System.Reflection;
 using System.Windows.Media;
 using System.Windows.Media;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.DependencyInjection;
 using Newtonsoft.Json;
 using Newtonsoft.Json;
-using PixiEditor.Models.Commands.Attributes;
-using PixiEditor.Models.Commands.Attributes.Evaluators;
 using PixiEditor.Models.Commands.Commands;
 using PixiEditor.Models.Commands.Commands;
 using PixiEditor.Models.Commands.Evaluators;
 using PixiEditor.Models.Commands.Evaluators;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
@@ -30,7 +28,7 @@ internal class CommandController
 
 
     public Dictionary<string, IconEvaluator> IconEvaluators { get; }
     public Dictionary<string, IconEvaluator> IconEvaluators { get; }
 
 
-    public CommandController(IServiceProvider services)
+    public CommandController()
     {
     {
         Current ??= this;
         Current ??= this;
 
 
@@ -66,7 +64,7 @@ internal class CommandController
         }
         }
     }
     }
 
 
-    private static List<(string internalName, string displayName)> FindCommandGroups(Type[] typesToSearchForAttributes)
+    private static List<(string internalName, string displayName)> FindCommandGroups(IEnumerable<Type> typesToSearchForAttributes)
     {
     {
         List<(string internalName, string displayName)> result = new();
         List<(string internalName, string displayName)> result = new();
 
 
@@ -109,76 +107,33 @@ internal class CommandController
             template = shortcutFile.LoadTemplate();
             template = shortcutFile.LoadTemplate();
             NoticeDialog.Show("Shortcuts file was corrupted, resetting to default.", "Corrupted shortcuts file");
             NoticeDialog.Show("Shortcuts file was corrupted, resetting to default.", "Corrupted shortcuts file");
         }
         }
-
-        Type[] allTypesInPixiEditorAssembly = typeof(CommandController).Assembly.GetTypes();
-
-        List<(string internalName, string displayName)> commandGroupsData = FindCommandGroups(allTypesInPixiEditorAssembly);
+        var compiledCommandList = new CommandNameList();
+        List<(string internalName, string displayName)> commandGroupsData = FindCommandGroups(compiledCommandList.Groups);
         OneToManyDictionary<string, Command> commands = new(); // internal name of the corr. group -> command in that group
         OneToManyDictionary<string, Command> commands = new(); // internal name of the corr. group -> command in that group
 
 
-        // Find evaluators
-        ForEachMethod(allTypesInPixiEditorAssembly, serviceProvider, (methodInfo, maybeServiceInstance) =>
-        {
-            var evaluatorAttrs = methodInfo.GetCustomAttributes<Evaluator.EvaluatorAttribute>();
-            foreach (var attribute in evaluatorAttrs)
-            {
-                switch (attribute)
-                {
-                    case Evaluator.CanExecuteAttribute canExecuteAttribute:
-                        {
-                            var getRequiredEvaluatorsObjectsOfCurrentEvaluator =
-                                (CommandController controller) =>
-                                    canExecuteAttribute.NamesOfRequiredCanExecuteEvaluators.Select(x => controller.CanExecuteEvaluators[x]);
-
-                            AddEvaluatorFactory<Evaluator.CanExecuteAttribute, CanExecuteEvaluator, bool>(
-                                methodInfo,
-                                maybeServiceInstance,
-                                canExecuteAttribute,
-                                CanExecuteEvaluators,
-                                evaluateFunction => new CanExecuteEvaluator()
-                                {
-                                    Name = attribute.Name,
-                                    Evaluate = evaluateFunctionArgument =>
-                                        evaluateFunction.Invoke(evaluateFunctionArgument) &&
-                                        getRequiredEvaluatorsObjectsOfCurrentEvaluator.Invoke(this).All(requiredEvaluator =>
-                                            requiredEvaluator.CallEvaluate(null, evaluateFunctionArgument))
-                                });
-                            break;
-                        }
-                    case Evaluator.IconAttribute icon:
-                        AddEvaluator<Evaluator.IconAttribute, IconEvaluator, ImageSource>(methodInfo, maybeServiceInstance, icon, IconEvaluators);
-                        break;
-                }
-            }
-        });
+        LoadEvaluators(serviceProvider, compiledCommandList);
+        LoadCommands(serviceProvider, compiledCommandList, commandGroupsData, commands, template);
+        LoadTools(serviceProvider, commandGroupsData, commands, template);
 
 
-        // Find basic commands
-        ForEachMethod(allTypesInPixiEditorAssembly, serviceProvider, (methodInfo, maybeServiceInstance) =>
+        foreach (var (groupInternalName, storedCommands) in commands)
         {
         {
-            var commandAttrs = methodInfo.GetCustomAttributes<CommandAttribute.CommandAttribute>();
-
-            foreach (var attribute in commandAttrs)
-            {
-                if (attribute is CommandAttribute.BasicAttribute basic)
-                {
-                    AddCommand(methodInfo, maybeServiceInstance, attribute, (isDebug, name, x, xCan, xIcon) => new Command.BasicCommand(x, xCan)
-                    {
-                        InternalName = name,
-                        IsDebug = isDebug,
-                        DisplayName = attribute.DisplayName,
-                        Description = attribute.Description,
-                        IconPath = attribute.IconPath,
-                        IconEvaluator = xIcon,
-                        DefaultShortcut = attribute.GetShortcut(),
-                        Shortcut = GetShortcut(name, attribute.GetShortcut()),
-                        Parameter = basic.Parameter,
-                    });
-                }
-            }
-        });
+            var groupData = commandGroupsData.FirstOrDefault(group => group.internalName == groupInternalName);
+            string groupDisplayName;
+            if (groupData == default)
+                groupDisplayName = "Misc";
+            else
+                groupDisplayName = groupData.displayName;
+            CommandGroups.Add(new(groupDisplayName, storedCommands));
+        }
+    }
 
 
-        // Find tool commands
-        foreach (var type in allTypesInPixiEditorAssembly)
+    private void LoadTools(IServiceProvider serviceProvider, List<(string internalName, string displayName)> commandGroupsData, OneToManyDictionary<string, Command> commands,
+        ShortcutsTemplate template)
+    {
+        foreach (var toolInstance in serviceProvider.GetServices<ToolViewModel>())
         {
         {
+            var type = toolInstance.GetType();
+
             if (!type.IsAssignableTo(typeof(ToolViewModel)))
             if (!type.IsAssignableTo(typeof(ToolViewModel)))
                 continue;
                 continue;
 
 
@@ -186,7 +141,6 @@ internal class CommandController
             if (toolAttr is null)
             if (toolAttr is null)
                 continue;
                 continue;
 
 
-            ToolViewModel toolInstance = serviceProvider.GetServices<ToolViewModel>().First(x => x.GetType() == type);
             string internalName = $"PixiEditor.Tools.Select.{type.Name}";
             string internalName = $"PixiEditor.Tools.Select.{type.Name}";
 
 
             var command = new Command.ToolCommand()
             var command = new Command.ToolCommand()
@@ -198,86 +152,65 @@ internal class CommandController
                 IconEvaluator = IconEvaluator.Default,
                 IconEvaluator = IconEvaluator.Default,
                 TransientKey = toolAttr.Transient,
                 TransientKey = toolAttr.Transient,
                 DefaultShortcut = toolAttr.GetShortcut(),
                 DefaultShortcut = toolAttr.GetShortcut(),
-                Shortcut = GetShortcut(internalName, toolAttr.GetShortcut()),
+                Shortcut = GetShortcut(internalName, toolAttr.GetShortcut(), template),
                 ToolType = type,
                 ToolType = type,
             };
             };
 
 
             Commands.Add(command);
             Commands.Add(command);
-            AddCommandToCommandsCollection(command);
-        }
-
-        // save all commands into CommandGroups
-        foreach (var (groupInternalName, storedCommands) in commands)
-        {
-            var groupData = commandGroupsData.Where(group => group.internalName == groupInternalName).FirstOrDefault();
-            string groupDisplayName;
-            if (groupData == default)
-                groupDisplayName = "Misc";
-            else
-                groupDisplayName = groupData.displayName;
-            CommandGroups.Add(new(groupDisplayName, storedCommands));
+            AddCommandToCommandsCollection(command, commandGroupsData, commands);
         }
         }
+    }
 
 
-        KeyCombination GetShortcut(string internalName, KeyCombination defaultShortcut)
-            => template.Shortcuts.FirstOrDefault(x => x.Commands.Contains(internalName), new Shortcut(defaultShortcut, (List<string>)null)).KeyCombination;
+    private KeyCombination GetShortcut(string internalName, KeyCombination defaultShortcut, ShortcutsTemplate template) =>
+        template.Shortcuts
+            .FirstOrDefault(x => x.Commands.Contains(internalName), new Shortcut(defaultShortcut, (List<string>)null))
+            .KeyCombination;
 
 
-        void AddCommandToCommandsCollection(Command command)
-        {
-            (string internalName, string displayName) group = commandGroupsData.FirstOrDefault(x => command.InternalName.StartsWith(x.internalName));
-            if (group == default)
-                commands.Add("", command);
-            else
-                commands.Add(group.internalName, command);
-        }
-
-        void AddEvaluator<TAttr, T, TParameter>(MethodInfo method, object instance, TAttr attribute, IDictionary<string, T> evaluators)
-            where T : Evaluator<TParameter>, new()
-            where TAttr : Evaluator.EvaluatorAttribute
-            => AddEvaluatorFactory<TAttr, T, TParameter>(method, instance, attribute, evaluators, x => new T() { Name = attribute.Name, Evaluate = x });
+    private void AddCommandToCommandsCollection(Command command, List<(string internalName, string displayName)> commandGroupsData, OneToManyDictionary<string, Command> commands)
+    {
+        (string internalName, string displayName) group = commandGroupsData.FirstOrDefault(x => command.InternalName.StartsWith(x.internalName));
+        if (group == default)
+            commands.Add("", command);
+        else
+            commands.Add(group.internalName, command);
+    }
 
 
-        void AddEvaluatorFactory<TAttr, T, TParameter>(MethodInfo method, object serviceInstance, TAttr attribute, IDictionary<string, T> evaluators, Func<Func<object, TParameter>, T> factory)
-            where T : Evaluator<TParameter>, new()
-            where TAttr : Evaluator.EvaluatorAttribute
+    private void LoadCommands(IServiceProvider serviceProvider, CommandNameList compiledCommandList, List<(string internalName, string displayName)> commandGroupsData, OneToManyDictionary<string, Command> commands, ShortcutsTemplate template)
+    {
+        foreach (var type in compiledCommandList.Commands)
         {
         {
-            if (method.ReturnType != typeof(TParameter))
+            foreach (var methodNames in type.Value)
             {
             {
-                throw new Exception($"Invalid return type for the CanExecute evaluator '{attribute.Name}' at {method.ReflectedType.FullName}.{method.Name}\nExpected '{typeof(TParameter).FullName}'");
-            }
-            else if (method.GetParameters().Length > 1)
-            {
-                throw new Exception($"Too many parameters for the CanExecute evaluator '{attribute.Name}' at {method.ReflectedType.FullName}.{method.Name}");
-            }
-            else if (!method.IsStatic && serviceInstance is null)
-            {
-                throw new Exception($"No type instance for the CanExecute evaluator '{attribute.Name}' at {method.ReflectedType.FullName}.{method.Name} found");
-            }
+                var name = methodNames.Item1;
 
 
-            var parameters = method.GetParameters();
+                var methodInfo = type.Key.GetMethod(name, methodNames.Item2.ToArray());
 
 
-            Func<object, TParameter> func;
+                var commandAttrs = methodInfo.GetCustomAttributes<CommandAttribute.CommandAttribute>();
 
 
-            if (parameters.Length == 1)
-            {
-                func = x => (TParameter)method.Invoke(serviceInstance, new[] { CastParameter(x, parameters[0].ParameterType) });
-            }
-            else
-            {
-                func = x => (TParameter)method.Invoke(serviceInstance, null);
+                foreach (var attribute in commandAttrs)
+                {
+                    if (attribute is CommandAttribute.BasicAttribute basic)
+                    {
+                        AddCommand(methodInfo, serviceProvider.GetService(type.Key), attribute,
+                            (isDebug, name, x, xCan, xIcon) => new Command.BasicCommand(x, xCan)
+                            {
+                                InternalName = name,
+                                IsDebug = isDebug,
+                                DisplayName = attribute.DisplayName,
+                                Description = attribute.Description,
+                                IconPath = attribute.IconPath,
+                                IconEvaluator = xIcon,
+                                DefaultShortcut = attribute.GetShortcut(),
+                                Shortcut = GetShortcut(name, attribute.GetShortcut(), template),
+                                Parameter = basic.Parameter,
+                            });
+                    }
+                }
             }
             }
-
-            T evaluator = factory(func);
-
-            evaluators.Add(evaluator.Name, evaluator);
         }
         }
-
-        object CastParameter(object input, Type target)
-        {
-            if (target == typeof(object) || target == input?.GetType())
-                return input;
-            return Convert.ChangeType(input, target);
-        }
-
-        TCommand AddCommand<TAttr, TCommand>(MethodInfo method, object instance, TAttr attribute, Func<bool, string, Action<object>, CanExecuteEvaluator, IconEvaluator, TCommand> commandFactory)
+        
+        void AddCommand<TAttr, TCommand>(MethodInfo method, object instance, TAttr attribute,
+            Func<bool, string, Action<object>, CanExecuteEvaluator, IconEvaluator, TCommand> commandFactory)
             where TAttr : CommandAttribute.CommandAttribute
             where TAttr : CommandAttribute.CommandAttribute
             where TCommand : Command
             where TCommand : Command
         {
         {
@@ -285,11 +218,13 @@ internal class CommandController
             {
             {
                 if (method.GetParameters().Length > 1)
                 if (method.GetParameters().Length > 1)
                 {
                 {
-                    throw new Exception($"Too many parameters for the CanExecute evaluator '{attribute.InternalName}' at {method.ReflectedType.FullName}.{method.Name}");
+                    throw new Exception(
+                        $"Too many parameters for the CanExecute evaluator '{attribute.InternalName}' at {method.ReflectedType.FullName}.{method.Name}");
                 }
                 }
                 else if (!method.IsStatic && instance is null)
                 else if (!method.IsStatic && instance is null)
                 {
                 {
-                    throw new Exception($"No type instance for the CanExecute evaluator '{attribute.InternalName}' at {method.ReflectedType.FullName}.{method.Name} found");
+                    throw new Exception(
+                        $"No type instance for the CanExecute evaluator '{attribute.InternalName}' at {method.ReflectedType.FullName}.{method.Name} found");
                 }
                 }
             }
             }
 
 
@@ -297,7 +232,7 @@ internal class CommandController
 
 
             Action<object> action;
             Action<object> action;
 
 
-            if (parameters == null || parameters.Length != 1)
+            if (parameters is not { Length: 1 })
             {
             {
                 action = x => method.Invoke(instance, null);
                 action = x => method.Invoke(instance, null);
             }
             }
@@ -322,9 +257,112 @@ internal class CommandController
                 attribute.IconEvaluator != null ? IconEvaluators[attribute.IconEvaluator] : IconEvaluator.Default);
                 attribute.IconEvaluator != null ? IconEvaluators[attribute.IconEvaluator] : IconEvaluator.Default);
 
 
             Commands.Add(command);
             Commands.Add(command);
-            AddCommandToCommandsCollection(command);
+            AddCommandToCommandsCollection(command, commandGroupsData, commands);
+        }
+    }
+
+    private void LoadEvaluators(IServiceProvider serviceProvider, CommandNameList compiledCommandList)
+    {
+        object CastParameter(object input, Type target)
+        {
+            if (target == typeof(object) || target == input?.GetType())
+                return input;
+            return Convert.ChangeType(input, target);
+        }
+
+        void AddEvaluatorFactory<TAttr, T, TParameter>(MethodInfo method, object serviceInstance, TAttr attribute,
+            IDictionary<string, T> evaluators, Func<Func<object, TParameter>, T> factory)
+            where T : Evaluator<TParameter>, new()
+            where TAttr : Evaluator.EvaluatorAttribute
+        {
+            if (method.ReturnType != typeof(TParameter))
+            {
+                throw new Exception(
+                    $"Invalid return type for the CanExecute evaluator '{attribute.Name}' at {method.ReflectedType.FullName}.{method.Name}\nExpected '{typeof(TParameter).FullName}'");
+            }
+            else if (method.GetParameters().Length > 1)
+            {
+                throw new Exception(
+                    $"Too many parameters for the CanExecute evaluator '{attribute.Name}' at {method.ReflectedType.FullName}.{method.Name}");
+            }
+            else if (!method.IsStatic && serviceInstance is null)
+            {
+                throw new Exception(
+                    $"No type instance for the CanExecute evaluator '{attribute.Name}' at {method.ReflectedType.FullName}.{method.Name} found");
+            }
 
 
-            return command;
+            var parameters = method.GetParameters();
+
+            Func<object, TParameter> func;
+
+            if (parameters.Length == 1)
+            {
+                func = x => (TParameter)method.Invoke(serviceInstance,
+                    new[] { CastParameter(x, parameters[0].ParameterType) });
+            }
+            else
+            {
+                func = x => (TParameter)method.Invoke(serviceInstance, null);
+            }
+
+            T evaluator = factory(func);
+
+            evaluators.Add(evaluator.Name, evaluator);
+        }
+
+        void AddEvaluator<TAttr, T, TParameter>(MethodInfo method, object instance, TAttr attribute,
+            IDictionary<string, T> evaluators)
+            where T : Evaluator<TParameter>, new()
+            where TAttr : Evaluator.EvaluatorAttribute
+            => AddEvaluatorFactory<TAttr, T, TParameter>(method, instance, attribute, evaluators,
+                x => new T() { Name = attribute.Name, Evaluate = x });
+
+        {
+            foreach (var type in compiledCommandList.Evaluators)
+            {
+                foreach (var methodNames in type.Value)
+                {
+                    var name = methodNames.Item1;
+
+                    var methodInfo = type.Key.GetMethod(name, methodNames.Item2.ToArray());
+
+                    var commandAttrs = methodInfo.GetCustomAttributes<Evaluator.EvaluatorAttribute>();
+
+                    foreach (var attribute in commandAttrs)
+                    {
+                        switch (attribute)
+                        {
+                            case Evaluator.CanExecuteAttribute canExecuteAttribute:
+                            {
+                                var getRequiredEvaluatorsObjectsOfCurrentEvaluator =
+                                    (CommandController controller) =>
+                                        canExecuteAttribute.NamesOfRequiredCanExecuteEvaluators.Select(x =>
+                                            controller.CanExecuteEvaluators[x]);
+
+                                AddEvaluatorFactory<Evaluator.CanExecuteAttribute, CanExecuteEvaluator, bool>(
+                                    methodInfo,
+                                    serviceProvider.GetService(type.Key),
+                                    canExecuteAttribute,
+                                    CanExecuteEvaluators,
+                                    evaluateFunction => new CanExecuteEvaluator()
+                                    {
+                                        Name = attribute.Name,
+                                        Evaluate = evaluateFunctionArgument =>
+                                            evaluateFunction.Invoke(evaluateFunctionArgument) &&
+                                            getRequiredEvaluatorsObjectsOfCurrentEvaluator.Invoke(this).All(
+                                                requiredEvaluator =>
+                                                    requiredEvaluator.CallEvaluate(null, evaluateFunctionArgument))
+                                    });
+                                break;
+                            }
+                            case Evaluator.IconAttribute icon:
+                                AddEvaluator<Evaluator.IconAttribute, IconEvaluator, ImageSource>(methodInfo,
+                                    serviceProvider.GetService(type.Key), icon, IconEvaluators);
+                                break;
+                        }
+                    }
+                }
+            }
         }
         }
     }
     }
 
 

+ 28 - 0
src/PixiEditor/Models/Commands/CommandNameList.cs

@@ -0,0 +1,28 @@
+using System.Reflection;
+
+namespace PixiEditor.Models.Commands;
+
+internal partial class CommandNameList
+{
+    partial void AddCommands();
+
+    partial void AddEvaluators();
+
+    partial void AddGroups();
+    
+    public Dictionary<Type, List<(string, Type[])>> Commands { get; }
+    
+    public Dictionary<Type, List<(string, Type[])>> Evaluators { get; }
+    
+    public List<Type> Groups { get; }
+
+    public CommandNameList()
+    {
+        Commands = new();
+        Evaluators = new();
+        Groups = new();
+        AddCommands();
+        AddEvaluators();
+        AddGroups();
+    }
+}

+ 3 - 8
src/PixiEditor/Models/Commands/XAML/ShortcutBinding.cs

@@ -18,17 +18,12 @@ internal class ShortcutBinding : MarkupExtension
 
 
     public override object ProvideValue(IServiceProvider serviceProvider)
     public override object ProvideValue(IServiceProvider serviceProvider)
     {
     {
-        //if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
-        //{
+#if DEBUG
         var attribute = DesignCommandHelpers.GetCommandAttribute(Name);
         var attribute = DesignCommandHelpers.GetCommandAttribute(Name);
         return new KeyCombination(attribute.Key, attribute.Modifiers).ToString();
         return new KeyCombination(attribute.Key, attribute.Modifiers).ToString();
-        //}
-
-        if (commandController == null)
-        {
-            commandController = ViewModelMain.Current.CommandController;
-        }
+#endif
 
 
+        commandController ??= ViewModelMain.Current.CommandController;
         return GetBinding(commandController.Commands[Name]).ProvideValue(serviceProvider);
         return GetBinding(commandController.Commands[Name]).ProvideValue(serviceProvider);
     }
     }
 
 

+ 1 - 0
src/PixiEditor/PixiEditor.csproj

@@ -349,6 +349,7 @@
 		<ProjectReference Include="..\PixiEditor.DrawingApi.Skia\PixiEditor.DrawingApi.Skia.csproj" />
 		<ProjectReference Include="..\PixiEditor.DrawingApi.Skia\PixiEditor.DrawingApi.Skia.csproj" />
 		<ProjectReference Include="..\PixiEditor.UpdateModule\PixiEditor.UpdateModule.csproj" />
 		<ProjectReference Include="..\PixiEditor.UpdateModule\PixiEditor.UpdateModule.csproj" />
 		<ProjectReference Include="..\PixiEditor.Zoombox\PixiEditor.Zoombox.csproj" />
 		<ProjectReference Include="..\PixiEditor.Zoombox\PixiEditor.Zoombox.csproj" />
+		<ProjectReference Include="..\PixiEditorGen\PixiEditorGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"  />
 	</ItemGroup>
 	</ItemGroup>
 	<ItemGroup>
 	<ItemGroup>
 		<Reference Include="PixiParser">
 		<Reference Include="PixiParser">

+ 6 - 0
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentManagerViewModel.cs

@@ -4,6 +4,7 @@ using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Events;
 using PixiEditor.Models.Events;
+using PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 using PixiEditor.Views.UserControls.SymmetryOverlay;
 using PixiEditor.Views.UserControls.SymmetryOverlay;
 
 
 namespace PixiEditor.ViewModels.SubViewModels.Document;
 namespace PixiEditor.ViewModels.SubViewModels.Document;
@@ -28,6 +29,11 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>
             activeDocument = value;
             activeDocument = value;
             RaisePropertyChanged(nameof(ActiveDocument));
             RaisePropertyChanged(nameof(ActiveDocument));
             ActiveDocumentChanged?.Invoke(this, new(value, prevDoc));
             ActiveDocumentChanged?.Invoke(this, new(value, prevDoc));
+            
+            if (ViewModelMain.Current.ToolsSubViewModel.ActiveTool == null)
+            {
+                ViewModelMain.Current.ToolsSubViewModel.SetActiveTool<PenToolViewModel>();
+            }
         }
         }
     }
     }
 
 

+ 0 - 1
src/PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs

@@ -59,7 +59,6 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>
     public void SetupTools(IServiceProvider services)
     public void SetupTools(IServiceProvider services)
     {
     {
         ToolSet = services.GetServices<ToolViewModel>().ToList();
         ToolSet = services.GetServices<ToolViewModel>().ToList();
-        SetActiveTool<PenToolViewModel>();
     }
     }
 
 
     public void SetupToolsTooltipShortcuts(IServiceProvider services)
     public void SetupToolsTooltipShortcuts(IServiceProvider services)

+ 1 - 1
src/PixiEditor/ViewModels/ViewModelMain.cs

@@ -82,7 +82,7 @@ internal class ViewModelMain : ViewModelBase
                 return actionDisplay;
                 return actionDisplay;
             }
             }
 
 
-            return ToolsSubViewModel.ActiveTool.ActionDisplay;
+            return ToolsSubViewModel.ActiveTool?.ActionDisplay;
         }
         }
         set
         set
         {
         {

+ 189 - 0
src/PixiEditorGen/CommandNameListGenerator.cs

@@ -0,0 +1,189 @@
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace PixiEditorGen;
+
+[Generator(LanguageNames.CSharp)]
+public class CommandNameListGenerator : IIncrementalGenerator
+{
+    private const string Command = "PixiEditor.Models.Commands.Attributes.Commands";
+
+    private const string Evaluators = "PixiEditor.Models.Commands.Attributes.Evaluators.Evaluator";
+    
+    public void Initialize(IncrementalGeneratorInitializationContext context)
+    {
+        var commandList = context.SyntaxProvider.CreateSyntaxProvider(
+            (x, token) =>
+        {
+            return x is MethodDeclarationSyntax method && method.AttributeLists.Count > 0;
+        }, static (context, cancelToken) =>
+        {
+            var method = (MethodDeclarationSyntax)context.Node;
+
+            if (!HasCommandAttribute(method, context, cancelToken, Command))
+                return (null, null, null);
+
+            var symbol = context.SemanticModel.GetDeclaredSymbol(method, cancelToken);
+
+            if (symbol is IMethodSymbol methodSymbol)
+            {
+                if (methodSymbol.ReceiverType == null)
+                    return (null, null, null);
+                
+                return (methodSymbol.ReceiverType.ToDisplayString(), methodSymbol.Name, methodSymbol.Parameters.Select(x => x.ToDisplayString()));
+            }
+            else
+            {
+                return (null, null, null);
+            }
+        }).Where(x => x.Item1 != null);
+
+        var evaluatorList = context.SyntaxProvider.CreateSyntaxProvider(
+            (x, token) =>
+            {
+                return x is MethodDeclarationSyntax method && method.AttributeLists.Count > 0;
+            }, static (context, cancelToken) =>
+            {
+                var method = (MethodDeclarationSyntax)context.Node;
+
+                if (!HasCommandAttribute(method, context, cancelToken, Evaluators))
+                    return (null, null, null);
+
+                var symbol = context.SemanticModel.GetDeclaredSymbol(method, cancelToken);
+
+                if (symbol is IMethodSymbol methodSymbol)
+                {
+                    return (methodSymbol.ReceiverType.ToDisplayString(), methodSymbol.Name, methodSymbol.Parameters.Select(x => x.ToDisplayString()));
+                }
+                else
+                {
+                    return (null, null, null);
+                }
+            }).Where(x => x.Item1 != null);
+        
+        var groupList = context.SyntaxProvider.CreateSyntaxProvider(
+            (x, token) =>
+            {
+                return x is MethodDeclarationSyntax method && method.AttributeLists.Count > 0;
+            }, static (context, cancelToken) =>
+            {
+                var method = (MethodDeclarationSyntax)context.Node;
+
+                if (!HasCommandAttribute(method, context, cancelToken, Evaluators))
+                    return null;
+
+                var symbol = context.SemanticModel.GetDeclaredSymbol(method, cancelToken);
+
+                if (symbol is ITypeSymbol methodSymbol)
+                {
+                    return methodSymbol.ToDisplayString();
+                }
+                else
+                {
+                    return null;
+                }
+            }).Where(x => x != null);
+
+        context.RegisterSourceOutput(commandList.Collect(), static (context, methodNames) =>
+        {
+            var code = new StringBuilder(
+                @"namespace PixiEditor.Models.Commands;
+
+internal partial class CommandNameList {
+    partial void AddCommands() {");
+
+            List<string> createdClasses = new List<string>();
+
+            foreach (var method in methodNames)
+            {
+                if (!createdClasses.Contains(method.Item1))
+                {
+                    code.AppendLine($"      Commands.Add(typeof({method.Item1}), new());");
+                    createdClasses.Add(method.Item1);
+                }
+
+                var parameters = string.Join(",", method.Item3.Select(x => $"typeof({x})"));
+                
+                code.AppendLine($"      Commands[typeof({method.Item1})].Add((\"{method.Item2}\", new Type[] {{ {parameters} }}));");
+            }
+
+            code.Append("   }\n}");
+
+            context.AddSource("CommandNameList+Commands", code.ToString());
+        });
+        
+        context.RegisterSourceOutput(evaluatorList.Collect(), static (context, methodNames) =>
+        {
+            var code = new StringBuilder(
+                @"namespace PixiEditor.Models.Commands;
+
+internal partial class CommandNameList {
+    partial void AddEvaluators() {");
+
+            List<string> createdClasses = new List<string>();
+
+            foreach (var method in methodNames)
+            {
+                if (!createdClasses.Contains(method.Item1))
+                {
+                    code.AppendLine($"      Evaluators.Add(typeof({method.Item1}), new());");
+                    createdClasses.Add(method.Item1);
+                }
+
+                if (method.Item3 == null || !method.Item3.Any())
+                {
+                    code.AppendLine($"      Evaluators[typeof({method.Item1})].Add((\"{method.Item2}\", Array.Empty<Type>()));");
+                }
+                else
+                {
+                    var parameters = string.Join(",", method.Item3.Select(x => $"typeof({x})"));
+                
+                    code.AppendLine($"      Evaluators[typeof({method.Item1})].Add((\"{method.Item2}\", new Type[] {{ {parameters} }}));");
+                }
+            }
+
+            code.Append("   }\n}");
+
+            context.AddSource("CommandNameList+Evaluators", code.ToString());
+        });
+        
+        context.RegisterSourceOutput(groupList.Collect(), static (context, typeNames) =>
+        {
+            var code = new StringBuilder(
+                @"namespace PixiEditor.Models.Commands;
+
+internal partial class CommandNameList {
+    partial void AddGroups() {");
+
+            foreach (var name in typeNames)
+            {
+                code.AppendLine($"      Groups.Add(typeof({name}));");
+            }
+
+            code.Append("   }\n}");
+
+            context.AddSource("CommandNameList+Groups", code.ToString());
+        });
+    }
+    
+    private static bool HasCommandAttribute(MethodDeclarationSyntax method, GeneratorSyntaxContext context, CancellationToken token, string commandAttributeStart)
+    {
+        foreach (var attrList in method.AttributeLists)
+        {
+            foreach (var attribute in attrList.Attributes)
+            {
+                token.ThrowIfCancellationRequested();
+                var symbol = context.SemanticModel.GetSymbolInfo(attribute, token);
+                if (symbol.Symbol is not IMethodSymbol methodSymbol)
+                    continue;
+                if (!methodSymbol.ContainingType.ToDisplayString()
+                    .StartsWith(commandAttributeStart))
+                    continue;
+                return true;
+            }
+        }
+
+        return false;
+    }
+}

+ 18 - 0
src/PixiEditorGen/PixiEditorGen.csproj

@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <IncludeBuildOutput>false</IncludeBuildOutput>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <LangVersion>latest</LangVersion>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0"/>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true"
+          PackagePath="analyzers/dotnet/cs" Visible="false"/>
+  </ItemGroup>
+</Project>