1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Profiles - Finished moving and improving timeline logic

This commit is contained in:
Robert 2020-10-29 19:49:24 +01:00
parent 4ede3876d4
commit 458fd2a704
20 changed files with 354 additions and 248 deletions

View File

@ -8,7 +8,7 @@
/// <summary> /// <summary>
/// Performs an update on the model /// Performs an update on the model
/// </summary> /// </summary>
/// <param name="deltaTime">The delta time in seconds</param> /// <param name="timeline">The timeline to apply during update</param>
void Update(double deltaTime); void Update(Timeline timeline);
} }
} }

View File

@ -137,16 +137,18 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Updates the smoothing progress of the data binding /// Updates the smoothing progress of the data binding
/// </summary> /// </summary>
/// <param name="deltaTime">The time in seconds that passed since the last update</param> /// <param name="timeline">The timeline to apply during update</param>
public void Update(double deltaTime) public void Update(Timeline timeline)
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DataBinding"); throw new ObjectDisposedException("DataBinding");
// Data bindings cannot go back in time like brushes // Data bindings cannot go back in time like brushes
deltaTime = Math.Max(0, deltaTime); TimeSpan deltaTime = timeline.LastDelta;
if (deltaTime < TimeSpan.Zero)
deltaTime = TimeSpan.Zero;
_easingProgress = _easingProgress.Add(TimeSpan.FromSeconds(deltaTime)); _easingProgress = _easingProgress.Add(deltaTime);
if (_easingProgress > EasingTime) if (_easingProgress > EasingTime)
_easingProgress = EasingTime; _easingProgress = EasingTime;
} }

View File

@ -34,14 +34,12 @@ namespace Artemis.Core
_layerEffects = new List<BaseLayerEffect>(); _layerEffects = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>(); _expandedPropertyGroups = new List<string>();
ApplyRenderElementDefaults();
Parent.AddChild(this); Parent.AddChild(this);
} }
internal Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity) internal Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity)
{ {
FolderEntity = folderEntity; FolderEntity = folderEntity;
EntityId = folderEntity.Id; EntityId = folderEntity.Id;
Profile = profile; Profile = profile;
@ -52,11 +50,21 @@ namespace Artemis.Core
_layerEffects = new List<BaseLayerEffect>(); _layerEffects = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>(); _expandedPropertyGroups = new List<string>();
Load(); Load();
UpdateChildrenTimelineLength();
} }
/// <summary>
/// Gets a boolean indicating whether this folder is at the root of the profile tree
/// </summary>
public bool IsRootFolder => Parent == Profile; public bool IsRootFolder => Parent == Profile;
/// <summary>
/// Gets the longest timeline of all this folders children
/// </summary>
public Timeline LongestChildTimeline { get; private set; }
internal FolderEntity FolderEntity { get; set; } internal FolderEntity FolderEntity { get; set; }
internal override RenderElementEntity RenderElementEntity => FolderEntity; internal override RenderElementEntity RenderElementEntity => FolderEntity;
@ -80,11 +88,8 @@ namespace Artemis.Core
if (!Enabled) if (!Enabled)
return; return;
// Ensure the layer must still be displayed
UpdateDisplayCondition(); UpdateDisplayCondition();
UpdateTimeline(deltaTime);
// Update the layer timeline
UpdateTimeLines(deltaTime);
foreach (ProfileElement child in Children) foreach (ProfileElement child in Children)
child.Update(deltaTime); child.Update(deltaTime);
@ -94,8 +99,7 @@ namespace Artemis.Core
public override void Reset() public override void Reset()
{ {
DisplayConditionMet = false; DisplayConditionMet = false;
TimeLine = TimelineLength; Timeline.JumpToStart();
ExtraTimeLines.Clear();
foreach (ProfileElement child in Children) foreach (ProfileElement child in Children)
child.Reset(); child.Reset();
@ -108,7 +112,7 @@ namespace Artemis.Core
throw new ObjectDisposedException("Folder"); throw new ObjectDisposedException("Folder");
base.AddChild(child, order); base.AddChild(child, order);
UpdateTimeLineLength(); UpdateChildrenTimelineLength();
CalculateRenderProperties(); CalculateRenderProperties();
} }
@ -119,7 +123,7 @@ namespace Artemis.Core
throw new ObjectDisposedException("Folder"); throw new ObjectDisposedException("Folder");
base.RemoveChild(child); base.RemoveChild(child);
UpdateTimeLineLength(); UpdateChildrenTimelineLength();
CalculateRenderProperties(); CalculateRenderProperties();
} }
@ -147,14 +151,21 @@ namespace Artemis.Core
OnRenderPropertiesUpdated(); OnRenderPropertiesUpdated();
} }
protected internal override void UpdateTimeLineLength() private void UpdateChildrenTimelineLength()
{ {
TimelineLength = !Children.Any() ? TimeSpan.Zero : Children.OfType<RenderProfileElement>().Max(c => c.TimelineLength); Timeline longest = new Timeline() {MainSegmentLength = TimeSpan.Zero};
if (StartSegmentLength + MainSegmentLength + EndSegmentLength > TimelineLength) foreach (ProfileElement profileElement in Children)
TimelineLength = StartSegmentLength + MainSegmentLength + EndSegmentLength; {
if (profileElement is Folder folder && folder.LongestChildTimeline.Length > longest.Length)
longest = folder.LongestChildTimeline;
else if (profileElement is Layer layer &&
layer.Timeline.PlayMode == TimelinePlayMode.Once &&
layer.Timeline.StopMode == TimelineStopMode.Finish &&
layer.Timeline.Length > longest.Length)
longest = layer.Timeline;
}
if (Parent is RenderProfileElement parent) LongestChildTimeline = longest;
parent.UpdateTimeLineLength();
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@ -208,8 +219,6 @@ namespace Artemis.Core
#region Rendering #region Rendering
private TimeSpan _lastRenderTime;
public override void Render(SKCanvas canvas, SKImageInfo canvasInfo) public override void Render(SKCanvas canvas, SKImageInfo canvasInfo)
{ {
if (Disposed) if (Disposed)
@ -222,28 +231,24 @@ namespace Artemis.Core
if (Path == null) if (Path == null)
return; return;
RenderFolder(TimeLine, canvas, canvasInfo); RenderFolder(Timeline, canvas, canvasInfo);
} }
private void PrepareForRender(TimeSpan timeLine) private void PrepareForRender(Timeline timeline)
{ {
double renderDelta = (timeLine - _lastRenderTime).TotalSeconds;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
{ {
baseLayerEffect.BaseProperties?.Update(timeLine, renderDelta); baseLayerEffect.BaseProperties?.Update(timeline);
baseLayerEffect.Update(renderDelta); baseLayerEffect.Update(timeline.LastDelta.TotalSeconds);
} }
_lastRenderTime = timeLine;
} }
private void RenderFolder(TimeSpan timeLine, SKCanvas canvas, SKImageInfo canvasInfo) private void RenderFolder(Timeline timeline, SKCanvas canvas, SKImageInfo canvasInfo)
{ {
if (timeLine > TimelineLength) if (Timeline.IsFinished && LongestChildTimeline.IsFinished)
return; return;
PrepareForRender(timeLine); PrepareForRender(timeline);
if (_folderBitmap == null) if (_folderBitmap == null)
{ {

View File

@ -45,14 +45,14 @@ namespace Artemis.Core
_expandedPropertyGroups = new List<string>(); _expandedPropertyGroups = new List<string>();
Initialize(); Initialize();
ApplyRenderElementDefaults();
Parent.AddChild(this); Parent.AddChild(this);
} }
internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity) internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity)
{ {
LayerEntity = layerEntity; LayerEntity = layerEntity;
EntityId = layerEntity.Id;
Profile = profile; Profile = profile;
Parent = parent; Parent = parent;
General = new LayerGeneralProperties(); General = new LayerGeneralProperties();
@ -176,14 +176,6 @@ namespace Artemis.Core
ActivateLayerBrush(); ActivateLayerBrush();
} }
/// <inheritdoc />
public override void Reset()
{
DisplayConditionMet = false;
TimeLine = TimelineLength;
ExtraTimeLines.Clear();
}
#region Storage #region Storage
internal override void Load() internal override void Load()
@ -258,9 +250,7 @@ namespace Artemis.Core
#endregion #endregion
#region Rendering #region Rendering
private TimeSpan _lastRenderTime;
/// <inheritdoc /> /// <inheritdoc />
public override void Update(double deltaTime) public override void Update(double deltaTime)
{ {
@ -270,16 +260,15 @@ namespace Artemis.Core
if (!Enabled) if (!Enabled)
return; return;
// Ensure the layer must still be displayed
UpdateDisplayCondition(); UpdateDisplayCondition();
UpdateTimeline(deltaTime);
// Update the layer timeline
UpdateTimeLines(deltaTime);
} }
protected internal override void UpdateTimeLineLength() /// <inheritdoc />
public override void Reset()
{ {
TimelineLength = StartSegmentLength + MainSegmentLength + EndSegmentLength; DisplayConditionMet = false;
Timeline.JumpToStart();
} }
/// <inheritdoc /> /// <inheritdoc />
@ -298,35 +287,31 @@ namespace Artemis.Core
if (LayerBrush?.BaseProperties?.PropertiesInitialized == false || LayerBrush?.BrushType != LayerBrushType.Regular) if (LayerBrush?.BaseProperties?.PropertiesInitialized == false || LayerBrush?.BrushType != LayerBrushType.Regular)
return; return;
RenderLayer(TimeLine, canvas); RenderLayer(Timeline, canvas);
foreach (TimeSpan extraTimeLine in ExtraTimeLines) foreach (Timeline extraTimeline in Timeline.ExtraTimelines)
RenderLayer(extraTimeLine, canvas); RenderLayer(extraTimeline, canvas);
} }
private void PrepareForRender(TimeSpan renderTime) private void PrepareForRender(Timeline timeline)
{ {
double renderDelta = (renderTime - _lastRenderTime).TotalSeconds; General.Update(timeline);
Transform.Update(timeline);
General.Update(renderTime, renderDelta); LayerBrush.BaseProperties?.Update(timeline);
Transform.Update(renderTime, renderDelta); LayerBrush.Update(timeline.LastDelta.TotalSeconds);
LayerBrush.BaseProperties?.Update(renderTime, renderDelta);
LayerBrush.Update(renderDelta);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
{ {
baseLayerEffect.BaseProperties?.Update(renderTime, renderDelta); baseLayerEffect.BaseProperties?.Update(timeline);
baseLayerEffect.Update(renderDelta); baseLayerEffect.Update(timeline.LastDelta.TotalSeconds);
} }
_lastRenderTime = renderTime;
} }
private void RenderLayer(TimeSpan timeLine, SKCanvas canvas) private void RenderLayer(Timeline timeline, SKCanvas canvas)
{ {
if (timeLine > TimelineLength) if (timeline.IsFinished)
return; return;
PrepareForRender(timeLine); PrepareForRender(timeline);
if (_layerBitmap == null) if (_layerBitmap == null)
{ {

View File

@ -40,8 +40,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Updates the layer properties internal state /// Updates the layer properties internal state
/// </summary> /// </summary>
/// <param name="renderTime">The current position in the timeline</param> /// <param name="timeline">The timeline to apply to the property</param>
/// <param name="deltaTime">The position difference since last update</param> void Update(Timeline timeline);
void Update(TimeSpan renderTime, double deltaTime);
} }
} }

View File

@ -34,20 +34,18 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public string Path { get; private set; } public string Path { get; private set; }
/// <summary> /// <inheritdoc />
/// Updates the property, applying keyframes and data bindings to the current value public void Update(Timeline timeline)
/// </summary>
public void Update(TimeSpan time, double deltaTime)
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("LayerProperty"); throw new ObjectDisposedException("LayerProperty");
CurrentValue = BaseValue; CurrentValue = BaseValue;
if (ProfileElement.ApplyKeyframesEnabled) if (ProfileElement.ApplyKeyframesEnabled)
UpdateKeyframes(time); UpdateKeyframes(timeline);
if (ProfileElement.ApplyDataBindingsEnabled) if (ProfileElement.ApplyDataBindingsEnabled)
UpdateDataBindings(deltaTime); UpdateDataBindings(timeline);
OnUpdated(); OnUpdated();
} }
@ -125,8 +123,7 @@ namespace Artemis.Core
return; return;
_baseValue = value; _baseValue = value;
Update(ProfileElement.TimeLine, 0); ReapplyUpdate();
OnCurrentValueSet();
} }
} }
@ -169,8 +166,7 @@ namespace Artemis.Core
// Force an update so that the base value is applied to the current value and // Force an update so that the base value is applied to the current value and
// keyframes/data bindings are applied using the new base value // keyframes/data bindings are applied using the new base value
Update(ProfileElement.TimeLine, 0); ReapplyUpdate();
OnCurrentValueSet();
} }
/// <summary> /// <summary>
@ -185,6 +181,13 @@ namespace Artemis.Core
CurrentValue = DefaultValue; CurrentValue = DefaultValue;
} }
private void ReapplyUpdate()
{
ProfileElement.Timeline.ClearDelta();
Update(ProfileElement.Timeline);
OnCurrentValueSet();
}
#endregion #endregion
#region Keyframes #region Keyframes
@ -294,13 +297,13 @@ namespace Artemis.Core
_keyframes = _keyframes.OrderBy(k => k.Position).ToList(); _keyframes = _keyframes.OrderBy(k => k.Position).ToList();
} }
private void UpdateKeyframes(TimeSpan time) private void UpdateKeyframes(Timeline timeline)
{ {
if (!KeyframesSupported || !KeyframesEnabled) if (!KeyframesSupported || !KeyframesEnabled)
return; return;
// The current keyframe is the last keyframe before the current time // The current keyframe is the last keyframe before the current time
CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= time); CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= timeline.Position);
// Keyframes are sorted by position so we can safely assume the next keyframe's position is after the current // 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; int nextIndex = _keyframes.IndexOf(CurrentKeyframe) + 1;
NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[nextIndex] : null; NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[nextIndex] : null;
@ -314,7 +317,7 @@ namespace Artemis.Core
else else
{ {
TimeSpan timeDiff = NextKeyframe.Position - CurrentKeyframe.Position; TimeSpan timeDiff = NextKeyframe.Position - CurrentKeyframe.Position;
float keyframeProgress = (float) ((time - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds); float keyframeProgress = (float) ((timeline.Position - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds);
float keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction); float keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction);
UpdateCurrentValue(keyframeProgress, keyframeProgressEased); UpdateCurrentValue(keyframeProgress, keyframeProgressEased);
} }
@ -361,7 +364,7 @@ namespace Artemis.Core
{ {
return _dataBindingRegistrations; return _dataBindingRegistrations;
} }
public void RegisterDataBindingProperty<TProperty>(Expression<Func<T, TProperty>> propertyExpression, DataBindingConverter<T, TProperty> converter) public void RegisterDataBindingProperty<TProperty>(Expression<Func<T, TProperty>> propertyExpression, DataBindingConverter<T, TProperty> converter)
{ {
if (_disposed) if (_disposed)
@ -416,11 +419,11 @@ namespace Artemis.Core
OnDataBindingDisabled(new LayerPropertyEventArgs<T>(dataBinding.LayerProperty)); OnDataBindingDisabled(new LayerPropertyEventArgs<T>(dataBinding.LayerProperty));
} }
private void UpdateDataBindings(double deltaTime) private void UpdateDataBindings(Timeline timeline)
{ {
foreach (IDataBinding dataBinding in _dataBindings) foreach (IDataBinding dataBinding in _dataBindings)
{ {
dataBinding.Update(deltaTime); dataBinding.Update(timeline);
dataBinding.Apply(); dataBinding.Apply();
} }
} }
@ -484,12 +487,10 @@ namespace Artemis.Core
_keyframes.Clear(); _keyframes.Clear();
try try
{ {
_keyframes.AddRange(Entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe<T>( _keyframes.AddRange(
JsonConvert.DeserializeObject<T>(k.Value), Entity.KeyframeEntities.Where(k => k.Position <= ProfileElement.Timeline.Length)
k.Position, .Select(k => new LayerPropertyKeyframe<T>(JsonConvert.DeserializeObject<T>(k.Value), k.Position, (Easings.Functions) k.EasingFunction, this))
(Easings.Functions) k.EasingFunction, );
this
)));
} }
catch (JsonException e) catch (JsonException e)
{ {

View File

@ -198,12 +198,12 @@ namespace Artemis.Core
layerPropertyGroup.ApplyToEntity(); layerPropertyGroup.ApplyToEntity();
} }
internal void Update(TimeSpan renderTime, double deltaTime) internal void Update(Timeline timeline)
{ {
foreach (ILayerProperty layerProperty in LayerProperties) foreach (ILayerProperty layerProperty in LayerProperties)
layerProperty.Update(renderTime, deltaTime); layerProperty.Update(timeline);
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups) foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
layerPropertyGroup.Update(renderTime, deltaTime); layerPropertyGroup.Update(timeline);
} }
private void InitializeProperty(PropertyInfo propertyInfo, PropertyDescriptionAttribute propertyDescription) private void InitializeProperty(PropertyInfo propertyInfo, PropertyDescriptionAttribute propertyDescription)

View File

@ -17,6 +17,7 @@ namespace Artemis.Core
{ {
ApplyDataBindingsEnabled = true; ApplyDataBindingsEnabled = true;
ApplyKeyframesEnabled = true; ApplyKeyframesEnabled = true;
Timeline = new Timeline();
LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded; LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved; LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
@ -30,8 +31,8 @@ namespace Artemis.Core
? new DataModelConditionGroup(null, RenderElementEntity.DisplayCondition) ? new DataModelConditionGroup(null, RenderElementEntity.DisplayCondition)
: new DataModelConditionGroup(null); : new DataModelConditionGroup(null);
Timeline = RenderElementEntity.Timeline != null Timeline = RenderElementEntity.Timeline != null
? new Timeline(RenderElementEntity.Timeline) ? new Timeline(RenderElementEntity.Timeline)
: new Timeline(); : new Timeline();
ActivateEffects(); ActivateEffects();
@ -70,7 +71,17 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the timeline associated with this render element /// Gets the timeline associated with this render element
/// </summary> /// </summary>
public Timeline? Timeline { get; private set; } public Timeline Timeline { get; private set; }
/// <summary>
/// Updates the <see cref="Timeline"/> according to the provided <paramref name="deltaTime"/> and current display condition status
/// </summary>
public void UpdateTimeline(double deltaTime)
{
// The play mode dictates whether to stick to the main segment unless the display conditions contains events
bool stickToMainSegment = Timeline.PlayMode == TimelinePlayMode.Repeat && (DisplayConditionMet || DisplayCondition != null && DisplayCondition.ContainsEvents);
Timeline.Update(TimeSpan.FromSeconds(deltaTime), stickToMainSegment);
}
#endregion #endregion
@ -297,6 +308,9 @@ namespace Artemis.Core
/// </summary> /// </summary>
public bool ApplyDataBindingsEnabled { get; set; } public bool ApplyDataBindingsEnabled { get; set; }
/// <summary>
/// Evaluates the display conditions on this element and applies any required changes to the <see cref="Timeline"/>
/// </summary>
public void UpdateDisplayCondition() public void UpdateDisplayCondition()
{ {
if (DisplayCondition == null) if (DisplayCondition == null)
@ -306,26 +320,30 @@ namespace Artemis.Core
} }
bool conditionMet = DisplayCondition.Evaluate(); bool conditionMet = DisplayCondition.Evaluate();
// Regular conditions reset the timeline whenever their condition is met and was not met before that
if (!DisplayCondition.ContainsEvents) if (!DisplayCondition.ContainsEvents)
{ {
// Regular conditions reset the timeline whenever their condition is met and was not met before that
if (conditionMet && !DisplayConditionMet && Timeline.IsFinished) if (conditionMet && !DisplayConditionMet && Timeline.IsFinished)
Timeline.JumpToStart(); Timeline.JumpToStart();
// If regular conditions are no longer met, jump to the end segment if stop mode requires it
if (!conditionMet && DisplayConditionMet && Timeline.StopMode == TimelineStopMode.SkipToEnd)
Timeline.JumpToEndSegment();
} }
// Event conditions reset if the timeline finished and otherwise apply their overlap mode
else if (conditionMet) else if (conditionMet)
{ {
// Event conditions reset if the timeline finished
if (Timeline.IsFinished) if (Timeline.IsFinished)
{
Timeline.JumpToStart(); Timeline.JumpToStart();
} // and otherwise apply their overlap mode
else else
{ {
if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Restart) if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Restart)
Timeline.JumpToStart(); Timeline.JumpToStart();
else if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Copy) else if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Copy)
Timeline.AddExtraTimeline(); Timeline.AddExtraTimeline();
// The third option is ignore which is handled below:
// done
} }
} }

View File

@ -64,11 +64,14 @@ namespace Artemis.Core
private TimelinePlayMode _playMode; private TimelinePlayMode _playMode;
private TimelineStopMode _stopMode; private TimelineStopMode _stopMode;
private readonly List<Timeline> _extraTimelines; private readonly List<Timeline> _extraTimelines;
private TimeSpan _startSegmentLength;
private TimeSpan _mainSegmentLength;
private TimeSpan _endSegmentLength;
/// <summary> /// <summary>
/// Gets the parent this timeline is an extra timeline of /// Gets the parent this timeline is an extra timeline of
/// </summary> /// </summary>
public Timeline Parent { get; } public Timeline? Parent { get; }
/// <summary> /// <summary>
/// Gets the current position of the timeline /// Gets the current position of the timeline
@ -81,13 +84,21 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the delta that was applied during the last call to <see cref="Update" /> /// Gets the delta that was applied during the last call to <see cref="Update" />
/// <para>
/// Note: If this is an extra timeline <see cref="LastDelta" /> is always equal to <see cref="DeltaToParent" />
/// </para>
/// </summary> /// </summary>
public TimeSpan LastDelta public TimeSpan LastDelta
{ {
get => _lastDelta; get => Parent == null ? _lastDelta : DeltaToParent;
private set => SetAndNotify(ref _lastDelta, value); private set => SetAndNotify(ref _lastDelta, value);
} }
/// <summary>
/// Gets the delta to this timeline's <see cref="Parent" />
/// </summary>
public TimeSpan DeltaToParent => Parent != null ? Position - Parent.Position : TimeSpan.Zero;
/// <summary> /// <summary>
/// Gets or sets the mode in which the render element starts its timeline when display conditions are met /// Gets or sets the mode in which the render element starts its timeline when display conditions are met
/// </summary> /// </summary>
@ -124,7 +135,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets a boolean indicating whether the timeline has finished its run /// Gets a boolean indicating whether the timeline has finished its run
/// </summary> /// </summary>
public bool IsFinished => Position > Length; public bool IsFinished => Position > Length || Length == TimeSpan.Zero;
#region Segments #region Segments
@ -136,17 +147,41 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets or sets the length of the start segment /// Gets or sets the length of the start segment
/// </summary> /// </summary>
public TimeSpan StartSegmentLength { get; set; } public TimeSpan StartSegmentLength
{
get => _startSegmentLength;
set
{
if (SetAndNotify(ref _startSegmentLength, value))
NotifySegmentShiftAt(TimelineSegment.Start, false);
}
}
/// <summary> /// <summary>
/// Gets or sets the length of the main segment /// Gets or sets the length of the main segment
/// </summary> /// </summary>
public TimeSpan MainSegmentLength { get; set; } public TimeSpan MainSegmentLength
{
get => _mainSegmentLength;
set
{
if (SetAndNotify(ref _mainSegmentLength, value))
NotifySegmentShiftAt(TimelineSegment.Main, false);
}
}
/// <summary> /// <summary>
/// Gets or sets the length of the end segment /// Gets or sets the length of the end segment
/// </summary> /// </summary>
public TimeSpan EndSegmentLength { get; set; } public TimeSpan EndSegmentLength
{
get => _endSegmentLength;
set
{
if (SetAndNotify(ref _endSegmentLength, value))
NotifySegmentShiftAt(TimelineSegment.End, false);
}
}
/// <summary> /// <summary>
/// Gets or sets the start position of the main segment /// Gets or sets the start position of the main segment
@ -154,7 +189,11 @@ namespace Artemis.Core
public TimeSpan MainSegmentStartPosition public TimeSpan MainSegmentStartPosition
{ {
get => StartSegmentEndPosition; get => StartSegmentEndPosition;
set => StartSegmentEndPosition = value; set
{
StartSegmentEndPosition = value;
NotifySegmentShiftAt(TimelineSegment.Main, true);
}
} }
/// <summary> /// <summary>
@ -163,7 +202,11 @@ namespace Artemis.Core
public TimeSpan EndSegmentStartPosition public TimeSpan EndSegmentStartPosition
{ {
get => MainSegmentEndPosition; get => MainSegmentEndPosition;
set => MainSegmentEndPosition = value; set
{
MainSegmentEndPosition = value;
NotifySegmentShiftAt(TimelineSegment.End, true);
}
} }
/// <summary> /// <summary>
@ -172,7 +215,11 @@ namespace Artemis.Core
public TimeSpan StartSegmentEndPosition public TimeSpan StartSegmentEndPosition
{ {
get => StartSegmentLength; get => StartSegmentLength;
set => StartSegmentLength = value; set
{
StartSegmentLength = value;
NotifySegmentShiftAt(TimelineSegment.Start, false);
}
} }
/// <summary> /// <summary>
@ -181,7 +228,11 @@ namespace Artemis.Core
public TimeSpan MainSegmentEndPosition public TimeSpan MainSegmentEndPosition
{ {
get => StartSegmentEndPosition + MainSegmentLength; get => StartSegmentEndPosition + MainSegmentLength;
set => MainSegmentLength = value - StartSegmentEndPosition >= TimeSpan.Zero ? value - StartSegmentEndPosition : TimeSpan.Zero; set
{
MainSegmentLength = value - StartSegmentEndPosition >= TimeSpan.Zero ? value - StartSegmentEndPosition : TimeSpan.Zero;
NotifySegmentShiftAt(TimelineSegment.Main, false);
}
} }
/// <summary> /// <summary>
@ -190,11 +241,41 @@ namespace Artemis.Core
public TimeSpan EndSegmentEndPosition public TimeSpan EndSegmentEndPosition
{ {
get => MainSegmentEndPosition + EndSegmentLength; get => MainSegmentEndPosition + EndSegmentLength;
set => EndSegmentLength = value - MainSegmentEndPosition >= TimeSpan.Zero ? value - MainSegmentEndPosition : TimeSpan.Zero; set
{
EndSegmentLength = value - MainSegmentEndPosition >= TimeSpan.Zero ? value - MainSegmentEndPosition : TimeSpan.Zero;
NotifySegmentShiftAt(TimelineSegment.End, false);
}
} }
internal TimelineEntity Entity { get; set; } internal TimelineEntity Entity { get; set; }
/// <summary>
/// Notifies the right segments in a way that I don't have to think about it
/// </summary>
/// <param name="segment">The segment that was updated</param>
/// <param name="startUpdated">Whether the start point of the <paramref name="segment" /> was updated</param>
private void NotifySegmentShiftAt(TimelineSegment segment, bool startUpdated)
{
if (segment <= TimelineSegment.End)
{
if (startUpdated || segment < TimelineSegment.End)
NotifyOfPropertyChange(nameof(EndSegmentStartPosition));
NotifyOfPropertyChange(nameof(EndSegmentEndPosition));
}
if (segment <= TimelineSegment.Main)
{
if (startUpdated || segment < TimelineSegment.Main)
NotifyOfPropertyChange(nameof(MainSegmentStartPosition));
NotifyOfPropertyChange(nameof(MainSegmentEndPosition));
}
if (segment <= TimelineSegment.Start) NotifyOfPropertyChange(nameof(StartSegmentEndPosition));
NotifyOfPropertyChange(nameof(Length));
}
#endregion #endregion
#endregion #endregion
@ -213,6 +294,9 @@ namespace Artemis.Core
if (stickToMainSegment && Position >= MainSegmentStartPosition) if (stickToMainSegment && Position >= MainSegmentStartPosition)
Position = MainSegmentStartPosition + TimeSpan.FromMilliseconds(Position.TotalMilliseconds % MainSegmentLength.TotalMilliseconds); Position = MainSegmentStartPosition + TimeSpan.FromMilliseconds(Position.TotalMilliseconds % MainSegmentLength.TotalMilliseconds);
foreach (Timeline extraTimeline in _extraTimelines)
extraTimeline.Update(delta, false);
} }
/// <summary> /// <summary>
@ -225,6 +309,8 @@ namespace Artemis.Core
LastDelta = TimeSpan.Zero - Position; LastDelta = TimeSpan.Zero - Position;
Position = TimeSpan.Zero; Position = TimeSpan.Zero;
_extraTimelines.Clear();
} }
/// <summary> /// <summary>
@ -237,6 +323,8 @@ namespace Artemis.Core
LastDelta = EndSegmentStartPosition - Position; LastDelta = EndSegmentStartPosition - Position;
Position = EndSegmentStartPosition; Position = EndSegmentStartPosition;
_extraTimelines.Clear();
} }
/// <summary> /// <summary>
@ -249,6 +337,8 @@ namespace Artemis.Core
LastDelta = EndSegmentEndPosition - Position; LastDelta = EndSegmentEndPosition - Position;
Position = EndSegmentEndPosition; Position = EndSegmentEndPosition;
_extraTimelines.Clear();
} }
/// <summary> /// <summary>
@ -258,12 +348,20 @@ namespace Artemis.Core
/// <param name="stickToMainSegment">Whether to stick to the main segment, wrapping around if needed</param> /// <param name="stickToMainSegment">Whether to stick to the main segment, wrapping around if needed</param>
public void Override(TimeSpan position, bool stickToMainSegment) public void Override(TimeSpan position, bool stickToMainSegment)
{ {
_extraTimelines.Clear();
LastDelta = position - Position; LastDelta = position - Position;
Position = position; Position = position;
if (stickToMainSegment && Position >= MainSegmentStartPosition) if (stickToMainSegment && Position >= MainSegmentStartPosition)
Position = MainSegmentStartPosition + TimeSpan.FromMilliseconds(Position.TotalMilliseconds % MainSegmentLength.TotalMilliseconds); Position = MainSegmentStartPosition + TimeSpan.FromMilliseconds(Position.TotalMilliseconds % MainSegmentLength.TotalMilliseconds);
_extraTimelines.Clear();
}
/// <summary>
/// Sets the <see cref="LastDelta" /> to <see cref="TimeSpan.Zero" />
/// </summary>
public void ClearDelta()
{
LastDelta = TimeSpan.Zero;
} }
#endregion #endregion
@ -295,6 +393,13 @@ namespace Artemis.Core
#endregion #endregion
} }
internal enum TimelineSegment
{
Start,
Main,
End
}
/// <summary> /// <summary>
/// Represents a mode for render elements to start their timeline when display conditions are met /// Represents a mode for render elements to start their timeline when display conditions are met
/// </summary> /// </summary>

View File

@ -123,7 +123,7 @@ namespace Artemis.Core.Services
_introAnimation.Render(args.DeltaTime, args.Canvas, _rgbService.BitmapBrush.Bitmap.Info); _introAnimation.Render(args.DeltaTime, args.Canvas, _rgbService.BitmapBrush.Bitmap.Info);
} }
TimeSpan introLength = _introAnimation.AnimationProfile.GetAllLayers().Max(l => l.TimelineLength); TimeSpan introLength = _introAnimation.AnimationProfile.GetAllLayers().Max(l => l.Timeline.Length);
// Stop rendering after the profile finishes (take 1 second extra in case of slow updates) // Stop rendering after the profile finishes (take 1 second extra in case of slow updates)
Task.Run(async () => Task.Run(async () =>

View File

@ -13,31 +13,32 @@ namespace Artemis.Storage.Migrations
public void Apply(LiteRepository repository) public void Apply(LiteRepository repository)
{ {
List<ProfileEntity> profiles = repository.Query<ProfileEntity>().ToList(); // Lesson for next time: Use BsonDocuments in migrations
foreach (ProfileEntity profileEntity in profiles) // List<ProfileEntity> profiles = repository.Query<ProfileEntity>().ToList();
{ // foreach (ProfileEntity profileEntity in profiles)
foreach (FolderEntity folder in profileEntity.Folders.Where(f => f.MainSegmentLength == TimeSpan.Zero)) // {
{ // foreach (FolderEntity folder in profileEntity.Folders.Where(f => f.MainSegmentLength == TimeSpan.Zero))
if (folder.PropertyEntities.Any(p => p.KeyframeEntities.Any())) // {
folder.MainSegmentLength = folder.PropertyEntities.Where(p => p.KeyframeEntities.Any()).Max(p => p.KeyframeEntities.Max(k => k.Position)); // if (folder.PropertyEntities.Any(p => p.KeyframeEntities.Any()))
if (folder.MainSegmentLength == TimeSpan.Zero) // folder.MainSegmentLength = folder.PropertyEntities.Where(p => p.KeyframeEntities.Any()).Max(p => p.KeyframeEntities.Max(k => k.Position));
folder.MainSegmentLength = TimeSpan.FromSeconds(5); // if (folder.MainSegmentLength == TimeSpan.Zero)
// folder.MainSegmentLength = TimeSpan.FromSeconds(5);
folder.PlayMode = 0; //
} // folder.PlayMode = 0;
// }
foreach (LayerEntity layer in profileEntity.Layers.Where(l => l.MainSegmentLength == TimeSpan.Zero)) //
{ // foreach (LayerEntity layer in profileEntity.Layers.Where(l => l.MainSegmentLength == TimeSpan.Zero))
if (layer.PropertyEntities.Any(p => p.KeyframeEntities.Any())) // {
layer.MainSegmentLength = layer.PropertyEntities.Where(p => p.KeyframeEntities.Any()).Max(p => p.KeyframeEntities.Max(k => k.Position)); // if (layer.PropertyEntities.Any(p => p.KeyframeEntities.Any()))
if (layer.MainSegmentLength == TimeSpan.Zero) // layer.MainSegmentLength = layer.PropertyEntities.Where(p => p.KeyframeEntities.Any()).Max(p => p.KeyframeEntities.Max(k => k.Position));
layer.MainSegmentLength = TimeSpan.FromSeconds(5); // if (layer.MainSegmentLength == TimeSpan.Zero)
// layer.MainSegmentLength = TimeSpan.FromSeconds(5);
layer.PlayMode = 0; //
} // layer.PlayMode = 0;
// }
repository.Update(profileEntity); //
} // repository.Update(profileEntity);
// }
} }
} }
} }

View File

@ -52,8 +52,8 @@ namespace Artemis.UI.Shared.Services
set set
{ {
if (_currentTime.Equals(value)) return; if (_currentTime.Equals(value)) return;
if (SelectedProfileElement != null && value > SelectedProfileElement.TimelineLength) if (SelectedProfileElement != null && value > SelectedProfileElement.Timeline.Length)
_currentTime = SelectedProfileElement.TimelineLength; _currentTime = SelectedProfileElement.Timeline.Length;
else else
_currentTime = value; _currentTime = value;
UpdateProfilePreview(); UpdateProfilePreview();
@ -150,9 +150,9 @@ namespace Artemis.UI.Shared.Services
// Stick to the main segment for any element that is not currently selected // Stick to the main segment for any element that is not currently selected
foreach (Folder folder in SelectedProfile.GetAllFolders()) foreach (Folder folder in SelectedProfile.GetAllFolders())
folder.OverrideTimeLines(CurrentTime, folder != SelectedProfileElement); folder.Timeline.Override(CurrentTime, folder != SelectedProfileElement);
foreach (Layer layer in SelectedProfile.GetAllLayers()) foreach (Layer layer in SelectedProfile.GetAllLayers())
layer.OverrideTimeLines(CurrentTime, layer != SelectedProfileElement); layer.Timeline.Override(CurrentTime, layer != SelectedProfileElement);
_coreService.FrameRendered += CoreServiceOnFrameRendered; _coreService.FrameRendered += CoreServiceOnFrameRendered;
} }
@ -232,24 +232,23 @@ namespace Artemis.UI.Shared.Services
if (snapToSegments) if (snapToSegments)
{ {
// Snap to the end of the start segment // Snap to the end of the start segment
if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.StartSegmentLength.TotalMilliseconds) < tolerance.TotalMilliseconds) if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.Timeline.StartSegmentEndPosition.TotalMilliseconds) < tolerance.TotalMilliseconds)
return SelectedProfileElement.StartSegmentLength; return SelectedProfileElement.Timeline.StartSegmentEndPosition;
// Snap to the end of the main segment // Snap to the end of the main segment
TimeSpan mainSegmentEnd = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength; if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.Timeline.MainSegmentEndPosition.TotalMilliseconds) < tolerance.TotalMilliseconds)
if (Math.Abs(time.TotalMilliseconds - mainSegmentEnd.TotalMilliseconds) < tolerance.TotalMilliseconds) return SelectedProfileElement.Timeline.MainSegmentEndPosition;
return mainSegmentEnd;
// Snap to the end of the end segment (end of the timeline) // Snap to the end of the end segment (end of the timeline)
if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.TimelineLength.TotalMilliseconds) < tolerance.TotalMilliseconds) if (Math.Abs(time.TotalMilliseconds - SelectedProfileElement.Timeline.EndSegmentEndPosition.TotalMilliseconds) < tolerance.TotalMilliseconds)
return SelectedProfileElement.TimelineLength; return SelectedProfileElement.Timeline.EndSegmentEndPosition;
} }
if (snapToCurrentTime) if (snapToCurrentTime)
{ {
// Snap to the current time // Snap to the current time
if (Math.Abs(time.TotalMilliseconds - CurrentTime.TotalMilliseconds) < tolerance.TotalMilliseconds) if (Math.Abs(time.TotalMilliseconds - CurrentTime.TotalMilliseconds) < tolerance.TotalMilliseconds)
return SelectedProfileElement.StartSegmentLength; return CurrentTime;
} }
if (snapTimes != null) if (snapTimes != null)

View File

@ -200,7 +200,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<RadioButton Grid.Column="0" <RadioButton Grid.Column="0"
Style="{StaticResource MaterialDesignTabRadioButton}" Style="{StaticResource MaterialDesignTabRadioButton}"
IsChecked="{Binding Path=RenderProfileElement.EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Restart}}"> IsChecked="{Binding Path=RenderProfileElement.Timeline.EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Restart}}">
<TextBlock VerticalAlignment="Center" FontSize="12"> <TextBlock VerticalAlignment="Center" FontSize="12">
<materialDesign:PackIcon Kind="Repeat" VerticalAlignment="Center" Margin="-3 0 0 -3" /> <materialDesign:PackIcon Kind="Repeat" VerticalAlignment="Center" Margin="-3 0 0 -3" />
RESTART RESTART
@ -215,7 +215,7 @@
</RadioButton> </RadioButton>
<RadioButton Grid.Column="1" <RadioButton Grid.Column="1"
Style="{StaticResource MaterialDesignTabRadioButton}" Style="{StaticResource MaterialDesignTabRadioButton}"
IsChecked="{Binding Path=RenderProfileElement.EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Ignore}}"> IsChecked="{Binding Path=RenderProfileElement.Timeline.EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Ignore}}">
<TextBlock VerticalAlignment="Center" FontSize="12"> <TextBlock VerticalAlignment="Center" FontSize="12">
<materialDesign:PackIcon Kind="EarHearingOff" VerticalAlignment="Center" Margin="-3 0 0 -3" /> <materialDesign:PackIcon Kind="EarHearingOff" VerticalAlignment="Center" Margin="-3 0 0 -3" />
IGNORE IGNORE
@ -230,7 +230,7 @@
</RadioButton> </RadioButton>
<RadioButton Grid.Column="2" <RadioButton Grid.Column="2"
Style="{StaticResource MaterialDesignTabRadioButton}" Style="{StaticResource MaterialDesignTabRadioButton}"
IsChecked="{Binding Path=RenderProfileElement.EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Copy}}"> IsChecked="{Binding Path=RenderProfileElement.Timeline.EventOverlapMode, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Copy}}">
<TextBlock VerticalAlignment="Center" FontSize="12"> <TextBlock VerticalAlignment="Center" FontSize="12">
<materialDesign:PackIcon Kind="ContentCopy" VerticalAlignment="Center" Margin="-3 0 0 -3" /> <materialDesign:PackIcon Kind="ContentCopy" VerticalAlignment="Center" Margin="-3 0 0 -3" />
COPY COPY

View File

@ -43,24 +43,24 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
public bool DisplayContinuously public bool DisplayContinuously
{ {
get => RenderProfileElement?.PlayMode == TimelinePlayMode.Repeat; get => RenderProfileElement?.Timeline.PlayMode == TimelinePlayMode.Repeat;
set set
{ {
TimelinePlayMode playMode = value ? TimelinePlayMode.Repeat : TimelinePlayMode.Once; TimelinePlayMode playMode = value ? TimelinePlayMode.Repeat : TimelinePlayMode.Once;
if (RenderProfileElement == null || RenderProfileElement?.PlayMode == playMode) return; if (RenderProfileElement == null || RenderProfileElement?.Timeline.PlayMode == playMode) return;
RenderProfileElement.PlayMode = playMode; RenderProfileElement.Timeline.PlayMode = playMode;
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
} }
} }
public bool AlwaysFinishTimeline public bool AlwaysFinishTimeline
{ {
get => RenderProfileElement?.StopMode == TimelineStopMode.Finish; get => RenderProfileElement?.Timeline.StopMode == TimelineStopMode.Finish;
set set
{ {
TimelineStopMode stopMode = value ? TimelineStopMode.Finish : TimelineStopMode.SkipToEnd; TimelineStopMode stopMode = value ? TimelineStopMode.Finish : TimelineStopMode.SkipToEnd;
if (RenderProfileElement == null || RenderProfileElement?.StopMode == stopMode) return; if (RenderProfileElement == null || RenderProfileElement?.Timeline.StopMode == stopMode) return;
RenderProfileElement.StopMode = stopMode; RenderProfileElement.Timeline.StopMode = stopMode;
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
} }
} }

View File

@ -237,7 +237,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
} }
// While playing in preview data bindings aren't updated // While playing in preview data bindings aren't updated
Registration.DataBinding.Update(0.04); Registration.DataBinding.Update(Registration.LayerProperty.ProfileElement.Timeline);
if (ActiveItem.SupportsTestValue) if (ActiveItem.SupportsTestValue)
{ {

View File

@ -567,7 +567,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
{ {
if (Repeating && RepeatTimeline) if (Repeating && RepeatTimeline)
{ {
if (newTime > SelectedProfileElement.TimelineLength) if (newTime > SelectedProfileElement.Timeline.Length)
newTime = TimeSpan.Zero; newTime = TimeSpan.Zero;
} }
else if (Repeating && RepeatSegment) else if (Repeating && RepeatSegment)
@ -575,9 +575,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
if (newTime > GetCurrentSegmentEnd()) if (newTime > GetCurrentSegmentEnd())
newTime = GetCurrentSegmentStart(); newTime = GetCurrentSegmentStart();
} }
else if (newTime > SelectedProfileElement.TimelineLength) else if (newTime > SelectedProfileElement.Timeline.Length)
{ {
newTime = SelectedProfileElement.TimelineLength; newTime = SelectedProfileElement.Timeline.Length;
Pause(); Pause();
} }
} }

View File

@ -148,8 +148,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{ {
if (position < TimeSpan.Zero) if (position < TimeSpan.Zero)
LayerPropertyKeyframe.Position = TimeSpan.Zero; LayerPropertyKeyframe.Position = TimeSpan.Zero;
else if (position > _profileEditorService.SelectedProfileElement.TimelineLength) else if (position > _profileEditorService.SelectedProfileElement.Timeline.Length)
LayerPropertyKeyframe.Position = _profileEditorService.SelectedProfileElement.TimelineLength; LayerPropertyKeyframe.Position = _profileEditorService.SelectedProfileElement.Timeline.Length;
else else
LayerPropertyKeyframe.Position = position; LayerPropertyKeyframe.Position = position;
@ -170,7 +170,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
); );
// If possible, shift the keyframe to the right by 11 pixels // If possible, shift the keyframe to the right by 11 pixels
TimeSpan desiredPosition = newKeyframe.Position + TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11); TimeSpan desiredPosition = newKeyframe.Position + TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11);
if (desiredPosition <= newKeyframe.LayerProperty.ProfileElement.TimelineLength) if (desiredPosition <= newKeyframe.LayerProperty.ProfileElement.Timeline.Length)
newKeyframe.Position = desiredPosition; newKeyframe.Position = desiredPosition;
// Otherwise if possible shift it to the left by 11 pixels // Otherwise if possible shift it to the left by 11 pixels
else else

View File

