1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Nodes - Implemented saving/loading

Nodes - Added data model node
This commit is contained in:
Robert 2021-08-10 16:32:21 +02:00
parent 61d7f1e1fb
commit afc4bee7ac
29 changed files with 730 additions and 157 deletions

View File

@ -92,5 +92,17 @@ namespace Artemis.Core
}
}
}
public static int IndexOf<T>(this IReadOnlyCollection<T> self, T elementToFind)
{
int i = 0;
foreach (T element in self)
{
if (Equals(element, elementToFind))
return i;
i++;
}
return -1;
}
}
}

View File

@ -72,7 +72,12 @@ namespace Artemis.Core
SubscribeToDataModelStore();
}
internal DataModelPath(DataModel? target, DataModelPathEntity entity)
/// <summary>
/// Creates a new instance of the <see cref="DataModelPath" /> class based on a <see cref="DataModelPathEntity" />
/// </summary>
/// <param name="target"></param>
/// <param name="entity"></param>
public DataModelPath(DataModel? target, DataModelPathEntity entity)
{
Target = target;
Path = entity.Path;
@ -110,7 +115,10 @@ namespace Artemis.Core
/// </summary>
public IReadOnlyCollection<DataModelPathSegment> Segments => _segments.ToList().AsReadOnly();
internal DataModelPathEntity Entity { get; }
/// <summary>
/// Gets the entity used for persistent storage
/// </summary>
public DataModelPathEntity Entity { get; }
internal Func<object, object>? Accessor { get; private set; }
@ -173,6 +181,52 @@ namespace Artemis.Core
return string.IsNullOrWhiteSpace(Path) ? "this" : Path;
}
/// <summary>
/// Occurs whenever the path becomes invalid
/// </summary>
public event EventHandler? PathInvalidated;
/// <summary>
/// Occurs whenever the path becomes valid
/// </summary>
public event EventHandler? PathValidated;
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_disposed = true;
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
Invalidate();
}
}
/// <summary>
/// Invokes the <see cref="PathInvalidated" /> event
/// </summary>
protected virtual void OnPathValidated()
{
PathValidated?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Invokes the <see cref="PathValidated" /> event
/// </summary>
protected virtual void OnPathInvalidated()
{
PathInvalidated?.Invoke(this, EventArgs.Empty);
}
internal void Invalidate()
{
Target?.RemoveDataModelPath(this);
@ -262,27 +316,24 @@ namespace Artemis.Core
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
}
#region IDisposable
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
private void DataModelStoreOnDataModelAdded(object? sender, DataModelStoreEvent e)
{
if (disposing)
{
_disposed = true;
if (e.Registration.DataModel.Module.Id != Entity.DataModelId)
return;
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
Invalidate();
Target = e.Registration.DataModel;
Initialize();
}
Invalidate();
}
private void DataModelStoreOnDataModelRemoved(object? sender, DataModelStoreEvent e)
{
if (e.Registration.DataModel.Module.Id != Entity.DataModelId)
return;
Invalidate();
Target = null;
}
/// <inheritdoc />
@ -292,8 +343,6 @@ namespace Artemis.Core
GC.SuppressFinalize(this);
}
#endregion
#region Storage
/// <inheritdoc />
@ -324,58 +373,5 @@ namespace Artemis.Core
}
#endregion
#region Event handlers
private void DataModelStoreOnDataModelAdded(object? sender, DataModelStoreEvent e)
{
if (e.Registration.DataModel.Module.Id != Entity.DataModelId)
return;
Invalidate();
Target = e.Registration.DataModel;
Initialize();
}
private void DataModelStoreOnDataModelRemoved(object? sender, DataModelStoreEvent e)
{
if (e.Registration.DataModel.Module.Id != Entity.DataModelId)
return;
Invalidate();
Target = null;
}
#endregion
#region Events
/// <summary>
/// Occurs whenever the path becomes invalid
/// </summary>
public event EventHandler? PathInvalidated;
/// <summary>
/// Occurs whenever the path becomes valid
/// </summary>
public event EventHandler? PathValidated;
/// <summary>
/// Invokes the <see cref="PathInvalidated" /> event
/// </summary>
protected virtual void OnPathValidated()
{
PathValidated?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Invokes the <see cref="PathValidated" /> event
/// </summary>
protected virtual void OnPathInvalidated()
{
PathInvalidated?.Invoke(this, EventArgs.Empty);
}
#endregion
}
}

View File

