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

Nodes - Rewrote storage

This commit is contained in:
Robert 2022-04-10 11:59:32 +02:00
parent 3dfc25b092
commit 5b183d3010
23 changed files with 270 additions and 134 deletions

View File

@ -13,7 +13,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
{ {
private readonly string _displayName; private readonly string _displayName;
private readonly EventConditionEntity _entity; private readonly EventConditionEntity _entity;
private readonly EventDefaultNode _eventNode; private EventDefaultNode _eventNode;
private DataModelPath? _eventPath; private DataModelPath? _eventPath;
private DateTime _lastProcessedTrigger; private DateTime _lastProcessedTrigger;
private EventOverlapMode _overlapMode; private EventOverlapMode _overlapMode;
@ -199,6 +199,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
Script = _entity.Script != null Script = _entity.Script != null
? new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", _entity.Script, ProfileElement.Profile) ? new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", _entity.Script, ProfileElement.Profile)
: new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile); : new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile);
UpdateEventNode();
} }
/// <inheritdoc /> /// <inheritdoc />
@ -219,8 +220,15 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
public void LoadNodeScript() public void LoadNodeScript()
{ {
Script.Load(); Script.Load();
// The load action may have created an event node, use that one over the one we have here
INode? existingEventNode = Script.Nodes.FirstOrDefault(n => n.Id == EventDefaultNode.NodeId);
if (existingEventNode != null)
_eventNode = (EventDefaultNode) existingEventNode;
UpdateEventNode(); UpdateEventNode();
Script.LoadConnections(); Script.LoadConnections();
} }
#endregion #endregion
@ -253,13 +261,13 @@ public enum EventOverlapMode
/// </summary> /// </summary>
Restart, Restart,
/// <summary>
/// Ignore subsequent event fires until the timeline finishes
/// </summary>
Ignore,
/// <summary> /// <summary>
/// Play another copy of the timeline on top of the current run /// Play another copy of the timeline on top of the current run
/// </summary> /// </summary>
Copy Copy,
/// <summary>
/// Ignore subsequent event fires until the timeline finishes
/// </summary>
Ignore
} }

View File

