diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index cf2c1cb7d..5c1f0cb18 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -35,8 +35,8 @@ ..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll - - ..\packages\CS-Script.bin.3.27.2.0\lib\net46\CSScriptLibrary.dll + + ..\packages\CS-Script.bin.3.28.0.1\lib\net46\CSScriptLibrary.dll ..\packages\HidSharp.1.5\lib\net35\HidSharp.dll @@ -54,10 +54,10 @@ ..\packages\Microsoft.CodeAnalysis.Scripting.Common.2.6.1\lib\netstandard1.3\Microsoft.CodeAnalysis.Scripting.dll - ..\packages\CS-Script.bin.3.27.2.0\lib\net46\Mono.CSharp.dll + ..\packages\CS-Script.bin.3.28.0.1\lib\net46\Mono.CSharp.dll - - ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.11.0.1\lib\net45\Newtonsoft.Json.dll ..\packages\Ninject.3.3.4\lib\net45\Ninject.dll @@ -104,8 +104,8 @@ ..\packages\RGB.NET.Input.0.0.1.26\lib\net45\RGB.NET.Input.dll - - ..\packages\Sanford.Multimedia.Midi.6.4.2\lib\net20\Sanford.Multimedia.Midi.dll + + ..\packages\Sanford.Multimedia.Midi.6.6.0\lib\net20\Sanford.Multimedia.Midi.dll ..\packages\Stylet.1.1.21\lib\net45\Stylet.dll @@ -196,6 +196,9 @@ + + + @@ -209,10 +212,6 @@ - - - - {cd23bc5e-57f0-46ce-a007-24d031146219} diff --git a/src/Artemis.Core/Scripting.Extensions.cs b/src/Artemis.Core/Scripting.Extensions.cs new file mode 100644 index 000000000..070536442 --- /dev/null +++ b/src/Artemis.Core/Scripting.Extensions.cs @@ -0,0 +1,295 @@ +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 new file mode 100644 index 000000000..0e195776f --- /dev/null +++ b/src/Artemis.Core/Scripting.evaluator.cs @@ -0,0 +1,345 @@ +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 new file mode 100644 index 000000000..37c7ef2f7 --- /dev/null +++ b/src/Artemis.Core/Scripting.native.cs @@ -0,0 +1,354 @@ +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/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index bd3c697f3..42de5fab1 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -1,7 +1,10 @@ using System; +using System.Linq; using System.Threading.Tasks; using Artemis.Core.Exceptions; using Artemis.Core.Services.Interfaces; +using Artemis.Plugins.Interfaces; +using RGB.NET.Core; namespace Artemis.Core.Services { @@ -14,6 +17,7 @@ namespace Artemis.Core.Services { _pluginService = pluginService; _rgbService = rgbService; + _rgbService.Surface.Updating += SurfaceOnUpdating; Task.Run(Initialize); } @@ -34,10 +38,27 @@ namespace Artemis.Core.Services // Initialize the services await _pluginService.LoadPlugins(); await _rgbService.LoadDevices(); - + OnInitialized(); } + private void SurfaceOnUpdating(UpdatingEventArgs args) + { + try + { + // Update all active modules + foreach (var module in _pluginService.Plugins.OfType()) + module.Update(args.DeltaTime); + // Render all active modules + foreach (var module in _pluginService.Plugins.OfType()) + module.Render(args.DeltaTime); + } + catch (Exception e) + { + throw new ArtemisCoreException("Exception during update", e); + } + } + #region Events public event EventHandler Initialized; diff --git a/src/Artemis.Core/Services/Interfaces/IPluginService.cs b/src/Artemis.Core/Services/Interfaces/IPluginService.cs index 112c01053..f174f00bd 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginService.cs @@ -9,17 +9,32 @@ namespace Artemis.Core.Services.Interfaces { public interface IPluginService : IArtemisService, IDisposable { + /// + /// Indicates wether or not plugins are currently being loaded + /// bool LoadingPlugins { get; } + + /// + /// All loaded plugins + /// ReadOnlyCollection Plugins { get; } /// /// Loads all installed plugins. If plugins already loaded this will reload them all /// - /// Task LoadPlugins(); + /// + /// Reloads the plugin accompanying the provided plugin info + /// + /// The plugin info containing the plugin to reload Task ReloadPlugin(PluginInfo pluginInfo); - Task GetPluginViewModel(PluginInfo pluginInfo); + + /// + /// Gets the view model of the module accompanying the provided plugin info + /// + /// The plugin info containing the module for which to load the view model + Task GetModuleViewModel(PluginInfo pluginInfo); /// /// Occurs when a single plugin has loaded diff --git a/src/Artemis.Core/Services/PluginService.cs b/src/Artemis.Core/Services/PluginService.cs index 4458128f5..cade01e89 100644 --- a/src/Artemis.Core/Services/PluginService.cs +++ b/src/Artemis.Core/Services/PluginService.cs @@ -33,10 +33,6 @@ namespace Artemis.Core.Services public ReadOnlyCollection Plugins => _plugins.AsReadOnly(); /// - /// - /// Loads all installed plugins. If plugins already loaded this will reload them all - /// - /// public async Task LoadPlugins() { if (LoadingPlugins) @@ -56,18 +52,22 @@ namespace Artemis.Core.Services public async Task ReloadPlugin(PluginInfo pluginInfo) { + throw new NotImplementedException(); } - public async Task GetPluginViewModel(PluginInfo pluginInfo) + public async Task GetModuleViewModel(PluginInfo pluginInfo) { + // Don't attempt to locave VMs for something other than a module + if (pluginInfo.Plugin is IModule) + throw new ArtemisPluginException(pluginInfo, "Cannot locate a view model for this plugin as it's not a module."); // Compile the ViewModel and get the type var compile = await Task.Run(() => CSScript.LoadFile(pluginInfo.Folder + pluginInfo.ViewModel)); - var vmType = compile.ExportedTypes.FirstOrDefault(t => typeof(IPluginViewModel).IsAssignableFrom(t)); + var vmType = compile.ExportedTypes.FirstOrDefault(t => typeof(IModuleViewModel).IsAssignableFrom(t)); if (vmType == null) - throw new ArtemisPluginException(pluginInfo, "Cannot locate a view model for this plugin."); + throw new ArtemisPluginException(pluginInfo, "Cannot locate a view model for this module."); // Instantiate the ViewModel with Ninject - var vm = (IPluginViewModel) _kernel.Get(vmType); + var vm = (IModuleViewModel) _kernel.Get(vmType); vm.PluginInfo = pluginInfo; return vm; } @@ -94,25 +94,10 @@ namespace Artemis.Core.Services } #region Events - - /// - /// Occurs when a single plugin has loaded - /// + public event EventHandler PluginLoaded; - - /// - /// Occurs when a single plugin has reloaded - /// public event EventHandler PluginReloaded; - - /// - /// Occurs when loading all plugins has started - /// public event EventHandler StartedLoadingPlugins; - - /// - /// Occurs when loading all plugins has finished - /// public event EventHandler FinishedLoadedPlugins; private void OnPluginLoaded(PluginEventArgs e) diff --git a/src/Artemis.Core/packages.config b/src/Artemis.Core/packages.config index 693215aa2..ef2b52f54 100644 --- a/src/Artemis.Core/packages.config +++ b/src/Artemis.Core/packages.config @@ -1,15 +1,15 @@  - - + + - + - + @@ -28,7 +28,7 @@ - + diff --git a/src/Artemis.Plugins/Abstract/PluginViewModel.cs b/src/Artemis.Plugins/Abstract/ModuleViewModel.cs similarity index 74% rename from src/Artemis.Plugins/Abstract/PluginViewModel.cs rename to src/Artemis.Plugins/Abstract/ModuleViewModel.cs index cba287e00..338f9f911 100644 --- a/src/Artemis.Plugins/Abstract/PluginViewModel.cs +++ b/src/Artemis.Plugins/Abstract/ModuleViewModel.cs @@ -4,7 +4,7 @@ using Stylet; namespace Artemis.Plugins.Abstract { - public abstract class PluginViewModel : Screen, IPluginViewModel + public abstract class ModuleViewModel : Screen, IModuleViewModel { public PluginInfo PluginInfo { get; set; } } diff --git a/src/Artemis.Plugins/Artemis.Plugins.csproj b/src/Artemis.Plugins/Artemis.Plugins.csproj index 04adf6b1f..9c2269b82 100644 --- a/src/Artemis.Plugins/Artemis.Plugins.csproj +++ b/src/Artemis.Plugins/Artemis.Plugins.csproj @@ -30,8 +30,8 @@ 4 - - ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.11.0.1\lib\net45\Newtonsoft.Json.dll ..\packages\Ninject.3.3.4\lib\net45\Ninject.dll @@ -49,13 +49,13 @@ - + - + diff --git a/src/Artemis.Plugins/Interfaces/IModule.cs b/src/Artemis.Plugins/Interfaces/IModule.cs index 0eac4ca47..d75b99ee7 100644 --- a/src/Artemis.Plugins/Interfaces/IModule.cs +++ b/src/Artemis.Plugins/Interfaces/IModule.cs @@ -6,5 +6,16 @@ /// public interface IModule : IPlugin { + /// + /// Called each frame when the module must update + /// + /// Time since the last update + void Update(double deltaTime); + + /// + /// Called each frame when the module must render + /// + /// Time since the last render + void Render(double deltaTime); } } \ No newline at end of file diff --git a/src/Artemis.Plugins/Interfaces/IPluginViewModel.cs b/src/Artemis.Plugins/Interfaces/IModuleViewModel.cs similarity index 65% rename from src/Artemis.Plugins/Interfaces/IPluginViewModel.cs rename to src/Artemis.Plugins/Interfaces/IModuleViewModel.cs index 711fb20b7..83ef20391 100644 --- a/src/Artemis.Plugins/Interfaces/IPluginViewModel.cs +++ b/src/Artemis.Plugins/Interfaces/IModuleViewModel.cs @@ -5,9 +5,9 @@ namespace Artemis.Plugins.Interfaces { /// /// - /// Allows you to create a view model for a plugin + /// Allows you to create a view model for a module /// - public interface IPluginViewModel : IScreen + public interface IModuleViewModel : IScreen { PluginInfo PluginInfo { get; set; } } diff --git a/src/Artemis.Plugins/Interfaces/IPlugin.cs b/src/Artemis.Plugins/Interfaces/IPlugin.cs index 1c6da9f41..fc923e8ae 100644 --- a/src/Artemis.Plugins/Interfaces/IPlugin.cs +++ b/src/Artemis.Plugins/Interfaces/IPlugin.cs @@ -4,7 +4,8 @@ /// 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/packages.config b/src/Artemis.Plugins/packages.config index 9295a4bb5..ba9e759e2 100644 --- a/src/Artemis.Plugins/packages.config +++ b/src/Artemis.Plugins/packages.config @@ -1,6 +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 e09810ea4..c71aff2ca 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -39,11 +39,14 @@ ..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll + + ..\packages\ControlzEx.3.0.2.4\lib\net45\ControlzEx.dll + ..\packages\HidSharp.1.5\lib\net35\HidSharp.dll - - ..\packages\MahApps.Metro.1.5.0\lib\net45\MahApps.Metro.dll + + ..\packages\MahApps.Metro.1.6.0\lib\net45\MahApps.Metro.dll ..\packages\MaterialDesignColors.1.1.3\lib\net45\MaterialDesignColors.dll @@ -63,8 +66,8 @@ ..\packages\Ninject.Extensions.Factory.3.3.2\lib\net45\Ninject.Extensions.Factory.dll - - ..\packages\PropertyChanged.Fody.2.2.4.0\lib\net452\PropertyChanged.dll + + ..\packages\PropertyChanged.Fody.2.2.6\lib\net452\PropertyChanged.dll ..\packages\RGB.NET.Brushes.0.0.1.26\lib\net45\RGB.NET.Brushes.dll @@ -99,8 +102,8 @@ ..\packages\RGB.NET.Groups.0.0.1.26\lib\net45\RGB.NET.Groups.dll - - ..\packages\Sanford.Multimedia.Midi.6.4.1\lib\net20\Sanford.Multimedia.Midi.dll + + ..\packages\Sanford.Multimedia.Midi.6.6.0\lib\net20\Sanford.Multimedia.Midi.dll ..\packages\Stylet.1.1.21\lib\net45\Stylet.dll @@ -113,7 +116,7 @@ ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll - ..\packages\MahApps.Metro.1.5.0\lib\net45\System.Windows.Interactivity.dll + ..\packages\ControlzEx.3.0.2.4\lib\net45\System.Windows.Interactivity.dll @@ -235,18 +238,17 @@ - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + @@ -254,4 +256,5 @@ + \ No newline at end of file diff --git a/src/Artemis.UI/Stylet/ArtemisViewManager.cs b/src/Artemis.UI/Stylet/ArtemisViewManager.cs index f9ab1316d..859375f00 100644 --- a/src/Artemis.UI/Stylet/ArtemisViewManager.cs +++ b/src/Artemis.UI/Stylet/ArtemisViewManager.cs @@ -14,7 +14,7 @@ namespace Artemis.UI.Stylet public override UIElement CreateViewForModel(object model) { - if (model is IPluginViewModel) + if (model is IModuleViewModel) return CreateViewForPlugin(model); return base.CreateViewForModel(model); @@ -22,7 +22,7 @@ namespace Artemis.UI.Stylet private UIElement CreateViewForPlugin(object model) { - var pluginInfo = ((IPluginViewModel) model).PluginInfo; + var pluginInfo = ((IModuleViewModel) 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)) diff --git a/src/Artemis.UI/ViewModels/RootViewModel.cs b/src/Artemis.UI/ViewModels/RootViewModel.cs index 2f1ac82ba..5361a49d7 100644 --- a/src/Artemis.UI/ViewModels/RootViewModel.cs +++ b/src/Artemis.UI/ViewModels/RootViewModel.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; +using System.Threading.Tasks; +using System.Windows.Controls; 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; @@ -19,7 +21,7 @@ namespace Artemis.UI.ViewModels { _artemisViewModels = artemisViewModels; _pluginService = pluginService; - + // Add the built-in items Items.AddRange(artemisViewModels); // Activate the home item @@ -33,52 +35,78 @@ namespace Artemis.UI.ViewModels if (!LoadingPlugins) Modules.AddRange(_pluginService.Plugins.Where(p => p.Plugin is IModule)); + + PropertyChanged += OnSelectedModuleChanged; + PropertyChanged += OnSelectedPageChanged; } + public IObservableCollection Modules { get; set; } public bool MenuOpen { get; set; } public bool LoadingPlugins { get; set; } + public ListBoxItem SelectedPage { get; set; } + public PluginInfo SelectedModule { get; set; } private void PluginServiceOnStartedLoadingPlugins(object sender, EventArgs eventArgs) { LoadingPlugins = true; + Modules.Clear(); + SelectedModule = null; } private void PluginServiceOnFinishedLoadedPlugins(object sender, EventArgs eventArgs) { - LoadingPlugins = false; Modules.AddRange(_pluginService.Plugins.Where(p => p.Plugin is IModule)); - NavigateToModule(Modules.First()); + SelectedModule = null; + + LoadingPlugins = false; } - public void NavigateToHome() + public async Task NavigateToSelectedModule() { - ActivateItem(_artemisViewModels.First(v => v.GetType() == typeof(HomeViewModel))); - MenuOpen = false; - } + if (SelectedModule == null || LoadingPlugins) + return; - public void NavigateToNews() - { - } - - public void NavigateToWorkshop() - { - } - - public void NavigateToSettings() - { - 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); + var viewModel = await _pluginService.GetModuleViewModel(SelectedModule); // Tell Stylet to active the view model, the view manager will compile and show the XAML ActivateItem(viewModel); + + SelectedPage = null; + MenuOpen = false; + } + + private async void OnSelectedModuleChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "SelectedModule") + await NavigateToSelectedModule(); + } + + private void OnSelectedPageChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName != "SelectedPage" || SelectedPage == null) + return; + + switch (SelectedPage.Name) + { + case "Home": + ActivateItem(_artemisViewModels.First(v => v.GetType() == typeof(HomeViewModel))); + break; + case "News": + // ActivateItem(_artemisViewModels.First(v => v.GetType() == typeof(NewsViewModel))); + break; + case "Workshop": + // ActivateItem(_artemisViewModels.First(v => v.GetType() == typeof(WorkshopViewModel))); + break; + case "Settings": + ActivateItem(_artemisViewModels.First(v => v.GetType() == typeof(SettingsViewModel))); + break; + } + + SelectedModule = null; + MenuOpen = false; } } } \ No newline at end of file diff --git a/src/Artemis.UI/ViewModels/SettingsViewModel.cs b/src/Artemis.UI/ViewModels/SettingsViewModel.cs index c884cc9c1..97f6dd8ec 100644 --- a/src/Artemis.UI/ViewModels/SettingsViewModel.cs +++ b/src/Artemis.UI/ViewModels/SettingsViewModel.cs @@ -1,37 +1,10 @@ -using System.Linq; -using Artemis.Core.Services.Interfaces; -using Artemis.UI.ViewModels.Interfaces; -using RGB.NET.Core; +using Artemis.UI.ViewModels.Interfaces; using Stylet; namespace Artemis.UI.ViewModels { public class SettingsViewModel : Screen, ISettingsViewModel { - private readonly IRgbService _rgbService; - - public SettingsViewModel(IRgbService rgbService) - { - _rgbService = rgbService; - _rgbService.FinishedLoadedDevices += (sender, args) => SetTestDevice(); - } - - protected override void OnActivate() - { - SetTestDevice(); - base.OnActivate(); - } - - private void SetTestDevice() - { - if (!IsActive) - return; - - if (!_rgbService.LoadingDevices) - TestDevice = _rgbService.Surface.Devices.FirstOrDefault(d => d.DeviceInfo.DeviceType == RGBDeviceType.Keyboard); - } - - public IRGBDevice TestDevice { get; set; } public string Title => "Settings"; } } \ No newline at end of file diff --git a/src/Artemis.UI/Views/RootView.xaml b/src/Artemis.UI/Views/RootView.xaml index f1169de2c..6ec42fb6f 100644 --- a/src/Artemis.UI/Views/RootView.xaml +++ b/src/Artemis.UI/Views/RootView.xaml @@ -7,6 +7,7 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:s="https://github.com/canton7/Stylet" xmlns:vms="clr-namespace:Artemis.UI.ViewModels" + xmlns:models="clr-namespace:Artemis.Plugins.Models;assembly=Artemis.Plugins" mc:Ignorable="d" GlowBrush="{DynamicResource AccentColorBrush}" FontFamily="{StaticResource DefaultFont}" @@ -80,29 +81,30 @@ - + DockPanel.Dock="Top" + SelectedItem="{Binding SelectedPage}"> + Home - + News - + Workshop - + @@ -118,39 +120,23 @@ - - - - + + + + - Home - - - - - - News - - - - - - Workshop - - - - - - Settings - - - - + + + + + @@ -185,4 +171,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/Artemis.UI/Views/SettingsView.xaml b/src/Artemis.UI/Views/SettingsView.xaml index f8cf757eb..df815cf4b 100644 --- a/src/Artemis.UI/Views/SettingsView.xaml +++ b/src/Artemis.UI/Views/SettingsView.xaml @@ -3,11 +3,24 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:Artemis.UI.Views" - xmlns:visualizers="clr-namespace:Artemis.UI.Controls.Visualizers" mc:Ignorable="d" - d:DesignHeight="300" d:DesignWidth="300"> - - - + d:DesignHeight="600" d:DesignWidth="600"> + + + + + + + + + General + + General settings like start up with Windows etc. + + + Plugins + + A list of plugins and options to disable them + + \ No newline at end of file diff --git a/src/Artemis.UI/packages.config b/src/Artemis.UI/packages.config index e1f77e99a..d22cb7eaa 100644 --- a/src/Artemis.UI/packages.config +++ b/src/Artemis.UI/packages.config @@ -1,16 +1,17 @@  - + + - + - + @@ -25,7 +26,7 @@ - + \ No newline at end of file