@ -7,6 +7,7 @@ using Artemis.Core.LayerEffects.Placeholder;
using Artemis.Core.Properties;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract;
using Newtonsoft.Json;
using SkiaSharp;
namespace Artemis.Core
@ -66,6 +67,8 @@ namespace Artemis.Core
internal void LoadRenderElement()
{
DisplayCondition = RenderElementEntity.NodeScript != null ? new NodeScript<bool>(RenderElementEntity.NodeScript) : null;
// DisplayCondition = RenderElementEntity.DisplayCondition != null
// ? new DataModelConditionGroup(null, RenderElementEntity.DisplayCondition)
// : new DataModelConditionGroup(null);
@ -97,6 +100,8 @@ namespace Artemis.Core
}
// Conditions
DisplayCondition?.Save();
RenderElementEntity.NodeScript = DisplayCondition?.Entity;
// RenderElementEntity.DisplayCondition = DisplayCondition?.Entity;
// DisplayCondition?.Save();
@ -126,7 +131,7 @@ namespace Artemis.Core
// The play mode dictates whether to stick to the main segment unless the display conditions contains events
bool stickToMainSegment = (Timeline.PlayMode == TimelinePlayMode.Repeat || Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle) && DisplayConditionMet;
// if (DisplayCondition != null && DisplayCondition.ContainsEvents && Timeline.EventOverlapMode != TimeLineEventOverlapMode.Toggle)
// stickToMainSegment = false;
// stickToMainSegment = false;
Timeline.Update(TimeSpan.FromSeconds(deltaTime), stickToMainSegment);
}
@ -386,50 +391,51 @@ namespace Artemis.Core
if (Timeline.EventOverlapMode != TimeLineEventOverlapMode.Toggle)
_toggledOnByEvent = false;
DisplayCondition.Run();
bool conditionMet = DisplayCondition.Result;
// TODO: Handle this nicely, right now when there's only an exit node we assume true
bool conditionMet = DisplayCondition.Nodes.Count() == 1 || DisplayCondition.Result;
if (Parent is RenderProfileElement parent && !parent.DisplayConditionMet)
conditionMet = false;
// if (!DisplayCondition.ContainsEvents)
// {
// // Regular conditions reset the timeline whenever their condition is met and was not met before that
// if (conditionMet && !DisplayConditionMet && Timeline.IsFinished)
// Timeline.JumpToStart();
// // If regular conditions are no longer met, jump to the end segment if stop mode requires it
// if (!conditionMet && Timeline.StopMode == TimelineStopMode.SkipToEnd)
// Timeline.JumpToEndSegment();
// }
// else if (conditionMet)
if (conditionMet)
{
if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle)
{
_toggledOnByEvent = !_toggledOnByEvent;
if (_toggledOnByEvent)
Timeline.JumpToStart();
}
else
{
// Event conditions reset if the timeline finished
if (Timeline.IsFinished)
{
Timeline.JumpToStart();
}
// and otherwise apply their overlap mode
else
{
if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Restart)
Timeline.JumpToStart();
else if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Copy)
Timeline.AddExtraTimeline();
// The third option is ignore which is handled below:
// done
}
}
// Regular conditions reset the timeline whenever their condition is met and was not met before that
if (conditionMet && !DisplayConditionMet && Timeline.IsFinished)
Timeline.JumpToStart();
// If regular conditions are no longer met, jump to the end segment if stop mode requires it
if (!conditionMet && Timeline.StopMode == TimelineStopMode.SkipToEnd)
Timeline.JumpToEndSegment();
}
// else if (conditionMet)
// {
// if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle)
// {
// _toggledOnByEvent = !_toggledOnByEvent;
// if (_toggledOnByEvent)
// Timeline.JumpToStart();
// }
// else
// {
// // Event conditions reset if the timeline finished
// if (Timeline.IsFinished)
// {
// Timeline.JumpToStart();
// }
// // and otherwise apply their overlap mode
// else
// {
// if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Restart)
// Timeline.JumpToStart();
// else if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Copy)
// Timeline.AddExtraTimeline();
// // The third option is ignore which is handled below:
//
// // done
// }
// }
// }
DisplayConditionMet = Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle
? _toggledOnByEvent

