From 3dfc25b0925a1f177d5d7528979fe117f1d18cf2 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 9 Apr 2022 00:11:22 +0200 Subject: [PATCH] Core - Refactored display conditions Profile editor - Added UIs for each condition type --- .../Profile/Conditions/AlwaysOnCondition.cs | 78 +++ .../Profile/Conditions/EventCondition.cs | 451 ++++++++++-------- .../Models/Profile/Conditions/ICondition.cs | 11 +- .../Profile/Conditions/PlayOnceCondition.cs | 78 +++ .../Profile/Conditions/StaticCondition.cs | 113 ++++- src/Artemis.Core/Models/Profile/Folder.cs | 6 +- src/Artemis.Core/Models/Profile/Layer.cs | 4 +- .../Models/Profile/RenderProfileElement.cs | 35 +- src/Artemis.Core/Models/Profile/Timeline.cs | 81 +--- .../Internal/EventDefaultNode.cs | 46 +- src/Artemis.Core/VisualScripting/Node.cs | 10 +- .../Conditions/AlwaysOnConditionEntity.cs | 10 + .../Conditions/EventConditionEntity.cs | 3 +- .../Conditions/PlayOnceConditionEntity.cs | 9 + .../Conditions/StaticConditionEntity.cs | 2 + .../Entities/Profile/TimelineEntity.cs | 3 - .../ProfileEditor/Commands/DeleteKeyframe.cs | 2 +- .../Commands/UpdateEventConditionPath.cs | 73 +++ .../Commands/UpdateEventOverlapMode.cs | 38 ++ .../Commands/UpdateEventTriggerMode.cs | 38 ++ .../Commands/UpdateStaticPlayMode.cs | 38 ++ .../Commands/UpdateStaticStopMode.cs | 38 ++ .../ProfileEditor/ProfileEditorService.cs | 3 +- .../Ninject/Factories/IVMFactory.cs | 11 +- .../ConditionTypeViewModel.cs | 25 +- .../AlwaysOnConditionView.axaml | 16 + .../AlwaysOnConditionView.axaml.cs | 17 + .../AlwaysOnConditionViewModel.cs | 14 + .../ConditionTypes/EventConditionView.axaml | 59 +++ .../EventConditionView.axaml.cs | 17 + .../ConditionTypes/EventConditionViewModel.cs | 73 +++ .../PlayOnceConditionView.axaml | 15 + .../PlayOnceConditionView.axaml.cs | 17 + .../PlayOnceConditionViewModel.cs | 14 + .../ConditionTypes/StaticConditionView.axaml | 45 ++ .../StaticConditionView.axaml.cs | 17 + .../StaticConditionViewModel.cs | 56 +++ .../DisplayConditionScriptView.axaml | 78 +-- .../DisplayConditionScriptView.axaml.cs | 25 +- .../DisplayConditionScriptViewModel.cs | 98 ++-- .../Timeline/Segments/MainSegmentView.axaml | 7 - .../Timeline/Segments/MainSegmentViewModel.cs | 22 +- .../SurfaceEditor/SurfaceEditorViewModel.cs | 2 +- .../NodeScriptWindowView.axaml | 25 +- .../Nodes/Operators/EnumEqualsNode.cs | 14 +- .../Screens/EnumEqualsNodeCustomView.axaml | 2 +- .../Screens/EnumEqualsNodeCustomViewModel.cs | 12 +- 47 files changed, 1305 insertions(+), 546 deletions(-) create mode 100644 src/Artemis.Core/Models/Profile/Conditions/AlwaysOnCondition.cs create mode 100644 src/Artemis.Core/Models/Profile/Conditions/PlayOnceCondition.cs create mode 100644 src/Artemis.Storage/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs create mode 100644 src/Artemis.Storage/Entities/Profile/Conditions/PlayOnceConditionEntity.cs create mode 100644 src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateEventConditionPath.cs create mode 100644 src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateEventOverlapMode.cs create mode 100644 src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateEventTriggerMode.cs create mode 100644 src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateStaticPlayMode.cs create mode 100644 src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateStaticStopMode.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/AlwaysOnConditionView.axaml create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/AlwaysOnConditionView.axaml.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/AlwaysOnConditionViewModel.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/EventConditionView.axaml create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/EventConditionView.axaml.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/EventConditionViewModel.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/PlayOnceConditionView.axaml create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/PlayOnceConditionView.axaml.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/PlayOnceConditionViewModel.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/StaticConditionView.axaml create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/StaticConditionView.axaml.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/StaticConditionViewModel.cs diff --git a/src/Artemis.Core/Models/Profile/Conditions/AlwaysOnCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/AlwaysOnCondition.cs new file mode 100644 index 000000000..90c0a790a --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/AlwaysOnCondition.cs @@ -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 + + /// + public void Dispose() + { + } + + #endregion + + #region Implementation of IStorageModel + + /// + public void Load() + { + } + + /// + public void Save() + { + } + + #endregion + + #region Implementation of ICondition + + /// + public IConditionEntity Entity { get; } + + /// + public RenderProfileElement ProfileElement { get; } + + /// + public bool IsMet { get; private set; } + + /// + public void Update() + { + if (ProfileElement.Parent is RenderProfileElement parent) + IsMet = parent.DisplayConditionMet; + else + IsMet = true; + } + + /// + public void UpdateTimeline(double deltaTime) + { + ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), true); + } + + /// + public void OverrideTimeline(TimeSpan position) + { + ProfileElement.Timeline.Override(position, position > ProfileElement.Timeline.Length); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs index ecf83c4d1..6c8fd2c9a 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs @@ -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; + +/// +/// Represents a condition that is based on a +/// +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 _script; + private EventTriggerMode _triggerMode; + private bool _wasMet; + + /// + /// Creates a new instance of the class + /// + public EventCondition(RenderProfileElement profileElement) + { + ProfileElement = profileElement; + + _entity = new EventConditionEntity(); + _displayName = profileElement.GetType().Name; + _eventNode = new EventDefaultNode {X = -300}; + _script = new NodeScript($"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(); + } + + /// + /// Gets the script that drives the event condition + /// + public NodeScript Script + { + get => _script; + set => SetAndNotify(ref _script, value); + } + + /// + /// Gets or sets the path to the event that drives this event condition + /// + public DataModelPath? EventPath + { + get => _eventPath; + set => SetAndNotify(ref _eventPath, value); + } + + /// + /// Gets or sets how the condition behaves when the event fires. + /// + public EventTriggerMode TriggerMode + { + get => _triggerMode; + set => SetAndNotify(ref _triggerMode, value); + } + + /// + /// Gets or sets how the condition behaves when events trigger before the timeline finishes in the + /// event trigger mode. + /// + public EventOverlapMode OverlapMode + { + get => _overlapMode; + set => SetAndNotify(ref _overlapMode, value); + } + + /// + /// Updates the event node, applying the selected event + /// + 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); + } + + /// + /// Gets the start node of the event script, if any + /// + /// The start node of the event script, if any. + 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; + } + + /// + public IConditionEntity Entity => _entity; + + /// + public RenderProfileElement ProfileElement { get; } + + /// + public bool IsMet { get; private set; } + + /// + public void Update() + { + _wasMet = IsMet; + if (TriggerMode == EventTriggerMode.Toggle) + { + if (Evaluate()) + IsMet = !IsMet; + } + else + { + IsMet = Evaluate(); + } + } + + /// + 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); + } + + /// + public void OverrideTimeline(TimeSpan position) + { + ProfileElement.Timeline.Override(position, TriggerMode == EventTriggerMode.Toggle && position > ProfileElement.Timeline.Length); + } + + /// + public void Dispose() + { + Script?.Dispose(); + EventPath?.Dispose(); + } + + #region Storage + + /// + 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($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", _entity.Script, ProfileElement.Profile) + : new NodeScript($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile); + } + + /// + public void Save() + { + _entity.TriggerMode = (int) TriggerMode; + _entity.OverlapMode = (int) OverlapMode; + Script.Save(); + _entity.Script = Script?.Entity; + EventPath?.Save(); + _entity.EventPath = EventPath?.Entity; + } + + /// + public INodeScript NodeScript => Script; + + /// + public void LoadNodeScript() + { + Script.Load(); + UpdateEventNode(); + Script.LoadConnections(); + } + + #endregion +} + +/// +/// Represents a mode for render elements to start their timeline when display conditions events are fired. +/// +public enum EventTriggerMode { /// - /// Represents a condition that is based on a + /// Play the timeline once. /// - 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? _script; + Play, - /// - /// Creates a new instance of the class - /// - public EventCondition(ProfileElement profileElement) - { - _entity = new EventConditionEntity(); - _displayName = profileElement.GetType().Name; + /// + /// Toggle repeating the timeline. + /// + Toggle +} - ProfileElement = profileElement; - } +/// +/// Represents a mode for render elements to configure the behaviour of events that overlap i.e. trigger again before +/// the timeline finishes. +/// +public enum EventOverlapMode +{ + /// + /// Stop the current run and restart the timeline + /// + Restart, - internal EventCondition(EventConditionEntity entity, ProfileElement profileElement) - { - _entity = entity; - _displayName = profileElement.GetType().Name; + /// + /// Ignore subsequent event fires until the timeline finishes + /// + Ignore, - ProfileElement = profileElement; - Load(); - } - - /// - /// Gets the script that drives the event condition - /// - public NodeScript? Script - { - get => _script; - set => SetAndNotify(ref _script, value); - } - - /// - /// Gets or sets the path to the event that drives this event condition - /// - public DataModelPath? EventPath - { - get => _eventPath; - set => SetAndNotify(ref _eventPath, value); - } - - /// - /// Gets or sets how the condition behaves when events trigger before the timeline finishes - /// - public TimeLineEventOverlapMode EventOverlapMode - { - get => _eventOverlapMode; - set => SetAndNotify(ref _eventOverlapMode, value); - } - - /// - /// Updates the event node, applying the selected event - /// - 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); - } - - /// - /// Updates the with a new empty node script - /// - public void CreateEmptyNodeScript() - { - Script?.Dispose(); - Script = new NodeScript($"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; - } - - /// - public IConditionEntity Entity => _entity; - - /// - public ProfileElement ProfileElement { get; } - - /// - public bool IsMet { get; private set; } - - /// - public void Update() - { - if (EventOverlapMode == TimeLineEventOverlapMode.Toggle) - { - if (Evaluate()) - IsMet = !IsMet; - } - else - { - IsMet = Evaluate(); - } - } - - /// - 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 - } - - /// - public void Dispose() - { - Script?.Dispose(); - EventPath?.Dispose(); - } - - #region Storage - - /// - public void Load() - { - EventOverlapMode = (TimeLineEventOverlapMode) _entity.EventOverlapMode; - if (_entity.Script != null) - Script = new NodeScript($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", _entity.Script, ProfileElement.Profile); - if (_entity.EventPath != null) - EventPath = new DataModelPath(_entity.EventPath); - } - - /// - public void Save() - { - _entity.EventOverlapMode = (int) EventOverlapMode; - Script?.Save(); - _entity.Script = Script?.Entity; - EventPath?.Save(); - _entity.EventPath = EventPath?.Entity; - } - - /// - public INodeScript NodeScript => Script; - - /// - public void LoadNodeScript() - { - if (Script == null) - return; - - Script.Load(); - UpdateEventNode(); - Script.LoadConnections(); - } - - #endregion - } + /// + /// Play another copy of the timeline on top of the current run + /// + Copy } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/ICondition.cs b/src/Artemis.Core/Models/Profile/Conditions/ICondition.cs index 2f4df80f7..a56394c49 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/ICondition.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/ICondition.cs @@ -16,7 +16,7 @@ public interface ICondition : IDisposable, IStorageModel /// /// Gets the profile element this condition applies to /// - public ProfileElement ProfileElement { get; } + public RenderProfileElement ProfileElement { get; } /// /// Gets a boolean indicating whether the condition is currently met @@ -30,12 +30,11 @@ public interface ICondition : IDisposable, IStorageModel void Update(); /// - /// Applies the display condition to the provided timeline + /// Updates the timeline according to the provided as the display condition sees fit. /// - /// - /// - /// The timeline to apply the display condition to - void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline); + void UpdateTimeline(double deltaTime); + + void OverrideTimeline(TimeSpan position); } /// diff --git a/src/Artemis.Core/Models/Profile/Conditions/PlayOnceCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/PlayOnceCondition.cs new file mode 100644 index 000000000..cd33f2d91 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/PlayOnceCondition.cs @@ -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 + + /// + public void Dispose() + { + } + + #endregion + + #region Implementation of IStorageModel + + /// + public void Load() + { + } + + /// + public void Save() + { + } + + #endregion + + #region Implementation of ICondition + + /// + public IConditionEntity Entity { get; } + + /// + public RenderProfileElement ProfileElement { get; } + + /// + public bool IsMet { get; private set; } + + /// + public void Update() + { + if (ProfileElement.Parent is RenderProfileElement parent) + IsMet = parent.DisplayConditionMet; + else + IsMet = true; + } + + /// + public void UpdateTimeline(double deltaTime) + { + ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), false); + } + + /// + public void OverrideTimeline(TimeSpan position) + { + ProfileElement.Timeline.Override(position, false); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs index 028745bef..af47599ef 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/StaticCondition.cs @@ -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; /// /// Creates a new instance of the class /// - 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($"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; /// - public ProfileElement ProfileElement { get; } + public RenderProfileElement ProfileElement { get; } /// public bool IsMet { get; private set; } - /// - public void Update() + /// + /// Gets or sets the mode in which the render element starts its timeline when display conditions are met + /// + public StaticPlayMode PlayMode { - if (!Script.ExitNodeConnected) - { - IsMet = true; - return; - } + get => _playMode; + set => SetAndNotify(ref _playMode, value); + } - Script.Run(); - IsMet = Script.Result; + /// + /// Gets or sets the mode in which the render element stops its timeline when display conditions are no longer met + /// + public StaticStopMode StopMode + { + get => _stopMode; + set => SetAndNotify(ref _stopMode, value); } /// - 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; + } + } + + /// + 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); + } + + /// + public void OverrideTimeline(TimeSpan position) + { + ProfileElement.Timeline.Override(position, PlayMode == StaticPlayMode.Repeat && position > ProfileElement.Timeline.Length); } /// @@ -81,12 +120,18 @@ namespace Artemis.Core /// public void Load() { + PlayMode = (StaticPlayMode) _entity.PlayMode; + StopMode = (StaticStopMode) _entity.StopMode; + Script = new NodeScript($"Activate {_displayName}", $"Whether or not this {_displayName} should be active", _entity.Script, ProfileElement.Profile); } /// 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 } + + /// + /// Represents a mode for render elements to start their timeline when display conditions are met + /// + public enum StaticPlayMode + { + /// + /// Continue repeating the main segment of the timeline while the condition is met + /// + Repeat, + + /// + /// Only play the timeline once when the condition is met + /// + Once + } + + /// + /// Represents a mode for render elements to stop their timeline when display conditions are no longer met + /// + public enum StaticStopMode + { + /// + /// When conditions are no longer met, finish the the current run of the main timeline + /// + Finish, + + /// + /// When conditions are no longer met, skip to the end segment of the timeline + /// + SkipToEnd + } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 463ddfbb4..c42df2b5b 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -237,11 +237,11 @@ namespace Artemis.Core } /// - 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); ; } /// diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 21a8a113f..3afb7b74f 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -476,9 +476,9 @@ namespace Artemis.Core } /// - 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); diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 619f10fba..9064c1978 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -27,6 +27,7 @@ namespace Artemis.Core LayerEffectsList = new List(); LayerEffects = new ReadOnlyCollection(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 /// public event EventHandler? LayerEffectsUpdated; - + /// 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; } /// - /// Updates the according to the provided and current display - /// condition status + /// Updates the according to the provided and current display condition /// 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 /// /// Gets or sets the display condition used to determine whether this element is active or not /// - public ICondition? DisplayCondition + public ICondition DisplayCondition { get => _displayCondition; set => SetAndNotify(ref _displayCondition, value); } - private ICondition? _displayCondition; + private ICondition _displayCondition; /// /// Evaluates the display conditions on this element and applies any required changes to the @@ -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 /// /// The position to set the timeline to - /// Whether to stick to the main segment, wrapping around if needed - public abstract void OverrideTimelineAndApply(TimeSpan position, bool stickToMainSegment); + public abstract void OverrideTimelineAndApply(TimeSpan position); } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Timeline.cs b/src/Artemis.Core/Models/Profile/Timeline.cs index 606f0ba8b..016ea1ecb 100644 --- a/src/Artemis.Core/Models/Profile/Timeline.cs +++ b/src/Artemis.Core/Models/Profile/Timeline.cs @@ -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); } - /// - /// Gets or sets the mode in which the render element starts its timeline when display conditions are met - /// - public TimelinePlayMode PlayMode - { - get => _playMode; - set => SetAndNotify(ref _playMode, value); - } - /// - /// Gets or sets the mode in which the render element stops its timeline when display conditions are no longer met - /// - public TimelineStopMode StopMode - { - get => _stopMode; - set => SetAndNotify(ref _stopMode, value); - } /// /// 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 } - -/// -/// Represents a mode for render elements to start their timeline when display conditions are met -/// -public enum TimelinePlayMode -{ - /// - /// Continue repeating the main segment of the timeline while the condition is met - /// - Repeat, - - /// - /// Only play the timeline once when the condition is met - /// - Once -} - -/// -/// Represents a mode for render elements to stop their timeline when display conditions are no longer met -/// -public enum TimelineStopMode -{ - /// - /// When conditions are no longer met, finish the the current run of the main timeline - /// - Finish, - - /// - /// When conditions are no longer met, skip to the end segment of the timeline - /// - SkipToEnd -} - -/// -/// Represents a mode for render elements to start their timeline when display conditions events are fired -/// -public enum TimeLineEventOverlapMode -{ - /// - /// Stop the current run and restart the timeline - /// - Restart, - - /// - /// Ignore subsequent event fires until the timeline finishes - /// - Ignore, - - /// - /// Play another copy of the timeline on top of the current run - /// - Copy, - - /// - /// Repeat the timeline until the event fires again - /// - Toggle -} \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/Internal/EventDefaultNode.cs b/src/Artemis.Core/VisualScripting/Internal/EventDefaultNode.cs index b5ee6e227..ef7f2ffb9 100644 --- a/src/Artemis.Core/VisualScripting/Internal/EventDefaultNode.cs +++ b/src/Artemis.Core/VisualScripting/Internal/EventDefaultNode.cs @@ -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 _propertyPins; + private readonly List _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()!; } } + + /// + /// 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. + /// + 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; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/Node.cs b/src/Artemis.Core/VisualScripting/Node.cs index a9bdfb8f2..4622a1441 100644 --- a/src/Artemis.Core/VisualScripting/Node.cs +++ b/src/Artemis.Core/VisualScripting/Node.cs @@ -280,7 +280,7 @@ public abstract class Node : CorePropertyChanged, INode /// Serializes the object into a string /// /// The serialized object - internal virtual string SerializeStorage() + public virtual string SerializeStorage() { return string.Empty; } @@ -289,7 +289,7 @@ public abstract class Node : CorePropertyChanged, INode /// Deserializes the object and sets it /// /// The serialized object - internal virtual void DeserializeStorage(string serialized) + public virtual void DeserializeStorage(string serialized) { } @@ -333,12 +333,14 @@ public abstract class Node : Node /// public event EventHandler? StorageModified; - internal override string SerializeStorage() + /// + public override string SerializeStorage() { return CoreJson.SerializeObject(Storage, true); } - internal override void DeserializeStorage(string serialized) + /// + public override void DeserializeStorage(string serialized) { Storage = CoreJson.DeserializeObject(serialized) ?? default(TStorage); } diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs new file mode 100644 index 000000000..31793708b --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs @@ -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 + { + + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/EventConditionEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/EventConditionEntity.cs index 19b9587dc..c7e83890a 100644 --- a/src/Artemis.Storage/Entities/Profile/Conditions/EventConditionEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/Conditions/EventConditionEntity.cs @@ -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; } } diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/PlayOnceConditionEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/PlayOnceConditionEntity.cs new file mode 100644 index 000000000..0107523a5 --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/Conditions/PlayOnceConditionEntity.cs @@ -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 + { + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/StaticConditionEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/StaticConditionEntity.cs index 38a938c9a..edf362006 100644 --- a/src/Artemis.Storage/Entities/Profile/Conditions/StaticConditionEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/Conditions/StaticConditionEntity.cs @@ -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; } } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/TimelineEntity.cs b/src/Artemis.Storage/Entities/Profile/TimelineEntity.cs index 46dd7984b..790c86309 100644 --- a/src/Artemis.Storage/Entities/Profile/TimelineEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/TimelineEntity.cs @@ -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; } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/DeleteKeyframe.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/DeleteKeyframe.cs index eacb82fce..da36e77a9 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/DeleteKeyframe.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/DeleteKeyframe.cs @@ -10,7 +10,7 @@ public class DeleteKeyframe : IProfileEditorCommand private readonly ILayerPropertyKeyframe _keyframe; /// - /// Creates a new instance of the class. + /// Creates a new instance of the class. /// public DeleteKeyframe(ILayerPropertyKeyframe keyframe) { diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateEventConditionPath.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateEventConditionPath.cs new file mode 100644 index 000000000..b44d8516a --- /dev/null +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateEventConditionPath.cs @@ -0,0 +1,73 @@ +using System; +using Artemis.Core; +using Artemis.UI.Shared.Services.NodeEditor; + +namespace Artemis.UI.Shared.Services.ProfileEditor.Commands; + +/// +/// Represents a profile editor command that can be used to update an event condition's trigger mode. +/// +public class UpdateEventConditionPath : IProfileEditorCommand, IDisposable +{ + private readonly EventCondition _eventCondition; + private readonly DataModelPath? _value; + private readonly DataModelPath? _oldValue; + private readonly NodeConnectionStore? _store; + private bool _executed; + + /// + /// Creates a new instance of the class. + /// + public UpdateEventConditionPath(EventCondition eventCondition, DataModelPath? value) + { + _eventCondition = eventCondition; + _value = value; + _oldValue = eventCondition.EventPath; + + INode? startNode = eventCondition.GetStartNode(); + if (startNode != null) + _store = new NodeConnectionStore(startNode); + } + + /// + public string DisplayName => "Update event path"; + + /// + public void Execute() + { + // Store old connections + _store?.Store(); + + // Change the end node + _eventCondition.EventPath = _value; + _eventCondition.UpdateEventNode(); + + _executed = true; + } + + /// + public void Undo() + { + // Change the end node + _eventCondition.EventPath = _oldValue; + _eventCondition.UpdateEventNode(); + + // Restore old connections + _store?.Restore(); + + _executed = false; + } + + #region IDisposable + + /// + public void Dispose() + { + if (_executed) + _oldValue?.Dispose(); + else + _value?.Dispose(); + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateEventOverlapMode.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateEventOverlapMode.cs new file mode 100644 index 000000000..49aa59e10 --- /dev/null +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateEventOverlapMode.cs @@ -0,0 +1,38 @@ +using Artemis.Core; + +namespace Artemis.UI.Shared.Services.ProfileEditor.Commands; + +/// +/// Represents a profile editor command that can be used to update an event condition's overlap mode. +/// +public class UpdateEventOverlapMode : IProfileEditorCommand +{ + private readonly EventCondition _eventCondition; + private readonly EventOverlapMode _value; + private readonly EventOverlapMode _oldValue; + + /// + /// Creates a new instance of the class. + /// + public UpdateEventOverlapMode(EventCondition eventCondition, EventOverlapMode value) + { + _eventCondition = eventCondition; + _value = value; + _oldValue = eventCondition.OverlapMode; + } + + /// + public string DisplayName => "Update event overlap mode"; + + /// + public void Execute() + { + _eventCondition.OverlapMode = _value; + } + + /// + public void Undo() + { + _eventCondition.OverlapMode = _oldValue; + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateEventTriggerMode.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateEventTriggerMode.cs new file mode 100644 index 000000000..b25e514ae --- /dev/null +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateEventTriggerMode.cs @@ -0,0 +1,38 @@ +using Artemis.Core; + +namespace Artemis.UI.Shared.Services.ProfileEditor.Commands; + +/// +/// Represents a profile editor command that can be used to update an event condition's trigger mode. +/// +public class UpdateEventTriggerMode : IProfileEditorCommand +{ + private readonly EventCondition _eventCondition; + private readonly EventTriggerMode _oldValue; + private readonly EventTriggerMode _value; + + /// + /// Creates a new instance of the class. + /// + public UpdateEventTriggerMode(EventCondition eventCondition, EventTriggerMode value) + { + _eventCondition = eventCondition; + _value = value; + _oldValue = eventCondition.TriggerMode; + } + + /// + public string DisplayName => "Update event trigger mode"; + + /// + public void Execute() + { + _eventCondition.TriggerMode = _value; + } + + /// + public void Undo() + { + _eventCondition.TriggerMode = _oldValue; + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateStaticPlayMode.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateStaticPlayMode.cs new file mode 100644 index 000000000..acf7e1d1a --- /dev/null +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateStaticPlayMode.cs @@ -0,0 +1,38 @@ +using Artemis.Core; + +namespace Artemis.UI.Shared.Services.ProfileEditor.Commands; + +/// +/// Represents a profile editor command that can be used to update an static condition's play mode. +/// +public class UpdateStaticPlayMode : IProfileEditorCommand +{ + private readonly StaticCondition _staticCondition; + private readonly StaticPlayMode _value; + private readonly StaticPlayMode _oldValue; + + /// + /// Creates a new instance of the class. + /// + public UpdateStaticPlayMode(StaticCondition staticCondition, StaticPlayMode value) + { + _staticCondition = staticCondition; + _value = value; + _oldValue = staticCondition.PlayMode; + } + + /// + public string DisplayName => "Update condition play mode"; + + /// + public void Execute() + { + _staticCondition.PlayMode = _value; + } + + /// + public void Undo() + { + _staticCondition.PlayMode = _oldValue; + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateStaticStopMode.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateStaticStopMode.cs new file mode 100644 index 000000000..12641d95f --- /dev/null +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateStaticStopMode.cs @@ -0,0 +1,38 @@ +using Artemis.Core; + +namespace Artemis.UI.Shared.Services.ProfileEditor.Commands; + +/// +/// Represents a profile editor command that can be used to update an static condition's play mode. +/// +public class UpdateStaticStopMode : IProfileEditorCommand +{ + private readonly StaticCondition _staticCondition; + private readonly StaticStopMode _value; + private readonly StaticStopMode _oldValue; + + /// + /// Creates a new instance of the class. + /// + public UpdateStaticStopMode(StaticCondition staticCondition, StaticStopMode value) + { + _staticCondition = staticCondition; + _value = value; + _oldValue = staticCondition.StopMode; + } + + /// + public string DisplayName => "Update condition stop mode"; + + /// + public void Execute() + { + _staticCondition.StopMode = _value; + } + + /// + public void Undo() + { + _staticCondition.StopMode = _oldValue; + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs index a00c8b674..9634b3287 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs @@ -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); diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index d9af870e0..9759807b8 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -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); + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypeViewModel.cs index 5faaf0773..20683fb03 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypeViewModel.cs @@ -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; } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/AlwaysOnConditionView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/AlwaysOnConditionView.axaml new file mode 100644 index 000000000..e703f4359 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/AlwaysOnConditionView.axaml @@ -0,0 +1,16 @@ + + + + + + After playing the start segment, the main segment is endlessly repeated. + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/AlwaysOnConditionView.axaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/AlwaysOnConditionView.axaml.cs new file mode 100644 index 000000000..99520854f --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/AlwaysOnConditionView.axaml.cs @@ -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); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/AlwaysOnConditionViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/AlwaysOnConditionViewModel.cs new file mode 100644 index 000000000..94e6eeb55 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/AlwaysOnConditionViewModel.cs @@ -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; + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/EventConditionView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/EventConditionView.axaml new file mode 100644 index 000000000..09363a0de --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/EventConditionView.axaml @@ -0,0 +1,59 @@ + + + + + + Triggered by event + + + When the event fires.. + + + Play the timeline once + + + Toggle the element on or off + + + + And if already playing.. + + + Restart the timeline + + + Play a second copy + + + Do nothing + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/EventConditionView.axaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/EventConditionView.axaml.cs new file mode 100644 index 000000000..b9bfdfd71 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/EventConditionView.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes; + +public class EventConditionView : ReactiveUserControl +{ + public EventConditionView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/EventConditionViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/EventConditionViewModel.cs new file mode 100644 index 000000000..144f5ae33 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/EventConditionViewModel.cs @@ -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 _showOverlapOptions; + private readonly IWindowService _windowService; + private ObservableAsPropertyHelper? _eventPath; + private ObservableAsPropertyHelper? _selectedOverlapMode; + private ObservableAsPropertyHelper? _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 FilterTypes { get; } = new() {typeof(IDataModelEvent)}; + public ReactiveCommand 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(("nodeScript", _eventCondition.NodeScript)); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/PlayOnceConditionView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/PlayOnceConditionView.axaml new file mode 100644 index 000000000..174c61a60 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/PlayOnceConditionView.axaml @@ -0,0 +1,15 @@ + + + + + Every segment of the timeline is played once. + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/PlayOnceConditionView.axaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/PlayOnceConditionView.axaml.cs new file mode 100644 index 000000000..39b606054 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/PlayOnceConditionView.axaml.cs @@ -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); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/PlayOnceConditionViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/PlayOnceConditionViewModel.cs new file mode 100644 index 000000000..59fbf90ed --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/PlayOnceConditionViewModel.cs @@ -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; + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/StaticConditionView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/StaticConditionView.axaml new file mode 100644 index 000000000..f4cd60f7b --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/StaticConditionView.axaml @@ -0,0 +1,45 @@ + + + + + + While condition is met.. + + Repeat the main segment + Play the main segment once + + + And when no longer met.. + + Finish the main segment + Skip forward to the end segment + + + + + + The start- and end-segments are played once each time the condition is met. + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/StaticConditionView.axaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/StaticConditionView.axaml.cs new file mode 100644 index 000000000..83855685c --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/StaticConditionView.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes; + +public class StaticConditionView : ReactiveUserControl +{ + public StaticConditionView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/StaticConditionViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/StaticConditionViewModel.cs new file mode 100644 index 000000000..c6e7dadd7 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/StaticConditionViewModel.cs @@ -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? _selectedPlayMode; + private ObservableAsPropertyHelper? _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 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(("nodeScript", _staticCondition.NodeScript)); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/DisplayConditionScriptView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/DisplayConditionScriptView.axaml index eb186053f..fd8a5ca96 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/DisplayConditionScriptView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/DisplayConditionScriptView.axaml @@ -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 @@ - - - - - - Condition type + + Activation type @@ -44,64 +37,7 @@ - - - - Triggered by event - - - When the event fires.. - - - Play the timeline once - - - Toggle the element on or off - - - - And if already playing.. - - - Restart the timeline - - - Play a second copy - - - Do nothing - - - - - - - While condition is met.. - - Play the main segment once - Repeat the main segment - - - And when no longer met.. - - Finish the main segment - Skip forward to the end segment - - - - - - The start- and end-segments are always played once. - - - - - - + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/DisplayConditionScriptView.axaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/DisplayConditionScriptView.axaml.cs index f493916d0..730ad046a 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/DisplayConditionScriptView.axaml.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/DisplayConditionScriptView.axaml.cs @@ -1,18 +1,17 @@ using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; -namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition -{ - public partial class DisplayConditionScriptView : ReactiveUserControl - { - public DisplayConditionScriptView() - { - InitializeComponent(); - } +namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition; - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } +public class DisplayConditionScriptView : ReactiveUserControl +{ + public DisplayConditionScriptView() + { + InitializeComponent(); } -} + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/DisplayConditionScriptViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/DisplayConditionScriptViewModel.cs index 98d94ee2a..3568cfff7 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/DisplayConditionScriptViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/DisplayConditionScriptViewModel.cs @@ -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 _conditionViewModel; + private readonly IConditionVmFactory _conditionVmFactory; private readonly IProfileEditorService _profileEditorService; - private readonly ObservableAsPropertyHelper _showEventOptions; - private readonly ObservableAsPropertyHelper _showStaticOptions; - private readonly IWindowService _windowService; - private ObservableAsPropertyHelper? _nodeScriptViewModel; - private RenderProfileElement? _profileElement; + private ObservableAsPropertyHelper? _profileElement; private ObservableAsPropertyHelper? _selectedConditionTypeViewModel; - public DisplayConditionScriptViewModel(IProfileEditorService profileEditorService, INodeVmFactory nodeVmFactory, IWindowService windowService) + public DisplayConditionScriptViewModel(IProfileEditorService profileEditorService, IConditionVmFactory conditionVmFactory) { _profileEditorService = profileEditorService; - _windowService = windowService; + _conditionVmFactory = conditionVmFactory; ConditionTypeViewModels = new ObservableCollection { - 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()) - .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()) + .Select(p => p?.WhenAnyValue(element => element.DisplayCondition) ?? Observable.Return(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 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(("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))); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Segments/MainSegmentView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Segments/MainSegmentView.axaml index 9884a7533..8e1ac2788 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Segments/MainSegmentView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Segments/MainSegmentView.axaml @@ -46,13 +46,6 @@ Command="{Binding DisableSegment}"> - - -