@ -72,6 +72,30 @@ namespace Artemis.Core
Initialize(); Initialize();
} }
/// <summary>
/// Creates a new instance of the <see cref="Layer" /> class by copying the provided <paramref name="source"/>.
/// </summary>
/// <param name="source">The layer to copy</param>
/// <param name="parent">The parent of the layer</param>
public Layer(Layer source, ProfileElement parent) : base(parent, parent.Profile)
{
LayerEntity = CoreJson.DeserializeObject<LayerEntity>(CoreJson.SerializeObject(source.LayerEntity, true), true) ?? new LayerEntity();
LayerEntity.Id = Guid.NewGuid();
Profile = source.Profile;
Parent = parent;
_general = new LayerGeneralProperties();
_transform = new LayerTransformProperties();
_leds = new List<ArtemisLed>();
Leds = new ReadOnlyCollection<ArtemisLed>(_leds);
Adapter = new LayerAdapter(this);
Load();
Initialize();
}
/// <summary> /// <summary>
/// A collection of all the LEDs this layer is assigned to. /// A collection of all the LEDs this layer is assigned to.
/// </summary> /// </summary>
@ -349,7 +373,7 @@ namespace Artemis.Core
if (ShouldBeEnabled) if (ShouldBeEnabled)
Enable(); Enable();
else if (Timeline.IsFinished) else if (Timeline.IsFinished && !Children.Any())
Disable(); Disable();
if (Timeline.Delta == TimeSpan.Zero) if (Timeline.Delta == TimeSpan.Zero)
@ -365,15 +389,16 @@ namespace Artemis.Core
// Remove children that finished their timeline and update the rest // Remove children that finished their timeline and update the rest
for (int index = 0; index < Children.Count; index++) for (int index = 0; index < Children.Count; index++)
{ {
ProfileElement profileElement = Children[index]; Layer child = (Layer) Children[index];
if (((Layer) profileElement).Timeline.IsFinished) if (!child.Timeline.IsFinished)
{ {
RemoveChild(profileElement); child.Update(deltaTime);
profileElement.Dispose(); continue;
index--;
} }
else
profileElement.Update(deltaTime); RemoveChild(child);
child.Dispose();
index--;
} }
} }
@ -383,6 +408,12 @@ namespace Artemis.Core
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
RenderSelf(canvas, basePosition);
RenderChildren(canvas, basePosition);
}
private void RenderSelf(SKCanvas canvas, SKPointI basePosition)
{
// Ensure the layer is ready // Ensure the layer is ready
if (!Enabled || Path == null || LayerShape?.Path == null || !General.PropertiesInitialized || !Transform.PropertiesInitialized || !Leds.Any()) if (!Enabled || Path == null || LayerShape?.Path == null || !General.PropertiesInitialized || !Transform.PropertiesInitialized || !Leds.Any())
return; return;
@ -454,6 +485,13 @@ namespace Artemis.Core
Timeline.ClearDelta(); Timeline.ClearDelta();
} }
private void RenderChildren(SKCanvas canvas, SKPointI basePosition)
{
// Render children first so they go below
for (int i = Children.Count - 1; i >= 0; i--)
Children[i].Render(canvas, basePosition);
}
/// <inheritdoc /> /// <inheritdoc />
public override void Enable() public override void Enable()
{ {
@ -523,11 +561,11 @@ namespace Artemis.Core
/// </summary> /// </summary>
public void CreateCopyAsChild() public void CreateCopyAsChild()
{ {
throw new NotImplementedException(); Layer copy = new(this, this);
copy.AddLeds(Leds);
// Create a copy of the layer and it's properties copy.Enable();
copy.Timeline.JumpToStart();
// Add to children AddChild(copy);
} }
internal void CalculateRenderProperties() internal void CalculateRenderProperties()

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.Storage.Entities.Profile.Nodes;
using SkiaSharp; using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core
@ -51,11 +52,11 @@ namespace Artemis.Core
} }
} }
public static NodeTypeRegistration? Get(Guid pluginGuid, string type) public static NodeTypeRegistration? Get(NodeEntity entity)
{ {
lock (Registrations) lock (Registrations)
{ {
return Registrations.FirstOrDefault(r => r.Plugin.Guid == pluginGuid && r.NodeData.Type.Name == type); return Registrations.FirstOrDefault(r => r.MatchesEntity(entity));
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using Artemis.Storage.Entities.Profile.Nodes;
using SkiaSharp; using SkiaSharp;
namespace Artemis.Core namespace Artemis.Core
@ -37,6 +38,16 @@ namespace Artemis.Core
if (IsInStore) if (IsInStore)
NodeTypeStore.Remove(this); NodeTypeStore.Remove(this);
} }
/// <summary>
/// Determines whether the provided entity matches this node type registration.
/// </summary>
/// <param name="entity">The entity to check.</param>
/// <returns><see langword="true"/> if the entity matches this registration; otherwise <see langword="false"/>.</returns>
public bool MatchesEntity(NodeEntity entity)
{
return Plugin.Guid == entity.PluginId && NodeData.Type.Name == entity.Type;
}
} }
/// <summary> /// <summary>

View File

