Event condition - Added toggle off mode

Profile editor - Added element copy/pasting
Shared UI - Fix namespaces
This commit is contained in:
Robert committed 2022-04-17 20:23:18 +02:00
1 parent 8c7bbc3f0f
commit 52f2338154
84 files changed
+1059 -608

No files matched your search

@@ -1,6 +1,7 @@
using System;
using System.Linq;
using Artemis.Core.Internal;
using Artemis.Core.VisualScripting.Internal;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
@@ -13,13 +14,15 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
{
private readonly string _displayName;
private readonly EventConditionEntity _entity;
private EventDefaultNode _eventNode;
private IEventConditionNode _startNode;
private DataModelPath? _eventPath;
private DateTime _lastProcessedTrigger;
private EventOverlapMode _overlapMode;
private NodeScript<bool> _script;
private EventTriggerMode _triggerMode;
private bool _wasMet;
private DateTime _lastProcessedTrigger;
private object? _lastProcessedValue;
private EventOverlapMode _overlapMode;
private EventTriggerMode _triggerMode;
private EventToggleOffMode _toggleOffMode;
/// <summary>
/// Creates a new instance of the <see cref="EventCondition" /> class
@@ -30,7 +33,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
_entity = new EventConditionEntity();
_displayName = profileElement.GetType().Name;
_eventNode = new EventDefaultNode {X = -300};
_startNode = new EventConditionEventStartNode {X = -300};
_script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile);
}
@@ -40,7 +43,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
_entity = entity;
_displayName = profileElement.GetType().Name;
_eventNode = new EventDefaultNode();
_startNode = new EventConditionEventStartNode();
_script = null!;
Load();
@@ -83,18 +86,70 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
set => SetAndNotify(ref _overlapMode, value);
}
/// <summary>
/// Gets or sets the mode for render elements when toggling off the event when using <see cref="EventTriggerMode.Toggle"/>.
/// </summary>
public EventToggleOffMode ToggleOffMode
{
get => _toggleOffMode;
set => SetAndNotify(ref _toggleOffMode, value);
}
/// <summary>
/// Updates the event node, applying the selected event
/// </summary>
public void UpdateEventNode()
{
IDataModelEvent? dataModelEvent = EventPath?.GetValue() as IDataModelEvent;
_eventNode.CreatePins(dataModelEvent);
if (EventPath == null)
return;
if (dataModelEvent != null && !Script.Nodes.Contains(_eventNode))
Script.AddNode(_eventNode);
else if (dataModelEvent == null && Script.Nodes.Contains(_eventNode))
Script.RemoveNode(_eventNode);
Type? pathType = EventPath.GetPropertyType();
if (pathType == null)
return;
// Create an event node if the path type is a data model event
if (pathType.IsAssignableTo(typeof(IDataModelEvent)))
{
EventConditionEventStartNode eventNode;
// Ensure the start node is an event node
if (_startNode is not EventConditionEventStartNode node)
{
eventNode = new EventConditionEventStartNode();
ReplaceStartNode(eventNode);
_startNode = eventNode;
}
else
eventNode = node;
IDataModelEvent? dataModelEvent = EventPath?.GetValue() as IDataModelEvent;
eventNode.CreatePins(dataModelEvent);
}
// Create a value changed node if the path type is a regular value
else
{
// Ensure the start nod is a value changed node
EventConditionValueChangedStartNode valueChangedNode;
// Ensure the start node is an event node
if (_startNode is not EventConditionValueChangedStartNode node)
{
valueChangedNode = new EventConditionValueChangedStartNode();
ReplaceStartNode(valueChangedNode);
}
else
valueChangedNode = node;
valueChangedNode.UpdateOutputPins(EventPath);
}
}
private void ReplaceStartNode(IEventConditionNode newStartNode)
{
if (Script.Nodes.Contains(_startNode))
Script.RemoveNode(_startNode);
_startNode = newStartNode;
if (!Script.Nodes.Contains(_startNode))
Script.AddNode(_startNode);
}
/// <summary>
@@ -103,15 +158,30 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
/// <returns>The start node of the event script, if any.</returns>
public INode GetStartNode()
{
return _eventNode;
return _startNode;
}
private bool Evaluate()
{
if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger)
if (EventPath == null)
return false;
_lastProcessedTrigger = dataModelEvent.LastTrigger;
object? value = EventPath.GetValue();
if (_startNode is EventConditionEventStartNode)
{
if (value is not IDataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger)
return false;
_lastProcessedTrigger = dataModelEvent.LastTrigger;
}
else if (_startNode is EventConditionValueChangedStartNode valueChangedNode)
{
if (Equals(value, _lastProcessedValue))
return false;
valueChangedNode.UpdateValues(value, _lastProcessedValue);
_lastProcessedValue = value;
}
if (!Script.ExitNodeConnected)
return true;
@@ -151,6 +221,8 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
{
if (IsMet && !_wasMet)
ProfileElement.Timeline.JumpToStart();
if (!IsMet && _wasMet && ToggleOffMode == EventToggleOffMode.SkipToEnd)
ProfileElement.Timeline.JumpToEndSegment();
}
else
{
@@ -191,6 +263,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
{
TriggerMode = (EventTriggerMode) _entity.TriggerMode;
OverlapMode = (EventOverlapMode) _entity.OverlapMode;
ToggleOffMode = (EventToggleOffMode) _entity.ToggleOffMode;
if (_entity.EventPath != null)
EventPath = new DataModelPath(_entity.EventPath);
@@ -206,6 +279,8 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
{
_entity.TriggerMode = (int) TriggerMode;
_entity.OverlapMode = (int) OverlapMode;
_entity.ToggleOffMode = (int) ToggleOffMode;
Script.Save();
_entity.Script = Script?.Entity;
EventPath?.Save();
@@ -221,9 +296,9 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
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);
INode? existingEventNode = Script.Nodes.FirstOrDefault(n => n.Id == EventConditionEventStartNode.NodeId || n.Id == EventConditionValueChangedStartNode.NodeId);
if (existingEventNode != null)
_eventNode = (EventDefaultNode) existingEventNode;
_startNode = (IEventConditionNode) existingEventNode;
UpdateEventNode();
Script.LoadConnections();
@@ -268,4 +343,20 @@ public enum EventOverlapMode
/// Ignore subsequent event fires until the timeline finishes
/// </summary>
Ignore
}
/// <summary>
/// Represents a mode for render elements when toggling off the event when using <see cref="EventTriggerMode.Toggle"/>.
/// </summary>
public enum EventToggleOffMode
{
/// <summary>
/// When the event toggles the condition off, finish the the current run of the main timeline
/// </summary>
Finish,
/// <summary>
/// When the event toggles the condition off, skip to the end segment of the timeline
/// </summary>
SkipToEnd
}
@@ -1,67 +0,0 @@
using System;
using System.Collections.Generic;
namespace Artemis.Core
{
internal class DataModelValueChangedEvent<T> : IDataModelEvent
{
public DataModelValueChangedEvent(DataModelPath path)
{
Path = path;
}
public DataModelPath Path { get; }
public T? LastValue { get; private set; }
public T? CurrentValue { get; private set; }
public DateTime LastTrigger { get; private set; }
public TimeSpan TimeSinceLastTrigger => DateTime.Now - LastTrigger;
public int TriggerCount { get; private set; }
public Type ArgumentsType { get; } = typeof(DataModelValueChangedEventArgs<T>);
public string TriggerPastParticiple => "changed";
public bool TrackHistory { get; set; } = false;
public DataModelEventArgs? LastEventArgumentsUntyped { get; private set; }
public List<DataModelEventArgs> EventArgumentsHistoryUntyped { get; } = new();
public void Update()
{
if (!Path.IsValid)
return;
object? value = Path.GetValue();
if (value != null)
CurrentValue = (T?) value;
else
CurrentValue = default;
if (!Equals(LastValue, CurrentValue))
Trigger();
LastValue = CurrentValue;
}
public void Reset()
{
TriggerCount = 0;
}
private void Trigger()
{
LastEventArgumentsUntyped = new DataModelValueChangedEventArgs<T>(CurrentValue, LastValue);
LastTrigger = DateTime.Now;
TriggerCount++;
OnEventTriggered();
}
#region Events
public event EventHandler? EventTriggered;
internal virtual void OnEventTriggered()
{
EventTriggered?.Invoke(this, EventArgs.Empty);
}
#endregion
}
}
@@ -1,18 +0,0 @@
using Artemis.Core.Modules;
namespace Artemis.Core
{
internal class DataModelValueChangedEventArgs<T> : DataModelEventArgs
{
public DataModelValueChangedEventArgs(T? currentValue, T? previousValue)
{
CurrentValue = currentValue;
PreviousValue = previousValue;
}
[DataModelProperty(Description = "The current value of the property")]
public T? CurrentValue { get; }
[DataModelProperty(Description = "The previous value of the property")]
public T? PreviousValue { get; }
}
}
@@ -334,20 +334,18 @@ namespace Artemis.Core
/// Explorer
/// </summary>
/// <returns>The resulting name i.e. <c>New layer</c> or <c>New layer (2)</c></returns>
public string GetNewLayerName()
public string GetNewLayerName(string baseName = "New layer")
{
if (!Children.Any(c => c is Layer))
return "New layer";
if (!Children.Any(c => c is Layer && c.Name == baseName))
return baseName;
// Grab existing unnamed layers and get the first available number
// Looks slow but it's not https://stackoverflow.com/a/8865806/5015269
Regex regex = new(@"New layer \((\d+)\)");
int firstAvailable = Enumerable.Range(1, int.MaxValue)
.Except(Children.Where(c => c is Layer && c.Name != null && regex.IsMatch(c.Name))
.Select(c => int.Parse(regex.Match(c.Name!).Groups[1].Value))
.OrderBy(i => i))
.First();
return $"New layer ({firstAvailable})";
int current = 2;
while (true)
{
if (Children.All(c => c is Layer && c.Name != $"{baseName} ({current})"))
return $"{baseName} ({current})";
current++;
}
}
/// <summary>
@@ -355,20 +353,18 @@ namespace Artemis.Core
/// in Explorer
/// </summary>
/// <returns>The resulting name i.e. <c>New folder</c> or <c>New folder (2)</c></returns>
public string GetNewFolderName()
public string GetNewFolderName(string baseName = "New folder")
{
if (!Children.Any(c => c is Folder))
return "New folder";
if (!Children.Any(c => c is Folder && c.Name == baseName))
return baseName;
// Grab existing unnamed layers and get the first available number
// Looks slow but it's not https://stackoverflow.com/a/8865806/5015269
Regex regex = new(@"New folder \((\d+)\)");
int firstAvailable = Enumerable.Range(1, int.MaxValue)
.Except(Children.Where(c => c is Folder && c.Name != null && regex.IsMatch(c.Name))
.Select(c => int.Parse(regex.Match(c.Name!).Groups[1].Value))
.OrderBy(i => i))
.First();
return $"New folder ({firstAvailable})";
int current = 2;
while (true)
{
if (Children.All(c => c is Folder && c.Name != $"{baseName} ({current})"))
return $"{baseName} ({current})";
current++;
}
}
#endregion