mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +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 (EventOverlapMode == TimeLineEventOverlapMode.Restart)
|
||||
timeline.JumpToStart();
|
||||
else if (EventOverlapMode == TimeLineEventOverlapMode.Copy)
|
||||
timeline.AddExtraTimeline();
|
||||
else if (EventOverlapMode == TimeLineEventOverlapMode.Copy && ProfileElement is Layer layer)
|
||||
layer.CreateCopyAsChild();
|
||||
else if (EventOverlapMode == TimeLineEventOverlapMode.Toggle && !wasMet)
|
||||
timeline.JumpToStart();
|
||||
|
||||
|
||||
@ -102,6 +102,9 @@ namespace Artemis.Core
|
||||
else if (Timeline.IsFinished)
|
||||
Disable();
|
||||
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||
baseLayerEffect.InternalUpdate(Timeline);
|
||||
|
||||
foreach (ProfileElement child in Children)
|
||||
child.Update(deltaTime);
|
||||
}
|
||||
@ -176,41 +179,35 @@ namespace Artemis.Core
|
||||
// No point rendering if all children are disabled
|
||||
if (!Children.Any(c => c is RenderProfileElement {Enabled: true}))
|
||||
return;
|
||||
|
||||
lock (Timeline)
|
||||
|
||||
SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
|
||||
try
|
||||
{
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||
baseLayerEffect.InternalUpdate(Timeline);
|
||||
SKRectI rendererBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height);
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||
baseLayerEffect.InternalPreProcess(canvas, rendererBounds, layerPaint);
|
||||
|
||||
SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
|
||||
try
|
||||
{
|
||||
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
|
||||
if (layerPaint.Color.Alpha == 0)
|
||||
return;
|
||||
|
||||
// No point rendering if the alpha was set to zero by one of the effects
|
||||
if (layerPaint.Color.Alpha == 0)
|
||||
return;
|
||||
canvas.SaveLayer(layerPaint);
|
||||
canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y);
|
||||
|
||||
canvas.SaveLayer(layerPaint);
|
||||
canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y);
|
||||
// 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--)
|
||||
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
|
||||
for (int index = Children.Count - 1; index > -1; index--)
|
||||
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();
|
||||
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
|
||||
baseLayerEffect.InternalPostProcess(canvas, rendererBounds, layerPaint);
|
||||
}
|
||||
finally
|
||||
{
|
||||
canvas.Restore();
|
||||
layerPaint.DisposeSelfAndProperties();
|
||||
}
|
||||
|
||||
Timeline.ClearDelta();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -233,6 +230,14 @@ namespace Artemis.Core
|
||||
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 />
|
||||
public override void Disable()
|
||||
{
|
||||
@ -314,7 +319,7 @@ namespace Artemis.Core
|
||||
ChildrenList.Add(new Layer(Profile, this, childLayer));
|
||||
|
||||
// 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++)
|
||||
ChildrenList[index].Order = index + 1;
|
||||
|
||||
|
||||
@ -345,17 +345,30 @@ namespace Artemis.Core
|
||||
Enable();
|
||||
else if (Timeline.IsFinished)
|
||||
Disable();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Reset()
|
||||
{
|
||||
UpdateDisplayCondition();
|
||||
if (Timeline.Delta == TimeSpan.Zero)
|
||||
return;
|
||||
|
||||
if (DisplayConditionMet)
|
||||
Timeline.JumpToStart();
|
||||
else
|
||||
Timeline.JumpToEnd();
|
||||
General.Update(Timeline);
|
||||
Transform.Update(Timeline);
|
||||
LayerBrush?.InternalUpdate(Timeline);
|
||||
|
||||
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 />
|
||||
@ -365,88 +378,18 @@ namespace Artemis.Core
|
||||
throw new ObjectDisposedException("Layer");
|
||||
|
||||
// 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;
|
||||
|
||||
// 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
|
||||
if (LayerBrush == null || LayerBrush?.BaseProperties?.PropertiesInitialized == false)
|
||||
return;
|
||||
|
||||
RenderTimeline(Timeline, canvas, basePosition);
|
||||
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)
|
||||
if (Timeline.IsFinished || LayerBrush?.BrushType != LayerBrushType.Regular)
|
||||
return;
|
||||
|
||||
SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
|
||||
@ -501,34 +444,96 @@ namespace Artemis.Core
|
||||
canvas.Restore();
|
||||
layerPaint.DisposeSelfAndProperties();
|
||||
}
|
||||
|
||||
Timeline.ClearDelta();
|
||||
}
|
||||
|
||||
private void DelegateRendering(SKCanvas canvas, SKPath renderPath, SKRect bounds, SKPaint layerPaint)
|
||||
/// <inheritdoc />
|
||||
public override void Enable()
|
||||
{
|
||||
if (LayerBrush == null)
|
||||
throw new ArtemisCoreException("The layer is not yet ready for rendering");
|
||||
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 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))
|
||||
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);
|
||||
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);
|
||||
Children[0].Dispose();
|
||||
RemoveChild(Children[0]);
|
||||
}
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
canvas.Restore();
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a copy of this layer as a child and plays it once
|
||||
/// </summary>
|
||||
public void CreateCopyAsChild()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
// Create a copy of the layer and it's properties
|
||||
|
||||
// Add to children
|
||||
}
|
||||
|
||||
internal void CalculateRenderProperties()
|
||||
@ -582,6 +587,34 @@ namespace Artemis.Core
|
||||
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>
|
||||
/// Creates a transformation matrix that applies the current transformation settings
|
||||
/// </summary>
|
||||
|
||||
@ -49,6 +49,12 @@ namespace Artemis.Core
|
||||
Keyframes = new ReadOnlyCollection<LayerPropertyKeyframe<T>>(_keyframes);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Path} - {CurrentValue} ({PropertyType})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
|
||||
@ -137,7 +137,7 @@ namespace Artemis.Core
|
||||
/// Updates the <see cref="Timeline" /> according to the provided <paramref name="deltaTime" /> and current display
|
||||
/// condition status
|
||||
/// </summary>
|
||||
public void UpdateTimeline(double deltaTime)
|
||||
protected void UpdateTimeline(double deltaTime)
|
||||
{
|
||||
// TODO: Move to conditions
|
||||
|
||||
@ -327,7 +327,7 @@ namespace Artemis.Core
|
||||
|
||||
OrderEffects();
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal void ActivateLayerEffect(BaseLayerEffect layerEffect)
|
||||
{
|
||||
@ -406,5 +406,12 @@ namespace Artemis.Core
|
||||
}
|
||||
|
||||
#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>
|
||||
public class Timeline : CorePropertyChanged, IStorageModel
|
||||
{
|
||||
private const int MaxExtraTimelines = 15;
|
||||
private readonly object _lock = new();
|
||||
|
||||
/// <summary>
|
||||
@ -21,78 +20,33 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
||||
{
|
||||
Entity = new TimelineEntity();
|
||||
MainSegmentLength = TimeSpan.FromSeconds(5);
|
||||
|
||||
_extraTimelines = new List<Timeline>();
|
||||
ExtraTimelines = new ReadOnlyCollection<Timeline>(_extraTimelines);
|
||||
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
internal Timeline(TimelineEntity entity)
|
||||
{
|
||||
Entity = entity;
|
||||
_extraTimelines = new List<Timeline>();
|
||||
ExtraTimelines = new ReadOnlyCollection<Timeline>(_extraTimelines);
|
||||
|
||||
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 />
|
||||
public override string ToString()
|
||||
{
|
||||
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
|
||||
|
||||
private TimeSpan _position;
|
||||
private TimeSpan _lastDelta;
|
||||
private TimelinePlayMode _playMode;
|
||||
private TimelineStopMode _stopMode;
|
||||
private readonly List<Timeline> _extraTimelines;
|
||||
private TimeSpan _startSegmentLength;
|
||||
private TimeSpan _mainSegmentLength;
|
||||
private TimeSpan _endSegmentLength;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent this timeline is an extra timeline of
|
||||
/// </summary>
|
||||
public Timeline? Parent { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current position of the timeline
|
||||
/// </summary>
|
||||
@ -105,21 +59,13 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
||||
/// <summary>
|
||||
/// Gets the cumulative delta of all calls to <see cref="Update" /> that took place after the last call to
|
||||
/// <see cref="ClearDelta" />
|
||||
/// <para>
|
||||
/// Note: If this is an extra timeline <see cref="Delta" /> is always equal to <see cref="DeltaToParent" />
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public TimeSpan Delta
|
||||
{
|
||||
get => Parent == null ? _lastDelta : DeltaToParent;
|
||||
get => _lastDelta;
|
||||
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>
|
||||
/// Gets or sets the mode in which the render element starts its timeline when display conditions are met
|
||||
/// </summary>
|
||||
@ -138,15 +84,10 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
||||
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>
|
||||
/// Gets a boolean indicating whether the timeline has finished its run
|
||||
/// </summary>
|
||||
public bool IsFinished => Position > Length && !ExtraTimelines.Any();
|
||||
public bool IsFinished => Position > Length;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the timeline progress has been overridden
|
||||
@ -327,19 +268,15 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
||||
IsOverridden = false;
|
||||
_lastOverridePosition = Position;
|
||||
|
||||
if (stickToMainSegment && Position > MainSegmentEndPosition)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
if (!stickToMainSegment || Position <= MainSegmentEndPosition)
|
||||
return;
|
||||
|
||||
_extraTimelines.RemoveAll(t => t.IsFinished);
|
||||
foreach (Timeline extraTimeline in _extraTimelines)
|
||||
extraTimeline.Update(delta, false);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,11 +326,11 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="position">The position to set the timeline to</param>
|
||||
/// <param name="stickToMainSegment">Whether to stick to the main segment, wrapping around if needed</param>
|
||||
public void Override(TimeSpan position, bool stickToMainSegment)
|
||||
internal void Override(TimeSpan position, bool stickToMainSegment)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
@ -403,24 +340,22 @@ public class Timeline : CorePropertyChanged, IStorageModel
|
||||
IsOverridden = true;
|
||||
_lastOverridePosition = position;
|
||||
|
||||
if (stickToMainSegment && Position >= MainSegmentStartPosition)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (!stickToMainSegment || Position < MainSegmentStartPosition)
|
||||
return;
|
||||
|
||||
_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
|
||||
{
|
||||
renderElement.Enable();
|
||||
renderElement.Timeline.Override(
|
||||
renderElement.OverrideTimelineAndApply(
|
||||
CurrentTime,
|
||||
(renderElement != SelectedProfileElement || renderElement.Timeline.Length < CurrentTime) && renderElement.Timeline.PlayMode == TimelinePlayMode.Repeat
|
||||
);
|
||||
renderElement.Update(0);
|
||||
|
||||
foreach (ProfileElement child in renderElement.Children)
|
||||
TickProfileElement(child);
|
||||
|
||||
@ -79,10 +79,8 @@ internal class ProfileEditorService : IProfileEditorService
|
||||
else
|
||||
{
|
||||
renderElement.Enable();
|
||||
renderElement.Timeline.Override(
|
||||
time,
|
||||
(renderElement != _profileElementSubject.Value || renderElement.Timeline.Length < time) && renderElement.Timeline.PlayMode == TimelinePlayMode.Repeat
|
||||
);
|
||||
bool stickToMainSegment = (renderElement != _profileElementSubject.Value || renderElement.Timeline.Length < time) && renderElement.Timeline.PlayMode == TimelinePlayMode.Repeat;
|
||||
renderElement.OverrideTimelineAndApply(time, stickToMainSegment);
|
||||
|
||||
foreach (ProfileElement child in renderElement.Children)
|
||||
TickProfileElement(child, time);
|
||||
|
||||
@ -56,6 +56,9 @@
|
||||
<Compile Update="Screens\ProfileEditor\Panels\Properties\PropertiesView.axaml.cs">
|
||||
<DependentUpon>PropertiesView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Screens\ProfileEditor\Panels\VisualEditor\Visualizers\LayerShapeVisualizerView.axaml.cs">
|
||||
<DependentUpon>LayerShapeVisualizerView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="DefaultTypes\DataModel\Display\" />
|
||||
|
||||
@ -65,6 +65,7 @@ namespace Artemis.UI.Ninject.Factories
|
||||
ProfileEditorViewModel ProfileEditorViewModel(IScreen hostScreen);
|
||||
FolderTreeItemViewModel FolderTreeItemViewModel(TreeItemViewModel? parent, Folder folder);
|
||||
LayerTreeItemViewModel LayerTreeItemViewModel(TreeItemViewModel? parent, Layer layer);
|
||||
LayerShapeVisualizerViewModel LayerShapeVisualizerViewModel(Layer layer);
|
||||
LayerVisualizerViewModel LayerVisualizerViewModel(Layer layer);
|
||||
}
|
||||
|
||||
|
||||
@ -48,6 +48,12 @@
|
||||
|
||||
<!-- The middle layer contains visualizers -->
|
||||
<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>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas />
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive.Disposables;
|
||||
using Artemis.Core;
|
||||
@ -8,6 +9,8 @@ using Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools;
|
||||
using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor;
|
||||
@ -16,14 +19,22 @@ public class VisualEditorViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly IProfileEditorVmFactory _vmFactory;
|
||||
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration;
|
||||
private readonly SourceList<IVisualizerViewModel> _visualizers;
|
||||
|
||||
public VisualEditorViewModel(IProfileEditorService profileEditorService, IRgbService rgbService, IProfileEditorVmFactory vmFactory)
|
||||
{
|
||||
_vmFactory = vmFactory;
|
||||
_visualizers = new SourceList<IVisualizerViewModel>();
|
||||
|
||||
Devices = new ObservableCollection<ArtemisDevice>(rgbService.EnabledDevices);
|
||||
Visualizers = new ObservableCollection<IVisualizerViewModel>();
|
||||
Tools = new ObservableCollection<IToolViewModel>();
|
||||
|
||||
_visualizers.Connect()
|
||||
.Sort(SortExpressionComparer<IVisualizerViewModel>.Ascending(vm => vm.Order))
|
||||
.Bind(out ReadOnlyObservableCollection<IVisualizerViewModel> visualizers)
|
||||
.Subscribe();
|
||||
Visualizers = visualizers;
|
||||
|
||||
this.WhenActivated(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 ObservableCollection<ArtemisDevice> Devices { get; }
|
||||
public ObservableCollection<IVisualizerViewModel> Visualizers { get; set; }
|
||||
public ObservableCollection<IToolViewModel> Tools { get; set; }
|
||||
public ReadOnlyObservableCollection<IVisualizerViewModel> Visualizers { get; }
|
||||
public ObservableCollection<IToolViewModel> Tools { get; }
|
||||
|
||||
private void CreateVisualizers(ProfileConfiguration? profileConfiguration)
|
||||
{
|
||||
Visualizers.Clear();
|
||||
if (profileConfiguration?.Profile == null)
|
||||
return;
|
||||
|
||||
foreach (Layer layer in profileConfiguration.Profile.GetAllLayers())
|
||||
CreateVisualizer(layer);
|
||||
_visualizers.Edit(list =>
|
||||
{
|
||||
list.Clear();
|
||||
if (profileConfiguration?.Profile == null)
|
||||
return;
|
||||
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
|
||||
{
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers.LayerVisualizerView"
|
||||
x:DataType="visualizers:LayerVisualizerViewModel">
|
||||
x:DataType="visualizers:LayerVisualizerViewModel"
|
||||
ZIndex="1">
|
||||
<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}" />
|
||||
<Setter Property="Stroke" Value="{StaticResource SystemAccentColorDark2}" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
<Path Classes="layer-visualizer"
|
||||
Classes.layer-visualizer-selected="{CompiledBinding Selected}"
|
||||
Data="{CompiledBinding ShapeGeometry, Mode=OneWay}"
|
||||
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>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
||||
@ -2,66 +2,44 @@
|
||||
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 LayerVisualizerViewModel : ActivatableViewModelBase, IVisualizerViewModel
|
||||
{
|
||||
private Geometry? _shapeGeometry;
|
||||
private ObservableAsPropertyHelper<bool>? _selected;
|
||||
|
||||
public LayerVisualizerViewModel(Layer layer, IProfileEditorService profileEditorService)
|
||||
{
|
||||
Layer = layer;
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
Observable.FromEventPattern(x => Layer.RenderPropertiesUpdated += x, x => Layer.RenderPropertiesUpdated -= x).Subscribe(_ => UpdateShape()).DisposeWith(d);
|
||||
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.Position.CurrentValueSet += x, x => Layer.Transform.Position.CurrentValueSet -= x)
|
||||
.Subscribe(_ => UpdateTransform())
|
||||
Observable.FromEventPattern(x => Layer.RenderPropertiesUpdated += x, x => Layer.RenderPropertiesUpdated -= x)
|
||||
.Subscribe(_ => Update())
|
||||
.DisposeWith(d);
|
||||
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.Rotation.CurrentValueSet += x, x => Layer.Transform.Rotation.CurrentValueSet -= x)
|
||||
.Subscribe(_ => UpdateTransform())
|
||||
_selected = profileEditorService.ProfileElement
|
||||
.Select(p => p == Layer)
|
||||
.ToProperty(this, vm => vm.Selected)
|
||||
.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 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;
|
||||
set => this.RaiseAndSetIfChanged(ref _shapeGeometry, value);
|
||||
this.RaisePropertyChanged(nameof(X));
|
||||
this.RaisePropertyChanged(nameof(Y));
|
||||
this.RaisePropertyChanged(nameof(LayerBounds));
|
||||
}
|
||||
|
||||
private void UpdateShape()
|
||||
{
|
||||
if (Layer.General.ShapeType.CurrentValue == LayerShapeType.Rectangle)
|
||||
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());
|
||||
}
|
||||
public int X => Layer.Bounds.Left;
|
||||
public int Y => Layer.Bounds.Top;
|
||||
public int Order => 1;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user