@ -9,6 +9,11 @@ namespace Artemis.Core
/// </summary> /// </summary>
public interface INode : INotifyPropertyChanged public interface INode : INotifyPropertyChanged
{ {
/// <summary>
/// Gets or sets the ID of the node.
/// </summary>
Guid Id { get; set; }
/// <summary> /// <summary>
/// Gets the name of the node /// Gets the name of the node
/// </summary> /// </summary>

View File

@ -59,6 +59,9 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Removes a node from the script /// Removes a node from the script
/// <para>
/// Note: If the node is <see cref="IDisposable"/> you must dispose it yourself, unless you plan to reuse the node.
/// </para>
/// </summary> /// </summary>
/// <param name="node">The node to remove</param> /// <param name="node">The node to remove</param>
void RemoveNode(INode node); void RemoveNode(INode node);

View File

@ -0,0 +1,36 @@
using System;
namespace Artemis.Core.Internal
{
/// <summary>
/// Represents a kind of node that cannot be deleted inside a <see cref="INode" />.
/// </summary>
public interface IDefaultNode : INode
{
}
/// <summary>
/// Represents a kind of node that cannot be deleted inside a <see cref="NodeScript" />.
/// </summary>
public abstract class DefaultNode : Node, IDefaultNode
{
#region Properties & Fields
/// <inheritdoc />
public override bool IsDefaultNode => true;
#endregion
#region Constructors
/// <inheritdoc />
protected DefaultNode(Guid id, string name, string description = "") : base(name, description)
{
Id = id;
Name = name;
Description = description;
}
#endregion
}
}

View File

@ -7,19 +7,18 @@ using Humanizer;
namespace Artemis.Core.Internal namespace Artemis.Core.Internal
{ {
internal class EventDefaultNode : Node internal class EventDefaultNode : DefaultNode
{ {
internal static readonly Guid NodeId = new("278735FE-69E9-4A73-A6B8-59E83EE19305");
private readonly Dictionary<PropertyInfo, OutputPin> _propertyPins; private readonly Dictionary<PropertyInfo, OutputPin> _propertyPins;
private readonly List<OutputPin> _pinBucket = new(); private readonly List<OutputPin> _pinBucket = new();
private IDataModelEvent? _dataModelEvent; private IDataModelEvent? _dataModelEvent;
public EventDefaultNode() : base("Event Arguments", "Contains the event arguments that triggered the evaluation") public EventDefaultNode() : base(NodeId, "Event Arguments", "Contains the event arguments that triggered the evaluation")
{ {
_propertyPins = new Dictionary<PropertyInfo, OutputPin>(); _propertyPins = new Dictionary<PropertyInfo, OutputPin>();
} }
public override bool IsDefaultNode => true;
public void CreatePins(IDataModelEvent? dataModelEvent) public void CreatePins(IDataModelEvent? dataModelEvent)
{ {
if (_dataModelEvent == dataModelEvent) if (_dataModelEvent == dataModelEvent)
@ -33,9 +32,12 @@ namespace Artemis.Core.Internal
if (dataModelEvent == null) if (dataModelEvent == null)
return; return;
foreach (PropertyInfo propertyInfo in dataModelEvent.ArgumentsType.GetProperties(BindingFlags.Instance | BindingFlags.Public) foreach (PropertyInfo propertyInfo in dataModelEvent.ArgumentsType
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.CustomAttributes.All(a => a.AttributeType != typeof(DataModelIgnoreAttribute)))) .Where(p => p.CustomAttributes.All(a => a.AttributeType != typeof(DataModelIgnoreAttribute))))
{
_propertyPins.Add(propertyInfo, CreateOrAddOutputPin(propertyInfo.PropertyType, propertyInfo.Name.Humanize())); _propertyPins.Add(propertyInfo, CreateOrAddOutputPin(propertyInfo.PropertyType, propertyInfo.Name.Humanize()));
}
} }
public override void Evaluate() public override void Evaluate()

View File

@ -1,12 +1,16 @@
namespace Artemis.Core.Internal using System;
namespace Artemis.Core.Internal
{ {
internal interface IExitNode : INode internal interface IExitNode : INode
{ } {
protected static readonly Guid NodeId = new("410C824D-C5E3-4E3A-8080-D50F6C8B83B8");
}
internal class ExitNode<T> : Node, IExitNode internal class ExitNode<T> : Node, IExitNode
{ {
#region Properties & Fields #region Properties & Fields
public InputPin<T> Input { get; } public InputPin<T> Input { get; }
public T? Value { get; private set; } public T? Value { get; private set; }
@ -19,6 +23,7 @@
public ExitNode(string name, string description = "") public ExitNode(string name, string description = "")
{ {
Id = IExitNode.NodeId;
Name = name; Name = name;
Description = description; Description = description;

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using Artemis.Core.Properties;
using Ninject; using Ninject;
using Ninject.Parameters; using Ninject.Parameters;
@ -16,6 +15,15 @@ public abstract class Node : CorePropertyChanged, INode
public event EventHandler? Resetting; public event EventHandler? Resetting;
#region Properties & Fields #region Properties & Fields
private Guid _id;
/// <inheritdoc />
public Guid Id
{
get => _id;
set => SetAndNotify(ref _id , value);
}
private string _name; private string _name;
@ -65,7 +73,7 @@ public abstract class Node : CorePropertyChanged, INode
public IReadOnlyCollection<IPin> Pins => new ReadOnlyCollection<IPin>(_pins); public IReadOnlyCollection<IPin> Pins => new ReadOnlyCollection<IPin>(_pins);
private readonly List<IPinCollection> _pinCollections = new(); private readonly List<IPinCollection> _pinCollections = new();
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyCollection<IPinCollection> PinCollections => new ReadOnlyCollection<IPinCollection>(_pinCollections); public IReadOnlyCollection<IPinCollection> PinCollections => new ReadOnlyCollection<IPinCollection>(_pinCollections);

View File

@ -16,7 +16,9 @@ namespace Artemis.Core
{ {
private void NodeTypeStoreOnNodeTypeChanged(object? sender, NodeTypeStoreEvent e) private void NodeTypeStoreOnNodeTypeChanged(object? sender, NodeTypeStoreEvent e)
{ {
Load(); // Only respond to node changes applicable to the current script
if (Entity.Nodes.Any(n => e.TypeRegistration.MatchesEntity(n)))
Load();
} }
/// <inheritdoc /> /// <inheritdoc />
@ -153,41 +155,38 @@ namespace Artemis.Core
{ {
lock (_nodes) lock (_nodes)
{ {
List<INode> removeNodes = _nodes.Where(n => !n.IsExitNode).ToList(); // Remove nodes no longer on the entity
List<INode> removeNodes = _nodes.Where(n => Entity.Nodes.All(e => e.Id != n.Id)).ToList();
foreach (INode removeNode in removeNodes) foreach (INode removeNode in removeNodes)
{
RemoveNode(removeNode); RemoveNode(removeNode);
if (removeNode is IDisposable disposable)
disposable.Dispose();
}
} }
// Create nodes // Create missing nodes nodes
foreach (NodeEntity entityNode in Entity.Nodes) foreach (NodeEntity nodeEntity in Entity.Nodes)
{ {
INode? node = LoadNode(entityNode, entityNode.IsExitNode ? ExitNode : null); INode? node = Nodes.FirstOrDefault(n => n.Id == nodeEntity.Id);
if (node == null) // If the node already exists, apply the entity to it
continue; if (node != null)
LoadExistingNode(node, nodeEntity);
if (!entityNode.IsExitNode) else
AddNode(node); {
INode? loaded = LoadNode(nodeEntity);
if (loaded != null)
AddNode(loaded);
}
} }
LoadConnections(); LoadConnections();
} }
private INode? LoadNode(NodeEntity nodeEntity, INode? node) private void LoadExistingNode(INode node, NodeEntity nodeEntity)
{ {
if (node == null) node.X = nodeEntity.X;
{ node.Y = nodeEntity.Y;
NodeTypeRegistration? nodeTypeRegistration = NodeTypeStore.Get(nodeEntity.PluginId, nodeEntity.Type);
if (nodeTypeRegistration == null)
return null;
// Create the node
node = nodeTypeRegistration.NodeData.CreateNode(this, nodeEntity);
}
else
{
node.X = nodeEntity.X;
node.Y = nodeEntity.Y;
}
// Restore pin collections // Restore pin collections
foreach (NodePinCollectionEntity entityNodePinCollection in nodeEntity.PinCollections) foreach (NodePinCollectionEntity entityNodePinCollection in nodeEntity.PinCollections)
@ -201,7 +200,17 @@ namespace Artemis.Core
while (collection.Count() < entityNodePinCollection.Amount) while (collection.Count() < entityNodePinCollection.Amount)
collection.Add(collection.CreatePin()); collection.Add(collection.CreatePin());
} }
}
private INode? LoadNode(NodeEntity nodeEntity)
{
NodeTypeRegistration? nodeTypeRegistration = NodeTypeStore.Get(nodeEntity);
if (nodeTypeRegistration == null)
return null;
// Create the node
INode node = nodeTypeRegistration.NodeData.CreateNode(this, nodeEntity);
LoadExistingNode(node, nodeEntity);
return node; return node;
} }
@ -213,10 +222,10 @@ namespace Artemis.Core
List<INode> nodes = Nodes.ToList(); List<INode> nodes = Nodes.ToList();
foreach (NodeConnectionEntity nodeConnectionEntity in Entity.Connections.OrderBy(p => p.SourcePinCollectionId)) foreach (NodeConnectionEntity nodeConnectionEntity in Entity.Connections.OrderBy(p => p.SourcePinCollectionId))
{ {
INode? source = nodes.ElementAtOrDefault(nodeConnectionEntity.SourceNode); INode? source = nodes.FirstOrDefault(n => n.Id == nodeConnectionEntity.SourceNode);
if (source == null) if (source == null)
continue; continue;
INode? target = nodes.ElementAtOrDefault(nodeConnectionEntity.TargetNode); INode? target = nodes.FirstOrDefault(n => n.Id == nodeConnectionEntity.TargetNode);
if (target == null) if (target == null)
continue; continue;
@ -263,12 +272,11 @@ namespace Artemis.Core
if (Nodes.Count() == 1) if (Nodes.Count() == 1)
return; return;
int id = 0;
foreach (INode node in Nodes) foreach (INode node in Nodes)
{ {
NodeEntity nodeEntity = new() NodeEntity nodeEntity = new()
{ {
Id = id, Id = node.Id,
PluginId = NodeTypeStore.GetPlugin(node)?.Guid ?? Constants.CorePlugin.Guid, PluginId = NodeTypeStore.GetPlugin(node)?.Guid ?? Constants.CorePlugin.Guid,
Type = node.GetType().Name, Type = node.GetType().Name,
X = node.X, X = node.X,
@ -294,7 +302,6 @@ namespace Artemis.Core
} }
Entity.Nodes.Add(nodeEntity); Entity.Nodes.Add(nodeEntity);
id++;
} }
// Store connections // Store connections
@ -315,7 +322,6 @@ namespace Artemis.Core
private void SavePins(INode node, int collectionId, IEnumerable<IPin> pins) private void SavePins(INode node, int collectionId, IEnumerable<IPin> pins)
{ {
int sourcePinId = 0; int sourcePinId = 0;
List<INode> nodes = Nodes.ToList();
foreach (IPin sourcePin in pins.Where(p => p.Direction == PinDirection.Input)) foreach (IPin sourcePin in pins.Where(p => p.Direction == PinDirection.Input))
{ {
foreach (IPin targetPin in sourcePin.ConnectedTo) foreach (IPin targetPin in sourcePin.ConnectedTo)
@ -337,11 +343,11 @@ namespace Artemis.Core
Entity.Connections.Add(new NodeConnectionEntity Entity.Connections.Add(new NodeConnectionEntity
{ {
SourceType = sourcePin.Type.Name, SourceType = sourcePin.Type.Name,
SourceNode = nodes.IndexOf(node), SourceNode = node.Id,
SourcePinCollectionId = collectionId, SourcePinCollectionId = collectionId,
SourcePinId = sourcePinId, SourcePinId = sourcePinId,
TargetType = targetPin.Type.Name, TargetType = targetPin.Type.Name,
TargetNode = nodes.IndexOf(targetPin.Node), TargetNode = targetPin.Node.Id,
TargetPinCollectionId = targetPinCollectionId, TargetPinCollectionId = targetPinCollectionId,
TargetPinId = targetPinId TargetPinId = targetPinId
}); });

View File

@ -1,10 +1,12 @@
namespace Artemis.Storage.Entities.Profile.Nodes using System;
namespace Artemis.Storage.Entities.Profile.Nodes
{ {
public class NodeConnectionEntity public class NodeConnectionEntity
{ {
public string SourceType { get; set; } public string SourceType { get; set; }
public int SourceNode { get; set; } public Guid SourceNode { get; set; }
public int TargetNode { get; set; } public Guid TargetNode { get; set; }
public int SourcePinCollectionId { get; set; } public int SourcePinCollectionId { get; set; }
public int SourcePinId { get; set; } public int SourcePinId { get; set; }
public string TargetType { get; set; } public string TargetType { get; set; }

View File

@ -10,7 +10,7 @@ namespace Artemis.Storage.Entities.Profile.Nodes
PinCollections = new List<NodePinCollectionEntity>(); PinCollections = new List<NodePinCollectionEntity>();
} }
public int Id { get; set; } public Guid Id { get; set; }
public string Type { get; set; } public string Type { get; set; }
public Guid PluginId { get; set; } public Guid PluginId { get; set; }

View File

@ -39,7 +39,8 @@
<TextBlock Name="MainButtonLabel" <TextBlock Name="MainButtonLabel"
Grid.Column="0" Grid.Column="0"
VerticalAlignment="Center" VerticalAlignment="Center"
TextAlignment="Left" /> TextAlignment="Left"
TextTrimming="CharacterEllipsis"/>
<TextBlock Name="ChevronTextBlock" <TextBlock Name="ChevronTextBlock"
Grid.Column="1" Grid.Column="1"
FontFamily="{DynamicResource SymbolThemeFontFamily}" FontFamily="{DynamicResource SymbolThemeFontFamily}"

View File

@ -18,7 +18,8 @@
DockPanel.Dock="Top" DockPanel.Dock="Top"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
FilterTypes="{CompiledBinding FilterTypes}" FilterTypes="{CompiledBinding FilterTypes}"
DataModelPath="{CompiledBinding EventPath}"/> DataModelPath="{CompiledBinding EventPath}"
ShowFullPath="{CompiledBinding ShowFullPaths.Value}"/>
<TextBlock DockPanel.Dock="Top">When the event fires..</TextBlock> <TextBlock DockPanel.Dock="Top">When the event fires..</TextBlock>
<ComboBox PlaceholderText="Select a play mode" HorizontalAlignment="Stretch" DockPanel.Dock="Top" SelectedIndex="{CompiledBinding SelectedTriggerMode}"> <ComboBox PlaceholderText="Select a play mode" HorizontalAlignment="Stretch" DockPanel.Dock="Top" SelectedIndex="{CompiledBinding SelectedTriggerMode}">

View File

@ -4,6 +4,7 @@ using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Screens.VisualScripting; using Artemis.UI.Screens.VisualScripting;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
@ -20,15 +21,17 @@ public class EventConditionViewModel : ActivatableViewModelBase
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly ObservableAsPropertyHelper<bool> _showOverlapOptions; private readonly ObservableAsPropertyHelper<bool> _showOverlapOptions;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private readonly ISettingsService _settingsService;
private ObservableAsPropertyHelper<DataModelPath?>? _eventPath; private ObservableAsPropertyHelper<DataModelPath?>? _eventPath;
private ObservableAsPropertyHelper<int>? _selectedOverlapMode; private ObservableAsPropertyHelper<int>? _selectedOverlapMode;
private ObservableAsPropertyHelper<int>? _selectedTriggerMode; private ObservableAsPropertyHelper<int>? _selectedTriggerMode;
public EventConditionViewModel(EventCondition eventCondition, IProfileEditorService profileEditorService, IWindowService windowService) public EventConditionViewModel(EventCondition eventCondition, IProfileEditorService profileEditorService, IWindowService windowService, ISettingsService settingsService)
{ {
_eventCondition = eventCondition; _eventCondition = eventCondition;
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_windowService = windowService; _windowService = windowService;
_settingsService = settingsService;
_showOverlapOptions = this.WhenAnyValue(vm => vm.SelectedTriggerMode) _showOverlapOptions = this.WhenAnyValue(vm => vm.SelectedTriggerMode)
.Select(m => m == 0) .Select(m => m == 0)
.ToProperty(this, vm => vm.ShowOverlapOptions); .ToProperty(this, vm => vm.ShowOverlapOptions);
@ -47,6 +50,7 @@ public class EventConditionViewModel : ActivatableViewModelBase
public ReactiveCommand<Unit, Unit> OpenEditor { get; } public ReactiveCommand<Unit, Unit> OpenEditor { get; }
public bool ShowOverlapOptions => _showOverlapOptions.Value; public bool ShowOverlapOptions => _showOverlapOptions.Value;
public bool IsConditionForLayer => _eventCondition.ProfileElement is Layer; public bool IsConditionForLayer => _eventCondition.ProfileElement is Layer;
public PluginSetting<bool> ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false);
public DataModelPath? EventPath public DataModelPath? EventPath
{ {

View File

@ -128,40 +128,41 @@
<MenuItem Header="_Options"> <MenuItem Header="_Options">
<MenuItem Header="Focus Selected Layer" <MenuItem Header="Focus Selected Layer"
ToolTip.Tip="If enabled, displays only the layer you currently have selected" ToolTip.Tip="If enabled, displays only the layer you currently have selected"
IsEnabled="False"> Command="{CompiledBinding ToggleBooleanSetting}"
CommandParameter="{CompiledBinding FocusSelectedLayer}">
<MenuItem.Icon> <MenuItem.Icon>
<CheckBox BorderThickness="0" <avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding FocusSelectedLayer.Value}"></avalonia:MaterialIcon>
IsHitTestVisible="False" </MenuItem.Icon>
IsChecked="{Binding FocusSelectedLayer.Value}" />
</MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Display Data Model Values"> <MenuItem Header="Display Data Model Values"
<MenuItem.Icon> Command="{CompiledBinding ToggleBooleanSetting}"
<CheckBox BorderThickness="0" CommandParameter="{CompiledBinding ShowDataModelValues}">
IsHitTestVisible="False" <MenuItem.Icon>
IsChecked="{Binding ShowDataModelValues.Value}" /> <avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding ShowDataModelValues.Value}"></avalonia:MaterialIcon>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Display Full Condition Paths"> <MenuItem Header="Display Full Condition Paths"
<MenuItem.Icon> Command="{CompiledBinding ToggleBooleanSetting}"
<CheckBox BorderThickness="0" CommandParameter="{CompiledBinding ShowFullPaths}">
IsHitTestVisible="False" <MenuItem.Icon>
IsChecked="{Binding ShowFullPaths.Value}" /> <avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding ShowFullPaths.Value}"></avalonia:MaterialIcon>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Always Display Cable Values" ToolTip.Tip="If enabled, cable values are always shown instead of only on hover"> <MenuItem Header="Always Display Cable Values"
<MenuItem.Icon> ToolTip.Tip="If enabled, cable values are always shown instead of only on hover"
<CheckBox BorderThickness="0" Command="{CompiledBinding ToggleBooleanSetting}"
IsHitTestVisible="False" CommandParameter="{CompiledBinding AlwaysShowValues}">
IsChecked="{Binding AlwaysShowValues.Value}" /> <MenuItem.Icon>
</MenuItem.Icon> <avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding AlwaysShowValues.Value}"></avalonia:MaterialIcon>
</MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Apply All Data Bindings During Edit" ToolTip.Tip="If enabled, updates all data bindings instead of only the one you are editing"> <MenuItem Header="Apply All Data Bindings During Edit"
<MenuItem.Icon> ToolTip.Tip="If enabled, updates all data bindings instead of only the one you are editing"
<CheckBox BorderThickness="0" Command="{CompiledBinding ToggleBooleanSetting}"
IsHitTestVisible="False" CommandParameter="{CompiledBinding AlwaysApplyDataBindings}">
IsChecked="{Binding AlwaysApplyDataBindings.Value}" /> <MenuItem.Icon>
</MenuItem.Icon> <avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding AlwaysApplyDataBindings.Value}"></avalonia:MaterialIcon>
</MenuItem.Icon>
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="_Help"> <MenuItem Header="_Help">

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Reactive;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using Artemis.Core; using Artemis.Core;
@ -12,13 +13,15 @@ namespace Artemis.UI.Screens.ProfileEditor.MenuBar;
public class MenuBarViewModel : ActivatableViewModelBase public class MenuBarViewModel : ActivatableViewModelBase
{ {
private readonly IProfileService _profileService; private readonly IProfileService _profileService;
private readonly ISettingsService _settingsService;
private ProfileEditorHistory? _history; private ProfileEditorHistory? _history;
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration; private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration;
private ObservableAsPropertyHelper<bool>? _isSuspended; private ObservableAsPropertyHelper<bool>? _isSuspended;
public MenuBarViewModel(IProfileEditorService profileEditorService, IProfileService profileService) public MenuBarViewModel(IProfileEditorService profileEditorService, IProfileService profileService, ISettingsService settingsService)
{ {
_profileService = profileService; _profileService = profileService;
_settingsService = settingsService;
this.WhenActivated(d => this.WhenActivated(d =>
{ {
profileEditorService.History.Subscribe(history => History = history).DisposeWith(d); profileEditorService.History.Subscribe(history => History = history).DisposeWith(d);
@ -29,16 +32,30 @@ public class MenuBarViewModel : ActivatableViewModelBase
.ToProperty(this, vm => vm.IsSuspended) .ToProperty(this, vm => vm.IsSuspended)
.DisposeWith(d); .DisposeWith(d);
}); });
ToggleBooleanSetting = ReactiveCommand.Create<PluginSetting<bool>>(ExecuteToggleBooleanSetting);
} }
private void ExecuteToggleBooleanSetting(PluginSetting<bool> setting)
{
setting.Value = !setting.Value;
setting.Save();
}
public ReactiveCommand<PluginSetting<bool>, Unit> ToggleBooleanSetting { get; }
public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value;
public PluginSetting<bool> FocusSelectedLayer => _settingsService.GetSetting("ProfileEditor.FocusSelectedLayer", false);
public PluginSetting<bool> ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
public PluginSetting<bool> ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false);
public PluginSetting<bool> AlwaysShowValues => _settingsService.GetSetting("ProfileEditor.AlwaysShowValues", false);
public PluginSetting<bool> AlwaysApplyDataBindings => _settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", false);
public ProfileEditorHistory? History public ProfileEditorHistory? History
{ {
get => _history; get => _history;
set => RaiseAndSetIfChanged(ref _history, value); set => RaiseAndSetIfChanged(ref _history, value);
} }
public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value;
public bool IsSuspended public bool IsSuspended
{ {
get => _isSuspended?.Value ?? false; get => _isSuspended?.Value ?? false;

View File

@ -46,7 +46,7 @@
</TreeView.Styles> </TreeView.Styles>
<TreeView.DataTemplates> <TreeView.DataTemplates>
<TreeDataTemplate DataType="{x:Type core:NodeData}"> <TreeDataTemplate DataType="{x:Type core:NodeData}">
<StackPanel Margin="-15 1 0 1"> <StackPanel Margin="-15 1 0 1" Background="Transparent" PointerReleased="InputElement_OnPointerReleased">
<TextBlock Classes="BodyStrongTextBlockStyle" Text="{Binding Name}"></TextBlock> <TextBlock Classes="BodyStrongTextBlockStyle" Text="{Binding Name}"></TextBlock>
<TextBlock Foreground="{DynamicResource TextFillColorSecondary}" Text="{Binding Description}"></TextBlock> <TextBlock Foreground="{DynamicResource TextFillColorSecondary}" Text="{Binding Description}"></TextBlock>
</StackPanel> </StackPanel>

View File

@ -1,8 +1,11 @@
using System; using System;
using System.Reactive.Linq; using System.Reactive.Linq;
using Artemis.Core;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using Avalonia.Controls.PanAndZoom; using Avalonia.Controls.PanAndZoom;
using Avalonia.Input;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
@ -26,4 +29,13 @@ public class NodePickerView : ReactiveUserControl<NodePickerViewModel>
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (sender is not IDataContextProvider {DataContext: NodeData nodeData} || ViewModel == null)
return;
ViewModel.CreateNode(nodeData);
ViewModel.IsVisible = false;
}
} }

View File

@ -21,7 +21,6 @@ public class NodePickerViewModel : ActivatableViewModelBase
private bool _isVisible; private bool _isVisible;
private Point _position; private Point _position;
private DateTime _closed;
private string? _searchText; private string? _searchText;
private object? _selectedNode; private object? _selectedNode;
private IPin? _targetPin; private IPin? _targetPin;
@ -43,8 +42,7 @@ public class NodePickerViewModel : ActivatableViewModelBase
this.WhenActivated(d => this.WhenActivated(d =>
{ {
if (DateTime.Now - _closed > TimeSpan.FromSeconds(10)) SearchText = null;
SearchText = null;
TargetPin = null; TargetPin = null;
nodeSourceList.Edit(list => nodeSourceList.Edit(list =>
@ -54,24 +52,8 @@ public class NodePickerViewModel : ActivatableViewModelBase
}); });
IsVisible = true; IsVisible = true;
Disposable.Create(() => IsVisible = false).DisposeWith(d);
Disposable.Create(() =>
{
_closed = DateTime.Now;
IsVisible = false;
}).DisposeWith(d);
}); });
this.WhenAnyValue(vm => vm.SelectedNode)
.WhereNotNull()
.Where(o => o is NodeData)
.Throttle(TimeSpan.FromMilliseconds(200), RxApp.MainThreadScheduler)
.Subscribe(data =>
{
CreateNode((NodeData) data);
IsVisible = false;
SelectedNode = null;
});
} }
public ReadOnlyObservableCollection<DynamicData.List.IGrouping<NodeData, string>> Categories { get; } public ReadOnlyObservableCollection<DynamicData.List.IGrouping<NodeData, string>> Categories { get; }

View File

@ -10,5 +10,6 @@
<dataModelPicker:DataModelPickerButton Classes="condensed" <dataModelPicker:DataModelPickerButton Classes="condensed"
DataModelPath="{CompiledBinding DataModelPath}" DataModelPath="{CompiledBinding DataModelPath}"
Modules="{CompiledBinding Modules}" Modules="{CompiledBinding Modules}"
ShowDataModelValues="{CompiledBinding ShowDataModelValues.Value}" /> ShowDataModelValues="{CompiledBinding ShowDataModelValues.Value}"
ShowFullPath="{CompiledBinding ShowFullPaths.Value}"/>
</UserControl> </UserControl>

View File

@ -1,5 +1,4 @@
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Events;
using Artemis.VisualScripting.Nodes.Operators.Screens; using Artemis.VisualScripting.Nodes.Operators.Screens;
namespace Artemis.VisualScripting.Nodes.Operators; namespace Artemis.VisualScripting.Nodes.Operators;
@ -11,13 +10,6 @@ public class EnumEqualsNode : Node<int, EnumEqualsNodeCustomViewModel>
{ {
InputPin = CreateInputPin<Enum>(); InputPin = CreateInputPin<Enum>();
OutputPin = CreateOutputPin<bool>(); OutputPin = CreateOutputPin<bool>();
InputPin.PinConnected += InputPinOnPinConnected;
}
private void InputPinOnPinConnected(object? sender, SingleValueEventArgs<IPin> e)
{
Storage = 0;
} }
public InputPin<Enum> InputPin { get; } public InputPin<Enum> InputPin { get; }