diff --git a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs
index d467ba655..8c54d17f5 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs
@@ -149,9 +149,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
{
if (TriggerMode == EventTriggerMode.Toggle)
{
- if (!IsMet && _wasMet)
- ProfileElement.Timeline.JumpToEnd();
- else if (IsMet && !_wasMet)
+ if (IsMet && !_wasMet)
ProfileElement.Timeline.JumpToStart();
}
else
@@ -164,12 +162,13 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
{
if (OverlapMode == EventOverlapMode.Restart)
ProfileElement.Timeline.JumpToStart();
- else if (OverlapMode == EventOverlapMode.Copy && ProfileElement is Layer layer)
- layer.CreateCopyAsChild();
+ else if (OverlapMode == EventOverlapMode.Copy && ProfileElement is Layer layer && layer.Parent is not Layer)
+ layer.CreateRenderCopy(10);
}
}
- ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), TriggerMode == EventTriggerMode.Toggle);
+ // Stick to mean segment in toggle mode for as long as the condition is met
+ ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), TriggerMode == EventTriggerMode.Toggle && IsMet);
}
///
@@ -225,10 +224,9 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
INode? existingEventNode = Script.Nodes.FirstOrDefault(n => n.Id == EventDefaultNode.NodeId);
if (existingEventNode != null)
_eventNode = (EventDefaultNode) existingEventNode;
-
+
UpdateEventNode();
Script.LoadConnections();
-
}
#endregion
diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs
index c42df2b5b..8ffbaec64 100644
--- a/src/Artemis.Core/Models/Profile/Folder.cs
+++ b/src/Artemis.Core/Models/Profile/Folder.cs
@@ -190,8 +190,11 @@ namespace Artemis.Core
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);
+ foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
+ {
+ if (!baseLayerEffect.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)
@@ -204,8 +207,11 @@ namespace Artemis.Core
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);
+ foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
+ {
+ if (!baseLayerEffect.Suspended)
+ baseLayerEffect.InternalPostProcess(canvas, rendererBounds, layerPaint);
+ }
}
finally
{
diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs
index 769816e4e..79099830f 100644
--- a/src/Artemis.Core/Models/Profile/Layer.cs
+++ b/src/Artemis.Core/Models/Profile/Layer.cs
@@ -16,6 +16,7 @@ namespace Artemis.Core
///
public sealed class Layer : RenderProfileElement
{
+ private readonly List _renderCopies;
private LayerGeneralProperties _general;
private BaseLayerBrush? _layerBrush;
private LayerShape? _layerShape;
@@ -37,6 +38,8 @@ namespace Artemis.Core
Name = name;
Suspended = false;
+ // TODO: move to top
+ _renderCopies = new List();
_general = new LayerGeneralProperties();
_transform = new LayerTransformProperties();
@@ -61,6 +64,8 @@ namespace Artemis.Core
Profile = profile;
Parent = parent;
+ // TODO: move to top
+ _renderCopies = new List();
_general = new LayerGeneralProperties();
_transform = new LayerTransformProperties();
@@ -76,15 +81,15 @@ namespace Artemis.Core
/// Creates a new instance of the class by copying the provided .
///
/// The layer to copy
- /// The parent of the layer
- public Layer(Layer source, ProfileElement parent) : base(parent, parent.Profile)
+ private Layer(Layer source) : base(source, source.Profile)
{
- LayerEntity = CoreJson.DeserializeObject(CoreJson.SerializeObject(source.LayerEntity, true), true) ?? new LayerEntity();
- LayerEntity.Id = Guid.NewGuid();
+ LayerEntity = source.LayerEntity;
Profile = source.Profile;
- Parent = parent;
+ Parent = source;
+ // TODO: move to top
+ _renderCopies = new List();
_general = new LayerGeneralProperties();
_transform = new LayerTransformProperties();
@@ -94,6 +99,13 @@ namespace Artemis.Core
Adapter = new LayerAdapter(this);
Load();
Initialize();
+
+ Timeline.JumpToStart();
+ AddLeds(source.Leds);
+ Enable();
+
+ // After loading using the source entity create a new entity so the next call to Save won't mess with the source, just in case.
+ LayerEntity = new LayerEntity();
}
///
@@ -373,7 +385,7 @@ namespace Artemis.Core
if (ShouldBeEnabled)
Enable();
- else if (Timeline.IsFinished && !Children.Any())
+ else if (Timeline.IsFinished && !_renderCopies.Any())
Disable();
if (Timeline.Delta == TimeSpan.Zero)
@@ -383,22 +395,26 @@ namespace Artemis.Core
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++)
+ foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
{
- Layer child = (Layer) Children[index];
+ if (!baseLayerEffect.Suspended)
+ baseLayerEffect.InternalUpdate(Timeline);
+ }
+
+ // Remove render copies that finished their timeline and update the rest
+ for (int index = 0; index < _renderCopies.Count; index++)
+ {
+ Layer child = _renderCopies[index];
if (!child.Timeline.IsFinished)
{
child.Update(deltaTime);
- continue;
}
-
- RemoveChild(child);
- child.Dispose();
- index--;
+ else
+ {
+ _renderCopies.Remove(child);
+ child.Dispose();
+ index--;
+ }
}
}
@@ -408,20 +424,16 @@ namespace Artemis.Core
if (Disposed)
throw new ObjectDisposedException("Layer");
- RenderSelf(canvas, basePosition);
- RenderChildren(canvas, basePosition);
+ RenderLayer(canvas, basePosition);
+ RenderCopies(canvas, basePosition);
}
- private void RenderSelf(SKCanvas canvas, SKPointI basePosition)
+ private void RenderLayer(SKCanvas canvas, SKPointI basePosition)
{
// Ensure the layer is ready
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;
@@ -432,7 +444,7 @@ namespace Artemis.Core
SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
try
{
- canvas.Save();
+ using SKAutoCanvasRestore _ = new(canvas);
canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y);
using SKPath clipPath = new(Path);
clipPath.Transform(SKMatrix.CreateTranslation(Bounds.Left * -1, Bounds.Top * -1));
@@ -478,18 +490,16 @@ namespace Artemis.Core
}
finally
{
- canvas.Restore();
layerPaint.DisposeSelfAndProperties();
}
Timeline.ClearDelta();
}
- private void RenderChildren(SKCanvas canvas, SKPointI basePosition)
+ private void RenderCopies(SKCanvas canvas, SKPointI basePosition)
{
- // Render children first so they go below
- for (int i = Children.Count - 1; i >= 0; i--)
- Children[i].Render(canvas, basePosition);
+ for (int i = _renderCopies.Count - 1; i >= 0; i--)
+ _renderCopies[i].Render(canvas, basePosition);
}
///
@@ -549,23 +559,24 @@ namespace Artemis.Core
else
Timeline.JumpToEnd();
- while (Children.Any())
+ while (_renderCopies.Any())
{
- Children[0].Dispose();
- RemoveChild(Children[0]);
+ _renderCopies[0].Dispose();
+ _renderCopies.RemoveAt(0);
}
}
///
- /// Creates a copy of this layer as a child and plays it once
+ /// Creates a copy of this layer and renders it alongside this layer for as long as its timeline lasts.
///
- public void CreateCopyAsChild()
+ /// The total maximum of render copies to keep
+ public void CreateRenderCopy(int max)
{
- Layer copy = new(this, this);
- copy.AddLeds(Leds);
- copy.Enable();
- copy.Timeline.JumpToStart();
- AddChild(copy);
+ if (_renderCopies.Count >= max)
+ return;
+
+ Layer copy = new(this);
+ _renderCopies.Add(copy);
}
internal void CalculateRenderProperties()
@@ -623,26 +634,23 @@ namespace Artemis.Core
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
+ foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
{
- 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);
+ if (!baseLayerEffect.Suspended)
+ baseLayerEffect.InternalPreProcess(canvas, bounds, layerPaint);
}
- finally
+ using SKAutoCanvasRestore _ = new(canvas);
+ 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)
{
- canvas.Restore();
+ if (!baseLayerEffect.Suspended)
+ baseLayerEffect.InternalPostProcess(canvas, bounds, layerPaint);
}
}
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs
index 4749d4bc3..22167910d 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs
@@ -46,6 +46,11 @@ public interface IProfileEditorService : IArtemisSharedUIService
///
IObservable PixelsPerSecond { get; }
+ ///
+ /// Gets an observable of the suspended state.
+ ///
+ IObservable SuspendedEditing { get; }
+
///
/// Gets a source list of all available editor tools.
///
@@ -87,6 +92,12 @@ public interface IProfileEditorService : IArtemisSharedUIService
/// The new pixels per second.
void ChangePixelsPerSecond(int pixelsPerSecond);
+ ///
+ /// Changes the current suspended state.
+ ///
+ /// The new suspended state.
+ void ChangeSuspendedEditing(bool suspend);
+
///
/// Selects the provided keyframe.
///
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
index 9634b3287..26968f208 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
@@ -62,8 +62,6 @@ internal class ProfileEditorService : IProfileEditorService
});
}
- public IObservable SuspendedEditing { get; }
-
private ProfileEditorHistory? GetHistory(ProfileConfiguration? profileConfiguration)
{
if (profileConfiguration == null)
@@ -107,6 +105,7 @@ internal class ProfileEditorService : IProfileEditorService
public IObservable ProfileElement { get; }
public IObservable LayerProperty { get; }
public IObservable History { get; }
+ public IObservable SuspendedEditing { get; }
public IObservable Time { get; }
public IObservable Playing { get; }
public IObservable PixelsPerSecond { get; }
@@ -180,6 +179,25 @@ internal class ProfileEditorService : IProfileEditorService
_timeSubject.OnNext(time);
}
+ public void ChangeSuspendedEditing(bool suspend)
+ {
+ if (_suspendedEditingSubject.Value == suspend)
+ return;
+
+ _suspendedEditingSubject.OnNext(suspend);
+ if (suspend)
+ {
+ Pause();
+ _profileService.RenderForEditor = false;
+ }
+ else
+ {
+ if (_profileConfigurationSubject.Value != null)
+ _profileService.RenderForEditor = true;
+ Tick(_timeSubject.Value);
+ }
+ }
+
public void SelectKeyframe(ILayerPropertyKeyframe? keyframe, bool expand, bool toggle)
{
if (keyframe == null)
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml
index 8a7c99512..b6ead192e 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml
@@ -5,6 +5,7 @@
xmlns:controls="clr-namespace:Artemis.UI.Controls"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
+ xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="350"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.PropertiesView"
x:DataType="local:PropertiesViewModel">
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesViewModel.cs
index 75f8b494d..bfd8b37d5 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesViewModel.cs
@@ -31,6 +31,7 @@ public class PropertiesViewModel : ActivatableViewModelBase
private ObservableAsPropertyHelper? _layerProperty;
private ObservableAsPropertyHelper? _pixelsPerSecond;
private ObservableAsPropertyHelper? _profileElement;
+ private ObservableAsPropertyHelper? _suspendedEditing;
///
public PropertiesViewModel(IProfileEditorService profileEditorService,
@@ -55,6 +56,7 @@ public class PropertiesViewModel : ActivatableViewModelBase
_profileElement = profileEditorService.ProfileElement.ToProperty(this, vm => vm.ProfileElement).DisposeWith(d);
_pixelsPerSecond = profileEditorService.PixelsPerSecond.ToProperty(this, vm => vm.PixelsPerSecond).DisposeWith(d);
_layerProperty = profileEditorService.LayerProperty.ToProperty(this, vm => vm.LayerProperty).DisposeWith(d);
+ _suspendedEditing = profileEditorService.SuspendedEditing.ToProperty(this, vm => vm.SuspendedEditing).DisposeWith(d);
Disposable.Create(() =>
{
_settingsService.SaveAllSettings();
@@ -94,11 +96,13 @@ public class PropertiesViewModel : ActivatableViewModelBase
public RenderProfileElement? ProfileElement => _profileElement?.Value;
public Layer? Layer => _profileElement?.Value as Layer;
public ILayerProperty? LayerProperty => _layerProperty?.Value;
+ public bool SuspendedEditing => _suspendedEditing?.Value ?? false;
public int PixelsPerSecond => _pixelsPerSecond?.Value ?? 0;
public IObservable Playing => _profileEditorService.Playing;
public PluginSetting PropertiesTreeWidth => _settingsService.GetSetting("ProfileEditor.PropertiesTreeWidth", 500.0);
+
private void UpdatePropertyGroups()
{
if (ProfileElement == null)
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml
index 7aa8d3859..60ef0da43 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml
@@ -59,7 +59,7 @@
-
+
+
+
+