From e43d632adb780656703170497365c89f0348751a Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Mon, 8 Jan 2018 20:41:21 +0100 Subject: [PATCH] Added module (the first major plugin type) view(model) creation --- src/Artemis.Core/Artemis.Core.csproj | 7 +- .../Exceptions/ArtemisPluginException.cs | 2 +- src/Artemis.Core/Scripting.Extensions.cs | 295 --------------- src/Artemis.Core/Scripting.evaluator.cs | 345 ----------------- src/Artemis.Core/Scripting.native.cs | 354 ------------------ .../Services/Interfaces/IPluginService.cs | 2 +- src/Artemis.Core/Services/PluginService.cs | 10 +- src/Artemis.Core/packages.config | 1 + .../Abstract/PluginViewModel.cs | 2 + src/Artemis.Plugins/Artemis.Plugins.csproj | 5 + .../Interfaces/IDataModelExpansion.cs | 10 + src/Artemis.Plugins/Interfaces/IDevice.cs | 4 + src/Artemis.Plugins/Interfaces/ILayerType.cs | 4 + src/Artemis.Plugins/Interfaces/IModule.cs | 4 + src/Artemis.Plugins/Interfaces/IPlugin.cs | 10 +- .../Interfaces/IPluginViewModel.cs | 8 +- .../Models/PluginInfo.cs | 2 +- src/Artemis.Plugins/packages.config | 1 + src/Artemis.UI/Artemis.UI.csproj | 2 + src/Artemis.UI/Ninject/UiModule.cs | 13 +- .../Services/Interfaces/IArtemisUIService.cs | 8 + src/Artemis.UI/Stylet/ArtemisViewManager.cs | 46 +++ src/Artemis.UI/Stylet/NinjectBootstrapper.cs | 2 +- src/Artemis.UI/ViewModels/HomeViewModel.cs | 20 - .../Interfaces/IArtemisViewModel.cs | 6 +- src/Artemis.UI/ViewModels/RootViewModel.cs | 47 ++- 26 files changed, 172 insertions(+), 1038 deletions(-) delete mode 100644 src/Artemis.Core/Scripting.Extensions.cs delete mode 100644 src/Artemis.Core/Scripting.evaluator.cs delete mode 100644 src/Artemis.Core/Scripting.native.cs create mode 100644 src/Artemis.Plugins/Interfaces/IDataModelExpansion.cs rename src/{Artemis.Core => Artemis.Plugins}/Models/PluginInfo.cs (96%) create mode 100644 src/Artemis.UI/Services/Interfaces/IArtemisUIService.cs create mode 100644 src/Artemis.UI/Stylet/ArtemisViewManager.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index 399048bda..74c96f047 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -104,6 +104,9 @@ ..\packages\Sanford.Multimedia.Midi.6.4.2\lib\net20\Sanford.Multimedia.Midi.dll + + ..\packages\Stylet.1.1.21\lib\net45\Stylet.dll + ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll @@ -184,12 +187,8 @@ - - - - diff --git a/src/Artemis.Core/Exceptions/ArtemisPluginException.cs b/src/Artemis.Core/Exceptions/ArtemisPluginException.cs index 0282b0295..a2c96d709 100644 --- a/src/Artemis.Core/Exceptions/ArtemisPluginException.cs +++ b/src/Artemis.Core/Exceptions/ArtemisPluginException.cs @@ -1,5 +1,5 @@ using System; -using Artemis.Core.Models; +using Artemis.Plugins.Models; namespace Artemis.Core.Exceptions { diff --git a/src/Artemis.Core/Scripting.Extensions.cs b/src/Artemis.Core/Scripting.Extensions.cs deleted file mode 100644 index 070536442..000000000 --- a/src/Artemis.Core/Scripting.Extensions.cs +++ /dev/null @@ -1,295 +0,0 @@ -using CSScriptLibrary; -using System; -using System.Diagnostics; -using System.Reflection; -using System.Runtime.Remoting.Lifetime; -using System.Threading; -using System.Threading.Tasks; - -// Read in more details about all aspects of CS-Script hosting in applications -// here: http://www.csscript.net/help/Script_hosting_guideline_.html -// -// This file contains samples for the script hosting scenarios requiring asynchronous script execution as well as unloading the -// scripts being executed. -// AsyncSamples -// Samples demonstrate the use of Async and Await mechanism available in C# 5 and higher. Note that the async method extensions -// cover the complete set of CSScript.Evaluator methods. -// -// UnloadingSamples -// Samples demonstrate the use of temporary AppDoamain for loading and executing dynamic C# code (script). It is the -// only mechanism available for unloading dynamically loaded assemblies. This is a well known CLR design limitation that leads to -// memory leaks if the assembly/script loaded in the caller AppDomain. The problem affects all C# script engines (e.g. Roslyn, CodeDom) -// and it cannot be solved by the engine itself thus CS-Script provides a work around in form of the MethodExtensions for the -// CSScript.Evaluator methods that are compatible with the unloading mechanism. -// -// Nevertheless you should try to avoid using remote AppDoamain unless you have to. It is very heavy and also imposes the serialization -// constrains. -// -// All samples rely on the compiler agnostic CSScript.Evaluator API. -namespace CSScriptEvaluatorExtensions -{ - public class HostApp - { - public static void Test() - { - Console.WriteLine("---------------------------------------------"); - Console.WriteLine("Testing asynchronous API"); - Console.WriteLine("---------------------------------------------"); - new AsyncSamples().RunAll(); - Thread.Sleep(2000); - Console.WriteLine("\nPress 'Enter' to run uloading samples..."); - Console.ReadLine(); - Console.WriteLine("---------------------------------------------"); - Console.WriteLine("Testing unloading API"); - Console.WriteLine("---------------------------------------------"); - new UnloadingSamples().RunAll(); - } - - class AsyncSamples - { - public void RunAll() - { - Action run = (action, name) => { action(); Console.WriteLine(name); }; - - run(LoadDelegateAsync, "Start of " + nameof(LoadDelegateAsync)); - run(LoadMethodAsync, "Start of " + nameof(LoadMethodAsync)); - run(LoadCodeAsync, "Start of " + nameof(LoadCodeAsync)); - run(CreateDelegateAsync, "Start of " + nameof(CreateDelegateAsync)); - run(CompileCodeAsync, "Start of " + nameof(CompileCodeAsync)); - run(RemoteAsynch, "Start of " + nameof(RemoteAsynch)); - } - - async void LoadDelegateAsync() - { - var product = await CSScript.Evaluator - .LoadDelegateAsync>( - @"int Product(int a, int b) - { - return a * b; - }"); - - Console.WriteLine(" End of {0}: {1}", nameof(LoadDelegateAsync), product(4, 2)); - } - - public async void LoadMethodAsync() - { - dynamic script = await CSScript.Evaluator - .LoadMethodAsync(@"public int Sum(int a, int b) - { - return a + b; - } - public int Div(int a, int b) - { - return a/b; - }"); - - Console.WriteLine(" End of {0}: {1}", nameof(LoadMethodAsync), script.Div(15, 3)); - } - - public async void LoadCodeAsync() - { - //This use-case uses Interface Alignment and this requires all assemblies involved to have - //non-empty Assembly.Location - CSScript.GlobalSettings.InMemoryAssembly = false; - - ICalc calc = await CSScript.Evaluator - .LoadCodeAsync( - @"using System; - public class Script - { - public int Sum(int a, int b) - { - return a+b; - } - }"); - - Console.WriteLine(" End of {0}: {1}", nameof(LoadCodeAsync), calc.Sum(1, 2)); - } - - public async void CreateDelegateAsync() - { - var product = await CSScript.Evaluator - .CreateDelegateAsync( - @"int Product(int a, int b) - { - return a * b; - }"); - - Console.WriteLine(" End of {0}: {1}", nameof(CreateDelegateAsync), product(15, 3)); - } - - public async void CompileCodeAsync() - { - Assembly script = await CSScript.Evaluator - .CompileCodeAsync(@"using System; - public class Script - { - public int Sum(int a, int b) - { - return a+b; - } - }"); - dynamic calc = script.CreateObject("*"); - - Console.WriteLine(" End of {0}: {1}", nameof(CompileCodeAsync), calc.Sum(15, 3)); - } - - public async void RemoteAsynch() - { - var sum = await Task.Run(() => - CSScript.Evaluator - .CreateDelegateRemotely( - @"int Sum(int a, int b) - { - return a+b; - }") - ); - Console.WriteLine(" End of {0}: {1}", nameof(RemoteAsynch), sum(1, 2)); - - sum.UnloadOwnerDomain(); - } - } - - class UnloadingSamples - { - public void RunAll() - { - CreateDelegateRemotely(); - LoadMethodRemotely(); - LoadCodeRemotely(); - LoadCodeRemotelyWithInterface(); - } - - public void CreateDelegateRemotely() - { - var sum = CSScript.Evaluator - .CreateDelegateRemotely(@"int Sum(int a, int b) - { - return a+b; - }"); - - Console.WriteLine("{0}: {1}", nameof(CreateDelegateRemotely), sum(15, 3)); - - sum.UnloadOwnerDomain(); - } - - public void LoadCodeRemotely() - { - // Class Calc doesn't implement ICals interface. Thus the compiled object cannot be typecasted into - // the interface and Evaluator will emit duck-typed assembly instead. - // But Mono and Roslyn build file-less assemblies, meaning that they cannot be used to build - // duck-typed proxies and CodeDomEvaluator needs to be used explicitly. - // Note class Calc also inherits from MarshalByRefObject. This is required for all object that - // are passed between AppDomain: they must inherit from MarshalByRefObject or be serializable. - - //This use-case uses Interface Alignment and this requires all assemblies involved to have - //non-empty Assembly.Location - CSScript.GlobalSettings.InMemoryAssembly = false; - - var script = CSScript.CodeDomEvaluator - .LoadCodeRemotely( - @"using System; - public class Calc : MarshalByRefObject - { - object t; - public int Sum(int a, int b) - { - t = new Test(); - return a+b; - } - } - - class Test - { - ~Test() - { - Console.WriteLine(""Domain is unloaded: ~Test()""); - } - } - "); - - Console.WriteLine("{0}: {1}", nameof(LoadCodeRemotely), script.Sum(15, 3)); - - script.UnloadOwnerDomain(); - } - - public void LoadCodeRemotelyWithInterface() - { - // Note class Calc also inherits from MarshalByRefObject. This is required for all object that - // are passed between AppDomain: they must inherit from MarshalByRefObject or be serializable. - var script = CSScript.Evaluator - .LoadCodeRemotely( - @"using System; - public class Calc : MarshalByRefObject, CSScriptEvaluatorExtensions.ICalc - { - public int Sum(int a, int b) - { - return a+b; - } - } - "); - - Console.WriteLine("{0}: {1}", nameof(LoadCodeRemotelyWithInterface), script.Sum(15, 3)); - - script.UnloadOwnerDomain(); - } - - public void LoadMethodRemotely() - { - // LoadMethodRemotely is essentially the same as LoadCodeRemotely. It just deals not with the - // whole class definition but a single method(s) only. And the rest of the class definition is - // added automatically by CS-Script. The auto-generated class declaration also indicates - // that the class implements ICalc interface. Meaning that it will trigger compile error - // if the set of methods in the script code doesn't implement all interface members. - - //This use-case uses Interface Alignment and this requires all assemblies involved to have - //non-empty Assembly.Location - CSScript.GlobalSettings.InMemoryAssembly = false; - - var script = CSScript.Evaluator - .LoadMethodRemotely( - @"public int Sum(int a, int b) - { - return a+b; - } - public int Sub(int a, int b) - { - return a-b; - }"); - - Console.WriteLine("{0}: {1}", nameof(LoadMethodRemotely), script.Sum(15, 3)); - - script.UnloadOwnerDomain(); - } - - MethodDelegate sum; - ClientSponsor sumSponsor; - - public void KeepRemoteObjectAlive() - { - sum = CSScript.Evaluator - .CreateDelegateRemotely(@"int Sum(int a, int b) - { - return a+b; - }"); - - //Normally remote objects are disposed if they are not accessed withing a default timeout period. - //It is not even enough to keep transparent proxies or their wrappers (e.g. 'sum') referenced. - //To prevent GC collection in the remote domain use .NET ClientSponsor mechanism as below. - sumSponsor = sum.ExtendLifeFromMinutes(30); - } - } - } - - public interface ICalc - { - int Sum(int a, int b); - } - - public interface IFullCalc - { - int Sum(int a, int b); - - int Sub(int a, int b); - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Scripting.evaluator.cs b/src/Artemis.Core/Scripting.evaluator.cs deleted file mode 100644 index 0e195776f..000000000 --- a/src/Artemis.Core/Scripting.evaluator.cs +++ /dev/null @@ -1,345 +0,0 @@ -using CSScriptLibrary; -using System; -using System.Diagnostics; - -// Read in more details about all aspects of CS-Script hosting in applications -// here: http://www.csscript.net/help/Script_hosting_guideline_.html -// -// This file contains samples for the script hosting scenarios relying on CS-Script Evaluator interface (API). -// This API is a unified generic interface allowing dynamic switch of the underlying compiling services (Mono, Roslyn, CodeDom) -// without the need for changing the hosting code. -// -// Apart from Evaluator (compiler agnostic) API CS-Script offers alternative hosting model: CS-Script Native, -// which relies solely on CodeDom compiler. CS-Script Native offers some features that are not available with CS-Script Evaluator -// (e.g. script unloading). -// -// The choice of the underlying compiling engine (e.g. Mono vs CodeDom) when using CS-Script Evaluator is always dictated by the -// specifics of the hosting scenario. Thanks to in-process compiler hosting, Mono and Roslyn demonstrate much better compiling -// performance comparing to CodeDom engine. However they don't allow script debugging and caching easily supported with CodeDom. -// Mono and particularly Roslyn also leas create more memory pressure due to the higher volume of the temp assemblies loaded into -// the hosting AppDomain. Roslyn (at least CSharp.Scripting-v1.2.0.0) also has very high initial loading overhead up to 4 seconds. -// -// One of the possible approaches would be to use EvaluatorEngine.CodeDom during the active development and later on switch to Mono/Roslyn. - -namespace CSScriptEvaluatorApi -{ - public class HostApp - { - public static void Test() - { - // Just in case clear AlternativeCompiler so it is not set to Roslyn or anything else by - // the CS-Script installed (if any) on the host OS - CSScript.GlobalSettings.UseAlternativeCompiler = null; - - var samples = new EvaluatorSamples(); - - Console.WriteLine("Testing compiling services"); - Console.WriteLine("---------------------------------------------"); - - CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Mono; - Console.WriteLine(CSScript.Evaluator.GetType().Name + "..."); - samples.RunAll(); - - Console.WriteLine("---------------------------------------------"); - - CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Roslyn; - Console.WriteLine(CSScript.Evaluator.GetType().Name + "..."); - samples.RunAll(); - - Console.WriteLine("---------------------------------------------"); - - CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; - Console.WriteLine(CSScript.Evaluator.GetType().Name + "..."); - - samples.RunAll(); - - //samples.DebugTest(); //uncomment if want to fire an assertion during the script execution - - //Profile(); //uncomment if want to test performance of the engines - } - - class EvaluatorSamples - { - public void RunAll() - { - Action run = (action, name) => { action(); Console.WriteLine(name + " - OK"); }; - - run(CompileMethod_Instance, nameof(CompileMethod_Instance)); - run(CompileMethod_Static, nameof(CompileMethod_Static)); - run(CreateDelegate, nameof(CreateDelegate)); - run(LoadDelegate, nameof(LoadDelegate)); - run(LoadCode, nameof(LoadCode)); - run(LoadMethod, nameof(LoadMethod)); - run(LoadMethodWithInterface, nameof(LoadMethodWithInterface)); - run(LoadCode_WithInterface, nameof(LoadCode_WithInterface)); - run(LoadCode_WithDuckTypedInterface, nameof(LoadCode_WithDuckTypedInterface)); - } - - public void CompileMethod_Instance() - { - // 1- CompileMethod wraps method into a class definition and returns compiled assembly - // 2 - CreateObject creates instance of a first class in the assembly - - dynamic script = CSScript.Evaluator - .CompileMethod(@"int Sqr(int data) - { - return data * data; - }") - .CreateObject("*"); - - var result = script.Sqr(7); - } - - public void CompileMethod_Static() - { - // 1 - CompileMethod wraps method into a class definition and returns compiled assembly - // 2 - GetStaticMethod returns duck-typed delegate that accepts 'params object[]' arguments - // Note: GetStaticMethodWithArgs can be replaced with a more convenient/shorter version - // that takes the object instead of the Type and then queries objects type internally: - // "GetStaticMethod("*.Test", data)" - - var test = CSScript.Evaluator - .CompileMethod(@"using CSScriptEvaluatorApi; - static void Test(InputData data) - { - data.Index = GetIndex(); - } - static int GetIndex() - { - return Environment.TickCount; - }") - .GetStaticMethodWithArgs("*.Test", typeof(InputData)); - - var data = new InputData(); - test(data); - } - - public void CreateDelegate() - { - // Wraps method into a class definition, compiles it and loads the compiled assembly. - // It returns duck-typed delegate. A delegate with 'params object[]' arguments and - // without any specific return type. - - var sqr = CSScript.Evaluator - .CreateDelegate(@"int Sqr(int a) - { - return a * a; - }"); - - var r = sqr(3); - } - - public void LoadDelegate() - { - // Wraps method into a class definition, loads the compiled assembly - // and returns the method delegate for the method, which matches the delegate specified - // as the type parameter of LoadDelegate - - var product = CSScript.Evaluator - .LoadDelegate>( - @"int Product(int a, int b) - { - return a * b; - }"); - - int result = product(3, 2); - } - - public void LoadCode() - { - // LoadCode compiles code and returns instance of a first class - // in the compiled assembly - - dynamic script = CSScript.Evaluator - .LoadCode(@"using System; - public class Script - { - public int Sum(int a, int b) - { - return a+b; - } - }"); - - int result = script.Sum(1, 2); - } - - public void LoadMethod() - { - // LoadMethod compiles code and returns instance of a first class - // in the compiled assembly. - // LoadMethod is essentially the same as LoadCode. It just deals not with the - // whole class definition but a single method(s) only. And the rest of the class definition is - // added automatically by CS-Script. - // 'public' is optional as it will be injected if the code doesn't start with it. - dynamic script = CSScript.Evaluator - .LoadMethod(@"using System; - public int Sum(int a, int b) - { - return a+b; - }"); - - int result = script.Sum(1, 2); - } - - public void LoadMethodWithInterface() - { - // LoadMethod compiles code and returns instance of a first class - // in the compiled assembly. - // LoadMethod is essentially the same as LoadCode. It just deals not with the - // whole class definition but a single method(s) only. And the rest of the class definition is - // added automatically by CS-Script. The auto-generated class declaration also indicates - // that the class implements ICalc interface. Meaning that it will trigger compile error - // if the set of methods in the script code doesn't implement all interface members. - - //This use-case uses Interface Alignment and this requires all assemblies involved to have - //non-empty Assembly.Location - CSScript.GlobalSettings.InMemoryAssembly = false; - - ICalc script = CSScript.Evaluator - .LoadMethod( - @"int Sum(int a, int b) - { - return a+b; - }"); - - int result = script.Sum(1, 2); - } - - public void LoadCode_WithInterface() - { - // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly - // 2 - The script class implements host app interface so the returned object can be type casted into it - - var script = (ICalc)CSScript.Evaluator - .LoadCode(@"using System; - public class Script : CSScriptEvaluatorApi.ICalc - { - public int Sum(int a, int b) - { - return a+b; - } - }"); - - int result = script.Sum(1, 2); - } - - public void LoadCode_WithDuckTypedInterface() - { - // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly - // 2- The script class doesn't implement host app interface but it can still be aligned to - // one as long at it implements the interface members - - //This use-case uses Interface Alignment and this requires all assemblies involved to have - //non-empty Assembly.Location - CSScript.GlobalSettings.InMemoryAssembly = false; - - ICalc script = CSScript.MonoEvaluator - .LoadCode(@"using System; - public class Script - { - public int Sum(int a, int b) - { - return a+b; - } - }"); - - int result = script.Sum(1, 2); - } - - public void PerformanceTest(int count = -1) - { - var code = @"int Sqr(int a) - { - return a * a; - }"; - - if (count != -1) - code += "//" + count; //this unique extra code comment ensures the code to be compiled cannot be cached - - dynamic script = CSScript.Evaluator - .CompileMethod(code) - .CreateObject("*"); - - var r = script.Sqr(3); - } - - public void DebugTest() - { - //pops up an assertion dialog - - CSScript.EvaluatorConfig.DebugBuild = true; - CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; - - dynamic script = CSScript.Evaluator - .LoadCode(@"using System; - using System.Diagnostics; - public class Script - { - public int Sum(int a, int b) - { - Debug.Assert(false,""Testing CS-Script debugging...""); - return a+b; - } - }"); - - var r = script.Sum(3, 4); - } - } - - public static void Profile() - { - var sw = new Stopwatch(); - var samples = new EvaluatorSamples(); - var count = 20; - var inxed = 0; - bool preventCaching = false; - - Action run = () => - { - sw.Restart(); - for (int i = 0; i < count; i++) - if (preventCaching) - samples.PerformanceTest(inxed++); - else - samples.PerformanceTest(); - - Console.WriteLine(CSScript.Evaluator.GetType().Name + ": " + sw.ElapsedMilliseconds); - }; - - Action runAll = () => - { - Console.WriteLine("\n---------------------------------------------"); - Console.WriteLine($"Caching enabled: {!preventCaching}\n"); - - CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Mono; - run(); - - CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; - run(); - - CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Roslyn; - run(); - }; - - RoslynEvaluator.LoadCompilers(); //Roslyn is extremely heavy so exclude startup time from profiling - - Console.WriteLine("Testing performance"); - - preventCaching = true; - runAll(); - - preventCaching = false; - runAll(); - } - } - - public interface ICalc - { - int Sum(int a, int b); - } - - public class InputData - { - public int Index = 0; - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Scripting.native.cs b/src/Artemis.Core/Scripting.native.cs deleted file mode 100644 index 37c7ef2f7..000000000 --- a/src/Artemis.Core/Scripting.native.cs +++ /dev/null @@ -1,354 +0,0 @@ -using CSScriptLibrary; -using System; -using System.Linq; -using System.IO; -using csscript; -using System.CodeDom.Compiler; - -// Read in more details about all aspects of CS-Script hosting in applications -// here: http://www.csscript.net/help/Script_hosting_guideline_.html -// -// This file contains samples for the script hosting scenarios relying on CS-Script Native interface (API). -// This API is a compiler specific interface, which relies solely on CodeDom compiler. In most of the cases -// CS-Script Native model is the most flexible and natural choice -// -// Apart from Native API CS-Script offers alternative hosting model: CS-Script Evaluator, which provides -// a unified generic interface allowing dynamic switch the underlying compiling services (Mono, Roslyn, CodeDom) -// without the need for changing the hosting code. -// -// The Native interface is the original API that was designed to take maximum advantage of the dynamic C# code -// execution with CodeDom. The original implementation of this API was developed even before any compiler-as-service -// solution became available. Being based solely on CodeDOM the API doesn't utilize neither Mono nor Roslyn -// scripting solutions. Despite that CS-Script Native is the most mature, powerful and flexible API available with CS-Script. -// -// Native interface allows some unique features that are not available with CS-Script Evaluator: -// - Debugging scripts -// - Script caching -// - Script unloading - -namespace CSScriptNativeApi -{ - public class HostApp - { - public static void Test() - { - var host = new HostApp(); - host.Log("Testing compiling services CS-Script Native API"); - Console.WriteLine("---------------------------------------------"); - - CodeDomSamples.LoadMethod_Instance(); - CodeDomSamples.LoadMethod_Static(); - CodeDomSamples.LoadDelegate(); - CodeDomSamples.CreateAction(); - CodeDomSamples.CreateFunc(); - CodeDomSamples.LoadCode(); - CodeDomSamples.LoadCode_WithInterface(host); - CodeDomSamples.LoadCode_WithDuckTypedInterface(host); - CodeDomSamples.ExecuteAndUnload(); - //CodeDomSamples.DebugTest(); //uncomment if want to fire an assertion during the script execution - } - - public class CodeDomSamples - { - public static void LoadMethod_Instance() - { - // 1- LoadMethod wraps method into a class definition, compiles it and returns loaded assembly - // 2 - CreateObject creates instance of a first class in the assembly - - dynamic script = CSScript.LoadMethod(@"int Sqr(int data) - { - return data * data; - }") - .CreateObject("*"); - - var result = script.Sqr(7); - } - - public static void LoadMethod_Static() - { - // 1 - LoadMethod wraps method into a class definition, compiles it and returns loaded assembly - // 2 - GetStaticMethod returns first found static method as a duck-typed delegate that - // accepts 'params object[]' arguments - // - // Note: you can use GetStaticMethodWithArgs for higher precision method search: GetStaticMethodWithArgs("*.SayHello", typeof(string)); - var sayHello = CSScript.LoadMethod(@"static void SayHello(string greeting) - { - Console.WriteLine(greeting); - }") - .GetStaticMethod(); - - sayHello("Hello World!"); - } - - public static void LoadDelegate() - { - // LoadDelegate wraps method into a class definition, compiles it and loads the compiled assembly. - // It returns the method delegate for the method, which matches the delegate specified - // as the type parameter of LoadDelegate - - // The 'using System;' is optional; it demonstrates how to specify 'using' in the method-only syntax - - var sayHello = CSScript.LoadDelegate>( - @"void SayHello(string greeting) - { - Console.WriteLine(greeting); - }"); - - sayHello("Hello World!"); - } - - public static void CreateAction() - { - // Wraps method into a class definition, compiles it and loads the compiled assembly. - // It returns duck-typed delegate. A delegate with 'params object[]' arguments and - // without any specific return type. - - var sayHello = CSScript.CreateAction(@"void SayHello(string greeting) - { - Console.WriteLine(greeting); - }"); - - sayHello("Hello World!"); - } - - public static void CreateFunc() - { - // Wraps method into a class definition, compiles it and loads the compiled assembly. - // It returns duck-typed delegate. A delegate with 'params object[]' arguments and - // int as a return type. - - var Sqr = CSScript.CreateFunc(@"int Sqr(int a) - { - return a * a; - }"); - int r = Sqr(3); - } - - public static void LoadCode() - { - // LoadCode compiles code and returns instance of a first class - // in the compiled assembly - - dynamic script = CSScript.LoadCode(@"using System; - public class Script - { - public int Sum(int a, int b) - { - return a+b; - } - }") - .CreateObject("*"); - - int result = script.Sum(1, 2); - } - - public static void LoadCodeWithConfig() - { - // LoadCode compiles code and returns instance of a first class - // in the compiled assembly - - string file = Path.GetTempFileName(); - try - { - File.WriteAllText(file, @"using System; - public class Script - { - public int Sum(int a, int b) - { - return a+b; - } - }"); - - var settings = new Settings(); - //settings = null; // set to null to foll back to defaults - - dynamic script = CSScript.LoadWithConfig(file, null, false, settings, "/define:TEST") - .CreateObject("*"); - - int result = script.Sum(1, 2); - } - finally - { - if (File.Exists(file)) - File.Delete(file); - } - } - - public static void LoadCode_WithInterface(HostApp host) - { - // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly. - // 2 - The script class implements host app interface so the returned object can be type casted into it. - // 3 - In this sample host object is passed into script routine. - - var calc = (ICalc) CSScript.LoadCode(@"using CSScriptNativeApi; - public class Script : ICalc - { - public int Sum(int a, int b) - { - if(Host != null) - Host.Log(""Sum is invoked""); - return a + b; - } - - public HostApp Host { get; set; } - }") - .CreateObject("*"); - calc.Host = host; - int result = calc.Sum(1, 2); - } - - public static void LoadCode_WithDuckTypedInterface(HostApp host) - { - // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly - // 2- The script class doesn't implement host app interface but it can still be aligned to - // one as long at it implements the interface members - // 3 - In this sample host object is passed into script routine. - - //This use-case uses Interface Alignment and this requires all assemblies involved to have - //non-empty Assembly.Location - CSScript.GlobalSettings.InMemoryAssembly = false; - - ICalc calc = CSScript.LoadCode(@"using CSScriptNativeApi; - public class Script - { - public int Sum(int a, int b) - { - if(Host != null) - Host.Log(""Sum is invoked""); - return a + b; - } - - public HostApp Host { get; set; } - }") - .CreateObject("*") - .AlignToInterface(); - calc.Host = host; - int result = calc.Sum(1, 2); - } - - public static void ExecuteAndUnload() - { - // The script will be loaded into a temporary AppDomain and unloaded after the execution. - - // Note: remote execution is a subject of some restrictions associated with the nature of the - // CLR cross-AppDomain interaction model: - // * the script class must be serializable or derived from MarshalByRefObject. - // - // * any object (call arguments, return objects) that crosses ApPDomain boundaries - // must be serializable or derived from MarshalByRefObject. - // - // * long living script class instances may get disposed in remote domain even if they are - // being referenced in the current AppDomain. You need to use the usual .NET techniques - // to prevent that. See LifetimeManagement.cs sample for details. - - //This use-case uses Interface Alignment and this requires all assemblies involved to have - //non-empty Assembly.Location - CSScript.GlobalSettings.InMemoryAssembly = false; - - var code = @"using System; - public class Script : MarshalByRefObject - { - public void Hello(string greeting) - { - Console.WriteLine(greeting); - } - }"; - - //Note: usage of helper.CreateAndAlignToInterface("Script") is also acceptable - using (var helper = new AsmHelper(CSScript.CompileCode(code), null, deleteOnExit: true)) - { - IScript script = helper.CreateAndAlignToInterface("*"); - script.Hello("Hi there..."); - } - //from this point AsmHelper is disposed and the temp AppDomain is unloaded - } - - public static void DebugTest() - { - //pops up an assertion dialog - dynamic script = CSScript.LoadCode(@"using System; - using System.Diagnostics; - public class Script - { - public int Sum(int a, int b) - { - Debug.Assert(false,""Testing CS-Script debugging...""); - return a+b; - } - }", null, debugBuild: true).CreateObject("*"); - - int result = script.Sum(1, 2); - } - } - - public void Log(string message) - { - Console.WriteLine(message); - } - } - - public interface IScript - { - void Hello(string greeting); - } - - public interface ICalc - { - HostApp Host { get; set; } - - int Sum(int a, int b); - } - - public class Samples - { - static public void CompilingHistory() - { - string script = Path.GetTempFileName(); - string scriptAsm = script + ".dll"; - CSScript.KeepCompilingHistory = true; - - try - { - File.WriteAllText(script, @"using System; - using System.Windows.Forms; - public class Script - { - public int Sum(int a, int b) - { - return a+b; - } - }"); - - - - CSScript.CompileFile(script, scriptAsm, false, null); - - CompilingInfo info = CSScript.CompilingHistory - .Values - .FirstOrDefault(item => item.ScriptFile == script); - if (info != null) - { - Console.WriteLine("Script: " + info.ScriptFile); - - Console.WriteLine("Referenced assemblies:"); - foreach (string asm in info.Input.ReferencedAssemblies) - Console.WriteLine(asm); - - if (info.Result.Errors.HasErrors) - { - foreach (CompilerError err in info.Result.Errors) - if (!err.IsWarning) - Console.WriteLine("Error: " + err.ErrorText); - } - } - - CSScript.CompilingHistory.Clear(); - - } - finally - { - CSScript.KeepCompilingHistory = false; - } - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/IPluginService.cs b/src/Artemis.Core/Services/Interfaces/IPluginService.cs index f086b1513..112c01053 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginService.cs @@ -2,8 +2,8 @@ using System.Collections.ObjectModel; using System.Threading.Tasks; using Artemis.Core.Events; -using Artemis.Core.Models; using Artemis.Plugins.Interfaces; +using Artemis.Plugins.Models; namespace Artemis.Core.Services.Interfaces { diff --git a/src/Artemis.Core/Services/PluginService.cs b/src/Artemis.Core/Services/PluginService.cs index 475fbdace..e11e981ae 100644 --- a/src/Artemis.Core/Services/PluginService.cs +++ b/src/Artemis.Core/Services/PluginService.cs @@ -6,9 +6,9 @@ using System.Linq; using System.Threading.Tasks; using Artemis.Core.Events; using Artemis.Core.Exceptions; -using Artemis.Core.Models; using Artemis.Core.Services.Interfaces; using Artemis.Plugins.Interfaces; +using Artemis.Plugins.Models; using CSScriptLibrary; using Newtonsoft.Json; using Ninject; @@ -24,7 +24,7 @@ namespace Artemis.Core.Services { _kernel = kernel; _plugins = new List(); - + if (!Directory.Exists(Constants.DataFolder + "plugins")) Directory.CreateDirectory(Constants.DataFolder + "plugins"); } @@ -66,9 +66,11 @@ namespace Artemis.Core.Services throw new ArtemisPluginException(pluginInfo, "Cannot locate a view model for this plugin."); // Instantiate the ViewModel with Ninject - return (IPluginViewModel) _kernel.Get(vmType); + var vm = (IPluginViewModel) _kernel.Get(vmType); + vm.PluginInfo = pluginInfo; + return vm; } - + public void Dispose() { } diff --git a/src/Artemis.Core/packages.config b/src/Artemis.Core/packages.config index 51fe9d3d6..34f6373ff 100644 --- a/src/Artemis.Core/packages.config +++ b/src/Artemis.Core/packages.config @@ -28,6 +28,7 @@ + diff --git a/src/Artemis.Plugins/Abstract/PluginViewModel.cs b/src/Artemis.Plugins/Abstract/PluginViewModel.cs index 269cb1869..cba287e00 100644 --- a/src/Artemis.Plugins/Abstract/PluginViewModel.cs +++ b/src/Artemis.Plugins/Abstract/PluginViewModel.cs @@ -1,9 +1,11 @@ using Artemis.Plugins.Interfaces; +using Artemis.Plugins.Models; using Stylet; namespace Artemis.Plugins.Abstract { public abstract class PluginViewModel : Screen, IPluginViewModel { + public PluginInfo PluginInfo { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.Plugins/Artemis.Plugins.csproj b/src/Artemis.Plugins/Artemis.Plugins.csproj index 1f0dbc33a..04adf6b1f 100644 --- a/src/Artemis.Plugins/Artemis.Plugins.csproj +++ b/src/Artemis.Plugins/Artemis.Plugins.csproj @@ -30,6 +30,9 @@ 4 + + ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + ..\packages\Ninject.3.3.4\lib\net45\Ninject.dll @@ -47,11 +50,13 @@ + + diff --git a/src/Artemis.Plugins/Interfaces/IDataModelExpansion.cs b/src/Artemis.Plugins/Interfaces/IDataModelExpansion.cs new file mode 100644 index 000000000..9c0f1025a --- /dev/null +++ b/src/Artemis.Plugins/Interfaces/IDataModelExpansion.cs @@ -0,0 +1,10 @@ +namespace Artemis.Plugins.Interfaces +{ + /// + /// + /// Allows you to expand the application-wide datamodel + /// + public interface IDataModelExpansion : IPlugin + { + } +} \ No newline at end of file diff --git a/src/Artemis.Plugins/Interfaces/IDevice.cs b/src/Artemis.Plugins/Interfaces/IDevice.cs index b6508af47..b40566156 100644 --- a/src/Artemis.Plugins/Interfaces/IDevice.cs +++ b/src/Artemis.Plugins/Interfaces/IDevice.cs @@ -1,5 +1,9 @@ namespace Artemis.Plugins.Interfaces { + /// + /// + /// Allows you to implement your own RGB device + /// public interface IDevice : IPlugin { } diff --git a/src/Artemis.Plugins/Interfaces/ILayerType.cs b/src/Artemis.Plugins/Interfaces/ILayerType.cs index 012322ce5..1a06bc0ea 100644 --- a/src/Artemis.Plugins/Interfaces/ILayerType.cs +++ b/src/Artemis.Plugins/Interfaces/ILayerType.cs @@ -1,5 +1,9 @@ namespace Artemis.Plugins.Interfaces { + /// + /// + /// Allows you to create your own layer type + /// public interface ILayerType : IPlugin { } diff --git a/src/Artemis.Plugins/Interfaces/IModule.cs b/src/Artemis.Plugins/Interfaces/IModule.cs index c666ecc8e..0eac4ca47 100644 --- a/src/Artemis.Plugins/Interfaces/IModule.cs +++ b/src/Artemis.Plugins/Interfaces/IModule.cs @@ -1,5 +1,9 @@ namespace Artemis.Plugins.Interfaces { + /// + /// + /// Allows you to add support for new games/applications + /// public interface IModule : IPlugin { } diff --git a/src/Artemis.Plugins/Interfaces/IPlugin.cs b/src/Artemis.Plugins/Interfaces/IPlugin.cs index d9063dacc..1c6da9f41 100644 --- a/src/Artemis.Plugins/Interfaces/IPlugin.cs +++ b/src/Artemis.Plugins/Interfaces/IPlugin.cs @@ -1,10 +1,10 @@ -using System.Threading.Tasks; - -namespace Artemis.Plugins.Interfaces +namespace Artemis.Plugins.Interfaces { + /// + /// This is the base plugin type, use the other interfaces such as IModule to create plugins + /// public interface IPlugin - { - /// + {/// /// Called when the plugin is loaded /// void LoadPlugin(); diff --git a/src/Artemis.Plugins/Interfaces/IPluginViewModel.cs b/src/Artemis.Plugins/Interfaces/IPluginViewModel.cs index 9ba9cf6d1..711fb20b7 100644 --- a/src/Artemis.Plugins/Interfaces/IPluginViewModel.cs +++ b/src/Artemis.Plugins/Interfaces/IPluginViewModel.cs @@ -1,8 +1,14 @@ -using Stylet; +using Artemis.Plugins.Models; +using Stylet; namespace Artemis.Plugins.Interfaces { + /// + /// + /// Allows you to create a view model for a plugin + /// public interface IPluginViewModel : IScreen { + PluginInfo PluginInfo { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/PluginInfo.cs b/src/Artemis.Plugins/Models/PluginInfo.cs similarity index 96% rename from src/Artemis.Core/Models/PluginInfo.cs rename to src/Artemis.Plugins/Models/PluginInfo.cs index 1c8e2fa2b..f4819a1dc 100644 --- a/src/Artemis.Core/Models/PluginInfo.cs +++ b/src/Artemis.Plugins/Models/PluginInfo.cs @@ -1,7 +1,7 @@ using Artemis.Plugins.Interfaces; using Newtonsoft.Json; -namespace Artemis.Core.Models +namespace Artemis.Plugins.Models { public class PluginInfo { diff --git a/src/Artemis.Plugins/packages.config b/src/Artemis.Plugins/packages.config index 90d89f33c..9295a4bb5 100644 --- a/src/Artemis.Plugins/packages.config +++ b/src/Artemis.Plugins/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 8158fc1d2..569fbeec4 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -93,6 +93,8 @@ + + diff --git a/src/Artemis.UI/Ninject/UiModule.cs b/src/Artemis.UI/Ninject/UiModule.cs index a04c20fe3..85df21828 100644 --- a/src/Artemis.UI/Ninject/UiModule.cs +++ b/src/Artemis.UI/Ninject/UiModule.cs @@ -1,4 +1,5 @@ -using Artemis.UI.ViewModels.Interfaces; +using Artemis.UI.Services.Interfaces; +using Artemis.UI.ViewModels.Interfaces; using Ninject.Extensions.Conventions; using Ninject.Modules; @@ -17,6 +18,16 @@ namespace Artemis.UI.Ninject .InheritedFrom() .BindAllInterfaces(); }); + + // Bind all UI services as singletons + Kernel.Bind(x => + { + x.FromThisAssembly() + .SelectAllClasses() + .InheritedFrom() + .BindAllInterfaces() + .Configure(c => c.InSingletonScope()); + }); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Services/Interfaces/IArtemisUIService.cs b/src/Artemis.UI/Services/Interfaces/IArtemisUIService.cs new file mode 100644 index 000000000..38a201599 --- /dev/null +++ b/src/Artemis.UI/Services/Interfaces/IArtemisUIService.cs @@ -0,0 +1,8 @@ +namespace Artemis.UI.Services.Interfaces +{ + // ReSharper disable once InconsistentNaming + public interface IArtemisUIService + { + + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Stylet/ArtemisViewManager.cs b/src/Artemis.UI/Stylet/ArtemisViewManager.cs new file mode 100644 index 000000000..d77dd6502 --- /dev/null +++ b/src/Artemis.UI/Stylet/ArtemisViewManager.cs @@ -0,0 +1,46 @@ +using System.IO; +using System.Windows; +using System.Windows.Markup; +using Artemis.Core.Exceptions; +using Artemis.Plugins.Interfaces; +using Stylet; + +namespace Artemis.UI.Stylet +{ + public class ArtemisViewManager : ViewManager + { + public ArtemisViewManager(ViewManagerConfig config) : base(config) + { + } + + public override UIElement CreateViewForModel(object model) + { + if (model is IPluginViewModel) + return CreateViewForPlugin(model); + + return base.CreateViewForModel(model); + } + + private UIElement CreateViewForPlugin(object model) + { + var pluginInfo = ((IPluginViewModel) model).PluginInfo; + var viewPath = pluginInfo.Folder + pluginInfo.ViewModel.Replace("ViewModel", "View").Replace(".cs", ".xaml"); + // There doesn't have to be a view so make sure one exists + if (!File.Exists(viewPath)) + return null; + + // Compile the view if found, must be done on UI thread sadly + object view = null; + Execute.OnUIThread(() => view = XamlReader.Parse(File.ReadAllText(viewPath))); + var viewType = view.GetType(); + // Make sure it's a valid UIElement + if (viewType.IsAbstract || !typeof(UIElement).IsAssignableFrom(viewType)) + { + var e = new ArtemisPluginException(pluginInfo, $"Found type for view: {viewType.Name}, but it wasn't a class derived from UIElement"); + throw e; + } + + return (UIElement) view; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Stylet/NinjectBootstrapper.cs b/src/Artemis.UI/Stylet/NinjectBootstrapper.cs index 51a364424..d09cff98e 100644 --- a/src/Artemis.UI/Stylet/NinjectBootstrapper.cs +++ b/src/Artemis.UI/Stylet/NinjectBootstrapper.cs @@ -31,7 +31,7 @@ namespace Artemis.UI.Stylet ViewFactory = GetInstance, ViewAssemblies = new List {GetType().Assembly} }; - kernel.Bind().ToConstant(new ViewManager(viewManagerConfig)); + kernel.Bind().ToConstant(new ArtemisViewManager(viewManagerConfig)); kernel.Bind().ToConstant(this).InTransientScope(); kernel.Bind().ToMethod(c => new WindowManager(c.Kernel.Get(), diff --git a/src/Artemis.UI/ViewModels/HomeViewModel.cs b/src/Artemis.UI/ViewModels/HomeViewModel.cs index 367ec1a12..25d99189d 100644 --- a/src/Artemis.UI/ViewModels/HomeViewModel.cs +++ b/src/Artemis.UI/ViewModels/HomeViewModel.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using Artemis.Core.Services.Interfaces; using Artemis.UI.ViewModels.Interfaces; using Stylet; @@ -8,15 +7,6 @@ namespace Artemis.UI.ViewModels { public class HomeViewModel : Screen, IHomeViewModel { - private readonly IPluginService _pluginService; - - public HomeViewModel(IPluginService pluginService) - { - _pluginService = pluginService; - - _pluginService.FinishedLoadedPlugins += PluginServiceOnFinishedLoadedPlugins; - } - public string Title => "Home"; public void OpenUrl(string url) @@ -25,15 +15,5 @@ namespace Artemis.UI.ViewModels if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute)) Process.Start(url); } - - /// - /// Populates the sidebar with plugins when they are finished loading - /// - /// - /// - private void PluginServiceOnFinishedLoadedPlugins(object sender, EventArgs eventArgs) - { - throw new NotImplementedException(); - } } } \ No newline at end of file diff --git a/src/Artemis.UI/ViewModels/Interfaces/IArtemisViewModel.cs b/src/Artemis.UI/ViewModels/Interfaces/IArtemisViewModel.cs index fae827f1c..5ac9f1683 100644 --- a/src/Artemis.UI/ViewModels/Interfaces/IArtemisViewModel.cs +++ b/src/Artemis.UI/ViewModels/Interfaces/IArtemisViewModel.cs @@ -1,6 +1,8 @@ -namespace Artemis.UI.ViewModels.Interfaces +using Stylet; + +namespace Artemis.UI.ViewModels.Interfaces { - public interface IArtemisViewModel + public interface IArtemisViewModel : IScreen { string Title { get; } } diff --git a/src/Artemis.UI/ViewModels/RootViewModel.cs b/src/Artemis.UI/ViewModels/RootViewModel.cs index f77b30f2b..2f1ac82ba 100644 --- a/src/Artemis.UI/ViewModels/RootViewModel.cs +++ b/src/Artemis.UI/ViewModels/RootViewModel.cs @@ -1,24 +1,57 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using Artemis.Core.Services.Interfaces; +using Artemis.Plugins.Interfaces; +using Artemis.Plugins.Models; +using Artemis.UI.Services.Interfaces; using Artemis.UI.ViewModels.Interfaces; using Stylet; namespace Artemis.UI.ViewModels { - public class RootViewModel : Conductor.Collection.OneActive + public class RootViewModel : Conductor.Collection.OneActive { private readonly ICollection _artemisViewModels; + private readonly IPluginService _pluginService; - public RootViewModel(ICollection artemisViewModels) + public RootViewModel(ICollection artemisViewModels, IPluginService pluginService) { _artemisViewModels = artemisViewModels; + _pluginService = pluginService; + // Add the built-in items Items.AddRange(artemisViewModels); // Activate the home item ActiveItem = _artemisViewModels.First(v => v.GetType() == typeof(HomeViewModel)); + + // Sync up with the plugin service + Modules = new BindableCollection(); + LoadingPlugins = _pluginService.LoadingPlugins; + _pluginService.StartedLoadingPlugins += PluginServiceOnStartedLoadingPlugins; + _pluginService.FinishedLoadedPlugins += PluginServiceOnFinishedLoadedPlugins; + + if (!LoadingPlugins) + Modules.AddRange(_pluginService.Plugins.Where(p => p.Plugin is IModule)); } + public IObservableCollection Modules { get; set; } + public bool MenuOpen { get; set; } + public bool LoadingPlugins { get; set; } + + private void PluginServiceOnStartedLoadingPlugins(object sender, EventArgs eventArgs) + { + LoadingPlugins = true; + Modules.Clear(); + } + + private void PluginServiceOnFinishedLoadedPlugins(object sender, EventArgs eventArgs) + { + LoadingPlugins = false; + Modules.AddRange(_pluginService.Plugins.Where(p => p.Plugin is IModule)); + NavigateToModule(Modules.First()); + } public void NavigateToHome() { @@ -39,5 +72,13 @@ namespace Artemis.UI.ViewModels ActivateItem(_artemisViewModels.First(v => v.GetType() == typeof(SettingsViewModel))); MenuOpen = false; } + + public async void NavigateToModule(PluginInfo pluginInfo) + { + // Create a view model for the given plugin info (which will be a module) + var viewModel = await _pluginService.GetPluginViewModel(pluginInfo); + // Tell Stylet to active the view model, the view manager will compile and show the XAML + ActivateItem(viewModel); + } } } \ No newline at end of file