From 30be7a9915713999b527b431f05b46f08d192386 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 23 Aug 2022 23:28:42 +0200 Subject: [PATCH 01/15] Nodes - Fixed some connections not saving on dynamic incoming pins Nodes - Fixed undo/redo when deleting a node with dynamic incoming pins Nodes - Disabled layer property node for now until I have time to finish it --- .../VisualScripting/NodeScript.cs | 8 +++- .../NodeEditor/NodeConnectionStore.cs | 40 ++++++------------- .../Services/RegistrationService.cs | 6 ++- .../Nodes/External/LayerPropertyNode.cs | 2 +- 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/Artemis.Core/VisualScripting/NodeScript.cs b/src/Artemis.Core/VisualScripting/NodeScript.cs index 330b3b417..ce6a7af00 100644 --- a/src/Artemis.Core/VisualScripting/NodeScript.cs +++ b/src/Artemis.Core/VisualScripting/NodeScript.cs @@ -341,8 +341,14 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript, IStorageMod private void SavePins(INode node, int collectionId, IEnumerable pins) { int sourcePinId = 0; - foreach (IPin sourcePin in pins.Where(p => p.Direction == PinDirection.Input)) + foreach (IPin sourcePin in pins) { + if (sourcePin.Direction == PinDirection.Output) + { + sourcePinId++; + continue; + } + foreach (IPin targetPin in sourcePin.ConnectedTo) { int targetPinCollectionId = -1; diff --git a/src/Artemis.UI.Shared/Services/NodeEditor/NodeConnectionStore.cs b/src/Artemis.UI.Shared/Services/NodeEditor/NodeConnectionStore.cs index 3598d41fa..596e66304 100644 --- a/src/Artemis.UI.Shared/Services/NodeEditor/NodeConnectionStore.cs +++ b/src/Artemis.UI.Shared/Services/NodeEditor/NodeConnectionStore.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Artemis.Core; namespace Artemis.UI.Shared.Services.NodeEditor; @@ -30,20 +31,18 @@ public class NodeConnectionStore public void Store() { _pinConnections.Clear(); - foreach (IPin nodePin in Node.Pins) - { + + // Iterate to save + foreach (IPin nodePin in Node.Pins.ToList()) + _pinConnections.Add(nodePin, new List(nodePin.ConnectedTo)); + foreach (IPin nodePin in Node.PinCollections.ToList().SelectMany(nodePinCollection => nodePinCollection)) _pinConnections.Add(nodePin, new List(nodePin.ConnectedTo)); - nodePin.DisconnectAll(); - } - foreach (IPinCollection nodePinCollection in Node.PinCollections) - { - foreach (IPin nodePin in nodePinCollection) - { - _pinConnections.Add(nodePin, new List(nodePin.ConnectedTo)); - nodePin.DisconnectAll(); - } - } + // Iterate to disconnect + foreach (IPin nodePin in Node.Pins.ToList()) + nodePin.DisconnectAll(); + foreach (IPin nodePin in Node.PinCollections.ToList().SelectMany(nodePinCollection => nodePinCollection)) + nodePin.DisconnectAll(); } /// @@ -51,23 +50,10 @@ public class NodeConnectionStore /// public void Restore() { - foreach (IPin nodePin in Node.Pins) + foreach ((IPin? pin, List? connections) in _pinConnections) { - if (!_pinConnections.TryGetValue(nodePin, out List? connections)) - continue; foreach (IPin connection in connections) - nodePin.ConnectTo(connection); - } - - foreach (IPinCollection nodePinCollection in Node.PinCollections) - { - foreach (IPin nodePin in nodePinCollection) - { - if (!_pinConnections.TryGetValue(nodePin, out List? connections)) - continue; - foreach (IPin connection in connections) - nodePin.ConnectTo(connection); - } + pin.ConnectTo(connection); } _pinConnections.Clear(); diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index 6767cff71..9f87aa2db 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Linq; +using System.Reflection; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Controllers; @@ -106,6 +107,9 @@ public class RegistrationService : IRegistrationService _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(Enum), new SKColor(0xFF1E90FF)); foreach (Type nodeType in typeof(SumNumericsNode).Assembly.GetTypes().Where(t => typeof(INode).IsAssignableFrom(t) && t.IsPublic && !t.IsAbstract && !t.IsInterface)) - _nodeService.RegisterNodeType(Constants.CorePlugin, nodeType); + { + if (nodeType.GetCustomAttribute(typeof(NodeAttribute)) != null) + _nodeService.RegisterNodeType(Constants.CorePlugin, nodeType); + } } } \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/External/LayerPropertyNode.cs b/src/Artemis.VisualScripting/Nodes/External/LayerPropertyNode.cs index 89226a328..72fff998a 100644 --- a/src/Artemis.VisualScripting/Nodes/External/LayerPropertyNode.cs +++ b/src/Artemis.VisualScripting/Nodes/External/LayerPropertyNode.cs @@ -3,7 +3,7 @@ using Artemis.VisualScripting.Nodes.External.Screens; namespace Artemis.VisualScripting.Nodes.External; -[Node("Layer/Folder Property", "Outputs the property of a selected layer or folder", "External")] +// [Node("Layer/Folder Property", "Outputs the property of a selected layer or folder", "External")] public class LayerPropertyNode : Node { private readonly object _layerPropertyLock = new(); From e54e791062613683dbbfb1420761dd511a74a219 Mon Sep 17 00:00:00 2001 From: "aytac.kayadelen" Date: Wed, 24 Aug 2022 11:15:29 +0300 Subject: [PATCH 02/15] added string lLength and IsNullOrEmpty nodes --- .../Nodes/Text/StringLengthNode.cs | 24 +++++++++++++++ .../Nodes/Text/StrıngNullOrEmpty.cs | 29 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/Artemis.VisualScripting/Nodes/Text/StringLengthNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs diff --git a/src/Artemis.VisualScripting/Nodes/Text/StringLengthNode.cs b/src/Artemis.VisualScripting/Nodes/Text/StringLengthNode.cs new file mode 100644 index 000000000..f6a09ef66 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Text/StringLengthNode.cs @@ -0,0 +1,24 @@ +using Artemis.Core; + +namespace Artemis.VisualScripting.Nodes.Text; + +[Node("String Length", "Checks whether the first input is contained in the second input.", + "Text", InputType = typeof(string), OutputType = typeof(Numeric))] +public class StringLengthNode : Node +{ + public StringLengthNode() + : base("String Length", "Returns string length.") + { + Input1 = CreateInputPin(); + Result = CreateOutputPin(); + } + + public InputPin Input1 { get; } + + public OutputPin Result { get; } + + public override void Evaluate() + { + Result.Value = new Numeric((Input1.Value ?? "").Length); + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs b/src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs new file mode 100644 index 000000000..7dd6b1d0b --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs @@ -0,0 +1,29 @@ +using Artemis.Core; + +namespace Artemis.VisualScripting.Nodes.Text; + +[Node("String Null or WhiteSpace", "Checks whether the string is null or white space.", + "Text", InputType = typeof(string), OutputType = typeof(bool))] +public class StringNullOrWhiteSpaceNode : Node +{ + public StringNullOrWhiteSpaceNode() + : base("Null or White Space", "Returns true if null or white space") + { + Input1 = CreateInputPin(); + TrueResult = CreateOutputPin("true (Empty)"); + FalseResult = CreateOutputPin("false (Not Empty)"); + } + + public InputPin Input1 { get; } + + public OutputPin TrueResult { get; } + + public OutputPin FalseResult { get; } + + public override void Evaluate() + { + bool isNullOrWhiteSpace = string.IsNullOrWhiteSpace(Input1.Value); + TrueResult.Value = isNullOrWhiteSpace; + FalseResult.Value = !isNullOrWhiteSpace; + } +} \ No newline at end of file From a92c0adaf4b7d41a528a5f36743cb5e42dba8667 Mon Sep 17 00:00:00 2001 From: "aytac.kayadelen" Date: Wed, 24 Aug 2022 12:02:26 +0300 Subject: [PATCH 03/15] update output namings --- .../Nodes/Text/StrıngNullOrEmpty.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs b/src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs index 7dd6b1d0b..df1f5ee65 100644 --- a/src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs +++ b/src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs @@ -2,7 +2,7 @@ namespace Artemis.VisualScripting.Nodes.Text; -[Node("String Null or WhiteSpace", "Checks whether the string is null or white space.", +[Node("String Null or WhiteSpace", "Checks whether the string is null, empty or white space.", "Text", InputType = typeof(string), OutputType = typeof(bool))] public class StringNullOrWhiteSpaceNode : Node { @@ -10,20 +10,20 @@ public class StringNullOrWhiteSpaceNode : Node : base("Null or White Space", "Returns true if null or white space") { Input1 = CreateInputPin(); - TrueResult = CreateOutputPin("true (Empty)"); - FalseResult = CreateOutputPin("false (Not Empty)"); + NullOrWhiteSpaceResult = CreateOutputPin("White Space"); + HasContentResult = CreateOutputPin("Has Content"); } public InputPin Input1 { get; } - public OutputPin TrueResult { get; } + public OutputPin NullOrWhiteSpaceResult { get; } - public OutputPin FalseResult { get; } + public OutputPin HasContentResult { get; } public override void Evaluate() { bool isNullOrWhiteSpace = string.IsNullOrWhiteSpace(Input1.Value); - TrueResult.Value = isNullOrWhiteSpace; - FalseResult.Value = !isNullOrWhiteSpace; + NullOrWhiteSpaceResult.Value = isNullOrWhiteSpace; + HasContentResult.Value = !isNullOrWhiteSpace; } } \ No newline at end of file From 37f544a9c1f66f7cdfbcf86afaff88916d13b4cc Mon Sep 17 00:00:00 2001 From: "aytac.kayadelen" Date: Wed, 24 Aug 2022 13:12:00 +0300 Subject: [PATCH 04/15] simplify node output names --- src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs b/src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs index df1f5ee65..705946cdb 100644 --- a/src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs +++ b/src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs @@ -10,8 +10,8 @@ public class StringNullOrWhiteSpaceNode : Node : base("Null or White Space", "Returns true if null or white space") { Input1 = CreateInputPin(); - NullOrWhiteSpaceResult = CreateOutputPin("White Space"); - HasContentResult = CreateOutputPin("Has Content"); + NullOrWhiteSpaceResult = CreateOutputPin("true"); + HasContentResult = CreateOutputPin("false"); } public InputPin Input1 { get; } From 2e5977b55f530278a2b1c283849cc884fd6b670a Mon Sep 17 00:00:00 2001 From: "aytac.kayadelen" Date: Thu, 25 Aug 2022 10:17:54 +0300 Subject: [PATCH 05/15] omit "string" from descriptions --- .../Nodes/Text/StringLengthNode.cs | 6 ++-- .../Nodes/Text/StringNullOrEmptyNode.cs | 25 ++++++++++++++++ .../Nodes/Text/StrıngNullOrEmpty.cs | 29 ------------------- 3 files changed, 28 insertions(+), 32 deletions(-) create mode 100644 src/Artemis.VisualScripting/Nodes/Text/StringNullOrEmptyNode.cs delete mode 100644 src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs diff --git a/src/Artemis.VisualScripting/Nodes/Text/StringLengthNode.cs b/src/Artemis.VisualScripting/Nodes/Text/StringLengthNode.cs index f6a09ef66..421ad4dec 100644 --- a/src/Artemis.VisualScripting/Nodes/Text/StringLengthNode.cs +++ b/src/Artemis.VisualScripting/Nodes/Text/StringLengthNode.cs @@ -2,12 +2,12 @@ namespace Artemis.VisualScripting.Nodes.Text; -[Node("String Length", "Checks whether the first input is contained in the second input.", +[Node("Text Length", "Outputs the length of the input text.", "Text", InputType = typeof(string), OutputType = typeof(Numeric))] public class StringLengthNode : Node { public StringLengthNode() - : base("String Length", "Returns string length.") + : base("Text Length", "Outputs text length.") { Input1 = CreateInputPin(); Result = CreateOutputPin(); @@ -19,6 +19,6 @@ public class StringLengthNode : Node public override void Evaluate() { - Result.Value = new Numeric((Input1.Value ?? "").Length); + Result.Value = Input1.Value == null ? new Numeric(0) : new Numeric(Input1.Value.Length); } } \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Text/StringNullOrEmptyNode.cs b/src/Artemis.VisualScripting/Nodes/Text/StringNullOrEmptyNode.cs new file mode 100644 index 000000000..80db0442c --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Text/StringNullOrEmptyNode.cs @@ -0,0 +1,25 @@ +using Artemis.Core; + +namespace Artemis.VisualScripting.Nodes.Text; + +[Node("Text is empty", "Outputs true if the input text is empty, false if it contains any text.", + "Text", InputType = typeof(string), OutputType = typeof(bool))] +public class StringNullOrEmptyNode : Node +{ + public StringNullOrEmptyNode() + : base("Text is empty", "Outputs true if empty") + { + Input1 = CreateInputPin(); + Output1 = CreateOutputPin("true"); + } + + public InputPin Input1 { get; } + + public OutputPin Output1 { get; } + + public override void Evaluate() + { + bool isNullOrWhiteSpace = string.IsNullOrWhiteSpace(Input1.Value); + Output1.Value = isNullOrWhiteSpace; + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs b/src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs deleted file mode 100644 index 705946cdb..000000000 --- a/src/Artemis.VisualScripting/Nodes/Text/StrıngNullOrEmpty.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Artemis.Core; - -namespace Artemis.VisualScripting.Nodes.Text; - -[Node("String Null or WhiteSpace", "Checks whether the string is null, empty or white space.", - "Text", InputType = typeof(string), OutputType = typeof(bool))] -public class StringNullOrWhiteSpaceNode : Node -{ - public StringNullOrWhiteSpaceNode() - : base("Null or White Space", "Returns true if null or white space") - { - Input1 = CreateInputPin(); - NullOrWhiteSpaceResult = CreateOutputPin("true"); - HasContentResult = CreateOutputPin("false"); - } - - public InputPin Input1 { get; } - - public OutputPin NullOrWhiteSpaceResult { get; } - - public OutputPin HasContentResult { get; } - - public override void Evaluate() - { - bool isNullOrWhiteSpace = string.IsNullOrWhiteSpace(Input1.Value); - NullOrWhiteSpaceResult.Value = isNullOrWhiteSpace; - HasContentResult.Value = !isNullOrWhiteSpace; - } -} \ No newline at end of file From ce1cf948ce5a1fcd532f0ecd07e0802c331f9f0c Mon Sep 17 00:00:00 2001 From: "aytac.kayadelen" Date: Thu, 25 Aug 2022 14:08:22 +0300 Subject: [PATCH 06/15] remove 'Text Empty' node output name --- src/Artemis.VisualScripting/Nodes/Text/StringNullOrEmptyNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Artemis.VisualScripting/Nodes/Text/StringNullOrEmptyNode.cs b/src/Artemis.VisualScripting/Nodes/Text/StringNullOrEmptyNode.cs index 80db0442c..6c80e372f 100644 --- a/src/Artemis.VisualScripting/Nodes/Text/StringNullOrEmptyNode.cs +++ b/src/Artemis.VisualScripting/Nodes/Text/StringNullOrEmptyNode.cs @@ -10,7 +10,7 @@ public class StringNullOrEmptyNode : Node : base("Text is empty", "Outputs true if empty") { Input1 = CreateInputPin(); - Output1 = CreateOutputPin("true"); + Output1 = CreateOutputPin(); } public InputPin Input1 { get; } From da0d3abbddf4d7eb724677ffbc65ac0b7b901e71 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 25 Aug 2022 19:27:58 +0200 Subject: [PATCH 07/15] Nodes - Disconnect pins that changed their type but stayed an enum --- src/Artemis.Core/VisualScripting/InputPin.cs | 19 +++------ .../VisualScripting/Interfaces/IPin.cs | 3 +- src/Artemis.Core/VisualScripting/OutputPin.cs | 10 ++--- src/Artemis.Core/VisualScripting/Pin.cs | 39 +++++++++++++++++-- 4 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/Artemis.Core/VisualScripting/InputPin.cs b/src/Artemis.Core/VisualScripting/InputPin.cs index 108e7c620..9db82c693 100644 --- a/src/Artemis.Core/VisualScripting/InputPin.cs +++ b/src/Artemis.Core/VisualScripting/InputPin.cs @@ -94,18 +94,11 @@ public sealed class InputPin : Pin /// The new type of the pin. public void ChangeType(Type type) { - if (_type == type) + if (type == _type) return; - - // Disconnect pins incompatible with the new type - List toDisconnect = ConnectedTo.Where(p => !p.IsTypeCompatible(type)).ToList(); - foreach (IPin pin in toDisconnect) - DisconnectFrom(pin); - - // Change the type - SetAndNotify(ref _type, type, nameof(Type)); + + base.ChangeType(type, ref _type); Value = type.GetDefault(); - IsNumeric = type == typeof(Numeric); } private void Evaluate() @@ -117,10 +110,10 @@ public sealed class InputPin : Pin else Value = Type.GetDefault()!; } - else - { + else if (ConnectedTo.Count > 0) Value = ConnectedTo[0].PinValue; - } + else + Value = null; } #endregion diff --git a/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs b/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs index 819d57fc1..67a0619c3 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs @@ -85,6 +85,7 @@ public interface IPin /// Determines whether this pin is compatible with the given type /// /// The type to check for compatibility + /// A boolean indicating whether or not enums should be exactly equal or just both be enums /// if the type is compatible, otherwise . - public bool IsTypeCompatible(Type type); + public bool IsTypeCompatible(Type type, bool forgivingEnumMatching = true); } \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/OutputPin.cs b/src/Artemis.Core/VisualScripting/OutputPin.cs index f7d03e042..2e135eb13 100644 --- a/src/Artemis.Core/VisualScripting/OutputPin.cs +++ b/src/Artemis.Core/VisualScripting/OutputPin.cs @@ -84,15 +84,11 @@ public sealed class OutputPin : Pin /// The new type of the pin. public void ChangeType(Type type) { - // Disconnect pins incompatible with the new type - List toDisconnect = ConnectedTo.Where(p => !p.IsTypeCompatible(type)).ToList(); - foreach (IPin pin in toDisconnect) - DisconnectFrom(pin); + if (type == _type) + return; - // Change the type - SetAndNotify(ref _type, type, nameof(Type)); + base.ChangeType(type, ref _type); Value = type.GetDefault(); - IsNumeric = type == typeof(Numeric); } #endregion diff --git a/src/Artemis.Core/VisualScripting/Pin.cs b/src/Artemis.Core/VisualScripting/Pin.cs index 21e37c7f3..89fc2ce2a 100644 --- a/src/Artemis.Core/VisualScripting/Pin.cs +++ b/src/Artemis.Core/VisualScripting/Pin.cs @@ -127,14 +127,47 @@ public abstract class Pin : CorePropertyChanged, IPin } /// - public bool IsTypeCompatible(Type type) + public bool IsTypeCompatible(Type type, bool forgivingEnumMatching = true) { return Type == type - || (Type == typeof(Enum) && type.IsEnum) - || (Type.IsEnum && type == typeof(Enum)) + || (Direction == PinDirection.Input && Type == typeof(Enum) && type.IsEnum && forgivingEnumMatching) + || (Direction == PinDirection.Output && type == typeof(Enum) && Type.IsEnum && forgivingEnumMatching) || (Direction == PinDirection.Input && Type == typeof(object)) || (Direction == PinDirection.Output && type == typeof(object)); } + /// + /// Changes the type of this pin, disconnecting any pins that are incompatible with the new type. + /// + /// The new type of the pin. + /// The backing field of the current type of the pin. + protected void ChangeType(Type type, ref Type currentType) + { + // Enums are a special case that disconnect and, if still compatible, reconnect + if (type.IsEnum && currentType.IsEnum) + { + List connections = new(ConnectedTo); + DisconnectAll(); + + // Change the type + SetAndNotify(ref currentType, type, nameof(Type)); + IsNumeric = type == typeof(Numeric); + + foreach (IPin pin in connections.Where(p => p.IsTypeCompatible(type))) + ConnectTo(pin); + } + // Disconnect pins incompatible with the new type + else + { + List toDisconnect = ConnectedTo.Where(p => !p.IsTypeCompatible(type, false)).ToList(); + foreach (IPin pin in toDisconnect) + DisconnectFrom(pin); + + // Change the type + SetAndNotify(ref currentType, type, nameof(Type)); + IsNumeric = type == typeof(Numeric); + } + } + #endregion } \ No newline at end of file From 3f9de63b06a7ab75c546668181cee15489577476 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 21 Aug 2022 23:48:24 +0200 Subject: [PATCH 08/15] Aded operators to create numerics --- src/Artemis.Core/Utilities/Numeric.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Artemis.Core/Utilities/Numeric.cs b/src/Artemis.Core/Utilities/Numeric.cs index e5dca7fd5..36f04e14f 100644 --- a/src/Artemis.Core/Utilities/Numeric.cs +++ b/src/Artemis.Core/Utilities/Numeric.cs @@ -159,7 +159,12 @@ public readonly struct Numeric : IComparable, IConvertible { return (byte) Math.Clamp(p._value, 0, 255); } - + + public static implicit operator Numeric(double d) => new(d); + public static implicit operator Numeric(float f) => new(f); + public static implicit operator Numeric(int i) => new(i); + public static implicit operator Numeric(byte b) => new(b); + public static implicit operator long(Numeric p) { return (long) p._value; From 36f996eb1a5bc74ea8a665b03fb8f94124e0bc31 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 21 Aug 2022 23:51:01 +0200 Subject: [PATCH 09/15] Added some nodes --- .../Nodes/Color/LerpSKColorNode.cs | 57 ++++++++ .../Nodes/Color/RgbSKColorNode.cs | 38 +++++ .../Nodes/Mathematics/Clamp.cs | 39 +++++ .../Nodes/Mathematics/LerpNode.cs | 45 ++++++ .../Nodes/Mathematics/RangeNode.cs | 48 +++++++ .../Nodes/Mathematics/Saturate.cs | 35 +++++ .../Nodes/Static/RandomNumericValueNode.cs | 32 +++++ .../Nodes/Timing/DelayNode.cs | 117 +++++++++++++++ .../Nodes/Timing/EdgeNode.cs | 41 ++++++ .../Nodes/Timing/FlipFlopNode.cs | 45 ++++++ .../Nodes/Timing/LatchNode.cs | 113 +++++++++++++++ .../Nodes/Timing/SequencerNode.cs | 136 ++++++++++++++++++ 12 files changed, 746 insertions(+) create mode 100644 src/Artemis.VisualScripting/Nodes/Color/LerpSKColorNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Color/RgbSKColorNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Mathematics/Clamp.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Mathematics/LerpNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Mathematics/RangeNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Mathematics/Saturate.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Static/RandomNumericValueNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Timing/DelayNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Timing/EdgeNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Timing/FlipFlopNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Timing/LatchNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/Timing/SequencerNode.cs diff --git a/src/Artemis.VisualScripting/Nodes/Color/LerpSKColorNode.cs b/src/Artemis.VisualScripting/Nodes/Color/LerpSKColorNode.cs new file mode 100644 index 000000000..0ca5125e0 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Color/LerpSKColorNode.cs @@ -0,0 +1,57 @@ +using Artemis.Core; +using RGB.NET.Core; +using SkiaSharp; + +namespace Artemis.VisualScripting.Nodes.Color; + +[Node("Lerp (Color)", "Interpolates linear between the two colors A and B", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))] +public class LerpSKColorNode : Node +{ + #region Properties & Fields + + public InputPin A { get; } + public InputPin B { get; } + public InputPin T { get; } + + public OutputPin Result { get; } + + #endregion + + #region Constructors + + public LerpSKColorNode() + : base("Lerp", "Interpolates linear between the two values A and B") + { + A = CreateInputPin("A"); + B = CreateInputPin("B"); + T = CreateInputPin("T"); + + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() + { + SKColor a = A.Value; + SKColor b = B.Value; + float t = ((float)T.Value).Clamp(0f, 1f); + + float aAlpha = a.Alpha.GetPercentageFromByteValue(); + float aRed = a.Red.GetPercentageFromByteValue(); + float aGreen = a.Green.GetPercentageFromByteValue(); + float aBlue = a.Blue.GetPercentageFromByteValue(); + + float alpha = ((b.Alpha.GetPercentageFromByteValue() - aAlpha) * t) + aAlpha; + float red = ((b.Red.GetPercentageFromByteValue() - aRed) * t) + aRed; + float green = ((b.Green.GetPercentageFromByteValue() - aGreen) * t) + aGreen; + float blue = ((b.Blue.GetPercentageFromByteValue() - aBlue) * t) + aBlue; + + Result.Value = new SKColor(red.GetByteValueFromPercentage(), green.GetByteValueFromPercentage(), blue.GetByteValueFromPercentage(), alpha.GetByteValueFromPercentage()); + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Color/RgbSKColorNode.cs b/src/Artemis.VisualScripting/Nodes/Color/RgbSKColorNode.cs new file mode 100644 index 000000000..338ab17da --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Color/RgbSKColorNode.cs @@ -0,0 +1,38 @@ +using Artemis.Core; +using SkiaSharp; + +namespace Artemis.VisualScripting.Nodes.Color; + +[Node("RGB Color", "Creates a color from red, green and blue values", "Color", InputType = typeof(Numeric), OutputType = typeof(SKColor))] +public class RgbSKColorNode : Node +{ + #region Properties & Fields + + public InputPin R { get; set; } + public InputPin G { get; set; } + public InputPin B { get; set; } + public OutputPin Output { get; } + + #endregion + + #region Constructors + + public RgbSKColorNode() + : base("RGB Color", "Creates a color from red, green and blue values") + { + R = CreateInputPin("R"); + G = CreateInputPin("G"); + B = CreateInputPin("B"); + + Output = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() => Output.Value = new SKColor(R.Value, G.Value, B.Value); + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Mathematics/Clamp.cs b/src/Artemis.VisualScripting/Nodes/Mathematics/Clamp.cs new file mode 100644 index 000000000..2fcb944f1 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Mathematics/Clamp.cs @@ -0,0 +1,39 @@ +using Artemis.Core; +using RGB.NET.Core; + +namespace Artemis.VisualScripting.Nodes.Mathematics; + +[Node("Clamp", "Clamps the value to be in between min and max", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))] +public class ClampNode : Node +{ + #region Properties & Fields + + public InputPin Value { get; } + public InputPin Min { get; } + public InputPin Max { get; } + + public OutputPin Result { get; } + + #endregion + + #region Constructors + + public ClampNode() + : base("Clamp", "Clamps the value to be in between min and max") + { + Value = CreateInputPin("Value"); + Min = CreateInputPin("Min"); + Max = CreateInputPin("Max"); + + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() => Result.Value = ((float)Value.Value).Clamp(Min.Value, Max.Value); + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Mathematics/LerpNode.cs b/src/Artemis.VisualScripting/Nodes/Mathematics/LerpNode.cs new file mode 100644 index 000000000..23273208c --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Mathematics/LerpNode.cs @@ -0,0 +1,45 @@ +using Artemis.Core; +using RGB.NET.Core; + +namespace Artemis.VisualScripting.Nodes.Mathematics; + +[Node("Lerp", "Interpolates linear between the two values A and B", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))] +public class LerpNode : Node +{ + #region Properties & Fields + + public InputPin A { get; } + public InputPin B { get; } + public InputPin T { get; } + + public OutputPin Result { get; } + + #endregion + + #region Constructors + + public LerpNode() + : base("Lerp", "Interpolates linear between the two values A and B") + { + A = CreateInputPin("A"); + B = CreateInputPin("B"); + T = CreateInputPin("T"); + + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() + { + float a = A.Value; + float b = B.Value; + float t = ((float)T.Value).Clamp(0f, 1f); + Result.Value = ((b - a) * t) + a; + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Mathematics/RangeNode.cs b/src/Artemis.VisualScripting/Nodes/Mathematics/RangeNode.cs new file mode 100644 index 000000000..74e6cc50d --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Mathematics/RangeNode.cs @@ -0,0 +1,48 @@ +using Artemis.Core; +using RGB.NET.Core; + +namespace Artemis.VisualScripting.Nodes.Mathematics; + +[Node("Range", "Selects the best integer value in the given range by the given percentage", "Static", InputType = typeof(Numeric), OutputType = typeof(Numeric))] +public class RangeNode : Node +{ + #region Properties & Fields + + public InputPin Min { get; } + public InputPin Max { get; } + public InputPin Percentage { get; } + + public OutputPin Result { get; } + + #endregion + + #region Constructors + + public RangeNode() + : base("Range", "Selects the best integer value in the given range by the given percentage") + { + Min = CreateInputPin("Min"); + Max = CreateInputPin("Max"); + Percentage = CreateInputPin("Percentage"); + + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() + { + int min = Min.Value; + int max = Max.Value; + float percentage = ((float)Percentage.Value).Clamp(0f, 1f); + + int range = max - min; + + Result.Value = percentage >= 1.0f ? max : ((int)(percentage * (range + 1)) + min); + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Mathematics/Saturate.cs b/src/Artemis.VisualScripting/Nodes/Mathematics/Saturate.cs new file mode 100644 index 000000000..5c75da96b --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Mathematics/Saturate.cs @@ -0,0 +1,35 @@ +using Artemis.Core; +using RGB.NET.Core; + +namespace Artemis.VisualScripting.Nodes.Mathematics; + +[Node("Saturate", "Clamps the value to be in between 0 and 1", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))] +public class SaturateNode : Node +{ + #region Properties & Fields + + public InputPin Value { get; } + + public OutputPin Result { get; } + + #endregion + + #region Constructors + + public SaturateNode() + : base("Clamp", "Clamps the value to be in between 0 and 1") + { + Value = CreateInputPin(); + + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() => Result.Value = ((float)Value.Value).Clamp(0f, 1f); + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Static/RandomNumericValueNode.cs b/src/Artemis.VisualScripting/Nodes/Static/RandomNumericValueNode.cs new file mode 100644 index 000000000..a883d0dcb --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Static/RandomNumericValueNode.cs @@ -0,0 +1,32 @@ +using Artemis.Core; + +namespace Artemis.VisualScripting.Nodes.Static; + +[Node("Random", "Generates a random value between 0 and 1", "Static", OutputType = typeof(Numeric))] +public class RandomNumericValueNode : Node +{ + #region Properties & Fields + + private static readonly Random RANDOM = new(); + + public OutputPin Output { get; } + + #endregion + + #region Constructors + + public RandomNumericValueNode() + : base("Random", "Generates a random value between 0 and 1") + { + Output = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() => Output.Value = RANDOM.NextSingle(); + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Timing/DelayNode.cs b/src/Artemis.VisualScripting/Nodes/Timing/DelayNode.cs new file mode 100644 index 000000000..2dee0a8e1 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Timing/DelayNode.cs @@ -0,0 +1,117 @@ +using System.Diagnostics; +using Artemis.Core; +using Artemis.Core.Events; +using RGB.NET.Core; + +namespace Artemis.VisualScripting.Nodes.Timing; + +[Node("Delay", "Delays the resolution of the input pin(s) for the given time after each update", "Timing", InputType = typeof(object), OutputType = typeof(object))] +public class DelayNode : Node +{ + #region Properties & Fields + + private long _lastUpdateTimestamp = 0; + + public InputPin Delay { get; } + public InputPinCollection Input { get; } + + public OutputPin IsUpdated { get; } + public OutputPin NextUpdateTime { get; } + + private Dictionary _pinPairs = new(); + + #endregion + + #region Constructors + + public DelayNode() + : base("Delay", "Delays the resolution of the input pin(s) for the given time after each update") + { + Delay = CreateInputPin("Delay"); + Input = CreateInputPinCollection(typeof(object), initialCount: 0); + + IsUpdated = CreateOutputPin("Updated"); + NextUpdateTime = CreateOutputPin("Next Update"); + + Input.PinAdded += OnInputPinAdded; + Input.PinRemoved += OnInputPinRemoved; + + Input.Add(Input.CreatePin()); + } + + #endregion + + #region Methods + + private void OnInputPinAdded(object? sender, SingleValueEventArgs args) + { + IPin inputPin = args.Value; + _pinPairs.Add(inputPin, CreateOutputPin(typeof(object))); + + inputPin.PinConnected += OnInputPinConnected; + inputPin.PinDisconnected += OnInputPinDisconnected; + + UpdatePinNames(); + } + + private void OnInputPinRemoved(object? sender, SingleValueEventArgs args) + { + IPin inputPin = args.Value; + RemovePin(_pinPairs[inputPin]); + _pinPairs.Remove(inputPin); + + inputPin.PinConnected -= OnInputPinConnected; + inputPin.PinDisconnected -= OnInputPinDisconnected; + + UpdatePinNames(); + } + + private void OnInputPinConnected(object? sender, SingleValueEventArgs args) + { + if (sender is not IPin inputPin || !_pinPairs.ContainsKey(inputPin)) return; + + OutputPin outputPin = _pinPairs[inputPin]; + outputPin.ChangeType(args.Value.Type); + } + + private void OnInputPinDisconnected(object? sender, SingleValueEventArgs args) + { + if (sender is not IPin inputPin || !_pinPairs.ContainsKey(inputPin)) return; + + OutputPin outputPin = _pinPairs[inputPin]; + outputPin.ChangeType(typeof(object)); + } + + private void UpdatePinNames() + { + int counter = 1; + foreach (IPin inputPin in Input.Pins) + { + string name = counter.ToString(); + inputPin.Name = name; + _pinPairs[inputPin].Name = name; + + counter++; + } + } + + /// + public override void Evaluate() + { + double nextUpdateIn = Delay.Value - TimerHelper.GetElapsedTime(_lastUpdateTimestamp); + NextUpdateTime.Value = nextUpdateIn; + + if (nextUpdateIn <= 0) + { + IsUpdated.Value = true; + foreach ((IPin input, OutputPin output) in _pinPairs) + output.Value = input.PinValue; + + _lastUpdateTimestamp = Stopwatch.GetTimestamp(); + } + else + IsUpdated.Value = false; + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Timing/EdgeNode.cs b/src/Artemis.VisualScripting/Nodes/Timing/EdgeNode.cs new file mode 100644 index 000000000..cf019289e --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Timing/EdgeNode.cs @@ -0,0 +1,41 @@ +using Artemis.Core; + +namespace Artemis.VisualScripting.Nodes.Timing; + +[Node("Edge", "Outputs true on each edge when the input changes", "Timing", InputType = typeof(bool), OutputType = typeof(bool))] +public class EdgeNode : Node +{ + #region Properties & Fields + + private bool _lastInput; + + public InputPin Input { get; } + public OutputPin Output { get; } + + #endregion + + #region Constructors + + public EdgeNode() + : base("Edge", "Outputs true on each edge when the input changes") + { + Input = CreateInputPin(); + Output = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() + { + bool input = Input.Value; + + Output.Value = input != _lastInput; + + _lastInput = input; + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Timing/FlipFlopNode.cs b/src/Artemis.VisualScripting/Nodes/Timing/FlipFlopNode.cs new file mode 100644 index 000000000..62b6c8f21 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Timing/FlipFlopNode.cs @@ -0,0 +1,45 @@ +using Artemis.Core; + +namespace Artemis.VisualScripting.Nodes.Timing; + +[Node("FlipFlop", "Inverts the output when the input changes from false to true", "Timing", InputType = typeof(bool), OutputType = typeof(bool))] +public class FlipFlopNode : Node +{ + #region Properties & Fields + + private bool _lastInput; + private bool _currentValue; + + public InputPin Input { get; } + public OutputPin Output { get; } + + #endregion + + #region Constructors + + public FlipFlopNode() + : base("FlipFlop", "Inverts the output when the input changes from false to true") + { + Input = CreateInputPin(); + Output = CreateOutputPin(); + } + + #endregion + + #region Methods + + /// + public override void Evaluate() + { + bool input = Input.Value; + if (input && !_lastInput) + { + _currentValue = !_currentValue; + Output.Value = _currentValue; + } + + _lastInput = input; + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Timing/LatchNode.cs b/src/Artemis.VisualScripting/Nodes/Timing/LatchNode.cs new file mode 100644 index 000000000..a509741b2 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Timing/LatchNode.cs @@ -0,0 +1,113 @@ +using System.Diagnostics; +using Artemis.Core; +using Artemis.Core.Events; +using RGB.NET.Core; + +namespace Artemis.VisualScripting.Nodes.Timing; + +[Node("Latch", "Only passes the input to the output as long as the control-pin is true. If the control pin is false the last passed value is provided.", "Timing", InputType = typeof(object), OutputType = typeof(object))] +public class LatchNode : Node +{ + #region Properties & Fields + + private long _lastUpdateTimestamp = 0; + + public InputPin Control { get; } + public InputPinCollection Input { get; } + + //TODO DarthAffe 21.08.2022: Find something to output to aling in- and outputs + public OutputPin LastUpdateTime { get; } + + private Dictionary _pinPairs = new(); + + #endregion + + #region Constructors + + public LatchNode() + : base("Latch", "Only passes the input to the output as long as the control-pin is true. If the control pin is false the last passed value is provided.") + { + Control = CreateInputPin("Control"); + Input = CreateInputPinCollection(typeof(object), initialCount: 0); + + LastUpdateTime = CreateOutputPin("Last Update"); + + Input.PinAdded += OnInputPinAdded; + Input.PinRemoved += OnInputPinRemoved; + + Input.Add(Input.CreatePin()); + } + + #endregion + + #region Methods + + private void OnInputPinAdded(object? sender, SingleValueEventArgs args) + { + IPin inputPin = args.Value; + _pinPairs.Add(inputPin, CreateOutputPin(typeof(object))); + + inputPin.PinConnected += OnInputPinConnected; + inputPin.PinDisconnected += OnInputPinDisconnected; + + UpdatePinNames(); + } + + private void OnInputPinRemoved(object? sender, SingleValueEventArgs args) + { + IPin inputPin = args.Value; + RemovePin(_pinPairs[inputPin]); + _pinPairs.Remove(inputPin); + + inputPin.PinConnected -= OnInputPinConnected; + inputPin.PinDisconnected -= OnInputPinDisconnected; + + UpdatePinNames(); + } + + private void OnInputPinConnected(object? sender, SingleValueEventArgs args) + { + if (sender is not IPin inputPin || !_pinPairs.ContainsKey(inputPin)) return; + + OutputPin outputPin = _pinPairs[inputPin]; + outputPin.ChangeType(args.Value.Type); + } + + private void OnInputPinDisconnected(object? sender, SingleValueEventArgs args) + { + if (sender is not IPin inputPin || !_pinPairs.ContainsKey(inputPin)) return; + + OutputPin outputPin = _pinPairs[inputPin]; + outputPin.ChangeType(typeof(object)); + } + + private void UpdatePinNames() + { + int counter = 1; + foreach (IPin inputPin in Input.Pins) + { + string name = counter.ToString(); + inputPin.Name = name; + _pinPairs[inputPin].Name = name; + + counter++; + } + } + + /// + public override void Evaluate() + { + if (Control.Value) + { + foreach ((IPin input, OutputPin output) in _pinPairs) + output.Value = input.PinValue; + + LastUpdateTime.Value = 0; + _lastUpdateTimestamp = Stopwatch.GetTimestamp(); + } + else + LastUpdateTime.Value = TimerHelper.GetElapsedTime(_lastUpdateTimestamp); + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Timing/SequencerNode.cs b/src/Artemis.VisualScripting/Nodes/Timing/SequencerNode.cs new file mode 100644 index 000000000..6fd2c0ec5 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Timing/SequencerNode.cs @@ -0,0 +1,136 @@ +using Artemis.Core; +using Artemis.Core.Events; + +namespace Artemis.VisualScripting.Nodes.Timing; + +[Node("Sequencer", "Advances on input every time the control has a rising edge (change to true)", "Timing", OutputType = typeof(object))] +public class SequencerNode : Node +{ + #region Properties & Fields + + private int _currentIndex; + private Type _currentType; + private bool _updating; + private IPin? _currentCyclePin; + + private bool _lastInput; + + public InputPin Input { get; } + public InputPinCollection CycleValues { get; } + + public OutputPin Output { get; } + + #endregion + + #region Constructors + + public SequencerNode() + : base("Sequencer", "Advances on input every time the control has a rising edge (change to true)") + { + _currentType = typeof(object); + + Input = CreateInputPin("Control"); + CycleValues = CreateInputPinCollection(typeof(object), "", 0); + Output = CreateOutputPin(typeof(object)); + + CycleValues.PinAdded += CycleValuesOnPinAdded; + CycleValues.PinRemoved += CycleValuesOnPinRemoved; + CycleValues.Add(CycleValues.CreatePin()); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + bool input = Input.Value; + + if (input != _lastInput) + { + _currentIndex++; + + if (_currentIndex >= CycleValues.Count()) + _currentIndex = 0; + + _currentCyclePin = null; + } + + _currentCyclePin ??= CycleValues.ElementAt(_currentIndex); + + object? outputValue = _currentCyclePin.PinValue; + if (Output.Type.IsInstanceOfType(outputValue)) + Output.Value = outputValue; + else if (Output.Type.IsValueType) + Output.Value = Output.Type.GetDefault()!; + + _lastInput = input; + } + + private void CycleValuesOnPinAdded(object? sender, SingleValueEventArgs e) + { + e.Value.PinConnected += OnPinConnected; + e.Value.PinDisconnected += OnPinDisconnected; + + _currentCyclePin = null; + } + + private void CycleValuesOnPinRemoved(object? sender, SingleValueEventArgs e) + { + e.Value.PinConnected -= OnPinConnected; + e.Value.PinDisconnected -= OnPinDisconnected; + + _currentCyclePin = null; + } + + private void OnPinDisconnected(object? sender, SingleValueEventArgs e) => ProcessPinDisconnected(); + + private void OnPinConnected(object? sender, SingleValueEventArgs e) => ProcessPinConnected(e.Value); + + private void ProcessPinConnected(IPin source) + { + if (_updating) + return; + + try + { + _updating = true; + + // No need to change anything if the types haven't changed + if (_currentType != source.Type) + ChangeCurrentType(source.Type); + } + finally + { + _updating = false; + } + } + + private void ChangeCurrentType(Type type) + { + CycleValues.ChangeType(type); + Output.ChangeType(type); + + _currentType = type; + } + + private void ProcessPinDisconnected() + { + if (_updating) + return; + try + { + // If there's still a connected pin, stick to the current type + if (CycleValues.Any(v => v.ConnectedTo.Any())) + return; + + ChangeCurrentType(typeof(object)); + } + finally + { + _updating = false; + } + } + + #endregion +} \ No newline at end of file From 4ed6d10f38a8749a7d3bb5e8b3debf884a15de65 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 25 Aug 2022 20:34:00 +0200 Subject: [PATCH 10/15] Nodes - Added IList support Nodes - Added simple List operator node --- src/Artemis.Core/VisualScripting/Pin.cs | 8 ++-- .../Screens/VisualScripting/CableView.axaml | 4 ++ .../Nodes/List/ListOperatorNode.cs | 46 +++++++++++++++++++ .../Screens/ListOperatorNodeCustomView.axaml | 11 +++++ .../ListOperatorNodeCustomView.axaml.cs | 19 ++++++++ .../ListOperatorNodeCustomViewModel.cs | 27 +++++++++++ .../Screens/DisplayValueNodeCustomView.axaml | 4 ++ .../Nodes/Static/StaticStringValueNode.cs | 4 +- .../Nodes/Text/StringFormatNode.cs | 2 +- 9 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 src/Artemis.VisualScripting/Nodes/List/ListOperatorNode.cs create mode 100644 src/Artemis.VisualScripting/Nodes/List/Screens/ListOperatorNodeCustomView.axaml create mode 100644 src/Artemis.VisualScripting/Nodes/List/Screens/ListOperatorNodeCustomView.axaml.cs create mode 100644 src/Artemis.VisualScripting/Nodes/List/Screens/ListOperatorNodeCustomViewModel.cs diff --git a/src/Artemis.Core/VisualScripting/Pin.cs b/src/Artemis.Core/VisualScripting/Pin.cs index 89fc2ce2a..a562f7961 100644 --- a/src/Artemis.Core/VisualScripting/Pin.cs +++ b/src/Artemis.Core/VisualScripting/Pin.cs @@ -130,10 +130,10 @@ public abstract class Pin : CorePropertyChanged, IPin public bool IsTypeCompatible(Type type, bool forgivingEnumMatching = true) { return Type == type + || (Direction == PinDirection.Input && type.IsAssignableTo(Type)) + || (Direction == PinDirection.Output && type.IsAssignableFrom(Type)) || (Direction == PinDirection.Input && Type == typeof(Enum) && type.IsEnum && forgivingEnumMatching) - || (Direction == PinDirection.Output && type == typeof(Enum) && Type.IsEnum && forgivingEnumMatching) - || (Direction == PinDirection.Input && Type == typeof(object)) - || (Direction == PinDirection.Output && type == typeof(object)); + || (Direction == PinDirection.Output && type == typeof(Enum) && Type.IsEnum && forgivingEnumMatching); } /// @@ -168,6 +168,6 @@ public abstract class Pin : CorePropertyChanged, IPin IsNumeric = type == typeof(Numeric); } } - + #endregion } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/VisualScripting/CableView.axaml b/src/Artemis.UI/Screens/VisualScripting/CableView.axaml index 2c4dc4ee5..c882bda2c 100644 --- a/src/Artemis.UI/Screens/VisualScripting/CableView.axaml +++ b/src/Artemis.UI/Screens/VisualScripting/CableView.axaml @@ -7,6 +7,7 @@ xmlns:skiaSharp="clr-namespace:SkiaSharp;assembly=SkiaSharp" xmlns:shared="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" + xmlns:collections="clr-namespace:System.Collections;assembly=System.Runtime" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.VisualScripting.CableView" x:DataType="visualScripting:CableViewModel" @@ -79,6 +80,9 @@ + + + diff --git a/src/Artemis.VisualScripting/Nodes/List/ListOperatorNode.cs b/src/Artemis.VisualScripting/Nodes/List/ListOperatorNode.cs new file mode 100644 index 000000000..78614819d --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/List/ListOperatorNode.cs @@ -0,0 +1,46 @@ +using System.Collections; +using Artemis.Core; +using Artemis.VisualScripting.Nodes.List.Screens; + +namespace Artemis.VisualScripting.Nodes.List; + +[Node("List Operator", "Checks if any/all/no value in the input list matches the input value", "List", InputType = typeof(IEnumerable), OutputType = typeof(bool))] +public class ListOperatorNode : Node +{ + public ListOperatorNode() : base("List Operator", "Checks if any/all/no value in the input list matches the input value") + { + InputList = CreateInputPin(); + InputValue = CreateInputPin(); + + Ouput = CreateOutputPin(); + } + + public InputPin InputList { get; } + public InputPin InputValue { get; } + public OutputPin Ouput { get; } + + /// + public override void Evaluate() + { + if (InputList.Value == null) + { + Ouput.Value = Storage == ListOperator.None; + return; + } + + object? input = InputValue.Value; + if (Storage == ListOperator.Any) + Ouput.Value = InputList.Value.Cast().Any(v => v.Equals(input)); + else if (Storage == ListOperator.All) + Ouput.Value = InputList.Value.Cast().All(v => v.Equals(input)); + else if (Storage == ListOperator.All) + Ouput.Value = InputList.Value.Cast().All(v => !v.Equals(input)); + } +} + +public enum ListOperator +{ + Any, + All, + None +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/List/Screens/ListOperatorNodeCustomView.axaml b/src/Artemis.VisualScripting/Nodes/List/Screens/ListOperatorNodeCustomView.axaml new file mode 100644 index 000000000..faa4e5543 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/List/Screens/ListOperatorNodeCustomView.axaml @@ -0,0 +1,11 @@ + + + diff --git a/src/Artemis.VisualScripting/Nodes/List/Screens/ListOperatorNodeCustomView.axaml.cs b/src/Artemis.VisualScripting/Nodes/List/Screens/ListOperatorNodeCustomView.axaml.cs new file mode 100644 index 000000000..000d729e8 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/List/Screens/ListOperatorNodeCustomView.axaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.VisualScripting.Nodes.List.Screens; + +public partial class ListOperatorNodeCustomView : ReactiveUserControl +{ + public ListOperatorNodeCustomView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/List/Screens/ListOperatorNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/List/Screens/ListOperatorNodeCustomViewModel.cs new file mode 100644 index 000000000..134e6e1f1 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/List/Screens/ListOperatorNodeCustomViewModel.cs @@ -0,0 +1,27 @@ +using Artemis.Core; +using Artemis.UI.Shared.Services.NodeEditor; +using Artemis.UI.Shared.Services.NodeEditor.Commands; +using Artemis.UI.Shared.VisualScripting; +using ReactiveUI; + +namespace Artemis.VisualScripting.Nodes.List.Screens; + +public class ListOperatorNodeCustomViewModel : CustomNodeViewModel +{ + private readonly ListOperatorNode _node; + private readonly INodeEditorService _nodeEditorService; + + public ListOperatorNodeCustomViewModel(ListOperatorNode node, INodeScript script, INodeEditorService nodeEditorService) : base(node, script) + { + _node = node; + _nodeEditorService = nodeEditorService; + + NodeModified += (_, _) => this.RaisePropertyChanged(nameof(CurrentValue)); + } + + public ListOperator CurrentValue + { + get => _node.Storage; + set => _nodeEditorService.ExecuteCommand(Script, new UpdateStorage(_node, value)); + } +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Static/Screens/DisplayValueNodeCustomView.axaml b/src/Artemis.VisualScripting/Nodes/Static/Screens/DisplayValueNodeCustomView.axaml index ca473031e..100dd461b 100644 --- a/src/Artemis.VisualScripting/Nodes/Static/Screens/DisplayValueNodeCustomView.axaml +++ b/src/Artemis.VisualScripting/Nodes/Static/Screens/DisplayValueNodeCustomView.axaml @@ -6,6 +6,7 @@ xmlns:skiaSharp="clr-namespace:SkiaSharp;assembly=SkiaSharp" xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" xmlns:converters1="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" + xmlns:collections="clr-namespace:System.Collections;assembly=System.Runtime" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.VisualScripting.Nodes.Static.Screens.DisplayValueNodeCustomView" x:DataType="screens:DisplayValueNodeCustomViewModel"> @@ -43,6 +44,9 @@ + + + diff --git a/src/Artemis.VisualScripting/Nodes/Static/StaticStringValueNode.cs b/src/Artemis.VisualScripting/Nodes/Static/StaticStringValueNode.cs index bb886af23..a18093d8e 100644 --- a/src/Artemis.VisualScripting/Nodes/Static/StaticStringValueNode.cs +++ b/src/Artemis.VisualScripting/Nodes/Static/StaticStringValueNode.cs @@ -3,13 +3,13 @@ using Artemis.VisualScripting.Nodes.Static.Screens; namespace Artemis.VisualScripting.Nodes.Static; -[Node("String-Value", "Outputs a configurable static string value.", "Static", OutputType = typeof(string))] +[Node("Text-Value", "Outputs a configurable static text value.", "Static", OutputType = typeof(string))] public class StaticStringValueNode : Node { #region Constructors public StaticStringValueNode() - : base("String", "Outputs a configurable string value.") + : base("Text", "Outputs a configurable text value.") { Output = CreateOutputPin(); } diff --git a/src/Artemis.VisualScripting/Nodes/Text/StringFormatNode.cs b/src/Artemis.VisualScripting/Nodes/Text/StringFormatNode.cs index ef498bd59..915bb10d5 100644 --- a/src/Artemis.VisualScripting/Nodes/Text/StringFormatNode.cs +++ b/src/Artemis.VisualScripting/Nodes/Text/StringFormatNode.cs @@ -2,7 +2,7 @@ namespace Artemis.VisualScripting.Nodes.Text; -[Node("Format", "Formats the input string.", "Text", InputType = typeof(object), OutputType = typeof(string))] +[Node("Format", "Formats the input text.", "Text", InputType = typeof(object), OutputType = typeof(string))] public class StringFormatNode : Node { #region Constructors From 94fb47275e142bd009cefc6a9bfe4dfeecdc9c22 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 26 Aug 2022 09:06:46 +0200 Subject: [PATCH 11/15] API Docks - Updated styling --- docfx/docfx_project/api/index.md | 28 +- docfx/docfx_project/articles/intro.md | 1 - .../articles/plugins_getting_started.md | 3 - docfx/docfx_project/articles/toc.yml | 4 - docfx/docfx_project/docfx.json | 21 +- docfx/docfx_project/filterConfig.yml | 3 + docfx/docfx_project/images/application.ico | Bin 0 -> 113388 bytes docfx/docfx_project/images/logo-512.png | Bin 22804 -> 0 bytes .../templates/artemis/favicon.ico | Bin 112887 -> 0 bytes .../artemis/partials/logo.tmpl.partial | 6 - .../templates/artemis/styles/artemis.css | 22 - .../material/partials/head.tmpl.partial | 38 - .../templates/material/styles/main.css | 314 -------- .../templates/singulinkfx/layout/_master.tmpl | 62 ++ .../singulinkfx/partials/footer.tmpl.partial | 4 + .../singulinkfx/partials/head.tmpl.partial | 24 + .../singulinkfx/partials/li.tmpl.partial | 31 + .../singulinkfx/partials/logo.tmpl.partial | 6 + .../partials/namespace.tmpl.partial | 13 + .../singulinkfx/partials/navbar.tmpl.partial | 19 + .../singulinkfx/partials/scripts.tmpl.partial | 12 + .../partials/searchResults.tmpl.partial | 9 + .../singulinkfx/partials/toc.tmpl.partial | 5 + .../templates/singulinkfx/styles/config.css | 114 +++ .../templates/singulinkfx/styles/discord.css | 681 ++++++++++++++++++ .../singulinkfx/styles/down-arrow.svg | 44 ++ .../styles/jquery.twbsPagination.js | 317 ++++++++ .../templates/singulinkfx/styles/main.css | 0 .../templates/singulinkfx/styles/main.js | 0 .../singulinkfx/styles/singulink.css | 471 ++++++++++++ .../templates/singulinkfx/styles/singulink.js | 38 + .../templates/singulinkfx/styles/url.min.js | 1 + .../templates/singulinkfx/toc.html.tmpl | 22 + docfx/docfx_project/toc.yml | 4 - 34 files changed, 1915 insertions(+), 402 deletions(-) delete mode 100644 docfx/docfx_project/articles/intro.md delete mode 100644 docfx/docfx_project/articles/plugins_getting_started.md delete mode 100644 docfx/docfx_project/articles/toc.yml create mode 100644 docfx/docfx_project/images/application.ico delete mode 100644 docfx/docfx_project/images/logo-512.png delete mode 100644 docfx/docfx_project/templates/artemis/favicon.ico delete mode 100644 docfx/docfx_project/templates/artemis/partials/logo.tmpl.partial delete mode 100644 docfx/docfx_project/templates/artemis/styles/artemis.css delete mode 100644 docfx/docfx_project/templates/material/partials/head.tmpl.partial delete mode 100644 docfx/docfx_project/templates/material/styles/main.css create mode 100644 docfx/docfx_project/templates/singulinkfx/layout/_master.tmpl create mode 100644 docfx/docfx_project/templates/singulinkfx/partials/footer.tmpl.partial create mode 100644 docfx/docfx_project/templates/singulinkfx/partials/head.tmpl.partial create mode 100644 docfx/docfx_project/templates/singulinkfx/partials/li.tmpl.partial create mode 100644 docfx/docfx_project/templates/singulinkfx/partials/logo.tmpl.partial create mode 100644 docfx/docfx_project/templates/singulinkfx/partials/namespace.tmpl.partial create mode 100644 docfx/docfx_project/templates/singulinkfx/partials/navbar.tmpl.partial create mode 100644 docfx/docfx_project/templates/singulinkfx/partials/scripts.tmpl.partial create mode 100644 docfx/docfx_project/templates/singulinkfx/partials/searchResults.tmpl.partial create mode 100644 docfx/docfx_project/templates/singulinkfx/partials/toc.tmpl.partial create mode 100644 docfx/docfx_project/templates/singulinkfx/styles/config.css create mode 100644 docfx/docfx_project/templates/singulinkfx/styles/discord.css create mode 100644 docfx/docfx_project/templates/singulinkfx/styles/down-arrow.svg create mode 100644 docfx/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.js create mode 100644 docfx/docfx_project/templates/singulinkfx/styles/main.css create mode 100644 docfx/docfx_project/templates/singulinkfx/styles/main.js create mode 100644 docfx/docfx_project/templates/singulinkfx/styles/singulink.css create mode 100644 docfx/docfx_project/templates/singulinkfx/styles/singulink.js create mode 100644 docfx/docfx_project/templates/singulinkfx/styles/url.min.js create mode 100644 docfx/docfx_project/templates/singulinkfx/toc.html.tmpl diff --git a/docfx/docfx_project/api/index.md b/docfx/docfx_project/api/index.md index 78dc9c005..0d7007d2d 100644 --- a/docfx/docfx_project/api/index.md +++ b/docfx/docfx_project/api/index.md @@ -1,2 +1,26 @@ -# PLACEHOLDER -TODO: Add .NET projects to the *src* folder and run `docfx` to generate **REAL** *API Documentation*! + +# Welcome to the **Artemis API documentation** +On this website you can browse the Artemis Core and Shared UI API. +A large part of this documentation is being generated based on code but over time the plan is to expand the documentation with written guides. + + +## Plugins +Artemis 2.0 has been developed from the ground up with plugins in mind. This means almost all functionality can be expanded. The following plugin types are currently available and fully implemented: + - [DeviceProvider](api/Artemis.Core.DeviceProviders.DeviceProvider.html) + - [LayerBrush\](api/Artemis.Core.LayerBrushes.LayerBrush-1.html) + - [PerLedLayerBrush\](api/Artemis.Core.LayerBrushes.PerLedLayerBrush-1.html) + - [LayerEffect](api/Artemis.Core.LayerEffects.LayerEffect-1.html) + - [Module](api/Artemis.Core.Modules.Module.html) + - [Module\](api/Artemis.Core.Modules.Module-1.html) + +These allow you to expand on Artemis's functionality. For quick and interactive plugin creation, use the [Visual Studio template extension](https://marketplace.visualstudio.com/items?itemName=SpoinkyNL.ArtemisTemplates). + +Example implementations of these plugins can be found on [GitHub](https://github.com/Artemis-RGB/Artemis/tree/master/src/Plugins). + +## Services +Artemis provides plugins with an API through a range of services. +All the services are available to plugins by using dependency injection in your plugin's constructor. Dependency injection is also available for the different view models plugins may provide. + +- [Core Services](api/Artemis.Core.Services.html#interfaces) +- [UI Services](api/Artemis.UI.Shared.Services.html#interfaces) + diff --git a/docfx/docfx_project/articles/intro.md b/docfx/docfx_project/articles/intro.md deleted file mode 100644 index b0db1682f..000000000 --- a/docfx/docfx_project/articles/intro.md +++ /dev/null @@ -1 +0,0 @@ -Bummer, no guides 😌 \ No newline at end of file diff --git a/docfx/docfx_project/articles/plugins_getting_started.md b/docfx/docfx_project/articles/plugins_getting_started.md deleted file mode 100644 index 484967e77..000000000 --- a/docfx/docfx_project/articles/plugins_getting_started.md +++ /dev/null @@ -1,3 +0,0 @@ -Plugins allow you to expand on Artemis's functionality. For quick and interactive plugin creation, use the [Visual Studio template extension](https://marketplace.visualstudio.com/items?itemName=SpoinkyNL.ArtemisTemplates). - -Example implementations of these plugins can be found on [GitHub](https://github.com/Artemis-RGB/Artemis/tree/master/src/Plugins). \ No newline at end of file diff --git a/docfx/docfx_project/articles/toc.yml b/docfx/docfx_project/articles/toc.yml deleted file mode 100644 index 2b505c1d9..000000000 --- a/docfx/docfx_project/articles/toc.yml +++ /dev/null @@ -1,4 +0,0 @@ -- name: Introduction - href: intro.md -- name: Plugins - Getting started - href: plugins_getting_started.md \ No newline at end of file diff --git a/docfx/docfx_project/docfx.json b/docfx/docfx_project/docfx.json index 30f8b4c7a..65522fb8e 100644 --- a/docfx/docfx_project/docfx.json +++ b/docfx/docfx_project/docfx.json @@ -20,14 +20,11 @@ "content": [ { "files": [ - "api/**.yml", - "api/index.md" + "api/**.yml" ] }, { "files": [ - "articles/**.md", - "articles/**/toc.yml", "toc.yml", "*.md" ] @@ -52,16 +49,24 @@ } ], "globalMetadata": { - "_appTitle": "Artemis documentation", - "_enableSearch": true + "_appTitle": "Artemis API Documentation", + "_appName": "Artemis", + "_appFaviconPath": "images/application.ico", + "_appLogoPath": "images/application.ico", + "_appFooter": "\r \r\r\r \r\r\r \r\r\r \r\r\r \r", + "_copyrightFooter": "Content is available under the PolyForm Noncommercial License, by Artemis RGB.", + "_enableSearch": true, + "_disableSideFilter": false, + "_enableNewTab": true, + "_disableContribution": false, + "_disableBreadcrumb": false }, "dest": "_site", "globalMetadataFiles": [], "fileMetadataFiles": [], "template": [ "default", - "templates/artemis", - "templates/material" + "templates/singulinkfx" ], "postProcessors": [], "markdownEngineName": "markdig", diff --git a/docfx/docfx_project/filterConfig.yml b/docfx/docfx_project/filterConfig.yml index fff7b04d2..d56cc1bd4 100644 --- a/docfx/docfx_project/filterConfig.yml +++ b/docfx/docfx_project/filterConfig.yml @@ -11,6 +11,9 @@ apiRules: - exclude: uidRegex: ^Stylet type: Type +- exclude: + uidRegex: ^Artemis\.Core\.CorePropertyChanged + type: Type - exclude: uidRegex: ^Artemis\.Core\.Properties type: Type diff --git a/docfx/docfx_project/images/application.ico b/docfx/docfx_project/images/application.ico new file mode 100644 index 0000000000000000000000000000000000000000..bdb89dbd0af2610300c383d3f10662157b49ccfe GIT binary patch literal 113388 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 literal 0 HcmV?d00001 diff --git a/docfx/docfx_project/images/logo-512.png b/docfx/docfx_project/images/logo-512.png deleted file mode 100644 index d1cd8ebe70ce8ac82eef0dc9d52f8e4408481c5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22804 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 L4tDo8a8CUXw*0?E diff --git a/docfx/docfx_project/templates/artemis/favicon.ico b/docfx/docfx_project/templates/artemis/favicon.ico deleted file mode 100644 index 015d1ffa8531e7e0a01ff368edb15cec40428223..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 112887 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 O3_AG^`RsI+)ZYP5w$HZ! diff --git a/docfx/docfx_project/templates/artemis/partials/logo.tmpl.partial b/docfx/docfx_project/templates/artemis/partials/logo.tmpl.partial deleted file mode 100644 index f4da7f2c7..000000000 --- a/docfx/docfx_project/templates/artemis/partials/logo.tmpl.partial +++ /dev/null @@ -1,6 +0,0 @@ -{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - - - - Artemis logo - diff --git a/docfx/docfx_project/templates/artemis/styles/artemis.css b/docfx/docfx_project/templates/artemis/styles/artemis.css deleted file mode 100644 index 2024ec68d..000000000 --- a/docfx/docfx_project/templates/artemis/styles/artemis.css +++ /dev/null @@ -1,22 +0,0 @@ -.sidefilter { - width: 320px; -} - -.sidetoc { - width: 320px; -} - -@media only screen and (max-width: 768px) -.article.grid-right { - margin-left: 0; -} - -.article.grid-right { - margin-left: 340px; -} - -@media (min-width: 1500px) { -.container { - width: 1470px; -} -} \ No newline at end of file diff --git a/docfx/docfx_project/templates/material/partials/head.tmpl.partial b/docfx/docfx_project/templates/material/partials/head.tmpl.partial deleted file mode 100644 index 9f55e9338..000000000 --- a/docfx/docfx_project/templates/material/partials/head.tmpl.partial +++ /dev/null @@ -1,38 +0,0 @@ -{{!Copyright (c) Oscar Vasquez. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - - - - - {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} - - - - {{#_description}}{{/_description}} - - - - - - - - {{#_noindex}}{{/_noindex}} - {{#_enableSearch}}{{/_enableSearch}} - {{#_enableNewTab}}{{/_enableNewTab}} - - -

- - \ No newline at end of file diff --git a/docfx/docfx_project/templates/material/styles/main.css b/docfx/docfx_project/templates/material/styles/main.css deleted file mode 100644 index 8fb996768..000000000 --- a/docfx/docfx_project/templates/material/styles/main.css +++ /dev/null @@ -1,314 +0,0 @@ -/* COLOR VARIABLES*/ -:root { - --header-bg-color: #009688; - --header-ft-color: #fff; - --highlight-light: #1de9b6; - --highlight-dark: #00bfa5; - --accent-dim: #e0e0e0; - --accent-super-dim: #f3f3f3; - --font-color: #34393e; - --card-box-shadow: 0 1px 2px 0 rgba(61, 65, 68, 0.06), 0 1px 3px 1px rgba(61, 65, 68, 0.16); - --search-box-shadow: 0 1px 2px 0 rgba(41, 45, 48, 0.36), 0 1px 3px 1px rgba(41, 45, 48, 0.46); - --transition: 350ms; -} - -body { - color: var(--font-color); - font-family: "Roboto", sans-serif; - line-height: 1.5; - font-size: 16px; - -ms-text-size-adjust: 100%; - -webkit-text-size-adjust: 100%; - word-wrap: break-word; -} - -/* HIGHLIGHT COLOR */ - -button, -a { - color: var(--highlight-dark); - cursor: pointer; -} - -button:hover, -button:focus, -a:hover, -a:focus { - color: var(--highlight-light); - text-decoration: none; -} - -.toc .nav > li.active > a { - color: var(--highlight-dark); -} - -.toc .nav > li.active > a:hover, -.toc .nav > li.active > a:focus { - color: var(--highlight-light); -} - -.pagination > .active > a { - background-color: var(--header-bg-color); - border-color: var(--header-bg-color); -} - -.pagination > .active > a, -.pagination > .active > a:focus, -.pagination > .active > a:hover, -.pagination > .active > span, -.pagination > .active > span:focus, -.pagination > .active > span:hover { - background-color: var(--highlight-light); - border-color: var(--highlight-light); -} - -.affix ul > li.active > a, .affix ul > li.active > a:before { - color: var(--highlight-dark); -} - -.affix > ul > li.active > a, .affix > ul > li.active > a:before { - color: var(--highlight-dark); -} - -/* HEADINGS */ - -h1 { - font-weight: 600; - font-size: 32px; -} - -h2 { - font-weight: 600; - font-size: 24px; - line-height: 1.8; -} - -h3 { - font-weight: 600; - font-size: 20px; - line-height: 1.8; -} - -h5 { - font-size: 14px; - padding: 10px 0px; -} - -article h1, -article h2, -article h3, -article h4 { - margin-top: 35px; - margin-bottom: 15px; -} - -article h4 { - padding-bottom: 8px; - border-bottom: 2px solid #ddd; -} - -/* NAVBAR */ - -.navbar-brand > img { - color: var(--header-ft-color); -} - -.navbar { - border: none; - /* Both navbars use box-shadow */ - -webkit-box-shadow: var(--card-box-shadow); - -moz-box-shadow: var(--card-box-shadow); - box-shadow: var(--card-box-shadow); -} - -.subnav { - border-top: 1px solid #ddd; - background-color: #fff; -} - -.navbar-inverse { - background-color: var(--header-bg-color); - z-index: 100; -} - -.navbar-inverse .navbar-nav > li > a, -.navbar-inverse .navbar-text { - color: var(--header-ft-color); - background-color: var(--header-bg-color); - border-bottom: 3px solid transparent; - padding-bottom: 12px; - transition: 350ms; -} - -.navbar-inverse .navbar-nav > li > a:focus, -.navbar-inverse .navbar-nav > li > a:hover { - color: var(--header-ft-color); - background-color: var(--header-bg-color); - border-bottom: 3px solid white; -} - -.navbar-inverse .navbar-nav > .active > a, -.navbar-inverse .navbar-nav > .active > a:focus, -.navbar-inverse .navbar-nav > .active > a:hover { - color: var(--header-ft-color); - background-color: var(--header-bg-color); - border-bottom: 3px solid white; -} - -.navbar-form .form-control { - border: 0; - border-radius: 4px; - box-shadow: var(--search-box-shadow); - transition:var(--transition); -} - -.navbar-form .form-control:hover { - background-color: var(--accent-dim); -} - -/* NAVBAR TOGGLED (small screens) */ - -.navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form { - border: none; -} -.navbar-inverse .navbar-toggle { - box-shadow: var(--card-box-shadow); - border: none; -} - -.navbar-inverse .navbar-toggle:focus, -.navbar-inverse .navbar-toggle:hover { - background-color: var(--highlight-dark); -} - -/* SIDEBAR */ - -.toc .level1 > li { - font-weight: 400; -} - -.toc .nav > li > a { - color: var(--font-color); -} - -.sidefilter { - background-color: #fff; - border-left: none; - border-right: none; -} - -.sidefilter { - background-color: #fff; - border-left: none; - border-right: none; -} - -.toc-filter { - padding: 5px; - margin: 0; - box-shadow: var(--card-box-shadow); - transition:var(--transition); -} - -.toc-filter:hover { - background-color: var(--accent-super-dim); -} - -.toc-filter > input { - border: none; - background-color: inherit; - transition: inherit; -} - -.toc-filter > .filter-icon { - display: none; -} - -.sidetoc > .toc { - background-color: #fff; - overflow-x: hidden; -} - -.sidetoc { - background-color: #fff; - border: none; -} - -/* ALERTS */ - -.alert { - padding: 0px 0px 5px 0px; - color: inherit; - background-color: inherit; - border: none; - box-shadow: var(--card-box-shadow); -} - -.alert > p { - margin-bottom: 0; - padding: 5px 10px; -} - -.alert > ul { - margin-bottom: 0; - padding: 5px 40px; -} - -.alert > h5 { - padding: 10px 15px; - margin-top: 0; - text-transform: uppercase; - font-weight: bold; - border-radius: 4px 4px 0 0; -} - -.alert-info > h5 { - color: #1976d2; - border-bottom: 4px solid #1976d2; - background-color: #e3f2fd; -} - -.alert-warning > h5 { - color: #f57f17; - border-bottom: 4px solid #f57f17; - background-color: #fff3e0; -} - -.alert-danger > h5 { - color: #d32f2f; - border-bottom: 4px solid #d32f2f; - background-color: #ffebee; -} - -/* CODE HIGHLIGHT */ -pre { - padding: 9.5px; - margin: 0 0 10px; - font-size: 13px; - word-break: break-all; - word-wrap: break-word; - background-color: #fffaef; - border-radius: 4px; - border: none; - box-shadow: var(--card-box-shadow); -} - -/* STYLE FOR IMAGES */ - -.article .small-image { - margin-top: 15px; - box-shadow: var(--card-box-shadow); - max-width: 350px; -} - -.article .medium-image { - margin-top: 15px; - box-shadow: var(--card-box-shadow); - max-width: 550px; -} - -.article .large-image { - margin-top: 15px; - box-shadow: var(--card-box-shadow); - max-width: 700px; -} \ No newline at end of file diff --git a/docfx/docfx_project/templates/singulinkfx/layout/_master.tmpl b/docfx/docfx_project/templates/singulinkfx/layout/_master.tmpl new file mode 100644 index 000000000..a309bf597 --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/layout/_master.tmpl @@ -0,0 +1,62 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} +{{!include(/^styles/.*/)}} +{{!include(/^fonts/.*/)}} +{{!include(favicon.ico)}} +{{!include(logo.svg)}} +{{!include(search-stopwords.json)}} + + + + {{>partials/head}} + + +
+ + + + + {{>partials/logo}} +
+ +
+
+ +
+
+ {{>partials/navbar}} +
+ {{^_disableToc}} + {{>partials/toc}} + {{/_disableToc}} +
+ {{>partials/footer}} +
+ +
+ {{#_enableSearch}} + {{>partials/searchResults}} + {{/_enableSearch}} + +
+ {{^_disableBreadcrumb}} + {{>partials/breadcrumb}} + {{/_disableBreadcrumb}} + +
+ {{!body}} +
+
+ + {{#_copyrightFooter}} +
+ {{_copyrightFooter}} +
+ {{/_copyrightFooter}} +
+
+ + {{>partials/scripts}} + + diff --git a/docfx/docfx_project/templates/singulinkfx/partials/footer.tmpl.partial b/docfx/docfx_project/templates/singulinkfx/partials/footer.tmpl.partial new file mode 100644 index 000000000..dd601a975 --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/partials/footer.tmpl.partial @@ -0,0 +1,4 @@ +
+ {{{_appFooter}}} + {{^_appFooter}}DocFX + Singulink = ♥{{/_appFooter}} +
\ No newline at end of file diff --git a/docfx/docfx_project/templates/singulinkfx/partials/head.tmpl.partial b/docfx/docfx_project/templates/singulinkfx/partials/head.tmpl.partial new file mode 100644 index 000000000..01f8b6393 --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/partials/head.tmpl.partial @@ -0,0 +1,24 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + + + {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} + + + + {{#_description}}{{/_description}} + + + + + + + + + + + {{#_noindex}}{{/_noindex}} + {{#_enableSearch}}{{/_enableSearch}} + {{#_enableNewTab}}{{/_enableNewTab}} + \ No newline at end of file diff --git a/docfx/docfx_project/templates/singulinkfx/partials/li.tmpl.partial b/docfx/docfx_project/templates/singulinkfx/partials/li.tmpl.partial new file mode 100644 index 000000000..2c8a3d0e7 --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/partials/li.tmpl.partial @@ -0,0 +1,31 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + +
    + {{#items}} + {{^dropdown}} +
  • + {{^leaf}} + + {{/leaf}} + {{#topicHref}} + {{name}} + {{/topicHref}} + {{^topicHref}} + {{{name}}} + {{/topicHref}} + + {{^leaf}} + {{>partials/li}} + {{/leaf}} +
  • + {{/dropdown}} + {{#dropdown}} +
  • + {{name}} +
      + {{>partials/dd-li}} +
    +
  • + {{/dropdown}} + {{/items}} +
diff --git a/docfx/docfx_project/templates/singulinkfx/partials/logo.tmpl.partial b/docfx/docfx_project/templates/singulinkfx/partials/logo.tmpl.partial new file mode 100644 index 000000000..738ab5b6f --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/partials/logo.tmpl.partial @@ -0,0 +1,6 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + {{_appName}} + {{_appName}} + \ No newline at end of file diff --git a/docfx/docfx_project/templates/singulinkfx/partials/namespace.tmpl.partial b/docfx/docfx_project/templates/singulinkfx/partials/namespace.tmpl.partial new file mode 100644 index 000000000..42d64e69b --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/partials/namespace.tmpl.partial @@ -0,0 +1,13 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + +

{{>partials/title}}

+
{{{summary}}}
+
{{{conceptual}}}
+
{{{remarks}}}
+{{#children}} +

{{>partials/namespaceSubtitle}}

+ {{#children}} +
+
{{{summary}}}
+ {{/children}} +{{/children}} diff --git a/docfx/docfx_project/templates/singulinkfx/partials/navbar.tmpl.partial b/docfx/docfx_project/templates/singulinkfx/partials/navbar.tmpl.partial new file mode 100644 index 000000000..cfddfd830 --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/partials/navbar.tmpl.partial @@ -0,0 +1,19 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + +
+
+ {{>partials/logo}} +
+ + {{#_enableSearch}} +
+
+ + +
+
+ {{/_enableSearch}} + +
+
+
\ No newline at end of file diff --git a/docfx/docfx_project/templates/singulinkfx/partials/scripts.tmpl.partial b/docfx/docfx_project/templates/singulinkfx/partials/scripts.tmpl.partial new file mode 100644 index 000000000..90fb7d576 --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/partials/scripts.tmpl.partial @@ -0,0 +1,12 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + + + + + + + + + \ No newline at end of file diff --git a/docfx/docfx_project/templates/singulinkfx/partials/searchResults.tmpl.partial b/docfx/docfx_project/templates/singulinkfx/partials/searchResults.tmpl.partial new file mode 100644 index 000000000..9f08c90e3 --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/partials/searchResults.tmpl.partial @@ -0,0 +1,9 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + +
+

{{__global.searchResults}}

+
+

+
+
    +
    \ No newline at end of file diff --git a/docfx/docfx_project/templates/singulinkfx/partials/toc.tmpl.partial b/docfx/docfx_project/templates/singulinkfx/partials/toc.tmpl.partial new file mode 100644 index 000000000..c660966b6 --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/partials/toc.tmpl.partial @@ -0,0 +1,5 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + +
    +
    +
    diff --git a/docfx/docfx_project/templates/singulinkfx/styles/config.css b/docfx/docfx_project/templates/singulinkfx/styles/config.css new file mode 100644 index 000000000..9f3ca697b --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/styles/config.css @@ -0,0 +1,114 @@ +/* Theme Configuration Options */ + +:root +{ + /* General */ + + --base-font-size: 16px; + --smalldevice-base-font-size: 14px; /* Base font size for devices < 1024px */ + + --main-bg-color: #1f1f23; + --footer-bg-color: rgba(0,0,0,.4); + --separator-color: #42474f; + + --table-strip-bg-color: #151515; + --table-header-bg-color: black; + --table-header-color: hsla(0,0%,100%,.8); + --table-header-border-color: #040405; + + /* Text */ + + --appname-color: white; + + --h1-color: white; + --h2-color: #f2f2f2; + --h3-color: #e3e3e3; + --h4-color: #ffffff; + --h5-color: #e0e0e0; + + --text-color: #e1e1e1; + --link-color: #00b0f4; + --link-hover-color: #2ec4ff; + + /* Mobile Topbar */ + + --topbar-bg-color: #18191c; + + /* Button */ + + --button-color: #747f8d; + + /* Sidebar */ + + --sidebar-width: 400px; + --sidebar-bg-color: #292B30; + + --search-color: #bdbdbd; + --search-bg-color: #1b1e21; + --search-searchicon-color: #e3e3e3; + --search-border-color: black; + + --sidebar-item-color: white; + --sidebar-active-item-color: #00b0f4; + --sidebar-level1-item-bg-color: #222429; + --sidebar-level1-item-hover-bg-color: #1D1F22; + + --toc-filter-color: #bdbdbd; + --toc-filter-bg-color: #1b1e21; + --toc-filter-filtericon-color: #e3e3e3; + --toc-filter-clearicon-color: #e68585; + --toc-filter-border-color: black; + + /* Scrollbars */ + + --scrollbar-bg-color: transparent; + --scrollbar-thumb-bg-color: rgba(0,0,0,.4); + --scrollbar-thumb-border-color: transparent; + + /* Alerts and Blocks */ + + --alert-info-border-color: rgba(114,137,218,.5); + --alert-info-bg-color: rgba(114,137,218,.1); + + --alert-warning-border-color: rgba(250,166,26,.5); + --alert-warning-bg-color: rgba(250,166,26,.1); + + --alert-danger-border-color: rgba(240,71,71,.5); + --alert-danger-bg-color: rgba(240,71,71,.1); + + --alert-tip-border-color: rgba(255,255,255,.5); + --alert-tip-bg-color: rgba(255,255,255,.1); + + --blockquote-border-color: rgba(255,255,255,.5); + --blockquote-bg-color: rgba(255,255,255,.1); + + --breadcrumb-bg-color: #2f3136; + + /* Inline Code */ + + --ref-bg-color: black; + --ref-color: #89d4f1; + + /* Code Blocks */ + + --code-bg-color: #151515; + --code-color: #d6deeb; + --code-keyword-color: #569cd6; + --code-comment-color: #57a64a; + --code-macro-color: #beb7ff; + --code-string-color: #d69d85; + --code-string-escape-color: #ffd68f; + --code-field-color: #c8c8c8; + --code-function-color: #dcdcaa; + --code-control-color: #d8a0df; + --code-class-color: #4ec9b0; + --code-number-color: #b5cea8; + --code-params-color: #9a9a9a; + --code-breakpoint-color: #8c2f2f; +} + +/* Code Block Overrides */ + +pre, legend { + --scrollbar-thumb-bg-color: #333; +} \ No newline at end of file diff --git a/docfx/docfx_project/templates/singulinkfx/styles/discord.css b/docfx/docfx_project/templates/singulinkfx/styles/discord.css new file mode 100644 index 000000000..bb3e55a06 --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/styles/discord.css @@ -0,0 +1,681 @@ +/* Discord Style */ + +::-webkit-scrollbar { + width: 10px; +} + +::-webkit-scrollbar-track { + background: var(--scrollbar-bg-color); +} + +::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb-bg-color); + border-color: var(--scrollbar-thumb-border-color); + border-radius: 5px; +} + +::marker { + unicode-bidi: isolate; + font-variant-numeric: tabular-nums; + text-transform: none; + text-indent: 0px !important; + text-align: start !important; + text-align-last: start !important; +} + +*, :after, :before +{ + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html, body +{ + padding: 0; + margin: 0; + font: 15px/150% 'Roboto', sans-serif; + overflow: hidden; + color: var(--text-color); + background-color: var(--main-bg-color); + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + +img { + max-width: 100%; +} + +ul > li, ol > li { + display: list-item; +} + +h1,h2,h3,h4,h5 +{ + color: var(--link-active-color); + position: relative; +} + +h1, h2 +{ + margin-block-start: 2em; +} + +h3 +{ + margin-block-start: 1em; + font-weight: 300; + font-size: 1.5em; + color: var(--h3-color); + margin-block-start: 3em; +} + +h4 +{ + opacity: 1; + color: var(--h4-color); + font-size: large; + border-bottom: 2px solid var(--separator-color); + margin: 20px 0 0 0; +} + + +h5 { + margin-block-end: .8em; + margin-block-start: 1em; + font-size: .85em; + font-weight: 500; + color: var(--h5-color); +} + +h6 { + font-size: .75em; + margin: 0; +} + +p +{ + font-weight: 400; +} + +ul +{ + position: relative; +} + +ul, ol +{ + padding-inline-start: 3em; +} + +ul.level1 +{ + list-style-type: none; + padding-inline-start: 0; +} + +ul.level2, ul.level3 +{ + padding-inline-start: 1em; + list-style-type: none; + font-size: .9em; +} + +a +{ + color: var(--link-color); + text-decoration: none; + transition: color .25s; +} + +a:focus, a:hover +{ + color: var(--link-hover-color); + text-decoration: underline; +} + +a.anchorjs-link:hover { + text-decoration: none; +} + +a.active, a:active +{ + color: var(--link-active-color); +} + +.body-content +{ + display: flex; + flex-direction: row; + height: 100%; + overflow-x: hidden; + overflow-y: hidden; +} + +.page-title +{ + margin-block-start: 0; +} + +nav +{ + width: 300px; + transition: left .5s ease-out; + position: fixed; + left: -350px; + top: 40px; + bottom: 0; + background-color: var(--sidebar-bg-color); + overflow-y: auto; + + display: flex; + flex-direction: column; + + z-index: 1000; +} + +h1:first-child +{ + margin-block-start: 1.1em; + margin-top: 1.1em; +} + +.sidebar +{ + padding: 32px 17px 32px 32px; + flex: 1; +} + +.sidebar-item +{ + font-size: 1em; + font-weight: 400; + display: block; + padding: 4px 16px; + color: var(--sidebar-item-color); +} + +.sidebar-item.large, #navbar .sidebar-item +{ + padding: 8px 16px; +} + +a.sidebar-item:hover, a.sidebar-item:focus +{ + color: var(--link-active-color); + text-decoration: none; +} + +a.sidebar-item.active +{ + color: var(--link-active-color); +} + +ul.level1 > li > a.sidebar-item +{ + background-color: transparent; + border-radius: 4px; +} + +#toc ul.level1 > li > a.sidebar-item.active +{ + background-color: var(--link-active-bg-color); +} + +.sidebar-item-separator +{ + height: 2px; + width: 100%; + background-color: var(--separator-color); + margin: 2em 0; + opacity: .8; +} + +span.sidebar-item +{ + font-weight: 700; + text-transform: uppercase; + font-size: .8em; + color: var(--text-color); + margin-block-start: 1.25em; +} + +.main-panel +{ + background-color: var(--main-bg-color); + flex: 1; + overflow-y: auto; + padding: 20px 40px; +} + +.top-navbar +{ + display: flex; + flex-direction: row; + align-items: center; + padding: 0 40px; + height: 40px; + background-color: var(--topbar-bg-color); +} + +.burger-icon +{ + margin-right: 1em; + color: var(--button-color); +} + +.burger-icon:hover, .burger-icon:focus +{ + color: var(--link-active-color); +} + +.burger-icon.active, .burger-icon:active +{ + color: var(--link-active-color); +} + +.brand +{ + display: flex; + align-items: center; + justify-content: start; +} + +.logomark +{ + height: 28px; +} + +.brand-title +{ + padding: 0 .5em; + font-size: .9em; + color: var(--link-active-color); +} + +.footer +{ + background-color: var(--footer-bg-color); + padding: 20px; + margin: 0 20px 20px 20px; + border-radius: 8px; + color: var(--link-active-color); +} + +.footer > h4 +{ + margin-block-start: 0; +} + +.blackout +{ + display: block; + visibility: hidden; + position: absolute; + z-index: 100; + top: 40px; + bottom: 0; + left: 0; + right: 0; + background-color: var(--footer-bg-color); +} + +@keyframes showThat { + 0% { opacity: 0; visibility: hidden; } + 1% { opacity: 0; visibility: visible; } + 100% { opacity: 1; visibility: visible;} +} + +@keyframes hideThat { + 0% { opacity: 1; visibility: visible; } + 99% { opacity: 0; visibility: visible; } + 100% { opacity: 0; visibility: hidden;} +} + +.showThat +{ + animation: showThat .5s forwards; +} + +.hideThat +{ + animation: hideThat .5s forwards; +} + + + +@media (min-width: 1024px) +{ + nav + { + position: relative; + left: 0!important; + top: 0; + bottom: 0; + } + + .top-navbar + { + display: none; + } + + .blackout + { + display: none; + } +} + +/* Table */ + +.table-responsive +{ + overflow-x: auto; + margin-bottom: 64px; +} + +table +{ + background-color: var(--code-bg-color); + border-collapse: collapse; + width: 100%; + table-layout: auto; +} + +table.table-striped tbody tr:nth-child(2n) +{ + background-color: var(--table-strip-bg-color); +} + +table thead +{ + background: var(--table-header-bg-color); +} + +table th +{ + color: var(--table-header-color); + text-transform: uppercase; + font-size: 12px; + line-height: 15px; + border-bottom: 1px solid var(--table-header-border-color); + padding: 8px; +} + +.table-condensed th { + text-align: left; +} + +table td +{ + padding: 8px; + font-weight: 300; +} + +table td > p +{ + margin: 0; +} + +/* Alerts */ +.alert { + border-radius: 4px; + padding: 8px; + margin: 25px 0; +} + +.alert > h5 +{ + display: none; + margin: 0; +} + +.alert > p +{ + margin: 0; + font-weight: 300; + font-size: 13px; +} + +.alert.alert-info +{ + border: 2px solid var(--alert-info-border-color); + background: var(--alert-info-bg-color); +} + +.alert.alert-warning +{ + border: 2px solid var(--alert-warning-border-color); + background: var(--alert-warning-bg-color); +} + +.alert.alert-danger +{ + border: 2px solid var(--alert-danger-border-color); + background: var(--alert-danger-bg-color); +} + +.TIP.alert.alert-info +{ + border: 2px solid var(--alert-tip-border-color); + background: var(--alert-tip-bg-color); +} + +blockquote { + margin: 8px 0; + border-left: 4px solid var(--blockquote-border-color); + padding: 8px; + background: var(--blockquote-bg-color); + border-radius: 4px; +} + +blockquote > p { + margin: 0; + font-style: italic; + font-size: 13px; +} + + +/* Breadcrumb */ + +#breadcrumb +{ + padding: 8px 16px; + background: var(--breadcrumb-bg-color); + border-radius: 4px; + margin-bottom: 30px; +} + +#breadcrumb:empty +{ + display: none; +} + +ul.breadcrumb +{ + display: flex; + flex-direction: row; + margin: 0; +} + +ul.breadcrumb > li { + margin-right: 6px; +} + +ul.breadcrumb > li::before +{ + content: "/"; + margin-right: 5px; +} + +ul.breadcrumb > li:first-child::before +{ + content: ""; + margin: 0; +} + + +/* Code */ + +legend, pre +{ + display: block; + background-color: var(--code-bg-color); + padding: 16px; + border-radius: 4px; +} + +code +{ + background-color: var(--code-bg-color); + padding: 2px 4px; + border-radius: 4px; +} + +.hljs +{ + background: transparent; +} + +/* DocFX related */ + +.small { + font-size: .9em; +} + +.pull-right +{ + float: right; +} + +.hide +{ + display: none; +} + +@media (max-width: 1023.98px) +{ + .mobile-hide + { + display: none; + } +} + +li +{ + display: block; + position: relative; +} + +.expand-stub +{ + cursor: pointer; + position: absolute; + width: 20px; + height: 20px; + left: -10px; +} + +ul.level1 > li > .expand-stub +{ + display: none; +} + +.toc .nav > li > .expand-stub::before, .toc .nav > li.active > .expand-stub::before +{ + content: " "; + position: absolute; + transform: rotate(-90deg); + width: 10px; + height: 10px; + top: 5px; + left: 5px; + background-repeat: no-repeat; + background: url(down-arrow.svg); +} + +.toc .nav > li.active > .expand-stub::before, .toc .nav > li.in > .expand-stub::before, .toc .nav > li.in.active > .expand-stub::before, .toc .nav > li.filtered > .expand-stub::before +{ + transform: none; +} + +li > ul +{ + display: none; +} + +li.in > ul +{ + display: block; +} + +ul.level2 > li > a.sidebar-item, +ul.level3 > li > a.sidebar-item +{ + font-weight: 500; + font-size: .95em; + padding: 0; + margin: 2px 16px; +} + +ul.level2 > li > a.sidebar-item +{ + color: var(--sidebar-item-2nd-color); +} + +ul.level3 > li > a.sidebar-item +{ + color: var(--sidebar-item-3rd-color); +} + +ul.level2 > li > a.sidebar-item:hover, +ul.level2 > li > a.sidebar-item:focus, +ul.level3 > li > a.sidebar-item:hover, +ul.level3 > li > a.sidebar-item:focus +{ + color: var(--link-active-color); + text-decoration: underline; +} + +ul.level2 > li > a.sidebar-item.active, +ul.level3 > li > a.sidebar-item.active +{ + color: var(--link-active-color); +} + +.inheritance .level0:before, +.inheritance .level1:before, +.inheritance .level2:before, +.inheritance .level3:before, +.inheritance .level4:before, +.inheritance .level5:before { + content: '↳'; + margin-right: 5px; +} + +.inheritance .level0 { + margin-left: 0em; +} + +.inheritance .level1 { + margin-left: 1em; +} + +.inheritance .level2 { + margin-left: 2em; +} + +.inheritance .level3 { + margin-left: 3em; +} + +.inheritance .level4 { + margin-left: 4em; +} + +.inheritance .level5 { + margin-left: 5em; +} \ No newline at end of file diff --git a/docfx/docfx_project/templates/singulinkfx/styles/down-arrow.svg b/docfx/docfx_project/templates/singulinkfx/styles/down-arrow.svg new file mode 100644 index 000000000..e086126a2 --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/styles/down-arrow.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docfx/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.js b/docfx/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.js new file mode 100644 index 000000000..332c01c62 --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/styles/jquery.twbsPagination.js @@ -0,0 +1,317 @@ +/*! + * jQuery pagination plugin v1.4.1 + * http://esimakin.github.io/twbs-pagination/ + * + * Copyright 2014-2016, Eugene Simakin + * Released under Apache 2.0 license + * http://apache.org/licenses/LICENSE-2.0.html + */ +(function ($, window, document, undefined) { + + 'use strict'; + + var old = $.fn.twbsPagination; + + // PROTOTYPE AND CONSTRUCTOR + + var TwbsPagination = function (element, options) { + this.$element = $(element); + this.options = $.extend({}, $.fn.twbsPagination.defaults, options); + + if (this.options.startPage < 1 || this.options.startPage > this.options.totalPages) { + throw new Error('Start page option is incorrect'); + } + + this.options.totalPages = parseInt(this.options.totalPages); + if (isNaN(this.options.totalPages)) { + throw new Error('Total pages option is not correct!'); + } + + this.options.visiblePages = parseInt(this.options.visiblePages); + if (isNaN(this.options.visiblePages)) { + throw new Error('Visible pages option is not correct!'); + } + + if (this.options.onPageClick instanceof Function) { + this.$element.first().on('page', this.options.onPageClick); + } + + // hide if only one page exists + if (this.options.hideOnlyOnePage && this.options.totalPages == 1) { + this.$element.trigger('page', 1); + return this; + } + + if (this.options.totalPages < this.options.visiblePages) { + this.options.visiblePages = this.options.totalPages; + } + + if (this.options.href) { + this.options.startPage = this.getPageFromQueryString(); + if (!this.options.startPage) { + this.options.startPage = 1; + } + } + + var tagName = (typeof this.$element.prop === 'function') ? + this.$element.prop('tagName') : this.$element.attr('tagName'); + + if (tagName === 'UL') { + this.$listContainer = this.$element; + } else { + this.$listContainer = $('
      '); + } + + this.$listContainer.addClass(this.options.paginationClass); + + if (tagName !== 'UL') { + this.$element.append(this.$listContainer); + } + + if (this.options.initiateStartPageClick) { + this.show(this.options.startPage); + } else { + this.render(this.getPages(this.options.startPage)); + this.setupEvents(); + } + + return this; + }; + + TwbsPagination.prototype = { + + constructor: TwbsPagination, + + destroy: function () { + this.$element.empty(); + this.$element.removeData('twbs-pagination'); + this.$element.off('page'); + + return this; + }, + + show: function (page) { + if (page < 1 || page > this.options.totalPages) { + throw new Error('Page is incorrect.'); + } + this.currentPage = page; + + this.render(this.getPages(page)); + this.setupEvents(); + + this.$element.trigger('page', page); + + return this; + }, + + buildListItems: function (pages) { + var listItems = []; + + if (this.options.first) { + listItems.push(this.buildItem('first', 1)); + } + + if (this.options.prev) { + var prev = pages.currentPage > 1 ? pages.currentPage - 1 : this.options.loop ? this.options.totalPages : 1; + listItems.push(this.buildItem('prev', prev)); + } + + for (var i = 0; i < pages.numeric.length; i++) { + listItems.push(this.buildItem('page', pages.numeric[i])); + } + + if (this.options.next) { + var next = pages.currentPage < this.options.totalPages ? pages.currentPage + 1 : this.options.loop ? 1 : this.options.totalPages; + listItems.push(this.buildItem('next', next)); + } + + if (this.options.last) { + listItems.push(this.buildItem('last', this.options.totalPages)); + } + + return listItems; + }, + + buildItem: function (type, page) { + var $itemContainer = $('
    • '), + $itemContent = $(''), + itemText = this.options[type] ? this.makeText(this.options[type], page) : page; + + $itemContainer.addClass(this.options[type + 'Class']); + $itemContainer.data('page', page); + $itemContainer.data('page-type', type); + $itemContainer.append($itemContent.attr('href', this.makeHref(page)).addClass(this.options.anchorClass).html(itemText)); + + return $itemContainer; + }, + + getPages: function (currentPage) { + var pages = []; + + var half = Math.floor(this.options.visiblePages / 2); + var start = currentPage - half + 1 - this.options.visiblePages % 2; + var end = currentPage + half; + + // handle boundary case + if (start <= 0) { + start = 1; + end = this.options.visiblePages; + } + if (end > this.options.totalPages) { + start = this.options.totalPages - this.options.visiblePages + 1; + end = this.options.totalPages; + } + + var itPage = start; + while (itPage <= end) { + pages.push(itPage); + itPage++; + } + + return {"currentPage": currentPage, "numeric": pages}; + }, + + render: function (pages) { + var _this = this; + this.$listContainer.children().remove(); + var items = this.buildListItems(pages); + jQuery.each(items, function(key, item){ + _this.$listContainer.append(item); + }); + + this.$listContainer.children().each(function () { + var $this = $(this), + pageType = $this.data('page-type'); + + switch (pageType) { + case 'page': + if ($this.data('page') === pages.currentPage) { + $this.addClass(_this.options.activeClass); + } + break; + case 'first': + $this.toggleClass(_this.options.disabledClass, pages.currentPage === 1); + break; + case 'last': + $this.toggleClass(_this.options.disabledClass, pages.currentPage === _this.options.totalPages); + break; + case 'prev': + $this.toggleClass(_this.options.disabledClass, !_this.options.loop && pages.currentPage === 1); + break; + case 'next': + $this.toggleClass(_this.options.disabledClass, + !_this.options.loop && pages.currentPage === _this.options.totalPages); + break; + default: + break; + } + + }); + }, + + setupEvents: function () { + var _this = this; + this.$listContainer.off('click').on('click', 'li', function (evt) { + var $this = $(this); + if ($this.hasClass(_this.options.disabledClass) || $this.hasClass(_this.options.activeClass)) { + return false; + } + // Prevent click event if href is not set. + !_this.options.href && evt.preventDefault(); + _this.show(parseInt($this.data('page'))); + }); + }, + + makeHref: function (page) { + return this.options.href ? this.generateQueryString(page) : "#"; + }, + + makeText: function (text, page) { + return text.replace(this.options.pageVariable, page) + .replace(this.options.totalPagesVariable, this.options.totalPages) + }, + getPageFromQueryString: function (searchStr) { + var search = this.getSearchString(searchStr), + regex = new RegExp(this.options.pageVariable + '(=([^&#]*)|&|#|$)'), + page = regex.exec(search); + if (!page || !page[2]) { + return null; + } + page = decodeURIComponent(page[2]); + page = parseInt(page); + if (isNaN(page)) { + return null; + } + return page; + }, + generateQueryString: function (pageNumber, searchStr) { + var search = this.getSearchString(searchStr), + regex = new RegExp(this.options.pageVariable + '=*[^&#]*'); + if (!search) return ''; + return '?' + search.replace(regex, this.options.pageVariable + '=' + pageNumber); + + }, + getSearchString: function (searchStr) { + var search = searchStr || window.location.search; + if (search === '') { + return null; + } + if (search.indexOf('?') === 0) search = search.substr(1); + return search; + } + + }; + + // PLUGIN DEFINITION + + $.fn.twbsPagination = function (option) { + var args = Array.prototype.slice.call(arguments, 1); + var methodReturn; + + var $this = $(this); + var data = $this.data('twbs-pagination'); + var options = typeof option === 'object' ? option : {}; + + if (!data) $this.data('twbs-pagination', (data = new TwbsPagination(this, options) )); + if (typeof option === 'string') methodReturn = data[ option ].apply(data, args); + + return ( methodReturn === undefined ) ? $this : methodReturn; + }; + + $.fn.twbsPagination.defaults = { + totalPages: 1, + startPage: 1, + visiblePages: 5, + initiateStartPageClick: true, + hideOnlyOnePage: false, + href: false, + pageVariable: '{{page}}', + totalPagesVariable: '{{total_pages}}', + page: null, + first: 'First', + prev: 'Previous', + next: 'Next', + last: 'Last', + loop: false, + onPageClick: null, + paginationClass: 'pagination', + nextClass: 'page-item next', + prevClass: 'page-item prev', + lastClass: 'page-item last', + firstClass: 'page-item first', + pageClass: 'page-item', + activeClass: 'active', + disabledClass: 'disabled', + anchorClass: 'page-link' + }; + + $.fn.twbsPagination.Constructor = TwbsPagination; + + $.fn.twbsPagination.noConflict = function () { + $.fn.twbsPagination = old; + return this; + }; + + $.fn.twbsPagination.version = "1.4.1"; + +})(window.jQuery, window, document); diff --git a/docfx/docfx_project/templates/singulinkfx/styles/main.css b/docfx/docfx_project/templates/singulinkfx/styles/main.css new file mode 100644 index 000000000..e69de29bb diff --git a/docfx/docfx_project/templates/singulinkfx/styles/main.js b/docfx/docfx_project/templates/singulinkfx/styles/main.js new file mode 100644 index 000000000..e69de29bb diff --git a/docfx/docfx_project/templates/singulinkfx/styles/singulink.css b/docfx/docfx_project/templates/singulinkfx/styles/singulink.css new file mode 100644 index 000000000..b7200ca68 --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/styles/singulink.css @@ -0,0 +1,471 @@ +body { + font-size: var(--base-font-size); +} + +@media (max-width: 1024px) { + body { + font-size: var(--smalldevice-base-font-size); + } +} + +/* Headings */ + +h1, h2, h3, h4, h5 { + line-height: initial; +} + +h1, h1:first-child { + font-size: 2.25em; + letter-spacing: 0.5px; + color: var(--h1-color); + margin-block-start: 1em; + margin-block-end: -0.05em; +} + +.article h1 { + margin-block-end: -0.2em; +} + +h2 { + font-size: 2.1em; + color: var(--h2-color); +} + +.article h2 { + margin-block-start: 1.3em; + padding-bottom: 6px; + border-bottom: 1px solid var(--separator-color); +} + +h3 { + font-size: 1.95em; + font-weight: 500; + margin-block-start: 1.7em; +} + +.article h3 { + font-size: 1.85em; + font-weight: 500; + margin-block-start: 1.2em; + margin-block-end: 0.9em; +} + +h4 { + font-size: 1.8em; + font-weight: 400; + margin-block-start: 2em; + padding-bottom: 10px; +} + +.article h4 { + font-size: 1.5em; + font-weight: 300; + margin-block-start: 1em; + margin-block-end: 1em; + padding-bottom: 0; + border-bottom: none; +} + +h5 { + font-size: 1.1em; +} + +.article h5 { + font-size: 1.13em; + font-weight: 400; + text-decoration: underline; + margin-block-start: 1.5em; + margin-block-end: 1.0em; +} + +a.brand:hover +{ + text-decoration: none; +} + +a.brand .brand-title { + font-size: 1.4em; + font-weight: 500; + letter-spacing: 0.5px; + color: var(--appname-color); + margin-top: 1px; + padding: 0 0 0 0.4em; +} + +@media (min-width: 1024px) { + a.brand .brand-title { + font-size: 1.55em; + } +} + + +a.brand .logomark { + height: 35px; +} + +/* Top bar */ + +.top-navbar { + height: 60px; + padding: 0 0 0 10px; +} + +.burger-icon { + margin-right: 10px; +} + +/* Side Bar */ + +.sidebar { + padding: 25px 17px 32px 17px; +} + +.blackout { + top: 60px; +} + +@media (max-width: 1023.98px) { + .navbar-nav { + margin-top: 0; + } +} + +nav { + width: 94%; + max-width: var(--sidebar-width); + left: calc(var(--sidebar-width) * -1); +} + +@media (max-width: 1023.98px) { + nav { + top: 60px; + } +} + +nav ul { + list-style-type: none; +} + +nav .nav a, nav .nav a:hover { + text-decoration: none; + cursor: pointer; + display: block; +} + +nav a.sidebar-item { + padding: 4px 0 4px 10px; + cursor: pointer; +} + +nav a:focus, nav a.sidebar-item:hover, nav a.sidebar-item:focus { + text-decoration: underline; +} + +nav a, nav a:hover, nav a:focus { + color: var(--sidebar-item-color) !important; +} + +nav a.active, nav a.active:hover, nav a.active:focus { + color: var(--sidebar-active-item-color) !important; +} + +.sidebar-item-separator { + margin: 20px 0; +} + +#toc ul li a { + padding: 0 0 0 10px; +} + +.search { + background: var(--search-bg-color); + border: 1px solid var(--search-border-color); + border-radius: 5px; + position: relative; + margin-block-start: 25px; +} + +@media (max-width: 1023.98px) { + .search { + margin-block-start: 0; + margin-block-end: 15px; + } +} + +.search > input { + font-size: 0.95em; + color: var(--search-color); + border: 0; + background: none; + padding: 11px 30px 10px 37px; + width: 100%; +} + +.search > input:focus { + outline: 0; +} + +.search > .search-icon { + font-size: 1.2em; + color: var(--search-searchicon-color); + position: absolute; + top: 9px; + left: 9px; +} + +.toc-filter { + background: var(--toc-filter-bg-color); + border: 1px solid var(--toc-filter-border-color); + border-radius: 5px; + position: relative; +} + +.toc-filter > input { + font-size: 0.95em; + color: var(--toc-filter-color); + border: 0; + background: none; + padding: 11px 30px 10px 37px; + width: 100%; +} + +.toc-filter > input:focus { + outline: 0; +} + +.toc-filter > .filter-icon { + font-size: 1.2em; + color: var(--toc-filter-filtericon-color); + position: absolute; + top: 9px; + left: 9px; +} + +.toc-filter > .clear-icon { + color: var(--toc-filter-clearicon-color); + position: absolute; + top: 9px; + right: 9px; + cursor: pointer; +} + +.toc .nav > li > .expand-stub::before, .toc .nav > li.active > .expand-stub::before +{ + width: 8px; + height: 8px; + top: 6px; + left: 6px; +} + +#toc ul.level2 +{ + margin-bottom: 20px; +} + +#toc ul.level1 > li > a { + font-weight: 500; + margin-bottom: 10px; + padding: 5px 10px; +} + +#toc ul.level1 > li > a, #toc ul.level1 > li > a.active { + background-color: var(--sidebar-level1-item-bg-color) !important; + border-radius: 2px; +} + +#toc ul.level1 > li > a:hover, #toc ul.level1 > li > a.active:hover, +#toc ul.level1 > li > a:focus, #toc ul.level1 > li > a.active:focus { + background-color: var(--sidebar-level1-item-hover-bg-color) !important; + text-decoration: none; +} + +ul.level2 { + padding-inline-start: 0.7em; +} + +ul.level2 .expand-stub { + top: 1px; +} + +ul.level2 > li > a, ul.level2 > li > a.sidebar-item { + font-weight: 400; + color: var(--sidebar-item-color); + margin: 4px 0 4px; +} + +ul.level3 { + padding-inline-start: 1em; +} + +ul.level3 > li > a, ul.level3 > li > a.sidebar-item { + font-size: 1.05em; + color: var(--sidebar-item-color); + margin: 4px 0; +} + +ul.level4 { + padding-inline-start: 0; + margin-bottom: 12px; +} + +ul.level4 > li > a, ul.level4 > li > a.sidebar-item { + font-size: 1.05em; + color: var(--sidebar-item-color); + margin: 5px 0 5px 10px; +} + +/* Breadcrumbs */ + +.subnav.navbar { + margin: 0 -15px; +} + +#breadcrumb { + overflow: scroll; + margin-bottom: 0; +} + +#breadcrumb::-webkit-scrollbar { + display: none; +} + +#breadcrumb a { + white-space: nowrap; +} + +#breadcrumb wbr { + display: none; +} + +/* Search Results */ + +#search-results h1 { + margin-block-start: 0.5em; +} + +#search-results .item-title { + font-size: 1.3em; + margin-top: 1.5em; +} + +#search-results .item-href { + font-size: 0.85em; +} + +#search-results .item-brief { + margin-top: 0.7em; +} + +#search-results ul.pagination { + text-align: center; + padding: 10px 0 0 0; + margin-block-start: 40px; + border-top: 1px solid var(--separator-color); +} + +#search-results ul.pagination > li { + display: inline-block; + margin: 0 10px; +} + +#search-results ul.pagination > li.disabled a, #search-results ul.pagination > li.disabled a:hover { + color: var(--text-color); + cursor: txt; + text-decoration: none; +} + +/* Content */ + +.main-panel { + margin-bottom: 60px; + padding: 20px; +} + +@media (min-width: 1024px) { + .main-panel { + margin-bottom: 0; + padding: 20px 40px; + } +} + +.pull-right { + margin-top: 70px; + /* Fix unclickable links */ + position: relative; + z-index: 1; +} + +.divider { + margin-left: 4px; +} + +article ul li, article ol li { + margin-bottom: 10px; +} + +legend, pre { + padding: 6px; +} + +.hljs { + color: var(--code-color); +} + +.hljs::-webkit-scrollbar { + height: 6px; +} + +.hljs-keyword, .hljs-title, .hljs-built_in { + font-style: normal; +} + +p .xref, code { + background-color: var(--ref-bg-color); + color: var(--ref-color); + padding: 2px 3px; + font-family: monospace; + font-size: 0.95em; + border-radius: 6px; +} + +span.parametername { + font-family: monospace; +} + +.table { + width: auto; +} + +.table-responsive { + margin-bottom: 0; +} + +table th { + font-size: 14px; + padding: 9px 10px; +} + +table td p { + font-weight: 300; +} + +table td { + padding: 6px 10px; +} + +.footer { + text-align: center; + color: var(--text-color); + padding: 10px; +} + +.footer a:hover, .footer a:focus { + text-decoration: underline; +} + +.copyright-footer { + font-size: 0.85em; + font-weight: bold; + text-align: center; + margin-block-start: 30px; +} \ No newline at end of file diff --git a/docfx/docfx_project/templates/singulinkfx/styles/singulink.js b/docfx/docfx_project/templates/singulinkfx/styles/singulink.js new file mode 100644 index 000000000..c2c0b4c45 --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/styles/singulink.js @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information. + +function toggleMenu() { + + var sidebar = document.getElementById("sidebar"); + var blackout = document.getElementById("blackout"); + + if (sidebar.style.left === "0px") + { + sidebar.style.left = "-" + sidebar.offsetWidth + "px"; + blackout.classList.remove("showThat"); + blackout.classList.add("hideThat"); + } + else + { + sidebar.style.left = "0px"; + blackout.classList.remove("hideThat"); + blackout.classList.add("showThat"); + } +} + +$(function() { + $('table').each(function(a, tbl) { + var currentTableRows = $(tbl).find('tbody tr').length; + $(tbl).find('th').each(function(i) { + var remove = 0; + var currentTable = $(this).parents('table'); + + var tds = currentTable.find('tr td:nth-child(' + (i + 1) + ')'); + tds.each(function(j) { if ($(this).text().trim() === '') remove++; }); + + if (remove == currentTableRows) { + $(this).hide(); + tds.hide(); + } + }); + }); +}); \ No newline at end of file diff --git a/docfx/docfx_project/templates/singulinkfx/styles/url.min.js b/docfx/docfx_project/templates/singulinkfx/styles/url.min.js new file mode 100644 index 000000000..8057e0aa0 --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/styles/url.min.js @@ -0,0 +1 @@ +/*! url - v1.8.6 - 2013-11-22 */window.url=function(){function a(a){return!isNaN(parseFloat(a))&&isFinite(a)}return function(b,c){var d=c||window.location.toString();if(!b)return d;b=b.toString(),"//"===d.substring(0,2)?d="http:"+d:1===d.split("://").length&&(d="http://"+d),c=d.split("/");var e={auth:""},f=c[2].split("@");1===f.length?f=f[0].split(":"):(e.auth=f[0],f=f[1].split(":")),e.protocol=c[0],e.hostname=f[0],e.port=f[1]||("https"===e.protocol.split(":")[0].toLowerCase()?"443":"80"),e.pathname=(c.length>3?"/":"")+c.slice(3,c.length).join("/").split("?")[0].split("#")[0];var g=e.pathname;"/"===g.charAt(g.length-1)&&(g=g.substring(0,g.length-1));var h=e.hostname,i=h.split("."),j=g.split("/");if("hostname"===b)return h;if("domain"===b)return/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/.test(h)?h:i.slice(-2).join(".");if("sub"===b)return i.slice(0,i.length-2).join(".");if("port"===b)return e.port;if("protocol"===b)return e.protocol.split(":")[0];if("auth"===b)return e.auth;if("user"===b)return e.auth.split(":")[0];if("pass"===b)return e.auth.split(":")[1]||"";if("path"===b)return e.pathname;if("."===b.charAt(0)){if(b=b.substring(1),a(b))return b=parseInt(b,10),i[0>b?i.length+b:b-1]||""}else{if(a(b))return b=parseInt(b,10),j[0>b?j.length+b:b]||"";if("file"===b)return j.slice(-1)[0];if("filename"===b)return j.slice(-1)[0].split(".")[0];if("fileext"===b)return j.slice(-1)[0].split(".")[1]||"";if("?"===b.charAt(0)||"#"===b.charAt(0)){var k=d,l=null;if("?"===b.charAt(0)?k=(k.split("?")[1]||"").split("#")[0]:"#"===b.charAt(0)&&(k=k.split("#")[1]||""),!b.charAt(1))return k;b=b.substring(1),k=k.split("&");for(var m=0,n=k.length;n>m;m++)if(l=k[m].split("="),l[0]===b)return l[1]||"";return null}}return""}}(),"undefined"!=typeof jQuery&&jQuery.extend({url:function(a,b){return window.url(a,b)}}); \ No newline at end of file diff --git a/docfx/docfx_project/templates/singulinkfx/toc.html.tmpl b/docfx/docfx_project/templates/singulinkfx/toc.html.tmpl new file mode 100644 index 000000000..6549e62e7 --- /dev/null +++ b/docfx/docfx_project/templates/singulinkfx/toc.html.tmpl @@ -0,0 +1,22 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + +
      +
      + {{^_disableSideFilter}} +
      +
      + + + +
      +
      + {{/_disableSideFilter}} +
      +
      + {{^leaf}} + {{>partials/li}} + {{/leaf}} +
      +
      +
      +
      \ No newline at end of file diff --git a/docfx/docfx_project/toc.yml b/docfx/docfx_project/toc.yml index 7db7ecaea..842c6b36f 100644 --- a/docfx/docfx_project/toc.yml +++ b/docfx/docfx_project/toc.yml @@ -1,6 +1,2 @@ -- name: Articles - href: articles/ - homepage: articles/intro.md - name: API Documentation href: api/ - homepage: api/index.md From 262b0ebe99badc631619bea0c18c6eccfc118cdd Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 26 Aug 2022 09:15:23 +0200 Subject: [PATCH 12/15] API Docs - Optimise CI --- ci/azure-pipelines-docfx.yml | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/ci/azure-pipelines-docfx.yml b/ci/azure-pipelines-docfx.yml index db588fe8c..4a40053a7 100644 --- a/ci/azure-pipelines-docfx.yml +++ b/ci/azure-pipelines-docfx.yml @@ -7,14 +7,6 @@ trigger: - master pr: none -resources: - repositories: - - repository: RGBNET - type: github - endpoint: github.com_SpoinkyNL - name: DarthAffe/RGB.NET - ref: Development - pool: vmImage: 'windows-latest' @@ -28,22 +20,13 @@ variables: SourceVersion: $(Build.SourceVersion) steps: -- checkout: RGBNET - path: s/RGB.NET - checkout: self path: s/Artemis - task: DotNetCoreCLI@2 - displayName: 'RGB.NET - Build' + displayName: 'dotnet build Artemis' inputs: command: 'build' - projects: '$(rgbSolution)' - arguments: '--configuration Release' - -- task: DotNetCoreCLI@2 - displayName: 'dotnet restore Artemis' - inputs: - command: 'restore' projects: '$(artemisSolution)' feedsToUse: 'config' nugetConfigPath: '$(Pipeline.Workspace)/s/Artemis/src/NuGet.Config' From dc00d7ea6fa21d6fbda1d4f5523a897fe33192af Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 26 Aug 2022 09:36:12 +0200 Subject: [PATCH 13/15] API Docs - Fix build --- docfx/docfx_project/docfx.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docfx/docfx_project/docfx.json b/docfx/docfx_project/docfx.json index 65522fb8e..dc1d65129 100644 --- a/docfx/docfx_project/docfx.json +++ b/docfx/docfx_project/docfx.json @@ -4,8 +4,8 @@ "src": [ { "files": [ - "Artemis.Core/Artemis.Core.csproj", - "Artemis.UI.Shared/Artemis.UI.Shared.csproj", + "Artemis.Core/bin/net6.0/Artemis.Core.dll", + "Artemis.UI.Shared/bin/net6.0/Artemis.UI.Shared.dll", ], "src": "../../src" } From 928d9711af1e8ce31c4908f44a51dddd28efef99 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 26 Aug 2022 20:32:36 +0200 Subject: [PATCH 14/15] Core - Moved startup arguments from CoreService to Constants Web server - Added setting to disable the web server Web server - Added --disable-webserver startup argument to disable the web server UI - Fixed Artemis not bringing existing instances to foreground if already running --- src/Artemis.Core/Constants.cs | 6 + src/Artemis.Core/Services/CoreService.cs | 10 +- .../Services/Interfaces/ICoreService.cs | 7 +- .../Interfaces/IPluginManagementService.cs | 2 +- .../Services/PluginManagementService.cs | 10 +- .../Services/WebServer/WebServerService.cs | 63 +- src/Artemis.UI.Windows/App.axaml.cs | 74 +- .../ApplicationStateManager.cs | 75 -- src/Artemis.UI/ArtemisBootstrapper.cs | 5 + src/Artemis.UI/MainWindow.axaml.cs | 4 +- src/Artemis.UI/Screens/Root/RootViewModel.cs | 7 +- .../Settings/Tabs/GeneralTabView.axaml | 705 +++++++++--------- .../Settings/Tabs/GeneralTabViewModel.cs | 1 + .../Nodes/Mathematics/Saturate.cs | 2 +- 14 files changed, 516 insertions(+), 455 deletions(-) diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index 15f337748..e37f18ffb 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; using System.Reflection; using Artemis.Core.JsonConverters; @@ -90,6 +91,11 @@ public static class Constants /// public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null); + /// + /// Gets the startup arguments provided to the application + /// + public static ReadOnlyCollection StartupArguments { get; set; } = null!; + internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")}; internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")}; diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 3aad1511b..81d8232ab 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -58,7 +58,6 @@ internal class CoreService : ICoreService _scriptingService = scriptingService; _loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug); _frameStopWatch = new Stopwatch(); - StartupArguments = new List(); _rgbService.Surface.Updating += SurfaceOnUpdating; _loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel(); @@ -78,7 +77,7 @@ internal class CoreService : ICoreService private void ApplyLoggingLevel() { - string? argument = StartupArguments.FirstOrDefault(a => a.StartsWith("--logging")); + string? argument = Constants.StartupArguments.FirstOrDefault(a => a.StartsWith("--logging")); if (argument != null) { // Parse the provided log level @@ -194,7 +193,6 @@ internal class CoreService : ICoreService public int FrameRate { get; private set; } public TimeSpan FrameTime { get; private set; } public bool ProfileRenderingDisabled { get; set; } - public List StartupArguments { get; set; } public bool IsElevated { get; set; } public void Dispose() @@ -217,7 +215,7 @@ internal class CoreService : ICoreService Constants.BuildInfo.BuildNumber, Constants.BuildInfo.SourceBranch ); - _logger.Information("Startup arguments: {args}", StartupArguments); + _logger.Information("Startup arguments: {args}", Constants.StartupArguments); _logger.Information("Elevated permissions: {perms}", IsElevated); _logger.Information("Stopwatch high resolution: {perms}", Stopwatch.IsHighResolution); @@ -230,9 +228,9 @@ internal class CoreService : ICoreService // Initialize the services _pluginManagementService.CopyBuiltInPlugins(); - _pluginManagementService.LoadPlugins(StartupArguments, IsElevated); + _pluginManagementService.LoadPlugins(IsElevated); - _rgbService.ApplyPreferredGraphicsContext(StartupArguments.Contains("--force-software-render")); + _rgbService.ApplyPreferredGraphicsContext(Constants.StartupArguments.Contains("--force-software-render")); _rgbService.SetRenderPaused(false); OnInitialized(); } diff --git a/src/Artemis.Core/Services/Interfaces/ICoreService.cs b/src/Artemis.Core/Services/Interfaces/ICoreService.cs index 4eb41b269..8dd297038 100644 --- a/src/Artemis.Core/Services/Interfaces/ICoreService.cs +++ b/src/Artemis.Core/Services/Interfaces/ICoreService.cs @@ -27,12 +27,7 @@ public interface ICoreService : IArtemisService, IDisposable /// Gets or sets whether profiles are rendered each frame by calling their Render method /// bool ProfileRenderingDisabled { get; set; } - - /// - /// Gets or sets a list of startup arguments - /// - List StartupArguments { get; set; } - + /// /// Gets a boolean indicating whether Artemis is running in an elevated environment (admin permissions) /// diff --git a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs index d1281ef8e..209158529 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs @@ -26,7 +26,7 @@ public interface IPluginManagementService : IArtemisService, IDisposable /// /// Loads all installed plugins. If plugins already loaded this will reload them all /// - void LoadPlugins(List startupArguments, bool isElevated); + void LoadPlugins(bool isElevated); /// /// Unloads all installed plugins. diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 5b1abd65b..c5eba9608 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -203,17 +203,17 @@ internal class PluginManagementService : IPluginManagementService #region Plugins - public void LoadPlugins(List startupArguments, bool isElevated) + public void LoadPlugins(bool isElevated) { - if (startupArguments.Contains("--no-plugins")) + if (Constants.StartupArguments.Contains("--no-plugins")) { _logger.Warning("Artemis launched with --no-plugins, skipping the loading of plugins"); return; } - bool ignorePluginLock = startupArguments.Contains("--ignore-plugin-lock"); - bool stayElevated = startupArguments.Contains("--force-elevation"); - bool droppedAdmin = startupArguments.Contains("--dropped-admin"); + bool ignorePluginLock = Constants.StartupArguments.Contains("--ignore-plugin-lock"); + bool stayElevated = Constants.StartupArguments.Contains("--force-elevation"); + bool droppedAdmin = Constants.StartupArguments.Contains("--dropped-admin"); if (LoadingPlugins) throw new ArtemisCoreException("Cannot load plugins while a previous load hasn't been completed yet."); diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs index 0fffc9570..f082ed6df 100644 --- a/src/Artemis.Core/Services/WebServer/WebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using Artemis.Core.Modules; using EmbedIO; @@ -17,15 +18,19 @@ internal class WebServerService : IWebServerService, IDisposable private readonly List _controllers; private readonly ILogger _logger; private readonly List _modules; + private readonly PluginSetting _webServerEnabledSetting; private readonly PluginSetting _webServerPortSetting; + private CancellationTokenSource? _cts; - public WebServerService(ILogger logger, ISettingsService settingsService, IPluginManagementService pluginManagementService) + public WebServerService(ILogger logger, ICoreService coreService, ISettingsService settingsService, IPluginManagementService pluginManagementService) { _logger = logger; _controllers = new List(); _modules = new List(); + _webServerEnabledSetting = settingsService.GetSetting("WebServer.Enabled", true); _webServerPortSetting = settingsService.GetSetting("WebServer.Port", 9696); + _webServerEnabledSetting.SettingChanged += WebServerEnabledSettingOnSettingChanged; _webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged; pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled; @@ -33,9 +38,9 @@ internal class WebServerService : IWebServerService, IDisposable StartWebServer(); } - protected virtual void OnWebServerStarting() + private void WebServerEnabledSettingOnSettingChanged(object? sender, EventArgs e) { - WebServerStarting?.Invoke(this, EventArgs.Empty); + StartWebServer(); } private void WebServerPortSettingOnSettingChanged(object? sender, EventArgs e) @@ -72,14 +77,23 @@ internal class WebServerService : IWebServerService, IDisposable public WebServer? Server { get; private set; } public PluginsModule PluginsModule { get; } - public event EventHandler? WebServerStarting; #region Web server managament private WebServer CreateWebServer() { - Server?.Dispose(); - Server = null; + if (Server != null) + { + if (_cts != null) + { + _cts.Cancel(); + _cts = null; + } + + Server.Dispose(); + OnWebServerStopped(); + Server = null; + } WebApiModule apiModule = new("/", JsonNetSerializer); PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/"; @@ -112,8 +126,20 @@ internal class WebServerService : IWebServerService, IDisposable private void StartWebServer() { Server = CreateWebServer(); + + if (!_webServerEnabledSetting.Value) + return; + + if (Constants.StartupArguments.Contains("--disable-webserver")) + { + _logger.Warning("Artemis launched with --disable-webserver, not enabling the webserver"); + return; + } + OnWebServerStarting(); - Server.Start(); + _cts = new CancellationTokenSource(); + Server.Start(_cts.Token); + OnWebServerStarted(); } #endregion @@ -276,4 +302,27 @@ internal class WebServerService : IWebServerService, IDisposable } #endregion + + #region Events + + protected virtual void OnWebServerStopped() + { + WebServerStopped?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnWebServerStarting() + { + WebServerStarting?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnWebServerStarted() + { + WebServerStarted?.Invoke(this, EventArgs.Empty); + } + + public event EventHandler? WebServerStopped; + public event EventHandler? WebServerStarting; + public event EventHandler? WebServerStarted; + + #endregion } \ No newline at end of file diff --git a/src/Artemis.UI.Windows/App.axaml.cs b/src/Artemis.UI.Windows/App.axaml.cs index 411bd3227..f82d58f29 100644 --- a/src/Artemis.UI.Windows/App.axaml.cs +++ b/src/Artemis.UI.Windows/App.axaml.cs @@ -1,3 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Windows.Ninject; using Artemis.UI.Windows.Providers.Input; @@ -13,14 +21,23 @@ namespace Artemis.UI.Windows; public class App : Application { + private StandardKernel? _kernel; + private bool _shutDown; + // ReSharper disable NotAccessedField.Local private ApplicationStateManager? _applicationStateManager; + private Mutex? _artemisMutex; // ReSharper restore NotAccessedField.Local - private StandardKernel? _kernel; - public override void Initialize() { + // If Artemis is already running, bring it to foreground and stop this process + if (FocusExistingInstance()) + { + _shutDown = true; + Environment.Exit(1); + } + _kernel = ArtemisBootstrapper.Bootstrap(this, new WindowsModule()); Program.CreateLogger(_kernel); RxApp.MainThreadScheduler = AvaloniaScheduler.Instance; @@ -29,7 +46,7 @@ public class App : Application public override void OnFrameworkInitializationCompleted() { - if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode) + if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode || _shutDown) return; ArtemisBootstrapper.Initialize(); @@ -42,4 +59,55 @@ public class App : Application IInputService inputService = standardKernel.Get(); inputService.AddInputProvider(standardKernel.Get()); } + + private bool FocusExistingInstance() + { + _artemisMutex = new Mutex(true, "Artemis-3c24b502-64e6-4587-84bf-9072970e535f", out bool createdNew); + return !createdNew && RemoteFocus(); + } + + private bool RemoteFocus() + { + // At this point we cannot read the database yet to retrieve the web server port. + // Instead use the method external applications should use as well. + if (!File.Exists(Path.Combine(Constants.DataFolder, "webserver.txt"))) + { + KillOtherInstances(); + return false; + } + + string url = File.ReadAllText(Path.Combine(Constants.DataFolder, "webserver.txt")); + using HttpClient client = new(); + try + { + CancellationTokenSource cts = new(); + cts.CancelAfter(2000); + + HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground"), cts.Token); + httpResponseMessage.EnsureSuccessStatusCode(); + return true; + } + catch (Exception) + { + KillOtherInstances(); + return false; + } + } + + private void KillOtherInstances() + { + // Kill everything else heh + List processes = Process.GetProcessesByName("Artemis.UI.Windows").Where(p => p.Id != Process.GetCurrentProcess().Id).ToList(); + foreach (Process process in processes) + { + try + { + process.Kill(true); + } + catch (Exception) + { + // ignored + } + } + } } \ No newline at end of file diff --git a/src/Artemis.UI.Windows/ApplicationStateManager.cs b/src/Artemis.UI.Windows/ApplicationStateManager.cs index 6225b10e5..c08f42316 100644 --- a/src/Artemis.UI.Windows/ApplicationStateManager.cs +++ b/src/Artemis.UI.Windows/ApplicationStateManager.cs @@ -3,12 +3,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Net.Http; using System.Security.Principal; -using System.Threading; using Artemis.Core; using Artemis.Core.Services; -using Artemis.UI.Shared.Services; using Artemis.UI.Windows.Utilities; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; @@ -19,14 +16,8 @@ namespace Artemis.UI.Windows; public class ApplicationStateManager { - private readonly IWindowService _windowService; - - // ReSharper disable once NotAccessedField.Local - Kept in scope to ensure it does not get released - private Mutex? _artemisMutex; - public ApplicationStateManager(IKernel kernel, string[] startupArguments) { - _windowService = kernel.Get(); StartupArguments = startupArguments; IsElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); @@ -51,72 +42,6 @@ public class ApplicationStateManager public string[] StartupArguments { get; } public bool IsElevated { get; } - public bool FocusExistingInstance() - { - _artemisMutex = new Mutex(true, "Artemis-3c24b502-64e6-4587-84bf-9072970e535f", out bool createdNew); - if (createdNew) - return false; - - return RemoteFocus(); - } - - public void DisplayException(Exception e) - { - try - { - _windowService.ShowExceptionDialog("An unhandled exception occured", e); - } - catch - { - // ignored, we tried - } - } - - private bool RemoteFocus() - { - // At this point we cannot read the database yet to retrieve the web server port. - // Instead use the method external applications should use as well. - if (!File.Exists(Path.Combine(Constants.DataFolder, "webserver.txt"))) - { - KillOtherInstances(); - return false; - } - - string url = File.ReadAllText(Path.Combine(Constants.DataFolder, "webserver.txt")); - using HttpClient client = new(); - try - { - CancellationTokenSource cts = new(); - cts.CancelAfter(2000); - - HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground"), cts.Token); - httpResponseMessage.EnsureSuccessStatusCode(); - return true; - } - catch (Exception) - { - KillOtherInstances(); - return false; - } - } - - private void KillOtherInstances() - { - // Kill everything else heh - List processes = Process.GetProcessesByName("Artemis.UI.Windows").Where(p => p.Id != Process.GetCurrentProcess().Id).ToList(); - foreach (Process process in processes) - { - try - { - process.Kill(true); - } - catch (Exception) - { - // ignored - } - } - } - private void UtilitiesOnRestartRequested(object? sender, RestartEventArgs e) { List argsList = new(); diff --git a/src/Artemis.UI/ArtemisBootstrapper.cs b/src/Artemis.UI/ArtemisBootstrapper.cs index 5eac6489d..7bade4f67 100644 --- a/src/Artemis.UI/ArtemisBootstrapper.cs +++ b/src/Artemis.UI/ArtemisBootstrapper.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; using System.Reactive; using Artemis.Core; using Artemis.Core.Ninject; @@ -52,6 +55,8 @@ public static class ArtemisBootstrapper if (_application.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) return; + Constants.StartupArguments = new ReadOnlyCollection(new List(desktop.Args)); + // Don't shut down when the last window closes, we might still be active in the tray desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown; // Create the root view model that drives the UI diff --git a/src/Artemis.UI/MainWindow.axaml.cs b/src/Artemis.UI/MainWindow.axaml.cs index df0371090..a22f46831 100644 --- a/src/Artemis.UI/MainWindow.axaml.cs +++ b/src/Artemis.UI/MainWindow.axaml.cs @@ -47,12 +47,12 @@ public class MainWindow : ReactiveCoreWindow private void OnActivated(object? sender, EventArgs e) { - ViewModel.Focused(); + ViewModel?.Focused(); } private void OnDeactivated(object? sender, EventArgs e) { - ViewModel.Unfocused(); + ViewModel?.Unfocused(); } private void InitializeComponent() diff --git a/src/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Artemis.UI/Screens/Root/RootViewModel.cs index 0845b50e7..61fbe58c2 100644 --- a/src/Artemis.UI/Screens/Root/RootViewModel.cs +++ b/src/Artemis.UI/Screens/Root/RootViewModel.cs @@ -54,8 +54,7 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi _defaultTitleBarViewModel = defaultTitleBarViewModel; _sidebarVmFactory = sidebarVmFactory; _lifeTime = (IClassicDesktopStyleApplicationLifetime) Application.Current!.ApplicationLifetime!; - - coreService.StartupArguments = _lifeTime.Args.ToList(); + mainWindowService.ConfigureMainWindowProvider(this); DisplayAccordingToSettings(); @@ -99,8 +98,8 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi private void DisplayAccordingToSettings() { - bool autoRunning = _coreService.StartupArguments.Contains("--autorun"); - bool minimized = _coreService.StartupArguments.Contains("--minimized"); + bool autoRunning = Constants.StartupArguments.Contains("--autorun"); + bool minimized = Constants.StartupArguments.Contains("--minimized"); bool showOnAutoRun = _settingsService.GetSetting("UI.ShowOnStartup", true).Value; if ((autoRunning && !showOnAutoRun) || minimized) diff --git a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml index 012f083ad..d8cd0ff65 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml @@ -13,364 +13,379 @@ x:DataType="settings:GeneralTabViewModel"> - - - - General - - - - - - - Auto-run on startup - - - - + + + + General + + + + + + + Auto-run on startup + + + + - - - Hide window on auto-run - - - - - - + + + Hide window on auto-run + + + + + + - - - Startup delay - - Set the amount of seconds to wait before auto-running Artemis. - - - If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value. - - - - - - - - - sec - - - - - - - - - Log level - - - Sets the logging level, a higher logging level will result in more log files. - - - - - - - + + + Startup delay + + Set the amount of seconds to wait before auto-running Artemis. + + + If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value. + + + + + + + + + sec + + + + - - - - Logs - - - Opens the directory where logs are stored. - - - -