diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs index 36a73ea76..534f48926 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq.Expressions; using Artemis.Core.Services.Interfaces; using Artemis.Storage.Entities.Profile.Abstract; @@ -34,9 +35,10 @@ namespace Artemis.Core.Models.Profile.Conditions.Abstract } } - public abstract DisplayConditionPartEntity GetEntity(); + public abstract bool Evaluate(); - internal abstract void ApplyToEntity(); internal abstract void Initialize(IDataModelService dataModelService); + internal abstract void ApplyToEntity(); + internal abstract DisplayConditionPartEntity GetEntity(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs index 960387384..c6fcbfc97 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs @@ -33,6 +33,23 @@ namespace Artemis.Core.Models.Profile.Conditions public BooleanOperator BooleanOperator { get; set; } public DisplayConditionGroupEntity DisplayConditionGroupEntity { get; set; } + public override bool Evaluate() + { + switch (BooleanOperator) + { + case BooleanOperator.And: + return Children.All(c => c.Evaluate()); + case BooleanOperator.Or: + return Children.Any(c => c.Evaluate()); + case BooleanOperator.AndNot: + return Children.All(c => !c.Evaluate()); + case BooleanOperator.OrNot: + return Children.Any(c => !c.Evaluate()); + default: + throw new ArgumentOutOfRangeException(); + } + } + internal override void ApplyToEntity() { DisplayConditionGroupEntity.BooleanOperator = (int) BooleanOperator; @@ -49,7 +66,7 @@ namespace Artemis.Core.Models.Profile.Conditions child.Initialize(dataModelService); } - public override DisplayConditionPartEntity GetEntity() + internal override DisplayConditionPartEntity GetEntity() { return DisplayConditionGroupEntity; } diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs index d5596b0b8..2e96b913a 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs @@ -8,18 +8,24 @@ namespace Artemis.Core.Models.Profile.Conditions { public ListOperator ListOperator { get; set; } + public override bool Evaluate() + { + return true; + } + + internal override void ApplyToEntity() { } - internal override void Initialize(IDataModelService dataModelService) - { - } - - public override DisplayConditionPartEntity GetEntity() + internal override DisplayConditionPartEntity GetEntity() { return null; } + + internal override void Initialize(IDataModelService dataModelService) + { + } } public enum ListOperator diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs index 4b694d69d..04a1af178 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs @@ -61,6 +61,7 @@ namespace Artemis.Core.Models.Profile.Conditions ValidateOperator(); ValidateRightSide(); + CreateExpression(); } @@ -91,6 +92,7 @@ namespace Artemis.Core.Models.Profile.Conditions RightPropertyPath = null; SetStaticValue(staticValue); + CreateExpression(); } @@ -111,14 +113,21 @@ namespace Artemis.Core.Models.Profile.Conditions var leftType = LeftDataModel.GetTypeAtPath(LeftPropertyPath); if (displayConditionOperator.SupportsType(leftType)) Operator = displayConditionOperator; + + CreateExpression(); } - public void CreateExpression() + private void CreateExpression() { + DynamicConditionLambda = null; + CompiledDynamicConditionLambda = null; + StaticConditionLambda = null; + CompiledStaticConditionLambda = null; + if (PredicateType == PredicateType.Dynamic) CreateDynamicExpression(); - else - CreateStaticExpression(); + + CreateStaticExpression(); } internal override void ApplyToEntity() @@ -134,6 +143,16 @@ namespace Artemis.Core.Models.Profile.Conditions DisplayConditionPredicateEntity.OperatorType = Operator?.GetType().Name; } + public override bool Evaluate() + { + if (CompiledDynamicConditionLambda != null) + return CompiledDynamicConditionLambda(LeftDataModel, RightDataModel); + if (CompiledStaticConditionLambda != null) + return CompiledStaticConditionLambda(LeftDataModel); + + return false; + } + internal override void Initialize(IDataModelService dataModelService) { // Left side @@ -184,7 +203,7 @@ namespace Artemis.Core.Models.Profile.Conditions } } - public override DisplayConditionPartEntity GetEntity() + internal override DisplayConditionPartEntity GetEntity() { return DisplayConditionPredicateEntity; } @@ -199,9 +218,6 @@ namespace Artemis.Core.Models.Profile.Conditions Operator = null; } - /// - /// Validates the right side, ensuring it is still compatible with the current left side - /// private void ValidateRightSide() { var leftSideType = LeftDataModel.GetTypeAtPath(LeftPropertyPath); @@ -223,10 +239,6 @@ namespace Artemis.Core.Models.Profile.Conditions } } - /// - /// Updates the current static value, ensuring it is a valid type. This assumes the types are compatible if they - /// differ. - /// private void SetStaticValue(object staticValue) { // If the left side is empty simply apply the value, any validation will wait diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index f1dd44db7..728848333 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -65,6 +65,8 @@ namespace Artemis.Core.Models.Profile if (!Enabled) return; + UpdateDisplayCondition(); + foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled)) baseLayerEffect.Update(deltaTime); diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index a96d9b15a..2aa064252 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -205,6 +205,8 @@ namespace Artemis.Core.Models.Profile if (LayerBrush?.BaseProperties == null || !LayerBrush.BaseProperties.PropertiesInitialized) return; + UpdateDisplayCondition(); + // TODO: Remove, this is slow and stupid // For now, reset all keyframe engines after the last keyframe was hit // This is a placeholder method of repeating the animation until repeat modes are implemented diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs index d0537d8b1..d54a254a4 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs @@ -18,6 +18,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// public BaseLayerProperty BaseLayerProperty { get; internal set; } + /// + /// The timeline this keyframe is contained in + /// + public abstract Timeline Timeline { get; set; } + /// /// The position of this keyframe in the timeline /// @@ -28,4 +33,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// public Easings.Functions EasingFunction { get; set; } } + + public enum Timeline + { + Start, + Main, + End + } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index d5eab15fd..e3f255975 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -23,10 +23,16 @@ namespace Artemis.Core.Models.Profile.LayerProperties private T _currentValue; private bool _isInitialized; private List> _keyframes; + private List> _startKeyframes; + private List> _mainKeyframes; + private List> _endKeyframes; protected LayerProperty() { _keyframes = new List>(); + _startKeyframes = new List>(); + _mainKeyframes = new List>(); + _endKeyframes = new List>(); } /// @@ -84,7 +90,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// The value to set. /// /// An optional time to set the value add, if provided and property is using keyframes the value will be set to an new - /// or existing keyframe. + /// or existing keyframe in the currently active timeline. /// public void SetCurrentValue(T value, TimeSpan? time) { @@ -93,10 +99,10 @@ namespace Artemis.Core.Models.Profile.LayerProperties else { // If on a keyframe, update the keyframe - var currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value); + var currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value && k.Timeline == ProfileElement.CurrentTimeline); // Create a new keyframe if none found if (currentKeyframe == null) - AddKeyframe(new LayerPropertyKeyframe(value, time.Value, Easings.Functions.Linear, this)); + AddKeyframe(new LayerPropertyKeyframe(value, time.Value, ProfileElement.CurrentTimeline, Easings.Functions.Linear, this)); else currentKeyframe.Value = value; @@ -132,6 +138,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties var newKeyframe = new LayerPropertyKeyframe( keyframe.Value, keyframe.Position, + keyframe.Timeline, keyframe.EasingFunction, keyframe.LayerProperty ); @@ -193,22 +200,42 @@ namespace Artemis.Core.Models.Profile.LayerProperties if (!KeyframesSupported || !KeyframesEnabled) return; + var keyframeSet = _keyframes; + if (ProfileElement.CurrentTimeline == Timeline.Start) + keyframeSet = _startKeyframes; + else if (ProfileElement.CurrentTimeline == Timeline.Main) + keyframeSet = _mainKeyframes; + else if (ProfileElement.CurrentTimeline == Timeline.End) + keyframeSet = _endKeyframes; + // The current keyframe is the last keyframe before the current time - CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= TimelineProgress); + CurrentKeyframe = keyframeSet.LastOrDefault(k => k.Position <= TimelineProgress); + + // If the current keyframe is null, try to find it in previous timelines + if (CurrentKeyframe == null && ProfileElement.CurrentTimeline == Timeline.Main) + CurrentKeyframe = _startKeyframes.LastOrDefault(); + else if (CurrentKeyframe == null && ProfileElement.CurrentTimeline == Timeline.End) + CurrentKeyframe = _mainKeyframes.LastOrDefault() ?? _startKeyframes.LastOrDefault(); + // Keyframes are sorted by position so we can safely assume the next keyframe's position is after the current - var nextIndex = _keyframes.IndexOf(CurrentKeyframe) + 1; - NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[nextIndex] : null; + var nextIndex = keyframeSet.IndexOf(CurrentKeyframe) + 1; + NextKeyframe = keyframeSet.Count > nextIndex ? keyframeSet[nextIndex] : null; // No need to update the current value if either of the keyframes are null if (CurrentKeyframe == null) - CurrentValue = _keyframes.Any() ? _keyframes[0].Value : BaseValue; + CurrentValue = keyframeSet.Any() ? keyframeSet[0].Value : BaseValue; else if (NextKeyframe == null) CurrentValue = CurrentKeyframe.Value; // Only determine progress and current value if both keyframes are present else { - var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position; - var keyframeProgress = (float) ((TimelineProgress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds); + // If the current keyframe belongs to a previous timeline, consider it starting at 0 + var currentKeyframePosition = CurrentKeyframe.Position; + if (CurrentKeyframe.Timeline != ProfileElement.CurrentTimeline) + currentKeyframePosition = TimeSpan.Zero; + + var timeDiff = NextKeyframe.Position - currentKeyframePosition; + var keyframeProgress = (float) ((TimelineProgress - currentKeyframePosition).TotalMilliseconds / timeDiff.TotalMilliseconds); var keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction); UpdateCurrentValue(keyframeProgress, keyframeProgressEased); } @@ -227,11 +254,15 @@ namespace Artemis.Core.Models.Profile.LayerProperties } /// - /// Sorts the keyframes in ascending order by position + /// Sorts the keyframes in ascending order by position and divides the keyframes into different timelines /// internal void SortKeyframes() { _keyframes = _keyframes.OrderBy(k => k.Position).ToList(); + + _startKeyframes = _keyframes.Where(k => k.Timeline == Timeline.Start).ToList(); + _mainKeyframes = _keyframes.Where(k => k.Timeline == Timeline.Main).ToList(); + _endKeyframes = _keyframes.Where(k => k.Timeline == Timeline.End).ToList(); } internal override void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage) @@ -258,6 +289,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties _keyframes.AddRange(entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe( JsonConvert.DeserializeObject(k.Value), k.Position, + (Timeline) k.Timeline, (Easings.Functions) k.EasingFunction, this ))); @@ -287,6 +319,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties { Value = JsonConvert.SerializeObject(k.Value), Position = k.Position, + Timeline = (int) k.Timeline, EasingFunction = (int) k.EasingFunction })); } diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs index af37209aa..9f6791a80 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs @@ -6,10 +6,12 @@ namespace Artemis.Core.Models.Profile.LayerProperties public class LayerPropertyKeyframe : BaseLayerPropertyKeyframe { private TimeSpan _position; + private Timeline _timeline; - public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction, LayerProperty layerProperty) : base(layerProperty) + public LayerPropertyKeyframe(T value, TimeSpan position, Timeline timeline, Easings.Functions easingFunction, LayerProperty layerProperty) : base(layerProperty) { _position = position; + _timeline = timeline; Value = value; LayerProperty = layerProperty; EasingFunction = easingFunction; @@ -25,6 +27,17 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// public T Value { get; set; } + /// + public override Timeline Timeline + { + get => _timeline; + set + { + _timeline = value; + LayerProperty.SortKeyframes(); + } + } + /// public override TimeSpan Position { diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 80338b462..a8aa76848 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.Annotations; using Artemis.Core.Models.Profile.Conditions; +using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.LayerEffect.Abstract; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile.Abstract; @@ -18,6 +19,11 @@ namespace Artemis.Core.Models.Profile private SKPath _path; internal abstract RenderElementEntity RenderElementEntity { get; } + /// + /// Gets or sets the currently active timeline + /// + public Timeline CurrentTimeline { get; set; } + /// /// Gets the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is /// clipped. @@ -142,6 +148,11 @@ namespace Artemis.Core.Models.Profile set => SetAndNotify(ref _displayConditionGroup, value); } + public void UpdateDisplayCondition() + { + var rootGroupResult = DisplayConditionGroup.Evaluate(); + } + #endregion #region Events diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 3406dabc2..d970852c7 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -19,10 +19,10 @@ namespace Artemis.Core.Services.Storage /// public class ProfileService : IProfileService { - private readonly IRenderElementService _renderElementService; private readonly ILogger _logger; private readonly IPluginService _pluginService; private readonly IProfileRepository _profileRepository; + private readonly IRenderElementService _renderElementService; private readonly ISurfaceService _surfaceService; internal ProfileService(ILogger logger, IPluginService pluginService, ISurfaceService surfaceService, IRenderElementService renderElementService, IProfileRepository profileRepository) @@ -39,6 +39,8 @@ namespace Artemis.Core.Services.Storage _pluginService.PluginDisabled += OnPluginToggled; } + public JsonSerializerSettings MementoSettings { get; set; } = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All}; + public void ActivateDefaultProfiles() { foreach (var profileModule in _pluginService.GetPluginsOfType()) @@ -109,7 +111,7 @@ namespace Artemis.Core.Services.Storage public void UpdateProfile(Profile profile, bool includeChildren) { _logger.Debug("Updating profile " + profile); - var memento = JsonConvert.SerializeObject(profile.ProfileEntity); + var memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings); profile.RedoStack.Clear(); profile.UndoStack.Push(memento); @@ -135,9 +137,9 @@ namespace Artemis.Core.Services.Storage ActivateProfile(module, null); var top = profile.UndoStack.Pop(); - var memento = JsonConvert.SerializeObject(profile.ProfileEntity); + var memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings); profile.RedoStack.Push(memento); - profile.ProfileEntity = JsonConvert.DeserializeObject(top); + profile.ProfileEntity = JsonConvert.DeserializeObject(top, MementoSettings); profile.ApplyToProfile(); ActivateProfile(module, profile); @@ -155,9 +157,9 @@ namespace Artemis.Core.Services.Storage ActivateProfile(module, null); var top = profile.RedoStack.Pop(); - var memento = JsonConvert.SerializeObject(profile.ProfileEntity); + var memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings); profile.UndoStack.Push(memento); - profile.ProfileEntity = JsonConvert.DeserializeObject(top); + profile.ProfileEntity = JsonConvert.DeserializeObject(top, MementoSettings); profile.ApplyToProfile(); ActivateProfile(module, profile); diff --git a/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs b/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs index de95b56b8..610bf0910 100644 --- a/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs @@ -22,6 +22,7 @@ namespace Artemis.Storage.Entities.Profile public class KeyframeEntity { public TimeSpan Position { get; set; } + public int Timeline { get; set; } public string Value { get; set; } public int EasingFunction { get; set; } } diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs index 4738fe07b..9f627d4ea 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Models; using Artemis.UI.Shared.Events; @@ -14,6 +15,7 @@ namespace Artemis.UI.Shared.Services.Interfaces Profile SelectedProfile { get; } RenderProfileElement SelectedProfileElement { get; } TimeSpan CurrentTime { get; set; } + Timeline CurrentTimeline { get; set; } int PixelsPerSecond { get; set; } IReadOnlyList RegisteredPropertyEditors { get; } IKernel Kernel { get; } @@ -54,6 +56,11 @@ namespace Artemis.UI.Shared.Services.Interfaces /// event EventHandler CurrentTimeChanged; + /// + /// Occurs when the current editor timeline is changed + /// + event EventHandler CurrentTimelineChanged; + /// /// Occurs when the pixels per second (zoom level) is changed /// diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index 34384bddf..ad64bc88b 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Exceptions; using Artemis.Core.Plugins.Models; @@ -24,6 +25,7 @@ namespace Artemis.UI.Shared.Services private TimeSpan _currentTime; private TimeSpan _lastUpdateTime; private int _pixelsPerSecond; + private Timeline _currentTimeline; public ProfileEditorService(ICoreService coreService, IProfileService profileService, IKernel kernel, ILogger logger) { @@ -46,14 +48,25 @@ namespace Artemis.UI.Shared.Services get => _currentTime; set { - if (_currentTime.Equals(value)) - return; + if (_currentTime.Equals(value)) return; _currentTime = value; UpdateProfilePreview(); OnCurrentTimeChanged(); } } + public Timeline CurrentTimeline + { + get => _currentTimeline; + set + { + if (_currentTimeline.Equals(value)) return; + _currentTimeline = value; + UpdateProfilePreview(); + OnCurrentTimelineChanged(); + } + } + public int PixelsPerSecond { get => _pixelsPerSecond; @@ -113,12 +126,14 @@ namespace Artemis.UI.Shared.Services var delta = CurrentTime - _lastUpdateTime; foreach (var folder in SelectedProfile.GetAllFolders()) { + folder.CurrentTimeline = CurrentTimeline; foreach (var baseLayerEffect in folder.LayerEffects) baseLayerEffect.Update(delta.TotalSeconds); } foreach (var layer in SelectedProfile.GetAllLayers()) { + layer.CurrentTimeline = CurrentTimeline; layer.OverrideProgress(CurrentTime); layer.LayerBrush?.Update(delta.TotalSeconds); foreach (var baseLayerEffect in layer.LayerEffects) @@ -212,6 +227,7 @@ namespace Artemis.UI.Shared.Services public event EventHandler ProfileElementSelected; public event EventHandler SelectedProfileElementUpdated; public event EventHandler CurrentTimeChanged; + public event EventHandler CurrentTimelineChanged; public event EventHandler PixelsPerSecondChanged; public event EventHandler ProfilePreviewUpdated; @@ -250,6 +266,11 @@ namespace Artemis.UI.Shared.Services CurrentTimeChanged?.Invoke(this, EventArgs.Empty); } + protected virtual void OnCurrentTimelineChanged() + { + CurrentTimelineChanged?.Invoke(this, EventArgs.Empty); + } + protected virtual void OnPixelsPerSecondChanged() { PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml index d20afb360..c0b04af58 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml @@ -148,13 +148,11 @@ HorizontalAlignment="Left"> - - diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index ab6cb5e15..9df02ef19 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -9,27 +9,63 @@ mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> + + + + - + - + Display conditions - + - - + + - - Disabled - - Enabled + + + Play once + + + When conditions no longer met + + + + + + When conditions are no longer met, finish the timelines and then stop displaying. + + + + + + WAIT FOR FINISH + + + + + + + When conditions are no longer met, stop displaying immediately. + + + + + + SKIP + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml index 2e7726bfb..6b14a0e04 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml @@ -291,7 +291,7 @@ - + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 8cc81045e..0013113d6 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -84,6 +84,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties } } + public int CurrentTimelineIndex + { + get => (int) ProfileEditorService.CurrentTimeline; + set + { + ProfileEditorService.CurrentTimeline = (Core.Models.Profile.LayerProperties.Timeline) value; + ProfileEditorService.CurrentTime = TimeSpan.Zero; + } + } + public bool PropertyTreeVisible => PropertyTreeIndex == 0; public RenderProfileElement SelectedProfileElement @@ -130,6 +140,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected; ProfileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged; + ProfileEditorService.CurrentTimelineChanged += ProfileEditorServiceOnCurrentTimelineChanged; ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; base.OnInitialActivate(); @@ -139,6 +150,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties { ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; ProfileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged; + ProfileEditorService.CurrentTimelineChanged -= ProfileEditorServiceOnCurrentTimelineChanged; ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; PopulateProperties(null); @@ -168,6 +180,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties NotifyOfPropertyChange(nameof(TimeCaretPosition)); } + private void ProfileEditorServiceOnCurrentTimelineChanged(object? sender, EventArgs e) + { + NotifyOfPropertyChange(nameof(CurrentTimelineIndex)); + TimelineViewModel.UpdateKeyframes(); + } + private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e) { NotifyOfPropertyChange(nameof(TimeCaretPosition)); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs index 8e320fd77..6273574bf 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs @@ -27,6 +27,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline var newKeyframe = new LayerPropertyKeyframe( LayerPropertyKeyframe.Value, LayerPropertyKeyframe.Position, + LayerPropertyKeyframe.Timeline, LayerPropertyKeyframe.EasingFunction, LayerPropertyKeyframe.LayerProperty ); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupView.xaml index 1fae0fbca..7fa179733 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupView.xaml @@ -23,7 +23,7 @@ Visibility="{Binding LayerPropertyGroupViewModel.IsExpanded, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" ItemsSource="{Binding TimelineKeyframeViewModels}" Background="{DynamicResource MaterialDesignToolBarBackground}" - HorizontalAlignment="Left"> + HorizontalAlignment="Stretch"> @@ -49,20 +49,22 @@ + HorizontalAlignment="Stretch" + HorizontalContentAlignment="Stretch"> + + + - + - + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs index 2a8a9578f..7d4dab2f6 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs @@ -2,16 +2,19 @@ using System.ComponentModel; using System.Linq; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; +using Artemis.UI.Shared.Services.Interfaces; using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline { public class TimelinePropertyGroupViewModel : PropertyChangedBase { + private readonly IProfileEditorService _profileEditorService; private BindableCollection _timelineKeyframeViewModels; - public TimelinePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) + public TimelinePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel, IProfileEditorService profileEditorService) { + _profileEditorService = profileEditorService; LayerPropertyGroupViewModel = (LayerPropertyGroupViewModel) layerPropertyBaseViewModel; TimelineKeyframeViewModels = new BindableCollection(); @@ -32,6 +35,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline { TimelineKeyframeViewModels.Clear(); TimelineKeyframeViewModels.AddRange(LayerPropertyGroupViewModel.GetKeyframes(false) + .Where(k => k.Timeline == _profileEditorService.CurrentTimeline) .Select(k => LayerPropertyGroupViewModel.ProfileEditorService.PixelsPerSecond * k.Position.TotalSeconds)); } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml index 87f5eaa42..d20695b20 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml @@ -9,7 +9,9 @@ mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:TimelinePropertyViewModel}" - Visibility="{Binding LayerPropertyBaseViewModel.IsVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"> + Visibility="{Binding LayerPropertyBaseViewModel.IsVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" + MinWidth="{Binding Width}" + HorizontalAlignment="Stretch"> k.Timeline == _profileEditorService.CurrentTimeline) + .ToList(); var toRemove = TimelineKeyframeViewModels.Where(t => !keyframes.Contains(t.BaseLayerPropertyKeyframe)).ToList(); TimelineKeyframeViewModels.RemoveRange(toRemove); TimelineKeyframeViewModels.AddRange( @@ -60,12 +62,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline { foreach (var timelineKeyframeViewModel in TimelineKeyframeViewModels) timelineKeyframeViewModel.Update(_profileEditorService.PixelsPerSecond); + + Width = TimelineKeyframeViewModels.Any() ? TimelineKeyframeViewModels.Max(t => t.X) + 25 : 0; } } public abstract class TimelinePropertyViewModel : PropertyChangedBase, IDisposable { private BindableCollection _timelineKeyframeViewModels; + private double _width; protected TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) { @@ -81,6 +86,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline set => SetAndNotify(ref _timelineKeyframeViewModels, value); } + public double Width + { + get => _width; + set => SetAndNotify(ref _width, value); + } + public abstract void Dispose(); public abstract void UpdateKeyframes(); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineView.xaml index 9645851d1..1e6549ac2 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineView.xaml @@ -12,7 +12,8 @@ + MouseMove="{s:Action TimelineCanvasMouseMove}" + HorizontalAlignment="Stretch"> @@ -31,12 +32,11 @@ - + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs index f8dd39b8e..ba7135f71 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs @@ -18,7 +18,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline private readonly LayerPropertiesViewModel _layerPropertiesViewModel; private readonly IProfileEditorService _profileEditorService; private RectangleGeometry _selectionRectangle; - private double _width; public TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups, IProfileEditorService profileEditorService) @@ -33,12 +32,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline public BindableCollection LayerPropertyGroups { get; } - public double Width - { - get => _width; - set => SetAndNotify(ref _width, value); - } - public RectangleGeometry SelectionRectangle { get => _selectionRectangle; diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs index c0470e579..6e4017456 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs @@ -50,6 +50,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree LayerPropertyViewModel.LayerProperty.AddKeyframe(new LayerPropertyKeyframe( LayerPropertyViewModel.LayerProperty.CurrentValue, _profileEditorService.CurrentTime, + _profileEditorService.CurrentTimeline, Easings.Functions.Linear, LayerPropertyViewModel.LayerProperty ));