diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index dab4fc5a4..0dfb96dd2 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -30,7 +30,6 @@ namespace Artemis.Core Profile = Parent.Profile; Name = name; Enabled = true; - DisplayContinuously = true; _layerEffects = new List(); _expandedPropertyGroups = new List(); @@ -56,24 +55,23 @@ namespace Artemis.Core Load(); } + public bool IsRootFolder => Parent == Profile; + internal FolderEntity FolderEntity { get; set; } + internal override RenderElementEntity RenderElementEntity => FolderEntity; + /// public override List GetAllLayerProperties() { List result = new List(); foreach (BaseLayerEffect layerEffect in LayerEffects) - { if (layerEffect.BaseProperties != null) result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties()); - } return result; } - internal override RenderElementEntity RenderElementEntity => FolderEntity; - public bool IsRootFolder => Parent == Profile; - public override void Update(double deltaTime) { if (_disposed) @@ -82,149 +80,28 @@ namespace Artemis.Core if (!Enabled) return; - // Disable data bindings during an override - bool wasApplyingDataBindings = ApplyDataBindingsEnabled; - ApplyDataBindingsEnabled = false; - + // Ensure the layer must still be displayed UpdateDisplayCondition(); - // Update the layer timeline, this will give us a new delta time which could be negative in case the main segment wrapped back - // to it's start - UpdateTimeline(deltaTime); + // Update the layer timeline + UpdateTimeLines(deltaTime); - foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - { - baseLayerEffect.BaseProperties?.Update(deltaTime); - baseLayerEffect.Update(deltaTime); - } - - // Iterate the children in reverse because that's how they must be rendered too - for (int index = Children.Count - 1; index > -1; index--) - { - ProfileElement profileElement = Children[index]; - profileElement.Update(deltaTime); - } - - // Restore the old data bindings enabled state - ApplyDataBindingsEnabled = wasApplyingDataBindings; + foreach (ProfileElement child in Children) + child.Update(deltaTime); } - protected internal override void UpdateTimelineLength() + /// + public override void Reset() { - TimelineLength = !Children.Any() ? TimeSpan.Zero : Children.OfType().Max(c => c.TimelineLength); - if (StartSegmentLength + MainSegmentLength + EndSegmentLength > TimelineLength) - TimelineLength = StartSegmentLength + MainSegmentLength + EndSegmentLength; - - if (Parent is RenderProfileElement parent) - parent.UpdateTimelineLength(); - } - - public override void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment) - { - if (_disposed) - throw new ObjectDisposedException("Folder"); - - if (!Enabled) - return; - - // If the condition is event-based, never display continuously - bool displayContinuously = (DisplayCondition == null || !DisplayCondition.ContainsEvents) && DisplayContinuously; - TimeSpan beginTime = TimelinePosition; - - if (stickToMainSegment) - { - if (!displayContinuously) - { - TimeSpan position = timeOverride + StartSegmentLength; - if (position > StartSegmentLength + EndSegmentLength) - TimelinePosition = StartSegmentLength + EndSegmentLength; - } - else - { - double progress = timeOverride.TotalMilliseconds % MainSegmentLength.TotalMilliseconds; - if (progress > 0) - TimelinePosition = TimeSpan.FromMilliseconds(progress) + StartSegmentLength; - else - TimelinePosition = StartSegmentLength; - } - } - else - TimelinePosition = timeOverride; - - double delta = (TimelinePosition - beginTime).TotalSeconds; + DisplayConditionMet = false; + TimeLine = TimelineLength; + ExtraTimeLines.Clear(); foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - { - baseLayerEffect.BaseProperties?.Update(delta); - baseLayerEffect.Update(delta); - } - } + baseLayerEffect.BaseProperties?.Reset(); - public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) - { - if (_disposed) - throw new ObjectDisposedException("Folder"); - - if (Path == null || !Enabled || !Children.Any(c => c.Enabled)) - return; - - // No need to render if at the end of the timeline - if (TimelinePosition > TimelineLength) - return; - - if (_folderBitmap == null) - _folderBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height)); - else if (_folderBitmap.Info.Width != (int) Path.Bounds.Width || _folderBitmap.Info.Height != (int) Path.Bounds.Height) - { - _folderBitmap.Dispose(); - _folderBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height)); - } - - using SKPath folderPath = new SKPath(Path); - using SKCanvas folderCanvas = new SKCanvas(_folderBitmap); - using SKPaint folderPaint = new SKPaint(); - folderCanvas.Clear(); - - folderPath.Transform(SKMatrix.MakeTranslation(folderPath.Bounds.Left * -1, folderPath.Bounds.Top * -1)); - - SKPoint targetLocation = Path.Bounds.Location; - if (Parent is Folder parentFolder) - targetLocation -= parentFolder.Path.Bounds.Location; - - canvas.Save(); - - using SKPath clipPath = new SKPath(folderPath); - clipPath.Transform(SKMatrix.MakeTranslation(targetLocation.X, targetLocation.Y)); - canvas.ClipPath(clipPath); - - foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - baseLayerEffect.PreProcess(folderCanvas, _folderBitmap.Info, folderPath, folderPaint); - - // No point rendering if the alpha was set to zero by one of the effects - if (folderPaint.Color.Alpha == 0) - return; - - // Iterate the children in reverse because the first layer must be rendered last to end up on top - for (int index = Children.Count - 1; index > -1; index--) - { - folderCanvas.Save(); - ProfileElement profileElement = Children[index]; - profileElement.Render(deltaTime, folderCanvas, _folderBitmap.Info); - folderCanvas.Restore(); - } - - // If required, apply the opacity override of the module to the root folder - if (IsRootFolder && Profile.Module.OpacityOverride < 1) - { - double multiplier = Easings.SineEaseInOut(Profile.Module.OpacityOverride); - folderPaint.Color = folderPaint.Color.WithAlpha((byte) (folderPaint.Color.Alpha * multiplier)); - } - - foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - baseLayerEffect.PostProcess(canvas, canvasInfo, folderPath, folderPaint); - canvas.DrawBitmap(_folderBitmap, targetLocation, folderPaint); - - canvas.Restore(); + foreach (ProfileElement child in Children) + child.Reset(); } /// @@ -234,7 +111,7 @@ namespace Artemis.Core throw new ObjectDisposedException("Folder"); base.AddChild(child, order); - UpdateTimelineLength(); + UpdateTimeLineLength(); CalculateRenderProperties(); } @@ -245,7 +122,7 @@ namespace Artemis.Core throw new ObjectDisposedException("Folder"); base.RemoveChild(child); - UpdateTimelineLength(); + UpdateTimeLineLength(); CalculateRenderProperties(); } @@ -261,10 +138,8 @@ namespace Artemis.Core SKPath path = new SKPath {FillType = SKPathFillType.Winding}; foreach (ProfileElement child in Children) - { if (child is RenderProfileElement effectChild && effectChild.Path != null) path.AddPath(effectChild.Path); - } Path = path; @@ -275,6 +150,16 @@ namespace Artemis.Core OnRenderPropertiesUpdated(); } + protected internal override void UpdateTimeLineLength() + { + TimelineLength = !Children.Any() ? TimeSpan.Zero : Children.OfType().Max(c => c.TimelineLength); + if (StartSegmentLength + MainSegmentLength + EndSegmentLength > TimelineLength) + TimelineLength = StartSegmentLength + MainSegmentLength + EndSegmentLength; + + if (Parent is RenderProfileElement parent) + parent.UpdateTimeLineLength(); + } + protected override void Dispose(bool disposing) { _disposed = true; @@ -324,6 +209,104 @@ namespace Artemis.Core SaveRenderElement(); } + #region Rendering + + private TimeSpan _lastRenderTime; + + public override void Render(SKCanvas canvas, SKImageInfo canvasInfo) + { + if (_disposed) + throw new ObjectDisposedException("Folder"); + + if (!Enabled || !Children.Any(c => c.Enabled)) + return; + + // Ensure the folder is ready + if (Path == null) + return; + + RenderFolder(TimeLine, canvas, canvasInfo); + } + + private void PrepareForRender(TimeSpan timeLine) + { + double renderDelta = (timeLine - _lastRenderTime).TotalSeconds; + + foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) + { + baseLayerEffect.BaseProperties?.Update(renderDelta); + baseLayerEffect.Update(renderDelta); + } + + _lastRenderTime = timeLine; + } + + private void RenderFolder(TimeSpan timeLine, SKCanvas canvas, SKImageInfo canvasInfo) + { + if (timeLine > TimelineLength) + return; + + PrepareForRender(timeLine); + + if (_folderBitmap == null) + { + _folderBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height)); + } + else if (_folderBitmap.Info.Width != (int) Path.Bounds.Width || _folderBitmap.Info.Height != (int) Path.Bounds.Height) + { + _folderBitmap.Dispose(); + _folderBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height)); + } + + using SKPath folderPath = new SKPath(Path); + using SKCanvas folderCanvas = new SKCanvas(_folderBitmap); + using SKPaint folderPaint = new SKPaint(); + folderCanvas.Clear(); + + folderPath.Transform(SKMatrix.MakeTranslation(folderPath.Bounds.Left * -1, folderPath.Bounds.Top * -1)); + + SKPoint targetLocation = Path.Bounds.Location; + if (Parent is Folder parentFolder) + targetLocation -= parentFolder.Path.Bounds.Location; + + canvas.Save(); + + using SKPath clipPath = new SKPath(folderPath); + clipPath.Transform(SKMatrix.MakeTranslation(targetLocation.X, targetLocation.Y)); + canvas.ClipPath(clipPath); + + foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) + baseLayerEffect.PreProcess(folderCanvas, _folderBitmap.Info, folderPath, folderPaint); + + // No point rendering if the alpha was set to zero by one of the effects + if (folderPaint.Color.Alpha == 0) + return; + + // Iterate the children in reverse because the first layer must be rendered last to end up on top + for (int index = Children.Count - 1; index > -1; index--) + { + folderCanvas.Save(); + ProfileElement profileElement = Children[index]; + profileElement.Render(folderCanvas, _folderBitmap.Info); + folderCanvas.Restore(); + } + + // If required, apply the opacity override of the module to the root folder + if (IsRootFolder && Profile.Module.OpacityOverride < 1) + { + double multiplier = Easings.SineEaseInOut(Profile.Module.OpacityOverride); + folderPaint.Color = folderPaint.Color.WithAlpha((byte) (folderPaint.Color.Alpha * multiplier)); + } + + foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) + baseLayerEffect.PostProcess(canvas, canvasInfo, folderPath, folderPaint); + canvas.DrawBitmap(_folderBitmap, targetLocation, folderPaint); + + canvas.Restore(); + } + + #endregion + #region Events public event EventHandler RenderPropertiesUpdated; diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 3bb195fd4..a920689eb 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -37,7 +37,6 @@ namespace Artemis.Core Profile = Parent.Profile; Name = name; Enabled = true; - DisplayContinuously = true; General = new LayerGeneralProperties(); Transform = new LayerTransformProperties(); @@ -177,6 +176,21 @@ namespace Artemis.Core ActivateLayerBrush(); } + /// + public override void Reset() + { + DisplayConditionMet = false; + TimeLine = TimelineLength; + ExtraTimeLines.Clear(); + + General.Reset(); + Transform.Reset(); + LayerBrush.BaseProperties?.Reset(); + + foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) + baseLayerEffect.BaseProperties?.Reset(); + } + #region Storage internal override void Load() @@ -251,6 +265,8 @@ namespace Artemis.Core #endregion #region Rendering + + private TimeSpan _lastRenderTime; /// public override void Update(double deltaTime) @@ -258,97 +274,28 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("Layer"); - if (!Enabled || LayerBrush?.BaseProperties == null || !LayerBrush.BaseProperties.PropertiesInitialized) + if (!Enabled) return; // Ensure the layer must still be displayed UpdateDisplayCondition(); - // Update the layer timeline, this will give us a new delta time which could be negative in case the main segment wrapped back - // to it's start - UpdateTimeline(deltaTime); - - // No point updating further than this if the layer is not going to be rendered - if (TimelinePosition > TimelineLength) - return; - - General.Update(deltaTime); - Transform.Update(deltaTime); - LayerBrush.BaseProperties?.Update(deltaTime); - LayerBrush.Update(deltaTime); - - foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - { - baseLayerEffect.BaseProperties?.Update(deltaTime); - baseLayerEffect.Update(deltaTime); - } + // Update the layer timeline + UpdateTimeLines(deltaTime); } - protected internal override void UpdateTimelineLength() + protected internal override void UpdateTimeLineLength() { TimelineLength = StartSegmentLength + MainSegmentLength + EndSegmentLength; } - - public override void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment) - { - if (_disposed) - throw new ObjectDisposedException("Layer"); - - if (!Enabled || LayerBrush?.BaseProperties == null || !LayerBrush.BaseProperties.PropertiesInitialized) - return; - - // Disable data bindings during an override - bool wasApplyingDataBindings = ApplyDataBindingsEnabled; - ApplyDataBindingsEnabled = false; - - // If the condition is event-based, never display continuously - bool displayContinuously = (DisplayCondition == null || !DisplayCondition.ContainsEvents) && DisplayContinuously; - TimeSpan beginTime = TimelinePosition; - - if (stickToMainSegment) - { - if (!displayContinuously) - { - TimelinePosition = StartSegmentLength + timeOverride; - } - else - { - double progress = timeOverride.TotalMilliseconds % MainSegmentLength.TotalMilliseconds; - if (progress > 0) - TimelinePosition = TimeSpan.FromMilliseconds(progress) + StartSegmentLength; - else - TimelinePosition = StartSegmentLength; - } - } - else - { - TimelinePosition = timeOverride; - } - - double delta = (TimelinePosition - beginTime).TotalSeconds; - - General.Update(delta); - Transform.Update(delta); - LayerBrush.BaseProperties?.Update(delta); - LayerBrush.Update(delta); - - foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - { - baseLayerEffect.BaseProperties?.Update(delta); - baseLayerEffect.Update(delta); - } - - // Restore the old data bindings enabled state - ApplyDataBindingsEnabled = wasApplyingDataBindings; - } - + /// - public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) + public override void Render(SKCanvas canvas, SKImageInfo canvasInfo) { if (_disposed) throw new ObjectDisposedException("Layer"); - if (!Enabled || TimelinePosition > TimelineLength) + if (!Enabled) return; // Ensure the layer is ready @@ -358,14 +305,44 @@ namespace Artemis.Core if (LayerBrush?.BaseProperties?.PropertiesInitialized == false || LayerBrush?.BrushType != LayerBrushType.Regular) return; + RenderLayer(TimeLine, canvas); + foreach (TimeSpan extraTimeLine in ExtraTimeLines) + RenderLayer(extraTimeLine, canvas); + } + + private void PrepareForRender(TimeSpan timeLine) + { + double renderDelta = (timeLine - _lastRenderTime).TotalSeconds; + + General.Update(renderDelta); + Transform.Update(renderDelta); + LayerBrush.BaseProperties?.Update(renderDelta); + LayerBrush.Update(renderDelta); + + foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) + { + baseLayerEffect.BaseProperties?.Update(renderDelta); + baseLayerEffect.Update(renderDelta); + } + + _lastRenderTime = timeLine; + } + + private void RenderLayer(TimeSpan timeLine, SKCanvas canvas) + { + if (timeLine > TimelineLength || timeLine == TimeSpan.Zero && !DisplayConditionMet) + return; + + PrepareForRender(timeLine); + if (_layerBitmap == null) { - _layerBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height)); + _layerBitmap = new SKBitmap(new SKImageInfo((int)Path.Bounds.Width, (int)Path.Bounds.Height)); } - else if (_layerBitmap.Info.Width != (int) Path.Bounds.Width || _layerBitmap.Info.Height != (int) Path.Bounds.Height) + else if (_layerBitmap.Info.Width != (int)Path.Bounds.Width || _layerBitmap.Info.Height != (int)Path.Bounds.Height) { _layerBitmap.Dispose(); - _layerBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height)); + _layerBitmap = new SKBitmap(new SKImageInfo((int)Path.Bounds.Width, (int)Path.Bounds.Height)); } using SKPath layerPath = new SKPath(Path); @@ -373,7 +350,7 @@ namespace Artemis.Core using SKPaint layerPaint = new SKPaint { FilterQuality = SKFilterQuality.Low, - Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f)) + Color = new SKColor(0, 0, 0, (byte)(Transform.Opacity.CurrentValue * 2.55f)) }; layerCanvas.Clear(); @@ -393,7 +370,7 @@ namespace Artemis.Core else if (General.ResizeMode.CurrentValue == LayerResizeMode.Clip) ClipRender(layerCanvas, _layerBitmap.Info, layerPaint, layerPath); - using SKPaint canvasPaint = new SKPaint {BlendMode = General.BlendMode.CurrentValue}; + using SKPaint canvasPaint = new SKPaint { BlendMode = General.BlendMode.CurrentValue }; foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) baseLayerEffect.PostProcess(layerCanvas, _layerBitmap.Info, layerPath, canvasPaint); @@ -406,7 +383,7 @@ namespace Artemis.Core (canvasPath.Bounds.Left - targetLocation.X) * -1, (canvasPath.Bounds.Top - targetLocation.Y) * -1) ); - canvas.ClipPath(canvasPath); + // canvas.ClipPath(canvasPath); canvas.DrawBitmap(_layerBitmap, targetLocation, canvasPaint); } diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs index aaa36796b..73e0d4a69 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs @@ -36,5 +36,10 @@ namespace Artemis.Core /// Returns a list off all data binding registrations /// List GetAllDataBindingRegistrations(); + + /// + /// Resets the internal state of the property + /// + void Reset(); } } \ 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 910ca1324..c86ed4277 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -19,6 +19,7 @@ namespace Artemis.Core public class LayerProperty : ILayerProperty { private bool _disposed; + private TimeSpan _keyframeProgress; /// /// Creates a new instance of the class @@ -43,9 +44,10 @@ namespace Artemis.Core throw new ObjectDisposedException("LayerProperty"); CurrentValue = BaseValue; + _keyframeProgress = _keyframeProgress.Add(TimeSpan.FromSeconds(deltaTime)); if (ProfileElement.ApplyKeyframesEnabled) - UpdateKeyframes(); + UpdateKeyframes(deltaTime); if (ProfileElement.ApplyDataBindingsEnabled) UpdateDataBindings(deltaTime); @@ -294,13 +296,13 @@ namespace Artemis.Core _keyframes = _keyframes.OrderBy(k => k.Position).ToList(); } - private void UpdateKeyframes() + private void UpdateKeyframes(double deltaTime) { if (!KeyframesSupported || !KeyframesEnabled) return; - + // The current keyframe is the last keyframe before the current time - CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= ProfileElement.TimelinePosition); + CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= _keyframeProgress); // Keyframes are sorted by position so we can safely assume the next keyframe's position is after the current int nextIndex = _keyframes.IndexOf(CurrentKeyframe) + 1; NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[nextIndex] : null; @@ -314,7 +316,7 @@ namespace Artemis.Core else { TimeSpan timeDiff = NextKeyframe.Position - CurrentKeyframe.Position; - float keyframeProgress = (float) ((ProfileElement.TimelinePosition - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds); + float keyframeProgress = (float) ((_keyframeProgress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds); float keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction); UpdateCurrentValue(keyframeProgress, keyframeProgressEased); } @@ -362,6 +364,12 @@ namespace Artemis.Core return _dataBindingRegistrations; } + /// + public void Reset() + { + _keyframeProgress = TimeSpan.Zero; + } + public void RegisterDataBindingProperty(Expression> propertyExpression, DataBindingConverter converter) { if (_disposed) diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index c127199ef..5de2a87f1 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -300,5 +300,16 @@ namespace Artemis.Core } #endregion + + /// + /// Resets the internal state of the property group + /// + public void Reset() + { + foreach (ILayerProperty layerProperty in LayerProperties) + layerProperty.Reset(); + foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups) + layerPropertyGroup.Reset(); + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index cc224c313..d02ad8689 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -66,7 +66,7 @@ namespace Artemis.Core } } - public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) + public override void Render(SKCanvas canvas, SKImageInfo canvasInfo) { lock (this) { @@ -76,10 +76,17 @@ namespace Artemis.Core throw new ArtemisCoreException($"Cannot render inactive profile: {this}"); foreach (ProfileElement profileElement in Children) - profileElement.Render(deltaTime, canvas, canvasInfo); + profileElement.Render(canvas, canvasInfo); } } + /// + public override void Reset() + { + foreach (ProfileElement child in Children) + child.Reset(); + } + public Folder GetRootFolder() { if (_disposed) diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index 836d8c2e3..b7bd5f56d 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -91,7 +91,12 @@ namespace Artemis.Core /// /// Renders the element /// - public abstract void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo); + public abstract void Render(SKCanvas canvas, SKImageInfo canvasInfo); + + /// + /// Resets the internal state of the element + /// + public abstract void Reset(); /// public override string ToString() @@ -110,7 +115,7 @@ namespace Artemis.Core { if (_disposed) throw new ObjectDisposedException(GetType().Name); - + lock (ChildrenList) { if (ChildrenList.Contains(child)) diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index c94b48ca4..99fefe907 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -17,6 +17,7 @@ namespace Artemis.Core { ApplyDataBindingsEnabled = true; ApplyKeyframesEnabled = true; + ExtraTimeLines = new List(); LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded; LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved; @@ -49,8 +50,9 @@ namespace Artemis.Core StartSegmentLength = RenderElementEntity.StartSegmentLength; MainSegmentLength = RenderElementEntity.MainSegmentLength; EndSegmentLength = RenderElementEntity.EndSegmentLength; - DisplayContinuously = RenderElementEntity.DisplayContinuously; - AlwaysFinishTimeline = RenderElementEntity.AlwaysFinishTimeline; + PlayMode = (TimelinePlayMode) RenderElementEntity.PlayMode; + StopMode = (TimelineStopMode) RenderElementEntity.StopMode; + EventOverlapMode = (TimeLineEventOverlapMode) RenderElementEntity.EventOverlapMode; DisplayCondition = RenderElementEntity.DisplayCondition != null ? new DataModelConditionGroup(null, RenderElementEntity.DisplayCondition) @@ -64,8 +66,9 @@ namespace Artemis.Core RenderElementEntity.StartSegmentLength = StartSegmentLength; RenderElementEntity.MainSegmentLength = MainSegmentLength; RenderElementEntity.EndSegmentLength = EndSegmentLength; - RenderElementEntity.DisplayContinuously = DisplayContinuously; - RenderElementEntity.AlwaysFinishTimeline = AlwaysFinishTimeline; + RenderElementEntity.PlayMode = (int) PlayMode; + RenderElementEntity.StopMode = (int) StopMode; + RenderElementEntity.EventOverlapMode = (int) EventOverlapMode; RenderElementEntity.LayerEffects.Clear(); foreach (BaseLayerEffect layerEffect in LayerEffects) @@ -146,8 +149,10 @@ namespace Artemis.Core private TimeSpan _startSegmentLength; private TimeSpan _mainSegmentLength; private TimeSpan _endSegmentLength; - private bool _displayContinuously; - private bool _alwaysFinishTimeline; + private TimeSpan _timeLine; + private TimelinePlayMode _playMode; + private TimelineStopMode _stopMode; + private TimeLineEventOverlapMode _eventOverlapMode; /// /// Gets or sets the length of the start segment @@ -158,9 +163,9 @@ namespace Artemis.Core set { if (!SetAndNotify(ref _startSegmentLength, value)) return; - UpdateTimelineLength(); + UpdateTimeLineLength(); if (Parent is RenderProfileElement renderElement) - renderElement.UpdateTimelineLength(); + renderElement.UpdateTimeLineLength(); } } @@ -173,9 +178,9 @@ namespace Artemis.Core set { if (!SetAndNotify(ref _mainSegmentLength, value)) return; - UpdateTimelineLength(); + UpdateTimeLineLength(); if (Parent is RenderProfileElement renderElement) - renderElement.UpdateTimelineLength(); + renderElement.UpdateTimeLineLength(); } } @@ -188,37 +193,53 @@ namespace Artemis.Core set { if (!SetAndNotify(ref _endSegmentLength, value)) return; - UpdateTimelineLength(); + UpdateTimeLineLength(); if (Parent is RenderProfileElement renderElement) - renderElement.UpdateTimelineLength(); + renderElement.UpdateTimeLineLength(); } } /// - /// Gets the current timeline position + /// Gets the current time line position /// - public TimeSpan TimelinePosition + public TimeSpan TimeLine { - get => _timelinePosition; - protected set => SetAndNotify(ref _timelinePosition, value); + get => _timeLine; + protected set => SetAndNotify(ref _timeLine, value); } /// - /// Gets or sets whether main timeline should repeat itself as long as display conditions are met + /// Gets a list of extra time lines to render the element at each frame. Extra time lines are removed when they reach + /// their end /// - public bool DisplayContinuously + public List ExtraTimeLines { get; } + + /// + /// Gets or sets the mode in which the render element starts its timeline when display conditions are met + /// + public TimelinePlayMode PlayMode { - get => _displayContinuously; - set => SetAndNotify(ref _displayContinuously, value); + get => _playMode; + set => SetAndNotify(ref _playMode, value); } /// - /// Gets or sets whether the timeline should finish when conditions are no longer met + /// Gets or sets the mode in which the render element stops its timeline when display conditions are no longer met /// - public bool AlwaysFinishTimeline + public TimelineStopMode StopMode { - get => _alwaysFinishTimeline; - set => SetAndNotify(ref _alwaysFinishTimeline, value); + get => _stopMode; + set => SetAndNotify(ref _stopMode, value); + } + + /// + /// Gets or sets the mode in which the render element responds to display condition events being fired before the + /// timeline finished + /// + public TimeLineEventOverlapMode EventOverlapMode + { + get => _eventOverlapMode; + set => SetAndNotify(ref _eventOverlapMode, value); } /// @@ -226,48 +247,62 @@ namespace Artemis.Core /// public TimeSpan TimelineLength { get; protected set; } - protected double UpdateTimeline(double deltaTime) + /// + /// Updates the time line and any extra time lines present in + /// + /// The delta with which to move the time lines + protected void UpdateTimeLines(double deltaTime) { - bool displayContinuously = DisplayContinuously; - bool alwaysFinishTimeline = AlwaysFinishTimeline; + bool repeatMainSegment = PlayMode == TimelinePlayMode.Repeat; + bool finishMainSegment = StopMode == TimelineStopMode.Finish; // If the condition is event-based, never display continuously and always finish the timeline if (DisplayCondition != null && DisplayCondition.ContainsEvents) { - displayContinuously = false; - alwaysFinishTimeline = true; + repeatMainSegment = false; + finishMainSegment = true; } - - TimeSpan oldPosition = _timelinePosition; + TimeSpan deltaTimeSpan = TimeSpan.FromSeconds(deltaTime); TimeSpan mainSegmentEnd = StartSegmentLength + MainSegmentLength; - TimelinePosition += deltaTimeSpan; - // Manage segments while the condition is met - if (DisplayConditionMet) + // Update the main time line + if (TimeLine != TimeSpan.Zero || DisplayConditionMet) { - // If we are at the end of the main timeline, wrap around back to the beginning - if (displayContinuously && TimelinePosition >= mainSegmentEnd) - TimelinePosition = StartSegmentLength; - } - else - { - // Skip to the last segment if conditions are no longer met - if (!alwaysFinishTimeline && TimelinePosition < mainSegmentEnd) - TimelinePosition = mainSegmentEnd; + TimeLine += deltaTimeSpan; + + // Apply play and stop mode + if (DisplayConditionMet && repeatMainSegment && TimeLine >= mainSegmentEnd) + TimeLine = StartSegmentLength; + else if (!DisplayConditionMet && !finishMainSegment) + TimeLine = mainSegmentEnd.Add(new TimeSpan(1)); } - return (TimelinePosition - oldPosition).TotalSeconds; + lock (ExtraTimeLines) + { + // Remove extra time lines that have finished + ExtraTimeLines.RemoveAll(t => t >= mainSegmentEnd); + // Update remaining extra time lines + for (int index = 0; index < ExtraTimeLines.Count; index++) + ExtraTimeLines[index] += deltaTimeSpan; + } } - protected internal abstract void UpdateTimelineLength(); - /// - /// Overrides the progress of the element + /// Overrides the time line to the specified time and clears any extra time lines /// - /// - /// - public abstract void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment); + /// The time to override to + /// Whether to stick to the main segment, wrapping around if needed + public void OverrideTimeLines(TimeSpan time, bool stickToMainSegment) + { + ExtraTimeLines.Clear(); + TimeLine = time; + + if (stickToMainSegment && TimeLine > StartSegmentLength) + TimeLine = StartSegmentLength + TimeSpan.FromMilliseconds(time.TotalMilliseconds % MainSegmentLength.TotalMilliseconds); + } + + protected internal abstract void UpdateTimeLineLength(); #endregion @@ -402,11 +437,10 @@ namespace Artemis.Core public bool DisplayConditionMet { get => _displayConditionMet; - private set => SetAndNotify(ref _displayConditionMet, value); + protected set => SetAndNotify(ref _displayConditionMet, value); } private DataModelConditionGroup _displayCondition; - private TimeSpan _timelinePosition; private bool _displayConditionMet; /// @@ -430,9 +464,33 @@ namespace Artemis.Core public void UpdateDisplayCondition() { - bool conditionMet = DisplayCondition == null || DisplayCondition.Evaluate(); - if (conditionMet && !DisplayConditionMet) - TimelinePosition = TimeSpan.Zero; + if (DisplayCondition == null) + { + DisplayConditionMet = true; + return; + } + + bool conditionMet = DisplayCondition.Evaluate(); + + // Regular conditions reset the timeline whenever their condition is met and was not met before that + if (!DisplayCondition.ContainsEvents) + { + if (conditionMet && !DisplayConditionMet && TimeLine > TimelineLength) + TimeLine = TimeSpan.Zero; + } + // Event conditions reset if the timeline finished and otherwise apply their overlap mode + else if (conditionMet) + { + if (TimeLine > TimelineLength) + TimeLine = TimeSpan.Zero; + else + { + if (EventOverlapMode == TimeLineEventOverlapMode.Restart) + TimeLine = TimeSpan.Zero; + else if (EventOverlapMode == TimeLineEventOverlapMode.Copy) + ExtraTimeLines.Add(new TimeSpan()); + } + } DisplayConditionMet = conditionMet; } @@ -450,4 +508,57 @@ namespace Artemis.Core #endregion } + + /// + /// 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 + } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs index 8f1930609..d72015308 100644 --- a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs @@ -81,8 +81,9 @@ namespace Artemis.Core.Modules internal override void InternalDisablePlugin() { - DataModel = null; + Deactivate(true); base.InternalDisablePlugin(); + DataModel = null; } } @@ -112,7 +113,7 @@ namespace Artemis.Core.Modules /// /// Gets the currently active profile /// - public Profile ActiveProfile { get; private set; } + public Profile? ActiveProfile { get; private set; } /// /// Disables updating the profile, rendering does continue @@ -174,7 +175,7 @@ namespace Artemis.Core.Modules lock (this) { // Render the profile - ActiveProfile?.Render(deltaTime, canvas, canvasInfo); + ActiveProfile?.Render(canvas, canvasInfo); } ProfileRendered(deltaTime, surface, canvas, canvasInfo); diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs index ac2bd2d04..d7a1d9787 100644 --- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs +++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs @@ -49,6 +49,11 @@ namespace Artemis.Core.Services /// void ActivateLastProfile(ProfileModule profileModule); + /// + /// Reloads the currently active profile on the provided profile module + /// + void ReloadProfile(ProfileModule module); + /// /// Asynchronously activates the last profile of the given profile module using a fade animation /// diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 32512f627..880ff1ab1 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -82,6 +82,19 @@ namespace Artemis.Core.Services return profile; } + public void ReloadProfile(ProfileModule module) + { + if (module.ActiveProfile == null) + return; + + ProfileEntity entity = _profileRepository.Get(module.ActiveProfile.EntityId); + Profile profile = new Profile(module, entity); + InstantiateProfile(profile); + + module.ChangeActiveProfile(null, _surfaceService.ActiveSurface); + module.ChangeActiveProfile(profile, _surfaceService.ActiveSurface); + } + public async Task ActivateProfileAnimated(ProfileDescriptor profileDescriptor) { if (profileDescriptor.ProfileModule.ActiveProfile?.EntityId == profileDescriptor.Id) diff --git a/src/Artemis.Core/Utilities/IntroAnimation.cs b/src/Artemis.Core/Utilities/IntroAnimation.cs index 63008a8dc..3e4b90eb8 100644 --- a/src/Artemis.Core/Utilities/IntroAnimation.cs +++ b/src/Artemis.Core/Utilities/IntroAnimation.cs @@ -32,7 +32,7 @@ namespace Artemis.Core return; AnimationProfile.Update(deltaTime); - AnimationProfile.Render(deltaTime, canvas, bitmapInfo); + AnimationProfile.Render(canvas, bitmapInfo); } private void CreateIntroProfile() diff --git a/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs b/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs index 7e7556e36..e538642e3 100644 --- a/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs @@ -9,8 +9,10 @@ namespace Artemis.Storage.Entities.Profile.Abstract public TimeSpan StartSegmentLength { get; set; } public TimeSpan MainSegmentLength { get; set; } public TimeSpan EndSegmentLength { get; set; } - public bool DisplayContinuously { get; set; } - public bool AlwaysFinishTimeline { get; set; } + + public int PlayMode { get; set; } + public int StopMode { get; set; } + public int EventOverlapMode { get; set; } public List LayerEffects { get; set; } public List PropertyEntities { get; set; } diff --git a/src/Artemis.Storage/Migrations/M4ProfileSegments.cs b/src/Artemis.Storage/Migrations/M4ProfileSegments.cs index d5c85542e..e9a92bdfb 100644 --- a/src/Artemis.Storage/Migrations/M4ProfileSegments.cs +++ b/src/Artemis.Storage/Migrations/M4ProfileSegments.cs @@ -23,7 +23,7 @@ namespace Artemis.Storage.Migrations if (folder.MainSegmentLength == TimeSpan.Zero) folder.MainSegmentLength = TimeSpan.FromSeconds(5); - folder.DisplayContinuously = true; + folder.PlayMode = 0; } foreach (LayerEntity layer in profileEntity.Layers.Where(l => l.MainSegmentLength == TimeSpan.Zero)) @@ -33,7 +33,7 @@ namespace Artemis.Storage.Migrations if (layer.MainSegmentLength == TimeSpan.Zero) layer.MainSegmentLength = TimeSpan.FromSeconds(5); - layer.DisplayContinuously = true; + layer.PlayMode = 0; } repository.Update(profileEntity); diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index 8afc4f4c0..1cce0362c 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -15,23 +15,37 @@ namespace Artemis.UI.Shared.Services internal class ProfileEditorService : IProfileEditorService { private readonly ILogger _logger; + private readonly ICoreService _coreService; private readonly IProfileService _profileService; private readonly List _registeredPropertyEditors; private readonly object _selectedProfileElementLock = new object(); private readonly object _selectedProfileLock = new object(); private TimeSpan _currentTime; private int _pixelsPerSecond; + private bool _previewInvalidated; - public ProfileEditorService(IProfileService profileService, IKernel kernel, ILogger logger) + public ProfileEditorService(IProfileService profileService, IKernel kernel, ILogger logger, ICoreService coreService) { _profileService = profileService; _logger = logger; + _coreService = coreService; _registeredPropertyEditors = new List(); + _coreService.FrameRendered += CoreServiceOnFrameRendered; + Kernel = kernel; PixelsPerSecond = 100; } + private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e) + { + if (_previewInvalidated) + { + _previewInvalidated = false; + Execute.PostToUIThread(OnProfilePreviewUpdated); + } + } + public IKernel Kernel { get; } public ReadOnlyCollection RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly(); public Profile SelectedProfile { get; private set; } @@ -142,11 +156,11 @@ namespace Artemis.UI.Shared.Services // Stick to the main segment for any element that is not currently selected foreach (Folder folder in SelectedProfile.GetAllFolders()) - folder.OverrideProgress(CurrentTime, folder != SelectedProfileElement); + folder.OverrideTimeLines(CurrentTime, folder != SelectedProfileElement); foreach (Layer layer in SelectedProfile.GetAllLayers()) - layer.OverrideProgress(CurrentTime, layer != SelectedProfileElement); + layer.OverrideTimeLines(CurrentTime, layer != SelectedProfileElement); - OnProfilePreviewUpdated(); + _previewInvalidated = true; } public bool UndoUpdateProfile() diff --git a/src/Artemis.UI/Converters/ComparisonConverter.cs b/src/Artemis.UI/Converters/ComparisonConverter.cs new file mode 100644 index 000000000..6d3a1f196 --- /dev/null +++ b/src/Artemis.UI/Converters/ComparisonConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Artemis.UI.Converters +{ + public class ComparisonConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value?.Equals(parameter); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value?.Equals(true) == true ? parameter : Binding.DoNothing; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index 4ba2ff183..38d8331c0 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -7,12 +7,14 @@ xmlns:s="https://github.com/canton7/Stylet" xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:displayConditions="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayConditions" + xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" x:Class="Artemis.UI.Screens.ProfileEditor.DisplayConditions.DisplayConditionsView" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance {x:Type displayConditions:DisplayConditionsViewModel}}"> + @@ -198,7 +200,7 @@ + IsChecked="{Binding Path=RenderProfileElement.EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Restart}}"> RESTART @@ -213,7 +215,7 @@ + IsChecked="{Binding Path=RenderProfileElement.EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Ignore}}"> IGNORE @@ -228,7 +230,7 @@ + IsChecked="{Binding Path=RenderProfileElement.EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Copy}}"> COPY diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index e1afe10d5..9f2dd89a6 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -43,22 +43,24 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions public bool DisplayContinuously { - get => RenderProfileElement?.DisplayContinuously ?? false; + get => RenderProfileElement?.PlayMode == TimelinePlayMode.Repeat; set { - if (RenderProfileElement == null || RenderProfileElement.DisplayContinuously == value) return; - RenderProfileElement.DisplayContinuously = value; + TimelinePlayMode playMode = value ? TimelinePlayMode.Repeat : TimelinePlayMode.Once; + if (RenderProfileElement == null || RenderProfileElement?.PlayMode == playMode) return; + RenderProfileElement.PlayMode = playMode; _profileEditorService.UpdateSelectedProfileElement(); } } public bool AlwaysFinishTimeline { - get => RenderProfileElement?.AlwaysFinishTimeline ?? false; + get => RenderProfileElement?.StopMode == TimelineStopMode.Finish; set { - if (RenderProfileElement == null || RenderProfileElement.AlwaysFinishTimeline == value) return; - RenderProfileElement.AlwaysFinishTimeline = value; + TimelineStopMode stopMode = value ? TimelineStopMode.Finish : TimelineStopMode.SkipToEnd; + if (RenderProfileElement == null || RenderProfileElement?.StopMode == stopMode) return; + RenderProfileElement.StopMode = stopMode; _profileEditorService.UpdateSelectedProfileElement(); } } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs index 578bbf7a2..4c9b4ab4d 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs @@ -104,15 +104,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline { if (Segment != SegmentViewModelType.Main) return false; - - return SelectedProfileElement?.DisplayContinuously ?? false; + return SelectedProfileElement?.PlayMode == TimelinePlayMode.Repeat; } set { if (Segment != SegmentViewModelType.Main) return; - - SelectedProfileElement.DisplayContinuously = value; + SelectedProfileElement.PlayMode = value ? TimelinePlayMode.Repeat : TimelinePlayMode.Once; ProfileEditorService.UpdateSelectedProfileElement(); NotifyOfPropertyChange(nameof(RepeatSegment)); } @@ -348,7 +346,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline e.PropertyName == nameof(RenderProfileElement.MainSegmentLength) || e.PropertyName == nameof(RenderProfileElement.EndSegmentLength)) Update(); - else if (e.PropertyName == nameof(RenderProfileElement.DisplayContinuously)) + else if (e.PropertyName == nameof(RenderProfileElement.PlayMode)) NotifyOfPropertyChange(nameof(RepeatSegment)); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs index f90b48300..36fb2e3ad 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs @@ -246,6 +246,7 @@ namespace Artemis.UI.Screens.ProfileEditor { LoadWorkspaceSettings(); Module.IsProfileUpdatingDisabled = true; + Module.ActiveProfile?.Reset(); Module.ActiveProfileChanged += ModuleOnActiveProfileChanged; LoadProfiles(); @@ -261,6 +262,7 @@ namespace Artemis.UI.Screens.ProfileEditor { SaveWorkspaceSettings(); Module.IsProfileUpdatingDisabled = false; + Module.ActiveProfile?.Reset(); Module.ActiveProfileChanged -= ModuleOnActiveProfileChanged; _profileEditorService.ChangeSelectedProfile(null); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLayerViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLayerViewModel.cs index ddebeb6b9..977051f10 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLayerViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLayerViewModel.cs @@ -125,25 +125,22 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization return; } - Execute.PostToUIThread(() => + Rect bounds = _layerEditorService.GetLayerBounds(Layer); + Geometry shapeGeometry = Geometry.Empty; + switch (Layer.LayerShape) { - Rect bounds = _layerEditorService.GetLayerBounds(Layer); - Geometry shapeGeometry = Geometry.Empty; - switch (Layer.LayerShape) - { - case EllipseShape _: - shapeGeometry = new EllipseGeometry(bounds); - break; - case RectangleShape _: - shapeGeometry = new RectangleGeometry(bounds); - break; - } + case EllipseShape _: + shapeGeometry = new EllipseGeometry(bounds); + break; + case RectangleShape _: + shapeGeometry = new RectangleGeometry(bounds); + break; + } - if (Layer.LayerBrush == null || Layer.LayerBrush.SupportsTransformation) - shapeGeometry.Transform = _layerEditorService.GetLayerTransformGroup(Layer); + if (Layer.LayerBrush == null || Layer.LayerBrush.SupportsTransformation) + shapeGeometry.Transform = _layerEditorService.GetLayerTransformGroup(Layer); - ShapeGeometry = shapeGeometry; - }); + ShapeGeometry = shapeGeometry; } private void CreateViewportRectangle()