diff --git a/src/Artemis.Core/Services/NodeService.cs b/src/Artemis.Core/Services/NodeService.cs index 0de01d907..92ee2d1f1 100644 --- a/src/Artemis.Core/Services/NodeService.cs +++ b/src/Artemis.Core/Services/NodeService.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.Security.Cryptography; +using System.Text; using Artemis.Storage.Entities.Profile.Nodes; -using Newtonsoft.Json; using Ninject; -using Ninject.Parameters; using SkiaSharp; namespace Artemis.Core.Services @@ -37,9 +37,19 @@ namespace Artemis.Core.Services #region Methods /// - public TypeColorRegistration? GetTypeColor(Type type) + public TypeColorRegistration GetTypeColorRegistration(Type type) { - return NodeTypeStore.GetColor(type); + TypeColorRegistration? match = NodeTypeStore.GetColor(type); + if (match != null) + return match; + + // Come up with a random color based on the type name that should be the same each time + MD5 md5Hasher = MD5.Create(); + byte[] hashed = md5Hasher.ComputeHash(Encoding.UTF8.GetBytes(type.FullName!)); + int hash = BitConverter.ToInt32(hashed, 0); + + SKColor baseColor = SKColor.FromHsl(hash % 255, 50 + hash % 50, 50); + return new TypeColorRegistration(type, baseColor, Constants.CorePlugin); } public NodeTypeRegistration RegisterNodeType(Plugin plugin, Type nodeType) @@ -53,7 +63,7 @@ namespace Artemis.Core.Services string name = nodeAttribute?.Name ?? nodeType.Name; string description = nodeAttribute?.Description ?? string.Empty; string category = nodeAttribute?.Category ?? string.Empty; - + NodeData nodeData = new(plugin, nodeType, name, description, category, nodeAttribute?.InputType, nodeAttribute?.OutputType, (s, e) => CreateNode(s, e, nodeType)); return NodeTypeStore.Add(nodeData); } @@ -105,7 +115,7 @@ namespace Artemis.Core.Services /// /// Gets the best matching registration for the provided type /// - TypeColorRegistration? GetTypeColor(Type type); + TypeColorRegistration GetTypeColorRegistration(Type type); /// /// Registers a node of the provided diff --git a/src/Artemis.Core/VisualScripting/CustomNodeViewModel.cs b/src/Artemis.Core/VisualScripting/CustomNodeViewModel.cs index 5e4aeef46..dd109637b 100644 --- a/src/Artemis.Core/VisualScripting/CustomNodeViewModel.cs +++ b/src/Artemis.Core/VisualScripting/CustomNodeViewModel.cs @@ -12,16 +12,6 @@ namespace Artemis.Core /// public INode Node { get; } - /// - /// Called whenever the custom view model is activated - /// - void OnActivate(); - - /// - /// Called whenever the custom view model is closed - /// - void OnDeactivate(); - /// /// Occurs whenever the node was modified by the view model /// diff --git a/src/Artemis.Core/VisualScripting/NodeScript.cs b/src/Artemis.Core/VisualScripting/NodeScript.cs index b924b0ae4..263d995e8 100644 --- a/src/Artemis.Core/VisualScripting/NodeScript.cs +++ b/src/Artemis.Core/VisualScripting/NodeScript.cs @@ -125,9 +125,6 @@ namespace Artemis.Core _nodes.Remove(node); } - if (node is IDisposable disposable) - disposable.Dispose(); - NodeRemoved?.Invoke(this, new SingleValueEventArgs(node)); } diff --git a/src/Artemis.UI.Shared/Utilities/TypeUtilities.cs b/src/Artemis.UI.Shared/Utilities/TypeUtilities.cs index 314cdb989..7aa20d342 100644 --- a/src/Artemis.UI.Shared/Utilities/TypeUtilities.cs +++ b/src/Artemis.UI.Shared/Utilities/TypeUtilities.cs @@ -25,7 +25,7 @@ namespace Artemis.UI.Shared if (type == typeof(object)) return (SKColors.White.ToColor(), SKColors.White.Darken(0.35f).ToColor()); - TypeColorRegistration? typeColorRegistration = NodeService?.GetTypeColor(type); + TypeColorRegistration? typeColorRegistration = NodeService?.GetTypeColorRegistration(type); if (typeColorRegistration != null) return (typeColorRegistration.Color.ToColor(), typeColorRegistration.DarkenedColor.ToColor()); diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs index 1f24ff5ba..56ae49395 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs @@ -154,12 +154,12 @@ namespace Artemis.VisualScripting.Editor.Controls private void GetCustomViewModel() { - CustomViewModel?.OnDeactivate(); + // CustomViewModel?.OnDeactivate(); if (Node?.Node is Node customViewModelNode) { CustomViewModel = customViewModelNode.GetCustomViewModel(); - CustomViewModel?.OnActivate(); + // CustomViewModel?.OnActivate(); } else CustomViewModel = null; @@ -182,7 +182,7 @@ namespace Artemis.VisualScripting.Editor.Controls if (CustomViewModel != null) { CustomViewModel.NodeModified -= CustomViewModelOnNodeModified; - CustomViewModel.OnDeactivate(); + // CustomViewModel.OnDeactivate(); } CustomViewModel = null; diff --git a/src/Artemis.sln b/src/Artemis.sln index 46d3ed238..9eb974685 100644 --- a/src/Artemis.sln +++ b/src/Artemis.sln @@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.Linux", "Avaloni EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.MacOS", "Avalonia\Artemis.UI.MacOS\Artemis.UI.MacOS.csproj", "{2F5F16DC-FACF-4559-9882-37C2949814C7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.VisualScripting", "Avalonia\Artemis.VisualScripting\Artemis.VisualScripting.csproj", "{412B921A-26F5-4AE6-8B32-0C19BE54F421}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -103,6 +105,14 @@ Global {2F5F16DC-FACF-4559-9882-37C2949814C7}.Release|Any CPU.Build.0 = Release|Any CPU {2F5F16DC-FACF-4559-9882-37C2949814C7}.Release|x64.ActiveCfg = Release|Any CPU {2F5F16DC-FACF-4559-9882-37C2949814C7}.Release|x64.Build.0 = Release|Any CPU + {412B921A-26F5-4AE6-8B32-0C19BE54F421}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {412B921A-26F5-4AE6-8B32-0C19BE54F421}.Debug|Any CPU.Build.0 = Debug|Any CPU + {412B921A-26F5-4AE6-8B32-0C19BE54F421}.Debug|x64.ActiveCfg = Debug|Any CPU + {412B921A-26F5-4AE6-8B32-0C19BE54F421}.Debug|x64.Build.0 = Debug|Any CPU + {412B921A-26F5-4AE6-8B32-0C19BE54F421}.Release|Any CPU.ActiveCfg = Release|Any CPU + {412B921A-26F5-4AE6-8B32-0C19BE54F421}.Release|Any CPU.Build.0 = Release|Any CPU + {412B921A-26F5-4AE6-8B32-0C19BE54F421}.Release|x64.ActiveCfg = Release|Any CPU + {412B921A-26F5-4AE6-8B32-0C19BE54F421}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -113,6 +123,7 @@ Global {DE45A288-9320-461F-BE2A-26DFE3817216} = {960CAAC5-AA73-49F5-BF2F-DF2C789DF042} {9012C8E2-3BEC-42F5-8270-7352A5922B04} = {960CAAC5-AA73-49F5-BF2F-DF2C789DF042} {2F5F16DC-FACF-4559-9882-37C2949814C7} = {960CAAC5-AA73-49F5-BF2F-DF2C789DF042} + {412B921A-26F5-4AE6-8B32-0C19BE54F421} = {960CAAC5-AA73-49F5-BF2F-DF2C789DF042} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C203080A-4473-4CC2-844B-F552EA43D66A} diff --git a/src/Avalonia/Artemis.UI.Linux/packages.lock.json b/src/Avalonia/Artemis.UI.Linux/packages.lock.json index 2ea08bb81..748e8244f 100644 --- a/src/Avalonia/Artemis.UI.Linux/packages.lock.json +++ b/src/Avalonia/Artemis.UI.Linux/packages.lock.json @@ -426,6 +426,11 @@ "resolved": "5.0.0", "contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q==" }, + "Microsoft.Extensions.ObjectPool": { + "type": "Transitive", + "resolved": "5.0.9", + "contentHash": "grj0e6Me0EQsgaurV0fxP0xd8sz8eZVK+Jb816DPzNADHaqXaXJD3xZX9SFjyDl3ykAYvD0y77o5vRd9Hzsk9g==" + }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "5.0.0", @@ -544,6 +549,14 @@ "Ninject": "3.3.3" } }, + "NoStringEvaluating": { + "type": "Transitive", + "resolved": "2.2.2", + "contentHash": "hJHivPDA1Vxn0CCgOtHKZ3fmldxQuz7VL1J4lEaPTXCf+Vwcx1FDf05mGMh6olYMSxoKimGX8YK2sEoqeH3pnA==", + "dependencies": { + "Microsoft.Extensions.ObjectPool": "5.0.9" + } + }, "ReactiveUI.Validation": { "type": "Transitive", "resolved": "2.2.1", @@ -1758,6 +1771,7 @@ "dependencies": { "Artemis.Core": "1.0.0", "Artemis.UI.Shared": "1.0.0", + "Artemis.VisualScripting": "1.0.0", "Avalonia": "0.10.13", "Avalonia.Controls.PanAndZoom": "10.12.0", "Avalonia.Desktop": "0.10.13", @@ -1795,6 +1809,19 @@ "ReactiveUI.Validation": "2.2.1", "SkiaSharp": "2.88.0-preview.178" } + }, + "artemis.visualscripting": { + "type": "Project", + "dependencies": { + "Artemis.Core": "1.0.0", + "Artemis.UI.Shared": "1.0.0", + "Avalonia": "0.10.13", + "Avalonia.ReactiveUI": "0.10.13", + "Ninject": "3.3.4", + "NoStringEvaluating": "2.2.2", + "ReactiveUI": "17.1.50", + "SkiaSharp": "2.88.0-preview.178" + } } } } diff --git a/src/Avalonia/Artemis.UI.MacOS/packages.lock.json b/src/Avalonia/Artemis.UI.MacOS/packages.lock.json index 2ea08bb81..748e8244f 100644 --- a/src/Avalonia/Artemis.UI.MacOS/packages.lock.json +++ b/src/Avalonia/Artemis.UI.MacOS/packages.lock.json @@ -426,6 +426,11 @@ "resolved": "5.0.0", "contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q==" }, + "Microsoft.Extensions.ObjectPool": { + "type": "Transitive", + "resolved": "5.0.9", + "contentHash": "grj0e6Me0EQsgaurV0fxP0xd8sz8eZVK+Jb816DPzNADHaqXaXJD3xZX9SFjyDl3ykAYvD0y77o5vRd9Hzsk9g==" + }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "5.0.0", @@ -544,6 +549,14 @@ "Ninject": "3.3.3" } }, + "NoStringEvaluating": { + "type": "Transitive", + "resolved": "2.2.2", + "contentHash": "hJHivPDA1Vxn0CCgOtHKZ3fmldxQuz7VL1J4lEaPTXCf+Vwcx1FDf05mGMh6olYMSxoKimGX8YK2sEoqeH3pnA==", + "dependencies": { + "Microsoft.Extensions.ObjectPool": "5.0.9" + } + }, "ReactiveUI.Validation": { "type": "Transitive", "resolved": "2.2.1", @@ -1758,6 +1771,7 @@ "dependencies": { "Artemis.Core": "1.0.0", "Artemis.UI.Shared": "1.0.0", + "Artemis.VisualScripting": "1.0.0", "Avalonia": "0.10.13", "Avalonia.Controls.PanAndZoom": "10.12.0", "Avalonia.Desktop": "0.10.13", @@ -1795,6 +1809,19 @@ "ReactiveUI.Validation": "2.2.1", "SkiaSharp": "2.88.0-preview.178" } + }, + "artemis.visualscripting": { + "type": "Project", + "dependencies": { + "Artemis.Core": "1.0.0", + "Artemis.UI.Shared": "1.0.0", + "Avalonia": "0.10.13", + "Avalonia.ReactiveUI": "0.10.13", + "Ninject": "3.3.4", + "NoStringEvaluating": "2.2.2", + "ReactiveUI": "17.1.50", + "SkiaSharp": "2.88.0-preview.178" + } } } } diff --git a/src/Avalonia/Artemis.UI.Shared/Services/DataModelUIService.cs b/src/Avalonia/Artemis.UI.Shared/Services/DataModelUIService.cs index 8a3060d13..e0136d83f 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/DataModelUIService.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/DataModelUIService.cs @@ -38,7 +38,7 @@ namespace Artemis.UI.Shared.Services public DataModelPropertiesViewModel GetMainDataModelVisualization() { DataModelPropertiesViewModel viewModel = new(null, null, null); - foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().Where(d => d.IsExpansion).OrderBy(d => d.DataModelDescription.Name)) + foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().Where(d => d.IsExpansion || d.Module.IsActivated).OrderBy(d => d.DataModelDescription.Name)) viewModel.Children.Add(new DataModelPropertiesViewModel(dataModelExpansion, viewModel, new DataModelPath(dataModelExpansion))); // Update to populate children diff --git a/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/Commands/AddNode.cs b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/Commands/AddNode.cs new file mode 100644 index 000000000..2ac132a0f --- /dev/null +++ b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/Commands/AddNode.cs @@ -0,0 +1,53 @@ +using System; +using Artemis.Core; + +namespace Artemis.UI.Shared.Services.NodeEditor.Commands; + +/// +/// Represents a node editor command that can be used to add a node. +/// +public class AddNode : INodeEditorCommand, IDisposable +{ + private readonly INodeScript _nodeScript; + private readonly INode _node; + private bool _isRemoved; + + /// + /// Creates a new instance of the class. + /// + /// The node script the node belongs to. + /// The node to delete. + public AddNode(INodeScript nodeScript, INode node) + { + _nodeScript = nodeScript; + _node = node; + } + + /// + public string DisplayName => $"Add '{_node.Name}' node"; + + /// + public void Execute() + { + _nodeScript.AddNode(_node); + _isRemoved = false; + } + + /// + public void Undo() + { + _nodeScript.RemoveNode(_node); + _isRemoved = true; + } + + #region IDisposable + + /// + public void Dispose() + { + if (_isRemoved) + _nodeScript.Dispose(); + } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/Commands/CompositeCommand.cs b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/Commands/CompositeCommand.cs new file mode 100644 index 000000000..6d0c3eaa2 --- /dev/null +++ b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/Commands/CompositeCommand.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Artemis.UI.Shared.Services.NodeEditor.Commands; + +/// +/// Represents a profile editor command that can be used to combine multiple commands into one. +/// +public class CompositeCommand : INodeEditorCommand, IDisposable +{ + private bool _ignoreNextExecute; + private readonly List _commands; + + /// + /// Creates a new instance of the class. + /// + /// The commands to execute. + /// The display name of the composite command. + public CompositeCommand(IEnumerable commands, string displayName) + { + if (commands == null) + throw new ArgumentNullException(nameof(commands)); + _commands = commands.ToList(); + DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName)); + } + + /// + /// Creates a new instance of the class. + /// + /// The commands to execute. + /// The display name of the composite command. + /// Whether or not to ignore the first execute because commands are already executed + internal CompositeCommand(IEnumerable commands, string displayName, bool ignoreFirstExecute) + { + if (commands == null) + throw new ArgumentNullException(nameof(commands)); + + _ignoreNextExecute = ignoreFirstExecute; + _commands = commands.ToList(); + DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName)); + } + + /// + public void Dispose() + { + foreach (INodeEditorCommand NodeEditorCommand in _commands) + if (NodeEditorCommand is IDisposable disposable) + disposable.Dispose(); + } + + #region Implementation of INodeEditorCommand + + /// + public string DisplayName { get; } + + /// + public void Execute() + { + if (_ignoreNextExecute) + { + _ignoreNextExecute = false; + return; + } + + foreach (INodeEditorCommand NodeEditorCommand in _commands) + NodeEditorCommand.Execute(); + } + + /// + public void Undo() + { + // Undo in reverse by iterating from the back + for (int index = _commands.Count - 1; index >= 0; index--) + _commands[index].Undo(); + } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/Commands/DeleteNode.cs b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/Commands/DeleteNode.cs new file mode 100644 index 000000000..6c1577427 --- /dev/null +++ b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/Commands/DeleteNode.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using Artemis.Core; + +namespace Artemis.UI.Shared.Services.NodeEditor.Commands; + +/// +/// Represents a node editor command that can be used to delete a node. +/// +public class DeleteNode : INodeEditorCommand, IDisposable +{ + private readonly INode _node; + private readonly INodeScript _nodeScript; + private readonly Dictionary> _pinConnections = new(); + private bool _isRemoved; + + /// + /// Creates a new instance of the class. + /// + /// The node script the node belongs to. + /// The node to delete. + public DeleteNode(INodeScript nodeScript, INode node) + { + _nodeScript = nodeScript; + _node = node; + } + + private void StoreConnections() + { + _pinConnections.Clear(); + foreach (IPin nodePin in _node.Pins) + { + _pinConnections.Add(nodePin, nodePin.ConnectedTo); + nodePin.DisconnectAll(); + } + + foreach (IPinCollection nodePinCollection in _node.PinCollections) + { + foreach (IPin nodePin in nodePinCollection) + { + _pinConnections.Add(nodePin, nodePin.ConnectedTo); + nodePin.DisconnectAll(); + } + } + } + + private void RestoreConnections() + { + foreach (IPin nodePin in _node.Pins) + { + if (_pinConnections.TryGetValue(nodePin, out IReadOnlyList? connections)) + foreach (IPin connection in connections) + nodePin.ConnectTo(connection); + } + + foreach (IPinCollection nodePinCollection in _node.PinCollections) + { + foreach (IPin nodePin in nodePinCollection) + { + if (_pinConnections.TryGetValue(nodePin, out IReadOnlyList? connections)) + foreach (IPin connection in connections) + nodePin.ConnectTo(connection); + } + } + + _pinConnections.Clear(); + } + + /// + public void Dispose() + { + if (_isRemoved) + _nodeScript.Dispose(); + } + + /// + public string DisplayName => $"Delete '{_node.Name}' node"; + + /// + public void Execute() + { + StoreConnections(); + _nodeScript.RemoveNode(_node); + + _isRemoved = true; + } + + /// + public void Undo() + { + _nodeScript.AddNode(_node); + RestoreConnections(); + + _isRemoved = false; + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/Commands/MoveNode.cs b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/Commands/MoveNode.cs new file mode 100644 index 000000000..0df32684d --- /dev/null +++ b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/Commands/MoveNode.cs @@ -0,0 +1,48 @@ +using Artemis.Core; + +namespace Artemis.UI.Shared.Services.NodeEditor.Commands; + +/// +/// Represents a node editor command that can be used to move a node. +/// +public class MoveNode : INodeEditorCommand +{ + private readonly INode _node; + private readonly double _originalX; + private readonly double _originalY; + private readonly double _x; + private readonly double _y; + + /// + /// Creates a new instance of the class. + /// + /// The node to update. + /// The new X-position. + /// The new Y-position. + public MoveNode(INode node, double x, double y) + { + _node = node; + _x = x; + _y = y; + + _originalX = node.X; + _originalY = node.Y; + } + + /// + public string DisplayName => "Move node"; + + /// + public void Execute() + { + _node.X = _x; + _node.Y = _y; + } + + /// + public void Undo() + { + _node.X = _originalX; + _node.Y = _originalY; + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/INodeEditorCommand.cs b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/INodeEditorCommand.cs new file mode 100644 index 000000000..ae85d13ef --- /dev/null +++ b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/INodeEditorCommand.cs @@ -0,0 +1,23 @@ +namespace Artemis.UI.Shared.Services.NodeEditor +{ + /// + /// Represents a command that can be executed and if needed, undone + /// + public interface INodeEditorCommand + { + /// + /// Gets the name of the command + /// + string DisplayName { get; } + + /// + /// Executes the command + /// + void Execute(); + + /// + /// Undoes the command + /// + void Undo(); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/INodeEditorService.cs b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/INodeEditorService.cs new file mode 100644 index 000000000..c3fdf4864 --- /dev/null +++ b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/INodeEditorService.cs @@ -0,0 +1,31 @@ +using System; +using Artemis.Core; +using Artemis.UI.Shared.Services.Interfaces; +using Artemis.UI.Shared.Services.ProfileEditor; + +namespace Artemis.UI.Shared.Services.NodeEditor; + +public interface INodeEditorService : IArtemisSharedUIService +{ + /// + /// Gets the editor history for the provided node script. + /// + /// The node script to get the editor history for. + /// The node editor history of the given node script. + NodeEditorHistory GetHistory(INodeScript nodeScript); + + /// + /// Executes the provided command and adds it to the history. + /// + /// The node script to execute the command upon. + /// The command to execute. + void ExecuteCommand(INodeScript nodeScript, INodeEditorCommand command); + + /// + /// Creates a new command scope which can be used to group undo/redo actions of multiple commands. + /// + /// The node script to create the scope for. + /// The name of the command scope. + /// The command scope that will group any commands until disposed. + NodeEditorCommandScope CreateCommandScope(INodeScript nodeScript, string name); +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/NodeEditorCommandScope.cs b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/NodeEditorCommandScope.cs new file mode 100644 index 000000000..fbd40520c --- /dev/null +++ b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/NodeEditorCommandScope.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Artemis.Core; + +namespace Artemis.UI.Shared.Services.NodeEditor; + +/// +/// Represents a scope in which editor commands are executed until disposed. +/// +public class NodeEditorCommandScope : IDisposable +{ + private readonly List _commands; + + private readonly NodeEditorService _nodeEditorService; + private readonly INodeScript _nodeScript; + + internal NodeEditorCommandScope(NodeEditorService nodeEditorService, INodeScript nodeScript, string name) + { + Name = name; + _nodeEditorService = nodeEditorService; + _nodeScript = nodeScript; + _commands = new List(); + } + + /// + /// Gets the name of the scope. + /// + public string Name { get; } + + /// + /// Gets a read only collection of commands in the scope. + /// + public ReadOnlyCollection NodeEditorCommands => new(_commands); + + internal void AddCommand(INodeEditorCommand command) + { + command.Execute(); + _commands.Add(command); + } + + /// + public void Dispose() + { + _nodeEditorService.StopCommandScope(_nodeScript); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/NodeEditorHistory.cs b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/NodeEditorHistory.cs new file mode 100644 index 000000000..006e4adeb --- /dev/null +++ b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/NodeEditorHistory.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using Artemis.Core; +using ReactiveUI; + +namespace Artemis.UI.Shared.Services.NodeEditor; + +public class NodeEditorHistory +{ + private readonly Subject _canRedo = new(); + private readonly Subject _canUndo = new(); + private readonly Stack _redoCommands = new(); + private readonly Stack _undoCommands = new(); + + public NodeEditorHistory(INodeScript nodeScript) + { + NodeScript = nodeScript; + + Execute = ReactiveCommand.Create(ExecuteEditorCommand); + Undo = ReactiveCommand.Create(ExecuteUndo, CanUndo); + Redo = ReactiveCommand.Create(ExecuteRedo, CanRedo); + } + + public INodeScript NodeScript { get; } + public IObservable CanUndo => _canUndo.AsObservable().DistinctUntilChanged(); + public IObservable CanRedo => _canRedo.AsObservable().DistinctUntilChanged(); + + public ReactiveCommand Execute { get; } + public ReactiveCommand Undo { get; } + public ReactiveCommand Redo { get; } + + public void Clear() + { + ClearRedo(); + ClearUndo(); + UpdateSubjects(); + } + + public void ExecuteEditorCommand(INodeEditorCommand command) + { + command.Execute(); + + _undoCommands.Push(command); + ClearRedo(); + UpdateSubjects(); + } + + private void ClearRedo() + { + foreach (INodeEditorCommand nodeEditorCommand in _redoCommands) + if (nodeEditorCommand is IDisposable disposable) + disposable.Dispose(); + + _redoCommands.Clear(); + } + + private void ClearUndo() + { + foreach (INodeEditorCommand nodeEditorCommand in _undoCommands) + if (nodeEditorCommand is IDisposable disposable) + disposable.Dispose(); + + _undoCommands.Clear(); + } + + private INodeEditorCommand? ExecuteUndo() + { + if (!_undoCommands.TryPop(out INodeEditorCommand? command)) + return null; + + command.Undo(); + _redoCommands.Push(command); + UpdateSubjects(); + + return command; + } + + private INodeEditorCommand? ExecuteRedo() + { + if (!_redoCommands.TryPop(out INodeEditorCommand? command)) + return null; + + command.Execute(); + _undoCommands.Push(command); + UpdateSubjects(); + + return command; + } + + private void UpdateSubjects() + { + _canUndo.OnNext(_undoCommands.Any()); + _canRedo.OnNext(_redoCommands.Any()); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/NodeEditorService.cs b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/NodeEditorService.cs new file mode 100644 index 000000000..5f717b0e3 --- /dev/null +++ b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/NodeEditorService.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using Artemis.Core; +using Artemis.UI.Shared.Services.Interfaces; +using Artemis.UI.Shared.Services.NodeEditor.Commands; + +namespace Artemis.UI.Shared.Services.NodeEditor; + +/// +public class NodeEditorService : INodeEditorService +{ + private readonly IWindowService _windowService; + + public NodeEditorService(IWindowService windowService) + { + _windowService = windowService; + } + + private readonly Dictionary _nodeEditorHistories = new(); + private readonly Dictionary _nodeEditorCommandScopes = new(); + + /// + public NodeEditorHistory GetHistory(INodeScript nodeScript) + { + if (_nodeEditorHistories.TryGetValue(nodeScript, out NodeEditorHistory? history)) + return history; + + NodeEditorHistory newHistory = new(nodeScript); + _nodeEditorHistories.Add(nodeScript, newHistory); + return newHistory; + } + + /// + public void ExecuteCommand(INodeScript nodeScript, INodeEditorCommand command) + { + try + { + NodeEditorHistory history = GetHistory(nodeScript); + + // If a scope is active add the command to it, the scope will execute it immediately + _nodeEditorCommandScopes.TryGetValue(nodeScript, out NodeEditorCommandScope? scope); + if (scope != null) + { + scope.AddCommand(command); + return; + } + + history.Execute.Execute(command).Subscribe(); + } + catch (Exception e) + { + _windowService.ShowExceptionDialog("Editor command failed", e); + throw; + } + } + + /// + public NodeEditorCommandScope CreateCommandScope(INodeScript nodeScript, string name) + { + if (_nodeEditorCommandScopes.TryGetValue(nodeScript, out NodeEditorCommandScope? scope)) + throw new ArtemisSharedUIException($"A command scope is already active, name: {scope.Name}."); + + NodeEditorCommandScope newScope = new(this, nodeScript, name); + _nodeEditorCommandScopes.Add(nodeScript, newScope); + return newScope; + } + + internal void StopCommandScope(INodeScript nodeScript) + { + // This might happen if the scope is disposed twice, it's no biggie + if (!_nodeEditorCommandScopes.TryGetValue(nodeScript, out NodeEditorCommandScope? scope)) + return; + + _nodeEditorCommandScopes.Remove(nodeScript); + + // Executing the composite command won't do anything the first time (see last ctor variable) + // commands were already executed each time they were added to the scope + ExecuteCommand(nodeScript, new CompositeCommand(scope.NodeEditorCommands, scope.Name, true)); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/VisualScripting/CustomNodeViewModel.cs b/src/Avalonia/Artemis.UI.Shared/VisualScripting/CustomNodeViewModel.cs new file mode 100644 index 000000000..dcff99245 --- /dev/null +++ b/src/Avalonia/Artemis.UI.Shared/VisualScripting/CustomNodeViewModel.cs @@ -0,0 +1,49 @@ +using System; +using System.ComponentModel; +using System.Reactive.Disposables; +using Artemis.Core; +using ReactiveUI; + +namespace Artemis.UI.Shared.VisualScripting +{ + public abstract class CustomNodeViewModel : ActivatableViewModelBase, ICustomNodeViewModel + { + protected CustomNodeViewModel(INode node) + { + Node = node; + + this.WhenActivated(d => + { + Node.PropertyChanged += NodeOnPropertyChanged; + Disposable.Create(() => Node.PropertyChanged -= NodeOnPropertyChanged).DisposeWith(d); + }); + } + + public INode Node { get; } + + #region Events + + /// + public event EventHandler NodeModified; + + /// + /// Invokes the event + /// + protected virtual void OnNodeModified() + { + NodeModified?.Invoke(this, EventArgs.Empty); + } + + #endregion + + #region Event handlers + + private void NodeOnPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "Storage") + OnNodeModified(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Windows/packages.lock.json b/src/Avalonia/Artemis.UI.Windows/packages.lock.json index 8ce041f51..c8195bec5 100644 --- a/src/Avalonia/Artemis.UI.Windows/packages.lock.json +++ b/src/Avalonia/Artemis.UI.Windows/packages.lock.json @@ -90,11 +90,6 @@ "Splat": "14.1.45" } }, - "ArtemisRGB.Plugins.BuildTask": { - "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "B8A5NkUEzTUgc5M/QACfyjIF5M23EUzSx8A8l/owJtB0bgmij6y/MQW1i/PcS3EDFQRothBOUuC3BCPY5UoRRQ==" - }, "Avalonia.Angle.Windows.Natives": { "type": "Transitive", "resolved": "2.1.0.2020091801", @@ -447,6 +442,11 @@ "resolved": "5.0.0", "contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q==" }, + "Microsoft.Extensions.ObjectPool": { + "type": "Transitive", + "resolved": "5.0.9", + "contentHash": "grj0e6Me0EQsgaurV0fxP0xd8sz8eZVK+Jb816DPzNADHaqXaXJD3xZX9SFjyDl3ykAYvD0y77o5vRd9Hzsk9g==" + }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "5.0.0", @@ -565,10 +565,13 @@ "Ninject": "3.3.3" } }, - "OpenRGB.NET": { + "NoStringEvaluating": { "type": "Transitive", - "resolved": "1.7.0", - "contentHash": "12pMEUaeoG8mN707QRO9hdT529+UnqUpwMW1H/gDTMsJrerhJve6Yt5Dnheu1isQB4PWP1wu3IDVbHCchznkiw==" + "resolved": "2.2.2", + "contentHash": "hJHivPDA1Vxn0CCgOtHKZ3fmldxQuz7VL1J4lEaPTXCf+Vwcx1FDf05mGMh6olYMSxoKimGX8YK2sEoqeH3pnA==", + "dependencies": { + "Microsoft.Extensions.ObjectPool": "5.0.9" + } }, "ReactiveUI.Validation": { "type": "Transitive", @@ -1772,21 +1775,6 @@ "System.ValueTuple": "4.5.0" } }, - "artemis.plugins.devices.openrgb": { - "type": "Project", - "dependencies": { - "ArtemisRGB.Plugins.BuildTask": "1.1.0", - "Avalonia": "0.10.11", - "Avalonia.Controls.DataGrid": "0.10.11", - "Avalonia.ReactiveUI": "0.10.11", - "Material.Icons.Avalonia": "1.0.2", - "OpenRGB.NET": "1.7.0", - "RGB.NET.Core": "1.0.0-prerelease7", - "ReactiveUI": "16.3.10", - "Serilog": "2.10.0", - "SkiaSharp": "2.88.0-preview.178" - } - }, "artemis.storage": { "type": "Project", "dependencies": { @@ -1799,6 +1787,7 @@ "dependencies": { "Artemis.Core": "1.0.0", "Artemis.UI.Shared": "1.0.0", + "Artemis.VisualScripting": "1.0.0", "Avalonia": "0.10.13", "Avalonia.Controls.PanAndZoom": "10.12.0", "Avalonia.Desktop": "0.10.13", @@ -1836,6 +1825,19 @@ "ReactiveUI.Validation": "2.2.1", "SkiaSharp": "2.88.0-preview.178" } + }, + "artemis.visualscripting": { + "type": "Project", + "dependencies": { + "Artemis.Core": "1.0.0", + "Artemis.UI.Shared": "1.0.0", + "Avalonia": "0.10.13", + "Avalonia.ReactiveUI": "0.10.13", + "Ninject": "3.3.4", + "NoStringEvaluating": "2.2.2", + "ReactiveUI": "17.1.50", + "SkiaSharp": "2.88.0-preview.178" + } } } } diff --git a/src/Avalonia/Artemis.UI/Artemis.UI.csproj b/src/Avalonia/Artemis.UI/Artemis.UI.csproj index d0eb58247..44930e0ee 100644 --- a/src/Avalonia/Artemis.UI/Artemis.UI.csproj +++ b/src/Avalonia/Artemis.UI/Artemis.UI.csproj @@ -39,6 +39,7 @@ + diff --git a/src/Avalonia/Artemis.UI/ArtemisBootstrapper.cs b/src/Avalonia/Artemis.UI/ArtemisBootstrapper.cs index a72152b46..cca29609a 100644 --- a/src/Avalonia/Artemis.UI/ArtemisBootstrapper.cs +++ b/src/Avalonia/Artemis.UI/ArtemisBootstrapper.cs @@ -7,6 +7,7 @@ using Artemis.UI.Ninject; using Artemis.UI.Screens.Root; using Artemis.UI.Shared.Ninject; using Artemis.UI.Shared.Services.Interfaces; +using Artemis.VisualScripting.Ninject; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; @@ -36,6 +37,7 @@ namespace Artemis.UI _kernel.Load(); _kernel.Load(); _kernel.Load(); + _kernel.Load(); _kernel.Load(modules); _kernel.UseNinjectDependencyResolver(); diff --git a/src/Avalonia/Artemis.UI/Converters/ColorLuminosityConverter.cs b/src/Avalonia/Artemis.UI/Converters/ColorLuminosityConverter.cs new file mode 100644 index 000000000..46fa7140d --- /dev/null +++ b/src/Avalonia/Artemis.UI/Converters/ColorLuminosityConverter.cs @@ -0,0 +1,44 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; +using Avalonia.Media; +using Avalonia.Skia; +using SkiaSharp; + +namespace Artemis.UI.Converters; + +public class ColorLuminosityConverter : IValueConverter +{ + /// + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is Color color && double.TryParse(parameter?.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out double multiplier)) + return multiplier > 0 ? Brighten(color, (float) multiplier) : Darken(color, (float) multiplier * -1); + + return value; + } + + /// + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return value; + } + + private static Color Brighten(Color color, float multiplier) + { + color.ToSKColor().ToHsl(out float h, out float s, out float l); + l += l * (multiplier / 100f); + SKColor skColor = SKColor.FromHsl(h, s, l); + + return new Color(skColor.Alpha, skColor.Red, skColor.Green, skColor.Blue); + } + + private static Color Darken(Color color, float multiplier) + { + color.ToSKColor().ToHsl(out float h, out float s, out float l); + l -= l * (multiplier / 100f); + SKColor skColor = SKColor.FromHsl(h, s, l); + + return new Color(skColor.Alpha, skColor.Red, skColor.Green, skColor.Blue); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Converters/ColorOpacityConverter.cs b/src/Avalonia/Artemis.UI/Converters/ColorOpacityConverter.cs index e69ed58f5..f3f12ad41 100644 --- a/src/Avalonia/Artemis.UI/Converters/ColorOpacityConverter.cs +++ b/src/Avalonia/Artemis.UI/Converters/ColorOpacityConverter.cs @@ -11,7 +11,7 @@ public class ColorOpacityConverter : IValueConverter public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is Color color && double.TryParse(parameter?.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out double multiplier)) - return new Color((byte) (color.A * multiplier), color.R, color.G, color.B); + return new Color((byte)(color.A * multiplier), color.R, color.G, color.B); return value; } diff --git a/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs index d575bfbc3..652feee62 100644 --- a/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -90,14 +90,18 @@ namespace Artemis.UI.Ninject.Factories ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel); } - public interface INodeVmFactory + public interface INodeVmFactory : IVmFactory { NodeScriptViewModel NodeScriptViewModel(NodeScript nodeScript); NodePickerViewModel NodePickerViewModel(NodeScript nodeScript); - NodeViewModel NodeViewModel(INode node); - InputPinCollectionViewModel InputPinCollectionViewModel(InputPinCollection inputPinCollection); - InputPinViewModel InputPinViewModel(InputPin inputPin); - OutputPinCollectionViewModel OutputPinCollectionViewModel(OutputPinCollection outputPinCollection); - OutputPinViewModel OutputPinViewModel(OutputPin outputPin); + NodeViewModel NodeViewModel(NodeScript nodeScript, INode node); + } + + public interface INodePinVmFactory + { + PinCollectionViewModel InputPinCollectionViewModel(PinCollection inputPinCollection); + PinViewModel InputPinViewModel(IPin inputPin); + PinCollectionViewModel OutputPinCollectionViewModel(PinCollection outputPinCollection); + PinViewModel OutputPinViewModel(IPin outputPin); } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Ninject/InstanceProviders/NodePinViewModelInstanceProvider.cs b/src/Avalonia/Artemis.UI/Ninject/InstanceProviders/NodePinViewModelInstanceProvider.cs new file mode 100644 index 000000000..c3a46614d --- /dev/null +++ b/src/Avalonia/Artemis.UI/Ninject/InstanceProviders/NodePinViewModelInstanceProvider.cs @@ -0,0 +1,37 @@ +using System; +using System.Reflection; +using Artemis.Core; +using Artemis.UI.Screens.VisualScripting.Pins; +using Ninject.Extensions.Factory; + +namespace Artemis.UI.Ninject.InstanceProviders; + +public class NodePinViewModelInstanceProvider : StandardInstanceProvider +{ + protected override Type GetType(MethodInfo methodInfo, object[] arguments) + { + if (methodInfo.ReturnType != typeof(PinCollectionViewModel) && methodInfo.ReturnType != typeof(PinViewModel)) + return base.GetType(methodInfo, arguments); + + if (arguments[0] is IPin pin) + return CreatePinViewModelType(pin); + if (arguments[0] is IPinCollection pinCollection) + return CreatePinCollectionViewModelType(pinCollection); + + return base.GetType(methodInfo, arguments); + } + + private Type CreatePinViewModelType(IPin pin) + { + if (pin.Direction == PinDirection.Input) + return typeof(InputPinViewModel<>).MakeGenericType(pin.Type); + return typeof(OutputPinViewModel<>).MakeGenericType(pin.Type); + } + + private Type CreatePinCollectionViewModelType(IPinCollection pinCollection) + { + if (pinCollection.Direction == PinDirection.Input) + return typeof(InputPinCollectionViewModel<>).MakeGenericType(pinCollection.Type); + return typeof(OutputPinCollectionViewModel<>).MakeGenericType(pinCollection.Type); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Ninject/UIModule.cs b/src/Avalonia/Artemis.UI/Ninject/UIModule.cs index e186b02a9..9abf0ef83 100644 --- a/src/Avalonia/Artemis.UI/Ninject/UIModule.cs +++ b/src/Avalonia/Artemis.UI/Ninject/UIModule.cs @@ -59,6 +59,7 @@ namespace Artemis.UI.Ninject }); Kernel.Bind().ToFactory(() => new LayerPropertyViewModelInstanceProvider()); + Kernel.Bind().ToFactory(() => new NodePinViewModelInstanceProvider()); // Bind all UI services as singletons Kernel.Bind(x => diff --git a/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs index 59acf2659..a3e1dc0b4 100644 --- a/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs @@ -181,6 +181,7 @@ namespace Artemis.UI.Screens.Root _registrationService.RegisterBuiltInDataModelDisplays(); _registrationService.RegisterBuiltInDataModelInputs(); _registrationService.RegisterBuiltInPropertyEditors(); + _registrationService.RegisterBuiltInNodeTypes(); if (_lifeTime.MainWindow == null) { diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml index 329c64a18..bb7c03328 100644 --- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml @@ -3,43 +3,54 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" + xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" + mc:Ignorable="d" d:DesignWidth="650" d:DesignHeight="450" x:Class="Artemis.UI.Screens.VisualScripting.NodePickerView" - x:DataType="visualScripting:NodePickerViewModel"> + x:DataType="visualScripting:NodePickerViewModel" + Width="600" + Height="400"> - - - - - Test + + + + + + + + + + + + + + + + None of the nodes match your search + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml.cs index 921da0c83..e6ddc86fc 100644 --- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml.cs +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml.cs @@ -1,7 +1,13 @@ +using System; +using System.Reactive.Linq; using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Mixins; +using Avalonia.LogicalTree; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; +using Avalonia.VisualTree; +using ReactiveUI; namespace Artemis.UI.Screens.VisualScripting { @@ -10,6 +16,13 @@ namespace Artemis.UI.Screens.VisualScripting public NodePickerView() { InitializeComponent(); + this.WhenActivated( + d => ViewModel + .WhenAnyValue(vm => vm.IsVisible) + .Where(visible => !visible) + .Subscribe(_ => this.FindLogicalAncestorOfType()?.ContextFlyout?.Hide()) + .DisposeWith(d) + ); } private void InitializeComponent() @@ -17,4 +30,4 @@ namespace Artemis.UI.Screens.VisualScripting AvaloniaXamlLoader.Load(this); } } -} +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerViewModel.cs index 231463b3b..9e66457c8 100644 --- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerViewModel.cs @@ -1,20 +1,51 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.ObjectModel; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Shared; +using Artemis.UI.Shared.Services.NodeEditor; +using Artemis.UI.Shared.Services.NodeEditor.Commands; using Avalonia; +using ReactiveUI; namespace Artemis.UI.Screens.VisualScripting; public class NodePickerViewModel : ActivatableViewModelBase { + private readonly INodeEditorService _nodeEditorService; + private readonly NodeScript _nodeScript; private readonly INodeService _nodeService; private bool _isVisible; private Point _position; + private string? _searchText; + private NodeData? _selectedNode; + + public NodePickerViewModel(NodeScript nodeScript, INodeService nodeService, INodeEditorService nodeEditorService) + { + _nodeScript = nodeScript; + _nodeService = nodeService; + _nodeEditorService = nodeEditorService; + + Nodes = new ObservableCollection(_nodeService.AvailableNodes); + + this.WhenActivated(d => + { + IsVisible = true; + Disposable.Create(() => IsVisible = false).DisposeWith(d); + }); + + this.WhenAnyValue(vm => vm.SelectedNode).WhereNotNull().Throttle(TimeSpan.FromMilliseconds(200), RxApp.MainThreadScheduler).Subscribe(data => + { + CreateNode(data); + Hide(); + SelectedNode = null; + }); + } + + public ObservableCollection Nodes { get; } public bool IsVisible { @@ -28,9 +59,16 @@ public class NodePickerViewModel : ActivatableViewModelBase set => RaiseAndSetIfChanged(ref _position, value); } - public NodePickerViewModel(INodeService nodeService) + public string? SearchText { - _nodeService = nodeService; + get => _searchText; + set => RaiseAndSetIfChanged(ref _searchText, value); + } + + public NodeData? SelectedNode + { + get => _selectedNode; + set => RaiseAndSetIfChanged(ref _selectedNode, value); } public void Show(Point position) @@ -43,4 +81,13 @@ public class NodePickerViewModel : ActivatableViewModelBase { IsVisible = false; } + + private void CreateNode(NodeData data) + { + INode node = data.CreateNode(_nodeScript, null); + node.X = Position.X; + node.Y = Position.Y; + + _nodeEditorService.ExecuteCommand(_nodeScript, new AddNode(_nodeScript, node)); + } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml index 2f0c9114b..fd189eb1d 100644 --- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml @@ -2,18 +2,70 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:paz="clr-namespace:Avalonia.Controls.PanAndZoom;assembly=Avalonia.Controls.PanAndZoom" xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.VisualScripting.NodeScriptView" x:DataType="visualScripting:NodeScriptViewModel"> - - + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs index 8f5e65282..c3094011b 100644 --- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs @@ -1,16 +1,50 @@ +using System; using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.PanAndZoom; using Avalonia.Input; +using Avalonia.Interactivity; using Avalonia.Markup.Xaml; +using Avalonia.Media; using Avalonia.ReactiveUI; +using Avalonia.VisualTree; namespace Artemis.UI.Screens.VisualScripting { public partial class NodeScriptView : ReactiveUserControl { + private readonly ZoomBorder _zoomBorder; + private readonly Grid _grid; + public NodeScriptView() { InitializeComponent(); + + _zoomBorder = this.Find("ZoomBorder"); + _grid = this.Find("ContainerGrid"); + _zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged; + UpdateZoomBorderBackground(); + + _grid?.AddHandler(PointerReleasedEvent, CanvasOnPointerReleased, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true); + } + + private void CanvasOnPointerReleased(object? sender, PointerReleasedEventArgs e) + { + // If the flyout handled the click, update the position of the node picker + if (e.Handled && ViewModel != null) + ViewModel.NodePickerViewModel.Position = e.GetPosition(_grid); + } + + private void ZoomBorderOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.Property.Name == nameof(_zoomBorder.Background)) + UpdateZoomBorderBackground(); + } + + private void UpdateZoomBorderBackground() + { + if (_zoomBorder.Background is VisualBrush visualBrush) + visualBrush.DestinationRect = new RelativeRect(_zoomBorder.OffsetX * -1, _zoomBorder.OffsetY * -1, 20, 20, RelativeUnit.Absolute); } private void InitializeComponent() @@ -18,12 +52,9 @@ namespace Artemis.UI.Screens.VisualScripting AvaloniaXamlLoader.Load(this); } - private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e) + private void ZoomBorder_OnZoomChanged(object sender, ZoomChangedEventArgs e) { - if (e.InitialPressMouseButton == MouseButton.Right) - ViewModel?.ShowNodePicker(e.GetCurrentPoint(this).Position); - if (e.InitialPressMouseButton == MouseButton.Left) - ViewModel?.HideNodePicker(); + UpdateZoomBorderBackground(); } } -} +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs index e584407bc..88caf49a6 100644 --- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs @@ -7,6 +7,7 @@ using Artemis.Core.Events; using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared; +using Artemis.UI.Shared.Services.NodeEditor; using Avalonia; using Avalonia.Controls.Mixins; using ReactiveUI; @@ -16,15 +17,18 @@ namespace Artemis.UI.Screens.VisualScripting; public class NodeScriptViewModel : ActivatableViewModelBase { private readonly INodeService _nodeService; + private readonly INodeEditorService _nodeEditorService; private readonly INodeVmFactory _nodeVmFactory; - public NodeScriptViewModel(NodeScript nodeScript, INodeVmFactory nodeVmFactory, INodeService nodeService) + public NodeScriptViewModel(NodeScript nodeScript, INodeVmFactory nodeVmFactory, INodeService nodeService, INodeEditorService nodeEditorService) { _nodeVmFactory = nodeVmFactory; _nodeService = nodeService; + _nodeEditorService = nodeEditorService; NodeScript = nodeScript; NodePickerViewModel = _nodeVmFactory.NodePickerViewModel(nodeScript); + History = _nodeEditorService.GetHistory(NodeScript); this.WhenActivated(d => { @@ -38,17 +42,18 @@ public class NodeScriptViewModel : ActivatableViewModelBase NodeViewModels = new ObservableCollection(); foreach (INode nodeScriptNode in NodeScript.Nodes) - NodeViewModels.Add(_nodeVmFactory.NodeViewModel(nodeScriptNode)); + NodeViewModels.Add(_nodeVmFactory.NodeViewModel(NodeScript, nodeScriptNode)); } public NodeScript NodeScript { get; } public ObservableCollection NodeViewModels { get; } + public ObservableCollection CableViewModels { get; } public NodePickerViewModel NodePickerViewModel { get; } - + public NodeEditorHistory History { get; } private void HandleNodeAdded(SingleValueEventArgs eventArgs) { - NodeViewModels.Add(_nodeVmFactory.NodeViewModel(eventArgs.Value)); + NodeViewModels.Add(_nodeVmFactory.NodeViewModel(NodeScript, eventArgs.Value)); } private void HandleNodeRemoved(SingleValueEventArgs eventArgs) @@ -57,14 +62,4 @@ public class NodeScriptViewModel : ActivatableViewModelBase if (toRemove != null) NodeViewModels.Remove(toRemove); } - - public void ShowNodePicker(Point position) - { - NodePickerViewModel.Show(position); - } - - public void HideNodePicker() - { - NodePickerViewModel.Hide(); - } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml index 37cfb4264..a3ce0fbda 100644 --- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml @@ -2,7 +2,57 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Screens.VisualScripting.NodeView"> - Welcome to Avalonia! - + xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" + xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting" + mc:Ignorable="d" d:DesignWidth="250" d:DesignHeight="150" + x:Class="Artemis.UI.Screens.VisualScripting.NodeView" + x:DataType="visualScripting:NodeViewModel"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml.cs index 7d8e70086..9244d4791 100644 --- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml.cs +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml.cs @@ -1,20 +1,36 @@ +using System; using Avalonia; -using Avalonia.Controls; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; -namespace Artemis.UI.Screens.VisualScripting -{ - public partial class NodeView : ReactiveUserControl - { - public NodeView() - { - InitializeComponent(); - } +namespace Artemis.UI.Screens.VisualScripting; - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } +public class NodeView : ReactiveUserControl +{ + public NodeView() + { + InitializeComponent(); } -} + + #region Overrides of Layoutable + + /// + protected override Size MeasureOverride(Size availableSize) + { + // Take the base implementation's size + (double width, double height) = base.MeasureOverride(availableSize); + + // Ceil the resulting size + width = Math.Ceiling(width / 10.0) * 10.0; + height = Math.Ceiling(height / 10.0) * 10.0; + + return new Size(width, height); + } + + #endregion + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs index e38eed9ed..07be3a158 100644 --- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs @@ -1,23 +1,79 @@ -using Artemis.Core; +using System; +using System.Collections.ObjectModel; +using System.Reactive; +using System.Reactive.Linq; +using Artemis.Core; +using Artemis.UI.Ninject.Factories; +using Artemis.UI.Screens.VisualScripting.Pins; using Artemis.UI.Shared; -using Avalonia; +using Artemis.UI.Shared.Services.NodeEditor; +using Artemis.UI.Shared.Services.NodeEditor.Commands; +using Avalonia.Controls.Mixins; +using DynamicData; +using ReactiveUI; namespace Artemis.UI.Screens.VisualScripting; public class NodeViewModel : ActivatableViewModelBase { - private Point _position; + private readonly NodeScript _nodeScript; + private readonly INodeEditorService _nodeEditorService; - public NodeViewModel(INode node) + private ICustomNodeViewModel? _customNodeViewModel; + private ReactiveCommand? _deleteNode; + private ObservableAsPropertyHelper? _isStaticNode; + + public NodeViewModel(NodeScript nodeScript, INode node, INodePinVmFactory nodePinVmFactory, INodeEditorService nodeEditorService) { + _nodeScript = nodeScript; + _nodeEditorService = nodeEditorService; Node = node; + + SourceList nodePins = new(); + this.WhenActivated(d => + { + _isStaticNode = Node.WhenAnyValue(n => n.IsDefaultNode, n => n.IsExitNode) + .Select(tuple => tuple.Item1 || tuple.Item2) + .ToProperty(this, model => model.IsStaticNode) + .DisposeWith(d); + + Node.WhenAnyValue(n => n.Pins).Subscribe(pins => nodePins.Edit(source => + { + source.Clear(); + source.AddRange(pins); + })).DisposeWith(d); + }); + + DeleteNode = ReactiveCommand.Create(ExecuteDeleteNode, this.WhenAnyValue(vm => vm.IsStaticNode).Select(v => !v)); + + nodePins.Connect().Filter(n => n.Direction == PinDirection.Input).Transform(nodePinVmFactory.InputPinViewModel).Bind(out ReadOnlyObservableCollection inputPins).Subscribe(); + nodePins.Connect().Filter(n => n.Direction == PinDirection.Output).Transform(nodePinVmFactory.OutputPinViewModel).Bind(out ReadOnlyObservableCollection outputPins).Subscribe(); + InputPinViewModels = inputPins; + OutputPinViewModels = outputPins; } + public bool IsStaticNode => _isStaticNode?.Value ?? true; + public INode Node { get; } + public ReadOnlyObservableCollection InputPinViewModels { get; } + public ReadOnlyObservableCollection InputPinCollectionViewModels { get; } + public ReadOnlyObservableCollection OutputPinViewModels { get; } + public ReadOnlyObservableCollection OutputPinCollectionViewModels { get; } - public Point Position + public ICustomNodeViewModel? CustomNodeViewModel { - get => _position; - set => RaiseAndSetIfChanged(ref _position, value); + get => _customNodeViewModel; + set => RaiseAndSetIfChanged(ref _customNodeViewModel, value); + } + + public ReactiveCommand? DeleteNode + { + get => _deleteNode; + set => RaiseAndSetIfChanged(ref _deleteNode, value); + } + + private void ExecuteDeleteNode() + { + _nodeEditorService.ExecuteCommand(_nodeScript, new DeleteNode(_nodeScript, Node)); } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinView.axaml index ae1b6724d..523457358 100644 --- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinView.axaml @@ -2,7 +2,30 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Screens.VisualScripting.Pins.InputPinView"> - Welcome to Avalonia! - + xmlns:pins="clr-namespace:Artemis.UI.Screens.VisualScripting.Pins" + mc:Ignorable="d" + d:DesignWidth="200" + x:Class="Artemis.UI.Screens.VisualScripting.Pins.InputPinView" + x:DataType="pins:PinViewModel"> + + + + + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinViewModel.cs index ed148b458..098c76d3c 100644 --- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinViewModel.cs @@ -1,4 +1,5 @@ using Artemis.Core; +using Artemis.Core.Services; namespace Artemis.UI.Screens.VisualScripting.Pins; @@ -6,7 +7,7 @@ public class InputPinViewModel : PinViewModel { public InputPin InputPin { get; } - public InputPinViewModel(InputPin inputPin) : base(inputPin) + public InputPinViewModel(InputPin inputPin, INodeService nodeService) : base(inputPin, nodeService) { InputPin = inputPin; } diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinView.axaml index 893ff48e2..cb8e3d832 100644 --- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinView.axaml @@ -2,7 +2,30 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Screens.VisualScripting.Pins.OutputPinView"> - Welcome to Avalonia! - + xmlns:pins="clr-namespace:Artemis.UI.Screens.VisualScripting.Pins" + mc:Ignorable="d" + d:DesignWidth="200" + x:Class="Artemis.UI.Screens.VisualScripting.Pins.OutputPinView" + x:DataType="pins:PinViewModel"> + + + + + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinViewModel.cs index 0719fdcab..8f0e8e82b 100644 --- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinViewModel.cs @@ -1,4 +1,5 @@ using Artemis.Core; +using Artemis.Core.Services; namespace Artemis.UI.Screens.VisualScripting.Pins; @@ -6,7 +7,7 @@ public class OutputPinViewModel : PinViewModel { public OutputPin OutputPin { get; } - public OutputPinViewModel(OutputPin outputPin) : base(outputPin) + public OutputPinViewModel(OutputPin outputPin, INodeService nodeService) : base(outputPin, nodeService) { OutputPin = outputPin; } diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/PinViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/PinViewModel.cs index ec321632a..0b1ce6202 100644 --- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/PinViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/PinViewModel.cs @@ -1,14 +1,22 @@ using Artemis.Core; +using Artemis.Core.Services; using Artemis.UI.Shared; +using Avalonia.Media; namespace Artemis.UI.Screens.VisualScripting.Pins; public abstract class PinViewModel : ActivatableViewModelBase { - protected PinViewModel(IPin pin) + protected PinViewModel(IPin pin, INodeService nodeService) { Pin = pin; + + TypeColorRegistration registration = nodeService.GetTypeColorRegistration(Pin.Type); + PinColor = new Color(registration.Color.Alpha, registration.Color.Red, registration.Color.Green, registration.Color.Blue); + DarkenedPinColor = new Color(registration.DarkenedColor.Alpha, registration.DarkenedColor.Red, registration.DarkenedColor.Green, registration.DarkenedColor.Blue); } public IPin Pin { get; } + public Color PinColor { get; } + public Color DarkenedPinColor { get; } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/VisualScripting.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/VisualScripting.axaml new file mode 100644 index 000000000..afd62ecf2 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/VisualScripting.axaml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/src/Avalonia/Artemis.UI/Screens/Workshop/WorkshopView.axaml b/src/Avalonia/Artemis.UI/Screens/Workshop/WorkshopView.axaml index 1580a3f95..6f972839c 100644 --- a/src/Avalonia/Artemis.UI/Screens/Workshop/WorkshopView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/Workshop/WorkshopView.axaml @@ -12,49 +12,54 @@ x:Class="Artemis.UI.Screens.Workshop.WorkshopView" x:DataType="workshop:WorkshopViewModel"> - - Workshop!! :3 - - - Notification tests - - - - + + + + Nodes tests + + + + + + Notification tests + + + + - + - - - - - - - + + + + + + - + - - + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/Workshop/WorkshopViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Workshop/WorkshopViewModel.cs index cb7f09681..694ad3741 100644 --- a/src/Avalonia/Artemis.UI/Screens/Workshop/WorkshopViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Workshop/WorkshopViewModel.cs @@ -2,6 +2,8 @@ using System.Reactive; using System.Reactive.Linq; using Artemis.Core; +using Artemis.UI.Ninject.Factories; +using Artemis.UI.Screens.VisualScripting; using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Interfaces; using Avalonia.Input; @@ -25,15 +27,19 @@ namespace Artemis.UI.Screens.Workshop new ColorGradientStop(new SKColor(0xFF00FCCC), 1f), }; - public WorkshopViewModel(IScreen hostScreen, INotificationService notificationService) : base(hostScreen, "workshop") + public WorkshopViewModel(IScreen hostScreen, INotificationService notificationService, INodeVmFactory nodeVmFactory) : base(hostScreen, "workshop") { _notificationService = notificationService; _cursor = this.WhenAnyValue(vm => vm.SelectedCursor).Select(c => new Cursor(c)).ToProperty(this, vm => vm.Cursor); DisplayName = "Workshop"; ShowNotification = ReactiveCommand.Create(ExecuteShowNotification); + + VisualEditorViewModel = nodeVmFactory.NodeScriptViewModel(new NodeScript("Test script", "A test script")); } + public NodeScriptViewModel VisualEditorViewModel { get; } + public ReactiveCommand ShowNotification { get; set; } public StandardCursorType SelectedCursor diff --git a/src/Avalonia/Artemis.UI/Services/Interfaces/IRegistrationService.cs b/src/Avalonia/Artemis.UI/Services/Interfaces/IRegistrationService.cs index 4c45d8b28..838947dbc 100644 --- a/src/Avalonia/Artemis.UI/Services/Interfaces/IRegistrationService.cs +++ b/src/Avalonia/Artemis.UI/Services/Interfaces/IRegistrationService.cs @@ -7,5 +7,6 @@ void RegisterBuiltInPropertyEditors(); void RegisterControllers(); void ApplyPreferredGraphicsContext(); + void RegisterBuiltInNodeTypes(); } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Services/RegistrationService.cs b/src/Avalonia/Artemis.UI/Services/RegistrationService.cs index f6f8e6c53..24574e35b 100644 --- a/src/Avalonia/Artemis.UI/Services/RegistrationService.cs +++ b/src/Avalonia/Artemis.UI/Services/RegistrationService.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.DefaultTypes.PropertyInput; @@ -6,9 +9,11 @@ using Artemis.UI.Services.Interfaces; using Artemis.UI.Shared.Providers; using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.PropertyInput; +using Artemis.VisualScripting.Nodes; using Avalonia; using DynamicData; using Ninject; +using SkiaSharp; namespace Artemis.UI.Services; @@ -17,13 +22,15 @@ public class RegistrationService : IRegistrationService private readonly IKernel _kernel; private readonly IInputService _inputService; private readonly IPropertyInputService _propertyInputService; + private readonly INodeService _nodeService; private bool _registeredBuiltInPropertyEditors; - public RegistrationService(IKernel kernel, IInputService inputService, IPropertyInputService propertyInputService, IProfileEditorService profileEditorService, IEnumerable toolViewModels) + public RegistrationService(IKernel kernel, IInputService inputService, IPropertyInputService propertyInputService, IProfileEditorService profileEditorService, INodeService nodeService, IEnumerable toolViewModels) { _kernel = kernel; _inputService = inputService; _propertyInputService = propertyInputService; + _nodeService = nodeService; profileEditorService.Tools.AddRange(toolViewModels); CreateCursorResources(); @@ -75,4 +82,18 @@ public class RegistrationService : IRegistrationService public void ApplyPreferredGraphicsContext() { } + + public void RegisterBuiltInNodeTypes() + { + _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(bool), new SKColor(0xFFCD3232)); + _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(string), new SKColor(0xFFFFD700)); + _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(int), new SKColor(0xFF32CD32)); + _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(float), new SKColor(0xFFFF7C00)); + _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(SKColor), new SKColor(0xFFAD3EED)); + _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(IList), new SKColor(0xFFED3E61)); + _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); + } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/packages.lock.json b/src/Avalonia/Artemis.UI/packages.lock.json index 9a5fbda19..1e975be58 100644 --- a/src/Avalonia/Artemis.UI/packages.lock.json +++ b/src/Avalonia/Artemis.UI/packages.lock.json @@ -481,6 +481,11 @@ "resolved": "5.0.0", "contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q==" }, + "Microsoft.Extensions.ObjectPool": { + "type": "Transitive", + "resolved": "5.0.9", + "contentHash": "grj0e6Me0EQsgaurV0fxP0xd8sz8eZVK+Jb816DPzNADHaqXaXJD3xZX9SFjyDl3ykAYvD0y77o5vRd9Hzsk9g==" + }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "5.0.0", @@ -599,6 +604,14 @@ "Ninject": "3.3.3" } }, + "NoStringEvaluating": { + "type": "Transitive", + "resolved": "2.2.2", + "contentHash": "hJHivPDA1Vxn0CCgOtHKZ3fmldxQuz7VL1J4lEaPTXCf+Vwcx1FDf05mGMh6olYMSxoKimGX8YK2sEoqeH3pnA==", + "dependencies": { + "Microsoft.Extensions.ObjectPool": "5.0.9" + } + }, "RGB.NET.Presets": { "type": "Transitive", "resolved": "1.0.0-prerelease7", @@ -1783,6 +1796,19 @@ "ReactiveUI.Validation": "2.2.1", "SkiaSharp": "2.88.0-preview.178" } + }, + "artemis.visualscripting": { + "type": "Project", + "dependencies": { + "Artemis.Core": "1.0.0", + "Artemis.UI.Shared": "1.0.0", + "Avalonia": "0.10.13", + "Avalonia.ReactiveUI": "0.10.13", + "Ninject": "3.3.4", + "NoStringEvaluating": "2.2.2", + "ReactiveUI": "17.1.50", + "SkiaSharp": "2.88.0-preview.178" + } } } } diff --git a/src/Avalonia/Artemis.VisualScripting/Artemis.VisualScripting.csproj b/src/Avalonia/Artemis.VisualScripting/Artemis.VisualScripting.csproj new file mode 100644 index 000000000..be4170e96 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Artemis.VisualScripting.csproj @@ -0,0 +1,74 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + $(DefaultXamlRuntime) + MSBuild:Compile + + + $(DefaultXamlRuntime) + MSBuild:Compile + + + $(DefaultXamlRuntime) + MSBuild:Compile + + + $(DefaultXamlRuntime) + MSBuild:Compile + + + $(DefaultXamlRuntime) + MSBuild:Compile + + + $(DefaultXamlRuntime) + MSBuild:Compile + + + $(DefaultXamlRuntime) + MSBuild:Compile + + + $(DefaultXamlRuntime) + MSBuild:Compile + + + $(DefaultXamlRuntime) + MSBuild:Compile + + + + + + + + + diff --git a/src/Avalonia/Artemis.VisualScripting/Ninject/NoStringNinjectModule.cs b/src/Avalonia/Artemis.VisualScripting/Ninject/NoStringNinjectModule.cs new file mode 100644 index 000000000..3bf75bd56 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Ninject/NoStringNinjectModule.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using Microsoft.Extensions.ObjectPool; +using Ninject.Modules; +using NoStringEvaluating; +using NoStringEvaluating.Contract; +using NoStringEvaluating.Models.Values; +using NoStringEvaluating.Services.Cache; +using NoStringEvaluating.Services.Checking; +using NoStringEvaluating.Services.Parsing; +using NoStringEvaluating.Services.Parsing.NodeReaders; + +namespace Artemis.VisualScripting.Ninject +{ + public class NoStringNinjectModule : NinjectModule + { + public override void Load() + { + // Pooling + Bind>>() + .ToConstant(ObjectPool.Create>()) + .InSingletonScope(); + + Bind>>() + .ToConstant(ObjectPool.Create>()) + .InSingletonScope(); + + Bind>() + .ToConstant(ObjectPool.Create()) + .InSingletonScope(); + + // Parser + Bind().To().InSingletonScope(); + Bind().To().InSingletonScope(); + Bind().To().InSingletonScope(); + + // Checker + Bind().To().InSingletonScope(); + + // Evaluator + Bind().To().InSingletonScope(); + + // If needed + InjectUserDefinedFunctions(); + } + + private void InjectUserDefinedFunctions() + { + IFunctionReader functionReader = (IFunctionReader) Kernel!.GetService(typeof(IFunctionReader)); + NoStringFunctionsInitializer.InitializeFunctions(functionReader, typeof(NoStringNinjectModule)); + } + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/BoolOperations.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/BoolOperations.cs new file mode 100644 index 000000000..a900a4fb1 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/BoolOperations.cs @@ -0,0 +1,299 @@ +using System.Collections; +using Artemis.Core; +using Artemis.VisualScripting.Nodes.CustomViewModels; + +namespace Artemis.VisualScripting.Nodes; + +[Node("Greater than", "Checks if the first input is greater than the second.", "Operators", InputType = typeof(object), OutputType = typeof(bool))] +public class GreaterThanNode : Node +{ + #region Constructors + + public GreaterThanNode() + : base("Greater than", "Checks if the first input is greater than the second.") + { + Input1 = CreateInputPin(); + Input2 = CreateInputPin(); + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + if (Input1.Value is Numeric numeric1 && Input2.Value is Numeric numeric2) + { + Result.Value = numeric1 > numeric2; + return; + } + + if (Input2.Value != null && Input1.Value != null && Input1.Value.IsNumber() && Input2.Value.IsNumber()) + { + Result.Value = Convert.ToSingle(Input1.Value) > Convert.ToSingle(Input2.Value); + return; + } + + try + { + Result.Value = Comparer.DefaultInvariant.Compare(Input1.Value, Input2.Value) == 1; + } + catch + { + Result.Value = false; + } + } + + #endregion + + #region Properties & Fields + + public InputPin Input1 { get; } + public InputPin Input2 { get; } + + public OutputPin Result { get; } + + #endregion +} + +[Node("Less than", "Checks if the first input is less than the second.", "Operators", InputType = typeof(object), OutputType = typeof(bool))] +public class LessThanNode : Node +{ + #region Constructors + + public LessThanNode() + : base("Less than", "Checks if the first input is less than the second.") + { + Input1 = CreateInputPin(); + Input2 = CreateInputPin(); + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + if (Input1.Value is Numeric numeric1 && Input2.Value is Numeric numeric2) + { + Result.Value = numeric1 < numeric2; + return; + } + + if (Input2.Value != null && Input1.Value != null && Input1.Value.IsNumber() && Input2.Value.IsNumber()) + { + Result.Value = Convert.ToSingle(Input1.Value) < Convert.ToSingle(Input2.Value); + return; + } + + try + { + Result.Value = Comparer.DefaultInvariant.Compare(Input1.Value, Input2.Value) == -1; + } + catch + { + Result.Value = false; + } + } + + #endregion + + #region Properties & Fields + + public InputPin Input1 { get; } + public InputPin Input2 { get; } + + public OutputPin Result { get; } + + #endregion +} + +[Node("Equals", "Checks if the two inputs are equals.", "Operators", InputType = typeof(bool), OutputType = typeof(bool))] +public class EqualsNode : Node +{ + #region Constructors + + public EqualsNode() + : base("Equals", "Checks if the two inputs are equals.") + { + Input1 = CreateInputPin(); + Input2 = CreateInputPin(); + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + try + { + Result.Value = Equals(Input1.Value, Input2.Value); + } + catch + { + Result.Value = false; + } + } + + #endregion + + #region Properties & Fields + + public InputPin Input1 { get; } + public InputPin Input2 { get; } + + public OutputPin Result { get; } + + #endregion +} + +[Node("Negate", "Negates the boolean.", "Operators", InputType = typeof(bool), OutputType = typeof(bool))] +public class NegateNode : Node +{ + #region Constructors + + public NegateNode() + : base("Negate", "Negates the boolean.") + { + Input = CreateInputPin(); + Output = CreateOutputPin(); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + Output.Value = !Input.Value; + } + + #endregion + + #region Properties & Fields + + public InputPin Input { get; } + public OutputPin Output { get; } + + #endregion +} + +[Node("And", "Checks if all inputs are true.", "Operators", InputType = typeof(bool), OutputType = typeof(bool))] +public class AndNode : Node +{ + #region Constructors + + public AndNode() + : base("And", "Checks if all inputs are true.") + { + Input = CreateInputPinCollection(); + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + Result.Value = Input.Values.All(v => v); + } + + #endregion + + #region Properties & Fields + + public InputPinCollection Input { get; set; } + public OutputPin Result { get; } + + #endregion +} + +[Node("Or", "Checks if any inputs are true.", "Operators", InputType = typeof(bool), OutputType = typeof(bool))] +public class OrNode : Node +{ + #region Constructors + + public OrNode() + : base("Or", "Checks if any inputs are true.") + { + Input = CreateInputPinCollection(); + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + Result.Value = Input.Values.Any(v => v); + } + + #endregion + + #region Properties & Fields + + public InputPinCollection Input { get; set; } + public OutputPin Result { get; } + + #endregion +} + +[Node("Exclusive Or", "Checks if one of the inputs is true.", "Operators", InputType = typeof(bool), OutputType = typeof(bool))] +public class XorNode : Node +{ + #region Constructors + + public XorNode() + : base("Exclusive Or", "Checks if one of the inputs is true.") + { + Input = CreateInputPinCollection(); + Result = CreateOutputPin(); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + Result.Value = Input.Values.Count(v => v) == 1; + } + + #endregion + + #region Properties & Fields + + public InputPinCollection Input { get; set; } + public OutputPin Result { get; } + + #endregion +} + +[Node("Enum Equals", "Determines the equality between an input and a selected enum value", "Operators", InputType = typeof(Enum), OutputType = typeof(bool))] +public class EnumEqualsNode : Node +{ + public EnumEqualsNode() : base("Enum Equals", "Determines the equality between an input and a selected enum value") + { + InputPin = CreateInputPin(); + OutputPin = CreateOutputPin(); + } + + public InputPin InputPin { get; } + public OutputPin OutputPin { get; } + + #region Overrides of Node + + /// + public override void Evaluate() + { + OutputPin.Value = InputPin.Value != null && InputPin.Value.Equals(Storage); + } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Color/BrightenSKColorNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/BrightenSKColorNode.cs new file mode 100644 index 000000000..384d09d88 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/BrightenSKColorNode.cs @@ -0,0 +1,26 @@ +using Artemis.Core; +using SkiaSharp; + +namespace Artemis.VisualScripting.Nodes.Color; + +[Node("Brighten Color", "Brightens a color by a specified amount in percent", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))] +public class BrightenSKColorNode : Node +{ + public BrightenSKColorNode() : base("Brighten Color", "Brightens a color by a specified amount in percent") + { + Input = CreateInputPin("Color"); + Percentage = CreateInputPin("%"); + Output = CreateOutputPin(); + } + + public InputPin Input { get; } + public InputPin Percentage { get; } + public OutputPin Output { get; set; } + + public override void Evaluate() + { + Input.Value.ToHsl(out float h, out float s, out float l); + l += l * (Percentage.Value / 100f); + Output.Value = SKColor.FromHsl(h, s, l); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Color/CustomViewModels/StaticSKColorValueNodeCustomViewModel.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/CustomViewModels/StaticSKColorValueNodeCustomViewModel.cs new file mode 100644 index 000000000..a71435a36 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/CustomViewModels/StaticSKColorValueNodeCustomViewModel.cs @@ -0,0 +1,10 @@ +using Artemis.UI.Shared.VisualScripting; + +namespace Artemis.VisualScripting.Nodes.Color.CustomViewModels; + +public class StaticSKColorValueNodeCustomViewModel : CustomNodeViewModel +{ + public StaticSKColorValueNodeCustomViewModel(StaticSKColorValueNode node) : base(node) + { + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Color/CustomViews/StaticSKColorValueNodeCustomView.xaml b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/CustomViews/StaticSKColorValueNodeCustomView.xaml new file mode 100644 index 000000000..eb02e7984 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/CustomViews/StaticSKColorValueNodeCustomView.xaml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Color/DarkenSKColorNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/DarkenSKColorNode.cs new file mode 100644 index 000000000..8ba31d1d6 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/DarkenSKColorNode.cs @@ -0,0 +1,26 @@ +using Artemis.Core; +using SkiaSharp; + +namespace Artemis.VisualScripting.Nodes.Color; + +[Node("Darken Color", "Darkens a color by a specified amount in percent", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))] +public class DarkenSKColorNode : Node +{ + public DarkenSKColorNode() : base("Darken Color", "Darkens a color by a specified amount in percent") + { + Input = CreateInputPin("Color"); + Percentage = CreateInputPin("%"); + Output = CreateOutputPin(); + } + + public InputPin Input { get; } + public InputPin Percentage { get; } + public OutputPin Output { get; set; } + + public override void Evaluate() + { + Input.Value.ToHsl(out float h, out float s, out float l); + l -= l * (Percentage.Value / 100f); + Output.Value = SKColor.FromHsl(h, s, l); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Color/DesaturateSKColorNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/DesaturateSKColorNode.cs new file mode 100644 index 000000000..818b77bb4 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/DesaturateSKColorNode.cs @@ -0,0 +1,26 @@ +using Artemis.Core; +using SkiaSharp; + +namespace Artemis.VisualScripting.Nodes.Color; + +[Node("Desaturate Color", "Desaturates a color by a specified amount in percent", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))] +public class DesaturateSKColorNode : Node +{ + public DesaturateSKColorNode() : base("Desaturate Color", "Desaturates a color by a specified amount in percent") + { + Input = CreateInputPin("Color"); + Percentage = CreateInputPin("%"); + Output = CreateOutputPin(); + } + + public InputPin Input { get; } + public InputPin Percentage { get; } + public OutputPin Output { get; set; } + + public override void Evaluate() + { + Input.Value.ToHsl(out float h, out float s, out float l); + s -= s * (Percentage.Value / 100f); + Output.Value = SKColor.FromHsl(h, Math.Clamp(s, 0f, 100f), l); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Color/HslSKColorNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/HslSKColorNode.cs new file mode 100644 index 000000000..558041355 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/HslSKColorNode.cs @@ -0,0 +1,31 @@ +using Artemis.Core; +using SkiaSharp; + +namespace Artemis.VisualScripting.Nodes.Color; + +[Node("HSL Color", "Creates a color from hue, saturation and lightness values", "Color", InputType = typeof(Numeric), OutputType = typeof(SKColor))] +public class HslSKColorNode : Node +{ + public HslSKColorNode() : base("HSL Color", "Creates a color from hue, saturation and lightness values") + { + H = CreateInputPin("H"); + S = CreateInputPin("S"); + L = CreateInputPin("L"); + Output = CreateOutputPin(); + } + + public InputPin H { get; set; } + public InputPin S { get; set; } + public InputPin L { get; set; } + public OutputPin Output { get; } + + #region Overrides of Node + + /// + public override void Evaluate() + { + Output.Value = SKColor.FromHsl(H.Value, S.Value, L.Value); + } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Color/InvertSKColorNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/InvertSKColorNode.cs new file mode 100644 index 000000000..5abf00994 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/InvertSKColorNode.cs @@ -0,0 +1,27 @@ +using Artemis.Core; +using SkiaSharp; + +namespace Artemis.VisualScripting.Nodes.Color; + +[Node("Invert Color", "Inverts a color by a specified amount in percent", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))] +public class InvertSKColorNode : Node +{ + public InvertSKColorNode() : base("Invert Color", "Inverts a color") + { + Input = CreateInputPin(); + Output = CreateOutputPin(); + } + + public InputPin Input { get; } + public OutputPin Output { get; set; } + + public override void Evaluate() + { + Output.Value = new SKColor( + (byte) (255 - Input.Value.Red), + (byte) (255 - Input.Value.Green), + (byte) (255 - Input.Value.Blue), + Input.Value.Alpha + ); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Color/RotateHueSkColorNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/RotateHueSkColorNode.cs new file mode 100644 index 000000000..22d16ef53 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/RotateHueSkColorNode.cs @@ -0,0 +1,26 @@ +using Artemis.Core; +using SkiaSharp; + +namespace Artemis.VisualScripting.Nodes.Color; + +[Node("Rotate Color Hue", "Rotates the hue of a color by a specified amount in degrees", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))] +public class RotateHueSKColorNode : Node +{ + public RotateHueSKColorNode() : base("Rotate Color Hue", "Rotates the hue of a color by a specified amount in degrees") + { + Input = CreateInputPin("Color"); + Amount = CreateInputPin("Amount"); + Output = CreateOutputPin(); + } + + public InputPin Input { get; } + public InputPin Amount { get; } + public OutputPin Output { get; set; } + + public override void Evaluate() + { + Input.Value.ToHsl(out float h, out float s, out float l); + h += Amount.Value; + Output.Value = SKColor.FromHsl(h % 360, s, l); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Color/SaturateSkColorNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/SaturateSkColorNode.cs new file mode 100644 index 000000000..252db28dc --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/SaturateSkColorNode.cs @@ -0,0 +1,26 @@ +using Artemis.Core; +using SkiaSharp; + +namespace Artemis.VisualScripting.Nodes.Color; + +[Node("Saturate Color", "Saturates a color by a specified amount in percent", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))] +public class SaturateSKColorNode : Node +{ + public SaturateSKColorNode() : base("Saturate Color", "Saturates a color by a specified amount in percent") + { + Input = CreateInputPin("Color"); + Percentage = CreateInputPin("%"); + Output = CreateOutputPin(); + } + + public InputPin Input { get; } + public InputPin Percentage { get; } + public OutputPin Output { get; set; } + + public override void Evaluate() + { + Input.Value.ToHsl(out float h, out float s, out float l); + s += s * (Percentage.Value / 100f); + Output.Value = SKColor.FromHsl(h, Math.Clamp(s, 0f, 100f), l); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Color/StaticSKColorValueNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/StaticSKColorValueNode.cs new file mode 100644 index 000000000..b6f0512f5 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/StaticSKColorValueNode.cs @@ -0,0 +1,34 @@ +using Artemis.Core; +using Artemis.VisualScripting.Nodes.Color.CustomViewModels; +using SkiaSharp; + +namespace Artemis.VisualScripting.Nodes.Color; + +[Node("Color-Value", "Outputs a configurable color value.", "Static", InputType = typeof(SKColor), OutputType = typeof(SKColor))] +public class StaticSKColorValueNode : Node +{ + #region Constructors + + public StaticSKColorValueNode() + : base("Color", "Outputs a configurable color value.") + { + Output = CreateOutputPin(); + } + + #endregion + + #region Properties & Fields + + public OutputPin Output { get; } + + #endregion + + #region Methods + + public override void Evaluate() + { + Output.Value = Storage; + } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Color/SumSKColorsNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/SumSKColorsNode.cs new file mode 100644 index 000000000..a67a5238f --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/SumSKColorsNode.cs @@ -0,0 +1,45 @@ +using Artemis.Core; +using SkiaSharp; + +namespace Artemis.VisualScripting.Nodes.Color; + +[Node("Sum (Color)", "Sums the connected color values.", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))] +public class SumSKColorsNode : Node +{ + #region Constructors + + public SumSKColorsNode() + : base("Sum", "Sums the connected color values.") + { + Values = CreateInputPinCollection("Values", 2); + Sum = CreateOutputPin("Sum"); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + SKColor result = SKColor.Empty; + + bool first = true; + foreach (SKColor current in Values.Values) + { + result = first ? current : result.Sum(current); + first = false; + } + + Sum.Value = result; + } + + #endregion + + #region Properties & Fields + + public InputPinCollection Values { get; } + + public OutputPin Sum { get; } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/ConvertNodes.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/ConvertNodes.cs new file mode 100644 index 000000000..a3358fd31 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/ConvertNodes.cs @@ -0,0 +1,80 @@ +using Artemis.Core; + +namespace Artemis.VisualScripting.Nodes; + +[Node("To String", "Converts the input to a string.", "Conversion", InputType = typeof(object), OutputType = typeof(string))] +public class ConvertToStringNode : Node +{ + #region Constructors + + public ConvertToStringNode() + : base("To String", "Converts the input to a string.") + { + Input = CreateInputPin(); + String = CreateOutputPin(); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + String.Value = Input.Value?.ToString(); + } + + #endregion + + #region Properties & Fields + + public InputPin Input { get; } + + public OutputPin String { get; } + + #endregion +} + +[Node("To Numeric", "Converts the input to a numeric.", "Conversion", InputType = typeof(object), OutputType = typeof(Numeric))] +public class ConvertToNumericNode : Node +{ + #region Constructors + + public ConvertToNumericNode() + : base("To Numeric", "Converts the input to a numeric.") + { + Input = CreateInputPin(); + Output = CreateOutputPin(); + } + + #endregion + + #region Properties & Fields + + public InputPin Input { get; } + + public OutputPin Output { get; } + + #endregion + + #region Methods + + public override void Evaluate() + { + Output.Value = Input.Value switch + { + int input => new Numeric(input), + double input => new Numeric(input), + float input => new Numeric(input), + byte input => new Numeric(input), + _ => TryParse(Input.Value) + }; + } + + private Numeric TryParse(object input) + { + Numeric.TryParse(input?.ToString(), out Numeric value); + return value; + } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/EnumEqualsNodeCustomViewModel.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/EnumEqualsNodeCustomViewModel.cs new file mode 100644 index 000000000..54d8bbc7e --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/EnumEqualsNodeCustomViewModel.cs @@ -0,0 +1,49 @@ +using System.Collections.ObjectModel; +using Artemis.Core; +using Artemis.Core.Events; +using Artemis.UI.Shared.VisualScripting; +using DynamicData; + +namespace Artemis.VisualScripting.Nodes.CustomViewModels; + +public class EnumEqualsNodeCustomViewModel : CustomNodeViewModel +{ + private readonly EnumEqualsNode _node; + + public EnumEqualsNodeCustomViewModel(EnumEqualsNode node) : base(node) + { + _node = node; + } + + public ObservableCollection<(Enum, string)> EnumValues { get; } = new(); + + // public override void OnActivate() + // { + // _node.InputPin.PinConnected += InputPinOnPinConnected; + // _node.InputPin.PinDisconnected += InputPinOnPinDisconnected; + // + // if (_node.InputPin.Value != null && _node.InputPin.Value.GetType().IsEnum) + // EnumValues.AddRange(EnumUtilities.GetAllValuesAndDescriptions(_node.InputPin.Value.GetType())); + // base.OnActivate(); + // } + // + // public override void OnDeactivate() + // { + // _node.InputPin.PinConnected -= InputPinOnPinConnected; + // _node.InputPin.PinDisconnected -= InputPinOnPinDisconnected; + // + // base.OnDeactivate(); + // } + + private void InputPinOnPinDisconnected(object sender, SingleValueEventArgs e) + { + EnumValues.Clear(); + } + + private void InputPinOnPinConnected(object sender, SingleValueEventArgs e) + { + EnumValues.Clear(); + if (_node.InputPin.Value != null && _node.InputPin.Value.GetType().IsEnum) + EnumValues.AddRange(EnumUtilities.GetAllValuesAndDescriptions(_node.InputPin.Value.GetType())); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs new file mode 100644 index 000000000..f11a6ab08 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs @@ -0,0 +1,73 @@ +using System.Collections.ObjectModel; +using Artemis.Core; +using Artemis.UI.Shared.VisualScripting; + +namespace Artemis.VisualScripting.Nodes.CustomViewModels; + +public class LayerPropertyNodeCustomViewModel : CustomNodeViewModel +{ + private readonly LayerPropertyNode _node; + private ILayerProperty _selectedLayerProperty; + + private RenderProfileElement _selectedProfileElement; + + public LayerPropertyNodeCustomViewModel(LayerPropertyNode node) : base(node) + { + _node = node; + } + + public ObservableCollection ProfileElements { get; } = new(); + + // public RenderProfileElement SelectedProfileElement + // { + // get => _selectedProfileElement; + // set + // { + // if (!SetAndNotify(ref _selectedProfileElement, value)) return; + // _node.ChangeProfileElement(_selectedProfileElement); + // GetLayerProperties(); + // } + // } + + public ObservableCollection LayerProperties { get; } = new(); + + // public ILayerProperty SelectedLayerProperty + // { + // get => _selectedLayerProperty; + // set + // { + // if (!SetAndNotify(ref _selectedLayerProperty, value)) return; + // _node.ChangeLayerProperty(_selectedLayerProperty); + // } + // } + + // private void GetProfileElements() + // { + // ProfileElements.Clear(); + // if (_node.Script.Context is not Profile profile) + // return; + // + // List elements = new(profile.GetAllRenderElements()); + // + // ProfileElements.AddRange(elements.OrderBy(e => e.Order)); + // _selectedProfileElement = _node.ProfileElement; + // NotifyOfPropertyChange(nameof(SelectedProfileElement)); + // } + // + // private void GetLayerProperties() + // { + // LayerProperties.Clear(); + // if (_node.ProfileElement == null) + // return; + // + // LayerProperties.AddRange(_node.ProfileElement.GetAllLayerProperties().Where(l => !l.IsHidden && l.DataBindingsSupported)); + // _selectedLayerProperty = _node.LayerProperty; + // NotifyOfPropertyChange(nameof(SelectedLayerProperty)); + // } + // + // public override void OnActivate() + // { + // GetProfileElements(); + // GetLayerProperties(); + // } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs new file mode 100644 index 000000000..271e3d4dc --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs @@ -0,0 +1,18 @@ +using Artemis.Core; +using Artemis.UI.Shared.VisualScripting; + +namespace Artemis.VisualScripting.Nodes.CustomViewModels; + +public class StaticNumericValueNodeCustomViewModel : CustomNodeViewModel +{ + public StaticNumericValueNodeCustomViewModel(INode node) : base(node) + { + } +} + +public class StaticStringValueNodeCustomViewModel : CustomNodeViewModel +{ + public StaticStringValueNodeCustomViewModel(INode node) : base(node) + { + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/EnumEqualsNodeCustomView.xaml b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/EnumEqualsNodeCustomView.xaml new file mode 100644 index 000000000..da9a0378f --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/EnumEqualsNodeCustomView.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/LayerPropertyNodeCustomView.xaml b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/LayerPropertyNodeCustomView.xaml new file mode 100644 index 000000000..2b526f152 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/LayerPropertyNodeCustomView.xaml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticNumericValueNodeCustomView.xaml b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticNumericValueNodeCustomView.xaml new file mode 100644 index 000000000..6c43dfcbf --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticNumericValueNodeCustomView.xaml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticStringValueNodeCustomView.xaml b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticStringValueNodeCustomView.xaml new file mode 100644 index 000000000..54335410e --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticStringValueNodeCustomView.xaml @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelEventNodeCustomViewModel.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelEventNodeCustomViewModel.cs new file mode 100644 index 000000000..daeb894dc --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelEventNodeCustomViewModel.cs @@ -0,0 +1,72 @@ +using System.Collections.ObjectModel; +using Artemis.Core; +using Artemis.Core.Modules; +using Artemis.Core.Services; +using Artemis.UI.Shared.VisualScripting; + +namespace Artemis.VisualScripting.Nodes.DataModel.CustomViewModels; + +public class DataModelEventNodeCustomViewModel : CustomNodeViewModel +{ + private readonly DataModelEventNode _node; + private ObservableCollection _modules; + + public DataModelEventNodeCustomViewModel(DataModelEventNode node, ISettingsService settingsService) : base(node) + { + _node = node; + + ShowFullPaths = settingsService.GetSetting("ProfileEditor.ShowFullPaths", true); + ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); + } + + public PluginSetting ShowFullPaths { get; } + public PluginSetting ShowDataModelValues { get; } + public ObservableCollection FilterTypes { get; } = new() {typeof(IDataModelEvent)}; + + // public ObservableCollection Modules + // { + // get => _modules; + // set => SetAndNotify(ref _modules, value); + // } + + public DataModelPath DataModelPath + { + get => _node.DataModelPath; + set + { + if (ReferenceEquals(_node.DataModelPath, value)) + return; + + _node.DataModelPath?.Dispose(); + _node.DataModelPath = value; + _node.DataModelPath.Save(); + + _node.Storage = _node.DataModelPath.Entity; + } + } + + // public override void OnActivate() + // { + // if (Modules != null) + // return; + // + // Modules = new ObservableCollection(); + // if (_node.Script.Context is Profile scriptProfile && scriptProfile.Configuration.Module != null) + // Modules.Add(scriptProfile.Configuration.Module); + // else if (_node.Script.Context is ProfileConfiguration profileConfiguration && profileConfiguration.Module != null) + // Modules.Add(profileConfiguration.Module); + // + // _node.PropertyChanged += NodeOnPropertyChanged; + // } + // + // public override void OnDeactivate() + // { + // _node.PropertyChanged -= NodeOnPropertyChanged; + // } + // + // private void NodeOnPropertyChanged(object sender, PropertyChangedEventArgs e) + // { + // if (e.PropertyName == nameof(DataModelNode.DataModelPath)) + // OnPropertyChanged(nameof(DataModelPath)); + // } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelNodeCustomViewModel.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelNodeCustomViewModel.cs new file mode 100644 index 000000000..f62bc7533 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelNodeCustomViewModel.cs @@ -0,0 +1,72 @@ +using System.Collections.ObjectModel; +using Artemis.Core; +using Artemis.Core.Modules; +using Artemis.Core.Services; +using Artemis.UI.Shared.VisualScripting; + +namespace Artemis.VisualScripting.Nodes.DataModel.CustomViewModels; + +public class DataModelNodeCustomViewModel : CustomNodeViewModel +{ + private readonly DataModelNode _node; + private ObservableCollection _modules; + + public DataModelNodeCustomViewModel(DataModelNode node, ISettingsService settingsService) : base(node) + { + _node = node; + + ShowFullPaths = settingsService.GetSetting("ProfileEditor.ShowFullPaths", true); + ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); + } + + public PluginSetting ShowFullPaths { get; } + public PluginSetting ShowDataModelValues { get; } + + // public ObservableCollection Modules + // { + // get => _modules; + // set => SetAndNotify(ref _modules, value); + // } + + public DataModelPath DataModelPath + { + get => _node.DataModelPath; + set + { + if (ReferenceEquals(_node.DataModelPath, value)) + return; + + _node.DataModelPath?.Dispose(); + _node.DataModelPath = value; + _node.DataModelPath.Save(); + + _node.Storage = _node.DataModelPath.Entity; + _node.UpdateOutputPin(false); + } + } + + // public override void OnActivate() + // { + // if (Modules != null) + // return; + // + // Modules = new ObservableCollection(); + // if (_node.Script.Context is Profile scriptProfile && scriptProfile.Configuration.Module != null) + // Modules.Add(scriptProfile.Configuration.Module); + // else if (_node.Script.Context is ProfileConfiguration profileConfiguration && profileConfiguration.Module != null) + // Modules.Add(profileConfiguration.Module); + // + // _node.PropertyChanged += NodeOnPropertyChanged; + // } + // + // public override void OnDeactivate() + // { + // _node.PropertyChanged -= NodeOnPropertyChanged; + // } + // + // private void NodeOnPropertyChanged(object sender, PropertyChangedEventArgs e) + // { + // if (e.PropertyName == nameof(DataModelNode.DataModelPath)) + // OnPropertyChanged(nameof(DataModelPath)); + // } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelEventNodeCustomView.xaml b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelEventNodeCustomView.xaml new file mode 100644 index 000000000..924179fe4 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelEventNodeCustomView.xaml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelNodeCustomView.xaml b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelNodeCustomView.xaml new file mode 100644 index 000000000..ada3716fa --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelNodeCustomView.xaml @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/DataModelEventNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/DataModelEventNode.cs new file mode 100644 index 000000000..c720da2ae --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/DataModelEventNode.cs @@ -0,0 +1,187 @@ +using Artemis.Core; +using Artemis.Core.Events; +using Artemis.Storage.Entities.Profile; +using Artemis.VisualScripting.Nodes.DataModel.CustomViewModels; + +namespace Artemis.VisualScripting.Nodes.DataModel; + +[Node("Data Model-Event", "Responds to a data model event trigger", "Data Model", OutputType = typeof(bool))] +public class DataModelEventNode : Node, IDisposable +{ + private int _currentIndex; + private Type _currentType; + private DataModelPath _dataModelPath; + private DateTime _lastTrigger; + private bool _updating; + + public DataModelEventNode() : base("Data Model-Event", "Responds to a data model event trigger") + { + _currentType = typeof(object); + CreateCycleValues(typeof(object), 1); + Output = CreateOutputPin(typeof(object)); + } + + public InputPinCollection CycleValues { get; set; } + public OutputPin Output { get; set; } + public INodeScript Script { get; set; } + + public DataModelPath DataModelPath + { + get => _dataModelPath; + set => SetAndNotify(ref _dataModelPath, value); + } + + public override void Initialize(INodeScript script) + { + Script = script; + + if (Storage == null) + return; + + DataModelPath = new DataModelPath(Storage); + } + + public override void Evaluate() + { + object outputValue = null; + if (DataModelPath?.GetValue() is IDataModelEvent dataModelEvent) + { + if (dataModelEvent.LastTrigger > _lastTrigger) + { + _lastTrigger = dataModelEvent.LastTrigger; + _currentIndex++; + + if (_currentIndex >= CycleValues.Count()) + _currentIndex = 0; + } + + outputValue = CycleValues.ElementAt(_currentIndex).PinValue; + } + + if (Output.Type.IsInstanceOfType(outputValue)) + Output.Value = outputValue; + else if (Output.Type.IsValueType) + Output.Value = Output.Type.GetDefault()!; + } + + private void CreateCycleValues(Type type, int initialCount) + { + if (CycleValues != null) + { + CycleValues.PinAdded -= CycleValuesOnPinAdded; + CycleValues.PinRemoved -= CycleValuesOnPinRemoved; + foreach (IPin pin in CycleValues) + { + pin.PinConnected -= OnPinConnected; + pin.PinDisconnected -= OnPinDisconnected; + } + + RemovePinCollection(CycleValues); + } + + CycleValues = CreateInputPinCollection(type, "", initialCount); + CycleValues.PinAdded += CycleValuesOnPinAdded; + CycleValues.PinRemoved += CycleValuesOnPinRemoved; + foreach (IPin pin in CycleValues) + { + pin.PinConnected += OnPinConnected; + pin.PinDisconnected += OnPinDisconnected; + } + } + + private void CycleValuesOnPinAdded(object sender, SingleValueEventArgs e) + { + e.Value.PinConnected += OnPinConnected; + e.Value.PinDisconnected += OnPinDisconnected; + } + + private void CycleValuesOnPinRemoved(object sender, SingleValueEventArgs e) + { + e.Value.PinConnected -= OnPinConnected; + e.Value.PinDisconnected -= OnPinDisconnected; + } + + private void OnPinDisconnected(object sender, SingleValueEventArgs e) + { + ProcessPinDisconnected(); + } + + private void OnPinConnected(object sender, SingleValueEventArgs e) + { + IPin source = e.Value; + IPin target = (IPin) sender; + ProcessPinConnected(source, target); + } + + private void ProcessPinConnected(IPin source, IPin target) + { + if (_updating) + return; + + try + { + _updating = true; + + // No need to change anything if the types haven't changed + if (_currentType == source.Type) + return; + + int reconnectIndex = CycleValues.ToList().IndexOf(target); + ChangeCurrentType(source.Type); + + if (reconnectIndex != -1) + { + CycleValues.ToList()[reconnectIndex].ConnectTo(source); + source.ConnectTo(CycleValues.ToList()[reconnectIndex]); + } + } + finally + { + _updating = false; + } + } + + private void ChangeCurrentType(Type type) + { + CreateCycleValues(type, CycleValues.Count()); + + List oldOutputConnections = Output.ConnectedTo.ToList(); + RemovePin(Output); + Output = CreateOutputPin(type); + foreach (IPin oldOutputConnection in oldOutputConnections.Where(o => o.Type.IsAssignableFrom(Output.Type))) + { + oldOutputConnection.DisconnectAll(); + oldOutputConnection.ConnectTo(Output); + Output.ConnectTo(oldOutputConnection); + } + + _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; + } + } + + private void UpdatePinsType(IPin source) + { + } + + /// + public void Dispose() + { + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/DataModelNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/DataModelNode.cs new file mode 100644 index 000000000..0c0239709 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/DataModelNode.cs @@ -0,0 +1,95 @@ +using Artemis.Core; +using Artemis.Storage.Entities.Profile; +using Artemis.VisualScripting.Nodes.DataModel.CustomViewModels; +using Avalonia.Threading; + +namespace Artemis.VisualScripting.Nodes.DataModel; + +[Node("Data Model-Value", "Outputs a selectable data model value.", "Data Model")] +public class DataModelNode : Node, IDisposable +{ + private DataModelPath _dataModelPath; + + public DataModelNode() : base("Data Model", "Outputs a selectable data model value") + { + } + + public INodeScript Script { get; private set; } + public OutputPin Output { get; private set; } + + public DataModelPath DataModelPath + { + get => _dataModelPath; + set => SetAndNotify(ref _dataModelPath, value); + } + + public override void Initialize(INodeScript script) + { + Script = script; + + if (Storage == null) + return; + + DataModelPath = new DataModelPath(Storage); + DataModelPath.PathValidated += DataModelPathOnPathValidated; + + UpdateOutputPin(false); + } + + public override void Evaluate() + { + if (DataModelPath.IsValid) + { + if (Output == null) + UpdateOutputPin(false); + + object pathValue = DataModelPath.GetValue(); + + if (pathValue == null) + { + if (!Output.Type.IsValueType) + Output.Value = null; + } + else + { + if (Output.Type == typeof(Numeric)) + Output.Value = new Numeric(pathValue); + else + Output.Value = pathValue; + } + } + } + + public void UpdateOutputPin(bool loadConnections) + { + Type type = DataModelPath?.GetPropertyType(); + if (Numeric.IsTypeCompatible(type)) + type = typeof(Numeric); + + if (Output != null && Output.Type == type) + return; + + if (Output != null) + { + RemovePin(Output); + Output = null; + } + + if (type != null) + Output = CreateOutputPin(type); + + if (loadConnections && Script is NodeScript nodeScript) + nodeScript.LoadConnections(); + } + + private void DataModelPathOnPathValidated(object sender, EventArgs e) + { + Dispatcher.UIThread.InvokeAsync(() => UpdateOutputPin(true)); + } + + /// + public void Dispose() + { + DataModelPath?.Dispose(); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViewModels/EasingTypeNodeCustomViewModel.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViewModels/EasingTypeNodeCustomViewModel.cs new file mode 100644 index 000000000..ae4a1de1f --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViewModels/EasingTypeNodeCustomViewModel.cs @@ -0,0 +1,54 @@ +using System.Collections.ObjectModel; +using Artemis.Core; +using Artemis.UI.Shared.VisualScripting; + +namespace Artemis.VisualScripting.Nodes.Easing.CustomViewModels; + +public class EasingTypeNodeCustomViewModel : CustomNodeViewModel +{ + private readonly EasingTypeNode _node; + private NodeEasingViewModel _selectedEasingViewModel; + + public EasingTypeNodeCustomViewModel(EasingTypeNode node) : base(node) + { + _node = node; + EasingViewModels = new ObservableCollection(Enum.GetValues(typeof(Easings.Functions)).Cast().Select(e => new NodeEasingViewModel(e))); + } + + public ObservableCollection EasingViewModels { get; } + + public NodeEasingViewModel SelectedEasingViewModel + { + get => _selectedEasingViewModel; + set + { + _selectedEasingViewModel = value; + _node.Storage = _selectedEasingViewModel.EasingFunction; + } + } + + // public override void OnActivate() + // { + // _node.PropertyChanged += NodeOnPropertyChanged; + // SelectedEasingViewModel = GetNodeEasingViewModel(); + // } + // + // public override void OnDeactivate() + // { + // _node.PropertyChanged -= NodeOnPropertyChanged; + // } + // + // private void NodeOnPropertyChanged(object sender, PropertyChangedEventArgs e) + // { + // if (e.PropertyName == nameof(_node.Storage)) + // { + // _selectedEasingViewModel = GetNodeEasingViewModel(); + // NotifyOfPropertyChange(nameof(SelectedEasingViewModel)); + // } + // } + + private NodeEasingViewModel GetNodeEasingViewModel() + { + return EasingViewModels.FirstOrDefault(vm => vm.EasingFunction == _node.Storage); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViewModels/NodeEasingViewModel.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViewModels/NodeEasingViewModel.cs new file mode 100644 index 000000000..9fb73fd42 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViewModels/NodeEasingViewModel.cs @@ -0,0 +1,27 @@ +using Artemis.Core; +using Artemis.UI.Shared; +using Avalonia; +using Humanizer; + +namespace Artemis.VisualScripting.Nodes.Easing.CustomViewModels; + +public class NodeEasingViewModel : ViewModelBase +{ + public NodeEasingViewModel(Easings.Functions easingFunction) + { + EasingFunction = easingFunction; + Description = easingFunction.Humanize(); + + EasingPoints = new List(); + for (int i = 1; i <= 10; i++) + { + int x = i; + double y = Easings.Interpolate(i / 10.0, EasingFunction) * 10; + EasingPoints.Add(new Point(x, y)); + } + } + + public Easings.Functions EasingFunction { get; } + public List EasingPoints { get; } + public string Description { get; } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViews/EasingTypeNodeCustomView.xaml b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViews/EasingTypeNodeCustomView.xaml new file mode 100644 index 000000000..1930207eb --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViews/EasingTypeNodeCustomView.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/EasingTypeNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/EasingTypeNode.cs new file mode 100644 index 000000000..a4e2717a6 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/EasingTypeNode.cs @@ -0,0 +1,20 @@ +using Artemis.Core; +using Artemis.VisualScripting.Nodes.Easing.CustomViewModels; + +namespace Artemis.VisualScripting.Nodes.Easing; + +[Node("Easing Type", "Outputs a selectable easing type.", "Easing", OutputType = typeof(Easings.Functions))] +public class EasingTypeNode : Node +{ + public EasingTypeNode() : base("Easing Type", "Outputs a selectable easing type.") + { + Output = CreateOutputPin(); + } + + public OutputPin Output { get; } + + public override void Evaluate() + { + Output.Value = Storage; + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/NumericEasingNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/NumericEasingNode.cs new file mode 100644 index 000000000..9fe327f8a --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/NumericEasingNode.cs @@ -0,0 +1,66 @@ +using Artemis.Core; + +namespace Artemis.VisualScripting.Nodes.Easing; + +[Node("Numeric Easing", "Outputs an eased numeric value", "Easing", InputType = typeof(Numeric), OutputType = typeof(Numeric))] +public class NumericEasingNode : Node +{ + private float _currentValue; + private DateTime _lastEvaluate = DateTime.MinValue; + private float _progress; + private float _sourceValue; + private float _targetValue; + + public NumericEasingNode() : base("Numeric Easing", "Outputs an eased numeric value") + { + Input = CreateInputPin(); + EasingTime = CreateInputPin("delay"); + EasingFunction = CreateInputPin("function"); + + Output = CreateOutputPin(); + } + + public InputPin Input { get; set; } + public InputPin EasingTime { get; set; } + public InputPin EasingFunction { get; set; } + + public OutputPin Output { get; set; } + + public override void Evaluate() + { + DateTime now = DateTime.Now; + + // If the value changed reset progress + if (Math.Abs(_targetValue - Input.Value) > 0.001f) + { + _sourceValue = _currentValue; + _targetValue = Input.Value; + _progress = 0f; + } + + // Update until finished + if (_progress < 1f) + { + Update(); + Output.Value = new Numeric(_currentValue); + } + // Stop updating past 1 and use the target value + else + { + Output.Value = new Numeric(_targetValue); + } + + _lastEvaluate = now; + } + + private void Update() + { + TimeSpan delta = DateTime.Now - _lastEvaluate; + + // In case of odd delta's, keep progress between 0f and 1f + _progress = Math.Clamp(_progress + (float) delta.TotalMilliseconds / EasingTime.Value, 0f, 1f); + + double eased = _sourceValue + (_targetValue - _sourceValue) * Easings.Interpolate(_progress, EasingFunction.Value); + _currentValue = (float) eased; + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/SKColorEasingNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/SKColorEasingNode.cs new file mode 100644 index 000000000..9c2d50062 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/SKColorEasingNode.cs @@ -0,0 +1,65 @@ +using Artemis.Core; +using SkiaSharp; + +namespace Artemis.VisualScripting.Nodes.Easing; + +[Node("Color Easing", "Outputs an eased color value", "Easing", InputType = typeof(SKColor), OutputType = typeof(SKColor))] +public class SKColorEasingNode : Node +{ + private SKColor _currentValue; + private DateTime _lastEvaluate = DateTime.MinValue; + private float _progress; + private SKColor _sourceValue; + private SKColor _targetValue; + + public SKColorEasingNode() : base("Color Easing", "Outputs an eased color value") + { + Input = CreateInputPin(); + EasingTime = CreateInputPin("delay"); + EasingFunction = CreateInputPin("function"); + + Output = CreateOutputPin(); + } + + public InputPin Input { get; set; } + public InputPin EasingTime { get; set; } + public InputPin EasingFunction { get; set; } + + public OutputPin Output { get; set; } + + public override void Evaluate() + { + DateTime now = DateTime.Now; + + // If the value changed reset progress + if (_targetValue != Input.Value) + { + _sourceValue = _currentValue; + _targetValue = Input.Value; + _progress = 0f; + } + + // Update until finished + if (_progress < 1f) + { + Update(); + Output.Value = _currentValue; + } + // Stop updating past 1 and use the target value + else + { + Output.Value = _targetValue; + } + + _lastEvaluate = now; + } + + private void Update() + { + TimeSpan delta = DateTime.Now - _lastEvaluate; + + // In case of odd delta's, keep progress between 0f and 1f + _progress = Math.Clamp(_progress + (float) delta.TotalMilliseconds / EasingTime.Value, 0f, 1f); + _currentValue = _sourceValue.Interpolate(_targetValue, (float) Easings.Interpolate(_progress, EasingFunction.Value)); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs new file mode 100644 index 000000000..c1511c841 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs @@ -0,0 +1,124 @@ +using Artemis.Core; +using Artemis.VisualScripting.Nodes.CustomViewModels; + +namespace Artemis.VisualScripting.Nodes; + +[Node("Layer/Folder Property", "Outputs the property of a selected layer or folder", "External")] +public class LayerPropertyNode : Node +{ + private readonly object _layerPropertyLock = new(); + + public INodeScript Script { get; private set; } + public RenderProfileElement ProfileElement { get; private set; } + public ILayerProperty LayerProperty { get; private set; } + + public override void Evaluate() + { + lock (_layerPropertyLock) + { + // In this case remove the pins so no further evaluations occur + if (LayerProperty == null) + { + CreatePins(); + return; + } + + List list = LayerProperty.BaseDataBinding.Properties.ToList(); + int index = 0; + foreach (IPin pin in Pins) + { + OutputPin outputPin = (OutputPin) pin; + IDataBindingProperty dataBindingProperty = list[index]; + index++; + + // TODO: Is this really non-nullable? + outputPin.Value = dataBindingProperty.GetValue(); + } + } + } + + public override void Initialize(INodeScript script) + { + Script = script; + + if (script.Context is Profile profile) + profile.ChildRemoved += ProfileOnChildRemoved; + + LoadLayerProperty(); + } + + public void LoadLayerProperty() + { + lock (_layerPropertyLock) + { + if (Script.Context is not Profile profile || Storage == null) + return; + + RenderProfileElement element = profile.GetAllRenderElements().FirstOrDefault(l => l.EntityId == Storage.ElementId); + + ProfileElement = element; + LayerProperty = element?.GetAllLayerProperties().FirstOrDefault(p => p.Path == Storage.PropertyPath); + CreatePins(); + } + } + + public void ChangeProfileElement(RenderProfileElement profileElement) + { + lock (_layerPropertyLock) + { + ProfileElement = profileElement; + LayerProperty = null; + + Storage = new LayerPropertyNodeEntity + { + ElementId = ProfileElement?.EntityId ?? Guid.Empty, + PropertyPath = null + }; + + CreatePins(); + } + } + + public void ChangeLayerProperty(ILayerProperty layerProperty) + { + lock (_layerPropertyLock) + { + LayerProperty = layerProperty; + + Storage = new LayerPropertyNodeEntity + { + ElementId = ProfileElement?.EntityId ?? Guid.Empty, + PropertyPath = LayerProperty?.Path + }; + + CreatePins(); + } + } + + private void CreatePins() + { + while (Pins.Any()) + RemovePin((Pin) Pins.First()); + + if (LayerProperty == null) + return; + + foreach (IDataBindingProperty dataBindingRegistration in LayerProperty.BaseDataBinding.Properties) + CreateOutputPin(dataBindingRegistration.ValueType, dataBindingRegistration.DisplayName); + } + + private void ProfileOnChildRemoved(object sender, EventArgs e) + { + if (Script.Context is not Profile profile) + return; + + if (!profile.GetAllRenderElements().Contains(ProfileElement)) + ChangeProfileElement(null); + } +} + +public class LayerPropertyNodeEntity +{ + public Guid ElementId { get; set; } + public string PropertyPath { get; set; } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/CustomViewModels/MathExpressionNodeCustomViewModel.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/CustomViewModels/MathExpressionNodeCustomViewModel.cs new file mode 100644 index 000000000..4bbe43396 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/CustomViewModels/MathExpressionNodeCustomViewModel.cs @@ -0,0 +1,11 @@ +using Artemis.Core; +using Artemis.UI.Shared.VisualScripting; + +namespace Artemis.VisualScripting.Nodes.Maths.CustomViewModels; + +public class MathExpressionNodeCustomViewModel : CustomNodeViewModel +{ + public MathExpressionNodeCustomViewModel(INode node) : base(node) + { + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/CustomViews/MathExpressionNodeCustomView.xaml b/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/CustomViews/MathExpressionNodeCustomView.xaml new file mode 100644 index 000000000..e39eb33ca --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/CustomViews/MathExpressionNodeCustomView.xaml @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/ExpressionNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/ExpressionNode.cs new file mode 100644 index 000000000..4c41be75c --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/ExpressionNode.cs @@ -0,0 +1,116 @@ +using Artemis.Core; +using Artemis.VisualScripting.Nodes.Maths.CustomViewModels; +using NoStringEvaluating.Contract; +using NoStringEvaluating.Contract.Variables; +using NoStringEvaluating.Models.Values; + +namespace Artemis.VisualScripting.Nodes.Maths; + +[Node("Math Expression", "Outputs the result of a math expression.", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))] +public class MathExpressionNode : Node +{ + private readonly INoStringEvaluator _evaluator; + private readonly PinsVariablesContainer _variables; + + #region Constructors + + public MathExpressionNode(INoStringEvaluator evaluator) + : base("Math Expression", "Outputs the result of a math expression.") + { + _evaluator = evaluator; + Output = CreateOutputPin(); + Values = CreateInputPinCollection("Values", 2); + Values.PinAdded += (_, _) => SetPinNames(); + Values.PinRemoved += (_, _) => SetPinNames(); + _variables = new PinsVariablesContainer(Values); + + SetPinNames(); + } + + #endregion + + #region Properties & Fields + + public OutputPin Output { get; } + public InputPinCollection Values { get; } + + #endregion + + #region Methods + + public override void Evaluate() + { + if (Storage != null) + Output.Value = new Numeric(_evaluator.CalcNumber(Storage, _variables)); + } + + private void SetPinNames() + { + int index = 1; + foreach (IPin value in Values) + { + value.Name = ExcelColumnFromNumber(index).ToLower(); + index++; + } + } + + public static string ExcelColumnFromNumber(int column) + { + string columnString = ""; + decimal columnNumber = column; + while (columnNumber > 0) + { + decimal currentLetterNumber = (columnNumber - 1) % 26; + char currentLetter = (char) (currentLetterNumber + 65); + columnString = currentLetter + columnString; + columnNumber = (columnNumber - (currentLetterNumber + 1)) / 26; + } + + return columnString; + } + + #endregion +} + +public class PinsVariablesContainer : IVariablesContainer +{ + private readonly InputPinCollection _values; + + public PinsVariablesContainer(InputPinCollection values) + { + _values = values; + } + + #region Implementation of IVariablesContainer + + /// + public IVariable AddOrUpdate(string name, double value) + { + throw new NotImplementedException(); + } + + /// + public EvaluatorValue GetValue(string name) + { + IPin pin = _values.FirstOrDefault(v => v.Name == name); + if (pin?.PinValue is Numeric numeric) + return new EvaluatorValue(numeric); + return new EvaluatorValue(0); + } + + /// + public bool TryGetValue(string name, out EvaluatorValue value) + { + IPin pin = _values.FirstOrDefault(v => v.Name == name); + if (pin?.PinValue is Numeric numeric) + { + value = new EvaluatorValue(numeric); + return true; + } + + value = new EvaluatorValue(0); + return false; + } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/RoundNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/RoundNode.cs new file mode 100644 index 000000000..ba081421f --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/RoundNode.cs @@ -0,0 +1,26 @@ +using Artemis.Core; + +namespace Artemis.VisualScripting.Nodes.Maths; + +[Node("Round", "Outputs a rounded numeric value.", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))] +public class RoundNode : Node +{ + public RoundNode() : base("Round", "Outputs a rounded numeric value.") + { + Input = CreateInputPin(); + Output = CreateOutputPin(); + } + + public OutputPin Output { get; set; } + public InputPin Input { get; set; } + + #region Overrides of Node + + /// + public override void Evaluate() + { + Output.Value = new Numeric(MathF.Round(Input.Value, MidpointRounding.AwayFromZero)); + } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/StaticValueNodes.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/StaticValueNodes.cs new file mode 100644 index 000000000..67fbef426 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/StaticValueNodes.cs @@ -0,0 +1,62 @@ +using Artemis.Core; +using Artemis.VisualScripting.Nodes.CustomViewModels; + +namespace Artemis.VisualScripting.Nodes; + +[Node("Numeric-Value", "Outputs a configurable static numeric value.", "Static", OutputType = typeof(Numeric))] +public class StaticNumericValueNode : Node +{ + #region Constructors + + public StaticNumericValueNode() + : base("Numeric", "Outputs a configurable numeric value.") + { + Output = CreateOutputPin(); + } + + #endregion + + #region Properties & Fields + + public OutputPin Output { get; } + + #endregion + + #region Methods + + public override void Evaluate() + { + Output.Value = Storage; + } + + #endregion +} + +[Node("String-Value", "Outputs a configurable static string value.", "Static", OutputType = typeof(string))] +public class StaticStringValueNode : Node +{ + #region Constructors + + public StaticStringValueNode() + : base("String", "Outputs a configurable string value.") + { + Output = CreateOutputPin(); + } + + #endregion + + #region Properties & Fields + + public OutputPin Output { get; } + + #endregion + + #region Methods + + public override void Evaluate() + { + Output.Value = Storage; + } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/StringFormatNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/StringFormatNode.cs new file mode 100644 index 000000000..347312f20 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/StringFormatNode.cs @@ -0,0 +1,37 @@ +using Artemis.Core; + +namespace Artemis.VisualScripting.Nodes; + +[Node("Format", "Formats the input string.", "Text", InputType = typeof(object), OutputType = typeof(string))] +public class StringFormatNode : Node +{ + #region Constructors + + public StringFormatNode() + : base("Format", "Formats the input string.") + { + Format = CreateInputPin("Format"); + Values = CreateInputPinCollection("Values"); + Output = CreateOutputPin("Result"); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + Output.Value = string.Format(Format.Value ?? string.Empty, Values.Values.ToArray()); + } + + #endregion + + #region Properties & Fields + + public InputPin Format { get; } + public InputPinCollection Values { get; } + + public OutputPin Output { get; } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/SumNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/SumNode.cs new file mode 100644 index 000000000..7cb49ebb5 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/Nodes/SumNode.cs @@ -0,0 +1,35 @@ +using Artemis.Core; + +namespace Artemis.VisualScripting.Nodes; + +[Node("Sum", "Sums the connected numeric values.", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))] +public class SumNumericsNode : Node +{ + #region Constructors + + public SumNumericsNode() + : base("Sum", "Sums the connected numeric values.") + { + Values = CreateInputPinCollection("Values", 2); + Sum = CreateOutputPin("Sum"); + } + + #endregion + + #region Methods + + public override void Evaluate() + { + Sum.Value = Values.Values.Sum(); + } + + #endregion + + #region Properties & Fields + + public InputPinCollection Values { get; } + + public OutputPin Sum { get; } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.VisualScripting/packages.lock.json b/src/Avalonia/Artemis.VisualScripting/packages.lock.json new file mode 100644 index 000000000..39d2fbeb4 --- /dev/null +++ b/src/Avalonia/Artemis.VisualScripting/packages.lock.json @@ -0,0 +1,1721 @@ +{ + "version": 1, + "dependencies": { + "net6.0": { + "Avalonia": { + "type": "Direct", + "requested": "[0.10.13, )", + "resolved": "0.10.13", + "contentHash": "7st8nMai1C1nqw1a2H+zXiVYTnnfFwZz7JGziEzJK4sF6+x/W77XkdcDgDHyihcK3clQZJexYr4f+PzK4BJhSQ==", + "dependencies": { + "Avalonia.Remote.Protocol": "0.10.13", + "JetBrains.Annotations": "10.3.0", + "System.ComponentModel.Annotations": "4.5.0", + "System.Memory": "4.5.3", + "System.Reactive": "5.0.0", + "System.Runtime.CompilerServices.Unsafe": "4.6.0", + "System.ValueTuple": "4.5.0" + } + }, + "Avalonia.ReactiveUI": { + "type": "Direct", + "requested": "[0.10.13, )", + "resolved": "0.10.13", + "contentHash": "s5UUJ/MG97Jv9i+kxlgNSKx8Q6uJkgYMJ/LdOR3VM+7T32IRyhuxrNNCYygqk6avuJ4QqvMLU02T6v3GhI7WWA==", + "dependencies": { + "Avalonia": "0.10.13", + "ReactiveUI": "13.2.10", + "System.Reactive": "5.0.0" + } + }, + "Ninject": { + "type": "Direct", + "requested": "[3.3.4, )", + "resolved": "3.3.4", + "contentHash": "CmbWW97FfJuh4LEOVZM/spqXl4KAulRUjqeMwRd5J9rDMQArmIYaDMU3pyzXXHT062tbF0OPIMwI7tSOtprPfg==", + "dependencies": { + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Emit.Lightweight": "4.3.0" + } + }, + "NoStringEvaluating": { + "type": "Direct", + "requested": "[2.2.2, )", + "resolved": "2.2.2", + "contentHash": "hJHivPDA1Vxn0CCgOtHKZ3fmldxQuz7VL1J4lEaPTXCf+Vwcx1FDf05mGMh6olYMSxoKimGX8YK2sEoqeH3pnA==", + "dependencies": { + "Microsoft.Extensions.ObjectPool": "5.0.9" + } + }, + "ReactiveUI": { + "type": "Direct", + "requested": "[17.1.50, )", + "resolved": "17.1.50", + "contentHash": "UofZH1WMwWNLvFkK2SH+gsYTkUmhFFJO0Pix9YG2RzdHQ92mRFCzHzPO1abeU8/cxzyc9hJHX7sBChzUj53Ulg==", + "dependencies": { + "DynamicData": "7.5.2", + "Splat": "14.1.45" + } + }, + "SkiaSharp": { + "type": "Direct", + "requested": "[2.88.0-preview.178, )", + "resolved": "2.88.0-preview.178", + "contentHash": "arzd/44ykiBPqGWUuQqNTuJ49rhsXOg4Zw1p2Mm3B/5PZzV1wcTH4V+J+4ra8RS0KbIoy4KWeNF+zHAifNsiRg==", + "dependencies": { + "SkiaSharp.NativeAssets.Win32": "2.88.0-preview.178", + "SkiaSharp.NativeAssets.macOS": "2.88.0-preview.178", + "System.Memory": "4.5.3" + } + }, + "Avalonia.Angle.Windows.Natives": { + "type": "Transitive", + "resolved": "2.1.0.2020091801", + "contentHash": "nGsCPI8FuUknU/e6hZIqlsKRDxClXHZyztmgM8vuwslFC/BIV3LqM2wKefWbr6SORX4Lct4nivhSMkdF/TrKgg==" + }, + "Avalonia.Controls.DataGrid": { + "type": "Transitive", + "resolved": "0.10.13", + "contentHash": "51xcaYtJN41vX/4xUu8rNyoISTO4bdswpfZmTPjeBTdofrhZ6mzOqbxVk6tqT4gt88MPihbaPil4jsD4X4Aixw==", + "dependencies": { + "Avalonia": "0.10.13", + "Avalonia.Remote.Protocol": "0.10.13", + "JetBrains.Annotations": "10.3.0", + "System.Reactive": "5.0.0" + } + }, + "Avalonia.Desktop": { + "type": "Transitive", + "resolved": "0.10.13", + "contentHash": "v+siRNQYvSZR9lt/bBgb81t6LGbSC7pUo+APgPmKYGLeYcMij1O6CWk7tCh9hihMxNHYw/PEB06r8ZBQIg9YPg==", + "dependencies": { + "Avalonia": "0.10.13", + "Avalonia.Native": "0.10.13", + "Avalonia.Skia": "0.10.13", + "Avalonia.Win32": "0.10.13", + "Avalonia.X11": "0.10.13" + } + }, + "Avalonia.Diagnostics": { + "type": "Transitive", + "resolved": "0.10.13", + "contentHash": "stIGj0Rv/p/Re0GqlXCc061paifG6wT0YvrTUV/fQloNctW8Y4sf1xZNzr9dxdSz6+LG2AZjdZcSUhUGOCe6Zg==", + "dependencies": { + "Avalonia": "0.10.13", + "Avalonia.Controls.DataGrid": "0.10.13", + "Microsoft.CodeAnalysis.CSharp.Scripting": "3.4.0", + "System.Reactive": "5.0.0" + } + }, + "Avalonia.FreeDesktop": { + "type": "Transitive", + "resolved": "0.10.13", + "contentHash": "/bKzscse/kY4RNMFk8r/jqLY/kS0fSkwp4TpqEF4UJeI8sHUvwe4yecnzNb1qDP9tCX4S6ML38LklAIqAk8gIQ==", + "dependencies": { + "Avalonia": "0.10.13", + "Tmds.DBus": "0.9.0" + } + }, + "Avalonia.Native": { + "type": "Transitive", + "resolved": "0.10.13", + "contentHash": "pq3WiiOyFyhJHnYdxP/fOlcG9DfqhJ0W5CCfPX48QyOdODbPgMF5LY6BU+McDpeAJTwQ4LqVfznHZoCeHH12gg==", + "dependencies": { + "Avalonia": "0.10.13" + } + }, + "Avalonia.Remote.Protocol": { + "type": "Transitive", + "resolved": "0.10.13", + "contentHash": "hnklCHyLCMrzWjMc3T0mYkRKdfUqpw2qCkf9HBRzyqnI6uG5tLw2QIlRF9zYC4BGOpx/B/647IcIjgq6H1ypzQ==" + }, + "Avalonia.Skia": { + "type": "Transitive", + "resolved": "0.10.13", + "contentHash": "aG0JlUhCpwGM/QsN/+rRak7XlPy0Jtd5HaiCdYKtuBOc+ISGs6hmCJDKjklNANp9gZR/TUUgXkqk5VFMQUJkTA==", + "dependencies": { + "Avalonia": "0.10.13", + "HarfBuzzSharp": "2.8.2-preview.178", + "HarfBuzzSharp.NativeAssets.Linux": "2.8.2-preview.178", + "HarfBuzzSharp.NativeAssets.WebAssembly": "2.8.2-preview.178", + "SkiaSharp": "2.88.0-preview.178", + "SkiaSharp.NativeAssets.Linux": "2.88.0-preview.178", + "SkiaSharp.NativeAssets.WebAssembly": "2.88.0-preview.178" + } + }, + "Avalonia.Svg.Skia": { + "type": "Transitive", + "resolved": "0.10.12", + "contentHash": "qsXKdm5eWpfoVPe0xgtxhbOYlhG8QdbYNJZTTihg/c4iPFYuh1G7DldiNskuVFuGiGxLVZ0g6ebql7ZkwbO1pA==", + "dependencies": { + "Avalonia": "0.10.12", + "Avalonia.Skia": "0.10.12", + "SkiaSharp": "2.88.0-preview.178", + "Svg.Skia": "0.5.12" + } + }, + "Avalonia.Win32": { + "type": "Transitive", + "resolved": "0.10.13", + "contentHash": "CNUGWafAonBYYbHMliujDrs4JH2giH435GxU+O1q/nGyO5Mm+PXCG4NYsg+0zwp8yQBapFK7eYwzamU+re+cDw==", + "dependencies": { + "Avalonia": "0.10.13", + "Avalonia.Angle.Windows.Natives": "2.1.0.2020091801", + "System.Drawing.Common": "4.5.0", + "System.Numerics.Vectors": "4.5.0" + } + }, + "Avalonia.X11": { + "type": "Transitive", + "resolved": "0.10.13", + "contentHash": "kXxn79KVB0ZfeZqQL7c2Dlvl96GBlRT8rzAh6g/j0hcgykQ55/e0be8Te6+Ny7hI+tFrob6lxvYdxYVUUCjHYg==", + "dependencies": { + "Avalonia": "0.10.13", + "Avalonia.FreeDesktop": "0.10.13", + "Avalonia.Skia": "0.10.13" + } + }, + "Castle.Core": { + "type": "Transitive", + "resolved": "4.2.0", + "contentHash": "1TtKHYYVfox7aUZ0akCqkULmAjpG8X5ZRzTzTiONY34xtvvaPuUSSdVL1VaF/1/ljRhOkpy+uKOGn6XoFGvorw==", + "dependencies": { + "NETStandard.Library": "1.6.1", + "System.Collections.Specialized": "4.3.0", + "System.ComponentModel": "4.3.0", + "System.ComponentModel.TypeConverter": "4.3.0", + "System.Diagnostics.TraceSource": "4.3.0", + "System.Dynamic.Runtime": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit": "4.3.0", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Xml.XmlDocument": "4.3.0" + } + }, + "DynamicData": { + "type": "Transitive", + "resolved": "7.5.4", + "contentHash": "1OpHPoyQGzHREiP6JXnPaBBx4KWVQZW7zBAZpKXc9kl4rcbEK4fo2/T3bDXZbHWKhDqVAISW9pE4Ug9+ms3RoA==", + "dependencies": { + "System.Reactive": "5.0.0" + } + }, + "EmbedIO": { + "type": "Transitive", + "resolved": "3.4.3", + "contentHash": "YM6hpZNAfvbbixfG9T4lWDGfF0D/TqutbTROL4ogVcHKwPF1hp+xS3ABwd3cxxTxvDFkj/zZl57QgWuFA8Igxw==", + "dependencies": { + "Unosquare.Swan.Lite": "3.0.0" + } + }, + "Fizzler": { + "type": "Transitive", + "resolved": "1.2.0", + "contentHash": "CPxuWF8EPvM0rwAtMTR5G+7EuLoGNXsEfqyx06upN9JyVALZ73KgbGn3SLFwGosifiUAXrvNHtXlUwGGytdECg==" + }, + "FluentAvaloniaUI": { + "type": "Transitive", + "resolved": "1.3.0", + "contentHash": "xzcsuOswakMpz/EdA59NEOgaCtZ/9zsd5QWTB0YYQqSv1GF95Uk2aMVtO5gtfNrCT4lZvGNWVf3HGjYz9cHovQ==", + "dependencies": { + "Avalonia": "0.10.13", + "Avalonia.Desktop": "0.10.13", + "Avalonia.Diagnostics": "0.10.13", + "MicroCom.CodeGenerator.MSBuild": "0.10.4", + "MicroCom.Runtime": "0.10.4" + } + }, + "HarfBuzzSharp": { + "type": "Transitive", + "resolved": "2.8.2-preview.178", + "contentHash": "OUir5qn95QRtlc8RWKfU/63xYwtuAbylL2oAj3eBWgAsVoWnFrEv+Oh1sj0xjW7mogFGaeGtY40lqAD1srWJcQ==", + "dependencies": { + "HarfBuzzSharp.NativeAssets.Win32": "2.8.2-preview.178", + "HarfBuzzSharp.NativeAssets.macOS": "2.8.2-preview.178", + "System.Memory": "4.5.3" + } + }, + "HarfBuzzSharp.NativeAssets.Linux": { + "type": "Transitive", + "resolved": "2.8.2-preview.178", + "contentHash": "4scihdELcRpCEubBsUMHUJn93Xvx6ASj596WfO9y8CEuFNW0LBMDL71HBCyq5zXsn8HyGjLtoBLW0PpXbVnpjQ==", + "dependencies": { + "HarfBuzzSharp": "2.8.2-preview.178" + } + }, + "HarfBuzzSharp.NativeAssets.macOS": { + "type": "Transitive", + "resolved": "2.8.2-preview.178", + "contentHash": "QtmAs62il4vFtt3fFOXhhPDl7TX+NGu4tFB5qmnqUn+EnSJW7mxqNk1n9I7+Z2ORym0nTP4dhcRNtOpOS7Oenw==" + }, + "HarfBuzzSharp.NativeAssets.WebAssembly": { + "type": "Transitive", + "resolved": "2.8.2-preview.178", + "contentHash": "+S8qtBAVTrt+E85jZXPxYthUgSUq7iB6UZ0v0WFsy9gWhZ/hVE3hZJpcgeywT9H/SRX3ZIX+qzpKJlOM+mUcNA==" + }, + "HarfBuzzSharp.NativeAssets.Win32": { + "type": "Transitive", + "resolved": "2.8.2-preview.178", + "contentHash": "EgeF5uCZcAriIHmWq3hKNxz/jBJLeP/PKU4yI87UNkJCt4hYignOMjY0irl/rGVZtTL/G05xxf7TB6sjisi8sQ==" + }, + "HidSharp": { + "type": "Transitive", + "resolved": "2.1.0", + "contentHash": "UTdxWvbgp2xzT1Ajaa2va+Qi3oNHJPasYmVhbKI2VVdu1VYP6yUG+RikhsHvpD7iM0S8e8UYb5Qm/LTWxx9QAA==" + }, + "Humanizer.Core": { + "type": "Transitive", + "resolved": "2.11.10", + "contentHash": "4TBsHSXPocdsEB5dewIHeKykTzIz5Ui7ouXw4JsUGI+ax4jjviVJVD7+gsPCNyA+b3de2EjYI+jcEq8I/1ZFSQ==" + }, + "JetBrains.Annotations": { + "type": "Transitive", + "resolved": "10.3.0", + "contentHash": "0GLU9lwGVXjUNlr9ZIdAgjqLI2Zm/XFGJFaqJ1T1sU+kwfeMLhm68+rblUrNUP9psRl4i8yM7Ghb4ia4oI2E5g==", + "dependencies": { + "System.Runtime": "4.1.0" + } + }, + "LiteDB": { + "type": "Transitive", + "resolved": "5.0.11", + "contentHash": "6cL4bOmVCUB0gIK+6qIr68HeqjjHZicPDGQjvJ87mIOvkFsEsJWkIps3yoKNeLpHhJQur++yoQ9Q8gxsdos0xQ==" + }, + "Material.Icons": { + "type": "Transitive", + "resolved": "1.0.2", + "contentHash": "4UIT91QbedjNfUYU+R3T60U+InozFtIsP1iUzGbkq/G0f1eDE3tXMWUuLEDO3yCEP2MHrPjAOpokwqk1rnWNGA==", + "dependencies": { + "Newtonsoft.Json": "12.0.3" + } + }, + "Material.Icons.Avalonia": { + "type": "Transitive", + "resolved": "1.0.2", + "contentHash": "hK0MQm2XyPwjT+DiviDOBjrJVQe6V0u+XTDVbxohkq58hUBlq0XZXmHHZ27jUJU6ZVP9ybu44aXfWycbVjnY2A==", + "dependencies": { + "Avalonia": "0.10.0", + "Material.Icons": "1.0.2" + } + }, + "McMaster.NETCore.Plugins": { + "type": "Transitive", + "resolved": "1.4.0", + "contentHash": "UKw5Z2/QHhkR7kiAJmqdCwVDMQV0lwsfj10+FG676r8DsJWIpxtachtEjE0qBs9WoK5GUQIqxgyFeYUSwuPszg==", + "dependencies": { + "Microsoft.DotNet.PlatformAbstractions": "3.1.6", + "Microsoft.Extensions.DependencyModel": "5.0.0" + } + }, + "MicroCom.CodeGenerator.MSBuild": { + "type": "Transitive", + "resolved": "0.10.4", + "contentHash": "aG1kLtkgX6lC8qpxVon4OFSCdWYEbQubIg+2/ychWTIFTrDHWFkhcC4YTn0IfGiVCLwh0Yj7eSc8nk5f3UoMKg==" + }, + "MicroCom.Runtime": { + "type": "Transitive", + "resolved": "0.10.4", + "contentHash": "enc2U+/1UnF3rtocxb5ofcg7cJSmJI4adbYPr8DZa5bQzvhqA/VbjlcalxoqjI3CR2RvM5WWpjKT0p3BriFJjw==" + }, + "Microsoft.CodeAnalysis.Analyzers": { + "type": "Transitive", + "resolved": "2.9.6", + "contentHash": "Kmms3TxGQMNb95Cu/3K+0bIcMnV4qf/phZBLAB0HUi65rBPxP4JO3aM2LoAcb+DFS600RQJMZ7ZLyYDTbLwJOQ==" + }, + "Microsoft.CodeAnalysis.Common": { + "type": "Transitive", + "resolved": "3.4.0", + "contentHash": "3ncA7cV+iXGA1VYwe2UEZXcvWyZSlbexWjM9AvocP7sik5UD93qt9Hq0fMRGk0jFRmvmE4T2g+bGfXiBVZEhLw==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "2.9.6", + "System.Collections.Immutable": "1.5.0", + "System.Memory": "4.5.3", + "System.Reflection.Metadata": "1.6.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.2", + "System.Text.Encoding.CodePages": "4.5.1", + "System.Threading.Tasks.Extensions": "4.5.3" + } + }, + "Microsoft.CodeAnalysis.CSharp": { + "type": "Transitive", + "resolved": "3.4.0", + "contentHash": "/LsTtgcMN6Tu1oo7/WYbRAHL4/ubXC/miEakwTpcZKJKtFo7D0AK95Hw0dbGxul6C8WJu60v6NP2435TDYZM+Q==", + "dependencies": { + "Microsoft.CodeAnalysis.Common": "[3.4.0]" + } + }, + "Microsoft.CodeAnalysis.CSharp.Scripting": { + "type": "Transitive", + "resolved": "3.4.0", + "contentHash": "tLgqc76qXHmONUhWhxo7z3TcL/LmGFWIUJm1exbQmVJohuQvJnejUMxmVkdxDfMuMZU1fIyJXPZ6Fkp4FEneAg==", + "dependencies": { + "Microsoft.CSharp": "4.3.0", + "Microsoft.CodeAnalysis.CSharp": "[3.4.0]", + "Microsoft.CodeAnalysis.Common": "[3.4.0]", + "Microsoft.CodeAnalysis.Scripting.Common": "[3.4.0]" + } + }, + "Microsoft.CodeAnalysis.Scripting.Common": { + "type": "Transitive", + "resolved": "3.4.0", + "contentHash": "+b6I3DZL2zvck+B/E/aiOveakj5U2G2BcYODQxcGh2IDbatNU3XXxGT1HumkWB5uIZI2Leu0opBgBpjScmjGMA==", + "dependencies": { + "Microsoft.CodeAnalysis.Common": "[3.4.0]" + } + }, + "Microsoft.CSharp": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "P+MBhIM0YX+JqROuf7i306ZLJEjQYA9uUyRDE+OqwUI5sh41e2ZbPQV3LfAPh+29cmceE1pUffXsGfR4eMY3KA==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Dynamic.Runtime": "4.3.0", + "System.Globalization": "4.3.0", + "System.Linq": "4.3.0", + "System.Linq.Expressions": "4.3.0", + "System.ObjectModel": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "Microsoft.DotNet.PlatformAbstractions": { + "type": "Transitive", + "resolved": "3.1.6", + "contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg==" + }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q==" + }, + "Microsoft.Extensions.ObjectPool": { + "type": "Transitive", + "resolved": "5.0.9", + "contentHash": "grj0e6Me0EQsgaurV0fxP0xd8sz8eZVK+Jb816DPzNADHaqXaXJD3xZX9SFjyDl3ykAYvD0y77o5vRd9Hzsk9g==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + }, + "Microsoft.NETCore.Targets": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" + }, + "Microsoft.Win32.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "LuI1oG+24TUj1ZRQQjM5Ew73BKnZE5NZ/7eAdh1o8ST5dPhUnJvIkiIn2re3MwnkRy6ELRnvEbBxHP8uALKhJw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "2.0.0" + } + }, + "NETStandard.Library": { + "type": "Transitive", + "resolved": "1.6.1", + "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.Win32.Primitives": "4.3.0", + "System.AppContext": "4.3.0", + "System.Collections": "4.3.0", + "System.Collections.Concurrent": "4.3.0", + "System.Console": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tools": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Calendars": "4.3.0", + "System.IO": "4.3.0", + "System.IO.Compression": "4.3.0", + "System.IO.Compression.ZipFile": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Linq": "4.3.0", + "System.Linq.Expressions": "4.3.0", + "System.Net.Http": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Net.Sockets": "4.3.0", + "System.ObjectModel": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Security.Cryptography.X509Certificates": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encoding.Extensions": "4.3.0", + "System.Text.RegularExpressions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "System.Threading.Timer": "4.3.0", + "System.Xml.ReaderWriter": "4.3.0", + "System.Xml.XDocument": "4.3.0" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "Ninject.Extensions.ChildKernel": { + "type": "Transitive", + "resolved": "3.3.0", + "contentHash": "vl/p3f8sIaCCHiKsjhq9R8n3bH705Hu1WJXNpMEz1UC79EV51Mk5TWYXQbRnsK20hxF48CiAgUBb9pMKfX6sLw==", + "dependencies": { + "Ninject": "3.3.4" + } + }, + "Ninject.Extensions.Conventions": { + "type": "Transitive", + "resolved": "3.3.0", + "contentHash": "bAMK7tRHIRQ+gjR1WxwTlNuP+/bKRIFf6NKObkWP3XVzFQhsLEKA0hEo73OXuBdpng0jczhqCGmwu630nIa/bg==", + "dependencies": { + "Ninject.Extensions.Factory": "3.3.2" + } + }, + "Ninject.Extensions.Factory": { + "type": "Transitive", + "resolved": "3.3.2", + "contentHash": "H9s77i9WsbgF6s7OieQ+c51KoW90jJAQqb0ClEqi6SBtL7jySUjh/5HCjnYgyQ8iYcWhvhw9cFnYxX9CB1kL7Q==", + "dependencies": { + "Castle.Core": "4.2.0", + "Ninject": "3.3.3" + } + }, + "ReactiveUI.Validation": { + "type": "Transitive", + "resolved": "2.2.1", + "contentHash": "rhEphZ4ErbGfNtbBQ/tYMsLJYHyLVyqidU+sgZ3kXKbS7QrNoM4j6PPxCwLMKsJUuvVL8JN45xgmB9tSwm7+lg==", + "dependencies": { + "ReactiveUI": "16.2.6" + } + }, + "RGB.NET.Core": { + "type": "Transitive", + "resolved": "1.0.0-prerelease7", + "contentHash": "IIja5sC4QZ5pbSNckRCG7TlY4U6j/dRbrl4e2FZqsTGgsevaVB3IqonUQLFY1GGst4xNSl2oh0A23coXQxXGbQ==" + }, + "RGB.NET.Layout": { + "type": "Transitive", + "resolved": "1.0.0-prerelease7", + "contentHash": "S0kfWVa8EfMOAl2WPHsq98dwaO+SNz9TWr1AtMkdo8aZuYIVhaJ1c+mSAMMnH1V+mSbxDWPHWkNzi9ITszJucA==", + "dependencies": { + "RGB.NET.Core": "1.0.0-prerelease7" + } + }, + "RGB.NET.Presets": { + "type": "Transitive", + "resolved": "1.0.0-prerelease7", + "contentHash": "NgShvOPQM0miOsdqMKjkNunngJUZMwr8KR8ME2/Ksir7wgIQfgJj1YwZy8aIj+ar7fDo6VZJZenAshs/Ul+04A==", + "dependencies": { + "RGB.NET.Core": "1.0.0-prerelease7" + } + }, + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==" + }, + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==" + }, + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==" + }, + "runtime.native.System": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.IO.Compression": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", + "dependencies": { + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" + } + }, + "runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", + "dependencies": { + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==" + }, + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==" + }, + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==" + }, + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==" + }, + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==" + }, + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==" + }, + "Serilog": { + "type": "Transitive", + "resolved": "2.10.0", + "contentHash": "+QX0hmf37a0/OZLxM3wL7V6/ADvC1XihXN4Kq/p6d8lCPfgkRdiuhbWlMaFjR9Av0dy5F0+MBeDmDdRZN/YwQA==" + }, + "Serilog.Sinks.Console": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "apLOvSJQLlIbKlbx+Y2UDHSP05kJsV7mou+fvJoRGs/iR+jC22r8cuFVMjjfVxz/AD4B2UCltFhE1naRLXwKNw==", + "dependencies": { + "Serilog": "2.10.0" + } + }, + "Serilog.Sinks.Debug": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "Y6g3OBJ4JzTyyw16fDqtFcQ41qQAydnEvEqmXjhwhgjsnG/FaJ8GUqF5ldsC/bVkK8KYmqrPhDO+tm4dF6xx4A==", + "dependencies": { + "Serilog": "2.10.0" + } + }, + "Serilog.Sinks.File": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "uwV5hdhWPwUH1szhO8PJpFiahqXmzPzJT/sOijH/kFgUx+cyoDTMM8MHD0adw9+Iem6itoibbUXHYslzXsLEAg==", + "dependencies": { + "Serilog": "2.10.0" + } + }, + "ShimSkiaSharp": { + "type": "Transitive", + "resolved": "0.5.12", + "contentHash": "oUGM7gQHRzbGPRs3E1pe5e8VwML21YyEz9xdo+r2ov1mAqSDPyXErVQP6pN4gnfYMVf5ADR7BVkVzt4R9Iz3gQ==" + }, + "SkiaSharp.HarfBuzz": { + "type": "Transitive", + "resolved": "2.88.0-preview.178", + "contentHash": "qTf3PbRJsTVOttQkYWOCswa1QnkH0dEOqMSgr3iXZQuKPNlj1qpGY8+OGPs25WKgUEqOpv2nog/AYQ/bpyOXzA==", + "dependencies": { + "HarfBuzzSharp": "2.8.2-preview.178", + "SkiaSharp": "2.88.0-preview.178" + } + }, + "SkiaSharp.NativeAssets.Linux": { + "type": "Transitive", + "resolved": "2.88.0-preview.178", + "contentHash": "nc9C8zGvL2G7p0lcTPhN4EOt2Mozv6KLJinMwjF97sYoI5cpkXCPZSRTcyf8k49gAZaOd+UMGaygCAz/8vaaWg==", + "dependencies": { + "SkiaSharp": "2.88.0-preview.178" + } + }, + "SkiaSharp.NativeAssets.macOS": { + "type": "Transitive", + "resolved": "2.88.0-preview.178", + "contentHash": "+Hs3ku4buimzBHuc8FoyjOcE6eU5r98zcG7WH/s+doYQ1bFIjk+dKfqthgZ2o0NRAv8D3esq9rWrZTj12q+m1w==" + }, + "SkiaSharp.NativeAssets.WebAssembly": { + "type": "Transitive", + "resolved": "2.88.0-preview.178", + "contentHash": "u4Ss81oOlx0dhu5fxl4vK5f2Hm7psHDUSAoQValNV/BmixsW4TkETE3dOnHNRWwI56++tRG9dK33HimZDUrUpw==" + }, + "SkiaSharp.NativeAssets.Win32": { + "type": "Transitive", + "resolved": "2.88.0-preview.178", + "contentHash": "9og9GCkdZc/NYrmbsRzohmIBRlS1oFegJiBMsoG93qYjhh2o6q5QBYxd61zw5Mgeytl3qj4YM+6BNIF4tcF+6w==" + }, + "Splat": { + "type": "Transitive", + "resolved": "14.1.45", + "contentHash": "ayHdfTUklD5ci0s9m4uYMccjtkKVjZ9fVPT5q3PN+SnvyD6bjQVRozOfUHwdwh4LAz9ETZjR/tAgrm+IapXKrw==" + }, + "Svg.Custom": { + "type": "Transitive", + "resolved": "0.5.12", + "contentHash": "kmjLQf5U5WC7tRGBedUhtrOUCR0NaNL2auzOA2a/oMwEA0Bjrpd6qvMTpJUS3HITxi8vJazGl270K+i0JvdJog==", + "dependencies": { + "Fizzler": "1.2.0", + "System.Memory": "4.5.3", + "System.ObjectModel": "4.3.0", + "System.ValueTuple": "4.5.0" + } + }, + "Svg.Model": { + "type": "Transitive", + "resolved": "0.5.12", + "contentHash": "/CPiXIugg4oVyYlQr26fB1X9iQfICALF8AJXbTWnXGoP2WZa1t6aZbAXPk3HoPApA0w5waf3XXkBiYYnWwawaQ==", + "dependencies": { + "ShimSkiaSharp": "0.5.12", + "Svg.Custom": "0.5.12" + } + }, + "Svg.Skia": { + "type": "Transitive", + "resolved": "0.5.12", + "contentHash": "KjKpjz0FKge+WpRzjD1bqywAW3vZhXwpR5c7Ej5OuP4xDrQjBwtFeB0iZ+yEJMzwXf/Rs4ImuN8m3bmBDJvMHg==", + "dependencies": { + "SkiaSharp": "2.88.0-preview.178", + "SkiaSharp.HarfBuzz": "2.88.0-preview.178", + "Svg.Custom": "0.5.12", + "Svg.Model": "0.5.12" + } + }, + "System.AppContext": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Collections.Concurrent": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "1.5.0", + "contentHash": "EXKiDFsChZW0RjrZ4FYHu9aW6+P4MCgEDCklsVseRfhoO0F+dXeMSsMRAlVXIo06kGJ/zv+2w1a2uc2+kxxSaQ==" + }, + "System.Collections.NonGeneric": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", + "dependencies": { + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Collections.Specialized": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", + "dependencies": { + "System.Collections.NonGeneric": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Extensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.ComponentModel": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.ComponentModel.Annotations": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg==" + }, + "System.ComponentModel.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", + "dependencies": { + "System.ComponentModel": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.ComponentModel.TypeConverter": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Collections.NonGeneric": "4.3.0", + "System.Collections.Specialized": "4.3.0", + "System.ComponentModel": "4.3.0", + "System.ComponentModel.Primitives": "4.3.0", + "System.Globalization": "4.3.0", + "System.Linq": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Console": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Diagnostics.Tools": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Diagnostics.TraceSource": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VnYp1NxGx8Ww731y2LJ1vpfb/DKVNKEZ8Jsh5SgQTZREL/YpWRArgh9pI8CDLmgHspZmLL697CaLvH85qQpRiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0", + "runtime.native.System": "4.3.0" + } + }, + "System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "AiJFxxVPdeITstiRS5aAu8+8Dpf5NawTMoapZ53Gfirml24p7HIfhjmCRxdXnmmf3IUA3AX3CcW7G73CjWxW/Q==", + "dependencies": { + "Microsoft.NETCore.Platforms": "2.0.0", + "Microsoft.Win32.SystemEvents": "4.5.0" + } + }, + "System.Dynamic.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Linq": "4.3.0", + "System.Linq.Expressions": "4.3.0", + "System.ObjectModel": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization.Calendars": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.InteropServices": "4.3.0" + } + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.IO.Compression": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Buffers": "4.3.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.IO.Compression": "4.3.0" + } + }, + "System.IO.Compression.ZipFile": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", + "dependencies": { + "System.Buffers": "4.3.0", + "System.IO": "4.3.0", + "System.IO.Compression": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.IO.FileSystem": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.IO.FileSystem.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "SxHB3nuNrpptVk+vZ/F+7OHEpoHUIKKMl02bUmYHQr1r+glbZQxs7pRtsf4ENO29TVm2TH3AEeep2fJcy92oYw==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.IO.FileSystem.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.Linq": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0" + } + }, + "System.Linq.Expressions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Linq": "4.3.0", + "System.ObjectModel": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Emit.Lightweight": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.3", + "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==" + }, + "System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.DiagnosticSource": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Extensions": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Security.Cryptography.X509Certificates": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Net.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Net.Sockets": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + }, + "System.ObjectModel": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Reactive": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ==" + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "VR4kk8XLKebQ4MZuKuIni/7oh+QGFmZW3qORd1GvBq/8026OpW501SzT/oypwiQl4TvT8ErnReh/NzY9u+C6wQ==" + }, + "System.Reflection.Emit.ILGeneration": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit.Lightweight": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ==" + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.TypeExtensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "4.6.0", + "contentHash": "HxozeSlipUK7dAroTYwIcGwKDeOVpQnJlpVaOkBz7CM4TsE5b/tKlQBZecTjh6FzcSbxndYaxxpsBMz+wMJeyw==" + }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Runtime.InteropServices.RuntimeInformation": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Threading": "4.3.0", + "runtime.native.System": "4.3.0" + } + }, + "System.Runtime.Numerics": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", + "dependencies": { + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0" + } + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.Apple": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Cng": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.Security.Cryptography.Csp": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Collections.Concurrent": "4.3.0", + "System.Linq": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", + "dependencies": { + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", + "dependencies": { + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Calendars": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Cng": "4.3.0", + "System.Security.Cryptography.Csp": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "4J2JQXbftjPMppIHJ7IC+VXQ9XfEagN92vZZNoG12i+zReYlim5dMoXFC1Zzg7tsnKDM7JPo5bYfFK4Jheq44w==", + "dependencies": { + "Microsoft.NETCore.Platforms": "2.1.2", + "System.Runtime.CompilerServices.Unsafe": "4.5.2" + } + }, + "System.Text.Encoding.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.Text.RegularExpressions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.Threading": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", + "dependencies": { + "System.Runtime": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.3", + "contentHash": "+MvhNtcvIbqmhANyKu91jQnvIRVSTiaOiFNfKWwXGHG48YAb4I/TyH8spsySiPYla7gKal5ZnF3teJqZAximyQ==" + }, + "System.Threading.Timer": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.ValueTuple": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" + }, + "System.Xml.ReaderWriter": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encoding.Extensions": "4.3.0", + "System.Text.RegularExpressions": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "System.Threading.Tasks.Extensions": "4.3.0" + } + }, + "System.Xml.XDocument": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tools": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Xml.ReaderWriter": "4.3.0" + } + }, + "System.Xml.XmlDocument": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Xml.ReaderWriter": "4.3.0" + } + }, + "Tmds.DBus": { + "type": "Transitive", + "resolved": "0.9.0", + "contentHash": "KcTWL9aKuob9Qo2sOTTKFePs1rKGTwZrcBvMFuGVIVR5RojX3oIFj5UBLYfSGjYgrcImC7LjQI3DdCFwUnhNXw==", + "dependencies": { + "System.Reflection.Emit": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" + } + }, + "Unosquare.Swan.Lite": { + "type": "Transitive", + "resolved": "3.0.0", + "contentHash": "noPwJJl1Q9uparXy1ogtkmyAPGNfSGb0BLT1292nFH1jdMKje6o2kvvrQUvF9Xklj+IoiAI0UzF6Aqxlvo10lw==" + }, + "artemis.core": { + "type": "Project", + "dependencies": { + "Artemis.Storage": "1.0.0", + "EmbedIO": "3.4.3", + "HidSharp": "2.1.0", + "Humanizer.Core": "2.11.10", + "LiteDB": "5.0.11", + "McMaster.NETCore.Plugins": "1.4.0", + "Newtonsoft.Json": "13.0.1", + "Ninject": "3.3.4", + "Ninject.Extensions.ChildKernel": "3.3.0", + "Ninject.Extensions.Conventions": "3.3.0", + "RGB.NET.Core": "1.0.0-prerelease7", + "RGB.NET.Layout": "1.0.0-prerelease7", + "RGB.NET.Presets": "1.0.0-prerelease7", + "Serilog": "2.10.0", + "Serilog.Sinks.Console": "4.0.1", + "Serilog.Sinks.Debug": "2.0.0", + "Serilog.Sinks.File": "5.0.0", + "SkiaSharp": "2.88.0-preview.178", + "System.Buffers": "4.5.1", + "System.IO.FileSystem.AccessControl": "5.0.0", + "System.Numerics.Vectors": "4.5.0", + "System.Reflection.Metadata": "5.0.0", + "System.ValueTuple": "4.5.0" + } + }, + "artemis.storage": { + "type": "Project", + "dependencies": { + "LiteDB": "5.0.11", + "Serilog": "2.10.0" + } + }, + "artemis.ui.shared": { + "type": "Project", + "dependencies": { + "Artemis.Core": "1.0.0", + "Avalonia": "0.10.13", + "Avalonia.ReactiveUI": "0.10.13", + "Avalonia.Svg.Skia": "0.10.12", + "DynamicData": "7.5.4", + "FluentAvaloniaUI": "1.3.0", + "Material.Icons.Avalonia": "1.0.2", + "RGB.NET.Core": "1.0.0-prerelease7", + "ReactiveUI": "17.1.50", + "ReactiveUI.Validation": "2.2.1", + "SkiaSharp": "2.88.0-preview.178" + } + } + } + } +} \ No newline at end of file