diff --git a/src/Artemis.Core.Tests/Conditions/EventConditionTests.cs b/src/Artemis.Core.Tests/Conditions/EventConditionTests.cs
index a9dfc3a84..c5bb7edf6 100644
--- a/src/Artemis.Core.Tests/Conditions/EventConditionTests.cs
+++ b/src/Artemis.Core.Tests/Conditions/EventConditionTests.cs
@@ -239,14 +239,16 @@ public class EventConditionTests : RenderTestBase
(Layer layer, EventCondition condition) = AddLayer(EventTriggerMode.Toggle);
DataModel.Event.Trigger();
+ // Render defaults to a 0.1s frame, so the timeline starts playing and ends up at 0.1s
using RenderFrame on = Render(layer.Profile);
+ // AddLayer gives the layer a 1 second main segment and no start or end segment. 0.1s + 1.5s = 1.6s goes 0.6s past
+ // the end, and while toggled on the timeline wraps around rather than finishing
using RenderFrame stillOn = Render(layer.Profile, 1.5);
condition.IsMet.ShouldBeTrue();
ShouldBeOn(on);
ShouldBeOn(stillOn);
- // Wrapped around the main segment rather than finishing
- layer.Timeline.Position.ShouldBe(TimeSpan.FromSeconds(0.5));
+ layer.Timeline.Position.ShouldBe(TimeSpan.FromSeconds(0.6));
}
[Fact]
diff --git a/src/Artemis.Core.Tests/Infrastructure/ExpectedColors.cs b/src/Artemis.Core.Tests/Infrastructure/ExpectedColors.cs
new file mode 100644
index 000000000..c08e40093
--- /dev/null
+++ b/src/Artemis.Core.Tests/Infrastructure/ExpectedColors.cs
@@ -0,0 +1,32 @@
+using SkiaSharp;
+
+namespace Artemis.Core.Tests.Infrastructure;
+
+///
+/// Helpers to describe expected colors by what produced them rather than by their channel values.
+///
+public static class ExpectedColors
+{
+ ///
+ /// Returns the color an LED shows when is drawn at the given opacity over an opaque
+ /// background. The canvas is cleared to black before each frame, so that is the default background.
+ ///
+ /// The color being drawn
+ /// The opacity in percent, like the opacity properties of layers and effects
+ /// The opaque color underneath, defaults to black
+ public static SKColor AtOpacity(this SKColor color, float opacity, SKColor? over = null)
+ {
+ SKColor background = over ?? SKColors.Black;
+ float alpha = opacity / 100f;
+ return new SKColor(
+ Blend(color.Red, background.Red, alpha),
+ Blend(color.Green, background.Green, alpha),
+ Blend(color.Blue, background.Blue, alpha)
+ );
+ }
+
+ private static byte Blend(byte top, byte bottom, float alpha)
+ {
+ return (byte) MathF.Round(top * alpha + bottom * (1 - alpha));
+ }
+}
diff --git a/src/Artemis.Core.Tests/Infrastructure/Plugins/LayerBrushes/SolidColorTestBrush.cs b/src/Artemis.Core.Tests/Infrastructure/Plugins/LayerBrushes/SolidColorTestBrush.cs
index 504df0e3c..79922550e 100644
--- a/src/Artemis.Core.Tests/Infrastructure/Plugins/LayerBrushes/SolidColorTestBrush.cs
+++ b/src/Artemis.Core.Tests/Infrastructure/Plugins/LayerBrushes/SolidColorTestBrush.cs
@@ -19,8 +19,10 @@ public class SolidColorTestBrush : LayerBrush
public override void Render(SKCanvas canvas, SKRect bounds, SKPaint paint)
{
- // Like the real brushes this overrides the paint color, the layer applies its opacity when compositing
- paint.Color = Properties.Color.CurrentValue;
+ // Like the real solid brush this draws with a shader, which keeps the alpha of the paint it is given
+ using SKShader shader = SKShader.CreateColor(Properties.Color.CurrentValue);
+ paint.Shader = shader;
canvas.DrawRect(bounds, paint);
+ paint.Shader = null;
}
}
diff --git a/src/Artemis.Core.Tests/Infrastructure/Rendering/RenderHarness.cs b/src/Artemis.Core.Tests/Infrastructure/Rendering/RenderHarness.cs
index d2aec92d1..91e96baf5 100644
--- a/src/Artemis.Core.Tests/Infrastructure/Rendering/RenderHarness.cs
+++ b/src/Artemis.Core.Tests/Infrastructure/Rendering/RenderHarness.cs
@@ -122,7 +122,7 @@ public sealed class RenderHarness : IRenderer, IDisposable
/// The layer and its brush, the brush exposes the brush properties
public (Layer Layer, TBrush Brush) AddLayer(Folder parent, IEnumerable leds, string name = "Test layer", int? order = null) where TBrush : BaseLayerBrush
{
- // Note: despite its documentation the Layer constructor does not add the layer to the parent
+ // The Layer constructor does not add the layer to the parent
Layer layer = new(parent, name);
parent.AddChild(layer, order);
layer.AddLeds(leds);
diff --git a/src/Artemis.Core.Tests/Profiles/CompositingTests.cs b/src/Artemis.Core.Tests/Profiles/CompositingTests.cs
index e404f22ee..8a64471c1 100644
--- a/src/Artemis.Core.Tests/Profiles/CompositingTests.cs
+++ b/src/Artemis.Core.Tests/Profiles/CompositingTests.cs
@@ -177,7 +177,8 @@ public class CompositingTests : RenderTestBase
using RenderFrame frame = Render(profile);
- frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(new SKColor(127, 0, 127));
+ // Blue plus red is magenta, the folder fades that as a whole
+ frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Magenta.AtOpacity(50));
}
#endregion
diff --git a/src/Artemis.Core.Tests/Profiles/FolderEffectTests.cs b/src/Artemis.Core.Tests/Profiles/FolderEffectTests.cs
index d164eb0f1..ba109a761 100644
--- a/src/Artemis.Core.Tests/Profiles/FolderEffectTests.cs
+++ b/src/Artemis.Core.Tests/Profiles/FolderEffectTests.cs
@@ -38,7 +38,7 @@ public class FolderEffectTests : RenderTestBase
(RenderFrame before, RenderFrame after) = RenderWithAndWithout(profile, opacity);
- after.ShouldOnlyDifferFrom(before, busy.Leds, c => Scale(c, 127));
+ after.ShouldOnlyDifferFrom(before, busy.Leds, c => c.AtOpacity(50));
}
[Fact]
@@ -79,7 +79,7 @@ public class FolderEffectTests : RenderTestBase
// The nested children get both, the rest of the folder only gets the swap
ArtemisLed[] outerOnly = busy.Leds.Except(busy.NestedLeds).ToArray();
foreach (ArtemisLed led in busy.NestedLeds)
- after.LedColors[led].ShouldBeColor(Scale(Swap(before.LedColors[led]), 127), 3);
+ after.LedColors[led].ShouldBeColor(Swap(before.LedColors[led]).AtOpacity(50), 3);
after.ShouldOnlyDifferFrom(before, outerOnly, Swap, busy.NestedLeds);
}
@@ -109,7 +109,7 @@ public class FolderEffectTests : RenderTestBase
using RenderFrame frame = Render(profile);
// The children are flattened before the folder is faded, the bottom layer does not shine through
- frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(new SKColor(0, 127, 0));
+ frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Lime.AtOpacity(50));
}
[Fact]
@@ -125,7 +125,7 @@ public class FolderEffectTests : RenderTestBase
using RenderFrame frame = Render(profile);
- frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(new SKColor(128, 127, 0));
+ frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Lime.AtOpacity(50, over: SKColors.Red));
}
[Fact]
@@ -201,11 +201,6 @@ public class FolderEffectTests : RenderTestBase
return new SKColor(color.Blue, color.Green, color.Red, color.Alpha);
}
- private static SKColor Scale(SKColor color, byte factor)
- {
- return new SKColor((byte) (color.Red * factor / 255), (byte) (color.Green * factor / 255), (byte) (color.Blue * factor / 255));
- }
-
private static SKColor Multiply(SKColor color, SKColor with)
{
return new SKColor((byte) (color.Red * with.Red / 255), (byte) (color.Green * with.Green / 255), (byte) (color.Blue * with.Blue / 255));
diff --git a/src/Artemis.Core.Tests/Profiles/LayerEffectTests.cs b/src/Artemis.Core.Tests/Profiles/LayerEffectTests.cs
index 37d043011..fddede447 100644
--- a/src/Artemis.Core.Tests/Profiles/LayerEffectTests.cs
+++ b/src/Artemis.Core.Tests/Profiles/LayerEffectTests.cs
@@ -18,7 +18,7 @@ public class LayerEffectTests : RenderTestBase
using RenderFrame frame = Render(profile);
- frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(new SKColor(127, 0, 0));
+ frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Red.AtOpacity(50));
}
[Fact]
@@ -32,11 +32,11 @@ public class LayerEffectTests : RenderTestBase
using RenderFrame frame = Render(profile);
// 50% of 50%
- frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(new SKColor(63, 0, 0));
+ frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Red.AtOpacity(25));
}
[Fact]
- public void PreProcessColorFilter_IsAppliedTwiceOnLayers()
+ public void PreProcessColorFilter_IsAppliedOnceOnLayers()
{
Profile profile = Harness.CreateProfile();
(Layer layer, _) = Harness.AddLayer(profile.GetRootFolder(), [Led(LedId.Keyboard_A)]);
@@ -44,11 +44,21 @@ public class LayerEffectTests : RenderTestBase
using RenderFrame frame = Render(profile);
- // Pinned as-is, likely unintended: Layer.DelegateRendering composites with the pre-processed paint and also hands
- // that same paint to the brush, so for any brush drawing with that paint (regular and per-LED alike) the filter
- // applies to the brush output and again when compositing.
- // Swapping red and blue twice cancels out, a hue shift effect would shift twice. Folders apply it once.
- frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Red);
+ // Swapping red and blue twice would cancel out
+ frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Blue);
+ }
+
+ [Fact]
+ public void PreProcessColorFilter_IsAppliedOnceOnPerLedBrushes()
+ {
+ Profile profile = Harness.CreateProfile();
+ (Layer layer, PerLedTestBrush brush) = Harness.AddLayer(profile.GetRootFolder(), [Led(LedId.Keyboard_A)]);
+ brush.ColorSelector = (_, _) => SKColors.Red;
+ Harness.AddEffect(layer);
+
+ using RenderFrame frame = Render(profile);
+
+ frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Blue);
}
[Fact]
@@ -137,8 +147,8 @@ public class LayerEffectTests : RenderTestBase
using RenderTimeline timeline = RenderTimeline(profile, 0.25, 0.75);
- timeline[0].GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(new SKColor(191, 0, 0));
- timeline[1].GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(new SKColor(63, 0, 0));
+ timeline[0].GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Red.AtOpacity(75));
+ timeline[1].GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Red.AtOpacity(25));
}
[Fact]
diff --git a/src/Artemis.Core.Tests/Profiles/LayerTransformTests.cs b/src/Artemis.Core.Tests/Profiles/LayerTransformTests.cs
index f013913cb..9ecd37e33 100644
--- a/src/Artemis.Core.Tests/Profiles/LayerTransformTests.cs
+++ b/src/Artemis.Core.Tests/Profiles/LayerTransformTests.cs
@@ -17,7 +17,7 @@ public class LayerTransformTests : RenderTestBase
using RenderFrame frame = Render(profile);
- frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(new SKColor(127, 0, 0));
+ frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Red.AtOpacity(50));
}
[Fact]
@@ -30,7 +30,22 @@ public class LayerTransformTests : RenderTestBase
using RenderFrame frame = Render(profile);
- frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(new SKColor(127, 0, 0));
+ frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Red.AtOpacity(50));
+ }
+
+ [Fact]
+ public void Opacity_AppliesOnceToGradientBrushes()
+ {
+ Profile profile = Harness.CreateProfile();
+ (Layer layer, GradientTestBrush brush) = Harness.AddLayer(profile.GetRootFolder(), [Led(LedId.Keyboard_A)]);
+ brush.Properties.Left.SetCurrentValue(SKColors.Red);
+ brush.Properties.Right.SetCurrentValue(SKColors.Red);
+ layer.Transform.Opacity.SetCurrentValue(50);
+
+ using RenderFrame frame = Render(profile);
+
+ // The gradient shader keeps the alpha of the paint, the brush must not get the layer opacity on top of compositing
+ frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Red.AtOpacity(50));
}
[Fact]
diff --git a/src/Artemis.Core.Tests/Profiles/ProfileSerializationTests.cs b/src/Artemis.Core.Tests/Profiles/ProfileSerializationTests.cs
index 187cc3cc8..e34dbf0e0 100644
--- a/src/Artemis.Core.Tests/Profiles/ProfileSerializationTests.cs
+++ b/src/Artemis.Core.Tests/Profiles/ProfileSerializationTests.cs
@@ -124,7 +124,7 @@ public class ProfileSerializationTests : RenderTestBase
}
[Fact]
- public void RoundTrip_LosesTheNameOfRenamedEffects()
+ public void RoundTrip_KeepsTheNameOfRenamedEffects()
{
Profile profile = Harness.CreateProfile();
(Layer layer, _) = Harness.AddLayer(profile.GetRootFolder(), [Led(LedId.Keyboard_A)]);
@@ -134,10 +134,22 @@ public class ProfileSerializationTests : RenderTestBase
Profile reloaded = Harness.SaveAndReload(profile);
- // Pinned as-is, a bug: BaseLayerEffect.Load checks HasBeenRenamed before loading it from the entity, so it is
- // always false at that point and the name falls back to the display name of the descriptor
BaseLayerEffect reloadedTint = reloaded.GetAllLayers().Single().LayerEffects.Single();
reloadedTint.HasBeenRenamed.ShouldBeTrue();
+ reloadedTint.Name.ShouldBe("Renamed tint");
+ }
+
+ [Fact]
+ public void RoundTrip_UsesTheDisplayNameOfEffectsThatWereNotRenamed()
+ {
+ Profile profile = Harness.CreateProfile();
+ (Layer layer, _) = Harness.AddLayer(profile.GetRootFolder(), [Led(LedId.Keyboard_A)]);
+ Harness.AddEffect(layer);
+
+ Profile reloaded = Harness.SaveAndReload(profile);
+
+ BaseLayerEffect reloadedTint = reloaded.GetAllLayers().Single().LayerEffects.Single();
+ reloadedTint.HasBeenRenamed.ShouldBeFalse();
reloadedTint.Name.ShouldBe("Tint");
}
diff --git a/src/Artemis.Core.Tests/Profiles/ProfileStructureTests.cs b/src/Artemis.Core.Tests/Profiles/ProfileStructureTests.cs
index 0c9f86267..bfbce3314 100644
--- a/src/Artemis.Core.Tests/Profiles/ProfileStructureTests.cs
+++ b/src/Artemis.Core.Tests/Profiles/ProfileStructureTests.cs
@@ -147,22 +147,21 @@ public class ProfileStructureTests : RenderTestBase
}
[Fact]
- public void SuspendingARenderingFolder_KeepsRenderingItsChildren()
+ public void SuspendingARenderingFolder_StopsRenderingItsChildren()
{
Profile profile = Harness.CreateProfile();
BusyFolder busy = BusyFolder.Create(Harness, Keyboard, profile);
using RenderFrame before = Harness.Render(profile, TimeSpan.FromSeconds(0.1));
busy.Folder.Suspended = true;
- Harness.Render(profile, TimeSpan.FromSeconds(10)).Dispose();
using RenderFrame after = Render(profile);
- // Pinned as-is, an inconsistency: Layer.Update disables a suspended layer, Folder.Update only disables a folder
- // once its timeline finishes, which never happens with the default always-on condition. The editor hides this by
- // disabling suspended elements itself, so it only affects code suspending folders outside of the editor
- busy.Folder.Enabled.ShouldBeTrue();
- foreach ((ArtemisLed led, SKColor color) in before.LedColors)
- after.LedColors[led].ShouldBeColor(color, 2, led.RgbLed.Id.ToString());
+ busy.Folder.Enabled.ShouldBeFalse();
+ foreach (ArtemisLed led in busy.Leds)
+ {
+ before.LedColors[led].ShouldNotBe(SKColors.Black, led.RgbLed.Id.ToString());
+ after.LedColors[led].ShouldBeColor(SKColors.Black, 2, led.RgbLed.Id.ToString());
+ }
}
[Fact]
@@ -218,7 +217,7 @@ public class ProfileStructureTests : RenderTestBase
using RenderTimeline timeline = RenderTimeline(profile, 0.5, 1, 1.5);
// The fade uses a cubic ease-in-out, which is at exactly half at the halfway point
- timeline[0].GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(new SKColor(127, 0, 0));
+ timeline[0].GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Red.AtOpacity(50));
timeline[1].GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Red);
timeline[2].GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Red);
}
diff --git a/src/Artemis.Core.Tests/Profiles/TimelineTests.cs b/src/Artemis.Core.Tests/Profiles/TimelineTests.cs
index 57c4ab80c..674f4a08a 100644
--- a/src/Artemis.Core.Tests/Profiles/TimelineTests.cs
+++ b/src/Artemis.Core.Tests/Profiles/TimelineTests.cs
@@ -92,7 +92,7 @@ public class TimelineTests : RenderTestBase
using RenderFrame frame = Render(profile, 0.5);
- frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(new SKColor(127, 0, 0));
+ frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Red.AtOpacity(50));
}
#endregion
@@ -117,19 +117,20 @@ public class TimelineTests : RenderTestBase
}
[Fact]
- public void AlwaysOn_WrapsAroundUsingTheFrameDeltaRatherThanTheOvershoot()
+ public void AlwaysOn_WrapsAroundByTheOvershoot()
{
Profile profile = Harness.CreateProfile();
(Layer layer, SolidColorTestBrush brush) = Harness.AddLayer(profile.GetRootFolder(), [Led(LedId.Keyboard_A)]);
layer.Timeline.MainSegmentLength = TimeSpan.FromSeconds(2);
AddKeyframes(brush.Properties.Color, (0, SKColors.Red), (2, SKColors.Blue));
- // Going from 1.5s to 2.5s overshoots the 2s main segment by 0.5s, the timeline lands on 1s instead of 0.5s.
- // Pinned as-is: with real frame times (~33ms) the difference is at most a single frame.
+ // Going from 1.5s to 2.5s overshoots the 2s main segment by 0.5s. A new timeline has no start segment, so the
+ // main segment starts at 0s and the timeline lands on 0.5s
using RenderTimeline timeline = RenderTimeline(profile, 1.5, 2.5);
- layer.Timeline.Position.ShouldBe(TimeSpan.FromSeconds(1));
- timeline[1].GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(Purple);
+ layer.Timeline.Position.ShouldBe(TimeSpan.FromSeconds(0.5));
+ // 0.5s is a quarter of the way from red to blue
+ timeline[1].GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(new SKColor(191, 0, 64));
}
[Fact]
diff --git a/src/Artemis.Core.Tests/Rendering/SurfaceTests.cs b/src/Artemis.Core.Tests/Rendering/SurfaceTests.cs
index 268abd02d..1144db283 100644
--- a/src/Artemis.Core.Tests/Rendering/SurfaceTests.cs
+++ b/src/Artemis.Core.Tests/Rendering/SurfaceTests.cs
@@ -95,28 +95,40 @@ public class SurfaceTests : RenderTestBase
}
[Fact]
- public void RotatingADevice_DoesNotUpdateItsLedsUntilItIsMoved()
+ public void RotatingADevice_UpdatesItsLeds()
{
ArtemisLed a = Led(LedId.Keyboard_A);
SKRect beforeRotation = a.AbsoluteRectangle;
+ int updates = 0;
+ Keyboard.DeviceUpdated += (_, _) => updates++;
Keyboard.Rotation = 90;
- // Pinned as-is: the Rotation and Scale setters only update the RGB.NET device. The Artemis LEDs, and with them the
- // texture, keep their old positions until X or Y is set. The device properties dialog gets away with this because
- // it reloads the device layout after saving
- a.AbsoluteRectangle.ShouldBe(beforeRotation);
- Math.Abs(a.RgbLed.AbsoluteBoundary.Location.X - beforeRotation.Left).ShouldBeGreaterThan(1);
-
- Keyboard.X = Keyboard.X;
a.AbsoluteRectangle.ShouldNotBe(beforeRotation);
+ a.AbsoluteRectangle.Left.ShouldBe(a.RgbLed.AbsoluteBoundary.Location.X, 1);
+ a.AbsoluteRectangle.Top.ShouldBe(a.RgbLed.AbsoluteBoundary.Location.Y, 1);
+ updates.ShouldBe(1);
+ }
+
+ [Fact]
+ public void ScalingADevice_UpdatesItsLeds()
+ {
+ ArtemisLed a = Led(LedId.Keyboard_A);
+ SKRect beforeScaling = a.AbsoluteRectangle;
+ int updates = 0;
+ Keyboard.DeviceUpdated += (_, _) => updates++;
+
+ Keyboard.Scale = 2;
+
+ // Rounded outwards to the render scale, so slightly wider than twice the size
+ a.AbsoluteRectangle.Width.ShouldBeInRange(beforeScaling.Width * 2, beforeScaling.Width * 2 + 4);
+ updates.ShouldBe(1);
}
[Fact]
public void RotatedDevice_SamplesFromItsRotatedLedPositions()
{
Keyboard.Rotation = 90;
- Keyboard.X = Keyboard.X;
SKRect rotatedA = Led(LedId.Keyboard_A).AbsoluteRectangle;
using RenderFrame frame = Harness.Render(canvas => Fill(canvas, rotatedA, SKColors.Red));
@@ -130,7 +142,6 @@ public class SurfaceTests : RenderTestBase
public void ScaledDevice_SamplesFromItsScaledLedPositions()
{
Keyboard.Scale = 2;
- Keyboard.X = Keyboard.X;
SKRect scaledA = Led(LedId.Keyboard_A).AbsoluteRectangle;
using RenderFrame frame = Harness.Render(canvas => Fill(canvas, scaledA, SKColors.Red));
diff --git a/src/Artemis.Core.Tests/Rendering/TextureSamplingTests.cs b/src/Artemis.Core.Tests/Rendering/TextureSamplingTests.cs
index bf78dc908..f02d9f368 100644
--- a/src/Artemis.Core.Tests/Rendering/TextureSamplingTests.cs
+++ b/src/Artemis.Core.Tests/Rendering/TextureSamplingTests.cs
@@ -63,10 +63,10 @@ public class TextureSamplingTests : RenderTestBase
[Fact]
public void SemiTransparentDrawing_IsBlendedOntoBlack()
{
- using RenderFrame frame = Harness.Render(canvas => Fill(canvas, Led(LedId.Keyboard_A).AbsoluteRectangle, new SKColor(255, 0, 0, 128)));
+ using RenderFrame frame = Harness.Render(canvas => Fill(canvas, Led(LedId.Keyboard_A).AbsoluteRectangle, SKColors.Red.WithAlpha(128)));
SaveOutput(frame);
- frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(new SKColor(128, 0, 0));
+ frame.GetColor(Keyboard, LedId.Keyboard_A).ShouldBeColor(SKColors.Red.AtOpacity(50));
}
[Fact]
diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs
index 548931484..dc9fe1160 100644
--- a/src/Artemis.Core/Models/Profile/Folder.cs
+++ b/src/Artemis.Core/Models/Profile/Folder.cs
@@ -16,8 +16,11 @@ public sealed class Folder : RenderProfileElement
private bool _isExpanded;
///
- /// Creates a new instance of the class and adds itself to the child collection of the provided
- ///
+ /// Creates a new instance of the class with the provided .
+ ///
+ /// The folder is not added to the child collection of the parent, use to
+ /// add it.
+ ///
///
/// The parent of the folder
/// The name of the folder
@@ -106,7 +109,7 @@ public sealed class Folder : RenderProfileElement
if (ShouldBeEnabled)
Enable();
- else if (Timeline.IsFinished)
+ else if (Suspended || Timeline.IsFinished)
Disable();
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs
index 88f605584..617035b5a 100644
--- a/src/Artemis.Core/Models/Profile/Layer.cs
+++ b/src/Artemis.Core/Models/Profile/Layer.cs
@@ -28,8 +28,11 @@ public sealed class Layer : RenderProfileElement
private List _missingLeds = [];
///
- /// Creates a new instance of the class and adds itself to the child collection of the provided
- ///
+ /// Creates a new instance of the class with the provided .
+ ///
+ /// The layer is not added to the child collection of the parent, use to
+ /// add it.
+ ///
///
/// The parent of the layer
/// The name of the layer
@@ -663,7 +666,14 @@ public sealed class Layer : RenderProfileElement
// Restore the blend mode before doing the actual render
layerPaint.BlendMode = SKBlendMode.SrcOver;
- LayerBrush.InternalRender(canvas, bounds, layerPaint);
+ // The opacity and the filters of the effects are applied when compositing the layer, the brush gets an opaque
+ // paint without filters so they don't apply to the brush output a second time
+ using SKPaint brushPaint = layerPaint.Clone();
+ brushPaint.Color = brushPaint.Color.WithAlpha(255);
+ brushPaint.ColorFilter = null;
+ brushPaint.ImageFilter = null;
+
+ LayerBrush.InternalRender(canvas, bounds, brushPaint);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
{
diff --git a/src/Artemis.Core/Models/Profile/Timeline.cs b/src/Artemis.Core/Models/Profile/Timeline.cs
index 6ba2b7001..b827aa5df 100644
--- a/src/Artemis.Core/Models/Profile/Timeline.cs
+++ b/src/Artemis.Core/Models/Profile/Timeline.cs
@@ -243,6 +243,7 @@ public class Timeline : CorePropertyChanged, IStorageModel
if (IsOverridden)
throw new ArtemisCoreException("Can't update an overridden timeline, call ClearOverride first.");
+ TimeSpan previousPosition = Position;
Delta += delta;
Position += delta;
@@ -251,10 +252,15 @@ public class Timeline : CorePropertyChanged, IStorageModel
// 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);
+ return;
+ }
+
+ // Wrap around by as much as the timeline went past the end of the main segment. If it was already past the end
+ // (for example a timeline that had finished), start the main segment over instead
+ TimeSpan overshoot = previousPosition <= MainSegmentEndPosition ? Position - MainSegmentEndPosition : delta;
+ Position = MainSegmentStartPosition + TimeSpan.FromTicks(overshoot.Ticks % MainSegmentLength.Ticks);
}
}
diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs
index 3874d3469..812f86147 100644
--- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs
+++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs
@@ -200,7 +200,11 @@ public class ArtemisDevice : CorePropertyChanged
{
DeviceEntity.Rotation = value;
if (RgbDevice.Surface != null)
+ {
RgbDevice.Rotation = DeviceEntity.Rotation;
+ CalculateRenderProperties();
+ }
+
OnPropertyChanged(nameof(Rotation));
}
}
@@ -215,7 +219,11 @@ public class ArtemisDevice : CorePropertyChanged
{
DeviceEntity.Scale = value;
if (RgbDevice.Surface != null)
+ {
RgbDevice.Scale = DeviceEntity.Scale;
+ CalculateRenderProperties();
+ }
+
OnPropertyChanged(nameof(Scale));
}
}
diff --git a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs
index 651df32f9..d615f150e 100644
--- a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs
+++ b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs
@@ -217,8 +217,8 @@ public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageMod
///
public void Load()
{
- Name = HasBeenRenamed ? LayerEffectEntity.Name : Descriptor.DisplayName;
HasBeenRenamed = LayerEffectEntity.HasBeenRenamed;
+ Name = HasBeenRenamed ? LayerEffectEntity.Name : Descriptor.DisplayName;
Order = LayerEffectEntity.Order;
}