diff --git a/Pilz.Plugins.Advanced/Extensions.cs b/Pilz.Plugins.Advanced/Extensions.cs new file mode 100644 index 0000000..7b4a1b6 --- /dev/null +++ b/Pilz.Plugins.Advanced/Extensions.cs @@ -0,0 +1,62 @@ +using SM64RomManager.Functions; +using System.Drawing; +using System.Reflection; +using Telerik.WinControls; +using Telerik.WinControls.UI; + +namespace Pilz.Plugins.Advanced +{ + public static class Extensions + { + public static Icon? ToIcon(this Image image) + { + if (image is Bitmap bitmap) + return Icon.FromHandle(bitmap.GetHicon()); + return null; + } + + public static RadMenuItem GetAsItem(this PluginModule module, bool addDefaultHandler = true) + { + var item = new RadMenuItem + { + Text = module.Name, + SvgImage = module.Icon, + Tag = module, + Visibility = module.Visible ? ElementVisibility.Visible : ElementVisibility.Collapsed + }; + + if (addDefaultHandler) + item.Click += RadMenuItem_RMMethod_Click; + + return item; + } + + public static RadMenuItem GetAsItem(this PluginFunction function, bool addDefaultHandler = true) + { + var item = new RadMenuItem + { + Text = function.Name, + SvgImage = function.Icon, + Tag = function, + Visibility = function.Enabled ? ElementVisibility.Visible : ElementVisibility.Collapsed + }; + + if (addDefaultHandler) + item.Click += RadMenuItem_RMFunction_Click; + + return item; + } + + private static void RadMenuItem_RMMethod_Click(object? sender, EventArgs e) + { + if (sender is RadMenuItem item && item.Tag is PluginModule function) + function.ShowUI(); + } + + private static void RadMenuItem_RMFunction_Click(object? sender, EventArgs e) + { + if (sender is RadMenuItem item && item.Tag is PluginFunction function) + function.Execute(); + } + } +} \ No newline at end of file diff --git a/Pilz.Plugins.Advanced/Pilz.Plugins.Advanced.csproj b/Pilz.Plugins.Advanced/Pilz.Plugins.Advanced.csproj new file mode 100644 index 0000000..f8254b8 --- /dev/null +++ b/Pilz.Plugins.Advanced/Pilz.Plugins.Advanced.csproj @@ -0,0 +1,20 @@ + + + + net6.0-windows + enable + enable + true + + + + + + + + + + + + + diff --git a/Pilz.Plugins.Advanced/PluginFunction.cs b/Pilz.Plugins.Advanced/PluginFunction.cs new file mode 100644 index 0000000..d964f7f --- /dev/null +++ b/Pilz.Plugins.Advanced/PluginFunction.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Telerik.WinControls; + +namespace Pilz.Plugins.Advanced +{ + public abstract class PluginFunction + { + public string Type { get; init; } + public string? Name { get; init; } + public RadSvgImage? Icon { get; set; } + public bool Enabled { get; set; } = true; + + protected PluginFunction(string functionType) + { + Type = functionType; + } + + protected PluginFunction(string functionType, string functionName) : this(functionType) + { + Name = functionName; + } + + public object? Execute() + { + return Execute((PluginFunctionParameter?)null); + } + + public T? Execute(params object?[]? @params) + { + return Execute(new PluginFunctionSimpleParamter(@params)); + } + + public object? Execute(params object?[]? @params) + { + return Execute(new PluginFunctionSimpleParamter(@params)); + } + + public T? Execute(PluginFunctionSimpleParamter? @params) + { + if (Execute(@params) is T result) + return result; + return default; + } + + public object? Execute(PluginFunctionParameter? @params) + { +#if !DEBUG + try + { +#endif + return ExecuteFunction(@params); +#if !DEBUG + } + catch (Exception) + { + return null; + } +#endif + } + + protected abstract object? ExecuteFunction(PluginFunctionParameter? @params); + } +} diff --git a/Pilz.Plugins.Advanced/PluginFunctionController.cs b/Pilz.Plugins.Advanced/PluginFunctionController.cs new file mode 100644 index 0000000..8891810 --- /dev/null +++ b/Pilz.Plugins.Advanced/PluginFunctionController.cs @@ -0,0 +1,60 @@ +namespace Pilz.Plugins.Advanced +{ + public sealed class PluginFunctionController + { + public static PluginFunctionController Instance { get; private set; } = new(); + + private readonly List functions = new(); + + public IReadOnlyCollection Functions => functions.AsReadOnly(); + + public void RegisterFunction(PluginFunction function) + { + if (!functions.Contains(function)) + functions.Add(function); + } + + public void UnregisterFunction(PluginFunction function) + { + functions.Remove(function); + } + + public IEnumerable GetFunctions(string functionType) + { + return functions.Where(n => n.Type == functionType); + } + + public void ExecuteAll(string functionType) + { + foreach (var function in GetFunctions(functionType)) + function.Execute(); + } + + public void ExecuteAll(string functionType, params object?[]? @params) + { + foreach (var function in GetFunctions(functionType)) + function.Execute(@params); + } + + public void ExecuteAll(string functionType, PluginFunctionParameter @params) + { + foreach (var function in GetFunctions(functionType)) + function.Execute(@params); + } + + public IEnumerable ExcuteAndGetResults(string functionType) + { + return GetFunctions(functionType).Select(n => n.Execute()); + } + + public IEnumerable ExcuteAndGetResults(string functionType, params object?[]? @params) + { + return GetFunctions(functionType).Select(n => n.Execute(@params)); + } + + public IEnumerable ExcuteAndGetResults(string functionType, PluginFunctionParameter @params) + { + return GetFunctions(functionType).Select(n => n.Execute(@params)); + } + } +} \ No newline at end of file diff --git a/Pilz.Plugins.Advanced/PluginFunctionParameter.cs b/Pilz.Plugins.Advanced/PluginFunctionParameter.cs new file mode 100644 index 0000000..1c840fe --- /dev/null +++ b/Pilz.Plugins.Advanced/PluginFunctionParameter.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Plugins.Advanced +{ + public class PluginFunctionParameter + { + } +} diff --git a/Pilz.Plugins.Advanced/PluginFunctionSimpleParamter.cs b/Pilz.Plugins.Advanced/PluginFunctionSimpleParamter.cs new file mode 100644 index 0000000..c8fcb8a --- /dev/null +++ b/Pilz.Plugins.Advanced/PluginFunctionSimpleParamter.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Plugins.Advanced +{ + public sealed class PluginFunctionSimpleParamter : PluginFunctionParameter + { + public object?[]? Params { get; init; } + + public PluginFunctionSimpleParamter(params object?[]? @params) + { + Params = @params; + } + } +} diff --git a/Pilz.Plugins.Advanced/PluginModule.cs b/Pilz.Plugins.Advanced/PluginModule.cs new file mode 100644 index 0000000..3ea2101 --- /dev/null +++ b/Pilz.Plugins.Advanced/PluginModule.cs @@ -0,0 +1,40 @@ +using Pilz.UI.Telerik; +using Pilz.UI.Telerik.Dialogs; +using System.Drawing; +using System.Windows.Forms; +using Telerik.WinControls; + +namespace Pilz.Plugins.Advanced +{ + public abstract class PluginModule + { + public string Type { get; init; } + public string Name { get; init; } + public RadSvgImage? Icon { get; set; } + public bool Visible { get; set; } = true; + public bool RequiresRomManager { get; set; } = true; + public bool AllowEmbedding { get; set; } = true; + + protected PluginModule(string moduleType, string moduleName) + { + Type = moduleType; + Name = moduleName; + } + + public virtual void ShowUI() + { + if (CreateNewUI() is PluginModuleUI ui) + { + ui.BackColor = Color.Transparent; + DialogBaseForm.Show(ui, Name, Icon!.ToImage().ToIcon()!); + } + } + + public PluginModuleUI CreateUI() + { + return CreateNewUI(); + } + + protected abstract PluginModuleUI CreateNewUI(); + } +} \ No newline at end of file diff --git a/Pilz.Plugins.Advanced/PluginModuleController.cs b/Pilz.Plugins.Advanced/PluginModuleController.cs new file mode 100644 index 0000000..5706168 --- /dev/null +++ b/Pilz.Plugins.Advanced/PluginModuleController.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Z.Collections.Extensions; + +namespace Pilz.Plugins.Advanced +{ + public sealed class PluginModuleController + { + public static PluginModuleController Instance { get; private set; } = new(); + + private readonly List modules = new(); + + public IReadOnlyList Modules => modules.AsReadOnly(); + + public void RegisterModule(PluginModule module) + { + if (!modules.Contains(module)) + modules.Add(module); + } + + public void UnregisterModule(PluginModule module) + { + modules.Remove(module); + } + } +} diff --git a/Pilz.Plugins.Advanced/PluginModuleUI.cs b/Pilz.Plugins.Advanced/PluginModuleUI.cs new file mode 100644 index 0000000..43006ba --- /dev/null +++ b/Pilz.Plugins.Advanced/PluginModuleUI.cs @@ -0,0 +1,22 @@ +using Pilz.UI.Telerik.Dialogs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Pilz.Plugins.Advanced +{ + public class PluginModuleUI : FlyoutDialogBase, ILoadContent + { + public PluginModuleUI() + { + ActionPanelVisible = false; + } + + public virtual void LoadContent() + { + } + } +} diff --git a/Pilz.Plugins/IPlugin.cs b/Pilz.Plugins/IPlugin.cs new file mode 100644 index 0000000..bb97eb5 --- /dev/null +++ b/Pilz.Plugins/IPlugin.cs @@ -0,0 +1,7 @@ +namespace Pilz.Plugins +{ + public interface IPlugin + { + public string Name { get; } + } +} \ No newline at end of file diff --git a/Pilz.Plugins/Pilz.Plugins.csproj b/Pilz.Plugins/Pilz.Plugins.csproj new file mode 100644 index 0000000..132c02c --- /dev/null +++ b/Pilz.Plugins/Pilz.Plugins.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/Pilz.Plugins/PluginLoadContext.cs b/Pilz.Plugins/PluginLoadContext.cs new file mode 100644 index 0000000..0de14a0 --- /dev/null +++ b/Pilz.Plugins/PluginLoadContext.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Plugins +{ + internal class PluginLoadContext : AssemblyLoadContext + { + private readonly AssemblyDependencyResolver _resolver; + + public PluginLoadContext(string pluginPath) + { + _resolver = new AssemblyDependencyResolver(pluginPath); + } + + protected override Assembly? Load(AssemblyName assemblyName) + { + var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); + + if (assemblyPath != null) + return LoadFromAssemblyPath(assemblyPath); + + return null; + } + + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); + + if (libraryPath != null) + return LoadUnmanagedDllFromPath(libraryPath); + + return IntPtr.Zero; + } + } +} diff --git a/Pilz.Plugins/PluginManager.cs b/Pilz.Plugins/PluginManager.cs new file mode 100644 index 0000000..13bc26b --- /dev/null +++ b/Pilz.Plugins/PluginManager.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Plugins +{ + public class PluginManager : PluginManager + { + public static PluginManager Instance { get; private set; } = new(); + } +} diff --git a/Pilz.Plugins/PluginManagerT.cs b/Pilz.Plugins/PluginManagerT.cs new file mode 100644 index 0000000..d5ab0fb --- /dev/null +++ b/Pilz.Plugins/PluginManagerT.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Plugins +{ + public class PluginManager where TPluginInterface : class where TPluginRuntimeInfo : PluginRuntimeInfo + { + public static IEnumerable LoadPlugins(string[] paths) + { + var states = new List(); + + foreach (string path in paths) + states.Add(LoadPlugin(path)); + + return states; + } + + public static TPluginRuntimeInfo LoadPlugin(string path) + { + var info = Activator.CreateInstance(); + var irmplugin = typeof(TPluginInterface); + var loadContext = new PluginLoadContext(path); + Assembly? assembly = null; + + if (File.Exists(path)) + { + try + { + assembly = loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(path))); + } + catch + { + info.Status = PluginStatus.ErrorAtLoading; + } + } + else + info.Status = PluginStatus.FileNotFound; + + if (assembly != null) + { + foreach (var type in assembly.GetTypes()) + { + if (info.Plugin == null && irmplugin.IsAssignableFrom(type)) + { + try + { + if (Activator.CreateInstance(type) is TPluginInterface plugin) + { + info.Plugin = plugin; + info.Status = PluginStatus.Success; + } + } + catch + { + info.Status = PluginStatus.ErrorAtInitializing; + } + } + } + + if (info.Plugin == null) + info.Status = PluginStatus.NoValidPlugin; + } + + return info; + } + } +} diff --git a/Pilz.Plugins/PluginRuntimeInfo.cs b/Pilz.Plugins/PluginRuntimeInfo.cs new file mode 100644 index 0000000..c0d5264 --- /dev/null +++ b/Pilz.Plugins/PluginRuntimeInfo.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Plugins +{ + public class PluginRuntimeInfo : PluginRuntimeInfo + { + } +} diff --git a/Pilz.Plugins/PluginRuntimeInfoT.cs b/Pilz.Plugins/PluginRuntimeInfoT.cs new file mode 100644 index 0000000..a5f9f18 --- /dev/null +++ b/Pilz.Plugins/PluginRuntimeInfoT.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Plugins +{ + public class PluginRuntimeInfo where T : class + { + public T? Plugin { get; internal set; } + public PluginStatus Status { get; internal set; } + } +} diff --git a/Pilz.Plugins/PluginStatus.cs b/Pilz.Plugins/PluginStatus.cs new file mode 100644 index 0000000..2c6b908 --- /dev/null +++ b/Pilz.Plugins/PluginStatus.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Plugins +{ + public enum PluginStatus + { + None, + Success, + FileNotFound, + ErrorAtLoading, + ErrorAtInitializing, + NoValidPlugin + } +} diff --git a/Pilz.UI.Telerik.SymbolFactory/Extensions.cs b/Pilz.UI.Telerik.SymbolFactory/Extensions.cs new file mode 100644 index 0000000..161f12a --- /dev/null +++ b/Pilz.UI.Telerik.SymbolFactory/Extensions.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Telerik.WinControls; +using Telerik.WinControls.Svg; + +namespace Pilz.UI.Telerik +{ + public static class Extensions + { + public static Image ToImage(this RadSvgImage svg) + { + return svg?.Document.Draw(svg.Width, svg.Height); + } + + public static void ApplyColor(this RadSvgImage svg, Color color) + { + svg.Document.Fill = new SvgColourServer(color); + svg.ClearCache(); + } + } +} diff --git a/Pilz.UI.Telerik.SymbolFactory/SymbolFactory.cs b/Pilz.UI.Telerik.SymbolFactory/SymbolFactory.cs index dce2f64..a0280f2 100644 --- a/Pilz.UI.Telerik.SymbolFactory/SymbolFactory.cs +++ b/Pilz.UI.Telerik.SymbolFactory/SymbolFactory.cs @@ -52,10 +52,7 @@ namespace Pilz.UI.Telerik public virtual RadSvgImage GetSvgImageColored(TSvgSymbols svgImage, Size size, Color color) { var img = GetSvgImage(svgImage, size); - - img.Document.Fill = new SvgColourServer(color); - img.ClearCache(); - + img.ApplyColor(color); return img; } @@ -81,7 +78,7 @@ namespace Pilz.UI.Telerik public virtual Image GetImageFromSvg(RadSvgImage svg) { - return svg.Document.Draw(svg.Width, svg.Height); + return svg.ToImage(); } } } \ No newline at end of file diff --git a/Pilz.sln b/Pilz.sln index 1f05bbb..c05cbc6 100644 --- a/Pilz.sln +++ b/Pilz.sln @@ -35,7 +35,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.UI.Telerik.SymbolFacto EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.UI.Telerik", "Pilz.UI.Telerik\Pilz.UI.Telerik.csproj", "{DF674119-CC28-40AA-968F-1E23D184A491}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pilz.Networking.CloudProviders.Nextcloud", "Pilz.Networking.CloudProviders.Nextcloud\Pilz.Networking.CloudProviders.Nextcloud.csproj", "{A91E966B-3A82-4F32-A703-2FC1C7654FD1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.Networking.CloudProviders.Nextcloud", "Pilz.Networking.CloudProviders.Nextcloud\Pilz.Networking.CloudProviders.Nextcloud.csproj", "{A91E966B-3A82-4F32-A703-2FC1C7654FD1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pilz.Plugins", "Pilz.Plugins\Pilz.Plugins.csproj", "{1170FCA6-192D-42FE-A79F-49EE03035554}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pilz.Plugins.Advanced", "Pilz.Plugins.Advanced\Pilz.Plugins.Advanced.csproj", "{72153EC8-B297-4A94-80AA-3574544BE8CF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -181,6 +185,22 @@ Global {A91E966B-3A82-4F32-A703-2FC1C7654FD1}.Release|Any CPU.Build.0 = Release|Any CPU {A91E966B-3A82-4F32-A703-2FC1C7654FD1}.Release|x86.ActiveCfg = Release|Any CPU {A91E966B-3A82-4F32-A703-2FC1C7654FD1}.Release|x86.Build.0 = Release|Any CPU + {1170FCA6-192D-42FE-A79F-49EE03035554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1170FCA6-192D-42FE-A79F-49EE03035554}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1170FCA6-192D-42FE-A79F-49EE03035554}.Debug|x86.ActiveCfg = Debug|Any CPU + {1170FCA6-192D-42FE-A79F-49EE03035554}.Debug|x86.Build.0 = Debug|Any CPU + {1170FCA6-192D-42FE-A79F-49EE03035554}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1170FCA6-192D-42FE-A79F-49EE03035554}.Release|Any CPU.Build.0 = Release|Any CPU + {1170FCA6-192D-42FE-A79F-49EE03035554}.Release|x86.ActiveCfg = Release|Any CPU + {1170FCA6-192D-42FE-A79F-49EE03035554}.Release|x86.Build.0 = Release|Any CPU + {72153EC8-B297-4A94-80AA-3574544BE8CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72153EC8-B297-4A94-80AA-3574544BE8CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72153EC8-B297-4A94-80AA-3574544BE8CF}.Debug|x86.ActiveCfg = Debug|Any CPU + {72153EC8-B297-4A94-80AA-3574544BE8CF}.Debug|x86.Build.0 = Debug|Any CPU + {72153EC8-B297-4A94-80AA-3574544BE8CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72153EC8-B297-4A94-80AA-3574544BE8CF}.Release|Any CPU.Build.0 = Release|Any CPU + {72153EC8-B297-4A94-80AA-3574544BE8CF}.Release|x86.ActiveCfg = Release|Any CPU + {72153EC8-B297-4A94-80AA-3574544BE8CF}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE