1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2026-01-02 10:43:31 +00:00

Profiles - Moved timeline logic to separate class (WIP)

This commit is contained in:
SpoinkyNL 2020-10-29 00:15:48 +01:00
parent df2f364cbe
commit 4ede3876d4
9 changed files with 445 additions and 302 deletions

View File

@ -74,7 +74,7 @@ namespace Artemis.Core
public override void Update(double deltaTime) public override void Update(double deltaTime)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Folder"); throw new ObjectDisposedException("Folder");
if (!Enabled) if (!Enabled)
@ -104,7 +104,7 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public override void AddChild(ProfileElement child, int? order = null) public override void AddChild(ProfileElement child, int? order = null)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Folder"); throw new ObjectDisposedException("Folder");
base.AddChild(child, order); base.AddChild(child, order);
@ -115,7 +115,7 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public override void RemoveChild(ProfileElement child) public override void RemoveChild(ProfileElement child)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Folder"); throw new ObjectDisposedException("Folder");
base.RemoveChild(child); base.RemoveChild(child);
@ -130,7 +130,7 @@ namespace Artemis.Core
public void CalculateRenderProperties() public void CalculateRenderProperties()
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Folder"); throw new ObjectDisposedException("Folder");
SKPath path = new SKPath {FillType = SKPathFillType.Winding}; SKPath path = new SKPath {FillType = SKPathFillType.Winding};
@ -159,7 +159,7 @@ namespace Artemis.Core
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
_disposed = true; Disposed = true;
foreach (ProfileElement profileElement in Children) foreach (ProfileElement profileElement in Children)
profileElement.Dispose(); profileElement.Dispose();
@ -189,7 +189,7 @@ namespace Artemis.Core
internal override void Save() internal override void Save()
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Folder"); throw new ObjectDisposedException("Folder");
FolderEntity.Id = EntityId; FolderEntity.Id = EntityId;
@ -212,7 +212,7 @@ namespace Artemis.Core
public override void Render(SKCanvas canvas, SKImageInfo canvasInfo) public override void Render(SKCanvas canvas, SKImageInfo canvasInfo)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Folder"); throw new ObjectDisposedException("Folder");
if (!Enabled || !Children.Any(c => c.Enabled)) if (!Enabled || !Children.Any(c => c.Enabled))

View File