@ -56,10 +56,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
start ??= TimeSpan.Zero; start ??= TimeSpan.Zero;
end ??= TimeSpan.MaxValue; end ??= TimeSpan.MaxValue;
List<LayerPropertyKeyframe<T>> toShift = LayerProperty.Keyframes.Where(k => k.Position >= start && k.Position < end).ToList(); List<LayerPropertyKeyframe<T>> toShift = LayerProperty.Keyframes.Where(k => k.Position > start && k.Position < end).ToList();
foreach (LayerPropertyKeyframe<T> keyframe in toShift) foreach (LayerPropertyKeyframe<T> keyframe in toShift)
keyframe.Position += amount; keyframe.Position += amount;
UpdateKeyframes(); UpdateKeyframes();
} }

View File

@ -48,7 +48,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected; ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected;
if (ProfileEditorService.SelectedProfileElement != null) if (ProfileEditorService.SelectedProfileElement != null)
ProfileEditorService.SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged; ProfileEditorService.SelectedProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged;
Update(); Update();
} }
@ -104,13 +104,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{ {
if (Segment != SegmentViewModelType.Main) if (Segment != SegmentViewModelType.Main)
return false; return false;
return SelectedProfileElement?.PlayMode == TimelinePlayMode.Repeat; return SelectedProfileElement?.Timeline.PlayMode == TimelinePlayMode.Repeat;
} }
set set
{ {
if (Segment != SegmentViewModelType.Main) if (Segment != SegmentViewModelType.Main || SelectedProfileElement == null)
return; return;
SelectedProfileElement.PlayMode = value ? TimelinePlayMode.Repeat : TimelinePlayMode.Once; SelectedProfileElement.Timeline.PlayMode = value ? TimelinePlayMode.Repeat : TimelinePlayMode.Once;
ProfileEditorService.UpdateSelectedProfileElement(); ProfileEditorService.UpdateSelectedProfileElement();
NotifyOfPropertyChange(nameof(RepeatSegment)); NotifyOfPropertyChange(nameof(RepeatSegment));
} }
@ -156,32 +156,35 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{ {
if (SelectedProfileElement == null) if (SelectedProfileElement == null)
{ {
SegmentLength = TimeSpan.Zero;
SegmentStart = TimeSpan.Zero; SegmentStart = TimeSpan.Zero;
SegmentEnd = TimeSpan.Zero; SegmentEnd = TimeSpan.Zero;
SegmentLength = TimeSpan.Zero;
SegmentStartPosition = 0; SegmentStartPosition = 0;
SegmentWidth = 0; SegmentWidth = 0;
SegmentEnabled = false; SegmentEnabled = false;
return; return;
} }
// It would be nice to do this differently if this patterns end up appearing in more places
if (Segment == SegmentViewModelType.Start) if (Segment == SegmentViewModelType.Start)
{ {
SegmentLength = SelectedProfileElement.StartSegmentLength;
SegmentStart = TimeSpan.Zero; SegmentStart = TimeSpan.Zero;
SegmentEnd = SelectedProfileElement.Timeline.StartSegmentEndPosition;
SegmentLength = SelectedProfileElement.Timeline.StartSegmentLength;
} }
else if (Segment == SegmentViewModelType.Main) else if (Segment == SegmentViewModelType.Main)
{ {
SegmentLength = SelectedProfileElement.MainSegmentLength; SegmentStart = SelectedProfileElement.Timeline.MainSegmentStartPosition;
SegmentStart = SelectedProfileElement.StartSegmentLength; SegmentEnd = SelectedProfileElement.Timeline.MainSegmentEndPosition;
SegmentLength = SelectedProfileElement.Timeline.MainSegmentLength;
} }
else if (Segment == SegmentViewModelType.End) else if (Segment == SegmentViewModelType.End)
{ {
SegmentLength = SelectedProfileElement.EndSegmentLength; SegmentStart = SelectedProfileElement.Timeline.EndSegmentStartPosition;
SegmentStart = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength; SegmentEnd = SelectedProfileElement.Timeline.EndSegmentEndPosition;
SegmentLength = SelectedProfileElement.Timeline.EndSegmentLength;
} }
SegmentEnd = SegmentStart + SegmentLength;
SegmentStartPosition = SegmentStart.TotalSeconds * ProfileEditorService.PixelsPerSecond; SegmentStartPosition = SegmentStart.TotalSeconds * ProfileEditorService.PixelsPerSecond;
SegmentWidth = SegmentLength.TotalSeconds * ProfileEditorService.PixelsPerSecond; SegmentWidth = SegmentLength.TotalSeconds * ProfileEditorService.PixelsPerSecond;
SegmentEnabled = SegmentLength != TimeSpan.Zero; SegmentEnabled = SegmentLength != TimeSpan.Zero;
@ -195,28 +198,25 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
public void DisableSegment() public void DisableSegment()
{ {
TimeSpan startSegmentEnd = SelectedProfileElement.StartSegmentLength;
TimeSpan mainSegmentEnd = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength;
TimeSpan oldSegmentLength = SegmentLength; TimeSpan oldSegmentLength = SegmentLength;
if (Segment == SegmentViewModelType.Start) if (Segment == SegmentViewModelType.Start)
{ {
// Remove keyframes that fall in this segment // Remove keyframes that fall in this segment
WipeKeyframes(null, startSegmentEnd); WipeKeyframes(null, SelectedProfileElement.Timeline.StartSegmentEndPosition);
SelectedProfileElement.StartSegmentLength = TimeSpan.Zero; SelectedProfileElement.Timeline.StartSegmentLength = TimeSpan.Zero;
} }
else if (Segment == SegmentViewModelType.Main) else if (Segment == SegmentViewModelType.Main)
{ {
// Remove keyframes that fall in this segment // Remove keyframes that fall in this segment
WipeKeyframes(startSegmentEnd, startSegmentEnd); WipeKeyframes(SelectedProfileElement.Timeline.MainSegmentStartPosition, SelectedProfileElement.Timeline.MainSegmentEndPosition);
SelectedProfileElement.MainSegmentLength = TimeSpan.Zero; SelectedProfileElement.Timeline.MainSegmentLength = TimeSpan.Zero;
} }
else if (Segment == SegmentViewModelType.End) else if (Segment == SegmentViewModelType.End)
{ {
// Remove keyframes that fall in this segment // Remove keyframes that fall in this segment
WipeKeyframes(mainSegmentEnd, null); WipeKeyframes(SelectedProfileElement.Timeline.EndSegmentStartPosition, SelectedProfileElement.Timeline.EndSegmentEndPosition);
SelectedProfileElement.EndSegmentLength = TimeSpan.Zero; SelectedProfileElement.Timeline.EndSegmentLength = TimeSpan.Zero;
} }
ShiftNextSegment(SegmentLength - oldSegmentLength); ShiftNextSegment(SegmentLength - oldSegmentLength);
@ -228,11 +228,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{ {
ShiftNextSegment(TimeSpan.FromSeconds(1)); ShiftNextSegment(TimeSpan.FromSeconds(1));
if (Segment == SegmentViewModelType.Start) if (Segment == SegmentViewModelType.Start)
SelectedProfileElement.StartSegmentLength = TimeSpan.FromSeconds(1); SelectedProfileElement.Timeline.StartSegmentLength = TimeSpan.FromSeconds(1);
else if (Segment == SegmentViewModelType.Main) else if (Segment == SegmentViewModelType.Main)
SelectedProfileElement.MainSegmentLength = TimeSpan.FromSeconds(1); SelectedProfileElement.Timeline.MainSegmentLength = TimeSpan.FromSeconds(1);
else if (Segment == SegmentViewModelType.End) else if (Segment == SegmentViewModelType.End)
SelectedProfileElement.EndSegmentLength = TimeSpan.FromSeconds(1); SelectedProfileElement.Timeline.EndSegmentLength = TimeSpan.FromSeconds(1);
ProfileEditorService.UpdateSelectedProfileElement(); ProfileEditorService.UpdateSelectedProfileElement();
Update(); Update();
@ -290,27 +290,24 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
public void UpdateLength(TimeSpan newTime) public void UpdateLength(TimeSpan newTime)
{ {
TimeSpan oldSegmentLength = SegmentLength; if (newTime < TimeSpan.FromMilliseconds(100))
if (Segment == SegmentViewModelType.Start) newTime = TimeSpan.FromMilliseconds(100);
{
if (newTime < TimeSpan.FromMilliseconds(100)) TimeSpan difference = newTime - SegmentEnd;
newTime = TimeSpan.FromMilliseconds(100);
SelectedProfileElement.StartSegmentLength = newTime; if (difference > TimeSpan.Zero)
} ShiftNextSegment(difference);
else if (Segment == SegmentViewModelType.Main)
{ if (Segment == SegmentViewModelType.Start)
if (newTime < SelectedProfileElement.StartSegmentLength + TimeSpan.FromMilliseconds(100)) SelectedProfileElement.Timeline.StartSegmentEndPosition = newTime;
newTime = SelectedProfileElement.StartSegmentLength + TimeSpan.FromMilliseconds(100); else if (Segment == SegmentViewModelType.Main)
SelectedProfileElement.MainSegmentLength = newTime - SelectedProfileElement.StartSegmentLength; SelectedProfileElement.Timeline.MainSegmentEndPosition = newTime;
} else if (Segment == SegmentViewModelType.End)
else if (Segment == SegmentViewModelType.End) SelectedProfileElement.Timeline.EndSegmentEndPosition = newTime;
{
if (newTime < SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength + TimeSpan.FromMilliseconds(100)) if (difference < TimeSpan.Zero)
newTime = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength + TimeSpan.FromMilliseconds(100); ShiftNextSegment(difference);
SelectedProfileElement.EndSegmentLength = newTime - SelectedProfileElement.StartSegmentLength - SelectedProfileElement.MainSegmentLength;
}
ShiftNextSegment(SegmentLength - oldSegmentLength);
Update(); Update();
} }
@ -323,7 +320,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected;
if (SelectedProfileElement != null) if (SelectedProfileElement != null)
SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged; SelectedProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged;
} }
#endregion #endregion
@ -333,20 +330,20 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
private void ProfileEditorServiceOnProfileElementSelected(object? sender, RenderProfileElementEventArgs e) private void ProfileEditorServiceOnProfileElementSelected(object? sender, RenderProfileElementEventArgs e)
{ {
if (e.PreviousRenderProfileElement != null) if (e.PreviousRenderProfileElement != null)
e.PreviousRenderProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged; e.PreviousRenderProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged;
if (e.RenderProfileElement != null) if (e.RenderProfileElement != null)
e.RenderProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged; e.RenderProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged;
Update(); Update();
} }
private void SelectedProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e) private void TimelineOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{ {
if (e.PropertyName == nameof(RenderProfileElement.StartSegmentLength) || if (e.PropertyName == nameof(Core.Timeline.StartSegmentLength) ||
e.PropertyName == nameof(RenderProfileElement.MainSegmentLength) || e.PropertyName == nameof(Core.Timeline.MainSegmentLength) ||
e.PropertyName == nameof(RenderProfileElement.EndSegmentLength)) e.PropertyName == nameof(Core.Timeline.EndSegmentLength))
Update(); Update();
else if (e.PropertyName == nameof(RenderProfileElement.PlayMode)) else if (e.PropertyName == nameof(Core.Timeline.PlayMode))
NotifyOfPropertyChange(nameof(RepeatSegment)); NotifyOfPropertyChange(nameof(RepeatSegment));
} }
@ -359,15 +356,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
public void ShiftNextSegment(TimeSpan amount) public void ShiftNextSegment(TimeSpan amount)
{ {
TimeSpan segmentEnd = TimeSpan.Zero;
if (Segment == SegmentViewModelType.Start) if (Segment == SegmentViewModelType.Start)
segmentEnd = SelectedProfileElement.StartSegmentLength; ShiftKeyframes(SelectedProfileElement.Timeline.StartSegmentEndPosition, null, amount);
else if (Segment == SegmentViewModelType.Main) else if (Segment == SegmentViewModelType.Main)
segmentEnd = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength; ShiftKeyframes(SelectedProfileElement.Timeline.MainSegmentEndPosition, null, amount);
else if (Segment == SegmentViewModelType.End) else if (Segment == SegmentViewModelType.End)
segmentEnd = SelectedProfileElement.TimelineLength; ShiftKeyframes(SelectedProfileElement.Timeline.EndSegmentEndPosition, null, amount);
ShiftKeyframes(segmentEnd, null, amount);
} }
private void WipeKeyframes(TimeSpan? start, TimeSpan? end) private void WipeKeyframes(TimeSpan? start, TimeSpan? end)

View File

@ -29,7 +29,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
_profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; _profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
_profileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected; _profileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected;
if (_profileEditorService.SelectedProfileElement != null) if (_profileEditorService.SelectedProfileElement != null)
_profileEditorService.SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged; _profileEditorService.SelectedProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged;
Update(); Update();
} }
@ -75,9 +75,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
TotalTimelineWidth = EndSegmentEndPosition; TotalTimelineWidth = EndSegmentEndPosition;
} }
private void SelectedProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e) private void TimelineOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{ {
Update(); if (e.PropertyName == nameof(Core.Timeline.StartSegmentLength) ||
e.PropertyName == nameof(Core.Timeline.MainSegmentLength) ||
e.PropertyName == nameof(Core.Timeline.EndSegmentLength))
Update();
} }
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e) private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
@ -88,9 +91,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
private void ProfileEditorServiceOnProfileElementSelected(object? sender, RenderProfileElementEventArgs e) private void ProfileEditorServiceOnProfileElementSelected(object? sender, RenderProfileElementEventArgs e)
{ {
if (e.PreviousRenderProfileElement != null) if (e.PreviousRenderProfileElement != null)
e.PreviousRenderProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged; e.PreviousRenderProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged;
if (e.RenderProfileElement != null) if (e.RenderProfileElement != null)
e.RenderProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged; e.RenderProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged;
Update(); Update();
} }
@ -184,13 +187,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
// If shift is held, snap to the current time // If shift is held, snap to the current time
// Take a tolerance of 5 pixels (half a keyframe width) // Take a tolerance of 5 pixels (half a keyframe width)
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
{ time = _profileEditorService.SnapToTimeline(time, TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 5), false, true);
float tolerance = 1000f / _profileEditorService.PixelsPerSecond * 5;
if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - time.TotalMilliseconds) < tolerance)
time = _profileEditorService.CurrentTime;
else if (Math.Abs(_profileEditorService.SelectedProfileElement.StartSegmentLength.TotalMilliseconds - time.TotalMilliseconds) < tolerance)
time = _profileEditorService.SelectedProfileElement.StartSegmentLength;
}
return time; return time;
} }
@ -351,7 +348,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
_profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; _profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
_profileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; _profileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected;
if (_profileEditorService.SelectedProfileElement != null) if (_profileEditorService.SelectedProfileElement != null)
_profileEditorService.SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged; _profileEditorService.SelectedProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged;
} }
#endregion #endregion