diff --git a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs index ad7913b54..9b51e0fcc 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using Artemis.Core.Internal; using Artemis.Storage.Entities.Profile.Conditions; namespace Artemis.Core @@ -8,7 +10,7 @@ namespace Artemis.Core private readonly string _displayName; private readonly object? _context; private DateTime _lastProcessedTrigger; - private DataModelPath _eventPath; + private DataModelPath? _eventPath; internal EventCondition(string displayName, object? context) { @@ -17,6 +19,7 @@ namespace Artemis.Core Entity = new EventConditionEntity(); Script = new NodeScript($"Activate {displayName}", $"Whether or not the event should activate the {displayName}", context); + UpdateEventNode(); } internal EventCondition(EventConditionEntity entity, string displayName, object? context) @@ -38,7 +41,7 @@ namespace Artemis.Core /// /// Gets or sets the path to the event that drives this event condition /// - public DataModelPath EventPath + public DataModelPath? EventPath { set => SetAndNotify(ref _eventPath, value); get => _eventPath; @@ -48,7 +51,7 @@ namespace Artemis.Core internal bool Evaluate() { - if (EventPath.GetValue() is not DataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger) + if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger) return false; // TODO: Place dataModelEvent.LastEventArgumentsUntyped; in the start node @@ -65,7 +68,7 @@ namespace Artemis.Core public void Dispose() { Script.Dispose(); - EventPath.Dispose(); + EventPath?.Dispose(); } #endregion @@ -73,6 +76,25 @@ namespace Artemis.Core internal void LoadNodeScript() { Script.Load(); + UpdateEventNode(); + } + + /// + /// Updates the event node, applying the selected event + /// + public void UpdateEventNode() + { + if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent) + return; + + if (Script.Nodes.FirstOrDefault(n => n is EventStartNode) is EventStartNode existing) + existing.UpdateDataModelEvent(dataModelEvent); + else + { + EventStartNode node = new(); + node.UpdateDataModelEvent(dataModelEvent); + Script.AddNode(node); + } } #region Implementation of IStorageModel @@ -82,13 +104,14 @@ namespace Artemis.Core { EventPath = new DataModelPath(null, Entity.EventPath); Script = new NodeScript($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", Entity.Script, _context); + UpdateEventNode(); } /// public void Save() { - EventPath.Save(); - Entity.EventPath = EventPath.Entity; + EventPath?.Save(); + Entity.EventPath = EventPath?.Entity; Script.Save(); Entity.Script = Script.Entity; } diff --git a/src/Artemis.Core/Models/Profile/Conditions/EventsCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/EventsCondition.cs index 8f8af3ad9..86edae3e0 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/EventsCondition.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/EventsCondition.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Conditions; @@ -18,7 +19,7 @@ namespace Artemis.Core _eventsList = new List(); ProfileElement = profileElement; - Events = new List(_eventsList); + Events = new ReadOnlyCollection(_eventsList); } internal EventsCondition(EventsConditionEntity entity, ProfileElement profileElement) @@ -27,7 +28,7 @@ namespace Artemis.Core _eventsList = new List(); ProfileElement = profileElement; - Events = new List(_eventsList); + Events = new ReadOnlyCollection(_eventsList); Load(); } diff --git a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs index b2ce08edb..20ba2ddc3 100644 --- a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs +++ b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs @@ -9,7 +9,7 @@ namespace Artemis.Core /// /// Represents the configuration of a profile, contained in a /// - public class ProfileConfiguration : CorePropertyChanged, IStorageModel, IDisposable + public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable { private ProfileCategory _category; private bool _disposed; @@ -277,6 +277,13 @@ namespace Artemis.Core } #endregion + + #region Overrides of BreakableModel + + /// + public override string BrokenDisplayName => "Profile Configuration"; + + #endregion } /// diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 68db68316..d981c27ff 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -221,8 +221,8 @@ namespace Artemis.Core.Services try { // Make sure the profile is active or inactive according to the parameters above - if (shouldBeActive && profileConfiguration.Profile == null) - ActivateProfile(profileConfiguration); + if (shouldBeActive && profileConfiguration.Profile == null && profileConfiguration.BrokenState != "Failed to activate profile") + profileConfiguration.TryOrBreak(() => ActivateProfile(profileConfiguration), "Failed to activate profile"); else if (!shouldBeActive && profileConfiguration.Profile != null) DeactivateProfile(profileConfiguration); @@ -597,9 +597,9 @@ namespace Artemis.Core.Services profile.Save(); - foreach (RenderProfileElement renderProfileElement in profile.GetAllRenderElements()) + foreach (RenderProfileElement renderProfileElement in profile.GetAllRenderElements()) renderProfileElement.Save(); - + _logger.Debug("Adapt profile - Saving " + profile); profile.RedoStack.Clear(); profile.UndoStack.Push(memento); diff --git a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs index 915f5bac1..a5d734f3d 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs @@ -10,6 +10,7 @@ namespace Artemis.Core string Name { get; } string Description { get; } bool IsExitNode { get; } + bool IsDefaultNode { get; } public double X { get; set; } public double Y { get; set; } diff --git a/src/Artemis.Core/VisualScripting/Internal/EventStartNode.cs b/src/Artemis.Core/VisualScripting/Internal/EventStartNode.cs new file mode 100644 index 000000000..c6fc57242 --- /dev/null +++ b/src/Artemis.Core/VisualScripting/Internal/EventStartNode.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Humanizer; + +namespace Artemis.Core.Internal +{ + internal class EventStartNode : Node + { + private IDataModelEvent? _dataModelEvent; + private readonly Dictionary _propertyPins; + + public EventStartNode() : base("Event Arguments", "Contains the event arguments that triggered the evaluation") + { + _propertyPins = new Dictionary(); + } + + public void UpdateDataModelEvent(IDataModelEvent dataModelEvent) + { + if (_dataModelEvent == dataModelEvent) + return; + + foreach (var (_, outputPin) in _propertyPins) + RemovePin(outputPin); + _propertyPins.Clear(); + + _dataModelEvent = dataModelEvent; + foreach (PropertyInfo propertyInfo in dataModelEvent.ArgumentsType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) + _propertyPins.Add(propertyInfo, CreateOutputPin(propertyInfo.PropertyType, propertyInfo.Name.Humanize())); + } + + #region Overrides of Node + + /// + public override void Evaluate() + { + if (_dataModelEvent?.LastEventArgumentsUntyped == null) + return; + + foreach (var (propertyInfo, outputPin) in _propertyPins) + { + if (outputPin.ConnectedTo.Any()) + outputPin.Value = propertyInfo.GetValue(_dataModelEvent.LastEventArgumentsUntyped) ?? outputPin.Type.GetDefault()!; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/Node.cs b/src/Artemis.Core/VisualScripting/Node.cs index 4d1acc1eb..669de437f 100644 --- a/src/Artemis.Core/VisualScripting/Node.cs +++ b/src/Artemis.Core/VisualScripting/Node.cs @@ -25,6 +25,7 @@ namespace Artemis.Core } private double _x; + public double X { get => _x; @@ -46,6 +47,7 @@ namespace Artemis.Core } public virtual bool IsExitNode => false; + public virtual bool IsDefaultNode => false; private readonly List _pins = new(); public IReadOnlyCollection Pins => new ReadOnlyCollection(_pins); diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index aaf677e09..bc46e929a 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -3,6 +3,8 @@ using Artemis.Core.Modules; using Artemis.Core.ScriptingProviders; using Artemis.UI.Screens.Header; using Artemis.UI.Screens.Plugins; +using Artemis.UI.Screens.ProfileEditor.DisplayConditions.Event; +using Artemis.UI.Screens.ProfileEditor.DisplayConditions.Static; using Artemis.UI.Screens.ProfileEditor.LayerProperties; using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings; using Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects; @@ -92,6 +94,13 @@ namespace Artemis.UI.Ninject.Factories TimelineSegmentViewModel TimelineSegmentViewModel(SegmentViewModelType segment, IObservableCollection layerPropertyGroups); } + public interface IConditionVmFactory : IVmFactory + { + StaticConditionViewModel StaticConditionViewModel(StaticCondition staticCondition); + EventsConditionViewModel EventsConditionViewModel(EventsCondition eventsCondition); + EventConditionViewModel EventConditionViewModel(EventCondition eventCondition); + } + public interface IPrerequisitesVmFactory : IVmFactory { PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall); diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionEventViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionEventViewModel.cs deleted file mode 100644 index 0c0a97b2c..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionEventViewModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions -{ - public class DisplayConditionEventViewModel : Screen - { - } -} diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index abaa2d145..9d5962688 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -37,10 +37,28 @@ + + + + + No condition, the element is always displayed + + + + + + NONE + + + @@ -55,9 +73,9 @@ CONSTANT - @@ -78,200 +96,10 @@ - - - - - - - - - - - - - - - - - - - - - - Click to edit script - - - - - - - - - - - - - - - - - - - - - Configure how the layer should act while the conditions above are met - - - - - - - - - - - - - - - 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 - - - - - - - - - - - Configure how the layer should act when the conditions above are no longer met - - - - - - - - - - - - - - - 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 - - - - - - SKIP TO END - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index 4936bf68d..699ea3340 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -1,6 +1,4 @@ -using System.ComponentModel; -using System.Windows.Input; -using Artemis.Core; +using Artemis.Core; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; @@ -8,84 +6,31 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { - public class DisplayConditionsViewModel : Conductor.Collection.OneActive, IProfileEditorPanelViewModel + public class DisplayConditionsViewModel : Conductor, IProfileEditorPanelViewModel { - private readonly INodeVmFactory _nodeVmFactory; + private readonly IConditionVmFactory _conditionVmFactory; private readonly IProfileEditorService _profileEditorService; - private readonly IWindowManager _windowManager; - private bool _isEventCondition; - private RenderProfileElement _renderProfileElement; + private DisplayConditionType _displayConditionType; - public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IWindowManager windowManager, INodeVmFactory nodeVmFactory) + public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IConditionVmFactory conditionVmFactory) { _profileEditorService = profileEditorService; - _windowManager = windowManager; - _nodeVmFactory = nodeVmFactory; - - Items.Add(new DisplayConditionEventViewModel() { DisplayName = "Event 1"}); - Items.Add(new DisplayConditionEventViewModel() { DisplayName = "Event 2"}); - Items.Add(new DisplayConditionEventViewModel() { DisplayName = "Event 3"}); - Items.Add(new DisplayConditionEventViewModel() { DisplayName = "Event 4"}); + _conditionVmFactory = conditionVmFactory; } - public bool IsEventCondition + public DisplayConditionType DisplayConditionType { - get => _isEventCondition; - set => SetAndNotify(ref _isEventCondition, value); - } - - public RenderProfileElement RenderProfileElement - { - get => _renderProfileElement; + get => _displayConditionType; set { - if (!SetAndNotify(ref _renderProfileElement, value)) return; - NotifyOfPropertyChange(nameof(DisplayContinuously)); - NotifyOfPropertyChange(nameof(AlwaysFinishTimeline)); - NotifyOfPropertyChange(nameof(EventOverlapMode)); + if (!SetAndNotify(ref _displayConditionType, value)) return; + ChangeConditionType(); } } - public bool DisplayContinuously - { - get => RenderProfileElement?.Timeline.PlayMode == TimelinePlayMode.Repeat; - set - { - TimelinePlayMode playMode = value ? TimelinePlayMode.Repeat : TimelinePlayMode.Once; - if (RenderProfileElement == null || RenderProfileElement?.Timeline.PlayMode == playMode) return; - RenderProfileElement.Timeline.PlayMode = playMode; - _profileEditorService.SaveSelectedProfileElement(); - } - } - - public bool AlwaysFinishTimeline - { - get => RenderProfileElement?.Timeline.StopMode == TimelineStopMode.Finish; - set - { - TimelineStopMode stopMode = value ? TimelineStopMode.Finish : TimelineStopMode.SkipToEnd; - if (RenderProfileElement == null || RenderProfileElement?.Timeline.StopMode == stopMode) return; - RenderProfileElement.Timeline.StopMode = stopMode; - _profileEditorService.SaveSelectedProfileElement(); - } - } - - public TimeLineEventOverlapMode EventOverlapMode - { - get => RenderProfileElement?.Timeline.EventOverlapMode ?? TimeLineEventOverlapMode.Restart; - set - { - if (RenderProfileElement == null || RenderProfileElement?.Timeline.EventOverlapMode == value) return; - RenderProfileElement.Timeline.EventOverlapMode = value; - _profileEditorService.SaveSelectedProfileElement(); - } - } - - public bool ConditionBehaviourEnabled => RenderProfileElement != null; - protected override void OnInitialActivate() { - _profileEditorService.SelectedProfileElementChanged += SelectedProfileEditorServiceOnSelectedProfileElementChanged; + _profileEditorService.SelectedProfileElementChanged += ProfileEditorServiceOnSelectedProfileElementChanged; Update(_profileEditorService.SelectedProfileElement); base.OnInitialActivate(); @@ -93,55 +38,63 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions protected override void OnClose() { - _profileEditorService.SelectedProfileElementChanged -= SelectedProfileEditorServiceOnSelectedProfileElementChanged; + _profileEditorService.SelectedProfileElementChanged -= ProfileEditorServiceOnSelectedProfileElementChanged; base.OnClose(); } + private void ChangeConditionType() + { + if (_profileEditorService.SelectedProfileElement == null) + return; + + if (DisplayConditionType == DisplayConditionType.Static) + _profileEditorService.SelectedProfileElement.ChangeDisplayCondition(new StaticCondition(_profileEditorService.SelectedProfileElement)); + else if (DisplayConditionType == DisplayConditionType.Events) + _profileEditorService.SelectedProfileElement.ChangeDisplayCondition(new EventsCondition(_profileEditorService.SelectedProfileElement)); + else + _profileEditorService.SelectedProfileElement.ChangeDisplayCondition(null); + + _profileEditorService.SaveSelectedProfileElement(); + Update(_profileEditorService.SelectedProfileElement); + } + private void Update(RenderProfileElement renderProfileElement) { - if (RenderProfileElement != null) - RenderProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged; - - RenderProfileElement = renderProfileElement; - - NotifyOfPropertyChange(nameof(DisplayContinuously)); - NotifyOfPropertyChange(nameof(AlwaysFinishTimeline)); - NotifyOfPropertyChange(nameof(ConditionBehaviourEnabled)); - - if (RenderProfileElement != null) - RenderProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged; - } - - #region Event handlers - - public void ScriptGridMouseUp(object sender, MouseButtonEventArgs e) - { - if (e.ChangedButton != MouseButton.Left) - return; - if (RenderProfileElement == null) + if (renderProfileElement == null) + { + ActiveItem = null; return; + } - // _windowManager.ShowDialog(_nodeVmFactory.NodeScriptWindowViewModel(RenderProfileElement.DisplayCondition)); - _profileEditorService.SaveSelectedProfileElement(); + if (renderProfileElement.DisplayCondition is StaticCondition staticCondition) + { + ActiveItem = _conditionVmFactory.StaticConditionViewModel(staticCondition); + _displayConditionType = DisplayConditionType.Static; + } + else if (renderProfileElement.DisplayCondition is EventsCondition eventsCondition) + { + ActiveItem = _conditionVmFactory.EventsConditionViewModel(eventsCondition); + _displayConditionType = DisplayConditionType.Events; + } + else + { + ActiveItem = null; + _displayConditionType = DisplayConditionType.None; + } + + NotifyOfPropertyChange(nameof(DisplayConditionType)); } - public void EventTriggerModeSelected() - { - _profileEditorService.SaveSelectedProfileElement(); - } - - private void SelectedProfileEditorServiceOnSelectedProfileElementChanged(object sender, RenderProfileElementEventArgs e) + private void ProfileEditorServiceOnSelectedProfileElementChanged(object sender, RenderProfileElementEventArgs e) { Update(e.RenderProfileElement); } + } - private void TimelineOnPropertyChanged(object sender, PropertyChangedEventArgs e) - { - NotifyOfPropertyChange(nameof(DisplayContinuously)); - NotifyOfPropertyChange(nameof(AlwaysFinishTimeline)); - NotifyOfPropertyChange(nameof(EventOverlapMode)); - } - - #endregion + public enum DisplayConditionType + { + None, + Static, + Events } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionView.xaml new file mode 100644 index 000000000..1ebd5faf6 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Event/EventConditionView.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + +