mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-31 17:53:32 +00:00
Profiles - Removed the technical concept of extra timelines for events
Profiles - Properly separated out update and render logic Profile editor - Implemented layer visualization
This commit is contained in:
parent
ff4ec16690
commit
89beb92935
@ -158,8 +158,8 @@ namespace Artemis.Core
|
|||||||
// If the timeline was already running, look at the event overlap mode
|
// If the timeline was already running, look at the event overlap mode
|
||||||
if (EventOverlapMode == TimeLineEventOverlapMode.Restart)
|
if (EventOverlapMode == TimeLineEventOverlapMode.Restart)
|
||||||
timeline.JumpToStart();
|
timeline.JumpToStart();
|
||||||
else if (EventOverlapMode == TimeLineEventOverlapMode.Copy)
|
else if (EventOverlapMode == TimeLineEventOverlapMode.Copy && ProfileElement is Layer layer)
|
||||||
timeline.AddExtraTimeline();
|
layer.CreateCopyAsChild();
|
||||||
else if (EventOverlapMode == TimeLineEventOverlapMode.Toggle && !wasMet)
|
else if (EventOverlapMode == TimeLineEventOverlapMode.Toggle && !wasMet)
|
||||||
timeline.JumpToStart();
|
timeline.JumpToStart();
|
||||||
|
|
||||||
|
|||||||
@ -102,6 +102,9 @@ namespace Artemis.Core
|
|||||||
else if (Timeline.IsFinished)
|
else if (Timeline.IsFinished)
|
||||||
Disable();
|
Disable();
|
||||||
|
|
||||||
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||||
|
baseLayerEffect.InternalUpdate(Timeline);
|
||||||
|
|
||||||
foreach (ProfileElement child in Children)
|
foreach (ProfileElement child in Children)
|
||||||
child.Update(deltaTime);
|
child.Update(deltaTime);
|
||||||
}
|
}
|
||||||
@ -176,41 +179,35 @@ namespace Artemis.Core
|
|||||||
// No point rendering if all children are disabled
|
// No point rendering if all children are disabled
|
||||||
if (!Children.Any(c => c is RenderProfileElement {Enabled: true}))
|
if (!Children.Any(c => c is RenderProfileElement {Enabled: true}))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
lock (Timeline)
|
SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
|
||||||
|
try
|
||||||
{
|
{
|
||||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
SKRectI rendererBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height);
|
||||||
baseLayerEffect.InternalUpdate(Timeline);
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||||
|
baseLayerEffect.InternalPreProcess(canvas, rendererBounds, layerPaint);
|
||||||
|
|
||||||
SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
|
// No point rendering if the alpha was set to zero by one of the effects
|
||||||
try
|
if (layerPaint.Color.Alpha == 0)
|
||||||
{
|
return;
|
||||||
SKRectI rendererBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height);
|
|
||||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
|
||||||
baseLayerEffect.InternalPreProcess(canvas, rendererBounds, layerPaint);
|
|
||||||
|
|
||||||
// No point rendering if the alpha was set to zero by one of the effects
|
canvas.SaveLayer(layerPaint);
|
||||||
if (layerPaint.Color.Alpha == 0)
|
canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y);
|
||||||
return;
|
|
||||||
|
|
||||||
canvas.SaveLayer(layerPaint);
|
// Iterate the children in reverse because the first layer must be rendered last to end up on top
|
||||||
canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y);
|
for (int index = Children.Count - 1; index > -1; index--)
|
||||||
|
Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top));
|
||||||
|
|
||||||
// Iterate the children in reverse because the first layer must be rendered last to end up on top
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||||
for (int index = Children.Count - 1; index > -1; index--)
|
baseLayerEffect.InternalPostProcess(canvas, rendererBounds, layerPaint);
|
||||||
Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top));
|
|
||||||
|
|
||||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
|
||||||
baseLayerEffect.InternalPostProcess(canvas, rendererBounds, layerPaint);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
canvas.Restore();
|
|
||||||
layerPaint.DisposeSelfAndProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timeline.ClearDelta();
|
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
canvas.Restore();
|
||||||
|
layerPaint.DisposeSelfAndProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
Timeline.ClearDelta();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -233,6 +230,14 @@ namespace Artemis.Core
|
|||||||
Enabled = true;
|
Enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OverrideTimelineAndApply(TimeSpan position, bool stickToMainSegment)
|
||||||
|
{
|
||||||
|
Timeline.Override(position, stickToMainSegment);
|
||||||
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||||
|
baseLayerEffect.InternalUpdate(Timeline);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Disable()
|
public override void Disable()
|
||||||
{
|
{
|
||||||
@ -314,7 +319,7 @@ namespace Artemis.Core
|
|||||||
ChildrenList.Add(new Layer(Profile, this, childLayer));
|
ChildrenList.Add(new Layer(Profile, this, childLayer));
|
||||||
|
|
||||||
// Ensure order integrity, should be unnecessary but no one is perfect specially me
|
// Ensure order integrity, should be unnecessary but no one is perfect specially me
|
||||||
ChildrenList.Sort((a,b) => a.Order.CompareTo(b.Order));
|
ChildrenList.Sort((a, b) => a.Order.CompareTo(b.Order));
|
||||||
for (int index = 0; index < ChildrenList.Count; index++)
|
for (int index = 0; index < ChildrenList.Count; index++)
|
||||||
ChildrenList[index].Order = index + 1;
|
ChildrenList[index].Order = index + 1;
|
||||||
|
|
||||||
|
|||||||
@ -345,17 +345,30 @@ namespace Artemis.Core
|
|||||||
Enable();
|
Enable();
|
||||||
else if (Timeline.IsFinished)
|
else if (Timeline.IsFinished)
|
||||||
Disable();
|
Disable();
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
if (Timeline.Delta == TimeSpan.Zero)
|
||||||
public override void Reset()
|
return;
|
||||||
{
|
|
||||||
UpdateDisplayCondition();
|
|
||||||
|
|
||||||
if (DisplayConditionMet)
|
General.Update(Timeline);
|
||||||
Timeline.JumpToStart();
|
Transform.Update(Timeline);
|
||||||
else
|
LayerBrush?.InternalUpdate(Timeline);
|
||||||
Timeline.JumpToEnd();
|
|
||||||
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||||
|
baseLayerEffect.InternalUpdate(Timeline);
|
||||||
|
|
||||||
|
// Remove children that finished their timeline and update the rest
|
||||||
|
for (int index = 0; index < Children.Count; index++)
|
||||||
|
{
|
||||||
|
ProfileElement profileElement = Children[index];
|
||||||
|
if (((Layer) profileElement).Timeline.IsFinished)
|
||||||
|
{
|
||||||
|
RemoveChild(profileElement);
|
||||||
|
profileElement.Dispose();
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
profileElement.Update(deltaTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -365,88 +378,18 @@ namespace Artemis.Core
|
|||||||
throw new ObjectDisposedException("Layer");
|
throw new ObjectDisposedException("Layer");
|
||||||
|
|
||||||
// Ensure the layer is ready
|
// Ensure the layer is ready
|
||||||
if (!Enabled || Path == null || LayerShape?.Path == null || !General.PropertiesInitialized || !Transform.PropertiesInitialized)
|
if (!Enabled || Path == null || LayerShape?.Path == null || !General.PropertiesInitialized || !Transform.PropertiesInitialized || !Leds.Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Render children first so they go below
|
||||||
|
for (int i = Children.Count - 1; i >= 0; i--)
|
||||||
|
Children[i].Render(canvas, basePosition);
|
||||||
|
|
||||||
// Ensure the brush is ready
|
// Ensure the brush is ready
|
||||||
if (LayerBrush == null || LayerBrush?.BaseProperties?.PropertiesInitialized == false)
|
if (LayerBrush == null || LayerBrush?.BaseProperties?.PropertiesInitialized == false)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
RenderTimeline(Timeline, canvas, basePosition);
|
if (Timeline.IsFinished || LayerBrush?.BrushType != LayerBrushType.Regular)
|
||||||
foreach (Timeline extraTimeline in Timeline.ExtraTimelines.ToList())
|
|
||||||
RenderTimeline(extraTimeline, canvas, basePosition);
|
|
||||||
Timeline.ClearDelta();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Enable()
|
|
||||||
{
|
|
||||||
if (Enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
bool tryOrBreak = TryOrBreak(() => LayerBrush?.InternalEnable(), "Failed to enable layer brush");
|
|
||||||
if (!tryOrBreak)
|
|
||||||
return;
|
|
||||||
|
|
||||||
tryOrBreak = TryOrBreak(() =>
|
|
||||||
{
|
|
||||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
|
|
||||||
baseLayerEffect.InternalEnable();
|
|
||||||
}, "Failed to enable one or more effects");
|
|
||||||
if (!tryOrBreak)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Activate()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Deactivate()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Disable()
|
|
||||||
{
|
|
||||||
if (!Enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
LayerBrush?.InternalDisable();
|
|
||||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
|
|
||||||
baseLayerEffect.InternalDisable();
|
|
||||||
|
|
||||||
Enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyTimeline(Timeline timeline)
|
|
||||||
{
|
|
||||||
if (timeline.Delta == TimeSpan.Zero)
|
|
||||||
return;
|
|
||||||
|
|
||||||
General.Update(timeline);
|
|
||||||
Transform.Update(timeline);
|
|
||||||
LayerBrush?.InternalUpdate(timeline);
|
|
||||||
|
|
||||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
|
||||||
baseLayerEffect.InternalUpdate(timeline);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderTimeline(Timeline timeline, SKCanvas canvas, SKPointI basePosition)
|
|
||||||
{
|
|
||||||
if (Path == null || LayerBrush == null)
|
|
||||||
throw new ArtemisCoreException("The layer is not yet ready for rendering");
|
|
||||||
|
|
||||||
if (!Leds.Any() || timeline.IsFinished)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ApplyTimeline(timeline);
|
|
||||||
|
|
||||||
if (LayerBrush?.BrushType != LayerBrushType.Regular)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
|
SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
|
||||||
@ -501,34 +444,96 @@ namespace Artemis.Core
|
|||||||
canvas.Restore();
|
canvas.Restore();
|
||||||
layerPaint.DisposeSelfAndProperties();
|
layerPaint.DisposeSelfAndProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timeline.ClearDelta();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DelegateRendering(SKCanvas canvas, SKPath renderPath, SKRect bounds, SKPaint layerPaint)
|
/// <inheritdoc />
|
||||||
|
public override void Enable()
|
||||||
{
|
{
|
||||||
if (LayerBrush == null)
|
if (Enabled)
|
||||||
throw new ArtemisCoreException("The layer is not yet ready for rendering");
|
return;
|
||||||
|
|
||||||
|
bool tryOrBreak = TryOrBreak(() => LayerBrush?.InternalEnable(), "Failed to enable layer brush");
|
||||||
|
if (!tryOrBreak)
|
||||||
|
return;
|
||||||
|
|
||||||
|
tryOrBreak = TryOrBreak(() =>
|
||||||
|
{
|
||||||
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
|
||||||
|
baseLayerEffect.InternalEnable();
|
||||||
|
}, "Failed to enable one or more effects");
|
||||||
|
if (!tryOrBreak)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OverrideTimelineAndApply(TimeSpan position, bool stickToMainSegment)
|
||||||
|
{
|
||||||
|
Timeline.Override(position, stickToMainSegment);
|
||||||
|
|
||||||
|
General.Update(Timeline);
|
||||||
|
Transform.Update(Timeline);
|
||||||
|
LayerBrush?.InternalUpdate(Timeline);
|
||||||
|
|
||||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||||
baseLayerEffect.InternalPreProcess(canvas, bounds, layerPaint);
|
baseLayerEffect.InternalUpdate(Timeline);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
/// <inheritdoc />
|
||||||
|
public override void Activate()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Deactivate()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Disable()
|
||||||
|
{
|
||||||
|
if (!Enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LayerBrush?.InternalDisable();
|
||||||
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
|
||||||
|
baseLayerEffect.InternalDisable();
|
||||||
|
|
||||||
|
Enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Reset()
|
||||||
|
{
|
||||||
|
UpdateDisplayCondition();
|
||||||
|
|
||||||
|
if (DisplayConditionMet)
|
||||||
|
Timeline.JumpToStart();
|
||||||
|
else
|
||||||
|
Timeline.JumpToEnd();
|
||||||
|
|
||||||
|
while (Children.Any())
|
||||||
{
|
{
|
||||||
canvas.SaveLayer(layerPaint);
|
Children[0].Dispose();
|
||||||
canvas.ClipPath(renderPath);
|
RemoveChild(Children[0]);
|
||||||
|
|
||||||
// Restore the blend mode before doing the actual render
|
|
||||||
layerPaint.BlendMode = SKBlendMode.SrcOver;
|
|
||||||
|
|
||||||
LayerBrush.InternalRender(canvas, bounds, layerPaint);
|
|
||||||
|
|
||||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
|
||||||
baseLayerEffect.InternalPostProcess(canvas, bounds, layerPaint);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
finally
|
/// <summary>
|
||||||
{
|
/// Creates a copy of this layer as a child and plays it once
|
||||||
canvas.Restore();
|
/// </summary>
|
||||||
}
|
public void CreateCopyAsChild()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
|
||||||
|
// Create a copy of the layer and it's properties
|
||||||
|
|
||||||
|
// Add to children
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void CalculateRenderProperties()
|
internal void CalculateRenderProperties()
|
||||||
@ -582,6 +587,34 @@ namespace Artemis.Core
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DelegateRendering(SKCanvas canvas, SKPath renderPath, SKRect bounds, SKPaint layerPaint)
|
||||||
|
{
|
||||||
|
if (LayerBrush == null)
|
||||||
|
throw new ArtemisCoreException("The layer is not yet ready for rendering");
|
||||||
|
|
||||||
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||||
|
baseLayerEffect.InternalPreProcess(canvas, bounds, layerPaint);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
canvas.SaveLayer(layerPaint);
|
||||||
|
canvas.ClipPath(renderPath);
|
||||||
|
|
||||||
|
// Restore the blend mode before doing the actual render
|
||||||
|
layerPaint.BlendMode = SKBlendMode.SrcOver;
|
||||||
|
|
||||||
|
LayerBrush.InternalRender(canvas, bounds, layerPaint);
|
||||||
|
|
||||||
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||||
|
baseLayerEffect.InternalPostProcess(canvas, bounds, layerPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
canvas.Restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a transformation matrix that applies the current transformation settings
|
/// Creates a transformation matrix that applies the current transformation settings
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -49,6 +49,12 @@ namespace Artemis.Core
|
|||||||
Keyframes = new ReadOnlyCollection<LayerPropertyKeyframe<T>>(_keyframes);
|
Keyframes = new ReadOnlyCollection<LayerPropertyKeyframe<T>>(_keyframes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Path} - {CurrentValue} ({PropertyType})";
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -137,7 +137,7 @@ namespace Artemis.Core
|
|||||||
/// Updates the <see cref="Timeline" /> according to the provided <paramref name="deltaTime" /> and current display
|
/// Updates the <see cref="Timeline" /> according to the provided <paramref name="deltaTime" /> and current display
|
||||||
/// condition status
|
/// condition status
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdateTimeline(double deltaTime)
|
protected void UpdateTimeline(double deltaTime)
|
||||||
{
|
{
|
||||||
// TODO: Move to conditions
|
// TODO: Move to conditions
|
||||||
|
|
||||||
@ -327,7 +327,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
OrderEffects();
|
OrderEffects();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal void ActivateLayerEffect(BaseLayerEffect layerEffect)
|
internal void ActivateLayerEffect(BaseLayerEffect layerEffect)
|
||||||
{
|
{
|
||||||
@ -406,5 +406,12 @@ namespace Artemis.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the main timeline to the specified time and clears any extra time lines
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="position">The position to set the timeline to</param>
|
||||||
|
/// <param name="stickToMainSegment">Whether to stick to the main segment, wrapping around if needed</param>
|
||||||
|
public abstract void OverrideTimelineAndApply(TimeSpan position, bool stickToMainSegment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -11,7 +11,6 @@ namespace Artemis.Core;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class Timeline : CorePropertyChanged, IStorageModel
|
public class Timeline : CorePropertyChanged, IStorageModel
|
||||||
{
|
{
|
||||||
private const int MaxExtraTimelines = 15;
|
|
||||||
private readonly object _lock = new();
|
private readonly object _lock = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -21,78 +20,33 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
|||||||
{
|
{
|
||||||
Entity = new TimelineEntity();
|
Entity = new TimelineEntity();
|
||||||
MainSegmentLength = TimeSpan.FromSeconds(5);
|
MainSegmentLength = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
_extraTimelines = new List<Timeline>();
|
|
||||||
ExtraTimelines = new ReadOnlyCollection<Timeline>(_extraTimelines);
|
|
||||||
|
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Timeline(TimelineEntity entity)
|
internal Timeline(TimelineEntity entity)
|
||||||
{
|
{
|
||||||
Entity = entity;
|
Entity = entity;
|
||||||
_extraTimelines = new List<Timeline>();
|
|
||||||
ExtraTimelines = new ReadOnlyCollection<Timeline>(_extraTimelines);
|
|
||||||
|
|
||||||
Load();
|
Load();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Timeline(Timeline parent)
|
|
||||||
{
|
|
||||||
Entity = new TimelineEntity();
|
|
||||||
Parent = parent;
|
|
||||||
StartSegmentLength = Parent.StartSegmentLength;
|
|
||||||
MainSegmentLength = Parent.MainSegmentLength;
|
|
||||||
EndSegmentLength = Parent.EndSegmentLength;
|
|
||||||
|
|
||||||
_extraTimelines = new List<Timeline>();
|
|
||||||
ExtraTimelines = new ReadOnlyCollection<Timeline>(_extraTimelines);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"Progress: {Position}/{Length} - delta: {Delta}";
|
return $"Progress: {Position}/{Length} - delta: {Delta}";
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Extra timelines
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds an extra timeline to this timeline
|
|
||||||
/// </summary>
|
|
||||||
public void AddExtraTimeline()
|
|
||||||
{
|
|
||||||
_extraTimelines.Add(new Timeline(this));
|
|
||||||
if (_extraTimelines.Count > MaxExtraTimelines)
|
|
||||||
_extraTimelines.RemoveAt(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes all extra timelines from this timeline
|
|
||||||
/// </summary>
|
|
||||||
public void ClearExtraTimelines()
|
|
||||||
{
|
|
||||||
_extraTimelines.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
|
|
||||||
private TimeSpan _position;
|
private TimeSpan _position;
|
||||||
private TimeSpan _lastDelta;
|
private TimeSpan _lastDelta;
|
||||||
private TimelinePlayMode _playMode;
|
private TimelinePlayMode _playMode;
|
||||||
private TimelineStopMode _stopMode;
|
private TimelineStopMode _stopMode;
|
||||||
private readonly List<Timeline> _extraTimelines;
|
|
||||||
private TimeSpan _startSegmentLength;
|
private TimeSpan _startSegmentLength;
|
||||||
private TimeSpan _mainSegmentLength;
|
private TimeSpan _mainSegmentLength;
|
||||||
private TimeSpan _endSegmentLength;
|
private TimeSpan _endSegmentLength;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the parent this timeline is an extra timeline of
|
|
||||||
/// </summary>
|
|
||||||
public Timeline? Parent { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current position of the timeline
|
/// Gets the current position of the timeline
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -105,21 +59,13 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cumulative delta of all calls to <see cref="Update" /> that took place after the last call to
|
/// Gets the cumulative delta of all calls to <see cref="Update" /> that took place after the last call to
|
||||||
/// <see cref="ClearDelta" />
|
/// <see cref="ClearDelta" />
|
||||||
/// <para>
|
|
||||||
/// Note: If this is an extra timeline <see cref="Delta" /> is always equal to <see cref="DeltaToParent" />
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeSpan Delta
|
public TimeSpan Delta
|
||||||
{
|
{
|
||||||
get => Parent == null ? _lastDelta : DeltaToParent;
|
get => _lastDelta;
|
||||||
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>
|
||||||
@ -138,15 +84,10 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
|||||||
set => SetAndNotify(ref _stopMode, value);
|
set => SetAndNotify(ref _stopMode, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a list of extra copies of the timeline applied to this timeline
|
|
||||||
/// </summary>
|
|
||||||
public ReadOnlyCollection<Timeline> ExtraTimelines { get; }
|
|
||||||
|
|
||||||
/// <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 && !ExtraTimelines.Any();
|
public bool IsFinished => Position > Length;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a boolean indicating whether the timeline progress has been overridden
|
/// Gets a boolean indicating whether the timeline progress has been overridden
|
||||||
@ -327,19 +268,15 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
|||||||
IsOverridden = false;
|
IsOverridden = false;
|
||||||
_lastOverridePosition = Position;
|
_lastOverridePosition = Position;
|
||||||
|
|
||||||
if (stickToMainSegment && Position > MainSegmentEndPosition)
|
if (!stickToMainSegment || Position <= MainSegmentEndPosition)
|
||||||
{
|
return;
|
||||||
// If the main segment has no length, simply stick to the start of the segment
|
|
||||||
if (MainSegmentLength == TimeSpan.Zero)
|
|
||||||
Position = MainSegmentStartPosition;
|
|
||||||
// Ensure wrapping back around retains the delta time
|
|
||||||
else
|
|
||||||
Position = MainSegmentStartPosition + TimeSpan.FromMilliseconds(delta.TotalMilliseconds % MainSegmentLength.TotalMilliseconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
_extraTimelines.RemoveAll(t => t.IsFinished);
|
// If the main segment has no length, simply stick to the start of the segment
|
||||||
foreach (Timeline extraTimeline in _extraTimelines)
|
if (MainSegmentLength == TimeSpan.Zero)
|
||||||
extraTimeline.Update(delta, false);
|
Position = MainSegmentStartPosition;
|
||||||
|
// Ensure wrapping back around retains the delta time
|
||||||
|
else
|
||||||
|
Position = MainSegmentStartPosition + TimeSpan.FromMilliseconds(delta.TotalMilliseconds % MainSegmentLength.TotalMilliseconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,11 +326,11 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Overrides the <see cref="Position" /> to the specified time and clears any extra time lines
|
/// Overrides the <see cref="Position" /> to the specified time
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="position">The position to set the timeline to</param>
|
/// <param name="position">The position to set the timeline to</param>
|
||||||
/// <param name="stickToMainSegment">Whether to stick to the main segment, wrapping around if needed</param>
|
/// <param name="stickToMainSegment">Whether to stick to the main segment, wrapping around if needed</param>
|
||||||
public void Override(TimeSpan position, bool stickToMainSegment)
|
internal void Override(TimeSpan position, bool stickToMainSegment)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
@ -403,24 +340,22 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
|||||||
IsOverridden = true;
|
IsOverridden = true;
|
||||||
_lastOverridePosition = position;
|
_lastOverridePosition = position;
|
||||||
|
|
||||||
if (stickToMainSegment && Position >= MainSegmentStartPosition)
|
if (!stickToMainSegment || Position < MainSegmentStartPosition)
|
||||||
{
|
return;
|
||||||
bool atSegmentStart = Position == MainSegmentStartPosition;
|
|
||||||
if (MainSegmentLength > TimeSpan.Zero)
|
|
||||||
{
|
|
||||||
Position = MainSegmentStartPosition + TimeSpan.FromMilliseconds(Position.TotalMilliseconds % MainSegmentLength.TotalMilliseconds);
|
|
||||||
// If the cursor is at the end of the timeline we don't want to wrap back around yet so only allow going to the start if the cursor
|
|
||||||
// is actually at the start of the segment
|
|
||||||
if (Position == MainSegmentStartPosition && !atSegmentStart)
|
|
||||||
Position = MainSegmentEndPosition;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Position = MainSegmentStartPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_extraTimelines.Clear();
|
bool atSegmentStart = Position == MainSegmentStartPosition;
|
||||||
|
if (MainSegmentLength > TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
Position = MainSegmentStartPosition + TimeSpan.FromMilliseconds(Position.TotalMilliseconds % MainSegmentLength.TotalMilliseconds);
|
||||||
|
// If the cursor is at the end of the timeline we don't want to wrap back around yet so only allow going to the start if the cursor
|
||||||
|
// is actually at the start of the segment
|
||||||
|
if (Position == MainSegmentStartPosition && !atSegmentStart)
|
||||||
|
Position = MainSegmentEndPosition;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Position = MainSegmentStartPosition;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -146,10 +146,11 @@ namespace Artemis.UI.Shared.Services
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
renderElement.Enable();
|
renderElement.Enable();
|
||||||
renderElement.Timeline.Override(
|
renderElement.OverrideTimelineAndApply(
|
||||||
CurrentTime,
|
CurrentTime,
|
||||||
(renderElement != SelectedProfileElement || renderElement.Timeline.Length < CurrentTime) && renderElement.Timeline.PlayMode == TimelinePlayMode.Repeat
|
(renderElement != SelectedProfileElement || renderElement.Timeline.Length < CurrentTime) && renderElement.Timeline.PlayMode == TimelinePlayMode.Repeat
|
||||||
);
|
);
|
||||||
|
renderElement.Update(0);
|
||||||
|
|
||||||
foreach (ProfileElement child in renderElement.Children)
|
foreach (ProfileElement child in renderElement.Children)
|
||||||
TickProfileElement(child);
|
TickProfileElement(child);
|
||||||
|
|||||||
@ -79,10 +79,8 @@ internal class ProfileEditorService : IProfileEditorService
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
renderElement.Enable();
|
renderElement.Enable();
|
||||||
renderElement.Timeline.Override(
|
bool stickToMainSegment = (renderElement != _profileElementSubject.Value || renderElement.Timeline.Length < time) && renderElement.Timeline.PlayMode == TimelinePlayMode.Repeat;
|
||||||
time,
|
renderElement.OverrideTimelineAndApply(time, stickToMainSegment);
|
||||||
(renderElement != _profileElementSubject.Value || renderElement.Timeline.Length < time) && renderElement.Timeline.PlayMode == TimelinePlayMode.Repeat
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach (ProfileElement child in renderElement.Children)
|
foreach (ProfileElement child in renderElement.Children)
|
||||||
TickProfileElement(child, time);
|
TickProfileElement(child, time);
|
||||||
|
|||||||
@ -56,6 +56,9 @@
|
|||||||
<Compile Update="Screens\ProfileEditor\Panels\Properties\PropertiesView.axaml.cs">
|
<Compile Update="Screens\ProfileEditor\Panels\Properties\PropertiesView.axaml.cs">
|
||||||
<DependentUpon>PropertiesView.axaml</DependentUpon>
|
<DependentUpon>PropertiesView.axaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Update="Screens\ProfileEditor\Panels\VisualEditor\Visualizers\LayerShapeVisualizerView.axaml.cs">
|
||||||
|
<DependentUpon>LayerShapeVisualizerView.axaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="DefaultTypes\DataModel\Display\" />
|
<Folder Include="DefaultTypes\DataModel\Display\" />
|
||||||
|
|||||||
@ -65,6 +65,7 @@ namespace Artemis.UI.Ninject.Factories
|
|||||||
ProfileEditorViewModel ProfileEditorViewModel(IScreen hostScreen);
|
ProfileEditorViewModel ProfileEditorViewModel(IScreen hostScreen);
|
||||||
FolderTreeItemViewModel FolderTreeItemViewModel(TreeItemViewModel? parent, Folder folder);
|
FolderTreeItemViewModel FolderTreeItemViewModel(TreeItemViewModel? parent, Folder folder);
|
||||||
LayerTreeItemViewModel LayerTreeItemViewModel(TreeItemViewModel? parent, Layer layer);
|
LayerTreeItemViewModel LayerTreeItemViewModel(TreeItemViewModel? parent, Layer layer);
|
||||||
|
LayerShapeVisualizerViewModel LayerShapeVisualizerViewModel(Layer layer);
|
||||||
LayerVisualizerViewModel LayerVisualizerViewModel(Layer layer);
|
LayerVisualizerViewModel LayerVisualizerViewModel(Layer layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -48,6 +48,12 @@
|
|||||||
|
|
||||||
<!-- The middle layer contains visualizers -->
|
<!-- The middle layer contains visualizers -->
|
||||||
<ItemsControl Items="{CompiledBinding Visualizers}" ClipToBounds="False">
|
<ItemsControl Items="{CompiledBinding Visualizers}" ClipToBounds="False">
|
||||||
|
<ItemsControl.Styles>
|
||||||
|
<Style Selector="ContentPresenter">
|
||||||
|
<Setter Property="Canvas.Left" Value="{Binding X}" />
|
||||||
|
<Setter Property="Canvas.Top" Value="{Binding Y}" />
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.Styles>
|
||||||
<ItemsControl.ItemsPanel>
|
<ItemsControl.ItemsPanel>
|
||||||
<ItemsPanelTemplate>
|
<ItemsPanelTemplate>
|
||||||
<Canvas />
|
<Canvas />
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
@ -8,6 +9,8 @@ using Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools;
|
|||||||
using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
|
using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
|
using DynamicData;
|
||||||
|
using DynamicData.Binding;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor;
|
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor;
|
||||||
@ -16,14 +19,22 @@ public class VisualEditorViewModel : ActivatableViewModelBase
|
|||||||
{
|
{
|
||||||
private readonly IProfileEditorVmFactory _vmFactory;
|
private readonly IProfileEditorVmFactory _vmFactory;
|
||||||
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration;
|
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration;
|
||||||
|
private readonly SourceList<IVisualizerViewModel> _visualizers;
|
||||||
|
|
||||||
public VisualEditorViewModel(IProfileEditorService profileEditorService, IRgbService rgbService, IProfileEditorVmFactory vmFactory)
|
public VisualEditorViewModel(IProfileEditorService profileEditorService, IRgbService rgbService, IProfileEditorVmFactory vmFactory)
|
||||||
{
|
{
|
||||||
_vmFactory = vmFactory;
|
_vmFactory = vmFactory;
|
||||||
|
_visualizers = new SourceList<IVisualizerViewModel>();
|
||||||
|
|
||||||
Devices = new ObservableCollection<ArtemisDevice>(rgbService.EnabledDevices);
|
Devices = new ObservableCollection<ArtemisDevice>(rgbService.EnabledDevices);
|
||||||
Visualizers = new ObservableCollection<IVisualizerViewModel>();
|
|
||||||
Tools = new ObservableCollection<IToolViewModel>();
|
Tools = new ObservableCollection<IToolViewModel>();
|
||||||
|
|
||||||
|
_visualizers.Connect()
|
||||||
|
.Sort(SortExpressionComparer<IVisualizerViewModel>.Ascending(vm => vm.Order))
|
||||||
|
.Bind(out ReadOnlyObservableCollection<IVisualizerViewModel> visualizers)
|
||||||
|
.Subscribe();
|
||||||
|
Visualizers = visualizers;
|
||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
_profileConfiguration = profileEditorService.ProfileConfiguration.ToProperty(this, vm => vm.ProfileConfiguration).DisposeWith(d);
|
_profileConfiguration = profileEditorService.ProfileConfiguration.ToProperty(this, vm => vm.ProfileConfiguration).DisposeWith(d);
|
||||||
@ -34,21 +45,24 @@ public class VisualEditorViewModel : ActivatableViewModelBase
|
|||||||
public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value;
|
public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value;
|
||||||
|
|
||||||
public ObservableCollection<ArtemisDevice> Devices { get; }
|
public ObservableCollection<ArtemisDevice> Devices { get; }
|
||||||
public ObservableCollection<IVisualizerViewModel> Visualizers { get; set; }
|
public ReadOnlyObservableCollection<IVisualizerViewModel> Visualizers { get; }
|
||||||
public ObservableCollection<IToolViewModel> Tools { get; set; }
|
public ObservableCollection<IToolViewModel> Tools { get; }
|
||||||
|
|
||||||
private void CreateVisualizers(ProfileConfiguration? profileConfiguration)
|
private void CreateVisualizers(ProfileConfiguration? profileConfiguration)
|
||||||
{
|
{
|
||||||
Visualizers.Clear();
|
_visualizers.Edit(list =>
|
||||||
if (profileConfiguration?.Profile == null)
|
{
|
||||||
return;
|
list.Clear();
|
||||||
|
if (profileConfiguration?.Profile == null)
|
||||||
foreach (Layer layer in profileConfiguration.Profile.GetAllLayers())
|
return;
|
||||||
CreateVisualizer(layer);
|
foreach (Layer layer in profileConfiguration.Profile.GetAllLayers())
|
||||||
|
CreateVisualizer(list, layer);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateVisualizer(Layer layer)
|
private void CreateVisualizer(ICollection<IVisualizerViewModel> visualizerViewModels, Layer layer)
|
||||||
{
|
{
|
||||||
Visualizers.Add(_vmFactory.LayerVisualizerViewModel(layer));
|
visualizerViewModels.Add(_vmFactory.LayerShapeVisualizerViewModel(layer));
|
||||||
|
visualizerViewModels.Add(_vmFactory.LayerVisualizerViewModel(layer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,4 +2,7 @@
|
|||||||
|
|
||||||
public interface IVisualizerViewModel
|
public interface IVisualizerViewModel
|
||||||
{
|
{
|
||||||
|
int X { get; }
|
||||||
|
int Y { get; }
|
||||||
|
int Order { get; }
|
||||||
}
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:visualizers="clr-namespace:Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers.LayerShapeVisualizerView"
|
||||||
|
x:DataType="visualizers:LayerShapeVisualizerViewModel"
|
||||||
|
ClipToBounds="False"
|
||||||
|
ZIndex="2">
|
||||||
|
<UserControl.Styles>
|
||||||
|
<Style Selector="Path.layer-visualizer">
|
||||||
|
<Setter Property="Stroke" Value="{StaticResource ButtonBorderBrushDisabled}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Path.layer-visualizer-selected">
|
||||||
|
<Setter Property="Stroke" Value="{StaticResource SystemAccentColorLight1}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Path.layer-visualizer-unbound">
|
||||||
|
<Setter Property="Stroke" Value="{StaticResource ButtonBorderBrushDisabled}" />
|
||||||
|
<Setter Property="Opacity" Value="0.50" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Path.layer-visualizer-unbound-selected">
|
||||||
|
<Setter Property="Opacity" Value="0.75" />
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
<Canvas>
|
||||||
|
<Path Classes="layer-visualizer-unbound"
|
||||||
|
Classes.layer-visualizer-unbound-selected="{CompiledBinding Selected}"
|
||||||
|
Data="{CompiledBinding ShapeGeometry, Mode=OneWay}"
|
||||||
|
StrokeThickness="2"
|
||||||
|
Margin="0 0 2 2"
|
||||||
|
StrokeJoin="Round">
|
||||||
|
</Path>
|
||||||
|
<Border ClipToBounds="True"
|
||||||
|
Width="{CompiledBinding LayerBounds.Width}"
|
||||||
|
Height="{CompiledBinding LayerBounds.Height}">
|
||||||
|
<Path Classes="layer-visualizer"
|
||||||
|
Classes.layer-visualizer-selected="{CompiledBinding Selected}"
|
||||||
|
Data="{CompiledBinding ShapeGeometry, Mode=OneWay}"
|
||||||
|
StrokeThickness="2"
|
||||||
|
Margin="0 0 2 2"
|
||||||
|
StrokeJoin="Round">
|
||||||
|
</Path>
|
||||||
|
</Border>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers
|
||||||
|
{
|
||||||
|
public partial class LayerShapeVisualizerView : ReactiveUserControl<LayerShapeVisualizerViewModel>
|
||||||
|
{
|
||||||
|
public LayerShapeVisualizerView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Extensions;
|
||||||
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls.Mixins;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
|
||||||
|
|
||||||
|
public class LayerShapeVisualizerViewModel : ActivatableViewModelBase, IVisualizerViewModel
|
||||||
|
{
|
||||||
|
private ObservableAsPropertyHelper<bool>? _selected;
|
||||||
|
private Geometry? _shapeGeometry;
|
||||||
|
|
||||||
|
public LayerShapeVisualizerViewModel(Layer layer, IProfileEditorService profileEditorService)
|
||||||
|
{
|
||||||
|
Layer = layer;
|
||||||
|
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
Observable.FromEventPattern(x => Layer.RenderPropertiesUpdated += x, x => Layer.RenderPropertiesUpdated -= x).Subscribe(_ => Update()).DisposeWith(d);
|
||||||
|
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.Position.CurrentValueSet += x, x => Layer.Transform.Position.CurrentValueSet -= x)
|
||||||
|
.Subscribe(_ => UpdateTransform())
|
||||||
|
.DisposeWith(d);
|
||||||
|
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.Rotation.CurrentValueSet += x, x => Layer.Transform.Rotation.CurrentValueSet -= x)
|
||||||
|
.Subscribe(_ => UpdateTransform())
|
||||||
|
.DisposeWith(d);
|
||||||
|
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.Scale.CurrentValueSet += x, x => Layer.Transform.Scale.CurrentValueSet -= x)
|
||||||
|
.Subscribe(_ => UpdateTransform())
|
||||||
|
.DisposeWith(d);
|
||||||
|
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.AnchorPoint.CurrentValueSet += x, x => Layer.Transform.AnchorPoint.CurrentValueSet -= x)
|
||||||
|
.Subscribe(_ => UpdateTransform())
|
||||||
|
.DisposeWith(d);
|
||||||
|
|
||||||
|
_selected = profileEditorService.ProfileElement.Select(p => p == Layer).ToProperty(this, vm => vm.Selected).DisposeWith(d);
|
||||||
|
|
||||||
|
profileEditorService.Time.Subscribe(_ => UpdateTransform()).DisposeWith(d);
|
||||||
|
Update();
|
||||||
|
UpdateTransform();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Layer Layer { get; }
|
||||||
|
public bool Selected => _selected?.Value ?? false;
|
||||||
|
public Rect LayerBounds => Layer.Bounds.ToRect();
|
||||||
|
|
||||||
|
public Geometry? ShapeGeometry
|
||||||
|
{
|
||||||
|
get => _shapeGeometry;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _shapeGeometry, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (Layer.General.ShapeType.CurrentValue == LayerShapeType.Rectangle)
|
||||||
|
ShapeGeometry = new RectangleGeometry(new Rect(0, 0, Layer.Bounds.Width, Layer.Bounds.Height));
|
||||||
|
else
|
||||||
|
ShapeGeometry = new EllipseGeometry(new Rect(0, 0, Layer.Bounds.Width, Layer.Bounds.Height));
|
||||||
|
|
||||||
|
this.RaisePropertyChanged(nameof(X));
|
||||||
|
this.RaisePropertyChanged(nameof(Y));
|
||||||
|
this.RaisePropertyChanged(nameof(LayerBounds));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTransform()
|
||||||
|
{
|
||||||
|
if (ShapeGeometry != null)
|
||||||
|
ShapeGeometry.Transform = new MatrixTransform(Layer.GetTransformMatrix(true, true, true, true).ToMatrix());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int X => Layer.Bounds.Left;
|
||||||
|
public int Y => Layer.Bounds.Top;
|
||||||
|
public int Order => 2;
|
||||||
|
}
|
||||||
@ -5,19 +5,24 @@
|
|||||||
xmlns:visualizers="clr-namespace:Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers"
|
xmlns:visualizers="clr-namespace:Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers.LayerVisualizerView"
|
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers.LayerVisualizerView"
|
||||||
x:DataType="visualizers:LayerVisualizerViewModel">
|
x:DataType="visualizers:LayerVisualizerViewModel"
|
||||||
|
ZIndex="1">
|
||||||
<UserControl.Styles>
|
<UserControl.Styles>
|
||||||
<Style Selector="Path.layer-visualizer">
|
<Style Selector="Path.layer-visualizer">
|
||||||
<Setter Property="Stroke" Value="{StaticResource ButtonBorderBrushDisabled}" />
|
<Setter Property="Stroke" Value="{StaticResource ButtonBorderBrushDisabled}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Path.layer-visualizer-selected">
|
<Style Selector="Path.layer-visualizer-selected">
|
||||||
<Setter Property="Stroke" Value="{StaticResource SystemAccentColorLight1}" />
|
<Setter Property="Stroke" Value="{StaticResource SystemAccentColorDark2}" />
|
||||||
</Style>
|
</Style>
|
||||||
</UserControl.Styles>
|
</UserControl.Styles>
|
||||||
<Path Classes="layer-visualizer"
|
<Path Classes="layer-visualizer"
|
||||||
Classes.layer-visualizer-selected="{CompiledBinding Selected}"
|
Classes.layer-visualizer-selected="{CompiledBinding Selected}"
|
||||||
Data="{CompiledBinding ShapeGeometry, Mode=OneWay}"
|
|
||||||
StrokeThickness="2"
|
StrokeThickness="2"
|
||||||
Margin="0 0 2 2">
|
Margin="0 0 2 2"
|
||||||
|
StrokeDashArray="6,2"
|
||||||
|
StrokeJoin="Round">
|
||||||
|
<Path.Data>
|
||||||
|
<RectangleGeometry Rect="{CompiledBinding LayerBounds}"></RectangleGeometry>
|
||||||
|
</Path.Data>
|
||||||
</Path>
|
</Path>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@ -2,66 +2,44 @@
|
|||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Extensions;
|
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.Controls.Mixins;
|
using Avalonia.Controls.Mixins;
|
||||||
using Avalonia.Media;
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
|
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
|
||||||
|
|
||||||
public class LayerVisualizerViewModel : ActivatableViewModelBase, IVisualizerViewModel
|
public class LayerVisualizerViewModel : ActivatableViewModelBase, IVisualizerViewModel
|
||||||
{
|
{
|
||||||
private Geometry? _shapeGeometry;
|
|
||||||
private ObservableAsPropertyHelper<bool>? _selected;
|
private ObservableAsPropertyHelper<bool>? _selected;
|
||||||
|
|
||||||
public LayerVisualizerViewModel(Layer layer, IProfileEditorService profileEditorService)
|
public LayerVisualizerViewModel(Layer layer, IProfileEditorService profileEditorService)
|
||||||
{
|
{
|
||||||
Layer = layer;
|
Layer = layer;
|
||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
Observable.FromEventPattern(x => Layer.RenderPropertiesUpdated += x, x => Layer.RenderPropertiesUpdated -= x).Subscribe(_ => UpdateShape()).DisposeWith(d);
|
Observable.FromEventPattern(x => Layer.RenderPropertiesUpdated += x, x => Layer.RenderPropertiesUpdated -= x)
|
||||||
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.Position.CurrentValueSet += x, x => Layer.Transform.Position.CurrentValueSet -= x)
|
.Subscribe(_ => Update())
|
||||||
.Subscribe(_ => UpdateTransform())
|
|
||||||
.DisposeWith(d);
|
.DisposeWith(d);
|
||||||
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.Rotation.CurrentValueSet += x, x => Layer.Transform.Rotation.CurrentValueSet -= x)
|
_selected = profileEditorService.ProfileElement
|
||||||
.Subscribe(_ => UpdateTransform())
|
.Select(p => p == Layer)
|
||||||
|
.ToProperty(this, vm => vm.Selected)
|
||||||
.DisposeWith(d);
|
.DisposeWith(d);
|
||||||
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.Scale.CurrentValueSet += x, x => Layer.Transform.Scale.CurrentValueSet -= x)
|
|
||||||
.Subscribe(_ => UpdateTransform())
|
|
||||||
.DisposeWith(d);
|
|
||||||
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.AnchorPoint.CurrentValueSet += x, x => Layer.Transform.AnchorPoint.CurrentValueSet -= x)
|
|
||||||
.Subscribe(_ => UpdateTransform())
|
|
||||||
.DisposeWith(d);
|
|
||||||
|
|
||||||
_selected = profileEditorService.ProfileElement.Select(p => p == Layer).ToProperty(this, vm => vm.Selected).DisposeWith(d);
|
|
||||||
|
|
||||||
profileEditorService.Time.Subscribe(_ => UpdateTransform()).DisposeWith(d);
|
|
||||||
UpdateShape();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Layer Layer { get; }
|
public Layer Layer { get; }
|
||||||
public bool Selected => _selected?.Value ?? false;
|
public bool Selected => _selected?.Value ?? false;
|
||||||
|
public Rect LayerBounds => new(0, 0, Layer.Bounds.Width, Layer.Bounds.Height);
|
||||||
|
|
||||||
public Geometry? ShapeGeometry
|
private void Update()
|
||||||
{
|
{
|
||||||
get => _shapeGeometry;
|
this.RaisePropertyChanged(nameof(X));
|
||||||
set => this.RaiseAndSetIfChanged(ref _shapeGeometry, value);
|
this.RaisePropertyChanged(nameof(Y));
|
||||||
|
this.RaisePropertyChanged(nameof(LayerBounds));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateShape()
|
public int X => Layer.Bounds.Left;
|
||||||
{
|
public int Y => Layer.Bounds.Top;
|
||||||
if (Layer.General.ShapeType.CurrentValue == LayerShapeType.Rectangle)
|
public int Order => 1;
|
||||||
ShapeGeometry = new RectangleGeometry(Layer.Bounds.ToRect());
|
|
||||||
else
|
|
||||||
ShapeGeometry = new EllipseGeometry(Layer.Bounds.ToRect());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateTransform()
|
|
||||||
{
|
|
||||||
if (ShapeGeometry != null)
|
|
||||||
ShapeGeometry.Transform = new MatrixTransform(Layer.GetTransformMatrix(false, true, true, true).ToMatrix());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user