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