@ -138,7 +138,7 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
_disposed = true; Disposed = true;
// Brush first in case it depends on any of the other disposables during it's own disposal // Brush first in case it depends on any of the other disposables during it's own disposal
_layerBrush?.Dispose(); _layerBrush?.Dispose();
@ -199,7 +199,7 @@ namespace Artemis.Core
internal override void Save() internal override void Save()
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
// Properties // Properties
@ -264,7 +264,7 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public override void Update(double deltaTime) public override void Update(double deltaTime)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
if (!Enabled) if (!Enabled)
@ -285,7 +285,7 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public override void Render(SKCanvas canvas, SKImageInfo canvasInfo) public override void Render(SKCanvas canvas, SKImageInfo canvasInfo)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
if (!Enabled) if (!Enabled)
@ -446,7 +446,7 @@ namespace Artemis.Core
internal void CalculateRenderProperties() internal void CalculateRenderProperties()
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
if (!Leds.Any()) if (!Leds.Any())
@ -475,7 +475,7 @@ namespace Artemis.Core
internal SKPoint GetLayerAnchorPosition(SKPath layerPath, bool zeroBased = false) internal SKPoint GetLayerAnchorPosition(SKPath layerPath, bool zeroBased = false)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
SKPoint positionProperty = Transform.Position.CurrentValue; SKPoint positionProperty = Transform.Position.CurrentValue;
@ -499,7 +499,7 @@ namespace Artemis.Core
/// <param name="path"></param> /// <param name="path"></param>
public void IncludePathInTranslation(SKPath path, bool zeroBased) public void IncludePathInTranslation(SKPath path, bool zeroBased)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
SKSize sizeProperty = Transform.Scale.CurrentValue; SKSize sizeProperty = Transform.Scale.CurrentValue;
@ -535,7 +535,7 @@ namespace Artemis.Core
/// <returns>The transformation matrix containing the current transformation settings</returns> /// <returns>The transformation matrix containing the current transformation settings</returns>
public SKMatrix GetTransformMatrix(bool zeroBased) public SKMatrix GetTransformMatrix(bool zeroBased)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
SKSize sizeProperty = Transform.Scale.CurrentValue; SKSize sizeProperty = Transform.Scale.CurrentValue;
@ -569,7 +569,7 @@ namespace Artemis.Core
/// </summary> /// </summary>
public void ExcludePathFromTranslation(SKPath path, bool zeroBased) public void ExcludePathFromTranslation(SKPath path, bool zeroBased)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
SKSize sizeProperty = Transform.Scale.CurrentValue; SKSize sizeProperty = Transform.Scale.CurrentValue;
@ -605,7 +605,7 @@ namespace Artemis.Core
/// <returns>The number of transformations applied</returns> /// <returns>The number of transformations applied</returns>
public int ExcludeCanvasFromTranslation(SKCanvas canvas, bool zeroBased) public int ExcludeCanvasFromTranslation(SKCanvas canvas, bool zeroBased)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
SKSize sizeProperty = Transform.Scale.CurrentValue; SKSize sizeProperty = Transform.Scale.CurrentValue;
@ -645,7 +645,7 @@ namespace Artemis.Core
/// <param name="led">The LED to add</param> /// <param name="led">The LED to add</param>
public void AddLed(ArtemisLed led) public void AddLed(ArtemisLed led)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
_leds.Add(led); _leds.Add(led);
@ -658,7 +658,7 @@ namespace Artemis.Core
/// <param name="leds">The LEDs to add</param> /// <param name="leds">The LEDs to add</param>
public void AddLeds(IEnumerable<ArtemisLed> leds) public void AddLeds(IEnumerable<ArtemisLed> leds)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
_leds.AddRange(leds); _leds.AddRange(leds);
@ -671,7 +671,7 @@ namespace Artemis.Core
/// <param name="led">The LED to remove</param> /// <param name="led">The LED to remove</param>
public void RemoveLed(ArtemisLed led) public void RemoveLed(ArtemisLed led)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
_leds.Remove(led); _leds.Remove(led);
@ -683,7 +683,7 @@ namespace Artemis.Core
/// </summary> /// </summary>
public void ClearLeds() public void ClearLeds()
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
_leds.Clear(); _leds.Clear();
@ -692,7 +692,7 @@ namespace Artemis.Core
internal void PopulateLeds(ArtemisSurface surface) internal void PopulateLeds(ArtemisSurface surface)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
List<ArtemisLed> leds = new List<ArtemisLed>(); List<ArtemisLed> leds = new List<ArtemisLed>();

View File

