mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Node editor - Implemented node visuals, pin visuals
Node editor - Implemented undo/redo and some commands
This commit is contained in:
parent
c99224ab2d
commit
034879a2c9
@ -1,10 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
using Artemis.Storage.Entities.Profile.Nodes;
|
using Artemis.Storage.Entities.Profile.Nodes;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Ninject;
|
using Ninject;
|
||||||
using Ninject.Parameters;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.Core.Services
|
namespace Artemis.Core.Services
|
||||||
@ -37,9 +37,19 @@ namespace Artemis.Core.Services
|
|||||||
#region Methods
|
#region Methods
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
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)
|
public NodeTypeRegistration RegisterNodeType(Plugin plugin, Type nodeType)
|
||||||
@ -53,7 +63,7 @@ namespace Artemis.Core.Services
|
|||||||
string name = nodeAttribute?.Name ?? nodeType.Name;
|
string name = nodeAttribute?.Name ?? nodeType.Name;
|
||||||
string description = nodeAttribute?.Description ?? string.Empty;
|
string description = nodeAttribute?.Description ?? string.Empty;
|
||||||
string category = nodeAttribute?.Category ?? 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));
|
NodeData nodeData = new(plugin, nodeType, name, description, category, nodeAttribute?.InputType, nodeAttribute?.OutputType, (s, e) => CreateNode(s, e, nodeType));
|
||||||
return NodeTypeStore.Add(nodeData);
|
return NodeTypeStore.Add(nodeData);
|
||||||
}
|
}
|
||||||
@ -105,7 +115,7 @@ namespace Artemis.Core.Services
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the best matching registration for the provided type
|
/// Gets the best matching registration for the provided type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TypeColorRegistration? GetTypeColor(Type type);
|
TypeColorRegistration GetTypeColorRegistration(Type type);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a node of the provided <paramref name="nodeType" />
|
/// Registers a node of the provided <paramref name="nodeType" />
|
||||||
|
|||||||
@ -12,16 +12,6 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public INode Node { get; }
|
public INode Node { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called whenever the custom view model is activated
|
|
||||||
/// </summary>
|
|
||||||
void OnActivate();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called whenever the custom view model is closed
|
|
||||||
/// </summary>
|
|
||||||
void OnDeactivate();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs whenever the node was modified by the view model
|
/// Occurs whenever the node was modified by the view model
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -125,9 +125,6 @@ namespace Artemis.Core
|
|||||||
_nodes.Remove(node);
|
_nodes.Remove(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node is IDisposable disposable)
|
|
||||||
disposable.Dispose();
|
|
||||||
|
|
||||||
NodeRemoved?.Invoke(this, new SingleValueEventArgs<INode>(node));
|
NodeRemoved?.Invoke(this, new SingleValueEventArgs<INode>(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ namespace Artemis.UI.Shared
|
|||||||
if (type == typeof(object))
|
if (type == typeof(object))
|
||||||
return (SKColors.White.ToColor(), SKColors.White.Darken(0.35f).ToColor());
|
return (SKColors.White.ToColor(), SKColors.White.Darken(0.35f).ToColor());
|
||||||
|
|
||||||
TypeColorRegistration? typeColorRegistration = NodeService?.GetTypeColor(type);
|
TypeColorRegistration? typeColorRegistration = NodeService?.GetTypeColorRegistration(type);
|
||||||
if (typeColorRegistration != null)
|
if (typeColorRegistration != null)
|
||||||
return (typeColorRegistration.Color.ToColor(), typeColorRegistration.DarkenedColor.ToColor());
|
return (typeColorRegistration.Color.ToColor(), typeColorRegistration.DarkenedColor.ToColor());
|
||||||
|
|
||||||
|
|||||||
@ -154,12 +154,12 @@ namespace Artemis.VisualScripting.Editor.Controls
|
|||||||
|
|
||||||
private void GetCustomViewModel()
|
private void GetCustomViewModel()
|
||||||
{
|
{
|
||||||
CustomViewModel?.OnDeactivate();
|
// CustomViewModel?.OnDeactivate();
|
||||||
|
|
||||||
if (Node?.Node is Node customViewModelNode)
|
if (Node?.Node is Node customViewModelNode)
|
||||||
{
|
{
|
||||||
CustomViewModel = customViewModelNode.GetCustomViewModel();
|
CustomViewModel = customViewModelNode.GetCustomViewModel();
|
||||||
CustomViewModel?.OnActivate();
|
// CustomViewModel?.OnActivate();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
CustomViewModel = null;
|
CustomViewModel = null;
|
||||||
@ -182,7 +182,7 @@ namespace Artemis.VisualScripting.Editor.Controls
|
|||||||
if (CustomViewModel != null)
|
if (CustomViewModel != null)
|
||||||
{
|
{
|
||||||
CustomViewModel.NodeModified -= CustomViewModelOnNodeModified;
|
CustomViewModel.NodeModified -= CustomViewModelOnNodeModified;
|
||||||
CustomViewModel.OnDeactivate();
|
// CustomViewModel.OnDeactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomViewModel = null;
|
CustomViewModel = null;
|
||||||
|
|||||||
@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.Linux", "Avaloni
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.MacOS", "Avalonia\Artemis.UI.MacOS\Artemis.UI.MacOS.csproj", "{2F5F16DC-FACF-4559-9882-37C2949814C7}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.MacOS", "Avalonia\Artemis.UI.MacOS\Artemis.UI.MacOS.csproj", "{2F5F16DC-FACF-4559-9882-37C2949814C7}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.VisualScripting", "Avalonia\Artemis.VisualScripting\Artemis.VisualScripting.csproj", "{412B921A-26F5-4AE6-8B32-0C19BE54F421}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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|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.ActiveCfg = Release|Any CPU
|
||||||
{2F5F16DC-FACF-4559-9882-37C2949814C7}.Release|x64.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -113,6 +123,7 @@ Global
|
|||||||
{DE45A288-9320-461F-BE2A-26DFE3817216} = {960CAAC5-AA73-49F5-BF2F-DF2C789DF042}
|
{DE45A288-9320-461F-BE2A-26DFE3817216} = {960CAAC5-AA73-49F5-BF2F-DF2C789DF042}
|
||||||
{9012C8E2-3BEC-42F5-8270-7352A5922B04} = {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}
|
{2F5F16DC-FACF-4559-9882-37C2949814C7} = {960CAAC5-AA73-49F5-BF2F-DF2C789DF042}
|
||||||
|
{412B921A-26F5-4AE6-8B32-0C19BE54F421} = {960CAAC5-AA73-49F5-BF2F-DF2C789DF042}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {C203080A-4473-4CC2-844B-F552EA43D66A}
|
SolutionGuid = {C203080A-4473-4CC2-844B-F552EA43D66A}
|
||||||
|
|||||||
@ -426,6 +426,11 @@
|
|||||||
"resolved": "5.0.0",
|
"resolved": "5.0.0",
|
||||||
"contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q=="
|
"contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q=="
|
||||||
},
|
},
|
||||||
|
"Microsoft.Extensions.ObjectPool": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "5.0.9",
|
||||||
|
"contentHash": "grj0e6Me0EQsgaurV0fxP0xd8sz8eZVK+Jb816DPzNADHaqXaXJD3xZX9SFjyDl3ykAYvD0y77o5vRd9Hzsk9g=="
|
||||||
|
},
|
||||||
"Microsoft.NETCore.Platforms": {
|
"Microsoft.NETCore.Platforms": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "5.0.0",
|
"resolved": "5.0.0",
|
||||||
@ -544,6 +549,14 @@
|
|||||||
"Ninject": "3.3.3"
|
"Ninject": "3.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"NoStringEvaluating": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.2.2",
|
||||||
|
"contentHash": "hJHivPDA1Vxn0CCgOtHKZ3fmldxQuz7VL1J4lEaPTXCf+Vwcx1FDf05mGMh6olYMSxoKimGX8YK2sEoqeH3pnA==",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.ObjectPool": "5.0.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ReactiveUI.Validation": {
|
"ReactiveUI.Validation": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "2.2.1",
|
"resolved": "2.2.1",
|
||||||
@ -1758,6 +1771,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Artemis.Core": "1.0.0",
|
"Artemis.Core": "1.0.0",
|
||||||
"Artemis.UI.Shared": "1.0.0",
|
"Artemis.UI.Shared": "1.0.0",
|
||||||
|
"Artemis.VisualScripting": "1.0.0",
|
||||||
"Avalonia": "0.10.13",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.Controls.PanAndZoom": "10.12.0",
|
"Avalonia.Controls.PanAndZoom": "10.12.0",
|
||||||
"Avalonia.Desktop": "0.10.13",
|
"Avalonia.Desktop": "0.10.13",
|
||||||
@ -1795,6 +1809,19 @@
|
|||||||
"ReactiveUI.Validation": "2.2.1",
|
"ReactiveUI.Validation": "2.2.1",
|
||||||
"SkiaSharp": "2.88.0-preview.178"
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -426,6 +426,11 @@
|
|||||||
"resolved": "5.0.0",
|
"resolved": "5.0.0",
|
||||||
"contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q=="
|
"contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q=="
|
||||||
},
|
},
|
||||||
|
"Microsoft.Extensions.ObjectPool": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "5.0.9",
|
||||||
|
"contentHash": "grj0e6Me0EQsgaurV0fxP0xd8sz8eZVK+Jb816DPzNADHaqXaXJD3xZX9SFjyDl3ykAYvD0y77o5vRd9Hzsk9g=="
|
||||||
|
},
|
||||||
"Microsoft.NETCore.Platforms": {
|
"Microsoft.NETCore.Platforms": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "5.0.0",
|
"resolved": "5.0.0",
|
||||||
@ -544,6 +549,14 @@
|
|||||||
"Ninject": "3.3.3"
|
"Ninject": "3.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"NoStringEvaluating": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.2.2",
|
||||||
|
"contentHash": "hJHivPDA1Vxn0CCgOtHKZ3fmldxQuz7VL1J4lEaPTXCf+Vwcx1FDf05mGMh6olYMSxoKimGX8YK2sEoqeH3pnA==",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.ObjectPool": "5.0.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ReactiveUI.Validation": {
|
"ReactiveUI.Validation": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "2.2.1",
|
"resolved": "2.2.1",
|
||||||
@ -1758,6 +1771,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Artemis.Core": "1.0.0",
|
"Artemis.Core": "1.0.0",
|
||||||
"Artemis.UI.Shared": "1.0.0",
|
"Artemis.UI.Shared": "1.0.0",
|
||||||
|
"Artemis.VisualScripting": "1.0.0",
|
||||||
"Avalonia": "0.10.13",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.Controls.PanAndZoom": "10.12.0",
|
"Avalonia.Controls.PanAndZoom": "10.12.0",
|
||||||
"Avalonia.Desktop": "0.10.13",
|
"Avalonia.Desktop": "0.10.13",
|
||||||
@ -1795,6 +1809,19 @@
|
|||||||
"ReactiveUI.Validation": "2.2.1",
|
"ReactiveUI.Validation": "2.2.1",
|
||||||
"SkiaSharp": "2.88.0-preview.178"
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,7 +38,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
public DataModelPropertiesViewModel GetMainDataModelVisualization()
|
public DataModelPropertiesViewModel GetMainDataModelVisualization()
|
||||||
{
|
{
|
||||||
DataModelPropertiesViewModel viewModel = new(null, null, null);
|
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)));
|
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModelExpansion, viewModel, new DataModelPath(dataModelExpansion)));
|
||||||
|
|
||||||
// Update to populate children
|
// Update to populate children
|
||||||
|
|||||||
@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using Artemis.Core;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a node editor command that can be used to add a node.
|
||||||
|
/// </summary>
|
||||||
|
public class AddNode : INodeEditorCommand, IDisposable
|
||||||
|
{
|
||||||
|
private readonly INodeScript _nodeScript;
|
||||||
|
private readonly INode _node;
|
||||||
|
private bool _isRemoved;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="MoveNode" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeScript">The node script the node belongs to.</param>
|
||||||
|
/// <param name="node">The node to delete.</param>
|
||||||
|
public AddNode(INodeScript nodeScript, INode node)
|
||||||
|
{
|
||||||
|
_nodeScript = nodeScript;
|
||||||
|
_node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string DisplayName => $"Add '{_node.Name}' node";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
_nodeScript.AddNode(_node);
|
||||||
|
_isRemoved = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Undo()
|
||||||
|
{
|
||||||
|
_nodeScript.RemoveNode(_node);
|
||||||
|
_isRemoved = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_isRemoved)
|
||||||
|
_nodeScript.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a profile editor command that can be used to combine multiple commands into one.
|
||||||
|
/// </summary>
|
||||||
|
public class CompositeCommand : INodeEditorCommand, IDisposable
|
||||||
|
{
|
||||||
|
private bool _ignoreNextExecute;
|
||||||
|
private readonly List<INodeEditorCommand> _commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="CompositeCommand" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commands">The commands to execute.</param>
|
||||||
|
/// <param name="displayName">The display name of the composite command.</param>
|
||||||
|
public CompositeCommand(IEnumerable<INodeEditorCommand> commands, string displayName)
|
||||||
|
{
|
||||||
|
if (commands == null)
|
||||||
|
throw new ArgumentNullException(nameof(commands));
|
||||||
|
_commands = commands.ToList();
|
||||||
|
DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="CompositeCommand" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commands">The commands to execute.</param>
|
||||||
|
/// <param name="displayName">The display name of the composite command.</param>
|
||||||
|
/// <param name="ignoreFirstExecute">Whether or not to ignore the first execute because commands are already executed</param>
|
||||||
|
internal CompositeCommand(IEnumerable<INodeEditorCommand> 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (INodeEditorCommand NodeEditorCommand in _commands)
|
||||||
|
if (NodeEditorCommand is IDisposable disposable)
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of INodeEditorCommand
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string DisplayName { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
if (_ignoreNextExecute)
|
||||||
|
{
|
||||||
|
_ignoreNextExecute = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (INodeEditorCommand NodeEditorCommand in _commands)
|
||||||
|
NodeEditorCommand.Execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Undo()
|
||||||
|
{
|
||||||
|
// Undo in reverse by iterating from the back
|
||||||
|
for (int index = _commands.Count - 1; index >= 0; index--)
|
||||||
|
_commands[index].Undo();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Artemis.Core;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a node editor command that can be used to delete a node.
|
||||||
|
/// </summary>
|
||||||
|
public class DeleteNode : INodeEditorCommand, IDisposable
|
||||||
|
{
|
||||||
|
private readonly INode _node;
|
||||||
|
private readonly INodeScript _nodeScript;
|
||||||
|
private readonly Dictionary<IPin, IReadOnlyList<IPin>> _pinConnections = new();
|
||||||
|
private bool _isRemoved;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="MoveNode" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeScript">The node script the node belongs to.</param>
|
||||||
|
/// <param name="node">The node to delete.</param>
|
||||||
|
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<IPin>? 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<IPin>? connections))
|
||||||
|
foreach (IPin connection in connections)
|
||||||
|
nodePin.ConnectTo(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_pinConnections.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_isRemoved)
|
||||||
|
_nodeScript.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string DisplayName => $"Delete '{_node.Name}' node";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
StoreConnections();
|
||||||
|
_nodeScript.RemoveNode(_node);
|
||||||
|
|
||||||
|
_isRemoved = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Undo()
|
||||||
|
{
|
||||||
|
_nodeScript.AddNode(_node);
|
||||||
|
RestoreConnections();
|
||||||
|
|
||||||
|
_isRemoved = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
using Artemis.Core;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a node editor command that can be used to move a node.
|
||||||
|
/// </summary>
|
||||||
|
public class MoveNode : INodeEditorCommand
|
||||||
|
{
|
||||||
|
private readonly INode _node;
|
||||||
|
private readonly double _originalX;
|
||||||
|
private readonly double _originalY;
|
||||||
|
private readonly double _x;
|
||||||
|
private readonly double _y;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="MoveNode" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">The node to update.</param>
|
||||||
|
/// <param name="x">The new X-position.</param>
|
||||||
|
/// <param name="y">The new Y-position.</param>
|
||||||
|
public MoveNode(INode node, double x, double y)
|
||||||
|
{
|
||||||
|
_node = node;
|
||||||
|
_x = x;
|
||||||
|
_y = y;
|
||||||
|
|
||||||
|
_originalX = node.X;
|
||||||
|
_originalY = node.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string DisplayName => "Move node";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
_node.X = _x;
|
||||||
|
_node.Y = _y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Undo()
|
||||||
|
{
|
||||||
|
_node.X = _originalX;
|
||||||
|
_node.Y = _originalY;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
namespace Artemis.UI.Shared.Services.NodeEditor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a command that can be executed and if needed, undone
|
||||||
|
/// </summary>
|
||||||
|
public interface INodeEditorCommand
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the command
|
||||||
|
/// </summary>
|
||||||
|
string DisplayName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes the command
|
||||||
|
/// </summary>
|
||||||
|
void Execute();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Undoes the command
|
||||||
|
/// </summary>
|
||||||
|
void Undo();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the editor history for the provided node script.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeScript">The node script to get the editor history for.</param>
|
||||||
|
/// <returns>The node editor history of the given node script.</returns>
|
||||||
|
NodeEditorHistory GetHistory(INodeScript nodeScript);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes the provided command and adds it to the history.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeScript">The node script to execute the command upon.</param>
|
||||||
|
/// <param name="command">The command to execute.</param>
|
||||||
|
void ExecuteCommand(INodeScript nodeScript, INodeEditorCommand command);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new command scope which can be used to group undo/redo actions of multiple commands.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeScript">The node script to create the scope for.</param>
|
||||||
|
/// <param name="name">The name of the command scope.</param>
|
||||||
|
/// <returns>The command scope that will group any commands until disposed.</returns>
|
||||||
|
NodeEditorCommandScope CreateCommandScope(INodeScript nodeScript, string name);
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Artemis.Core;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Services.NodeEditor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a scope in which editor commands are executed until disposed.
|
||||||
|
/// </summary>
|
||||||
|
public class NodeEditorCommandScope : IDisposable
|
||||||
|
{
|
||||||
|
private readonly List<INodeEditorCommand> _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<INodeEditorCommand>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the scope.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a read only collection of commands in the scope.
|
||||||
|
/// </summary>
|
||||||
|
public ReadOnlyCollection<INodeEditorCommand> NodeEditorCommands => new(_commands);
|
||||||
|
|
||||||
|
internal void AddCommand(INodeEditorCommand command)
|
||||||
|
{
|
||||||
|
command.Execute();
|
||||||
|
_commands.Add(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_nodeEditorService.StopCommandScope(_nodeScript);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<bool> _canRedo = new();
|
||||||
|
private readonly Subject<bool> _canUndo = new();
|
||||||
|
private readonly Stack<INodeEditorCommand> _redoCommands = new();
|
||||||
|
private readonly Stack<INodeEditorCommand> _undoCommands = new();
|
||||||
|
|
||||||
|
public NodeEditorHistory(INodeScript nodeScript)
|
||||||
|
{
|
||||||
|
NodeScript = nodeScript;
|
||||||
|
|
||||||
|
Execute = ReactiveCommand.Create<INodeEditorCommand>(ExecuteEditorCommand);
|
||||||
|
Undo = ReactiveCommand.Create(ExecuteUndo, CanUndo);
|
||||||
|
Redo = ReactiveCommand.Create(ExecuteRedo, CanRedo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public INodeScript NodeScript { get; }
|
||||||
|
public IObservable<bool> CanUndo => _canUndo.AsObservable().DistinctUntilChanged();
|
||||||
|
public IObservable<bool> CanRedo => _canRedo.AsObservable().DistinctUntilChanged();
|
||||||
|
|
||||||
|
public ReactiveCommand<INodeEditorCommand, Unit> Execute { get; }
|
||||||
|
public ReactiveCommand<Unit, INodeEditorCommand?> Undo { get; }
|
||||||
|
public ReactiveCommand<Unit, INodeEditorCommand?> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="INodeEditorService"/>
|
||||||
|
public class NodeEditorService : INodeEditorService
|
||||||
|
{
|
||||||
|
private readonly IWindowService _windowService;
|
||||||
|
|
||||||
|
public NodeEditorService(IWindowService windowService)
|
||||||
|
{
|
||||||
|
_windowService = windowService;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<INodeScript, NodeEditorHistory> _nodeEditorHistories = new();
|
||||||
|
private readonly Dictionary<INodeScript, NodeEditorCommandScope> _nodeEditorCommandScopes = new();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public NodeEditorHistory GetHistory(INodeScript nodeScript)
|
||||||
|
{
|
||||||
|
if (_nodeEditorHistories.TryGetValue(nodeScript, out NodeEditorHistory? history))
|
||||||
|
return history;
|
||||||
|
|
||||||
|
NodeEditorHistory newHistory = new(nodeScript);
|
||||||
|
_nodeEditorHistories.Add(nodeScript, newHistory);
|
||||||
|
return newHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event EventHandler NodeModified;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the <see cref="NodeModified"/> event
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -90,11 +90,6 @@
|
|||||||
"Splat": "14.1.45"
|
"Splat": "14.1.45"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ArtemisRGB.Plugins.BuildTask": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "1.1.0",
|
|
||||||
"contentHash": "B8A5NkUEzTUgc5M/QACfyjIF5M23EUzSx8A8l/owJtB0bgmij6y/MQW1i/PcS3EDFQRothBOUuC3BCPY5UoRRQ=="
|
|
||||||
},
|
|
||||||
"Avalonia.Angle.Windows.Natives": {
|
"Avalonia.Angle.Windows.Natives": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "2.1.0.2020091801",
|
"resolved": "2.1.0.2020091801",
|
||||||
@ -447,6 +442,11 @@
|
|||||||
"resolved": "5.0.0",
|
"resolved": "5.0.0",
|
||||||
"contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q=="
|
"contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q=="
|
||||||
},
|
},
|
||||||
|
"Microsoft.Extensions.ObjectPool": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "5.0.9",
|
||||||
|
"contentHash": "grj0e6Me0EQsgaurV0fxP0xd8sz8eZVK+Jb816DPzNADHaqXaXJD3xZX9SFjyDl3ykAYvD0y77o5vRd9Hzsk9g=="
|
||||||
|
},
|
||||||
"Microsoft.NETCore.Platforms": {
|
"Microsoft.NETCore.Platforms": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "5.0.0",
|
"resolved": "5.0.0",
|
||||||
@ -565,10 +565,13 @@
|
|||||||
"Ninject": "3.3.3"
|
"Ninject": "3.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"OpenRGB.NET": {
|
"NoStringEvaluating": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "1.7.0",
|
"resolved": "2.2.2",
|
||||||
"contentHash": "12pMEUaeoG8mN707QRO9hdT529+UnqUpwMW1H/gDTMsJrerhJve6Yt5Dnheu1isQB4PWP1wu3IDVbHCchznkiw=="
|
"contentHash": "hJHivPDA1Vxn0CCgOtHKZ3fmldxQuz7VL1J4lEaPTXCf+Vwcx1FDf05mGMh6olYMSxoKimGX8YK2sEoqeH3pnA==",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.ObjectPool": "5.0.9"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ReactiveUI.Validation": {
|
"ReactiveUI.Validation": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
@ -1772,21 +1775,6 @@
|
|||||||
"System.ValueTuple": "4.5.0"
|
"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": {
|
"artemis.storage": {
|
||||||
"type": "Project",
|
"type": "Project",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1799,6 +1787,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Artemis.Core": "1.0.0",
|
"Artemis.Core": "1.0.0",
|
||||||
"Artemis.UI.Shared": "1.0.0",
|
"Artemis.UI.Shared": "1.0.0",
|
||||||
|
"Artemis.VisualScripting": "1.0.0",
|
||||||
"Avalonia": "0.10.13",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.Controls.PanAndZoom": "10.12.0",
|
"Avalonia.Controls.PanAndZoom": "10.12.0",
|
||||||
"Avalonia.Desktop": "0.10.13",
|
"Avalonia.Desktop": "0.10.13",
|
||||||
@ -1836,6 +1825,19 @@
|
|||||||
"ReactiveUI.Validation": "2.2.1",
|
"ReactiveUI.Validation": "2.2.1",
|
||||||
"SkiaSharp": "2.88.0-preview.178"
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
|
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
|
||||||
<ProjectReference Include="..\Artemis.UI.Shared\Artemis.UI.Shared.csproj" />
|
<ProjectReference Include="..\Artemis.UI.Shared\Artemis.UI.Shared.csproj" />
|
||||||
|
<ProjectReference Include="..\Artemis.VisualScripting\Artemis.VisualScripting.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Assets\Images\Logo\bow-black.ico" />
|
<Content Include="Assets\Images\Logo\bow-black.ico" />
|
||||||
|
|||||||
@ -7,6 +7,7 @@ using Artemis.UI.Ninject;
|
|||||||
using Artemis.UI.Screens.Root;
|
using Artemis.UI.Screens.Root;
|
||||||
using Artemis.UI.Shared.Ninject;
|
using Artemis.UI.Shared.Ninject;
|
||||||
using Artemis.UI.Shared.Services.Interfaces;
|
using Artemis.UI.Shared.Services.Interfaces;
|
||||||
|
using Artemis.VisualScripting.Ninject;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
@ -36,6 +37,7 @@ namespace Artemis.UI
|
|||||||
_kernel.Load<CoreModule>();
|
_kernel.Load<CoreModule>();
|
||||||
_kernel.Load<UIModule>();
|
_kernel.Load<UIModule>();
|
||||||
_kernel.Load<SharedUIModule>();
|
_kernel.Load<SharedUIModule>();
|
||||||
|
_kernel.Load<NoStringNinjectModule>();
|
||||||
_kernel.Load(modules);
|
_kernel.Load(modules);
|
||||||
|
|
||||||
_kernel.UseNinjectDependencyResolver();
|
_kernel.UseNinjectDependencyResolver();
|
||||||
|
|||||||
@ -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
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,7 +11,7 @@ public class ColorOpacityConverter : IValueConverter
|
|||||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
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))
|
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;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -90,14 +90,18 @@ namespace Artemis.UI.Ninject.Factories
|
|||||||
ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel);
|
ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface INodeVmFactory
|
public interface INodeVmFactory : IVmFactory
|
||||||
{
|
{
|
||||||
NodeScriptViewModel NodeScriptViewModel(NodeScript nodeScript);
|
NodeScriptViewModel NodeScriptViewModel(NodeScript nodeScript);
|
||||||
NodePickerViewModel NodePickerViewModel(NodeScript nodeScript);
|
NodePickerViewModel NodePickerViewModel(NodeScript nodeScript);
|
||||||
NodeViewModel NodeViewModel(INode node);
|
NodeViewModel NodeViewModel(NodeScript nodeScript, INode node);
|
||||||
InputPinCollectionViewModel<T> InputPinCollectionViewModel<T>(InputPinCollection<T> inputPinCollection);
|
}
|
||||||
InputPinViewModel<T> InputPinViewModel<T>(InputPin<T> inputPin);
|
|
||||||
OutputPinCollectionViewModel<T> OutputPinCollectionViewModel<T>(OutputPinCollection<T> outputPinCollection);
|
public interface INodePinVmFactory
|
||||||
OutputPinViewModel<T> OutputPinViewModel<T>(OutputPin<T> outputPin);
|
{
|
||||||
|
PinCollectionViewModel InputPinCollectionViewModel(PinCollection inputPinCollection);
|
||||||
|
PinViewModel InputPinViewModel(IPin inputPin);
|
||||||
|
PinCollectionViewModel OutputPinCollectionViewModel(PinCollection outputPinCollection);
|
||||||
|
PinViewModel OutputPinViewModel(IPin outputPin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -59,6 +59,7 @@ namespace Artemis.UI.Ninject
|
|||||||
});
|
});
|
||||||
|
|
||||||
Kernel.Bind<IPropertyVmFactory>().ToFactory(() => new LayerPropertyViewModelInstanceProvider());
|
Kernel.Bind<IPropertyVmFactory>().ToFactory(() => new LayerPropertyViewModelInstanceProvider());
|
||||||
|
Kernel.Bind<INodePinVmFactory>().ToFactory(() => new NodePinViewModelInstanceProvider());
|
||||||
|
|
||||||
// Bind all UI services as singletons
|
// Bind all UI services as singletons
|
||||||
Kernel.Bind(x =>
|
Kernel.Bind(x =>
|
||||||
|
|||||||
@ -181,6 +181,7 @@ namespace Artemis.UI.Screens.Root
|
|||||||
_registrationService.RegisterBuiltInDataModelDisplays();
|
_registrationService.RegisterBuiltInDataModelDisplays();
|
||||||
_registrationService.RegisterBuiltInDataModelInputs();
|
_registrationService.RegisterBuiltInDataModelInputs();
|
||||||
_registrationService.RegisterBuiltInPropertyEditors();
|
_registrationService.RegisterBuiltInPropertyEditors();
|
||||||
|
_registrationService.RegisterBuiltInNodeTypes();
|
||||||
|
|
||||||
if (_lifeTime.MainWindow == null)
|
if (_lifeTime.MainWindow == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -3,43 +3,54 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting"
|
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:Class="Artemis.UI.Screens.VisualScripting.NodePickerView"
|
||||||
x:DataType="visualScripting:NodePickerViewModel">
|
x:DataType="visualScripting:NodePickerViewModel"
|
||||||
|
Width="600"
|
||||||
|
Height="400">
|
||||||
<UserControl.Styles>
|
<UserControl.Styles>
|
||||||
<Style Selector="Border.picker-container">
|
<Style Selector="TextBox#SearchBox">
|
||||||
|
<Setter Property="VerticalAlignment" Value="Top"></Setter>
|
||||||
</Style>
|
<Setter Property="InnerRightContent">
|
||||||
<Style Selector="Border.picker-container-hidden">
|
<Template>
|
||||||
<Style.Animations>
|
<StackPanel Orientation="Horizontal">
|
||||||
<Animation Duration="0:0:1">
|
<controls:Button Content=""
|
||||||
<KeyFrame Cue="0%">
|
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||||
<Setter Property="Opacity" Value="1.0" />
|
Classes="AppBarButton"
|
||||||
</KeyFrame>
|
Command="{Binding $parent[TextBox].Clear}"
|
||||||
<KeyFrame Cue="100%">
|
IsVisible="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBox}}, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||||
<Setter Property="Opacity" Value="0.0" />
|
<controls:Button Content=""
|
||||||
<Setter Property="IsVisible" Value="False" />
|
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||||
</KeyFrame>
|
Classes="AppBarButton"
|
||||||
</Animation>
|
IsHitTestVisible="False" />
|
||||||
</Style.Animations>
|
</StackPanel>
|
||||||
</Style>
|
</Template>
|
||||||
<Style Selector="Border.picker-container-visible">
|
</Setter>
|
||||||
<Style.Animations>
|
|
||||||
<Animation Duration="0:0:1">
|
|
||||||
<KeyFrame Cue="0%">
|
|
||||||
<Setter Property="Opacity" Value="0.0" />
|
|
||||||
<Setter Property="IsVisible" Value="True" />
|
|
||||||
</KeyFrame>
|
|
||||||
<KeyFrame Cue="100%">
|
|
||||||
<Setter Property="Opacity" Value="1.0" />
|
|
||||||
</KeyFrame>
|
|
||||||
</Animation>
|
|
||||||
</Style.Animations>
|
|
||||||
</Style>
|
</Style>
|
||||||
</UserControl.Styles>
|
</UserControl.Styles>
|
||||||
<Border Classes="grid picker-container"
|
<Border Classes="picker-container">
|
||||||
Classes.picker-container-hidden="{CompiledBinding !IsVisible}"
|
<Grid RowDefinitions="Auto,*">
|
||||||
Classes.picker-container-visible="{CompiledBinding !IsVisible}">
|
<TextBox Name="SearchBox" Text="{CompiledBinding SearchText}" Margin="0 0 0 15"></TextBox>
|
||||||
<TextBlock>Test</TextBlock>
|
<ListBox Grid.Row="1"
|
||||||
|
Items="{CompiledBinding Nodes}"
|
||||||
|
SelectedItem="{CompiledBinding SelectedNode}"
|
||||||
|
IsVisible="{CompiledBinding Nodes.Count}">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate DataType="core:NodeData">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{CompiledBinding Name}" FontWeight="Bold"></TextBlock>
|
||||||
|
<TextBlock Text="{CompiledBinding Description}"></TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
<StackPanel Grid.Row="1" VerticalAlignment="Center" Spacing="20" IsVisible="{CompiledBinding !Nodes.Count}">
|
||||||
|
<avalonia:MaterialIcon Kind="CloseCircle" Width="64" Height="64"></avalonia:MaterialIcon>
|
||||||
|
<TextBlock Classes="h4" TextAlignment="Center">None of the nodes match your search</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,7 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Mixins;
|
||||||
|
using Avalonia.LogicalTree;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting
|
namespace Artemis.UI.Screens.VisualScripting
|
||||||
{
|
{
|
||||||
@ -10,6 +16,13 @@ namespace Artemis.UI.Screens.VisualScripting
|
|||||||
public NodePickerView()
|
public NodePickerView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
this.WhenActivated(
|
||||||
|
d => ViewModel
|
||||||
|
.WhenAnyValue(vm => vm.IsVisible)
|
||||||
|
.Where(visible => !visible)
|
||||||
|
.Subscribe(_ => this.FindLogicalAncestorOfType<Grid>()?.ContextFlyout?.Hide())
|
||||||
|
.DisposeWith(d)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
@ -17,4 +30,4 @@ namespace Artemis.UI.Screens.VisualScripting
|
|||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,20 +1,51 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Reactive.Disposables;
|
||||||
using System.Text;
|
using System.Reactive.Linq;
|
||||||
using System.Threading.Tasks;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services.NodeEditor;
|
||||||
|
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting;
|
namespace Artemis.UI.Screens.VisualScripting;
|
||||||
|
|
||||||
public class NodePickerViewModel : ActivatableViewModelBase
|
public class NodePickerViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
|
private readonly INodeEditorService _nodeEditorService;
|
||||||
|
private readonly NodeScript _nodeScript;
|
||||||
private readonly INodeService _nodeService;
|
private readonly INodeService _nodeService;
|
||||||
|
|
||||||
private bool _isVisible;
|
private bool _isVisible;
|
||||||
private Point _position;
|
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<NodeData>(_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<NodeData> Nodes { get; }
|
||||||
|
|
||||||
public bool IsVisible
|
public bool IsVisible
|
||||||
{
|
{
|
||||||
@ -28,9 +59,16 @@ public class NodePickerViewModel : ActivatableViewModelBase
|
|||||||
set => RaiseAndSetIfChanged(ref _position, value);
|
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)
|
public void Show(Point position)
|
||||||
@ -43,4 +81,13 @@ public class NodePickerViewModel : ActivatableViewModelBase
|
|||||||
{
|
{
|
||||||
IsVisible = false;
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -2,18 +2,70 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
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"
|
xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.VisualScripting.NodeScriptView"
|
x:Class="Artemis.UI.Screens.VisualScripting.NodeScriptView"
|
||||||
x:DataType="visualScripting:NodeScriptViewModel">
|
x:DataType="visualScripting:NodeScriptViewModel">
|
||||||
<Canvas PointerReleased="InputElement_OnPointerReleased">
|
<UserControl.Styles>
|
||||||
<!-- Cables -->
|
<Style Selector="FlyoutPresenter.node-picker-flyout">
|
||||||
|
<Setter Property="MaxWidth" Value="1000"></Setter>
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
<UserControl.KeyBindings>
|
||||||
|
<KeyBinding Command="{CompiledBinding History.Undo}" Gesture="Ctrl+Z"></KeyBinding>
|
||||||
|
<KeyBinding Command="{CompiledBinding History.Redo}" Gesture="Ctrl+Y"></KeyBinding>
|
||||||
|
</UserControl.KeyBindings>
|
||||||
|
<paz:ZoomBorder Name="ZoomBorder"
|
||||||
|
Stretch="None"
|
||||||
|
ClipToBounds="True"
|
||||||
|
Focusable="True"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Background="{DynamicResource LargeCheckerboardBrush}"
|
||||||
|
ZoomChanged="ZoomBorder_OnZoomChanged">
|
||||||
|
<Grid Name="ContainerGrid" Background="Transparent">
|
||||||
|
<Grid.ContextFlyout>
|
||||||
|
<Flyout FlyoutPresenterClasses="node-picker-flyout">
|
||||||
|
<ContentControl Content="{CompiledBinding NodePickerViewModel}" />
|
||||||
|
</Flyout>
|
||||||
|
</Grid.ContextFlyout>
|
||||||
|
<Grid.Transitions>
|
||||||
|
<Transitions>
|
||||||
|
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2" Easing="CubicEaseOut" />
|
||||||
|
</Transitions>
|
||||||
|
</Grid.Transitions>
|
||||||
|
|
||||||
<!-- Nodes -->
|
<!-- Cables -->
|
||||||
|
<ItemsControl Items="{CompiledBinding CableViewModels}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<Canvas />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.Styles>
|
||||||
|
<Style Selector="ItemsControl > ContentPresenter">
|
||||||
|
<Setter Property="Canvas.Left" Value="{Binding Node.X, TargetNullValue=0}" />
|
||||||
|
<Setter Property="Canvas.Top" Value="{Binding Node.Y, TargetNullValue=0}" />
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.Styles>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
<!-- Flyout -->
|
<!-- Nodes -->
|
||||||
<ContentControl Content="{CompiledBinding NodePickerViewModel}"
|
<ItemsControl Items="{CompiledBinding NodeViewModels}">
|
||||||
Canvas.Left="{CompiledBinding NodePickerViewModel.Position.X}"
|
<ItemsControl.ItemsPanel>
|
||||||
Canvas.Top="{CompiledBinding NodePickerViewModel.Position.Y}"/>
|
<ItemsPanelTemplate>
|
||||||
</Canvas>
|
<Canvas />
|
||||||
</UserControl>
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.Styles>
|
||||||
|
<Style Selector="ItemsControl > ContentPresenter">
|
||||||
|
<Setter Property="Canvas.Left" Value="{Binding Node.X, TargetNullValue=0}" />
|
||||||
|
<Setter Property="Canvas.Top" Value="{Binding Node.Y, TargetNullValue=0}" />
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.Styles>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
</paz:ZoomBorder>
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
@ -1,16 +1,50 @@
|
|||||||
|
using System;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.PanAndZoom;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.Media;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting
|
namespace Artemis.UI.Screens.VisualScripting
|
||||||
{
|
{
|
||||||
public partial class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
|
public partial class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
|
||||||
{
|
{
|
||||||
|
private readonly ZoomBorder _zoomBorder;
|
||||||
|
private readonly Grid _grid;
|
||||||
|
|
||||||
public NodeScriptView()
|
public NodeScriptView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
_zoomBorder = this.Find<ZoomBorder>("ZoomBorder");
|
||||||
|
_grid = this.Find<Grid>("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()
|
private void InitializeComponent()
|
||||||
@ -18,12 +52,9 @@ namespace Artemis.UI.Screens.VisualScripting
|
|||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
private void ZoomBorder_OnZoomChanged(object sender, ZoomChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.InitialPressMouseButton == MouseButton.Right)
|
UpdateZoomBorderBackground();
|
||||||
ViewModel?.ShowNodePicker(e.GetCurrentPoint(this).Position);
|
|
||||||
if (e.InitialPressMouseButton == MouseButton.Left)
|
|
||||||
ViewModel?.HideNodePicker();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7,6 +7,7 @@ using Artemis.Core.Events;
|
|||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services.NodeEditor;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.Mixins;
|
using Avalonia.Controls.Mixins;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
@ -16,15 +17,18 @@ namespace Artemis.UI.Screens.VisualScripting;
|
|||||||
public class NodeScriptViewModel : ActivatableViewModelBase
|
public class NodeScriptViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
private readonly INodeService _nodeService;
|
private readonly INodeService _nodeService;
|
||||||
|
private readonly INodeEditorService _nodeEditorService;
|
||||||
private readonly INodeVmFactory _nodeVmFactory;
|
private readonly INodeVmFactory _nodeVmFactory;
|
||||||
|
|
||||||
public NodeScriptViewModel(NodeScript nodeScript, INodeVmFactory nodeVmFactory, INodeService nodeService)
|
public NodeScriptViewModel(NodeScript nodeScript, INodeVmFactory nodeVmFactory, INodeService nodeService, INodeEditorService nodeEditorService)
|
||||||
{
|
{
|
||||||
_nodeVmFactory = nodeVmFactory;
|
_nodeVmFactory = nodeVmFactory;
|
||||||
_nodeService = nodeService;
|
_nodeService = nodeService;
|
||||||
|
_nodeEditorService = nodeEditorService;
|
||||||
|
|
||||||
NodeScript = nodeScript;
|
NodeScript = nodeScript;
|
||||||
NodePickerViewModel = _nodeVmFactory.NodePickerViewModel(nodeScript);
|
NodePickerViewModel = _nodeVmFactory.NodePickerViewModel(nodeScript);
|
||||||
|
History = _nodeEditorService.GetHistory(NodeScript);
|
||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
@ -38,17 +42,18 @@ public class NodeScriptViewModel : ActivatableViewModelBase
|
|||||||
|
|
||||||
NodeViewModels = new ObservableCollection<NodeViewModel>();
|
NodeViewModels = new ObservableCollection<NodeViewModel>();
|
||||||
foreach (INode nodeScriptNode in NodeScript.Nodes)
|
foreach (INode nodeScriptNode in NodeScript.Nodes)
|
||||||
NodeViewModels.Add(_nodeVmFactory.NodeViewModel(nodeScriptNode));
|
NodeViewModels.Add(_nodeVmFactory.NodeViewModel(NodeScript, nodeScriptNode));
|
||||||
}
|
}
|
||||||
|
|
||||||
public NodeScript NodeScript { get; }
|
public NodeScript NodeScript { get; }
|
||||||
public ObservableCollection<NodeViewModel> NodeViewModels { get; }
|
public ObservableCollection<NodeViewModel> NodeViewModels { get; }
|
||||||
|
public ObservableCollection<CableViewModel> CableViewModels { get; }
|
||||||
public NodePickerViewModel NodePickerViewModel { get; }
|
public NodePickerViewModel NodePickerViewModel { get; }
|
||||||
|
public NodeEditorHistory History { get; }
|
||||||
|
|
||||||
private void HandleNodeAdded(SingleValueEventArgs<INode> eventArgs)
|
private void HandleNodeAdded(SingleValueEventArgs<INode> eventArgs)
|
||||||
{
|
{
|
||||||
NodeViewModels.Add(_nodeVmFactory.NodeViewModel(eventArgs.Value));
|
NodeViewModels.Add(_nodeVmFactory.NodeViewModel(NodeScript, eventArgs.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleNodeRemoved(SingleValueEventArgs<INode> eventArgs)
|
private void HandleNodeRemoved(SingleValueEventArgs<INode> eventArgs)
|
||||||
@ -57,14 +62,4 @@ public class NodeScriptViewModel : ActivatableViewModelBase
|
|||||||
if (toRemove != null)
|
if (toRemove != null)
|
||||||
NodeViewModels.Remove(toRemove);
|
NodeViewModels.Remove(toRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowNodePicker(Point position)
|
|
||||||
{
|
|
||||||
NodePickerViewModel.Show(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HideNodePicker()
|
|
||||||
{
|
|
||||||
NodePickerViewModel.Hide();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -2,7 +2,57 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
x:Class="Artemis.UI.Screens.VisualScripting.NodeView">
|
xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting"
|
||||||
Welcome to Avalonia!
|
mc:Ignorable="d" d:DesignWidth="250" d:DesignHeight="150"
|
||||||
</UserControl>
|
x:Class="Artemis.UI.Screens.VisualScripting.NodeView"
|
||||||
|
x:DataType="visualScripting:NodeViewModel">
|
||||||
|
<UserControl.Styles>
|
||||||
|
<Style Selector="Border.node-container">
|
||||||
|
<Setter Property="CornerRadius" Value="6" />
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ContentDialogBackground}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource CardStrokeColorDefaultBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ContentControl#CustomViewModelContainer">
|
||||||
|
<Setter Property="Margin" Value="20 0"></Setter>
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
<Border Classes="node-container">
|
||||||
|
<Grid RowDefinitions="Auto,*">
|
||||||
|
<Border Grid.Row="0" Background="{DynamicResource TaskDialogHeaderBackground}" CornerRadius="6 6 0 0">
|
||||||
|
<Grid Classes="node-header"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
ColumnDefinitions="*,Auto">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
TextAlignment="Center"
|
||||||
|
Margin="5"
|
||||||
|
Text="{CompiledBinding Node.Name}"
|
||||||
|
ToolTip.Tip="{CompiledBinding Node.Description}">
|
||||||
|
</TextBlock>
|
||||||
|
<Button VerticalAlignment="Center"
|
||||||
|
Classes="icon-button icon-button-small"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="5"
|
||||||
|
Command="{CompiledBinding DeleteNode}">
|
||||||
|
<avalonia:MaterialIcon Kind="Close"></avalonia:MaterialIcon>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1" ColumnDefinitions="Auto,*,Auto" Margin="5">
|
||||||
|
<StackPanel Grid.Column="0">
|
||||||
|
<ItemsControl Items="{CompiledBinding InputPinViewModels}" />
|
||||||
|
<ItemsControl Items="{CompiledBinding InputPinCollectionViewModels}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<ContentControl Name="CustomViewModelContainer" Grid.Column="1" Content="{CompiledBinding CustomNodeViewModel}" IsVisible="{CompiledBinding CustomNodeViewModel}" />
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="2">
|
||||||
|
<ItemsControl Items="{CompiledBinding OutputPinViewModels}" />
|
||||||
|
<ItemsControl Items="{CompiledBinding OutputPinCollectionViewModels}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
@ -1,20 +1,36 @@
|
|||||||
|
using System;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting
|
namespace Artemis.UI.Screens.VisualScripting;
|
||||||
{
|
|
||||||
public partial class NodeView : ReactiveUserControl<NodeViewModel>
|
|
||||||
{
|
|
||||||
public NodeView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent()
|
public class NodeView : ReactiveUserControl<NodeViewModel>
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
public NodeView()
|
||||||
}
|
{
|
||||||
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#region Overrides of Layoutable
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 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;
|
namespace Artemis.UI.Screens.VisualScripting;
|
||||||
|
|
||||||
public class NodeViewModel : ActivatableViewModelBase
|
public class NodeViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
private Point _position;
|
private readonly NodeScript _nodeScript;
|
||||||
|
private readonly INodeEditorService _nodeEditorService;
|
||||||
|
|
||||||
public NodeViewModel(INode node)
|
private ICustomNodeViewModel? _customNodeViewModel;
|
||||||
|
private ReactiveCommand<Unit, Unit>? _deleteNode;
|
||||||
|
private ObservableAsPropertyHelper<bool>? _isStaticNode;
|
||||||
|
|
||||||
|
public NodeViewModel(NodeScript nodeScript, INode node, INodePinVmFactory nodePinVmFactory, INodeEditorService nodeEditorService)
|
||||||
{
|
{
|
||||||
|
_nodeScript = nodeScript;
|
||||||
|
_nodeEditorService = nodeEditorService;
|
||||||
Node = node;
|
Node = node;
|
||||||
|
|
||||||
|
SourceList<IPin> 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<PinViewModel> inputPins).Subscribe();
|
||||||
|
nodePins.Connect().Filter(n => n.Direction == PinDirection.Output).Transform(nodePinVmFactory.OutputPinViewModel).Bind(out ReadOnlyObservableCollection<PinViewModel> outputPins).Subscribe();
|
||||||
|
InputPinViewModels = inputPins;
|
||||||
|
OutputPinViewModels = outputPins;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsStaticNode => _isStaticNode?.Value ?? true;
|
||||||
|
|
||||||
public INode Node { get; }
|
public INode Node { get; }
|
||||||
|
public ReadOnlyObservableCollection<PinViewModel> InputPinViewModels { get; }
|
||||||
|
public ReadOnlyObservableCollection<PinCollectionViewModel> InputPinCollectionViewModels { get; }
|
||||||
|
public ReadOnlyObservableCollection<PinViewModel> OutputPinViewModels { get; }
|
||||||
|
public ReadOnlyObservableCollection<PinCollectionViewModel> OutputPinCollectionViewModels { get; }
|
||||||
|
|
||||||
public Point Position
|
public ICustomNodeViewModel? CustomNodeViewModel
|
||||||
{
|
{
|
||||||
get => _position;
|
get => _customNodeViewModel;
|
||||||
set => RaiseAndSetIfChanged(ref _position, value);
|
set => RaiseAndSetIfChanged(ref _customNodeViewModel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReactiveCommand<Unit, Unit>? DeleteNode
|
||||||
|
{
|
||||||
|
get => _deleteNode;
|
||||||
|
set => RaiseAndSetIfChanged(ref _deleteNode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteDeleteNode()
|
||||||
|
{
|
||||||
|
_nodeEditorService.ExecuteCommand(_nodeScript, new DeleteNode(_nodeScript, Node));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,7 +2,30 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
xmlns:pins="clr-namespace:Artemis.UI.Screens.VisualScripting.Pins"
|
||||||
x:Class="Artemis.UI.Screens.VisualScripting.Pins.InputPinView">
|
mc:Ignorable="d"
|
||||||
Welcome to Avalonia!
|
d:DesignWidth="200"
|
||||||
</UserControl>
|
x:Class="Artemis.UI.Screens.VisualScripting.Pins.InputPinView"
|
||||||
|
x:DataType="pins:PinViewModel">
|
||||||
|
<UserControl.Styles>
|
||||||
|
<StyleInclude Source="/Screens/VisualScripting/VisualScripting.axaml" />
|
||||||
|
<Style Selector="StackPanel#PinContainer Border#VisualPinPoint">
|
||||||
|
<Setter Property="Background">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{CompiledBinding DarkenedPinColor}"></SolidColorBrush>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="BorderBrush">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{CompiledBinding PinColor}"></SolidColorBrush>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
<StackPanel Name="PinContainer" Orientation="Horizontal" Spacing="6">
|
||||||
|
<Border Name="PinPoint">
|
||||||
|
<Border Name="VisualPinPoint" />
|
||||||
|
</Border>
|
||||||
|
<TextBlock Name="PinName" VerticalAlignment="Center" Text="{CompiledBinding Pin.Name}" />
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
|
|
||||||
@ -6,7 +7,7 @@ public class InputPinViewModel<T> : PinViewModel
|
|||||||
{
|
{
|
||||||
public InputPin<T> InputPin { get; }
|
public InputPin<T> InputPin { get; }
|
||||||
|
|
||||||
public InputPinViewModel(InputPin<T> inputPin) : base(inputPin)
|
public InputPinViewModel(InputPin<T> inputPin, INodeService nodeService) : base(inputPin, nodeService)
|
||||||
{
|
{
|
||||||
InputPin = inputPin;
|
InputPin = inputPin;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,30 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
xmlns:pins="clr-namespace:Artemis.UI.Screens.VisualScripting.Pins"
|
||||||
x:Class="Artemis.UI.Screens.VisualScripting.Pins.OutputPinView">
|
mc:Ignorable="d"
|
||||||
Welcome to Avalonia!
|
d:DesignWidth="200"
|
||||||
</UserControl>
|
x:Class="Artemis.UI.Screens.VisualScripting.Pins.OutputPinView"
|
||||||
|
x:DataType="pins:PinViewModel">
|
||||||
|
<UserControl.Styles>
|
||||||
|
<StyleInclude Source="/Screens/VisualScripting/VisualScripting.axaml" />
|
||||||
|
<Style Selector="StackPanel#PinContainer Border#VisualPinPoint">
|
||||||
|
<Setter Property="Background">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{CompiledBinding DarkenedPinColor}" />
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="BorderBrush">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{CompiledBinding PinColor}" />
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
<StackPanel Name="PinContainer" Orientation="Horizontal" Spacing="6">
|
||||||
|
<TextBlock Name="PinName" VerticalAlignment="Center" Text="{CompiledBinding Pin.Name}" />
|
||||||
|
<Border Name="PinPoint">
|
||||||
|
<Border Name="VisualPinPoint" />
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
|
|
||||||
@ -6,7 +7,7 @@ public class OutputPinViewModel<T> : PinViewModel
|
|||||||
{
|
{
|
||||||
public OutputPin<T> OutputPin { get; }
|
public OutputPin<T> OutputPin { get; }
|
||||||
|
|
||||||
public OutputPinViewModel(OutputPin<T> outputPin) : base(outputPin)
|
public OutputPinViewModel(OutputPin<T> outputPin, INodeService nodeService) : base(outputPin, nodeService)
|
||||||
{
|
{
|
||||||
OutputPin = outputPin;
|
OutputPin = outputPin;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,22 @@
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
|
using Avalonia.Media;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
|
|
||||||
public abstract class PinViewModel : ActivatableViewModelBase
|
public abstract class PinViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
protected PinViewModel(IPin pin)
|
protected PinViewModel(IPin pin, INodeService nodeService)
|
||||||
{
|
{
|
||||||
Pin = pin;
|
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 IPin Pin { get; }
|
||||||
|
public Color PinColor { get; }
|
||||||
|
public Color DarkenedPinColor { get; }
|
||||||
}
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Design.PreviewWith>
|
||||||
|
<Border Padding="20">
|
||||||
|
<!-- Add Controls for Previewer Here -->
|
||||||
|
</Border>
|
||||||
|
</Design.PreviewWith>
|
||||||
|
|
||||||
|
<Style Selector="StackPanel#PinContainer">
|
||||||
|
<Setter Property="Height" Value="24" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="StackPanel#PinContainer Border#PinPoint">
|
||||||
|
<Setter Property="Width" Value="13" />
|
||||||
|
<Setter Property="Height" Value="13" />
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="StackPanel#PinContainer Border#VisualPinPoint">
|
||||||
|
<Setter Property="Width" Value="11" />
|
||||||
|
<Setter Property="Height" Value="11" />
|
||||||
|
<Setter Property="Margin" Value="2" />
|
||||||
|
<Setter Property="CornerRadius" Value="6" />
|
||||||
|
<Setter Property="Background" Value="DarkRed" />
|
||||||
|
<Setter Property="BorderBrush" Value="Red" />
|
||||||
|
<Setter Property="BorderThickness" Value="2" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="StackPanel#PinContainer TextBlock#PinName">
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Property="FontSize" Value="11" />
|
||||||
|
</Style>
|
||||||
|
</Styles>
|
||||||
@ -12,49 +12,54 @@
|
|||||||
x:Class="Artemis.UI.Screens.Workshop.WorkshopView"
|
x:Class="Artemis.UI.Screens.Workshop.WorkshopView"
|
||||||
x:DataType="workshop:WorkshopViewModel">
|
x:DataType="workshop:WorkshopViewModel">
|
||||||
<Border Classes="router-container">
|
<Border Classes="router-container">
|
||||||
<StackPanel Margin="12">
|
<StackPanel Margin="12" Spacing="5">
|
||||||
<TextBlock>Workshop!! :3</TextBlock>
|
<Border Classes="card">
|
||||||
<Border Classes="card" Margin="0 12">
|
<StackPanel Spacing="5">
|
||||||
<StackPanel Spacing="5">
|
<TextBlock Classes="h4">Nodes tests</TextBlock>
|
||||||
<TextBlock Classes="h4">Notification tests</TextBlock>
|
<ContentControl Content="{CompiledBinding VisualEditorViewModel}" HorizontalAlignment="Stretch" Height="800"></ContentControl>
|
||||||
<Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Informational}">
|
</StackPanel>
|
||||||
Notification test (default)
|
</Border>
|
||||||
</Button>
|
<Border Classes="card">
|
||||||
<Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Warning}">
|
<StackPanel Spacing="5">
|
||||||
Notification test (warning)
|
<TextBlock Classes="h4">Notification tests</TextBlock>
|
||||||
</Button>
|
<Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Informational}">
|
||||||
<Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Error}">
|
Notification test (default)
|
||||||
Notification test (error)
|
</Button>
|
||||||
</Button>
|
<Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Warning}">
|
||||||
<Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Success}">
|
Notification test (warning)
|
||||||
Notification test (success)
|
</Button>
|
||||||
</Button>
|
<Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Error}">
|
||||||
|
Notification test (error)
|
||||||
|
</Button>
|
||||||
|
<Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Success}">
|
||||||
|
Notification test (success)
|
||||||
|
</Button>
|
||||||
|
|
||||||
<controls:HotkeyBox Watermark="Some hotkey" Width="250" HorizontalAlignment="Left" />
|
<controls:HotkeyBox Watermark="Some hotkey" Width="250" HorizontalAlignment="Left" />
|
||||||
|
|
||||||
<controls1:NumberBox
|
<controls1:NumberBox
|
||||||
attachedProperties:NumberBoxAssist.PrefixText="$"
|
attachedProperties:NumberBoxAssist.PrefixText="$"
|
||||||
attachedProperties:NumberBoxAssist.SuffixText="%"></controls1:NumberBox>
|
attachedProperties:NumberBoxAssist.SuffixText="%"></controls1:NumberBox>
|
||||||
|
|
||||||
<TextBox
|
<TextBox
|
||||||
attachedProperties:TextBoxAssist.PrefixText="$"
|
attachedProperties:TextBoxAssist.PrefixText="$"
|
||||||
attachedProperties:TextBoxAssist.SuffixText="%"></TextBox>
|
attachedProperties:TextBoxAssist.SuffixText="%"></TextBox>
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||||
<Border Classes="card" Cursor="{CompiledBinding Cursor}">
|
<Border Classes="card" Cursor="{CompiledBinding Cursor}">
|
||||||
<TextBlock Text="{CompiledBinding SelectedCursor}"></TextBlock>
|
<TextBlock Text="{CompiledBinding SelectedCursor}"></TextBlock>
|
||||||
</Border>
|
</Border>
|
||||||
<controls:EnumComboBox Value="{CompiledBinding SelectedCursor}"></controls:EnumComboBox>
|
<controls:EnumComboBox Value="{CompiledBinding SelectedCursor}"></controls:EnumComboBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Button Command="{Binding CreateRandomGradient}">
|
<Button Command="{Binding CreateRandomGradient}">
|
||||||
Create random gradient
|
Create random gradient
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|
||||||
<gradientPicker:GradientPickerButton ColorGradient="{CompiledBinding ColorGradient}" IsCompact="True"/>
|
<gradientPicker:GradientPickerButton ColorGradient="{CompiledBinding ColorGradient}" IsCompact="True"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -2,6 +2,8 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Ninject.Factories;
|
||||||
|
using Artemis.UI.Screens.VisualScripting;
|
||||||
using Artemis.UI.Shared.Services.Builders;
|
using Artemis.UI.Shared.Services.Builders;
|
||||||
using Artemis.UI.Shared.Services.Interfaces;
|
using Artemis.UI.Shared.Services.Interfaces;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
@ -25,15 +27,19 @@ namespace Artemis.UI.Screens.Workshop
|
|||||||
new ColorGradientStop(new SKColor(0xFF00FCCC), 1f),
|
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;
|
_notificationService = notificationService;
|
||||||
_cursor = this.WhenAnyValue(vm => vm.SelectedCursor).Select(c => new Cursor(c)).ToProperty(this, vm => vm.Cursor);
|
_cursor = this.WhenAnyValue(vm => vm.SelectedCursor).Select(c => new Cursor(c)).ToProperty(this, vm => vm.Cursor);
|
||||||
|
|
||||||
DisplayName = "Workshop";
|
DisplayName = "Workshop";
|
||||||
ShowNotification = ReactiveCommand.Create<NotificationSeverity>(ExecuteShowNotification);
|
ShowNotification = ReactiveCommand.Create<NotificationSeverity>(ExecuteShowNotification);
|
||||||
|
|
||||||
|
VisualEditorViewModel = nodeVmFactory.NodeScriptViewModel(new NodeScript<bool>("Test script", "A test script"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NodeScriptViewModel VisualEditorViewModel { get; }
|
||||||
|
|
||||||
public ReactiveCommand<NotificationSeverity, Unit> ShowNotification { get; set; }
|
public ReactiveCommand<NotificationSeverity, Unit> ShowNotification { get; set; }
|
||||||
|
|
||||||
public StandardCursorType SelectedCursor
|
public StandardCursorType SelectedCursor
|
||||||
|
|||||||
@ -7,5 +7,6 @@
|
|||||||
void RegisterBuiltInPropertyEditors();
|
void RegisterBuiltInPropertyEditors();
|
||||||
void RegisterControllers();
|
void RegisterControllers();
|
||||||
void ApplyPreferredGraphicsContext();
|
void ApplyPreferredGraphicsContext();
|
||||||
|
void RegisterBuiltInNodeTypes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.DefaultTypes.PropertyInput;
|
using Artemis.UI.DefaultTypes.PropertyInput;
|
||||||
@ -6,9 +9,11 @@ using Artemis.UI.Services.Interfaces;
|
|||||||
using Artemis.UI.Shared.Providers;
|
using Artemis.UI.Shared.Providers;
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
using Artemis.UI.Shared.Services.PropertyInput;
|
using Artemis.UI.Shared.Services.PropertyInput;
|
||||||
|
using Artemis.VisualScripting.Nodes;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using Ninject;
|
using Ninject;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.UI.Services;
|
namespace Artemis.UI.Services;
|
||||||
|
|
||||||
@ -17,13 +22,15 @@ public class RegistrationService : IRegistrationService
|
|||||||
private readonly IKernel _kernel;
|
private readonly IKernel _kernel;
|
||||||
private readonly IInputService _inputService;
|
private readonly IInputService _inputService;
|
||||||
private readonly IPropertyInputService _propertyInputService;
|
private readonly IPropertyInputService _propertyInputService;
|
||||||
|
private readonly INodeService _nodeService;
|
||||||
private bool _registeredBuiltInPropertyEditors;
|
private bool _registeredBuiltInPropertyEditors;
|
||||||
|
|
||||||
public RegistrationService(IKernel kernel, IInputService inputService, IPropertyInputService propertyInputService, IProfileEditorService profileEditorService, IEnumerable<IToolViewModel> toolViewModels)
|
public RegistrationService(IKernel kernel, IInputService inputService, IPropertyInputService propertyInputService, IProfileEditorService profileEditorService, INodeService nodeService, IEnumerable<IToolViewModel> toolViewModels)
|
||||||
{
|
{
|
||||||
_kernel = kernel;
|
_kernel = kernel;
|
||||||
_inputService = inputService;
|
_inputService = inputService;
|
||||||
_propertyInputService = propertyInputService;
|
_propertyInputService = propertyInputService;
|
||||||
|
_nodeService = nodeService;
|
||||||
|
|
||||||
profileEditorService.Tools.AddRange(toolViewModels);
|
profileEditorService.Tools.AddRange(toolViewModels);
|
||||||
CreateCursorResources();
|
CreateCursorResources();
|
||||||
@ -75,4 +82,18 @@ public class RegistrationService : IRegistrationService
|
|||||||
public void ApplyPreferredGraphicsContext()
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -481,6 +481,11 @@
|
|||||||
"resolved": "5.0.0",
|
"resolved": "5.0.0",
|
||||||
"contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q=="
|
"contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q=="
|
||||||
},
|
},
|
||||||
|
"Microsoft.Extensions.ObjectPool": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "5.0.9",
|
||||||
|
"contentHash": "grj0e6Me0EQsgaurV0fxP0xd8sz8eZVK+Jb816DPzNADHaqXaXJD3xZX9SFjyDl3ykAYvD0y77o5vRd9Hzsk9g=="
|
||||||
|
},
|
||||||
"Microsoft.NETCore.Platforms": {
|
"Microsoft.NETCore.Platforms": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "5.0.0",
|
"resolved": "5.0.0",
|
||||||
@ -599,6 +604,14 @@
|
|||||||
"Ninject": "3.3.3"
|
"Ninject": "3.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"NoStringEvaluating": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.2.2",
|
||||||
|
"contentHash": "hJHivPDA1Vxn0CCgOtHKZ3fmldxQuz7VL1J4lEaPTXCf+Vwcx1FDf05mGMh6olYMSxoKimGX8YK2sEoqeH3pnA==",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.ObjectPool": "5.0.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
"RGB.NET.Presets": {
|
"RGB.NET.Presets": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "1.0.0-prerelease7",
|
"resolved": "1.0.0-prerelease7",
|
||||||
@ -1783,6 +1796,19 @@
|
|||||||
"ReactiveUI.Validation": "2.2.1",
|
"ReactiveUI.Validation": "2.2.1",
|
||||||
"SkiaSharp": "2.88.0-preview.178"
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,74 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Nodes\Color\CustomViews\StaticSKColorValueNodeCustomView.xaml" />
|
||||||
|
<None Remove="Nodes\CustomViews\EnumEqualsNodeCustomView.xaml" />
|
||||||
|
<None Remove="Nodes\CustomViews\LayerPropertyNodeCustomView.xaml" />
|
||||||
|
<None Remove="Nodes\CustomViews\StaticNumericValueNodeCustomView.xaml" />
|
||||||
|
<None Remove="Nodes\CustomViews\StaticStringValueNodeCustomView.xaml" />
|
||||||
|
<None Remove="Nodes\DataModel\CustomViews\DataModelEventNodeCustomView.xaml" />
|
||||||
|
<None Remove="Nodes\DataModel\CustomViews\DataModelNodeCustomView.xaml" />
|
||||||
|
<None Remove="Nodes\Easing\CustomViews\EasingTypeNodeCustomView.xaml" />
|
||||||
|
<None Remove="Nodes\Maths\CustomViews\MathExpressionNodeCustomView.xaml" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia" Version="0.10.13" />
|
||||||
|
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
|
||||||
|
<PackageReference Include="Ninject" Version="3.3.4" />
|
||||||
|
<PackageReference Include="NoStringEvaluating" Version="2.2.2" />
|
||||||
|
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
||||||
|
<PackageReference Include="SkiaSharp" Version="2.88.0-preview.178" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Include="Nodes\Color\CustomViews\StaticSKColorValueNodeCustomView.xaml">
|
||||||
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
<Page Include="Nodes\CustomViews\EnumEqualsNodeCustomView.xaml">
|
||||||
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
<Page Include="Nodes\CustomViews\LayerPropertyNodeCustomView.xaml">
|
||||||
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
<Page Include="Nodes\CustomViews\StaticNumericValueNodeCustomView.xaml">
|
||||||
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
<Page Include="Nodes\CustomViews\StaticStringValueNodeCustomView.xaml">
|
||||||
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
<Page Include="Nodes\DataModel\CustomViews\DataModelEventNodeCustomView.xaml">
|
||||||
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
<Page Include="Nodes\DataModel\CustomViews\DataModelNodeCustomView.xaml">
|
||||||
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
<Page Include="Nodes\Easing\CustomViews\EasingTypeNodeCustomView.xaml">
|
||||||
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
<Page Include="Nodes\Maths\CustomViews\MathExpressionNodeCustomView.xaml">
|
||||||
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\Artemis.UI.Shared\Artemis.UI.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@ -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<ObjectPool<Stack<InternalEvaluatorValue>>>()
|
||||||
|
.ToConstant(ObjectPool.Create<Stack<InternalEvaluatorValue>>())
|
||||||
|
.InSingletonScope();
|
||||||
|
|
||||||
|
Bind<ObjectPool<List<InternalEvaluatorValue>>>()
|
||||||
|
.ToConstant(ObjectPool.Create<List<InternalEvaluatorValue>>())
|
||||||
|
.InSingletonScope();
|
||||||
|
|
||||||
|
Bind<ObjectPool<ExtraTypeIdContainer>>()
|
||||||
|
.ToConstant(ObjectPool.Create<ExtraTypeIdContainer>())
|
||||||
|
.InSingletonScope();
|
||||||
|
|
||||||
|
// Parser
|
||||||
|
Bind<IFormulaCache>().To<FormulaCache>().InSingletonScope();
|
||||||
|
Bind<IFunctionReader>().To<FunctionReader>().InSingletonScope();
|
||||||
|
Bind<IFormulaParser>().To<FormulaParser>().InSingletonScope();
|
||||||
|
|
||||||
|
// Checker
|
||||||
|
Bind<IFormulaChecker>().To<FormulaChecker>().InSingletonScope();
|
||||||
|
|
||||||
|
// Evaluator
|
||||||
|
Bind<INoStringEvaluator>().To<NoStringEvaluator>().InSingletonScope();
|
||||||
|
|
||||||
|
// If needed
|
||||||
|
InjectUserDefinedFunctions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InjectUserDefinedFunctions()
|
||||||
|
{
|
||||||
|
IFunctionReader functionReader = (IFunctionReader) Kernel!.GetService(typeof(IFunctionReader));
|
||||||
|
NoStringFunctionsInitializer.InitializeFunctions(functionReader, typeof(NoStringNinjectModule));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
299
src/Avalonia/Artemis.VisualScripting/Nodes/BoolOperations.cs
Normal file
299
src/Avalonia/Artemis.VisualScripting/Nodes/BoolOperations.cs
Normal file
@ -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<object>();
|
||||||
|
Input2 = CreateInputPin<object>();
|
||||||
|
Result = CreateOutputPin<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#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<object> Input1 { get; }
|
||||||
|
public InputPin<object> Input2 { get; }
|
||||||
|
|
||||||
|
public OutputPin<bool> 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<object>();
|
||||||
|
Input2 = CreateInputPin<object>();
|
||||||
|
Result = CreateOutputPin<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#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<object> Input1 { get; }
|
||||||
|
public InputPin<object> Input2 { get; }
|
||||||
|
|
||||||
|
public OutputPin<bool> 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<object>();
|
||||||
|
Input2 = CreateInputPin<object>();
|
||||||
|
Result = CreateOutputPin<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#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<object> Input1 { get; }
|
||||||
|
public InputPin<object> Input2 { get; }
|
||||||
|
|
||||||
|
public OutputPin<bool> 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<bool>();
|
||||||
|
Output = CreateOutputPin<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
Output.Value = !Input.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
public InputPin<bool> Input { get; }
|
||||||
|
public OutputPin<bool> 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<bool>();
|
||||||
|
Result = CreateOutputPin<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
Result.Value = Input.Values.All(v => v);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
public InputPinCollection<bool> Input { get; set; }
|
||||||
|
public OutputPin<bool> 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<bool>();
|
||||||
|
Result = CreateOutputPin<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
Result.Value = Input.Values.Any(v => v);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
public InputPinCollection<bool> Input { get; set; }
|
||||||
|
public OutputPin<bool> 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<bool>();
|
||||||
|
Result = CreateOutputPin<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
Result.Value = Input.Values.Count(v => v) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
public InputPinCollection<bool> Input { get; set; }
|
||||||
|
public OutputPin<bool> 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<Enum, EnumEqualsNodeCustomViewModel>
|
||||||
|
{
|
||||||
|
public EnumEqualsNode() : base("Enum Equals", "Determines the equality between an input and a selected enum value")
|
||||||
|
{
|
||||||
|
InputPin = CreateInputPin<Enum>();
|
||||||
|
OutputPin = CreateOutputPin<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputPin<Enum> InputPin { get; }
|
||||||
|
public OutputPin<bool> OutputPin { get; }
|
||||||
|
|
||||||
|
#region Overrides of Node
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
OutputPin.Value = InputPin.Value != null && InputPin.Value.Equals(Storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -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<SKColor>("Color");
|
||||||
|
Percentage = CreateInputPin<Numeric>("%");
|
||||||
|
Output = CreateOutputPin<SKColor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputPin<SKColor> Input { get; }
|
||||||
|
public InputPin<Numeric> Percentage { get; }
|
||||||
|
public OutputPin<SKColor> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
<UserControl x:Class="Artemis.VisualScripting.Nodes.Color.CustomViews.StaticSKColorValueNodeCustomView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<shared:SKColorToColorConverter x:Key="SKColorToColorConverter" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<shared:ColorPicker VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Color="{Binding Node.Storage, Converter={StaticResource SKColorToColorConverter}}" />
|
||||||
|
</UserControl>
|
||||||
@ -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<SKColor>("Color");
|
||||||
|
Percentage = CreateInputPin<Numeric>("%");
|
||||||
|
Output = CreateOutputPin<SKColor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputPin<SKColor> Input { get; }
|
||||||
|
public InputPin<Numeric> Percentage { get; }
|
||||||
|
public OutputPin<SKColor> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<SKColor>("Color");
|
||||||
|
Percentage = CreateInputPin<Numeric>("%");
|
||||||
|
Output = CreateOutputPin<SKColor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputPin<SKColor> Input { get; }
|
||||||
|
public InputPin<Numeric> Percentage { get; }
|
||||||
|
public OutputPin<SKColor> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<Numeric>("H");
|
||||||
|
S = CreateInputPin<Numeric>("S");
|
||||||
|
L = CreateInputPin<Numeric>("L");
|
||||||
|
Output = CreateOutputPin<SKColor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputPin<Numeric> H { get; set; }
|
||||||
|
public InputPin<Numeric> S { get; set; }
|
||||||
|
public InputPin<Numeric> L { get; set; }
|
||||||
|
public OutputPin<SKColor> Output { get; }
|
||||||
|
|
||||||
|
#region Overrides of Node
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
Output.Value = SKColor.FromHsl(H.Value, S.Value, L.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -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<SKColor>();
|
||||||
|
Output = CreateOutputPin<SKColor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputPin<SKColor> Input { get; }
|
||||||
|
public OutputPin<SKColor> 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<SKColor>("Color");
|
||||||
|
Amount = CreateInputPin<Numeric>("Amount");
|
||||||
|
Output = CreateOutputPin<SKColor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputPin<SKColor> Input { get; }
|
||||||
|
public InputPin<Numeric> Amount { get; }
|
||||||
|
public OutputPin<SKColor> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<SKColor>("Color");
|
||||||
|
Percentage = CreateInputPin<Numeric>("%");
|
||||||
|
Output = CreateOutputPin<SKColor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputPin<SKColor> Input { get; }
|
||||||
|
public InputPin<Numeric> Percentage { get; }
|
||||||
|
public OutputPin<SKColor> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<SKColor, StaticSKColorValueNodeCustomViewModel>
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
public StaticSKColorValueNode()
|
||||||
|
: base("Color", "Outputs a configurable color value.")
|
||||||
|
{
|
||||||
|
Output = CreateOutputPin<SKColor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
public OutputPin<SKColor> Output { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
Output.Value = Storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -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<SKColor>("Values", 2);
|
||||||
|
Sum = CreateOutputPin<SKColor>("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<SKColor> Values { get; }
|
||||||
|
|
||||||
|
public OutputPin<SKColor> Sum { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
80
src/Avalonia/Artemis.VisualScripting/Nodes/ConvertNodes.cs
Normal file
80
src/Avalonia/Artemis.VisualScripting/Nodes/ConvertNodes.cs
Normal file
@ -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<object>();
|
||||||
|
String = CreateOutputPin<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
String.Value = Input.Value?.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
public InputPin<object> Input { get; }
|
||||||
|
|
||||||
|
public OutputPin<string> 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<object>();
|
||||||
|
Output = CreateOutputPin<Numeric>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
public InputPin<object> Input { get; }
|
||||||
|
|
||||||
|
public OutputPin<Numeric> 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
|
||||||
|
}
|
||||||
@ -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<IPin> e)
|
||||||
|
{
|
||||||
|
EnumValues.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InputPinOnPinConnected(object sender, SingleValueEventArgs<IPin> e)
|
||||||
|
{
|
||||||
|
EnumValues.Clear();
|
||||||
|
if (_node.InputPin.Value != null && _node.InputPin.Value.GetType().IsEnum)
|
||||||
|
EnumValues.AddRange(EnumUtilities.GetAllValuesAndDescriptions(_node.InputPin.Value.GetType()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<RenderProfileElement> ProfileElements { get; } = new();
|
||||||
|
|
||||||
|
// public RenderProfileElement SelectedProfileElement
|
||||||
|
// {
|
||||||
|
// get => _selectedProfileElement;
|
||||||
|
// set
|
||||||
|
// {
|
||||||
|
// if (!SetAndNotify(ref _selectedProfileElement, value)) return;
|
||||||
|
// _node.ChangeProfileElement(_selectedProfileElement);
|
||||||
|
// GetLayerProperties();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
public ObservableCollection<ILayerProperty> 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<RenderProfileElement> 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();
|
||||||
|
// }
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
<UserControl x:Class="Artemis.VisualScripting.Nodes.CustomViews.EnumEqualsNodeCustomView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800">
|
||||||
|
<Grid>
|
||||||
|
<ComboBox Width="140"
|
||||||
|
materialDesign:ComboBoxAssist.ClassicMode="True"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
SelectedValue="{Binding Node.Storage}"
|
||||||
|
ItemsSource="{Binding EnumValues}"
|
||||||
|
SelectedValuePath="Value"
|
||||||
|
DisplayMemberPath="Description">
|
||||||
|
<ComboBox.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<VirtualizingStackPanel />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ComboBox.ItemsPanel>
|
||||||
|
</ComboBox>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
<UserControl x:Class="Artemis.VisualScripting.Nodes.CustomViews.LayerPropertyNodeCustomView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800">
|
||||||
|
<StackPanel>
|
||||||
|
<ComboBox Margin="8 0"
|
||||||
|
ItemsSource="{Binding ProfileElements}"
|
||||||
|
SelectedValue="{Binding SelectedProfileElement}" />
|
||||||
|
<ComboBox Margin="8 0"
|
||||||
|
ItemsSource="{Binding LayerProperties}"
|
||||||
|
SelectedValue="{Binding SelectedLayerProperty}"
|
||||||
|
DisplayMemberPath="PropertyDescription.Name" />
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
<UserControl x:Class="Artemis.VisualScripting.Nodes.CustomViews.StaticNumericValueNodeCustomView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<shared:StringToNumericConverter x:Key="StringToNumericConverter" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<TextBox VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Text="{Binding Node.Storage, Converter={StaticResource StringToNumericConverter}}" />
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
<UserControl x:Class="Artemis.VisualScripting.Nodes.CustomViews.StaticStringValueNodeCustomView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800">
|
||||||
|
<TextBox VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Text="{Binding Node.Storage}" />
|
||||||
|
</UserControl>
|
||||||
@ -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<Module> _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<bool> ShowFullPaths { get; }
|
||||||
|
public PluginSetting<bool> ShowDataModelValues { get; }
|
||||||
|
public ObservableCollection<Type> FilterTypes { get; } = new() {typeof(IDataModelEvent)};
|
||||||
|
|
||||||
|
// public ObservableCollection<Module> 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<Module>();
|
||||||
|
// 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));
|
||||||
|
// }
|
||||||
|
}
|
||||||
@ -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<Module> _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<bool> ShowFullPaths { get; }
|
||||||
|
public PluginSetting<bool> ShowDataModelValues { get; }
|
||||||
|
|
||||||
|
// public ObservableCollection<Module> 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<Module>();
|
||||||
|
// 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));
|
||||||
|
// }
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
<UserControl x:Class="Artemis.VisualScripting.Nodes.DataModel.CustomViews.DataModelEventNodeCustomView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800">
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<controls:DataModelPicker DataModelPath="{Binding DataModelPath}"
|
||||||
|
Modules="{Binding Modules}"
|
||||||
|
ShowFullPath="{Binding ShowFullPaths.Value}"
|
||||||
|
ShowDataModelValues="{Binding ShowDataModelValues.Value}"
|
||||||
|
FilterTypes="{Binding FilterTypes}"
|
||||||
|
ButtonBrush="DarkGoldenrod" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
<UserControl x:Class="Artemis.VisualScripting.Nodes.DataModel.CustomViews.DataModelNodeCustomView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800">
|
||||||
|
<controls:DataModelPicker DataModelPath="{Binding DataModelPath}"
|
||||||
|
Modules="{Binding Modules}"
|
||||||
|
ShowFullPath="{Binding ShowFullPaths.Value}"
|
||||||
|
ShowDataModelValues="{Binding ShowDataModelValues.Value}"
|
||||||
|
ButtonBrush="#434343" />
|
||||||
|
</UserControl>
|
||||||
@ -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<DataModelPathEntity, DataModelEventNodeCustomViewModel>, 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<IPin> e)
|
||||||
|
{
|
||||||
|
e.Value.PinConnected += OnPinConnected;
|
||||||
|
e.Value.PinDisconnected += OnPinDisconnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CycleValuesOnPinRemoved(object sender, SingleValueEventArgs<IPin> e)
|
||||||
|
{
|
||||||
|
e.Value.PinConnected -= OnPinConnected;
|
||||||
|
e.Value.PinDisconnected -= OnPinDisconnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPinDisconnected(object sender, SingleValueEventArgs<IPin> e)
|
||||||
|
{
|
||||||
|
ProcessPinDisconnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPinConnected(object sender, SingleValueEventArgs<IPin> 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<IPin> 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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<DataModelPathEntity, DataModelNodeCustomViewModel>, 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
DataModelPath?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<NodeEasingViewModel>(Enum.GetValues(typeof(Easings.Functions)).Cast<Easings.Functions>().Select(e => new NodeEasingViewModel(e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<NodeEasingViewModel> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<Point>();
|
||||||
|
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<Point> EasingPoints { get; }
|
||||||
|
public string Description { get; }
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
<UserControl x:Class="Artemis.VisualScripting.Nodes.Easing.CustomViews.EasingTypeNodeCustomView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:viewModels="clr-namespace:Artemis.VisualScripting.Nodes.Easing.CustomViewModels"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800">
|
||||||
|
<ComboBox VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
ItemsSource="{Binding EasingViewModels}"
|
||||||
|
SelectedItem="{Binding SelectedEasingViewModel}">
|
||||||
|
<ComboBox.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<VirtualizingStackPanel />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ComboBox.ItemsPanel>
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type viewModels:NodeEasingViewModel}">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Polyline Stroke="{DynamicResource MaterialDesignBody}"
|
||||||
|
StrokeThickness="1.5"
|
||||||
|
Points="{Binding EasingPoints}"
|
||||||
|
Stretch="Uniform"
|
||||||
|
Width="14"
|
||||||
|
Height="14"
|
||||||
|
Margin="0 0 10 0" />
|
||||||
|
<TextBlock Text="{Binding Description}" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
@ -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<Easings.Functions, EasingTypeNodeCustomViewModel>
|
||||||
|
{
|
||||||
|
public EasingTypeNode() : base("Easing Type", "Outputs a selectable easing type.")
|
||||||
|
{
|
||||||
|
Output = CreateOutputPin<Easings.Functions>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputPin<Easings.Functions> Output { get; }
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
Output.Value = Storage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<Numeric>();
|
||||||
|
EasingTime = CreateInputPin<Numeric>("delay");
|
||||||
|
EasingFunction = CreateInputPin<Easings.Functions>("function");
|
||||||
|
|
||||||
|
Output = CreateOutputPin<Numeric>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputPin<Numeric> Input { get; set; }
|
||||||
|
public InputPin<Numeric> EasingTime { get; set; }
|
||||||
|
public InputPin<Easings.Functions> EasingFunction { get; set; }
|
||||||
|
|
||||||
|
public OutputPin<Numeric> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<SKColor>();
|
||||||
|
EasingTime = CreateInputPin<Numeric>("delay");
|
||||||
|
EasingFunction = CreateInputPin<Easings.Functions>("function");
|
||||||
|
|
||||||
|
Output = CreateOutputPin<SKColor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputPin<SKColor> Input { get; set; }
|
||||||
|
public InputPin<Numeric> EasingTime { get; set; }
|
||||||
|
public InputPin<Easings.Functions> EasingFunction { get; set; }
|
||||||
|
|
||||||
|
public OutputPin<SKColor> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
124
src/Avalonia/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs
Normal file
124
src/Avalonia/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs
Normal file
@ -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<LayerPropertyNodeEntity, LayerPropertyNodeCustomViewModel>
|
||||||
|
{
|
||||||
|
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<IDataBindingProperty> 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; }
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
<UserControl x:Class="Artemis.VisualScripting.Nodes.Maths.CustomViews.MathExpressionNodeCustomView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800">
|
||||||
|
<TextBox VerticalAlignment="Bottom"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Text="{Binding Node.Storage}" />
|
||||||
|
</UserControl>
|
||||||
@ -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<string, MathExpressionNodeCustomViewModel>
|
||||||
|
{
|
||||||
|
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<Numeric>();
|
||||||
|
Values = CreateInputPinCollection<Numeric>("Values", 2);
|
||||||
|
Values.PinAdded += (_, _) => SetPinNames();
|
||||||
|
Values.PinRemoved += (_, _) => SetPinNames();
|
||||||
|
_variables = new PinsVariablesContainer(Values);
|
||||||
|
|
||||||
|
SetPinNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
public OutputPin<Numeric> Output { get; }
|
||||||
|
public InputPinCollection<Numeric> 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<Numeric> _values;
|
||||||
|
|
||||||
|
public PinsVariablesContainer(InputPinCollection<Numeric> values)
|
||||||
|
{
|
||||||
|
_values = values;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of IVariablesContainer
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IVariable AddOrUpdate(string name, double value)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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
|
||||||
|
}
|
||||||
@ -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<Numeric>();
|
||||||
|
Output = CreateOutputPin<Numeric>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputPin<Numeric> Output { get; set; }
|
||||||
|
public InputPin<Numeric> Input { get; set; }
|
||||||
|
|
||||||
|
#region Overrides of Node
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
Output.Value = new Numeric(MathF.Round(Input.Value, MidpointRounding.AwayFromZero));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -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<Numeric, StaticNumericValueNodeCustomViewModel>
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
public StaticNumericValueNode()
|
||||||
|
: base("Numeric", "Outputs a configurable numeric value.")
|
||||||
|
{
|
||||||
|
Output = CreateOutputPin<Numeric>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
public OutputPin<Numeric> 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<string, StaticStringValueNodeCustomViewModel>
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
public StaticStringValueNode()
|
||||||
|
: base("String", "Outputs a configurable string value.")
|
||||||
|
{
|
||||||
|
Output = CreateOutputPin<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
public OutputPin<string> Output { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
Output.Value = Storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -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<string>("Format");
|
||||||
|
Values = CreateInputPinCollection<object>("Values");
|
||||||
|
Output = CreateOutputPin<string>("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<string> Format { get; }
|
||||||
|
public InputPinCollection<object> Values { get; }
|
||||||
|
|
||||||
|
public OutputPin<string> Output { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
35
src/Avalonia/Artemis.VisualScripting/Nodes/SumNode.cs
Normal file
35
src/Avalonia/Artemis.VisualScripting/Nodes/SumNode.cs
Normal file
@ -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<Numeric>("Values", 2);
|
||||||
|
Sum = CreateOutputPin<Numeric>("Sum");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
Sum.Value = Values.Values.Sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
public InputPinCollection<Numeric> Values { get; }
|
||||||
|
|
||||||
|
public OutputPin<Numeric> Sum { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
1721
src/Avalonia/Artemis.VisualScripting/packages.lock.json
Normal file
1721
src/Avalonia/Artemis.VisualScripting/packages.lock.json
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user