View File

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Artemis.Storage.Entities.Profile.Nodes;
using Ninject;
using Ninject.Parameters;
namespace Artemis.Core.Services
{
@ -44,15 +46,25 @@ namespace Artemis.Core.Services
string description = nodeAttribute?.Description ?? string.Empty;
string category = nodeAttribute?.Category ?? string.Empty;
NodeData nodeData = new(plugin, nodeType, name, description, category, () => CreateNode(nodeType));
NodeData nodeData = new(plugin, nodeType, name, description, category, (e) => CreateNode(e, nodeType));
return NodeTypeStore.Add(nodeData);
}
private INode CreateNode(Type nodeType)
private INode CreateNode(NodeEntity? entity, Type nodeType)
{
INode node = _kernel.Get(nodeType) as INode ?? throw new InvalidOperationException($"Node {nodeType} is not an INode");
if (entity != null)
{
node.X = entity.X;
node.Y = entity.Y;
node.Storage = entity.Storage;
}
if (node is CustomViewModelNode customViewModelNode)
customViewModelNode.BaseCustomViewModel = _kernel.Get(customViewModelNode.CustomViewModelType);
customViewModelNode.BaseCustomViewModel = _kernel.Get(customViewModelNode.CustomViewModelType, new ConstructorArgument("node", node));
node.Initialize();
return node;
}

View File

@ -23,7 +23,7 @@ namespace Artemis.Core
Registrations.Add(typeRegistration);
}
OnDataBindingModifierAdded(new NodeTypeStoreEvent(typeRegistration));
OnNodeTypeAdded(new NodeTypeStoreEvent(typeRegistration));
return typeRegistration;
}
@ -38,7 +38,7 @@ namespace Artemis.Core
typeRegistration.IsInStore = false;
}
OnDataBindingModifierRemoved(new NodeTypeStoreEvent(typeRegistration));
OnNodeTypeRemoved(new NodeTypeStoreEvent(typeRegistration));
}
public static IEnumerable<NodeData> GetAll()
@ -59,19 +59,27 @@ namespace Artemis.Core
#region Events
public static event EventHandler<NodeTypeStoreEvent>? DataBindingModifierAdded;
public static event EventHandler<NodeTypeStoreEvent>? DataBindingModifierRemoved;
public static event EventHandler<NodeTypeStoreEvent>? NodeTypeAdded;
public static event EventHandler<NodeTypeStoreEvent>? NodeTypeRemoved;
private static void OnDataBindingModifierAdded(NodeTypeStoreEvent e)
private static void OnNodeTypeAdded(NodeTypeStoreEvent e)
{
DataBindingModifierAdded?.Invoke(null, e);
NodeTypeAdded?.Invoke(null, e);
}
private static void OnDataBindingModifierRemoved(NodeTypeStoreEvent e)
private static void OnNodeTypeRemoved(NodeTypeStoreEvent e)
{
DataBindingModifierRemoved?.Invoke(null, e);
NodeTypeRemoved?.Invoke(null, e);
}
#endregion
public static Plugin? GetPlugin(INode node)
{
lock (Registrations)
{
return Registrations.FirstOrDefault(r => r.Plugin.GetType().Assembly == node.GetType().Assembly)?.Plugin;
}
}
}
}

View File

@ -0,0 +1,15 @@
using Newtonsoft.Json;
namespace Artemis.Core
{
public class CustomNodeViewModel : CorePropertyChanged
{
[JsonIgnore]
public INode Node { get; }
public CustomNodeViewModel(INode node)
{
Node = node;
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using Newtonsoft.Json;
namespace Artemis.Core
{
@ -32,6 +33,7 @@ namespace Artemis.Core
#region Constructors
[JsonConstructor]
internal InputPin(INode node, string name)
: base(node, name)
{ }

View File

@ -12,13 +12,15 @@ namespace Artemis.Core
public double X { get; set; }
public double Y { get; set; }
public object Storage { get; set; }
public IReadOnlyCollection<IPin> Pins { get; }
public IReadOnlyCollection<IPinCollection> PinCollections { get; }
event EventHandler Resetting;
void Initialize();
void Evaluate();
void Reset();
}
}
}

View File

@ -42,6 +42,14 @@ namespace Artemis.Core
set => SetAndNotify(ref _y, value);
}
private object? _storage;
public object? Storage
{
get => _storage;
set => SetAndNotify(ref _storage, value);
}
public virtual bool IsExitNode => false;
private readonly List<IPin> _pins = new();
@ -128,6 +136,8 @@ namespace Artemis.Core
return pin;
}
public virtual void Initialize() { }
public abstract void Evaluate();
public virtual void Reset()
@ -138,7 +148,7 @@ namespace Artemis.Core
#endregion
}
public abstract class Node<T> : CustomViewModelNode
public abstract class Node<T> : CustomViewModelNode where T : CustomNodeViewModel
{
protected Node()
{
@ -149,7 +159,7 @@ namespace Artemis.Core
}
public override Type CustomViewModelType => typeof(T);
public T? CustomViewModel => (T?) BaseCustomViewModel;
public T CustomViewModel => (T) BaseCustomViewModel!;
}
public abstract class CustomViewModelNode : Node