@ -56,7 +56,7 @@ namespace Artemis.Core
{ {
lock (this) lock (this)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Profile"); throw new ObjectDisposedException("Profile");
if (!IsActivated) if (!IsActivated)
throw new ArtemisCoreException($"Cannot update inactive profile: {this}"); throw new ArtemisCoreException($"Cannot update inactive profile: {this}");
@ -70,7 +70,7 @@ namespace Artemis.Core
{ {
lock (this) lock (this)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Profile"); throw new ObjectDisposedException("Profile");
if (!IsActivated) if (!IsActivated)
throw new ArtemisCoreException($"Cannot render inactive profile: {this}"); throw new ArtemisCoreException($"Cannot render inactive profile: {this}");
@ -89,7 +89,7 @@ namespace Artemis.Core
public Folder GetRootFolder() public Folder GetRootFolder()
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Profile"); throw new ObjectDisposedException("Profile");
return (Folder) Children.Single(); return (Folder) Children.Single();
@ -97,7 +97,7 @@ namespace Artemis.Core
internal override void Load() internal override void Load()
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Profile"); throw new ObjectDisposedException("Profile");
Name = ProfileEntity.Name; Name = ProfileEntity.Name;
@ -137,12 +137,12 @@ namespace Artemis.Core
ChildrenList.Clear(); ChildrenList.Clear();
IsActivated = false; IsActivated = false;
_disposed = true; Disposed = true;
} }
internal override void Save() internal override void Save()
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Profile"); throw new ObjectDisposedException("Profile");
ProfileEntity.Id = EntityId; ProfileEntity.Id = EntityId;
@ -164,7 +164,7 @@ namespace Artemis.Core
{ {
lock (this) lock (this)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Profile"); throw new ObjectDisposedException("Profile");
if (IsActivated) if (IsActivated)
return; return;
@ -177,7 +177,7 @@ namespace Artemis.Core
internal void PopulateLeds(ArtemisSurface surface) internal void PopulateLeds(ArtemisSurface surface)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException("Profile"); throw new ObjectDisposedException("Profile");
foreach (Layer layer in GetAllLayers()) foreach (Layer layer in GetAllLayers())

View File

@ -9,13 +9,13 @@ namespace Artemis.Core
{ {
public abstract class ProfileElement : PropertyChangedBase, IDisposable public abstract class ProfileElement : PropertyChangedBase, IDisposable
{ {
protected bool _disposed;
private bool _enabled; private bool _enabled;
private Guid _entityId; private Guid _entityId;
private string _name; private string _name;
private int _order; private int _order;
private ProfileElement _parent; private ProfileElement _parent;
private Profile _profile; private Profile _profile;
protected bool Disposed;
protected List<ProfileElement> ChildrenList; protected List<ProfileElement> ChildrenList;
protected ProfileElement() protected ProfileElement()
@ -113,7 +113,7 @@ namespace Artemis.Core
/// <param name="order">The order where to place the child (1-based), defaults to the end of the collection</param> /// <param name="order">The order where to place the child (1-based), defaults to the end of the collection</param>
public virtual void AddChild(ProfileElement child, int? order = null) public virtual void AddChild(ProfileElement child, int? order = null)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException(GetType().Name); throw new ObjectDisposedException(GetType().Name);
lock (ChildrenList) lock (ChildrenList)
@ -157,7 +157,7 @@ namespace Artemis.Core
/// <param name="child">The profile element to remove</param> /// <param name="child">The profile element to remove</param>
public virtual void RemoveChild(ProfileElement child) public virtual void RemoveChild(ProfileElement child)
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException(GetType().Name); throw new ObjectDisposedException(GetType().Name);
lock (ChildrenList) lock (ChildrenList)
@ -180,7 +180,7 @@ namespace Artemis.Core
/// <returns></returns> /// <returns></returns>
public List<Folder> GetAllFolders() public List<Folder> GetAllFolders()
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException(GetType().Name); throw new ObjectDisposedException(GetType().Name);
List<Folder> folders = new List<Folder>(); List<Folder> folders = new List<Folder>();
@ -201,7 +201,7 @@ namespace Artemis.Core
/// <returns></returns> /// <returns></returns>
public List<Layer> GetAllLayers() public List<Layer> GetAllLayers()
{ {
if (_disposed) if (Disposed)
throw new ObjectDisposedException(GetType().Name); throw new ObjectDisposedException(GetType().Name);
List<Layer> layers = new List<Layer>(); List<Layer> layers = new List<Layer>();

View File

@ -17,7 +17,6 @@ namespace Artemis.Core
{ {
ApplyDataBindingsEnabled = true; ApplyDataBindingsEnabled = true;
ApplyKeyframesEnabled = true; ApplyKeyframesEnabled = true;
ExtraTimeLines = new List<TimeSpan>();
LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded; LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved; LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
@ -25,51 +24,21 @@ namespace Artemis.Core
public abstract List<ILayerProperty> GetAllLayerProperties(); public abstract List<ILayerProperty> GetAllLayerProperties();
#region IDisposable
protected override void Dispose(bool disposing)
{
LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.Dispose();
base.Dispose(disposing);
}
#endregion
internal void ApplyRenderElementDefaults()
{
MainSegmentLength = TimeSpan.FromSeconds(5);
}
internal void LoadRenderElement() internal void LoadRenderElement()
{ {
StartSegmentLength = RenderElementEntity.StartSegmentLength;
MainSegmentLength = RenderElementEntity.MainSegmentLength;
EndSegmentLength = RenderElementEntity.EndSegmentLength;
PlayMode = (TimelinePlayMode) RenderElementEntity.PlayMode;
StopMode = (TimelineStopMode) RenderElementEntity.StopMode;
EventOverlapMode = (TimeLineEventOverlapMode) RenderElementEntity.EventOverlapMode;
DisplayCondition = RenderElementEntity.DisplayCondition != null DisplayCondition = RenderElementEntity.DisplayCondition != null
? new DataModelConditionGroup(null, RenderElementEntity.DisplayCondition) ? new DataModelConditionGroup(null, RenderElementEntity.DisplayCondition)
: new DataModelConditionGroup(null); : new DataModelConditionGroup(null);
Timeline = RenderElementEntity.Timeline != null
? new Timeline(RenderElementEntity.Timeline)
: new Timeline();
ActivateEffects(); ActivateEffects();
} }
internal void SaveRenderElement() internal void SaveRenderElement()
{ {
RenderElementEntity.StartSegmentLength = StartSegmentLength;
RenderElementEntity.MainSegmentLength = MainSegmentLength;
RenderElementEntity.EndSegmentLength = EndSegmentLength;
RenderElementEntity.PlayMode = (int) PlayMode;
RenderElementEntity.StopMode = (int) StopMode;
RenderElementEntity.EventOverlapMode = (int) EventOverlapMode;
RenderElementEntity.LayerEffects.Clear(); RenderElementEntity.LayerEffects.Clear();
foreach (BaseLayerEffect layerEffect in LayerEffects) foreach (BaseLayerEffect layerEffect in LayerEffects)
{ {
@ -90,8 +59,36 @@ namespace Artemis.Core
// Conditions // Conditions
RenderElementEntity.DisplayCondition = DisplayCondition?.Entity; RenderElementEntity.DisplayCondition = DisplayCondition?.Entity;
DisplayCondition?.Save(); DisplayCondition?.Save();
// Timeline
RenderElementEntity.Timeline = Timeline?.Entity;
Timeline?.Save();
} }
#region Timeline
/// <summary>
/// Gets the timeline associated with this render element
/// </summary>
public Timeline? Timeline { get; private set; }
#endregion
#region IDisposable
protected override void Dispose(bool disposing)
{
LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.Dispose();
base.Dispose(disposing);
}
#endregion
#region Properties #region Properties
private SKPath _path; private SKPath _path;
@ -144,168 +141,6 @@ namespace Artemis.Core
#endregion #endregion
#region Timeline
private TimeSpan _startSegmentLength;
private TimeSpan _mainSegmentLength;
private TimeSpan _endSegmentLength;
private TimeSpan _timeLine;
private TimelinePlayMode _playMode;
private TimelineStopMode _stopMode;
private TimeLineEventOverlapMode _eventOverlapMode;
/// <summary>
/// Gets or sets the length of the start segment
/// </summary>
public TimeSpan StartSegmentLength
{
get => _startSegmentLength;
set
{
if (!SetAndNotify(ref _startSegmentLength, value)) return;
UpdateTimeLineLength();
if (Parent is RenderProfileElement renderElement)
renderElement.UpdateTimeLineLength();
}
}
/// <summary>
/// Gets or sets the length of the main segment
/// </summary>
public TimeSpan MainSegmentLength
{
get => _mainSegmentLength;
set
{
if (!SetAndNotify(ref _mainSegmentLength, value)) return;
UpdateTimeLineLength();
if (Parent is RenderProfileElement renderElement)
renderElement.UpdateTimeLineLength();
}
}
/// <summary>
/// Gets or sets the length of the end segment
/// </summary>
public TimeSpan EndSegmentLength
{
get => _endSegmentLength;
set
{
if (!SetAndNotify(ref _endSegmentLength, value)) return;
UpdateTimeLineLength();
if (Parent is RenderProfileElement renderElement)
renderElement.UpdateTimeLineLength();
}
}
/// <summary>
/// Gets the current time line position
/// </summary>
public TimeSpan TimeLine
{
get => _timeLine;
protected set => SetAndNotify(ref _timeLine, value);
}
/// <summary>
/// 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 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 => _playMode;
set => SetAndNotify(ref _playMode, value);
}
/// <summary>
/// Gets or sets the mode in which the render element stops its timeline when display conditions are no longer met
/// </summary>
public TimelineStopMode StopMode
{
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>
/// Gets the max length of this element and any of its children
/// </summary>
public TimeSpan TimelineLength { get; protected set; }
/// <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 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)
{
repeatMainSegment = false;
finishMainSegment = true;
}
TimeSpan deltaTimeSpan = TimeSpan.FromSeconds(deltaTime);
TimeSpan mainSegmentEnd = StartSegmentLength + MainSegmentLength;
// Update the main time line
if (TimeLine != TimeSpan.Zero || DisplayConditionMet)
{
TimeLine += deltaTimeSpan;
// Apply play and stop mode
if (DisplayConditionMet && repeatMainSegment && TimeLine >= mainSegmentEnd)
TimeLine = StartSegmentLength;
else if (!DisplayConditionMet && !finishMainSegment)
TimeLine = mainSegmentEnd.Add(new TimeSpan(1));
}
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;
}
}
/// <summary>
/// Overrides the time line to the specified time and clears any extra time lines
/// </summary>
/// <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
#region Effect management #region Effect management
protected List<BaseLayerEffect> _layerEffects; protected List<BaseLayerEffect> _layerEffects;
@ -475,20 +310,22 @@ namespace Artemis.Core
// Regular conditions reset the timeline whenever their condition is met and was not met before that // Regular conditions reset the timeline whenever their condition is met and was not met before that
if (!DisplayCondition.ContainsEvents) if (!DisplayCondition.ContainsEvents)
{ {
if (conditionMet && !DisplayConditionMet && TimeLine > TimelineLength) if (conditionMet && !DisplayConditionMet && Timeline.IsFinished)
TimeLine = TimeSpan.Zero; Timeline.JumpToStart();
} }
// Event conditions reset if the timeline finished and otherwise apply their overlap mode // Event conditions reset if the timeline finished and otherwise apply their overlap mode
else if (conditionMet) else if (conditionMet)
{ {
if (TimeLine > TimelineLength) if (Timeline.IsFinished)
TimeLine = TimeSpan.Zero; {
Timeline.JumpToStart();
}
else else
{ {
if (EventOverlapMode == TimeLineEventOverlapMode.Restart) if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Restart)
TimeLine = TimeSpan.Zero; Timeline.JumpToStart();
else if (EventOverlapMode == TimeLineEventOverlapMode.Copy) else if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Copy)
ExtraTimeLines.Add(new TimeSpan()); Timeline.AddExtraTimeline();
} }
} }
@ -508,57 +345,4 @@ namespace Artemis.Core
#endregion #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
}
} }

View File

@ -0,0 +1,350 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Artemis.Storage.Entities.Profile;
using Stylet;
namespace Artemis.Core
{
/// <summary>
/// Represents a timeline used by profile elements
/// </summary>
public class Timeline : PropertyChangedBase, IStorageModel
{
/// <summary>
/// Creates a new instance of the <see cref="Timeline" /> class
/// </summary>
public Timeline()
{
Entity = new TimelineEntity();
_extraTimelines = new List<Timeline>();
MainSegmentLength = TimeSpan.FromSeconds(5);
Save();
}
internal Timeline(TimelineEntity entity)
{
Entity = entity;
_extraTimelines = new List<Timeline>();
Load();
}
private Timeline(Timeline parent)
{
Parent = parent;
}
#region Extra timelines
/// <summary>
/// Adds an extra timeline to this timeline
/// </summary>
public void AddExtraTimeline()
{
_extraTimelines.Add(new Timeline(this));
}
/// <summary>
/// Removes all extra timelines from this timeline
/// </summary>
public void ClearExtraTimelines()
{
_extraTimelines.Clear();
}
#endregion
#region Properties
private TimeSpan _position;
private TimeSpan _lastDelta;
private TimeLineEventOverlapMode _eventOverlapMode;
private TimelinePlayMode _playMode;
private TimelineStopMode _stopMode;
private readonly List<Timeline> _extraTimelines;
/// <summary>
/// Gets the parent this timeline is an extra timeline of
/// </summary>
public Timeline Parent { get; }
/// <summary>
/// Gets the current position of the timeline
/// </summary>
public TimeSpan Position
{
get => _position;
private set => SetAndNotify(ref _position, value);
}
/// <summary>
/// Gets the delta that was applied during the last call to <see cref="Update" />
/// </summary>
public TimeSpan LastDelta
{
get => _lastDelta;
private set => SetAndNotify(ref _lastDelta, value);
}
/// <summary>
/// Gets or sets the mode in which the render element starts its timeline when display conditions are met
/// </summary>
public TimelinePlayMode PlayMode
{
get => _playMode;
set => SetAndNotify(ref _playMode, value);
}
/// <summary>
/// Gets or sets the mode in which the render element stops its timeline when display conditions are no longer met
/// </summary>
public TimelineStopMode StopMode
{
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>
/// Gets a list of extra copies of the timeline applied to this timeline
/// </summary>
public ReadOnlyCollection<Timeline> ExtraTimelines => _extraTimelines.AsReadOnly();
/// <summary>
/// Gets a boolean indicating whether the timeline has finished its run
/// </summary>
public bool IsFinished => Position > Length;
#region Segments
/// <summary>
/// Gets the total length of this timeline
/// </summary>
public TimeSpan Length => StartSegmentLength + MainSegmentLength + EndSegmentLength;
/// <summary>
/// Gets or sets the length of the start segment
/// </summary>
public TimeSpan StartSegmentLength { get; set; }
/// <summary>
/// Gets or sets the length of the main segment
/// </summary>
public TimeSpan MainSegmentLength { get; set; }
/// <summary>
/// Gets or sets the length of the end segment
/// </summary>
public TimeSpan EndSegmentLength { get; set; }
/// <summary>
/// Gets or sets the start position of the main segment
/// </summary>
public TimeSpan MainSegmentStartPosition
{
get => StartSegmentEndPosition;
set => StartSegmentEndPosition = value;
}
/// <summary>
/// Gets or sets the end position of the end segment
/// </summary>
public TimeSpan EndSegmentStartPosition
{
get => MainSegmentEndPosition;
set => MainSegmentEndPosition = value;
}
/// <summary>
/// Gets or sets the end position of the start segment
/// </summary>
public TimeSpan StartSegmentEndPosition
{
get => StartSegmentLength;
set => StartSegmentLength = value;
}
/// <summary>
/// Gets or sets the end position of the main segment
/// </summary>
public TimeSpan MainSegmentEndPosition
{
get => StartSegmentEndPosition + MainSegmentLength;
set => MainSegmentLength = value - StartSegmentEndPosition >= TimeSpan.Zero ? value - StartSegmentEndPosition : TimeSpan.Zero;
}
/// <summary>
/// Gets or sets the end position of the end segment
/// </summary>
public TimeSpan EndSegmentEndPosition
{
get => MainSegmentEndPosition + EndSegmentLength;
set => EndSegmentLength = value - MainSegmentEndPosition >= TimeSpan.Zero ? value - MainSegmentEndPosition : TimeSpan.Zero;
}
internal TimelineEntity Entity { get; set; }
#endregion
#endregion
#region Updating
/// <summary>
/// Updates the timeline, applying the provided <paramref name="delta" /> to the <see cref="Position" />
/// </summary>
/// <param name="delta">The amount of time to apply to the position</param>
/// <param name="stickToMainSegment">Whether to stick to the main segment, wrapping around if needed</param>
public void Update(TimeSpan delta, bool stickToMainSegment)
{
LastDelta = delta;
Position += delta;
if (stickToMainSegment && Position >= MainSegmentStartPosition)
Position = MainSegmentStartPosition + TimeSpan.FromMilliseconds(Position.TotalMilliseconds % MainSegmentLength.TotalMilliseconds);
}
/// <summary>
/// Moves the position of the timeline backwards to the very start of the timeline
/// </summary>
public void JumpToStart()
{
if (Position == TimeSpan.Zero)
return;
LastDelta = TimeSpan.Zero - Position;
Position = TimeSpan.Zero;
}
/// <summary>
/// Moves the position of the timeline forwards to the beginning of the end segment
/// </summary>
public void JumpToEndSegment()
{
if (Position >= EndSegmentStartPosition)
return;
LastDelta = EndSegmentStartPosition - Position;
Position = EndSegmentStartPosition;
}
/// <summary>
/// Moves the position of the timeline forwards to the very end of the timeline
/// </summary>
public void JumpToEnd()
{
if (Position >= EndSegmentEndPosition)
return;
LastDelta = EndSegmentEndPosition - Position;
Position = EndSegmentEndPosition;
}
/// <summary>
/// Overrides the <see cref="Position" /> to the specified time and clears any extra time lines
/// </summary>
/// <param name="position">The position to set the timeline to</param>
/// <param name="stickToMainSegment">Whether to stick to the main segment, wrapping around if needed</param>
public void Override(TimeSpan position, bool stickToMainSegment)
{
_extraTimelines.Clear();
LastDelta = position - Position;
Position = position;
if (stickToMainSegment && Position >= MainSegmentStartPosition)
Position = MainSegmentStartPosition + TimeSpan.FromMilliseconds(Position.TotalMilliseconds % MainSegmentLength.TotalMilliseconds);
}
#endregion
#region Storage
/// <inheritdoc />
public void Load()
{
StartSegmentLength = Entity.StartSegmentLength;
MainSegmentLength = Entity.MainSegmentLength;
EndSegmentLength = Entity.EndSegmentLength;
PlayMode = (TimelinePlayMode) Entity.PlayMode;
StopMode = (TimelineStopMode) Entity.StopMode;
EventOverlapMode = (TimeLineEventOverlapMode) Entity.EventOverlapMode;
}
/// <inheritdoc />
public void Save()
{
Entity.StartSegmentLength = StartSegmentLength;
Entity.MainSegmentLength = MainSegmentLength;
Entity.EndSegmentLength = EndSegmentLength;
Entity.PlayMode = (int) PlayMode;
Entity.StopMode = (int) StopMode;
Entity.EventOverlapMode = (int) EventOverlapMode;
}
#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
}
}

View File

@ -6,18 +6,11 @@ namespace Artemis.Storage.Entities.Profile.Abstract
{ {
public abstract class RenderElementEntity public abstract class RenderElementEntity
{ {
public TimeSpan StartSegmentLength { get; set; }
public TimeSpan MainSegmentLength { get; set; }
public TimeSpan EndSegmentLength { get; set; }
public int PlayMode { get; set; }
public int StopMode { get; set; }
public int EventOverlapMode { get; set; }
public List<LayerEffectEntity> LayerEffects { get; set; } public List<LayerEffectEntity> LayerEffects { get; set; }
public List<PropertyEntity> PropertyEntities { get; set; } public List<PropertyEntity> PropertyEntities { get; set; }
public List<string> ExpandedPropertyGroups { get; set; } public List<string> ExpandedPropertyGroups { get; set; }
public DataModelConditionGroupEntity DisplayCondition { get; set; } public DataModelConditionGroupEntity DisplayCondition { get; set; }
public TimelineEntity Timeline { get; set; }
} }
} }

View File

@ -0,0 +1,15 @@
using System;
namespace Artemis.Storage.Entities.Profile
{
public class TimelineEntity
{
public TimeSpan StartSegmentLength { get; set; }
public TimeSpan MainSegmentLength { get; set; }
public TimeSpan EndSegmentLength { get; set; }
public int PlayMode { get; set; }
public int StopMode { get; set; }
public int EventOverlapMode { get; set; }
}
}

View File

@ -221,4 +221,5 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=luma/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/UserDictionary/Words/=luma/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Timelines/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>