mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Core - Refactored display conditions
Profile editor - Added UIs for each condition type
This commit is contained in:
parent
66ea718316
commit
3dfc25b092
@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
public class AlwaysOnCondition : ICondition
|
||||
{
|
||||
public AlwaysOnCondition(RenderProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
Entity = new AlwaysOnConditionEntity();
|
||||
}
|
||||
|
||||
public AlwaysOnCondition(AlwaysOnConditionEntity alwaysOnConditionEntity, RenderProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
Entity = alwaysOnConditionEntity;
|
||||
}
|
||||
|
||||
#region Implementation of IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IStorageModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ICondition
|
||||
|
||||
/// <inheritdoc />
|
||||
public IConditionEntity Entity { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public RenderProfileElement ProfileElement { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMet { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
{
|
||||
if (ProfileElement.Parent is RenderProfileElement parent)
|
||||
IsMet = parent.DisplayConditionMet;
|
||||
else
|
||||
IsMet = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateTimeline(double deltaTime)
|
||||
{
|
||||
ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OverrideTimeline(TimeSpan position)
|
||||
{
|
||||
ProfileElement.Timeline.Override(position, position > ProfileElement.Timeline.Length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -4,211 +4,262 @@ using Artemis.Core.Internal;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a condition that is based on a <see cref="DataModelEvent" />
|
||||
/// </summary>
|
||||
public class EventCondition : CorePropertyChanged, INodeScriptCondition
|
||||
{
|
||||
private readonly string _displayName;
|
||||
private readonly EventConditionEntity _entity;
|
||||
private readonly EventDefaultNode _eventNode;
|
||||
private DataModelPath? _eventPath;
|
||||
private DateTime _lastProcessedTrigger;
|
||||
private EventOverlapMode _overlapMode;
|
||||
private NodeScript<bool> _script;
|
||||
private EventTriggerMode _triggerMode;
|
||||
private bool _wasMet;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="EventCondition" /> class
|
||||
/// </summary>
|
||||
public EventCondition(RenderProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
|
||||
_entity = new EventConditionEntity();
|
||||
_displayName = profileElement.GetType().Name;
|
||||
_eventNode = new EventDefaultNode {X = -300};
|
||||
_script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile);
|
||||
}
|
||||
|
||||
internal EventCondition(EventConditionEntity entity, RenderProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
|
||||
_entity = entity;
|
||||
_displayName = profileElement.GetType().Name;
|
||||
_eventNode = new EventDefaultNode();
|
||||
_script = null!;
|
||||
|
||||
Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the script that drives the event condition
|
||||
/// </summary>
|
||||
public NodeScript<bool> Script
|
||||
{
|
||||
get => _script;
|
||||
set => SetAndNotify(ref _script, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path to the event that drives this event condition
|
||||
/// </summary>
|
||||
public DataModelPath? EventPath
|
||||
{
|
||||
get => _eventPath;
|
||||
set => SetAndNotify(ref _eventPath, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how the condition behaves when the event fires.
|
||||
/// </summary>
|
||||
public EventTriggerMode TriggerMode
|
||||
{
|
||||
get => _triggerMode;
|
||||
set => SetAndNotify(ref _triggerMode, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how the condition behaves when events trigger before the timeline finishes in the
|
||||
/// <see cref="EventTriggerMode.Play" /> event trigger mode.
|
||||
/// </summary>
|
||||
public EventOverlapMode OverlapMode
|
||||
{
|
||||
get => _overlapMode;
|
||||
set => SetAndNotify(ref _overlapMode, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the event node, applying the selected event
|
||||
/// </summary>
|
||||
public void UpdateEventNode()
|
||||
{
|
||||
IDataModelEvent? dataModelEvent = EventPath?.GetValue() as IDataModelEvent;
|
||||
_eventNode.CreatePins(dataModelEvent);
|
||||
|
||||
if (dataModelEvent != null && !Script.Nodes.Contains(_eventNode))
|
||||
Script.AddNode(_eventNode);
|
||||
else if (dataModelEvent == null && Script.Nodes.Contains(_eventNode))
|
||||
Script.RemoveNode(_eventNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the start node of the event script, if any
|
||||
/// </summary>
|
||||
/// <returns>The start node of the event script, if any.</returns>
|
||||
public INode GetStartNode()
|
||||
{
|
||||
return _eventNode;
|
||||
}
|
||||
|
||||
private bool Evaluate()
|
||||
{
|
||||
if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger)
|
||||
return false;
|
||||
|
||||
_lastProcessedTrigger = dataModelEvent.LastTrigger;
|
||||
|
||||
if (!Script.ExitNodeConnected)
|
||||
return true;
|
||||
|
||||
Script.Run();
|
||||
return Script.Result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IConditionEntity Entity => _entity;
|
||||
|
||||
/// <inheritdoc />
|
||||
public RenderProfileElement ProfileElement { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMet { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
{
|
||||
_wasMet = IsMet;
|
||||
if (TriggerMode == EventTriggerMode.Toggle)
|
||||
{
|
||||
if (Evaluate())
|
||||
IsMet = !IsMet;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsMet = Evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateTimeline(double deltaTime)
|
||||
{
|
||||
if (TriggerMode == EventTriggerMode.Toggle)
|
||||
{
|
||||
if (!IsMet && _wasMet)
|
||||
ProfileElement.Timeline.JumpToEnd();
|
||||
else if (IsMet && !_wasMet)
|
||||
ProfileElement.Timeline.JumpToStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsMet && ProfileElement.Timeline.IsFinished)
|
||||
{
|
||||
ProfileElement.Timeline.JumpToStart();
|
||||
}
|
||||
else if (IsMet)
|
||||
{
|
||||
if (OverlapMode == EventOverlapMode.Restart)
|
||||
ProfileElement.Timeline.JumpToStart();
|
||||
else if (OverlapMode == EventOverlapMode.Copy && ProfileElement is Layer layer)
|
||||
layer.CreateCopyAsChild();
|
||||
}
|
||||
}
|
||||
|
||||
ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), TriggerMode == EventTriggerMode.Toggle);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OverrideTimeline(TimeSpan position)
|
||||
{
|
||||
ProfileElement.Timeline.Override(position, TriggerMode == EventTriggerMode.Toggle && position > ProfileElement.Timeline.Length);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Script?.Dispose();
|
||||
EventPath?.Dispose();
|
||||
}
|
||||
|
||||
#region Storage
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
TriggerMode = (EventTriggerMode) _entity.TriggerMode;
|
||||
OverlapMode = (EventOverlapMode) _entity.OverlapMode;
|
||||
|
||||
if (_entity.EventPath != null)
|
||||
EventPath = new DataModelPath(_entity.EventPath);
|
||||
|
||||
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}", ProfileElement.Profile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
_entity.TriggerMode = (int) TriggerMode;
|
||||
_entity.OverlapMode = (int) OverlapMode;
|
||||
Script.Save();
|
||||
_entity.Script = Script?.Entity;
|
||||
EventPath?.Save();
|
||||
_entity.EventPath = EventPath?.Entity;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public INodeScript NodeScript => Script;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void LoadNodeScript()
|
||||
{
|
||||
Script.Load();
|
||||
UpdateEventNode();
|
||||
Script.LoadConnections();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a mode for render elements to start their timeline when display conditions events are fired.
|
||||
/// </summary>
|
||||
public enum EventTriggerMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a condition that is based on a <see cref="DataModelEvent" />
|
||||
/// Play the timeline once.
|
||||
/// </summary>
|
||||
public class EventCondition : CorePropertyChanged, INodeScriptCondition
|
||||
{
|
||||
private readonly string _displayName;
|
||||
private readonly EventConditionEntity _entity;
|
||||
private EventDefaultNode? _eventNode;
|
||||
private TimeLineEventOverlapMode _eventOverlapMode;
|
||||
private DataModelPath? _eventPath;
|
||||
private DateTime _lastProcessedTrigger;
|
||||
private NodeScript<bool>? _script;
|
||||
Play,
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="EventCondition" /> class
|
||||
/// </summary>
|
||||
public EventCondition(ProfileElement profileElement)
|
||||
{
|
||||
_entity = new EventConditionEntity();
|
||||
_displayName = profileElement.GetType().Name;
|
||||
/// <summary>
|
||||
/// Toggle repeating the timeline.
|
||||
/// </summary>
|
||||
Toggle
|
||||
}
|
||||
|
||||
ProfileElement = profileElement;
|
||||
}
|
||||
/// <summary>
|
||||
/// Represents a mode for render elements to configure the behaviour of events that overlap i.e. trigger again before
|
||||
/// the timeline finishes.
|
||||
/// </summary>
|
||||
public enum EventOverlapMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Stop the current run and restart the timeline
|
||||
/// </summary>
|
||||
Restart,
|
||||
|
||||
internal EventCondition(EventConditionEntity entity, ProfileElement profileElement)
|
||||
{
|
||||
_entity = entity;
|
||||
_displayName = profileElement.GetType().Name;
|
||||
/// <summary>
|
||||
/// Ignore subsequent event fires until the timeline finishes
|
||||
/// </summary>
|
||||
Ignore,
|
||||
|
||||
ProfileElement = profileElement;
|
||||
Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the script that drives the event condition
|
||||
/// </summary>
|
||||
public NodeScript<bool>? Script
|
||||
{
|
||||
get => _script;
|
||||
set => SetAndNotify(ref _script, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path to the event that drives this event condition
|
||||
/// </summary>
|
||||
public DataModelPath? EventPath
|
||||
{
|
||||
get => _eventPath;
|
||||
set => SetAndNotify(ref _eventPath, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how the condition behaves when events trigger before the timeline finishes
|
||||
/// </summary>
|
||||
public TimeLineEventOverlapMode EventOverlapMode
|
||||
{
|
||||
get => _eventOverlapMode;
|
||||
set => SetAndNotify(ref _eventOverlapMode, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the event node, applying the selected event
|
||||
/// </summary>
|
||||
public void UpdateEventNode()
|
||||
{
|
||||
if (Script == null || EventPath?.GetValue() is not IDataModelEvent dataModelEvent)
|
||||
return;
|
||||
|
||||
if (Script.Nodes.FirstOrDefault(n => n is EventDefaultNode) is EventDefaultNode existing)
|
||||
{
|
||||
existing.UpdateDataModelEvent(dataModelEvent);
|
||||
_eventNode = existing;
|
||||
}
|
||||
else
|
||||
{
|
||||
_eventNode = new EventDefaultNode {X = -300};
|
||||
_eventNode.UpdateDataModelEvent(dataModelEvent);
|
||||
}
|
||||
|
||||
if (_eventNode.Pins.Any() && !Script.Nodes.Contains(_eventNode))
|
||||
Script.AddNode(_eventNode);
|
||||
else
|
||||
Script.RemoveNode(_eventNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="Script" /> with a new empty node script
|
||||
/// </summary>
|
||||
public void CreateEmptyNodeScript()
|
||||
{
|
||||
Script?.Dispose();
|
||||
Script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile);
|
||||
UpdateEventNode();
|
||||
}
|
||||
|
||||
private bool Evaluate()
|
||||
{
|
||||
if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger)
|
||||
return false;
|
||||
|
||||
_lastProcessedTrigger = dataModelEvent.LastTrigger;
|
||||
|
||||
if (Script == null)
|
||||
return true;
|
||||
|
||||
Script.Run();
|
||||
return Script.Result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IConditionEntity Entity => _entity;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ProfileElement ProfileElement { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMet { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
{
|
||||
if (EventOverlapMode == TimeLineEventOverlapMode.Toggle)
|
||||
{
|
||||
if (Evaluate())
|
||||
IsMet = !IsMet;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsMet = Evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline)
|
||||
{
|
||||
if (!isMet)
|
||||
{
|
||||
if (EventOverlapMode == TimeLineEventOverlapMode.Toggle)
|
||||
timeline.JumpToEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
// Event overlap mode doesn't apply in this case
|
||||
if (timeline.IsFinished)
|
||||
{
|
||||
timeline.JumpToStart();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the timeline was already running, look at the event overlap mode
|
||||
if (EventOverlapMode == TimeLineEventOverlapMode.Restart)
|
||||
timeline.JumpToStart();
|
||||
else if (EventOverlapMode == TimeLineEventOverlapMode.Copy && ProfileElement is Layer layer)
|
||||
layer.CreateCopyAsChild();
|
||||
else if (EventOverlapMode == TimeLineEventOverlapMode.Toggle && !wasMet)
|
||||
timeline.JumpToStart();
|
||||
|
||||
// The remaining overlap mode is 'ignore' which requires no further action
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Script?.Dispose();
|
||||
EventPath?.Dispose();
|
||||
}
|
||||
|
||||
#region Storage
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
EventOverlapMode = (TimeLineEventOverlapMode) _entity.EventOverlapMode;
|
||||
if (_entity.Script != null)
|
||||
Script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", _entity.Script, ProfileElement.Profile);
|
||||
if (_entity.EventPath != null)
|
||||
EventPath = new DataModelPath(_entity.EventPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
_entity.EventOverlapMode = (int) EventOverlapMode;
|
||||
Script?.Save();
|
||||
_entity.Script = Script?.Entity;
|
||||
EventPath?.Save();
|
||||
_entity.EventPath = EventPath?.Entity;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public INodeScript NodeScript => Script;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void LoadNodeScript()
|
||||
{
|
||||
if (Script == null)
|
||||
return;
|
||||
|
||||
Script.Load();
|
||||
UpdateEventNode();
|
||||
Script.LoadConnections();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
/// <summary>
|
||||
/// Play another copy of the timeline on top of the current run
|
||||
/// </summary>
|
||||
Copy
|
||||
}
|
||||
@ -16,7 +16,7 @@ public interface ICondition : IDisposable, IStorageModel
|
||||
/// <summary>
|
||||
/// Gets the profile element this condition applies to
|
||||
/// </summary>
|
||||
public ProfileElement ProfileElement { get; }
|
||||
public RenderProfileElement ProfileElement { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the condition is currently met
|
||||
@ -30,12 +30,11 @@ public interface ICondition : IDisposable, IStorageModel
|
||||
void Update();
|
||||
|
||||
/// <summary>
|
||||
/// Applies the display condition to the provided timeline
|
||||
/// Updates the timeline according to the provided <paramref name="deltaTime" /> as the display condition sees fit.
|
||||
/// </summary>
|
||||
/// <param name="isMet"></param>
|
||||
/// <param name="wasMet"></param>
|
||||
/// <param name="timeline">The timeline to apply the display condition to</param>
|
||||
void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline);
|
||||
void UpdateTimeline(double deltaTime);
|
||||
|
||||
void OverrideTimeline(TimeSpan position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
public class PlayOnceCondition : ICondition
|
||||
{
|
||||
public PlayOnceCondition(RenderProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
Entity = new PlayOnceConditionEntity();
|
||||
}
|
||||
|
||||
public PlayOnceCondition(PlayOnceConditionEntity entity, RenderProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
#region Implementation of IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IStorageModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ICondition
|
||||
|
||||
/// <inheritdoc />
|
||||
public IConditionEntity Entity { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public RenderProfileElement ProfileElement { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMet { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
{
|
||||
if (ProfileElement.Parent is RenderProfileElement parent)
|
||||
IsMet = parent.DisplayConditionMet;
|
||||
else
|
||||
IsMet = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateTimeline(double deltaTime)
|
||||
{
|
||||
ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OverrideTimeline(TimeSpan position)
|
||||
{
|
||||
ProfileElement.Timeline.Override(position, false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using System;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
@ -10,11 +11,14 @@ namespace Artemis.Core
|
||||
{
|
||||
private readonly string _displayName;
|
||||
private readonly StaticConditionEntity _entity;
|
||||
private StaticPlayMode _playMode;
|
||||
private StaticStopMode _stopMode;
|
||||
private bool _wasMet;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="StaticCondition" /> class
|
||||
/// </summary>
|
||||
public StaticCondition(ProfileElement profileElement)
|
||||
public StaticCondition(RenderProfileElement profileElement)
|
||||
{
|
||||
_entity = new StaticConditionEntity();
|
||||
_displayName = profileElement.GetType().Name;
|
||||
@ -23,7 +27,7 @@ namespace Artemis.Core
|
||||
Script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not this {_displayName} should be active", profileElement.Profile);
|
||||
}
|
||||
|
||||
internal StaticCondition(StaticConditionEntity entity, ProfileElement profileElement)
|
||||
internal StaticCondition(StaticConditionEntity entity, RenderProfileElement profileElement)
|
||||
{
|
||||
_entity = entity;
|
||||
_displayName = profileElement.GetType().Name;
|
||||
@ -43,31 +47,66 @@ namespace Artemis.Core
|
||||
public IConditionEntity Entity => _entity;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ProfileElement ProfileElement { get; }
|
||||
public RenderProfileElement ProfileElement { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMet { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
/// <summary>
|
||||
/// Gets or sets the mode in which the render element starts its timeline when display conditions are met
|
||||
/// </summary>
|
||||
public StaticPlayMode PlayMode
|
||||
{
|
||||
if (!Script.ExitNodeConnected)
|
||||
{
|
||||
IsMet = true;
|
||||
return;
|
||||
}
|
||||
get => _playMode;
|
||||
set => SetAndNotify(ref _playMode, value);
|
||||
}
|
||||
|
||||
Script.Run();
|
||||
IsMet = Script.Result;
|
||||
/// <summary>
|
||||
/// Gets or sets the mode in which the render element stops its timeline when display conditions are no longer met
|
||||
/// </summary>
|
||||
public StaticStopMode StopMode
|
||||
{
|
||||
get => _stopMode;
|
||||
set => SetAndNotify(ref _stopMode, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline)
|
||||
public void Update()
|
||||
{
|
||||
if (isMet && !wasMet && timeline.IsFinished)
|
||||
timeline.JumpToStart();
|
||||
else if (!isMet && wasMet && timeline.StopMode == TimelineStopMode.SkipToEnd)
|
||||
timeline.JumpToEndSegment();
|
||||
_wasMet = IsMet;
|
||||
|
||||
// No need to run the script if the parent isn't met anyway
|
||||
bool parentConditionMet = ProfileElement.Parent is not RenderProfileElement renderProfileElement || renderProfileElement.DisplayConditionMet;
|
||||
if (!parentConditionMet)
|
||||
{
|
||||
IsMet = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Script.ExitNodeConnected)
|
||||
IsMet = true;
|
||||
else
|
||||
{
|
||||
Script.Run();
|
||||
IsMet = Script.Result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateTimeline(double deltaTime)
|
||||
{
|
||||
if (IsMet && !_wasMet && ProfileElement.Timeline.IsFinished)
|
||||
ProfileElement.Timeline.JumpToStart();
|
||||
else if (!IsMet && _wasMet && StopMode == StaticStopMode.SkipToEnd)
|
||||
ProfileElement.Timeline.JumpToEndSegment();
|
||||
|
||||
ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), PlayMode == StaticPlayMode.Repeat && IsMet);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OverrideTimeline(TimeSpan position)
|
||||
{
|
||||
ProfileElement.Timeline.Override(position, PlayMode == StaticPlayMode.Repeat && position > ProfileElement.Timeline.Length);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -81,12 +120,18 @@ namespace Artemis.Core
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
PlayMode = (StaticPlayMode) _entity.PlayMode;
|
||||
StopMode = (StaticStopMode) _entity.StopMode;
|
||||
|
||||
Script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not this {_displayName} should be active", _entity.Script, ProfileElement.Profile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
_entity.PlayMode = (int) PlayMode;
|
||||
_entity.StopMode = (int) StopMode;
|
||||
|
||||
Script.Save();
|
||||
_entity.Script = Script.Entity;
|
||||
}
|
||||
@ -102,4 +147,36 @@ namespace Artemis.Core
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a mode for render elements to start their timeline when display conditions are met
|
||||
/// </summary>
|
||||
public enum StaticPlayMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Continue repeating the main segment of the timeline while the condition is met
|
||||
/// </summary>
|
||||
Repeat,
|
||||
|
||||
/// <summary>
|
||||
/// Only play the timeline once when the condition is met
|
||||
/// </summary>
|
||||
Once
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a mode for render elements to stop their timeline when display conditions are no longer met
|
||||
/// </summary>
|
||||
public enum StaticStopMode
|
||||
{
|
||||
/// <summary>
|
||||
/// When conditions are no longer met, finish the the current run of the main timeline
|
||||
/// </summary>
|
||||
Finish,
|
||||
|
||||
/// <summary>
|
||||
/// When conditions are no longer met, skip to the end segment of the timeline
|
||||
/// </summary>
|
||||
SkipToEnd
|
||||
}
|
||||
}
|
||||
@ -237,11 +237,11 @@ namespace Artemis.Core
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OverrideTimelineAndApply(TimeSpan position, bool stickToMainSegment)
|
||||
public override void OverrideTimelineAndApply(TimeSpan position)
|
||||
{
|
||||
Timeline.Override(position, stickToMainSegment);
|
||||
DisplayCondition.OverrideTimeline(position);
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||
baseLayerEffect.InternalUpdate(Timeline);
|
||||
baseLayerEffect.InternalUpdate(Timeline); ;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@ -476,9 +476,9 @@ namespace Artemis.Core
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OverrideTimelineAndApply(TimeSpan position, bool stickToMainSegment)
|
||||
public override void OverrideTimelineAndApply(TimeSpan position)
|
||||
{
|
||||
Timeline.Override(position, stickToMainSegment);
|
||||
DisplayCondition.OverrideTimeline(position);
|
||||
|
||||
General.Update(Timeline);
|
||||
Transform.Update(Timeline);
|
||||
|
||||
@ -27,6 +27,7 @@ namespace Artemis.Core
|
||||
LayerEffectsList = new List<BaseLayerEffect>();
|
||||
LayerEffects = new ReadOnlyCollection<BaseLayerEffect>(LayerEffectsList);
|
||||
Parent = parent ?? throw new ArgumentNullException(nameof(parent));
|
||||
_displayCondition = new AlwaysOnCondition(this);
|
||||
|
||||
LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded;
|
||||
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
|
||||
@ -52,7 +53,7 @@ namespace Artemis.Core
|
||||
/// Occurs when a layer effect has been added or removed to this render element
|
||||
/// </summary>
|
||||
public event EventHandler? LayerEffectsUpdated;
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
@ -76,8 +77,10 @@ namespace Artemis.Core
|
||||
|
||||
DisplayCondition = RenderElementEntity.DisplayCondition switch
|
||||
{
|
||||
StaticConditionEntity staticConditionEntity => new StaticCondition(staticConditionEntity, this),
|
||||
EventConditionEntity eventConditionEntity => new EventCondition(eventConditionEntity, this),
|
||||
AlwaysOnConditionEntity entity => new AlwaysOnCondition(entity, this),
|
||||
PlayOnceConditionEntity entity => new PlayOnceCondition(entity, this),
|
||||
StaticConditionEntity entity => new StaticCondition(entity, this),
|
||||
EventConditionEntity entity => new EventCondition(entity, this),
|
||||
_ => DisplayCondition
|
||||
};
|
||||
|
||||
@ -124,17 +127,11 @@ namespace Artemis.Core
|
||||
public Timeline Timeline { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="Timeline" /> according to the provided <paramref name="deltaTime" /> and current display
|
||||
/// condition status
|
||||
/// Updates the <see cref="Timeline" /> according to the provided <paramref name="deltaTime" /> and current display condition
|
||||
/// </summary>
|
||||
protected void UpdateTimeline(double deltaTime)
|
||||
{
|
||||
// TODO: Move to conditions
|
||||
|
||||
// The play mode dictates whether to stick to the main segment unless the display conditions contains events
|
||||
bool stickToMainSegment = Timeline.PlayMode == TimelinePlayMode.Repeat && DisplayConditionMet;
|
||||
|
||||
Timeline.Update(TimeSpan.FromSeconds(deltaTime), stickToMainSegment);
|
||||
DisplayCondition.UpdateTimeline(deltaTime);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -317,7 +314,7 @@ namespace Artemis.Core
|
||||
|
||||
OrderEffects();
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal void ActivateLayerEffect(BaseLayerEffect layerEffect)
|
||||
{
|
||||
@ -365,13 +362,13 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// Gets or sets the display condition used to determine whether this element is active or not
|
||||
/// </summary>
|
||||
public ICondition? DisplayCondition
|
||||
public ICondition DisplayCondition
|
||||
{
|
||||
get => _displayCondition;
|
||||
set => SetAndNotify(ref _displayCondition, value);
|
||||
}
|
||||
|
||||
private ICondition? _displayCondition;
|
||||
private ICondition _displayCondition;
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the display conditions on this element and applies any required changes to the <see cref="Timeline" />
|
||||
@ -384,14 +381,7 @@ namespace Artemis.Core
|
||||
return;
|
||||
}
|
||||
|
||||
if (DisplayCondition == null)
|
||||
{
|
||||
DisplayConditionMet = true;
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayCondition.Update();
|
||||
DisplayCondition.ApplyToTimeline(DisplayCondition.IsMet, DisplayConditionMet, Timeline);
|
||||
DisplayConditionMet = DisplayCondition.IsMet;
|
||||
}
|
||||
|
||||
@ -401,7 +391,6 @@ namespace Artemis.Core
|
||||
/// Overrides the main timeline to the specified time and clears any extra time lines
|
||||
/// </summary>
|
||||
/// <param name="position">The position to set the timeline to</param>
|
||||
/// <param name="stickToMainSegment">Whether to stick to the main segment, wrapping around if needed</param>
|
||||
public abstract void OverrideTimelineAndApply(TimeSpan position, bool stickToMainSegment);
|
||||
public abstract void OverrideTimelineAndApply(TimeSpan position);
|
||||
}
|
||||
}
|
||||
@ -41,8 +41,7 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
||||
|
||||
private TimeSpan _position;
|
||||
private TimeSpan _lastDelta;
|
||||
private TimelinePlayMode _playMode;
|
||||
private TimelineStopMode _stopMode;
|
||||
|
||||
private TimeSpan _startSegmentLength;
|
||||
private TimeSpan _mainSegmentLength;
|
||||
private TimeSpan _endSegmentLength;
|
||||
@ -67,23 +66,7 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
||||
private set => SetAndNotify(ref _lastDelta, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mode in which the render element starts its timeline when display conditions are met
|
||||
/// </summary>
|
||||
public TimelinePlayMode PlayMode
|
||||
{
|
||||
get => _playMode;
|
||||
set => SetAndNotify(ref _playMode, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mode in which the render element stops its timeline when display conditions are no longer met
|
||||
/// </summary>
|
||||
public TimelineStopMode StopMode
|
||||
{
|
||||
get => _stopMode;
|
||||
set => SetAndNotify(ref _stopMode, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the timeline has finished its run
|
||||
@ -388,8 +371,6 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
||||
StartSegmentLength = Entity.StartSegmentLength;
|
||||
MainSegmentLength = Entity.MainSegmentLength;
|
||||
EndSegmentLength = Entity.EndSegmentLength;
|
||||
PlayMode = (TimelinePlayMode) Entity.PlayMode;
|
||||
StopMode = (TimelineStopMode) Entity.StopMode;
|
||||
|
||||
JumpToEnd();
|
||||
}
|
||||
@ -400,8 +381,6 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
||||
Entity.StartSegmentLength = StartSegmentLength;
|
||||
Entity.MainSegmentLength = MainSegmentLength;
|
||||
Entity.EndSegmentLength = EndSegmentLength;
|
||||
Entity.PlayMode = (int) PlayMode;
|
||||
Entity.StopMode = (int) StopMode;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -413,61 +392,3 @@ internal enum TimelineSegment
|
||||
Main,
|
||||
End
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a mode for render elements to start their timeline when display conditions are met
|
||||
/// </summary>
|
||||
public enum TimelinePlayMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Continue repeating the main segment of the timeline while the condition is met
|
||||
/// </summary>
|
||||
Repeat,
|
||||
|
||||
/// <summary>
|
||||
/// Only play the timeline once when the condition is met
|
||||
/// </summary>
|
||||
Once
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a mode for render elements to stop their timeline when display conditions are no longer met
|
||||
/// </summary>
|
||||
public enum TimelineStopMode
|
||||
{
|
||||
/// <summary>
|
||||
/// When conditions are no longer met, finish the the current run of the main timeline
|
||||
/// </summary>
|
||||
Finish,
|
||||
|
||||
/// <summary>
|
||||
/// When conditions are no longer met, skip to the end segment of the timeline
|
||||
/// </summary>
|
||||
SkipToEnd
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a mode for render elements to start their timeline when display conditions events are fired
|
||||
/// </summary>
|
||||
public enum TimeLineEventOverlapMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Stop the current run and restart the timeline
|
||||
/// </summary>
|
||||
Restart,
|
||||
|
||||
/// <summary>
|
||||
/// Ignore subsequent event fires until the timeline finishes
|
||||
/// </summary>
|
||||
Ignore,
|
||||
|
||||
/// <summary>
|
||||
/// Play another copy of the timeline on top of the current run
|
||||
/// </summary>
|
||||
Copy,
|
||||
|
||||
/// <summary>
|
||||
/// Repeat the timeline until the event fires again
|
||||
/// </summary>
|
||||
Toggle
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.Modules;
|
||||
@ -9,6 +10,7 @@ namespace Artemis.Core.Internal
|
||||
internal class EventDefaultNode : Node
|
||||
{
|
||||
private readonly Dictionary<PropertyInfo, OutputPin> _propertyPins;
|
||||
private readonly List<OutputPin> _pinBucket = new();
|
||||
private IDataModelEvent? _dataModelEvent;
|
||||
|
||||
public EventDefaultNode() : base("Event Arguments", "Contains the event arguments that triggered the evaluation")
|
||||
@ -18,19 +20,22 @@ namespace Artemis.Core.Internal
|
||||
|
||||
public override bool IsDefaultNode => true;
|
||||
|
||||
public void UpdateDataModelEvent(IDataModelEvent dataModelEvent)
|
||||
public void CreatePins(IDataModelEvent? dataModelEvent)
|
||||
{
|
||||
if (_dataModelEvent == dataModelEvent)
|
||||
return;
|
||||
|
||||
foreach (var (_, outputPin) in _propertyPins)
|
||||
RemovePin(outputPin);
|
||||
while (Pins.Any())
|
||||
RemovePin((Pin) Pins.First());
|
||||
_propertyPins.Clear();
|
||||
|
||||
_dataModelEvent = dataModelEvent;
|
||||
if (dataModelEvent == null)
|
||||
return;
|
||||
|
||||
foreach (PropertyInfo propertyInfo in dataModelEvent.ArgumentsType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(p => p.CustomAttributes.All(a => a.AttributeType != typeof(DataModelIgnoreAttribute))))
|
||||
_propertyPins.Add(propertyInfo, CreateOutputPin(propertyInfo.PropertyType, propertyInfo.Name.Humanize()));
|
||||
.Where(p => p.CustomAttributes.All(a => a.AttributeType != typeof(DataModelIgnoreAttribute))))
|
||||
_propertyPins.Add(propertyInfo, CreateOrAddOutputPin(propertyInfo.PropertyType, propertyInfo.Name.Humanize()));
|
||||
}
|
||||
|
||||
public override void Evaluate()
|
||||
@ -38,11 +43,38 @@ namespace Artemis.Core.Internal
|
||||
if (_dataModelEvent?.LastEventArgumentsUntyped == null)
|
||||
return;
|
||||
|
||||
foreach (var (propertyInfo, outputPin) in _propertyPins)
|
||||
foreach ((PropertyInfo propertyInfo, OutputPin outputPin) in _propertyPins)
|
||||
{
|
||||
if (outputPin.ConnectedTo.Any())
|
||||
outputPin.Value = propertyInfo.GetValue(_dataModelEvent.LastEventArgumentsUntyped) ?? outputPin.Type.GetDefault()!;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates or adds an input pin to the node using a bucket.
|
||||
/// The bucket might grow a bit over time as the user edits the node but pins won't get lost, enabling undo/redo in the
|
||||
/// editor.
|
||||
/// </summary>
|
||||
private OutputPin CreateOrAddOutputPin(Type valueType, string displayName)
|
||||
{
|
||||
// Grab the first pin from the bucket that isn't on the node yet
|
||||
OutputPin? pin = _pinBucket.FirstOrDefault(p => !Pins.Contains(p));
|
||||
|
||||
// If there is none, create a new one and add it to the bucket
|
||||
if (pin == null)
|
||||
{
|
||||
pin = CreateOutputPin(valueType, displayName);
|
||||
_pinBucket.Add(pin);
|
||||
}
|
||||
// If there was a pin in the bucket, update it's type and display name and reuse it
|
||||
else
|
||||
{
|
||||
pin.ChangeType(valueType);
|
||||
pin.Name = displayName;
|
||||
AddPin(pin);
|
||||
}
|
||||
|
||||
return pin;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -280,7 +280,7 @@ public abstract class Node : CorePropertyChanged, INode
|
||||
/// Serializes the <see cref="Storage" /> object into a string
|
||||
/// </summary>
|
||||
/// <returns>The serialized object</returns>
|
||||
internal virtual string SerializeStorage()
|
||||
public virtual string SerializeStorage()
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
@ -289,7 +289,7 @@ public abstract class Node : CorePropertyChanged, INode
|
||||
/// Deserializes the <see cref="Storage" /> object and sets it
|
||||
/// </summary>
|
||||
/// <param name="serialized">The serialized object</param>
|
||||
internal virtual void DeserializeStorage(string serialized)
|
||||
public virtual void DeserializeStorage(string serialized)
|
||||
{
|
||||
}
|
||||
|
||||
@ -333,12 +333,14 @@ public abstract class Node<TStorage> : Node
|
||||
/// </summary>
|
||||
public event EventHandler? StorageModified;
|
||||
|
||||
internal override string SerializeStorage()
|
||||
/// <inheritdoc />
|
||||
public override string SerializeStorage()
|
||||
{
|
||||
return CoreJson.SerializeObject(Storage, true);
|
||||
}
|
||||
|
||||
internal override void DeserializeStorage(string serialized)
|
||||
/// <inheritdoc />
|
||||
public override void DeserializeStorage(string serialized)
|
||||
{
|
||||
Storage = CoreJson.DeserializeObject<TStorage>(serialized) ?? default(TStorage);
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Nodes;
|
||||
|
||||
namespace Artemis.Storage.Entities.Profile.Conditions
|
||||
{
|
||||
public class AlwaysOnConditionEntity : IConditionEntity
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,8 @@ namespace Artemis.Storage.Entities.Profile.Conditions
|
||||
{
|
||||
public class EventConditionEntity : IConditionEntity
|
||||
{
|
||||
public int EventOverlapMode { get; set; }
|
||||
public int TriggerMode { get; set; }
|
||||
public int OverlapMode { get; set; }
|
||||
public DataModelPathEntity EventPath { get; set; }
|
||||
public NodeScriptEntity Script { get; set; }
|
||||
}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Nodes;
|
||||
|
||||
namespace Artemis.Storage.Entities.Profile.Conditions
|
||||
{
|
||||
public class PlayOnceConditionEntity : IConditionEntity
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,8 @@ namespace Artemis.Storage.Entities.Profile.Conditions
|
||||
{
|
||||
public class StaticConditionEntity : IConditionEntity
|
||||
{
|
||||
public int PlayMode { get; set; }
|
||||
public int StopMode { get; set; }
|
||||
public NodeScriptEntity Script { get; set; }
|
||||
}
|
||||
}
|
||||
@ -7,8 +7,5 @@ namespace Artemis.Storage.Entities.Profile
|
||||
public TimeSpan StartSegmentLength { get; set; }
|
||||
public TimeSpan MainSegmentLength { get; set; }
|
||||
public TimeSpan EndSegmentLength { get; set; }
|
||||
|
||||
public int PlayMode { get; set; }
|
||||
public int StopMode { get; set; }
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,7 @@ public class DeleteKeyframe : IProfileEditorCommand
|
||||
private readonly ILayerPropertyKeyframe _keyframe;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DeleteKeyframe{T}" /> class.
|
||||
/// Creates a new instance of the <see cref="DeleteKeyframe" /> class.
|
||||
/// </summary>
|
||||
public DeleteKeyframe(ILayerPropertyKeyframe keyframe)
|
||||
{
|
||||
|
||||
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Services.NodeEditor;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a profile editor command that can be used to update an event condition's trigger mode.
|
||||
/// </summary>
|
||||
public class UpdateEventConditionPath : IProfileEditorCommand, IDisposable
|
||||
{
|
||||
private readonly EventCondition _eventCondition;
|
||||
private readonly DataModelPath? _value;
|
||||
private readonly DataModelPath? _oldValue;
|
||||
private readonly NodeConnectionStore? _store;
|
||||
private bool _executed;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="UpdateEventTriggerMode" /> class.
|
||||
/// </summary>
|
||||
public UpdateEventConditionPath(EventCondition eventCondition, DataModelPath? value)
|
||||
{
|
||||
_eventCondition = eventCondition;
|
||||
_value = value;
|
||||
_oldValue = eventCondition.EventPath;
|
||||
|
||||
INode? startNode = eventCondition.GetStartNode();
|
||||
if (startNode != null)
|
||||
_store = new NodeConnectionStore(startNode);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => "Update event path";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Execute()
|
||||
{
|
||||
// Store old connections
|
||||
_store?.Store();
|
||||
|
||||
// Change the end node
|
||||
_eventCondition.EventPath = _value;
|
||||
_eventCondition.UpdateEventNode();
|
||||
|
||||
_executed = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
// Change the end node
|
||||
_eventCondition.EventPath = _oldValue;
|
||||
_eventCondition.UpdateEventNode();
|
||||
|
||||
// Restore old connections
|
||||
_store?.Restore();
|
||||
|
||||
_executed = false;
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_executed)
|
||||
_oldValue?.Dispose();
|
||||
else
|
||||
_value?.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a profile editor command that can be used to update an event condition's overlap mode.
|
||||
/// </summary>
|
||||
public class UpdateEventOverlapMode : IProfileEditorCommand
|
||||
{
|
||||
private readonly EventCondition _eventCondition;
|
||||
private readonly EventOverlapMode _value;
|
||||
private readonly EventOverlapMode _oldValue;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="UpdateEventOverlapMode" /> class.
|
||||
/// </summary>
|
||||
public UpdateEventOverlapMode(EventCondition eventCondition, EventOverlapMode value)
|
||||
{
|
||||
_eventCondition = eventCondition;
|
||||
_value = value;
|
||||
_oldValue = eventCondition.OverlapMode;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => "Update event overlap mode";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Execute()
|
||||
{
|
||||
_eventCondition.OverlapMode = _value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
_eventCondition.OverlapMode = _oldValue;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a profile editor command that can be used to update an event condition's trigger mode.
|
||||
/// </summary>
|
||||
public class UpdateEventTriggerMode : IProfileEditorCommand
|
||||
{
|
||||
private readonly EventCondition _eventCondition;
|
||||
private readonly EventTriggerMode _oldValue;
|
||||
private readonly EventTriggerMode _value;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="UpdateEventTriggerMode" /> class.
|
||||
/// </summary>
|
||||
public UpdateEventTriggerMode(EventCondition eventCondition, EventTriggerMode value)
|
||||
{
|
||||
_eventCondition = eventCondition;
|
||||
_value = value;
|
||||
_oldValue = eventCondition.TriggerMode;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => "Update event trigger mode";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Execute()
|
||||
{
|
||||
_eventCondition.TriggerMode = _value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
_eventCondition.TriggerMode = _oldValue;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a profile editor command that can be used to update an static condition's play mode.
|
||||
/// </summary>
|
||||
public class UpdateStaticPlayMode : IProfileEditorCommand
|
||||
{
|
||||
private readonly StaticCondition _staticCondition;
|
||||
private readonly StaticPlayMode _value;
|
||||
private readonly StaticPlayMode _oldValue;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="UpdateEventTriggerMode" /> class.
|
||||
/// </summary>
|
||||
public UpdateStaticPlayMode(StaticCondition staticCondition, StaticPlayMode value)
|
||||
{
|
||||
_staticCondition = staticCondition;
|
||||
_value = value;
|
||||
_oldValue = staticCondition.PlayMode;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => "Update condition play mode";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Execute()
|
||||
{
|
||||
_staticCondition.PlayMode = _value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
_staticCondition.PlayMode = _oldValue;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a profile editor command that can be used to update an static condition's play mode.
|
||||
/// </summary>
|
||||
public class UpdateStaticStopMode : IProfileEditorCommand
|
||||
{
|
||||
private readonly StaticCondition _staticCondition;
|
||||
private readonly StaticStopMode _value;
|
||||
private readonly StaticStopMode _oldValue;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="UpdateEventTriggerMode" /> class.
|
||||
/// </summary>
|
||||
public UpdateStaticStopMode(StaticCondition staticCondition, StaticStopMode value)
|
||||
{
|
||||
_staticCondition = staticCondition;
|
||||
_value = value;
|
||||
_oldValue = staticCondition.StopMode;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => "Update condition stop mode";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Execute()
|
||||
{
|
||||
_staticCondition.StopMode = _value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
_staticCondition.StopMode = _oldValue;
|
||||
}
|
||||
}
|
||||
@ -96,8 +96,7 @@ internal class ProfileEditorService : IProfileEditorService
|
||||
else
|
||||
{
|
||||
renderElement.Enable();
|
||||
bool stickToMainSegment = (renderElement != _profileElementSubject.Value || renderElement.Timeline.Length < time) && renderElement.Timeline.PlayMode == TimelinePlayMode.Repeat;
|
||||
renderElement.OverrideTimelineAndApply(time, stickToMainSegment);
|
||||
renderElement.OverrideTimelineAndApply(time);
|
||||
|
||||
foreach (ProfileElement child in renderElement.Children)
|
||||
TickProfileElement(child, time);
|
||||
|
||||
@ -5,6 +5,7 @@ using Artemis.Core.LayerEffects;
|
||||
using Artemis.UI.Screens.Device;
|
||||
using Artemis.UI.Screens.Plugins;
|
||||
using Artemis.UI.Screens.ProfileEditor;
|
||||
using Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes;
|
||||
using Artemis.UI.Screens.ProfileEditor.ProfileTree;
|
||||
using Artemis.UI.Screens.ProfileEditor.Properties;
|
||||
using Artemis.UI.Screens.ProfileEditor.Properties.DataBinding;
|
||||
@ -52,7 +53,7 @@ namespace Artemis.UI.Ninject.Factories
|
||||
SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(SidebarViewModel sidebarViewModel, ProfileConfiguration profileConfiguration);
|
||||
}
|
||||
|
||||
public interface SurfaceVmFactory : IVmFactory
|
||||
public interface ISurfaceVmFactory : IVmFactory
|
||||
{
|
||||
SurfaceDeviceViewModel SurfaceDeviceViewModel(ArtemisDevice device);
|
||||
ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device);
|
||||
@ -108,4 +109,12 @@ namespace Artemis.UI.Ninject.Factories
|
||||
InputPinCollectionViewModel InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel);
|
||||
OutputPinCollectionViewModel OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel);
|
||||
}
|
||||
|
||||
public interface IConditionVmFactory : IVmFactory
|
||||
{
|
||||
AlwaysOnConditionViewModel AlwaysOnConditionViewModel(AlwaysOnCondition alwaysOnCondition);
|
||||
PlayOnceConditionViewModel PlayOnceConditionViewModel(PlayOnceCondition playOnceCondition);
|
||||
StaticConditionViewModel StaticConditionViewModel(StaticCondition staticCondition);
|
||||
EventConditionViewModel EventConditionViewModel(EventCondition eventCondition);
|
||||
}
|
||||
}
|
||||
@ -1,19 +1,18 @@
|
||||
using System;
|
||||
using Artemis.UI.Shared;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition
|
||||
{
|
||||
public class ConditionTypeViewModel : ViewModelBase
|
||||
{
|
||||
public ConditionTypeViewModel(string name, string description, Type? conditionType)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
ConditionType = conditionType;
|
||||
}
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition;
|
||||
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
public Type? ConditionType { get; }
|
||||
public class ConditionTypeViewModel : ViewModelBase
|
||||
{
|
||||
public ConditionTypeViewModel(string name, string description, Type conditionType)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
ConditionType = conditionType;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
public Type ConditionType { get; }
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes.AlwaysOnConditionView">
|
||||
|
||||
<DockPanel VerticalAlignment="Top" Margin="0 5">
|
||||
<avalonia:MaterialIcon Kind="InfoCircle" Margin="5 0"/>
|
||||
<TextBlock Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap">
|
||||
After playing the start segment, the main segment is endlessly repeated.
|
||||
</TextBlock>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
@ -0,0 +1,17 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes;
|
||||
|
||||
public class AlwaysOnConditionView : UserControl
|
||||
{
|
||||
public AlwaysOnConditionView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes;
|
||||
|
||||
public class AlwaysOnConditionViewModel : ViewModelBase
|
||||
{
|
||||
private readonly AlwaysOnCondition _alwaysOnCondition;
|
||||
|
||||
public AlwaysOnConditionViewModel(AlwaysOnCondition alwaysOnCondition)
|
||||
{
|
||||
_alwaysOnCondition = alwaysOnCondition;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker;assembly=Artemis.UI.Shared"
|
||||
xmlns:conditionTypes="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes.EventConditionView"
|
||||
x:DataType="conditionTypes:EventConditionViewModel">
|
||||
<DockPanel HorizontalAlignment="Stretch">
|
||||
<DockPanel.Styles>
|
||||
<Style Selector="DockPanel > TextBlock">
|
||||
<Setter Property="Margin" Value="0 5" />
|
||||
</Style>
|
||||
</DockPanel.Styles>
|
||||
<TextBlock DockPanel.Dock="Top">Triggered by event</TextBlock>
|
||||
<dataModelPicker:DataModelPickerButton Placeholder="Select an event"
|
||||
DockPanel.Dock="Top"
|
||||
HorizontalAlignment="Stretch"
|
||||
FilterTypes="{CompiledBinding FilterTypes}"
|
||||
DataModelPath="{CompiledBinding EventPath}"/>
|
||||
|
||||
<TextBlock DockPanel.Dock="Top">When the event fires..</TextBlock>
|
||||
<ComboBox PlaceholderText="Select a play mode" HorizontalAlignment="Stretch" DockPanel.Dock="Top" SelectedIndex="{CompiledBinding SelectedTriggerMode}">
|
||||
<ComboBoxItem>
|
||||
Play the timeline once
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
Toggle the element on or off
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock DockPanel.Dock="Top" IsVisible="{CompiledBinding ShowOverlapOptions}">And if already playing..</TextBlock>
|
||||
<ComboBox PlaceholderText="Select a play mode"
|
||||
HorizontalAlignment="Stretch"
|
||||
DockPanel.Dock="Top"
|
||||
SelectedIndex="{CompiledBinding SelectedOverlapMode}"
|
||||
IsVisible="{CompiledBinding ShowOverlapOptions}">
|
||||
<ComboBoxItem>
|
||||
Restart the timeline
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem IsEnabled="{CompiledBinding IsConditionForLayer}">
|
||||
Play a second copy
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
Do nothing
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
|
||||
<Button DockPanel.Dock="Bottom"
|
||||
ToolTip.Tip="Open editor"
|
||||
Margin="0 15 0 5"
|
||||
Command="{CompiledBinding OpenEditor}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom">
|
||||
Edit condition script
|
||||
</Button>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
@ -0,0 +1,17 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes;
|
||||
|
||||
public class EventConditionView : ReactiveUserControl<EventConditionViewModel>
|
||||
{
|
||||
public EventConditionView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Screens.VisualScripting;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
using Avalonia.Controls.Mixins;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes;
|
||||
|
||||
public class EventConditionViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly EventCondition _eventCondition;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private readonly ObservableAsPropertyHelper<bool> _showOverlapOptions;
|
||||
private readonly IWindowService _windowService;
|
||||
private ObservableAsPropertyHelper<DataModelPath?>? _eventPath;
|
||||
private ObservableAsPropertyHelper<int>? _selectedOverlapMode;
|
||||
private ObservableAsPropertyHelper<int>? _selectedTriggerMode;
|
||||
|
||||
public EventConditionViewModel(EventCondition eventCondition, IProfileEditorService profileEditorService, IWindowService windowService)
|
||||
{
|
||||
_eventCondition = eventCondition;
|
||||
_profileEditorService = profileEditorService;
|
||||
_windowService = windowService;
|
||||
_showOverlapOptions = this.WhenAnyValue(vm => vm.SelectedTriggerMode)
|
||||
.Select(m => m == 0)
|
||||
.ToProperty(this, vm => vm.ShowOverlapOptions);
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
_eventPath = eventCondition.WhenAnyValue(c => c.EventPath).ToProperty(this, vm => vm.EventPath).DisposeWith(d);
|
||||
_selectedTriggerMode = eventCondition.WhenAnyValue(c => c.TriggerMode).Select(m => (int) m).ToProperty(this, vm => vm.SelectedTriggerMode).DisposeWith(d);
|
||||
_selectedOverlapMode = eventCondition.WhenAnyValue(c => c.OverlapMode).Select(m => (int) m).ToProperty(this, vm => vm.SelectedOverlapMode).DisposeWith(d);
|
||||
});
|
||||
|
||||
OpenEditor = ReactiveCommand.CreateFromTask(ExecuteOpenEditor);
|
||||
}
|
||||
|
||||
public ObservableCollection<Type> FilterTypes { get; } = new() {typeof(IDataModelEvent)};
|
||||
public ReactiveCommand<Unit, Unit> OpenEditor { get; }
|
||||
public bool ShowOverlapOptions => _showOverlapOptions.Value;
|
||||
public bool IsConditionForLayer => _eventCondition.ProfileElement is Layer;
|
||||
|
||||
public DataModelPath? EventPath
|
||||
{
|
||||
get => _eventPath?.Value != null ? new DataModelPath(_eventPath.Value) : null;
|
||||
set => _profileEditorService.ExecuteCommand(new UpdateEventConditionPath(_eventCondition, value != null ? new DataModelPath(value) : null));
|
||||
}
|
||||
|
||||
public int SelectedTriggerMode
|
||||
{
|
||||
get => _selectedTriggerMode?.Value ?? 0;
|
||||
set => _profileEditorService.ExecuteCommand(new UpdateEventTriggerMode(_eventCondition, (EventTriggerMode) value));
|
||||
}
|
||||
|
||||
public int SelectedOverlapMode
|
||||
{
|
||||
get => _selectedOverlapMode?.Value ?? 0;
|
||||
set => _profileEditorService.ExecuteCommand(new UpdateEventOverlapMode(_eventCondition, (EventOverlapMode) value));
|
||||
}
|
||||
|
||||
private async Task ExecuteOpenEditor()
|
||||
{
|
||||
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(("nodeScript", _eventCondition.NodeScript));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes.PlayOnceConditionView">
|
||||
<DockPanel VerticalAlignment="Top" Margin="0 5">
|
||||
<avalonia:MaterialIcon Kind="InfoCircle" Margin="5 0"/>
|
||||
<TextBlock Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap">
|
||||
Every segment of the timeline is played once.
|
||||
</TextBlock>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
@ -0,0 +1,17 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes;
|
||||
|
||||
public class PlayOnceConditionView : UserControl
|
||||
{
|
||||
public PlayOnceConditionView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes;
|
||||
|
||||
public class PlayOnceConditionViewModel : ViewModelBase
|
||||
{
|
||||
private readonly PlayOnceCondition _playOnceCondition;
|
||||
|
||||
public PlayOnceConditionViewModel(PlayOnceCondition playOnceCondition)
|
||||
{
|
||||
_playOnceCondition = playOnceCondition;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:conditionTypes="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes.StaticConditionView"
|
||||
x:DataType="conditionTypes:StaticConditionViewModel">
|
||||
<DockPanel HorizontalAlignment="Stretch">
|
||||
<DockPanel.Styles>
|
||||
<Style Selector="DockPanel > TextBlock">
|
||||
<Setter Property="Margin" Value="0 5" />
|
||||
</Style>
|
||||
</DockPanel.Styles>
|
||||
<TextBlock DockPanel.Dock="Top">While condition is met..</TextBlock>
|
||||
<ComboBox DockPanel.Dock="Top" PlaceholderText="Select a play mode" HorizontalAlignment="Stretch" SelectedIndex="{CompiledBinding SelectedPlayMode}">
|
||||
<ComboBoxItem>Repeat the main segment</ComboBoxItem>
|
||||
<ComboBoxItem>Play the main segment once</ComboBoxItem>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock DockPanel.Dock="Top">And when no longer met..</TextBlock>
|
||||
<ComboBox DockPanel.Dock="Top" PlaceholderText="Select a stop mode" HorizontalAlignment="Stretch" SelectedIndex="{CompiledBinding SelectedStopMode}">
|
||||
<ComboBoxItem>Finish the main segment</ComboBoxItem>
|
||||
<ComboBoxItem>Skip forward to the end segment</ComboBoxItem>
|
||||
</ComboBox>
|
||||
|
||||
<DockPanel DockPanel.Dock="Top" Margin="0 5">
|
||||
<avalonia:MaterialIcon Kind="InfoCircle" Margin="5 0" />
|
||||
<TextBlock Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap">
|
||||
The start- and end-segments are played once each time the condition is met.
|
||||
</TextBlock>
|
||||
</DockPanel>
|
||||
|
||||
<Button DockPanel.Dock="Bottom"
|
||||
ToolTip.Tip="Open editor"
|
||||
Margin="0 15 0 5"
|
||||
Command="{CompiledBinding OpenEditor}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom">
|
||||
Edit condition script
|
||||
</Button>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
@ -0,0 +1,17 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes;
|
||||
|
||||
public class StaticConditionView : ReactiveUserControl<StaticConditionViewModel>
|
||||
{
|
||||
public StaticConditionView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Screens.VisualScripting;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes;
|
||||
|
||||
public class StaticConditionViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private readonly StaticCondition _staticCondition;
|
||||
private readonly IWindowService _windowService;
|
||||
private ObservableAsPropertyHelper<int>? _selectedPlayMode;
|
||||
private ObservableAsPropertyHelper<int>? _selectedStopMode;
|
||||
|
||||
public StaticConditionViewModel(StaticCondition staticCondition, IProfileEditorService profileEditorService, IWindowService windowService)
|
||||
{
|
||||
_staticCondition = staticCondition;
|
||||
_profileEditorService = profileEditorService;
|
||||
_windowService = windowService;
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
_selectedPlayMode = staticCondition.WhenAnyValue(c => c.PlayMode).Select(m => (int) m).ToProperty(this, vm => vm.SelectedPlayMode).DisposeWith(d);
|
||||
_selectedStopMode = staticCondition.WhenAnyValue(c => c.StopMode).Select(m => (int) m).ToProperty(this, vm => vm.SelectedStopMode).DisposeWith(d);
|
||||
});
|
||||
|
||||
OpenEditor = ReactiveCommand.CreateFromTask(ExecuteOpenEditor);
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> OpenEditor { get; }
|
||||
|
||||
public int SelectedPlayMode
|
||||
{
|
||||
get => _selectedPlayMode?.Value ?? 0;
|
||||
set => _profileEditorService.ExecuteCommand(new UpdateStaticPlayMode(_staticCondition, (StaticPlayMode) value));
|
||||
}
|
||||
|
||||
public int SelectedStopMode
|
||||
{
|
||||
get => _selectedStopMode?.Value ?? 0;
|
||||
set => _profileEditorService.ExecuteCommand(new UpdateStaticStopMode(_staticCondition, (StaticStopMode) value));
|
||||
}
|
||||
|
||||
private async Task ExecuteOpenEditor()
|
||||
{
|
||||
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(("nodeScript", _staticCondition.NodeScript));
|
||||
}
|
||||
}
|
||||
@ -3,8 +3,6 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:displayCondition="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayCondition"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="650"
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.DisplayConditionScriptView"
|
||||
x:DataType="displayCondition:DisplayConditionScriptViewModel">
|
||||
@ -20,19 +18,14 @@
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
<ScrollViewer>
|
||||
<DockPanel LastChildFill="False">
|
||||
<DockPanel.Styles>
|
||||
<Style Selector="DockPanel > TextBlock">
|
||||
<Setter Property="Margin" Value="0 5"></Setter>
|
||||
</Style>
|
||||
|
||||
</DockPanel.Styles>
|
||||
<TextBlock DockPanel.Dock="Top">Condition type</TextBlock>
|
||||
<Grid RowDefinitions="Auto,Auto,*">
|
||||
<TextBlock Grid.Row="0" Margin="0 5">Activation type</TextBlock>
|
||||
<ComboBox Name="ConditionType"
|
||||
DockPanel.Dock="Top"
|
||||
Grid.Row="1"
|
||||
Classes="condition-type"
|
||||
PlaceholderText="Select a condition type"
|
||||
PlaceholderText="Select an activation type"
|
||||
Items="{CompiledBinding ConditionTypeViewModels}"
|
||||
IsEnabled="{CompiledBinding ProfileElement, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
SelectedItem="{CompiledBinding SelectedConditionTypeViewModel}"
|
||||
HorizontalAlignment="Stretch">
|
||||
<ComboBox.ItemTemplate>
|
||||
@ -44,64 +37,7 @@
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<!-- Event options -->
|
||||
<DockPanel IsVisible="{CompiledBinding ShowEventOptions}" DockPanel.Dock="Top" HorizontalAlignment="Stretch">
|
||||
<TextBlock DockPanel.Dock="Top">Triggered by event</TextBlock>
|
||||
<dataModelPicker:DataModelPickerButton Placeholder="Select an event"
|
||||
DockPanel.Dock="Top"
|
||||
HorizontalAlignment="Stretch" />
|
||||
|
||||
<TextBlock DockPanel.Dock="Top">When the event fires..</TextBlock>
|
||||
<ComboBox PlaceholderText="Select a play mode" HorizontalAlignment="Stretch" DockPanel.Dock="Top">
|
||||
<ComboBoxItem>
|
||||
Play the timeline once
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
Toggle the element on or off
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock DockPanel.Dock="Top">And if already playing..</TextBlock>
|
||||
<ComboBox PlaceholderText="Select a play mode" HorizontalAlignment="Stretch" DockPanel.Dock="Top">
|
||||
<ComboBoxItem>
|
||||
Restart the timeline
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
Play a second copy
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
Do nothing
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</DockPanel>
|
||||
|
||||
<!-- Static options -->
|
||||
<DockPanel IsVisible="{CompiledBinding ShowStaticOptions}" HorizontalAlignment="Stretch" DockPanel.Dock="Top">
|
||||
<TextBlock DockPanel.Dock="Top">While condition is met..</TextBlock>
|
||||
<ComboBox DockPanel.Dock="Top" PlaceholderText="Select a play mode" HorizontalAlignment="Stretch">
|
||||
<ComboBoxItem>Play the main segment once</ComboBoxItem>
|
||||
<ComboBoxItem>Repeat the main segment</ComboBoxItem>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock DockPanel.Dock="Top">And when no longer met..</TextBlock>
|
||||
<ComboBox DockPanel.Dock="Top" PlaceholderText="Select a stop mode" HorizontalAlignment="Stretch">
|
||||
<ComboBoxItem>Finish the main segment</ComboBoxItem>
|
||||
<ComboBoxItem>Skip forward to the end segment</ComboBoxItem>
|
||||
</ComboBox>
|
||||
|
||||
<DockPanel DockPanel.Dock="Top" Margin="0 5">
|
||||
<avalonia:MaterialIcon Kind="InfoCircle" Margin="5 0"></avalonia:MaterialIcon>
|
||||
<TextBlock Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap">
|
||||
The start- and end-segments are always played once.
|
||||
</TextBlock>
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
|
||||
<Button DockPanel.Dock="Bottom" ToolTip.Tip="Open editor" Margin="0 15 0 5" Command="{Binding OpenEditor}" HorizontalAlignment="Stretch">
|
||||
Edit condition script
|
||||
</Button>
|
||||
</DockPanel>
|
||||
<ContentControl Grid.Row="2" Content="{CompiledBinding ConditionViewModel}" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@ -1,18 +1,17 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition
|
||||
{
|
||||
public partial class DisplayConditionScriptView : ReactiveUserControl<DisplayConditionScriptViewModel>
|
||||
{
|
||||
public DisplayConditionScriptView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition;
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
public class DisplayConditionScriptView : ReactiveUserControl<DisplayConditionScriptViewModel>
|
||||
{
|
||||
public DisplayConditionScriptView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.VisualScripting;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
using Avalonia.Controls.Mixins;
|
||||
@ -17,81 +13,79 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition;
|
||||
|
||||
public class DisplayConditionScriptViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly ObservableAsPropertyHelper<ViewModelBase?> _conditionViewModel;
|
||||
private readonly IConditionVmFactory _conditionVmFactory;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private readonly ObservableAsPropertyHelper<bool> _showEventOptions;
|
||||
private readonly ObservableAsPropertyHelper<bool> _showStaticOptions;
|
||||
private readonly IWindowService _windowService;
|
||||
private ObservableAsPropertyHelper<NodeScriptViewModel?>? _nodeScriptViewModel;
|
||||
private RenderProfileElement? _profileElement;
|
||||
private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement;
|
||||
private ObservableAsPropertyHelper<ConditionTypeViewModel?>? _selectedConditionTypeViewModel;
|
||||
|
||||
public DisplayConditionScriptViewModel(IProfileEditorService profileEditorService, INodeVmFactory nodeVmFactory, IWindowService windowService)
|
||||
public DisplayConditionScriptViewModel(IProfileEditorService profileEditorService, IConditionVmFactory conditionVmFactory)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
_windowService = windowService;
|
||||
_conditionVmFactory = conditionVmFactory;
|
||||
|
||||
ConditionTypeViewModels = new ObservableCollection<ConditionTypeViewModel>
|
||||
{
|
||||
new("None", "The element is always active.", null),
|
||||
new("Regular", "The element is activated when the provided visual script ends in true.", typeof(StaticCondition)),
|
||||
new("Event", "The element is activated when the selected event fires.\r\n" +
|
||||
"Events that contain data can conditionally trigger the layer using a visual script.", typeof(EventCondition))
|
||||
new("Always", "The element is always active.", typeof(AlwaysOnCondition)),
|
||||
new("Once", "The element is shown once until its timeline is finished.", typeof(PlayOnceCondition)),
|
||||
new("Conditional", "The element is activated when the provided visual script ends in true.", typeof(StaticCondition)),
|
||||
new("On event", "The element is activated when the selected event fires.\r\n" +
|
||||
"Events that contain data can conditionally trigger the layer using a visual script.", typeof(EventCondition))
|
||||
};
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
profileEditorService.ProfileElement.Subscribe(p => _profileElement = p).DisposeWith(d);
|
||||
|
||||
_nodeScriptViewModel = profileEditorService.ProfileElement
|
||||
.Select(p => p?.WhenAnyValue(element => element.DisplayCondition) ?? Observable.Never<ICondition?>())
|
||||
.Switch()
|
||||
.Select(c => c is INodeScriptCondition {NodeScript: NodeScript nodeScript} ? nodeVmFactory.NodeScriptViewModel(nodeScript, true) : null)
|
||||
.ToProperty(this, vm => vm.NodeScriptViewModel)
|
||||
.DisposeWith(d);
|
||||
_profileElement = profileEditorService.ProfileElement.ToProperty(this, vm => vm.ProfileElement).DisposeWith(d);
|
||||
_selectedConditionTypeViewModel = profileEditorService.ProfileElement
|
||||
.Select(p => p?.WhenAnyValue(element => element.DisplayCondition) ?? Observable.Never<ICondition?>())
|
||||
.Select(p => p?.WhenAnyValue(element => element.DisplayCondition) ?? Observable.Return<ICondition?>(null))
|
||||
.Switch()
|
||||
.Select(c => c != null ? ConditionTypeViewModels.FirstOrDefault(vm => vm.ConditionType == c.GetType()) : null)
|
||||
.ToProperty(this, vm => vm.SelectedConditionTypeViewModel)
|
||||
.DisposeWith(d);
|
||||
});
|
||||
|
||||
_showStaticOptions = this.WhenAnyValue(vm => vm.SelectedConditionTypeViewModel)
|
||||
.Select(c => c != null && c.ConditionType == typeof(StaticCondition))
|
||||
.ToProperty(this, vm => vm.ShowStaticOptions);
|
||||
_showEventOptions = this.WhenAnyValue(vm => vm.SelectedConditionTypeViewModel)
|
||||
.Select(c => c != null && c.ConditionType == typeof(EventCondition))
|
||||
.ToProperty(this, vm => vm.ShowEventOptions);
|
||||
_conditionViewModel = this.WhenAnyValue(vm => vm.SelectedConditionTypeViewModel).Select(_ => CreateConditionViewModel()).ToProperty(this, vm => vm.ConditionViewModel);
|
||||
}
|
||||
|
||||
public NodeScriptViewModel? NodeScriptViewModel => _nodeScriptViewModel?.Value;
|
||||
public RenderProfileElement? ProfileElement => _profileElement?.Value;
|
||||
public ViewModelBase? ConditionViewModel => _conditionViewModel.Value;
|
||||
public ObservableCollection<ConditionTypeViewModel> ConditionTypeViewModels { get; }
|
||||
|
||||
public ConditionTypeViewModel? SelectedConditionTypeViewModel
|
||||
{
|
||||
get => _selectedConditionTypeViewModel?.Value;
|
||||
set
|
||||
{
|
||||
if (_profileElement == null)
|
||||
return;
|
||||
|
||||
ICondition? condition = null;
|
||||
if (value?.ConditionType == typeof(StaticCondition))
|
||||
condition = new StaticCondition(_profileElement);
|
||||
else if (value?.ConditionType == typeof(EventCondition))
|
||||
condition = new EventCondition(_profileElement);
|
||||
|
||||
_profileEditorService.ExecuteCommand(new ChangeConditionType(_profileElement, condition));
|
||||
}
|
||||
set => ApplyConditionType(value);
|
||||
}
|
||||
|
||||
public bool ShowStaticOptions => _showStaticOptions.Value;
|
||||
public bool ShowEventOptions => _showEventOptions.Value;
|
||||
|
||||
|
||||
public async Task OpenEditor()
|
||||
private ViewModelBase? CreateConditionViewModel()
|
||||
{
|
||||
if (_profileElement?.DisplayCondition is StaticCondition staticCondition)
|
||||
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(("nodeScript", staticCondition.Script));
|
||||
if (ProfileElement == null)
|
||||
return null;
|
||||
|
||||
if (ProfileElement.DisplayCondition is AlwaysOnCondition alwaysOnCondition)
|
||||
return _conditionVmFactory.AlwaysOnConditionViewModel(alwaysOnCondition);
|
||||
if (ProfileElement.DisplayCondition is PlayOnceCondition playOnceCondition)
|
||||
return _conditionVmFactory.PlayOnceConditionViewModel(playOnceCondition);
|
||||
if (ProfileElement.DisplayCondition is StaticCondition staticCondition)
|
||||
return _conditionVmFactory.StaticConditionViewModel(staticCondition);
|
||||
if (ProfileElement.DisplayCondition is EventCondition eventCondition)
|
||||
return _conditionVmFactory.EventConditionViewModel(eventCondition);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ApplyConditionType(ConditionTypeViewModel? value)
|
||||
{
|
||||
if (ProfileElement == null || value == null || ProfileElement.DisplayCondition.GetType() == value.ConditionType)
|
||||
return;
|
||||
|
||||
if (value.ConditionType == typeof(AlwaysOnCondition))
|
||||
_profileEditorService.ExecuteCommand(new ChangeConditionType(ProfileElement, new AlwaysOnCondition(ProfileElement)));
|
||||
if (value.ConditionType == typeof(PlayOnceCondition))
|
||||
_profileEditorService.ExecuteCommand(new ChangeConditionType(ProfileElement, new PlayOnceCondition(ProfileElement)));
|
||||
if (value.ConditionType == typeof(StaticCondition))
|
||||
_profileEditorService.ExecuteCommand(new ChangeConditionType(ProfileElement, new StaticCondition(ProfileElement)));
|
||||
if (value.ConditionType == typeof(EventCondition))
|
||||
_profileEditorService.ExecuteCommand(new ChangeConditionType(ProfileElement, new EventCondition(ProfileElement)));
|
||||
}
|
||||
}
|
||||
@ -46,13 +46,6 @@
|
||||
Command="{Binding DisableSegment}">
|
||||
<avalonia:MaterialIcon Kind="CloseCircle" />
|
||||
</Button>
|
||||
<ToggleButton Name="SegmentRepeat"
|
||||
Classes="icon-button icon-button-small"
|
||||
ToolTip.Tip="Repeat this segment"
|
||||
IsChecked="{CompiledBinding RepeatSegment}"
|
||||
Padding="0">
|
||||
<avalonia:MaterialIcon Kind="Repeat" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
|
||||
<Button Name="AddEndSegment"
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||
@ -13,12 +10,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments;
|
||||
|
||||
public class MainSegmentViewModel : TimelineSegmentViewModel
|
||||
{
|
||||
private RenderProfileElement? _profileElement;
|
||||
private ObservableAsPropertyHelper<double>? _start;
|
||||
private readonly ObservableAsPropertyHelper<double> _width;
|
||||
private ObservableAsPropertyHelper<double>? _end;
|
||||
private ObservableAsPropertyHelper<string?>? _endTimestamp;
|
||||
private ObservableAsPropertyHelper<bool>? _repeatSegment;
|
||||
private readonly ObservableAsPropertyHelper<double> _width;
|
||||
private RenderProfileElement? _profileElement;
|
||||
private ObservableAsPropertyHelper<double>? _start;
|
||||
|
||||
public MainSegmentViewModel(IProfileEditorService profileEditorService) : base(profileEditorService)
|
||||
{
|
||||
@ -44,12 +40,6 @@ public class MainSegmentViewModel : TimelineSegmentViewModel
|
||||
.Select(p => $"{Math.Floor(p.TotalSeconds):00}.{p.Milliseconds:000}")
|
||||
.ToProperty(this, vm => vm.EndTimestamp)
|
||||
.DisposeWith(d);
|
||||
_repeatSegment = profileEditorService.ProfileElement
|
||||
.Select(p => p?.WhenAnyValue(element => element.Timeline.PlayMode) ?? Observable.Never<TimelinePlayMode>())
|
||||
.Switch()
|
||||
.Select(p => p == TimelinePlayMode.Repeat)
|
||||
.ToProperty(this, vm => vm.RepeatSegment)
|
||||
.DisposeWith(d);
|
||||
});
|
||||
|
||||
_width = this.WhenAnyValue(vm => vm.StartX, vm => vm.EndX).Select(t => t.Item2 - t.Item1).ToProperty(this, vm => vm.Width);
|
||||
@ -73,10 +63,4 @@ public class MainSegmentViewModel : TimelineSegmentViewModel
|
||||
public override double Width => _width.Value;
|
||||
public override string? EndTimestamp => _endTimestamp?.Value;
|
||||
public override ResizeTimelineSegment.SegmentType Type => ResizeTimelineSegment.SegmentType.Main;
|
||||
|
||||
public bool RepeatSegment
|
||||
{
|
||||
get => _repeatSegment?.Value ?? false;
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@ -22,7 +22,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
|
||||
public SurfaceEditorViewModel(IScreen hostScreen,
|
||||
IRgbService rgbService,
|
||||
SurfaceVmFactory surfaceVmFactory,
|
||||
ISurfaceVmFactory surfaceVmFactory,
|
||||
ISettingsService settingsService) : base(hostScreen, "surface-editor")
|
||||
{
|
||||
_rgbService = rgbService;
|
||||
|
||||
@ -3,9 +3,30 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.VisualScripting.NodeScriptWindowView"
|
||||
x:DataType="visualScripting:NodeScriptWindowViewModel"
|
||||
Title="VisualEditorWindowView">
|
||||
<ContentControl Content="{CompiledBinding NodeScriptViewModel}" />
|
||||
Title="Artemis | Visual Script Editor"
|
||||
Width="1200"
|
||||
Height="800">
|
||||
<Grid Margin="15" ColumnDefinitions="*,*" RowDefinitions="Auto,*">
|
||||
<StackPanel Grid.Row="0">
|
||||
<TextBlock Classes="h4" Text="{CompiledBinding NodeScript.Name}"/>
|
||||
<TextBlock Classes="subtitle" Margin="0 0 0 10" Text="{CompiledBinding NodeScript.Description}"/>
|
||||
</StackPanel>
|
||||
<controls:HyperlinkButton Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/profiles/conditions">
|
||||
Learn more about visual scripts
|
||||
</controls:HyperlinkButton>
|
||||
|
||||
<Border Classes="card" Grid.Row="1" Grid.ColumnSpan="2">
|
||||
<ContentControl Content="{CompiledBinding NodeScriptViewModel}" />
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Window>
|
||||
@ -5,7 +5,7 @@ using Artemis.VisualScripting.Nodes.Operators.Screens;
|
||||
namespace Artemis.VisualScripting.Nodes.Operators;
|
||||
|
||||
[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 class EnumEqualsNode : Node<int, EnumEqualsNodeCustomViewModel>
|
||||
{
|
||||
public EnumEqualsNode() : base("Enum Equals", "Determines the equality between an input and a selected enum value")
|
||||
{
|
||||
@ -17,20 +17,18 @@ public class EnumEqualsNode : Node<Enum, EnumEqualsNodeCustomViewModel>
|
||||
|
||||
private void InputPinOnPinConnected(object? sender, SingleValueEventArgs<IPin> e)
|
||||
{
|
||||
if (Storage?.GetType() != InputPin.ConnectedTo.First().Type)
|
||||
Storage = Enum.GetValues(InputPin.ConnectedTo.First().Type).Cast<Enum>().FirstOrDefault();
|
||||
Storage = 0;
|
||||
}
|
||||
|
||||
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);
|
||||
if (InputPin.Value == null)
|
||||
OutputPin.Value = false;
|
||||
else
|
||||
OutputPin.Value = Convert.ToInt32(InputPin.Value) == Storage;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -8,7 +8,7 @@
|
||||
x:DataType="screens:EnumEqualsNodeCustomViewModel">
|
||||
<ComboBox IsEnabled="{CompiledBinding EnumValues.Count}"
|
||||
Items="{CompiledBinding EnumValues}"
|
||||
SelectedItem="{CompiledBinding CurrentValue}"
|
||||
SelectedIndex="{CompiledBinding CurrentValue}"
|
||||
PlaceholderText="Connect a pin..."
|
||||
Classes="condensed"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
@ -33,11 +33,7 @@ public class EnumEqualsNodeCustomViewModel : CustomNodeViewModel
|
||||
}
|
||||
|
||||
Observable.FromEventPattern<SingleValueEventArgs<IPin>>(x => _node.InputPin.PinConnected += x, x => _node.InputPin.PinConnected -= x)
|
||||
.Subscribe(p =>
|
||||
{
|
||||
EnumValues.AddRange(Enum.GetValues(p.EventArgs.Value.Type).Cast<Enum>());
|
||||
this.RaisePropertyChanged(nameof(CurrentValue));
|
||||
})
|
||||
.Subscribe(p => EnumValues.AddRange(Enum.GetValues(p.EventArgs.Value.Type).Cast<Enum>()))
|
||||
.DisposeWith(d);
|
||||
Observable.FromEventPattern<SingleValueEventArgs<IPin>>(x => _node.InputPin.PinDisconnected += x, x => _node.InputPin.PinDisconnected -= x)
|
||||
.Subscribe(_ => EnumValues.Clear())
|
||||
@ -47,13 +43,13 @@ public class EnumEqualsNodeCustomViewModel : CustomNodeViewModel
|
||||
|
||||
public ObservableCollection<Enum> EnumValues { get; } = new();
|
||||
|
||||
public Enum? CurrentValue
|
||||
public int CurrentValue
|
||||
{
|
||||
get => _node.Storage;
|
||||
set
|
||||
{
|
||||
if (value != null && !Equals(_node.Storage, value))
|
||||
_nodeEditorService.ExecuteCommand(Script, new UpdateStorage<Enum>(_node, value));
|
||||
if (!Equals(_node.Storage, value))
|
||||
_nodeEditorService.ExecuteCommand(Script, new UpdateStorage<int>(_node, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user