diff --git a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs
index 0ba1f5a86..cd4d99794 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs
@@ -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();
diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs
index 52296fb74..dd00ed756 100644
--- a/src/Artemis.Core/Models/Profile/Folder.cs
+++ b/src/Artemis.Core/Models/Profile/Folder.cs
@@ -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;
}
+ ///
+ public override void OverrideTimelineAndApply(TimeSpan position, bool stickToMainSegment)
+ {
+ Timeline.Override(position, stickToMainSegment);
+ foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
+ baseLayerEffect.InternalUpdate(Timeline);
+ }
+
///
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;
diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs
index 4eadc28c6..8b87d2fc5 100644
--- a/src/Artemis.Core/Models/Profile/Layer.cs
+++ b/src/Artemis.Core/Models/Profile/Layer.cs
@@ -345,17 +345,30 @@ namespace Artemis.Core
Enable();
else if (Timeline.IsFinished)
Disable();
- }
- ///
- 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);
+ }
}
///
@@ -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();
- }
-
- ///
- 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;
- }
-
- ///
- public override void Activate()
- {
- throw new NotImplementedException();
- }
-
- ///
- public override void Deactivate()
- {
- throw new NotImplementedException();
- }
-
- ///
- 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)
+ ///
+ 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;
+ }
+
+ ///
+ 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
+ ///
+ public override void Activate()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ public override void Deactivate()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ public override void Disable()
+ {
+ if (!Enabled)
+ return;
+
+ LayerBrush?.InternalDisable();
+ foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
+ baseLayerEffect.InternalDisable();
+
+ Enabled = false;
+ }
+
+ ///
+ 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();
- }
+ ///
+ /// Creates a copy of this layer as a child and plays it once
+ ///
+ 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();
+ }
+ }
+
///
/// Creates a transformation matrix that applies the current transformation settings
///
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
index 1abd9ecba..6bdf1fd6d 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
@@ -49,6 +49,12 @@ namespace Artemis.Core
Keyframes = new ReadOnlyCollection>(_keyframes);
}
+ ///
+ public override string ToString()
+ {
+ return $"{Path} - {CurrentValue} ({PropertyType})";
+ }
+
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs
index be2ec4a2b..85aec916d 100644
--- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs
+++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs
@@ -137,7 +137,7 @@ namespace Artemis.Core
/// Updates the according to the provided and current display
/// condition status
///
- 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
+
+ ///
+ /// Overrides the main timeline to the specified time and clears any extra time lines
+ ///
+ /// The position to set the timeline to
+ /// Whether to stick to the main segment, wrapping around if needed
+ public abstract void OverrideTimelineAndApply(TimeSpan position, bool stickToMainSegment);
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Timeline.cs b/src/Artemis.Core/Models/Profile/Timeline.cs
index ef4bf2740..4e17d746f 100644
--- a/src/Artemis.Core/Models/Profile/Timeline.cs
+++ b/src/Artemis.Core/Models/Profile/Timeline.cs
@@ -11,7 +11,6 @@ namespace Artemis.Core;
///
public class Timeline : CorePropertyChanged, IStorageModel
{
- private const int MaxExtraTimelines = 15;
private readonly object _lock = new();
///
@@ -21,78 +20,33 @@ public class Timeline : CorePropertyChanged, IStorageModel
{
Entity = new TimelineEntity();
MainSegmentLength = TimeSpan.FromSeconds(5);
-
- _extraTimelines = new List();
- ExtraTimelines = new ReadOnlyCollection(_extraTimelines);
-
+
Save();
}
internal Timeline(TimelineEntity entity)
{
Entity = entity;
- _extraTimelines = new List();
- ExtraTimelines = new ReadOnlyCollection(_extraTimelines);
Load();
}
- private Timeline(Timeline parent)
- {
- Entity = new TimelineEntity();
- Parent = parent;
- StartSegmentLength = Parent.StartSegmentLength;
- MainSegmentLength = Parent.MainSegmentLength;
- EndSegmentLength = Parent.EndSegmentLength;
-
- _extraTimelines = new List();
- ExtraTimelines = new ReadOnlyCollection(_extraTimelines);
- }
-
///
public override string ToString()
{
return $"Progress: {Position}/{Length} - delta: {Delta}";
}
-
- #region Extra timelines
-
- ///
- /// Adds an extra timeline to this timeline
- ///
- public void AddExtraTimeline()
- {
- _extraTimelines.Add(new Timeline(this));
- if (_extraTimelines.Count > MaxExtraTimelines)
- _extraTimelines.RemoveAt(0);
- }
-
- ///
- /// Removes all extra timelines from this timeline
- ///
- public void ClearExtraTimelines()
- {
- _extraTimelines.Clear();
- }
-
- #endregion
-
+
#region Properties
private TimeSpan _position;
private TimeSpan _lastDelta;
private TimelinePlayMode _playMode;
private TimelineStopMode _stopMode;
- private readonly List _extraTimelines;
private TimeSpan _startSegmentLength;
private TimeSpan _mainSegmentLength;
private TimeSpan _endSegmentLength;
-
- ///
- /// Gets the parent this timeline is an extra timeline of
- ///
- public Timeline? Parent { get; }
-
+
///
/// Gets the current position of the timeline
///
@@ -105,21 +59,13 @@ public class Timeline : CorePropertyChanged, IStorageModel
///
/// Gets the cumulative delta of all calls to that took place after the last call to
///
- ///
- /// Note: If this is an extra timeline is always equal to
- ///
///
public TimeSpan Delta
{
- get => Parent == null ? _lastDelta : DeltaToParent;
+ get => _lastDelta;
private set => SetAndNotify(ref _lastDelta, value);
}
-
- ///
- /// Gets the delta to this timeline's
- ///
- public TimeSpan DeltaToParent => Parent != null ? Position - Parent.Position : TimeSpan.Zero;
-
+
///
/// Gets or sets the mode in which the render element starts its timeline when display conditions are met
///
@@ -138,15 +84,10 @@ public class Timeline : CorePropertyChanged, IStorageModel
set => SetAndNotify(ref _stopMode, value);
}
- ///
- /// Gets a list of extra copies of the timeline applied to this timeline
- ///
- public ReadOnlyCollection ExtraTimelines { get; }
-
///
/// Gets a boolean indicating whether the timeline has finished its run
///
- public bool IsFinished => Position > Length && !ExtraTimelines.Any();
+ public bool IsFinished => Position > Length;
///
/// 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
}
///
- /// Overrides the to the specified time and clears any extra time lines
+ /// Overrides the to the specified time
///
/// The position to set the timeline to
/// Whether to stick to the main segment, wrapping around if needed
- 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;
+ }
}
}
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
index 19118d88f..68d1782f0 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
@@ -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);
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
index 1c52211dd..858fb5427 100644
--- a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
@@ -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);
diff --git a/src/Avalonia/Artemis.UI/Artemis.UI.csproj b/src/Avalonia/Artemis.UI/Artemis.UI.csproj
index 0de0c2b5d..f53a9a426 100644
--- a/src/Avalonia/Artemis.UI/Artemis.UI.csproj
+++ b/src/Avalonia/Artemis.UI/Artemis.UI.csproj
@@ -56,6 +56,9 @@
PropertiesView.axaml
+
+ LayerShapeVisualizerView.axaml
+
diff --git a/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs
index 7f7eaa297..b7163bcd3 100644
--- a/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs
+++ b/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs
@@ -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);
}
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml
index dcf5a3b7f..dfa802ef5 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml
@@ -48,6 +48,12 @@
+
+
+
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorViewModel.cs
index 5169bca53..1ad1fa430 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorViewModel.cs
@@ -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;
+ private readonly SourceList _visualizers;
public VisualEditorViewModel(IProfileEditorService profileEditorService, IRgbService rgbService, IProfileEditorVmFactory vmFactory)
{
_vmFactory = vmFactory;
+ _visualizers = new SourceList();
+
Devices = new ObservableCollection(rgbService.EnabledDevices);
- Visualizers = new ObservableCollection();
Tools = new ObservableCollection();
+ _visualizers.Connect()
+ .Sort(SortExpressionComparer.Ascending(vm => vm.Order))
+ .Bind(out ReadOnlyObservableCollection 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 Devices { get; }
- public ObservableCollection Visualizers { get; set; }
- public ObservableCollection Tools { get; set; }
+ public ReadOnlyObservableCollection Visualizers { get; }
+ public ObservableCollection 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 visualizerViewModels, Layer layer)
{
- Visualizers.Add(_vmFactory.LayerVisualizerViewModel(layer));
+ visualizerViewModels.Add(_vmFactory.LayerShapeVisualizerViewModel(layer));
+ visualizerViewModels.Add(_vmFactory.LayerVisualizerViewModel(layer));
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/IVisualizerViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/IVisualizerViewModel.cs
index 843d32114..385fab60f 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/IVisualizerViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/IVisualizerViewModel.cs
@@ -2,4 +2,7 @@
public interface IVisualizerViewModel
{
+ int X { get; }
+ int Y { get; }
+ int Order { get; }
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerShapeVisualizerView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerShapeVisualizerView.axaml
new file mode 100644
index 000000000..0fe4b62bb
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerShapeVisualizerView.axaml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerShapeVisualizerView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerShapeVisualizerView.axaml.cs
new file mode 100644
index 000000000..cbef48437
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerShapeVisualizerView.axaml.cs
@@ -0,0 +1,18 @@
+using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
+
+namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers
+{
+ public partial class LayerShapeVisualizerView : ReactiveUserControl
+ {
+ public LayerShapeVisualizerView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerShapeVisualizerViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerShapeVisualizerViewModel.cs
new file mode 100644
index 000000000..ea3baee08
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerShapeVisualizerViewModel.cs
@@ -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? _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(x => Layer.Transform.Position.CurrentValueSet += x, x => Layer.Transform.Position.CurrentValueSet -= x)
+ .Subscribe(_ => UpdateTransform())
+ .DisposeWith(d);
+ Observable.FromEventPattern(x => Layer.Transform.Rotation.CurrentValueSet += x, x => Layer.Transform.Rotation.CurrentValueSet -= x)
+ .Subscribe(_ => UpdateTransform())
+ .DisposeWith(d);
+ Observable.FromEventPattern(x => Layer.Transform.Scale.CurrentValueSet += x, x => Layer.Transform.Scale.CurrentValueSet -= x)
+ .Subscribe(_ => UpdateTransform())
+ .DisposeWith(d);
+ Observable.FromEventPattern(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;
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerVisualizerView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerVisualizerView.axaml
index ec8a92ae9..6e24c808a 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerVisualizerView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerVisualizerView.axaml
@@ -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">
+ Margin="0 0 2 2"
+ StrokeDashArray="6,2"
+ StrokeJoin="Round">
+
+
+
-
\ No newline at end of file
+
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerVisualizerViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerVisualizerViewModel.cs
index d74e985cb..aa5c84eab 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerVisualizerViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerVisualizerViewModel.cs
@@ -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? _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(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(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(x => Layer.Transform.Scale.CurrentValueSet += x, x => Layer.Transform.Scale.CurrentValueSet -= x)
- .Subscribe(_ => UpdateTransform())
- .DisposeWith(d);
- Observable.FromEventPattern(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;
}
\ No newline at end of file