Layers - Added support for rendering multiple timelines at once

Events - Added trigger modes
This commit is contained in:
Robert committed 2020-10-27 20:20:40 +01:00
1 parent c6a06f0131
commit 86d6e540d7
21 files changed
+495 -333

No files matched your search

+127 -144
View File
@@ -30,7 +30,6 @@ namespace Artemis.Core
Profile = Parent.Profile;
Name = name;
Enabled = true;
DisplayContinuously = true;
_layerEffects = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>();
@@ -56,24 +55,23 @@ namespace Artemis.Core
Load();
}
public bool IsRootFolder => Parent == Profile;
internal FolderEntity FolderEntity { get; set; }
internal override RenderElementEntity RenderElementEntity => FolderEntity;
/// <inheritdoc />
public override List<ILayerProperty> GetAllLayerProperties()
{
List<ILayerProperty> result = new List<ILayerProperty>();
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()
/// <inheritdoc />
public override void Reset()
{
TimelineLength = !Children.Any() ? TimeSpan.Zero : Children.OfType<RenderProfileElement>().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();
}
/// <inheritdoc />
@@ -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<RenderProfileElement>().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;
+60 -83
View File
@@ -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();
}
/// <inheritdoc />
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;
/// <inheritdoc />
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;
}
/// <inheritdoc />
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);
}
@@ -36,5 +36,10 @@ namespace Artemis.Core
/// Returns a list off all data binding registrations
/// </summary>
List<IDataBindingRegistration> GetAllDataBindingRegistrations();
/// <summary>
/// Resets the internal state of the property
/// </summary>
void Reset();
}
}
@@ -19,6 +19,7 @@ namespace Artemis.Core
public class LayerProperty<T> : ILayerProperty
{
private bool _disposed;
private TimeSpan _keyframeProgress;
/// <summary>
/// Creates a new instance of the <see cref="LayerProperty{T}" /> 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;
}
/// <inheritdoc />
public void Reset()
{
_keyframeProgress = TimeSpan.Zero;
}
public void RegisterDataBindingProperty<TProperty>(Expression<Func<T, TProperty>> propertyExpression, DataBindingConverter<T, TProperty> converter)
{
if (_disposed)
@@ -300,5 +300,16 @@ namespace Artemis.Core
}
#endregion
/// <summary>
/// Resets the internal state of the property group
/// </summary>
public void Reset()
{
foreach (ILayerProperty layerProperty in LayerProperties)
layerProperty.Reset();
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
layerPropertyGroup.Reset();
}
}
}
+9 -2
View File
@@ -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);
}
}
/// <inheritdoc />
public override void Reset()
{
foreach (ProfileElement child in Children)
child.Reset();
}
public Folder GetRootFolder()
{
if (_disposed)
@@ -91,7 +91,12 @@ namespace Artemis.Core
/// <summary>
/// Renders the element
/// </summary>
public abstract void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo);
public abstract void Render(SKCanvas canvas, SKImageInfo canvasInfo);
/// <summary>
/// Resets the internal state of the element
/// </summary>
public abstract void Reset();
/// <inheritdoc />
public override string ToString()
@@ -110,7 +115,7 @@ namespace Artemis.Core
{
if (_disposed)
throw new ObjectDisposedException(GetType().Name);
lock (ChildrenList)
{
if (ChildrenList.Contains(child))
@@ -17,6 +17,7 @@ namespace Artemis.Core
{
ApplyDataBindingsEnabled = true;
ApplyKeyframesEnabled = true;
ExtraTimeLines = new List<TimeSpan>();
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;
/// <summary>
/// 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();
}
}
/// <summary>
/// Gets the current timeline position
/// Gets the current time line position
/// </summary>
public TimeSpan TimelinePosition
public TimeSpan TimeLine
{
get => _timelinePosition;
protected set => SetAndNotify(ref _timelinePosition, value);
get => _timeLine;
protected set => SetAndNotify(ref _timeLine, value);
}
/// <summary>
/// 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
/// </summary>
public bool DisplayContinuously
public List<TimeSpan> ExtraTimeLines { get; }
/// <summary>
/// Gets or sets the mode in which the render element starts its timeline when display conditions are met
/// </summary>
public TimelinePlayMode PlayMode
{
get => _displayContinuously;
set => SetAndNotify(ref _displayContinuously, value);
get => _playMode;
set => SetAndNotify(ref _playMode, value);
}
/// <summary>
/// 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
/// </summary>
public bool AlwaysFinishTimeline
public TimelineStopMode StopMode
{
get => _alwaysFinishTimeline;
set => SetAndNotify(ref _alwaysFinishTimeline, value);
get => _stopMode;
set => SetAndNotify(ref _stopMode, value);
}
/// <summary>
/// Gets or sets the mode in which the render element responds to display condition events being fired before the
/// timeline finished
/// </summary>
public TimeLineEventOverlapMode EventOverlapMode
{
get => _eventOverlapMode;
set => SetAndNotify(ref _eventOverlapMode, value);
}
/// <summary>
@@ -226,48 +247,62 @@ namespace Artemis.Core
/// </summary>
public TimeSpan TimelineLength { get; protected set; }
protected double UpdateTimeline(double deltaTime)
/// <summary>
/// Updates the time line and any extra time lines present in <see cref="ExtraTimeLines" />
/// </summary>
/// <param name="deltaTime">The delta with which to move the time lines</param>
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();
/// <summary>
/// Overrides the progress of the element
/// Overrides the time line to the specified time and clears any extra time lines
/// </summary>
/// <param name="timeOverride"></param>
/// <param name="stickToMainSegment"></param>
public abstract void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment);
/// <param name="time">The time to override to</param>
/// <param name="stickToMainSegment">Whether to stick to the main segment, wrapping around if needed</param>
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;
/// <summary>
@@ -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
}
/// <summary>
/// Represents a mode for render elements to start their timeline when display conditions are met
/// </summary>
public enum TimelinePlayMode
{
/// <summary>
/// Continue repeating the main segment of the timeline while the condition is met
/// </summary>
Repeat,
/// <summary>
/// Only play the timeline once when the condition is met
/// </summary>
Once
}
/// <summary>
/// Represents a mode for render elements to stop their timeline when display conditions are no longer met
/// </summary>
public enum TimelineStopMode
{
/// <summary>
/// When conditions are no longer met, finish the the current run of the main timeline
/// </summary>
Finish,
/// <summary>
/// When conditions are no longer met, skip to the end segment of the timeline
/// </summary>
SkipToEnd
}
/// <summary>
/// Represents a mode for render elements to start their timeline when display conditions events are fired
/// </summary>
public enum TimeLineEventOverlapMode
{
/// <summary>
/// Stop the current run and restart the timeline
/// </summary>
Restart,
/// <summary>
/// Ignore subsequent event fires until the timeline finishes
/// </summary>
Ignore,
/// <summary>
/// Play another copy of the timeline on top of the current run
/// </summary>
Copy
}
}