View File

@ -1,4 +1,5 @@
using System;
using Artemis.Storage.Entities.Profile.Nodes;
namespace Artemis.Core
{
@ -13,13 +14,13 @@ namespace Artemis.Core
public string Description { get; }
public string Category { get; }
private Func<INode> _create;
private Func<NodeEntity?, INode> _create;
#endregion
#region Constructors
internal NodeData(Plugin plugin, Type type, string name, string description, string category, Func<INode> create)
internal NodeData(Plugin plugin, Type type, string name, string description, string category, Func<NodeEntity?, INode>? create)
{
this.Plugin = plugin;
this.Type = type;
@ -33,7 +34,7 @@ namespace Artemis.Core
#region Methods
public INode CreateNode() => _create();
public INode CreateNode(NodeEntity? entity) => _create(entity);
#endregion
}

View File

@ -1,15 +1,19 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core.Internal;
using Artemis.Core.Properties;
using Artemis.Storage.Entities.Profile.Nodes;
namespace Artemis.Core
{
public abstract class NodeScript : CorePropertyChanged, INodeScript
public abstract class NodeScript : CorePropertyChanged, INodeScript, IStorageModel
{
#region Properties & Fields
internal NodeScriptEntity Entity { get; }
public string Name { get; }
public string Description { get; }
@ -18,6 +22,7 @@ namespace Artemis.Core
protected INode ExitNode { get; set; }
public abstract Type ResultType { get; }
public abstract void CreateExitNode(string name, string description = "");
#endregion
@ -27,6 +32,22 @@ namespace Artemis.Core
{
this.Name = name;
this.Description = description;
this.Entity = new NodeScriptEntity();
NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeChanged;
NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged;
}
internal NodeScript(NodeScriptEntity entity)
{
this.Name = entity.Name;
this.Description = entity.Description;
this.Entity = entity;
Load();
NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeChanged;
NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged;
}
#endregion
@ -50,9 +71,203 @@ namespace Artemis.Core
{
_nodes.Remove(node);
}
public void Dispose()
{ }
{
NodeTypeStore.NodeTypeAdded -= NodeTypeStoreOnNodeTypeChanged;
NodeTypeStore.NodeTypeRemoved -= NodeTypeStoreOnNodeTypeChanged;
}
#endregion
#region Implementation of IStorageModel
/// <inheritdoc />
public void Load()
{
bool gotExitNode = false;
// Create nodes
Dictionary<int, INode> nodes = new();
foreach (NodeEntity entityNode in Entity.Nodes)
{
INode? node = LoadNode(entityNode);
if (node == null)
continue;
if (node.IsExitNode)
gotExitNode = true;
nodes.Add(entityNode.Id, node);
}
if (!gotExitNode)
CreateExitNode("Exit node");
LoadConnections(nodes);
_nodes.Clear();
_nodes.AddRange(nodes.Values);
}
private INode? LoadNode(NodeEntity nodeEntity)
{
INode node;
if (nodeEntity.IsExitNode)
{
CreateExitNode(nodeEntity.Name, nodeEntity.Description);
node = ExitNode;
}
else
{
NodeTypeRegistration? nodeTypeRegistration = NodeTypeStore.Get(nodeEntity.PluginId, nodeEntity.Type);
if (nodeTypeRegistration == null)
return null;
// Create the node
node = nodeTypeRegistration.NodeData.CreateNode(nodeEntity);
}
// Restore pin collections
foreach (NodePinCollectionEntity entityNodePinCollection in nodeEntity.PinCollections)
{
IPinCollection? collection = node.PinCollections.FirstOrDefault(c => c.Name == entityNodePinCollection.Name &&
c.Type.Name == entityNodePinCollection.Type &&
(int) c.Direction == entityNodePinCollection.Direction);
if (collection == null)
continue;
while (collection.Count() < entityNodePinCollection.Amount)
collection.AddPin();
}
return node;
}
private void LoadConnections(Dictionary<int, INode> nodes)
{
foreach (NodeConnectionEntity nodeConnectionEntity in Entity.Connections)
{
// Find the source and target node
if (!nodes.TryGetValue(nodeConnectionEntity.SourceNode, out INode? source) || !nodes.TryGetValue(nodeConnectionEntity.TargetNode, out INode? target))
continue;
IPin? sourcePin = nodeConnectionEntity.SourcePinCollectionId == -1
? source.Pins.ElementAtOrDefault(nodeConnectionEntity.SourcePinId)
: source.PinCollections.ElementAtOrDefault(nodeConnectionEntity.SourcePinCollectionId)?.ElementAtOrDefault(nodeConnectionEntity.SourcePinId);
IPin? targetPin = nodeConnectionEntity.TargetPinCollectionId == -1
? target.Pins.ElementAtOrDefault(nodeConnectionEntity.TargetPinId)
: target.PinCollections.ElementAtOrDefault(nodeConnectionEntity.TargetPinCollectionId)?.ElementAtOrDefault(nodeConnectionEntity.TargetPinId);
// Ensure both nodes have the required pins
if (sourcePin == null || targetPin == null)
continue;
targetPin.ConnectTo(sourcePin);
sourcePin.ConnectTo(targetPin);
}
}
/// <inheritdoc />
public void Save()
{
Entity.Name = Name;
Entity.Description = Description;
Entity.Nodes.Clear();
int id = 0;
Dictionary<INode, int> nodes = new();
foreach (INode node in Nodes)
{
NodeEntity nodeEntity = new()
{
Id = id,
PluginId = NodeTypeStore.GetPlugin(node)?.Guid ?? Constants.CorePlugin.Guid,
Type = node.GetType().Name,
X = node.X,
Y = node.Y,
Storage = node.Storage,
Name = node.Name,
Description = node.Description,
IsExitNode = node.IsExitNode
};
foreach (IPinCollection nodePinCollection in node.PinCollections)
{
nodeEntity.PinCollections.Add(new NodePinCollectionEntity
{
Name = nodePinCollection.Name,
Type = nodePinCollection.Type.Name,
Direction = (int) nodePinCollection.Direction,
Amount = nodePinCollection.Count()
});
}
Entity.Nodes.Add(nodeEntity);
nodes.Add(node, id);
id++;
}
// Store connections
Entity.Connections.Clear();
foreach (INode node in Nodes)
{
SavePins(nodes, node, -1, node.Pins);
int pinCollectionId = 0;
foreach (IPinCollection pinCollection in node.PinCollections)
{
SavePins(nodes, node, pinCollectionId, pinCollection);
pinCollectionId++;
}
}
}
private void SavePins(Dictionary<INode, int> nodes, INode node, int collectionId, IEnumerable<IPin> pins)
{
int sourcePinId = 0;
foreach (IPin sourcePin in pins.Where(p => p.Direction == PinDirection.Input))
{
foreach (IPin targetPin in sourcePin.ConnectedTo)
{
int targetPinCollectionId = -1;
int targetPinId;
IPinCollection? targetCollection = targetPin.Node.PinCollections.FirstOrDefault(c => c.Contains(targetPin));
if (targetCollection != null)
{
targetPinCollectionId = targetPin.Node.PinCollections.IndexOf(targetCollection);
targetPinId = targetCollection.ToList().IndexOf(targetPin);
}
else
targetPinId = targetPin.Node.Pins.IndexOf(targetPin);
Entity.Connections.Add(new NodeConnectionEntity()
{
SourceType = sourcePin.Type.Name,
SourceNode = nodes[node],
SourcePinCollectionId = collectionId,
SourcePinId = sourcePinId,
TargetType = targetPin.Type.Name,
TargetNode = nodes[targetPin.Node],
TargetPinCollectionId = targetPinCollectionId,
TargetPinId = targetPinId,
});
}
sourcePinId++;
}
}
#endregion
#region Event handlers
private void NodeTypeStoreOnNodeTypeChanged(object? sender, NodeTypeStoreEvent e)
{
Load();
}
#endregion
}
@ -61,14 +276,24 @@ namespace Artemis.Core
{
#region Properties & Fields
public T Result => ((ExitNode<T>)ExitNode).Value;
public T Result => ((ExitNode<T>) ExitNode).Value;
public override Type ResultType => typeof(T);
public override void CreateExitNode(string name, string description = "")
{
ExitNode = new ExitNode<T>(name, description);
}
#endregion
#region Constructors
internal NodeScript(NodeScriptEntity entity)
: base(entity)
{
}
public NodeScript(string name, string description)
: base(name, description)
{
@ -86,4 +311,4 @@ namespace Artemis.Core
#endregion
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using Newtonsoft.Json;
namespace Artemis.Core
{
@ -31,6 +32,7 @@ namespace Artemis.Core
#region Constructors
[JsonConstructor]
internal OutputPin(INode node, string name)
: base(node, name)
{ }

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Conditions;
using Artemis.Storage.Entities.Profile.Nodes;
namespace Artemis.Storage.Entities.Profile.Abstract
{
@ -15,5 +16,7 @@ namespace Artemis.Storage.Entities.Profile.Abstract
public DataModelConditionGroupEntity DisplayCondition { get; set; }
public TimelineEntity Timeline { get; set; }
public NodeScriptEntity NodeScript { get; set; }
}
}

View File

@ -0,0 +1,14 @@
namespace Artemis.Storage.Entities.Profile.Nodes
{
public class NodeConnectionEntity
{
public string SourceType { get; set; }
public int SourceNode { get; set; }
public int TargetNode { get; set; }
public int SourcePinCollectionId { get; set; }
public int SourcePinId { get; set; }
public string TargetType { get; set; }
public int TargetPinCollectionId { get; set; }
public int TargetPinId { get; set; }
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
namespace Artemis.Storage.Entities.Profile.Nodes
{
public class NodeEntity
{
public NodeEntity()
{
PinCollections = new List<NodePinCollectionEntity>();
}
public int Id { get; set; }
public string Type { get; set; }
public Guid PluginId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsExitNode { get; set; }
public double X { get; set; }
public double Y { get; set; }
public object Storage { get; set; }
public List<NodePinCollectionEntity> PinCollections { get; set; }
}
}

View File

@ -0,0 +1,10 @@
namespace Artemis.Storage.Entities.Profile.Nodes
{
public class NodePinCollectionEntity
{
public string Name { get; set; }
public string Type { get; set; }
public int Direction { set; get; }
public int Amount { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace Artemis.Storage.Entities.Profile.Nodes
{
public class NodeScriptEntity
{
public NodeScriptEntity()
{
Nodes = new List<NodeEntity>();
Connections = new List<NodeConnectionEntity>();
}
public string Name { get; set; }
public string Description { get; set; }
public List<NodeEntity> Nodes { get; set; }
public List<NodeConnectionEntity> Connections { get; set; }
}
}

View File

@ -1482,6 +1482,7 @@
"type": "Project",
"dependencies": {
"Artemis.Core": "1.0.0",
"Artemis.UI.Shared": "2.0.0",
"JetBrains.Annotations": "2021.1.0",
"Stylet": "1.3.6"
}

View File

@ -15,7 +15,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<PlatformTarget>x64</PlatformTarget>
<DocumentationFile>bin\Artemis.VisualScripting.xml</DocumentationFile>
<DocumentationFile></DocumentationFile>
<NoWarn></NoWarn>
<WarningLevel>5</WarningLevel>
</PropertyGroup>
@ -42,6 +42,7 @@
<ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI.Shared\Artemis.UI.Shared.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -383,7 +383,7 @@ namespace Artemis.VisualScripting.Editor.Controls
if (_creationBoxParent.ContextMenu != null)
_creationBoxParent.ContextMenu.IsOpen = false;
INode node = nodeData.CreateNode();
INode node = nodeData.CreateNode(null);
Script.AddNode(node);
InitializeNode(node, _lastRightClickLocation);

View File

@ -207,9 +207,18 @@
</StackPanel>
</Border>
<!-- TODO: Figure out how to only use this for CustomViewModelNode -->
<Border x:Name="BrdCustomView" Grid.Column="1">
<ContentControl s:View.Model="{Binding Node.CustomViewModel}"/>
<Border.Resources>
<DataTemplate DataType="{x:Type core:CustomViewModelNode}">
<ContentControl s:View.Model="{Binding BaseCustomViewModel}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type core:Node}">
<Border />
</DataTemplate>
</Border.Resources>
<ContentControl Content="{Binding Node}" />
</Border>
<Border x:Name="BrdOutputPins" Grid.Column="2">

View File

@ -0,0 +1,42 @@
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Input;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.VisualScripting.Nodes.CustomViewModels
{
public class DataModelNodeCustomViewModel : CustomNodeViewModel
{
private readonly DataModelNode _node;
public DataModelNodeCustomViewModel(DataModelNode node, IDataModelUIService dataModelUIService) : base(node)
{
_node = node;
Execute.OnUIThreadSync(() =>
{
SelectionViewModel = dataModelUIService.GetDynamicSelectionViewModel(module: null);
SelectionViewModel.PropertySelected += SelectionViewModelOnPropertySelected;
});
}
public DataModelDynamicViewModel SelectionViewModel { get; set; }
private void SelectionViewModelOnPropertySelected(object? sender, DataModelInputDynamicEventArgs e)
{
_node.DataModelPath = SelectionViewModel.DataModelPath;
if (_node.DataModelPath != null)
{
_node.DataModelPath.Save();
_node.Storage = _node.DataModelPath.Entity;
}
else
{
_node.Storage = null;
}
_node.UpdateOutputPin();
}
}
}

View File

@ -1,15 +1,24 @@
using Stylet;
using Artemis.Core;
namespace Artemis.VisualScripting.Nodes.CustomViewModels
{
public class StaticDoubleValueNodeCustomViewModel : PropertyChangedBase
public class StaticDoubleValueNodeCustomViewModel : CustomNodeViewModel
{
private double _input;
private readonly StaticDoubleValueNode _node;
public StaticDoubleValueNodeCustomViewModel(StaticDoubleValueNode node) : base(node)
{
_node = node;
}
public double Input
{
get => _input;
set => SetAndNotify(ref _input, value);
get => (double) _node.Storage;
set
{
_node.Storage = value;
OnPropertyChanged(nameof(Input));
}
}
}
}

View File

@ -1,15 +1,24 @@
using Stylet;
using Artemis.Core;
namespace Artemis.VisualScripting.Nodes.CustomViewModels
{
public class StaticIntegerValueNodeCustomViewModel : PropertyChangedBase
public class StaticIntegerValueNodeCustomViewModel : CustomNodeViewModel
{
private int _input;
private readonly StaticIntegerValueNode _node;
public StaticIntegerValueNodeCustomViewModel(StaticIntegerValueNode node) : base(node)
{
_node = node;
}
public int Input
{
get => _input;
set => SetAndNotify(ref _input, value);
get => (int)(long) _node.Storage;
set
{
_node.Storage = value;
OnPropertyChanged(nameof(Input));
}
}
}
}

View File

@ -1,15 +1,24 @@
using Stylet;
using Artemis.Core;
namespace Artemis.VisualScripting.Nodes.CustomViewModels
{
public class StaticStringValueNodeCustomViewModel : PropertyChangedBase
public class StaticStringValueNodeCustomViewModel : CustomNodeViewModel
{
private string _input;
private readonly StaticStringValueNode _node;
public StaticStringValueNodeCustomViewModel(StaticStringValueNode node) : base(node)
{
_node = node;
}
public string Input
{
get => _input;
set => SetAndNotify(ref _input, value);
get => (string) _node.Storage;
set
{
_node.Storage = value;
OnPropertyChanged(nameof(Input));
}
}
}
}

View File

@ -0,0 +1,11 @@
<UserControl x:Class="Artemis.VisualScripting.Nodes.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:local="clr-namespace:Artemis.VisualScripting.Nodes.CustomViews"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<ContentControl s:View.Model="{Binding SelectionViewModel}"></ContentControl>
</UserControl>

View File

@ -0,0 +1,48 @@
using System;
using System.Linq;
using Artemis.Core;
using Artemis.Storage.Entities.Profile;
using Artemis.VisualScripting.Nodes.CustomViewModels;
namespace Artemis.VisualScripting.Nodes
{
[Node("Data Model-Value", "Outputs a selectable data model value.")]
public class DataModelNode : Node<DataModelNodeCustomViewModel>
{
public DataModelNode() : base("Data Model", "Outputs a selectable data model value")
{
}
public OutputPin Output { get; private set; }
public DataModelPath DataModelPath { get; set; }
public override void Initialize()
{
if (Storage is not DataModelPathEntity pathEntity)
return;
DataModelPath = new DataModelPath(null, pathEntity);
CustomViewModel.SelectionViewModel.ChangeDataModelPath(DataModelPath);
UpdateOutputPin();
}
public override void Evaluate()
{
if (DataModelPath.IsValid && Output != null)
Output.Value = DataModelPath.GetValue()!;
}
public void UpdateOutputPin()
{
if (Pins.Contains(Output))
{
RemovePin(Output);
Output = null;
}
Type type = DataModelPath?.GetPropertyType();
if (type != null)
Output = CreateOutputPin(type);
}
}
}

View File

@ -26,9 +26,11 @@ namespace Artemis.VisualScripting.Nodes
public override void Evaluate()
{
Output.Value = CustomViewModel.Input;
Output.Value = (int) (Storage as long? ?? 0);
}
public override void Initialize() => Storage ??= 0;
#endregion
}
@ -55,9 +57,11 @@ namespace Artemis.VisualScripting.Nodes
public override void Evaluate()
{
Output.Value = CustomViewModel.Input;
Output.Value = Storage as double? ?? 0.0;
}
public override void Initialize() => Storage ??= 0.0;
#endregion
}
@ -84,9 +88,9 @@ namespace Artemis.VisualScripting.Nodes
public override void Evaluate()
{
Output.Value = CustomViewModel.Input;
Output.Value = Storage as string;
}
#endregion
}

View File

@ -57,6 +57,28 @@
"resolved": "5.0.10",
"contentHash": "x70WuqMDuP75dajqSLvO+AnI/BbwS6da+ukTO7rueV7VoXoQ5CRA9FV4r7cOS4OUr2NS1Up7LDIutjCxQycRvg=="
},
"MaterialDesignColors": {
"type": "Transitive",
"resolved": "2.0.1",
"contentHash": "Azl8nN23SD6QPE0PdsfpKiIqWTvH7rzXwgXPiFSEt91NFOrwB5cx3iq/sbINWMZunhXJ32+jVUHiV03B8eJbZw=="
},
"MaterialDesignExtensions": {
"type": "Transitive",
"resolved": "3.3.0",
"contentHash": "dlxWtdrMH8aHNib3dWJhNQ/nNiA2b/CNvr90w/5KB6erTisuTpyYVx2l2+UGCZvwhSX5mHTHQYHfjgAKbDrgjg==",
"dependencies": {
"MaterialDesignColors": "1.2.7",
"MaterialDesignThemes": "3.2.0"
}
},
"MaterialDesignThemes": {
"type": "Transitive",
"resolved": "4.1.0",
"contentHash": "WqrO9AbtdE4pLPtDk/C5BZRnkgWFwVGyUHWj7tRJrgnKl089DEobVXBCLeqp2mkgBeFHj4Xe3AfWyhmlnO6AZA==",
"dependencies": {
"MaterialDesignColors": "2.0.1"
}
},
"McMaster.NETCore.Plugins": {
"type": "Transitive",
"resolved": "1.4.0",
@ -104,6 +126,11 @@
"Microsoft.NETCore.Platforms": "3.0.0"
}
},
"Microsoft.Xaml.Behaviors.Wpf": {
"type": "Transitive",
"resolved": "1.1.31",
"contentHash": "LZpuf82ACZWldmfMuv3CTUMDh3o0xo0uHUaybR5HgqVLDBJJ9RZLykplQ/bTJd0/VDt3EhD4iDgUgbdIUAM+Kg=="
},
"NETStandard.Library": {
"type": "Transitive",
"resolved": "1.6.1",
@ -336,6 +363,11 @@
"System.Threading.Timer": "4.0.1"
}
},
"SharpVectors.Reloaded": {
"type": "Transitive",
"resolved": "1.7.5",
"contentHash": "v9U5sSMGFE2zCMbh42BYHkaRYkmwwhsKMGcNRdHAKqD1ryOf4mhqnJR0o07hwg5KIEmCI9bDdrgYSmiZWlL+eA=="
},
"SkiaSharp": {
"type": "Transitive",
"resolved": "2.80.2",
@ -344,6 +376,23 @@
"System.Memory": "4.5.3"
}
},
"SkiaSharp.Views.Desktop.Common": {
"type": "Transitive",
"resolved": "2.80.2",
"contentHash": "0vBvweMysgl1wgjuTQUhdJMD5z5nBjtYqmnHPeX+qHfkc336Wj2L3jEqwmGb0YP+RV47gFGz0EzMAW6szZch9w==",
"dependencies": {
"SkiaSharp": "2.80.2"
}
},
"SkiaSharp.Views.WPF": {
"type": "Transitive",
"resolved": "2.80.2",
"contentHash": "Fzo2+MNwHDh9Cob8sk7OO26kp3bhofjXMwlEK8IncF1ehu9hi3sH9iQDJrue9a88VEJJ+yyLISPUFcmXlGHSyQ==",
"dependencies": {
"SkiaSharp": "2.80.2",
"SkiaSharp.Views.Desktop.Common": "2.80.2"
}
},
"System.AppContext": {
"type": "Transitive",
"resolved": "4.3.0",
@ -1266,6 +1315,24 @@
"LiteDB": "5.0.10",
"Serilog": "2.10.0"
}
},
"artemis.ui.shared": {
"type": "Project",
"dependencies": {
"Artemis.Core": "1.0.0",
"Humanizer.Core": "2.11.10",
"MaterialDesignExtensions": "3.3.0",
"MaterialDesignThemes": "4.1.0",
"Microsoft.Xaml.Behaviors.Wpf": "1.1.31",
"Ninject": "3.3.4",
"Ninject.Extensions.Conventions": "3.3.0",
"SharpVectors.Reloaded": "1.7.5",
"SkiaSharp": "2.80.2",
"SkiaSharp.Views.WPF": "2.80.2",
"Stylet": "1.3.6",
"System.Buffers": "4.5.1",
"System.Numerics.Vectors": "4.5.0"
}
}
}
}