diff --git a/Project-Aurora/Project-Aurora/EffectsEngine/IEffectLayer.cs b/Project-Aurora/Project-Aurora/EffectsEngine/IEffectLayer.cs
index c6316d412..320ad8088 100644
--- a/Project-Aurora/Project-Aurora/EffectsEngine/IEffectLayer.cs
+++ b/Project-Aurora/Project-Aurora/EffectsEngine/IEffectLayer.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Drawing;
using AuroraRgb.Settings;
using Common.Devices;
@@ -48,10 +47,6 @@ public interface EffectLayer : IDisposable
/// Itself
void Set(KeySequence sequence, ref readonly Color color);
- void PercentEffect(Color foregroundColor, Color backgroundColor, IReadOnlyList keys, double value,
- double total, PercentEffectType percentEffectType = PercentEffectType.Progressive, double flashPast = 0.0,
- bool flashReversed = false, bool blinkBackground = false);
-
///
/// + Operator, sums two EffectLayer together.
///
diff --git a/Project-Aurora/Project-Aurora/EffectsEngine/NoRenderLayer.cs b/Project-Aurora/Project-Aurora/EffectsEngine/NoRenderLayer.cs
index ac0c5eaf2..4383ecec7 100644
--- a/Project-Aurora/Project-Aurora/EffectsEngine/NoRenderLayer.cs
+++ b/Project-Aurora/Project-Aurora/EffectsEngine/NoRenderLayer.cs
@@ -1,11 +1,9 @@
using System;
-using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using AuroraRgb.Settings;
-using AuroraRgb.Utils;
using Common.Devices;
using Common.Utils;
@@ -126,78 +124,6 @@ private void SetOver(DeviceKeys key, ref readonly Color foregroundColor)
Set(key, in newColor);
}
- ///
- /// Draws a percent effect on the layer bitmap using an array of DeviceKeys keys and solid colors.
- ///
- /// The foreground color, used as a "Progress bar color"
- /// The current progress value
- /// The maxiumum progress value
- public void PercentEffect(Color foregroundColor, Color backgroundColor, IReadOnlyList keys, double value,
- double total, PercentEffectType percentEffectType = PercentEffectType.Progressive, double flashPast = 0.0,
- bool flashReversed = false, bool blinkBackground = false)
- {
- var progressTotal = value / total;
- if (progressTotal < 0.0)
- progressTotal = 0.0;
- else if (progressTotal > 1.0)
- progressTotal = 1.0;
-
- var progress = progressTotal * keys.Count;
-
- if (flashPast > 0.0 && ((flashReversed && progressTotal >= flashPast) || (!flashReversed && progressTotal <= flashPast)))
- {
- var percent = Math.Sin(Time.GetMillisecondsSinceEpoch() % 1000.0D / 1000.0D * Math.PI);
- if (blinkBackground)
- backgroundColor = ColorUtils.BlendColors(backgroundColor, Color.Empty, percent);
- else
- foregroundColor = ColorUtils.BlendColors(backgroundColor, foregroundColor, percent);
- }
-
- if (percentEffectType is PercentEffectType.Highest_Key or PercentEffectType.Highest_Key_Blend && keys.Count > 0)
- {
- var activeKey = (int)Math.Ceiling(Math.Clamp(value, 0, 1) / (total / keys.Count)) - 1;
- var col = percentEffectType == PercentEffectType.Highest_Key ?
- foregroundColor : ColorUtils.BlendColors(backgroundColor, foregroundColor, progressTotal);
- for (var i = 0; i < keys.Count; i++)
- {
- if (i != activeKey)
- {
- Set(keys[i], Color.Transparent);
- }
- }
- Set(keys[activeKey], in col);
-
- }
- else
- {
- for (var i = 0; i < keys.Count; i++)
- {
- var currentKey = keys[i];
-
- switch (percentEffectType)
- {
- case PercentEffectType.AllAtOnce:
- Set(currentKey, ColorUtils.BlendColors(in backgroundColor, in foregroundColor, progressTotal));
- break;
- case PercentEffectType.Progressive_Gradual:
- if (i == (int)progress)
- {
- var percent = progress - i;
- Set(currentKey, ColorUtils.BlendColors(in backgroundColor, in foregroundColor, percent));
- }
- else if (i < (int)progress)
- Set(currentKey, foregroundColor);
- else
- Set(currentKey, backgroundColor);
- break;
- default:
- Set(currentKey, i < (int) progress ? foregroundColor : backgroundColor);
- break;
- }
- }
- }
- }
-
public void Exclude(KeySequence sequence)
{
_excludedZoneKeysCache.SetSequence(sequence);
diff --git a/Project-Aurora/Project-Aurora/EffectsEngine/ZoneKeyPercentDrawer.cs b/Project-Aurora/Project-Aurora/EffectsEngine/ZoneKeyPercentDrawer.cs
new file mode 100644
index 000000000..b88f43f90
--- /dev/null
+++ b/Project-Aurora/Project-Aurora/EffectsEngine/ZoneKeyPercentDrawer.cs
@@ -0,0 +1,288 @@
+using System;
+using System.Drawing;
+using System.Linq;
+using AuroraRgb.Settings;
+using AuroraRgb.Utils;
+using Common.Devices;
+
+namespace AuroraRgb.EffectsEngine;
+
+public class ZoneKeyPercentDrawer(EffectLayer effectLayer)
+{
+ public void PercentEffect(Color foregroundColor, Color backgroundColor, KeySequence sequence, double value,
+ double total = 1.0D, PercentEffectType percentEffectType = PercentEffectType.Progressive,
+ double flashPast = 0.0, bool flashReversed = false, bool blinkBackground = false)
+ {
+ var zoneKeysCache = new ZoneKeysCache();
+ zoneKeysCache.SetSequence(sequence);
+ var keys = zoneKeysCache.GetKeys();
+
+ if (sequence.Type == KeySequenceType.FreeForm)
+ {
+ PercentEffectOnFreeForm(foregroundColor, backgroundColor, sequence.Freeform, keys, value, total,
+ percentEffectType, flashPast, flashReversed, blinkBackground);
+ }
+ else
+ {
+ // Fallback to previous implementation for regular sequences
+ PercentEffectOnKeys(foregroundColor, backgroundColor, keys, value, total,
+ percentEffectType, flashPast, flashReversed, blinkBackground);
+ }
+ }
+
+ private void PercentEffectOnFreeForm(Color foregroundColor, Color backgroundColor, FreeFormObject freeform,
+ DeviceKeys[] keys, double value, double total, PercentEffectType percentEffectType,
+ double flashPast, bool flashReversed, bool blinkBackground)
+ {
+ switch (percentEffectType)
+ {
+ case PercentEffectType.AllAtOnce:
+ {
+ var progressTotal = Math.Clamp(value / total, 0.0, 1.0);
+
+ // Apply flash effect if needed
+ if (flashPast > 0.0 && ((flashReversed && progressTotal >= flashPast) || (!flashReversed && progressTotal <= flashPast)))
+ {
+ var percent = Math.Sin(Time.GetMillisecondsSinceEpoch() % 1000.0D / 1000.0D * Math.PI);
+ if (blinkBackground)
+ backgroundColor = ColorUtils.BlendColors(backgroundColor, Color.Empty, percent);
+ else
+ foregroundColor = ColorUtils.BlendColors(backgroundColor, foregroundColor, percent);
+ }
+
+ var keyColor = ColorUtils.BlendColors(backgroundColor, foregroundColor, progressTotal);
+
+ effectLayer.Set(keys, in keyColor);
+ }
+ return;
+ case PercentEffectType.Progressive:
+ case PercentEffectType.Progressive_Gradual:
+ PercentEffectOnFreeForm(foregroundColor, backgroundColor, freeform, keys, value, total, flashPast, flashReversed, blinkBackground, effectLayer,
+ percentEffectType == PercentEffectType.Progressive_Gradual);
+ return;
+ }
+ }
+
+ private static void PercentEffectOnFreeForm(Color foregroundColor, Color backgroundColor, FreeFormObject freeform,
+ DeviceKeys[] keys, double value, double total,
+ double flashPast, bool flashReversed, bool blinkBackground, EffectLayer effectLayer, bool gradual)
+ {
+ // Calculate progress
+ var progressTotal = Math.Clamp(value / total, 0.0, 1.0);
+
+ // Apply flash effect if needed
+ if (flashPast > 0.0 && ((flashReversed && progressTotal >= flashPast) || (!flashReversed && progressTotal <= flashPast)))
+ {
+ var percent = Math.Sin(Time.GetMillisecondsSinceEpoch() % 1000.0D / 1000.0D * Math.PI);
+ if (blinkBackground)
+ backgroundColor = ColorUtils.BlendColors(backgroundColor, Color.Empty, percent);
+ else
+ foregroundColor = ColorUtils.BlendColors(backgroundColor, foregroundColor, percent);
+ }
+
+ // Calculate freeform object's world coordinates and corners
+ var freeformCorners = GetFreeFormCorners(freeform, progressTotal);
+
+ // Iterate through keys and determine their color based on their position
+ foreach (var key in keys)
+ {
+ // Get key's rectangle in canvas coordinates
+ ref readonly var keyRect = ref Effects.Canvas.GetRectangle(key);
+
+ // Create key corners
+ var keyCorners = new[]
+ {
+ new PointF(keyRect.Left, keyRect.Bottom),
+ new PointF(keyRect.Right, keyRect.Bottom),
+ new PointF(keyRect.Right, keyRect.Top),
+ new PointF(keyRect.Left, keyRect.Top)
+ };
+
+ // Calculate coverage
+ var coverageRatio = CalculateKeyCoverage(freeformCorners, keyCorners);
+
+ Color keyColor;
+ if (gradual)
+ {
+ keyColor = ColorUtils.BlendColors(
+ backgroundColor,
+ foregroundColor,
+ Math.Min(coverageRatio, 1.0)
+ );
+ }
+ else
+ {
+ var isKeyCovered = coverageRatio >= 1f - float.Epsilon;
+ keyColor = isKeyCovered ? foregroundColor : backgroundColor;
+ }
+
+ effectLayer.Set(key, in keyColor);
+ }
+ }
+
+ ///
+ /// Calculates the coverage ratio of a key by a freeform object.
+ ///
+ /// Corners of the freeform object
+ /// Corners of the key
+ /// A ratio between 0 and 1 representing the key's coverage
+ private static float CalculateKeyCoverage(PointF[] freeformCorners, PointF[] keyCorners)
+ {
+ // Count how many key corners are inside the freeform
+ var containedCorners = keyCorners.Count(corner => PointInRectangle(corner, freeformCorners));
+
+ // Calculate coverage based on number of contained corners
+ return (float)containedCorners / keyCorners.Length;
+ }
+
+ ///
+ /// Gets the corners of a FreeFormObject in canvas coordinates
+ ///
+ private static PointF[] GetFreeFormCorners(FreeFormObject freeForm, double progress)
+ {
+ // Convert FreeForm coordinates to canvas coordinates
+ var xPos = (freeForm.X + Effects.Canvas.GridBaselineX) * Effects.Canvas.EditorToCanvasWidth;
+ var yPos = (freeForm.Y + Effects.Canvas.GridBaselineY) * Effects.Canvas.EditorToCanvasHeight;
+ var width = freeForm.Width * Effects.Canvas.EditorToCanvasWidth * progress;
+ var height = freeForm.Height * Effects.Canvas.EditorToCanvasHeight;
+
+ var left = xPos;
+ var right = xPos + width;
+ var top = yPos;
+ var bottom = yPos + height;
+
+ // Calculate the center point of rotation
+ var centerX = xPos + width / 2;
+ var centerY = yPos + height / 2;
+
+ // Convert angle to radians
+ var angleRad = freeForm.Angle * Math.PI / 180;
+ var cos = Math.Cos(angleRad);
+ var sin = Math.Sin(angleRad);
+
+ // Calculate the corners of the rotated rectangle
+ return
+ [
+ TransformPoint(left, top, centerX, centerY, cos, sin),
+ TransformPoint(right, top, centerX, centerY, cos, sin),
+ TransformPoint(right, bottom, centerX, centerY, cos, sin),
+ TransformPoint(left, bottom, centerX, centerY, cos, sin)
+ ];
+ }
+
+ ///
+ /// Transforms a point by rotating it around a center point
+ ///
+ private static PointF TransformPoint(double x, double y, double centerX, double centerY, double cos, double sin)
+ {
+ // Translate point to origin
+ var translatedX = x - centerX;
+ var translatedY = y - centerY;
+
+ // Rotate
+ var rotatedX = translatedX * cos - translatedY * sin;
+ var rotatedY = translatedX * sin + translatedY * cos;
+
+ // Translate back
+ return new PointF(
+ (float)(rotatedX + centerX),
+ (float)(rotatedY + centerY)
+ );
+ }
+
+ ///
+ /// Checks if a point is inside a rectangle
+ ///
+ private static bool PointInRectangle(PointF point, PointF[] rectangleCorners)
+ {
+ // Translate the point and rectangle so that the rectangle's first corner is at the origin
+ var translatedPoint = new PointF(
+ point.X - rectangleCorners[0].X,
+ point.Y - rectangleCorners[0].Y
+ );
+
+ // Calculate the vectors of the rectangle's sides
+ var vector1 = new PointF(
+ rectangleCorners[1].X - rectangleCorners[0].X,
+ rectangleCorners[1].Y - rectangleCorners[0].Y
+ );
+
+ var vector2 = new PointF(
+ rectangleCorners[3].X - rectangleCorners[0].X,
+ rectangleCorners[3].Y - rectangleCorners[0].Y
+ );
+
+ // Calculate dot products to determine if point is inside
+ var dot1 = translatedPoint.X * vector1.X + translatedPoint.Y * vector1.Y;
+ var dot2 = translatedPoint.X * vector2.X + translatedPoint.Y * vector2.Y;
+
+ // Check if the point is within the rectangle's side lengths
+ return dot1 >= 0 && dot1 <= vector1.X * vector1.X + vector1.Y * vector1.Y &&
+ dot2 >= 0 && dot2 <= vector2.X * vector2.X + vector2.Y * vector2.Y;
+ }
+
+ private void PercentEffectOnKeys(Color foregroundColor, Color backgroundColor, DeviceKeys[] keys, double value,
+ double total, PercentEffectType percentEffectType, double flashPast, bool flashReversed, bool blinkBackground)
+ {
+ // Previous implementation for non-freeform sequences
+ var progressTotal = Math.Clamp(value / total, 0.0, 1.0);
+ var progress = progressTotal * keys.Length;
+
+ // Flash effect logic
+ if (flashPast > 0.0 && ((flashReversed && progressTotal >= flashPast) || (!flashReversed && progressTotal <= flashPast)))
+ {
+ var percent = Math.Sin(Time.GetMillisecondsSinceEpoch() % 1000.0D / 1000.0D * Math.PI);
+ if (blinkBackground)
+ backgroundColor = ColorUtils.BlendColors(backgroundColor, Color.Empty, percent);
+ else
+ foregroundColor = ColorUtils.BlendColors(backgroundColor, foregroundColor, percent);
+ }
+
+
+ switch (percentEffectType)
+ {
+ case PercentEffectType.AllAtOnce:
+ effectLayer.Set(keys, ColorUtils.BlendColors(in backgroundColor, in foregroundColor, progressTotal));
+ break;
+ case PercentEffectType.Progressive_Gradual:
+ for (var i = 0; i < keys.Length; i++)
+ {
+ var currentKey = keys[i];
+ if (i == (int)progress)
+ {
+ var percent = progress - i;
+ var blendColor = ColorUtils.BlendColors(in backgroundColor, in foregroundColor, percent);
+ effectLayer.Set(currentKey, in blendColor);
+ }
+ else if (i < (int)progress)
+ effectLayer.Set(currentKey, in foregroundColor);
+ else
+ effectLayer.Set(currentKey, in backgroundColor);
+ }
+
+ break;
+ case PercentEffectType.Progressive:
+ for (var i = 0; i < keys.Length; i++)
+ {
+ var currentKey = keys[i];
+ effectLayer.Set(currentKey, i < (int)progress ? foregroundColor : backgroundColor);
+ }
+
+ break;
+ case PercentEffectType.Highest_Key:
+ {
+ effectLayer.Set(keys, in backgroundColor);
+ var highestKey = (int)progress;
+ effectLayer.Set(keys[highestKey], in foregroundColor);
+ break;
+ }
+ case PercentEffectType.Highest_Key_Blend:
+ {
+ var highestKey = (int)progress;
+ var blendColor = ColorUtils.BlendColors(in backgroundColor, in foregroundColor, progress);
+ effectLayer.Set(keys[highestKey], in blendColor);
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Project-Aurora/Project-Aurora/Settings/Layers/PercentLayerHandler.cs b/Project-Aurora/Project-Aurora/Settings/Layers/PercentLayerHandler.cs
index c6331acf7..c529c1c2a 100644
--- a/Project-Aurora/Project-Aurora/Settings/Layers/PercentLayerHandler.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Layers/PercentLayerHandler.cs
@@ -1,6 +1,4 @@
-using System.ComponentModel;
-using System.Globalization;
-using System.Linq;
+using System.Globalization;
using System.Windows.Controls;
using AuroraRgb.EffectsEngine;
using AuroraRgb.Profiles;
@@ -86,65 +84,49 @@ public override void Default()
}
}
-public class PercentLayerHandler() : LayerHandler("PercentLayer")
+public class PercentLayerHandler() : LayerHandler("PercentLayer")
where TProperty : PercentLayerHandlerProperties
{
private double _value;
- private readonly NoRenderLayer NoRenderLayer = new();
-
public override EffectLayer Render(IGameState gameState)
{
var keySequence = Properties.Sequence;
- EffectLayer layer = keySequence.Type switch
- {
- KeySequenceType.Sequence => NoRenderLayer,
- _ => EffectLayer,
- };
-
-
+
if (Invalidated)
{
- layer.Clear();
Invalidated = false;
_value = -1;
}
var value = Properties.Logic?._Value ?? gameState.GetNumber(Properties.VariablePath);
- if (MathUtils.NearlyEqual(_value, value, 0.000001))
+ if (MathUtils.NearlyEqual(_value, value, 0.000001) && !Invalidated)
{
- return layer;
+ return EffectLayer;
}
_value = value;
-
+
var maxvalue = Properties.Logic?._MaxValue ?? gameState.GetNumber(Properties.MaxVariablePath);
- if (keySequence.Type == KeySequenceType.Sequence)
- {
- NoRenderLayer.PercentEffect(Properties.PrimaryColor, Properties.SecondaryColor, keySequence.Keys, value, maxvalue,
- Properties.PercentType, Properties.BlinkThreshold, Properties.BlinkDirection, Properties.BlinkBackground);
- return NoRenderLayer;
- }
- EffectLayer.PercentEffect(Properties.PrimaryColor, Properties.SecondaryColor, keySequence, value, maxvalue,
+ EffectLayer.Clear();
+ var percentDrawer = new ZoneKeyPercentDrawer(EffectLayer);
+ percentDrawer.PercentEffect(Properties.PrimaryColor, Properties.SecondaryColor, keySequence, value, maxvalue,
Properties.PercentType, Properties.BlinkThreshold, Properties.BlinkDirection, Properties.BlinkBackground);
return EffectLayer;
}
public override void SetApplication(Application profile)
{
- if (profile != null)
- {
- if (!double.TryParse(Properties.VariablePath.GsiPath, CultureInfo.InvariantCulture, out _) &&
- !string.IsNullOrWhiteSpace(Properties.VariablePath.GsiPath) &&
- !profile.ParameterLookup.IsValidParameter(Properties.VariablePath.GsiPath)
- )
- Properties.VariablePath = VariablePath.Empty;
-
- if (!double.TryParse(Properties.MaxVariablePath.GsiPath, CultureInfo.InvariantCulture, out _) &&
- !string.IsNullOrWhiteSpace(Properties.MaxVariablePath.GsiPath) &&
- !profile.ParameterLookup.IsValidParameter(Properties.MaxVariablePath.GsiPath)
- )
- Properties.MaxVariablePath = VariablePath.Empty;
- }
+ if (!double.TryParse(Properties.VariablePath.GsiPath, CultureInfo.InvariantCulture, out _) &&
+ !string.IsNullOrWhiteSpace(Properties.VariablePath.GsiPath) &&
+ !profile.ParameterLookup.IsValidParameter(Properties.VariablePath.GsiPath)
+ )
+ Properties.VariablePath = VariablePath.Empty;
+
+ if (!double.TryParse(Properties.MaxVariablePath.GsiPath, CultureInfo.InvariantCulture, out _) &&
+ !string.IsNullOrWhiteSpace(Properties.MaxVariablePath.GsiPath) &&
+ !profile.ParameterLookup.IsValidParameter(Properties.MaxVariablePath.GsiPath)
+ )
+ Properties.MaxVariablePath = VariablePath.Empty;
base.SetApplication(profile);
}
}