1
0
mirror of https://github.com/DarthAffe/RGB.NET.git synced 2025-12-12 09:38:31 +00:00

Changed all namespaces to file-scope

This commit is contained in:
Darth Affe 2021-11-13 17:35:08 +01:00
parent a0c6ccebea
commit 30624035f1
353 changed files with 35775 additions and 36129 deletions

View File

@ -1,61 +1,60 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents the default-behavior for the work with colors.
/// </summary>
public class DefaultColorBehavior : IColorBehavior
{
/// <inheritdoc />
#region Methods
/// <summary>
/// Represents the default-behavior for the work with colors.
/// Converts the individual byte values of this <see cref="Color"/> to a human-readable string.
/// </summary>
public class DefaultColorBehavior : IColorBehavior
/// <returns>A string that contains the individual byte values of this <see cref="Color"/>. For example "[A: 255, R: 255, G: 0, B: 0]".</returns>
public virtual string ToString(in Color color) => $"[A: {color.GetA()}, R: {color.GetR()}, G: {color.GetG()}, B: {color.GetB()}]";
/// <summary>
/// Tests whether the specified object is a <see cref="Color" /> and is equivalent to this <see cref="Color" />.
/// </summary>
/// <param name="color">The color to test.</param>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Color" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
public virtual bool Equals(in Color color, object? obj)
{
#region Methods
if (obj is not Color color2) return false;
/// <summary>
/// Converts the individual byte values of this <see cref="Color"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the individual byte values of this <see cref="Color"/>. For example "[A: 255, R: 255, G: 0, B: 0]".</returns>
public virtual string ToString(in Color color) => $"[A: {color.GetA()}, R: {color.GetR()}, G: {color.GetG()}, B: {color.GetB()}]";
/// <summary>
/// Tests whether the specified object is a <see cref="Color" /> and is equivalent to this <see cref="Color" />.
/// </summary>
/// <param name="color">The color to test.</param>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Color" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
public virtual bool Equals(in Color color, object? obj)
{
if (obj is not Color color2) return false;
(float a, float r, float g, float b) = color2.GetRGB();
return color.A.EqualsInTolerance(a) && color.R.EqualsInTolerance(r) && color.G.EqualsInTolerance(g) && color.B.EqualsInTolerance(b);
}
/// <summary>
/// Returns a hash code for this <see cref="Color" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Color" />.</returns>
public virtual int GetHashCode(in Color color) => HashCode.Combine(color.A, color.R, color.G, color.B);
/// <summary>
/// Blends a <see cref="Color"/> over this color.
/// </summary>
/// <param name="baseColor">The <see cref="Color"/> to to blend over.</param>
/// <param name="blendColor">The <see cref="Color"/> to blend.</param>
public virtual Color Blend(in Color baseColor, in Color blendColor)
{
if (blendColor.A.EqualsInTolerance(0)) return baseColor;
if (blendColor.A.EqualsInTolerance(1))
return blendColor;
float resultA = (1.0f - ((1.0f - blendColor.A) * (1.0f - baseColor.A)));
float resultR = (((blendColor.R * blendColor.A) / resultA) + ((baseColor.R * baseColor.A * (1.0f - blendColor.A)) / resultA));
float resultG = (((blendColor.G * blendColor.A) / resultA) + ((baseColor.G * baseColor.A * (1.0f - blendColor.A)) / resultA));
float resultB = (((blendColor.B * blendColor.A) / resultA) + ((baseColor.B * baseColor.A * (1.0f - blendColor.A)) / resultA));
return new Color(resultA, resultR, resultG, resultB);
}
#endregion
(float a, float r, float g, float b) = color2.GetRGB();
return color.A.EqualsInTolerance(a) && color.R.EqualsInTolerance(r) && color.G.EqualsInTolerance(g) && color.B.EqualsInTolerance(b);
}
}
/// <summary>
/// Returns a hash code for this <see cref="Color" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Color" />.</returns>
public virtual int GetHashCode(in Color color) => HashCode.Combine(color.A, color.R, color.G, color.B);
/// <summary>
/// Blends a <see cref="Color"/> over this color.
/// </summary>
/// <param name="baseColor">The <see cref="Color"/> to to blend over.</param>
/// <param name="blendColor">The <see cref="Color"/> to blend.</param>
public virtual Color Blend(in Color baseColor, in Color blendColor)
{
if (blendColor.A.EqualsInTolerance(0)) return baseColor;
if (blendColor.A.EqualsInTolerance(1))
return blendColor;
float resultA = (1.0f - ((1.0f - blendColor.A) * (1.0f - baseColor.A)));
float resultR = (((blendColor.R * blendColor.A) / resultA) + ((baseColor.R * baseColor.A * (1.0f - blendColor.A)) / resultA));
float resultG = (((blendColor.G * blendColor.A) / resultA) + ((baseColor.G * baseColor.A * (1.0f - blendColor.A)) / resultA));
float resultB = (((blendColor.B * blendColor.A) / resultA) + ((baseColor.B * baseColor.A * (1.0f - blendColor.A)) / resultA));
return new Color(resultA, resultR, resultG, resultB);
}
#endregion
}

View File

@ -1,36 +1,35 @@
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a behavior of a color for base operations.
/// </summary>
public interface IColorBehavior
{
/// <summary>
/// Represents a behavior of a color for base operations.
/// Converts the specified <see cref="Color"/> to a string representation.
/// </summary>
public interface IColorBehavior
{
/// <summary>
/// Converts the specified <see cref="Color"/> to a string representation.
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The string representation of the specified color.</returns>
string ToString(in Color color);
/// <param name="color">The color to convert.</param>
/// <returns>The string representation of the specified color.</returns>
string ToString(in Color color);
/// <summary>
/// Tests whether the specified object is a <see cref="Color" /> and is equivalent to this <see cref="Color" />.
/// </summary>
/// <param name="color">The color to test.</param>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Color" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
bool Equals(in Color color, object? obj);
/// <summary>
/// Tests whether the specified object is a <see cref="Color" /> and is equivalent to this <see cref="Color" />.
/// </summary>
/// <param name="color">The color to test.</param>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Color" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
bool Equals(in Color color, object? obj);
/// <summary>
/// Returns a hash code for this <see cref="Color" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Color" />.</returns>
int GetHashCode(in Color color);
/// <summary>
/// Returns a hash code for this <see cref="Color" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Color" />.</returns>
int GetHashCode(in Color color);
/// <summary>
/// Blends a <see cref="Color"/> over this color.
/// </summary>
/// <param name="baseColor">The <see cref="Color"/> to to blend over.</param>
/// <param name="blendColor">The <see cref="Color"/> to blend.</param>
Color Blend(in Color baseColor, in Color blendColor);
}
}
/// <summary>
/// Blends a <see cref="Color"/> over this color.
/// </summary>
/// <param name="baseColor">The <see cref="Color"/> to to blend over.</param>
/// <param name="blendColor">The <see cref="Color"/> to blend.</param>
Color Blend(in Color baseColor, in Color blendColor);
}

View File

@ -5,280 +5,279 @@
using System;
using System.Diagnostics;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents an ARGB (alpha, red, green, blue) color.
/// </summary>
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
public readonly struct Color
{
#region Constants
private static readonly Color TRANSPARENT = new(0, 0, 0, 0);
/// <summary>
/// Represents an ARGB (alpha, red, green, blue) color.
/// Gets an transparent color [A: 0, R: 0, G: 0, B: 0]
/// </summary>
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
public readonly struct Color
public static ref readonly Color Transparent => ref TRANSPARENT;
#endregion
#region Properties & Fields
/// <summary>
/// Gets or sets the <see cref="IColorBehavior"/> used to perform operations on colors.
/// </summary>
public static IColorBehavior Behavior { get; set; } = new DefaultColorBehavior();
/// <summary>
/// Gets the alpha component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public float A { get; }
/// <summary>
/// Gets the red component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public float R { get; }
/// <summary>
/// Gets the green component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public float G { get; }
/// <summary>
/// Gets the blue component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public float B { get; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using RGB-Values.
/// Alpha defaults to 255.
/// </summary>
/// <param name="r">The red component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="g">The green component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="b">The blue component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
public Color(byte r, byte g, byte b)
: this(byte.MaxValue, r, g, b)
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using RGB-Values.
/// Alpha defaults to 255.
/// </summary>
/// <param name="r">The red component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="g">The green component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="b">The blue component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
public Color(int r, int g, int b)
: this((byte)r.Clamp(0, byte.MaxValue), (byte)g.Clamp(0, byte.MaxValue), (byte)b.Clamp(0, byte.MaxValue))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(byte a, byte r, byte g, byte b)
: this(a.GetPercentageFromByteValue(), r.GetPercentageFromByteValue(), g.GetPercentageFromByteValue(), b.GetPercentageFromByteValue())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(int a, int r, int g, int b)
: this((byte)a.Clamp(0, byte.MaxValue), (byte)r.Clamp(0, byte.MaxValue), (byte)g.Clamp(0, byte.MaxValue), (byte)b.Clamp(0, byte.MaxValue))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using RGB-percent values.
/// Alpha defaults to 1.0.
/// </summary>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(float r, float g, float b)
: this(1.0f, r, g, b)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(float a, byte r, byte g, byte b)
: this(a, r.GetPercentageFromByteValue(), g.GetPercentageFromByteValue(), b.GetPercentageFromByteValue())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(float a, int r, int g, int b)
: this(a, (byte)r.Clamp(0, byte.MaxValue), (byte)g.Clamp(0, byte.MaxValue), (byte)b.Clamp(0, byte.MaxValue))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(int a, float r, float g, float b)
: this((byte)a.Clamp(0, byte.MaxValue), r, g, b)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(byte a, float r, float g, float b)
: this(a.GetPercentageFromByteValue(), r, g, b)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(float a, float r, float g, float b)
{
#region Constants
private static readonly Color TRANSPARENT = new(0, 0, 0, 0);
/// <summary>
/// Gets an transparent color [A: 0, R: 0, G: 0, B: 0]
/// </summary>
public static ref readonly Color Transparent => ref TRANSPARENT;
#endregion
#region Properties & Fields
/// <summary>
/// Gets or sets the <see cref="IColorBehavior"/> used to perform operations on colors.
/// </summary>
public static IColorBehavior Behavior { get; set; } = new DefaultColorBehavior();
/// <summary>
/// Gets the alpha component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public float A { get; }
/// <summary>
/// Gets the red component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public float R { get; }
/// <summary>
/// Gets the green component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public float G { get; }
/// <summary>
/// Gets the blue component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public float B { get; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using RGB-Values.
/// Alpha defaults to 255.
/// </summary>
/// <param name="r">The red component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="g">The green component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="b">The blue component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
public Color(byte r, byte g, byte b)
: this(byte.MaxValue, r, g, b)
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using RGB-Values.
/// Alpha defaults to 255.
/// </summary>
/// <param name="r">The red component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="g">The green component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="b">The blue component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
public Color(int r, int g, int b)
: this((byte)r.Clamp(0, byte.MaxValue), (byte)g.Clamp(0, byte.MaxValue), (byte)b.Clamp(0, byte.MaxValue))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(byte a, byte r, byte g, byte b)
: this(a.GetPercentageFromByteValue(), r.GetPercentageFromByteValue(), g.GetPercentageFromByteValue(), b.GetPercentageFromByteValue())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(int a, int r, int g, int b)
: this((byte)a.Clamp(0, byte.MaxValue), (byte)r.Clamp(0, byte.MaxValue), (byte)g.Clamp(0, byte.MaxValue), (byte)b.Clamp(0, byte.MaxValue))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using RGB-percent values.
/// Alpha defaults to 1.0.
/// </summary>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(float r, float g, float b)
: this(1.0f, r, g, b)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(float a, byte r, byte g, byte b)
: this(a, r.GetPercentageFromByteValue(), g.GetPercentageFromByteValue(), b.GetPercentageFromByteValue())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(float a, int r, int g, int b)
: this(a, (byte)r.Clamp(0, byte.MaxValue), (byte)g.Clamp(0, byte.MaxValue), (byte)b.Clamp(0, byte.MaxValue))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(int a, float r, float g, float b)
: this((byte)a.Clamp(0, byte.MaxValue), r, g, b)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(byte a, float r, float g, float b)
: this(a.GetPercentageFromByteValue(), r, g, b)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(float a, float r, float g, float b)
{
A = a.Clamp(0, 1);
R = r.Clamp(0, 1);
G = g.Clamp(0, 1);
B = b.Clamp(0, 1);
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct by cloning a existing <see cref="T:RGB.NET.Core.Color" />.
/// </summary>
/// <param name="color">The <see cref="T:RGB.NET.Core.Color" /> the values are copied from.</param>
public Color(in Color color)
: this(color.A, color.R, color.G, color.B)
{ }
#endregion
#region Methods
/// <summary>
/// Gets a human-readable string, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <returns>A string that contains the individual byte values of this <see cref="Color"/>. Default format: "[A: 255, R: 255, G: 0, B: 0]".</returns>
public override string ToString() => Behavior.ToString(this);
/// <summary>
/// Tests whether the specified object is a <see cref="Color" /> and is equivalent to this <see cref="Color" />, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Color" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj) => Behavior.Equals(this, obj);
/// <summary>
/// Returns a hash code for this <see cref="Color" />, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Color" />.</returns>
// ReSharper disable once NonReadonlyMemberInGetHashCode
public override int GetHashCode() => Behavior.GetHashCode(this);
/// <summary>
/// Blends a <see cref="Color"/> over this color, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <param name="color">The <see cref="Color"/> to blend.</param>
public Color Blend(in Color color) => Behavior.Blend(this, color);
#endregion
#region Operators
/// <summary>
/// Blends the provided colors as if <see cref="Blend"/> would've been called on <paramref name="color1" />.
/// </summary>
/// <param name="color1">The base color.</param>
/// <param name="color2">The color to blend.</param>
/// <returns>The blended color.</returns>
public static Color operator +(in Color color1, in Color color2) => color1.Blend(color2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Color" /> are equal.
/// </summary>
/// <param name="color1">The first <see cref="Color" /> to compare.</param>
/// <param name="color2">The second <see cref="Color" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="color1" /> and <paramref name="color2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(in Color color1, in Color color2) => color1.Equals(color2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Color" /> are equal.
/// </summary>
/// <param name="color1">The first <see cref="Color" /> to compare.</param>
/// <param name="color2">The second <see cref="Color" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="color1" /> and <paramref name="color2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(in Color color1, in Color color2) => !(color1 == color2);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((byte r, byte g, byte b) components) => new(components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((byte a, byte r, byte g, byte b) components) => new(components.a, components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((int r, int g, int b) components) => new(components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((int a, int r, int g, int b) components) => new(components.a, components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((float r, float g, float b) components) => new(components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((float a, float r, float g, float b) components) => new(components.a, components.r, components.g, components.b);
#endregion
A = a.Clamp(0, 1);
R = r.Clamp(0, 1);
G = g.Clamp(0, 1);
B = b.Clamp(0, 1);
}
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct by cloning a existing <see cref="T:RGB.NET.Core.Color" />.
/// </summary>
/// <param name="color">The <see cref="T:RGB.NET.Core.Color" /> the values are copied from.</param>
public Color(in Color color)
: this(color.A, color.R, color.G, color.B)
{ }
#endregion
#region Methods
/// <summary>
/// Gets a human-readable string, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <returns>A string that contains the individual byte values of this <see cref="Color"/>. Default format: "[A: 255, R: 255, G: 0, B: 0]".</returns>
public override string ToString() => Behavior.ToString(this);
/// <summary>
/// Tests whether the specified object is a <see cref="Color" /> and is equivalent to this <see cref="Color" />, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Color" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj) => Behavior.Equals(this, obj);
/// <summary>
/// Returns a hash code for this <see cref="Color" />, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Color" />.</returns>
// ReSharper disable once NonReadonlyMemberInGetHashCode
public override int GetHashCode() => Behavior.GetHashCode(this);
/// <summary>
/// Blends a <see cref="Color"/> over this color, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <param name="color">The <see cref="Color"/> to blend.</param>
public Color Blend(in Color color) => Behavior.Blend(this, color);
#endregion
#region Operators
/// <summary>
/// Blends the provided colors as if <see cref="Blend"/> would've been called on <paramref name="color1" />.
/// </summary>
/// <param name="color1">The base color.</param>
/// <param name="color2">The color to blend.</param>
/// <returns>The blended color.</returns>
public static Color operator +(in Color color1, in Color color2) => color1.Blend(color2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Color" /> are equal.
/// </summary>
/// <param name="color1">The first <see cref="Color" /> to compare.</param>
/// <param name="color2">The second <see cref="Color" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="color1" /> and <paramref name="color2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(in Color color1, in Color color2) => color1.Equals(color2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Color" /> are equal.
/// </summary>
/// <param name="color1">The first <see cref="Color" /> to compare.</param>
/// <param name="color2">The second <see cref="Color" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="color1" /> and <paramref name="color2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(in Color color1, in Color color2) => !(color1 == color2);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((byte r, byte g, byte b) components) => new(components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((byte a, byte r, byte g, byte b) components) => new(components.a, components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((int r, int g, int b) components) => new(components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((int a, int r, int g, int b) components) => new(components.a, components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((float r, float g, float b) components) => new(components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((float a, float r, float g, float b) components) => new(components.a, components.r, components.g, components.b);
#endregion
}

View File

@ -2,228 +2,227 @@
// ReSharper disable UnusedMember.Global
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Contains helper-methods and extension for the <see cref="Color"/>-type to work in the HSV color space.
/// </summary>
public static class HSVColor
{
#region Getter
/// <summary>
/// Contains helper-methods and extension for the <see cref="Color"/>-type to work in the HSV color space.
/// Gets the hue component value (HSV-color space) of this <see cref="Color"/> as degree in the range [0..360].
/// </summary>
public static class HSVColor
/// <param name="color">The color to get the value from.</param>
/// <returns>The hue component value of the color.</returns>
public static float GetHue(this in Color color) => color.GetHSV().hue;
/// <summary>
/// Gets the saturation component value (HSV-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The saturation component value of the color.</returns>
public static float GetSaturation(this in Color color) => color.GetHSV().saturation;
/// <summary>
/// Gets the value component value (HSV-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The value component value of the color.</returns>
public static float GetValue(this in Color color) => color.GetHSV().value;
/// <summary>
/// Gets the hue, saturation and value component values (HSV-color space) of this <see cref="Color"/>.
/// Hue as degree in the range [0..1].
/// Saturation in the range [0..1].
/// Value in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the hue, saturation and value component value of the color.</returns>
public static (float hue, float saturation, float value) GetHSV(this in Color color)
=> CaclulateHSVFromRGB(color.R, color.G, color.B);
#endregion
#region Manipulation
/// <summary>
/// Adds the specified HSV values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="hue">The hue value to add.</param>
/// <param name="saturation">The saturation value to add.</param>
/// <param name="value">The value value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddHSV(this in Color color, float hue = 0, float saturation = 0, float value = 0)
{
#region Getter
/// <summary>
/// Gets the hue component value (HSV-color space) of this <see cref="Color"/> as degree in the range [0..360].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The hue component value of the color.</returns>
public static float GetHue(this in Color color) => color.GetHSV().hue;
/// <summary>
/// Gets the saturation component value (HSV-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The saturation component value of the color.</returns>
public static float GetSaturation(this in Color color) => color.GetHSV().saturation;
/// <summary>
/// Gets the value component value (HSV-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The value component value of the color.</returns>
public static float GetValue(this in Color color) => color.GetHSV().value;
/// <summary>
/// Gets the hue, saturation and value component values (HSV-color space) of this <see cref="Color"/>.
/// Hue as degree in the range [0..1].
/// Saturation in the range [0..1].
/// Value in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the hue, saturation and value component value of the color.</returns>
public static (float hue, float saturation, float value) GetHSV(this in Color color)
=> CaclulateHSVFromRGB(color.R, color.G, color.B);
#endregion
#region Manipulation
/// <summary>
/// Adds the specified HSV values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="hue">The hue value to add.</param>
/// <param name="saturation">The saturation value to add.</param>
/// <param name="value">The value value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddHSV(this in Color color, float hue = 0, float saturation = 0, float value = 0)
{
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue + hue, cSaturation + saturation, cValue + value);
}
/// <summary>
/// Subtracts the specified HSV values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="hue">The hue value to subtract.</param>
/// <param name="saturation">The saturation value to subtract.</param>
/// <param name="value">The value value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractHSV(this in Color color, float hue = 0, float saturation = 0, float value = 0)
{
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue - hue, cSaturation - saturation, cValue - value);
}
/// <summary>
/// Multiplies the specified HSV values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="hue">The hue value to multiply.</param>
/// <param name="saturation">The saturation value to multiply.</param>
/// <param name="value">The value value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyHSV(this in Color color, float hue = 1, float saturation = 1, float value = 1)
{
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue * hue, cSaturation * saturation, cValue * value);
}
/// <summary>
/// Divides the specified HSV values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="hue">The hue value to divide.</param>
/// <param name="saturation">The saturation value to divide.</param>
/// <param name="value">The value value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideHSV(this in Color color, float hue = 1, float saturation = 1, float value = 1)
{
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue / hue, cSaturation / saturation, cValue / value);
}
/// <summary>
/// Sets the specified hue value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="hue">The hue value to set.</param>
/// <param name="saturation">The saturation value to set.</param>
/// <param name="value">The value value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetHSV(this in Color color, float? hue = null, float? saturation = null, float? value = null)
{
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, hue ?? cHue, saturation ?? cSaturation, value ?? cValue);
}
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using HSV-Values.
/// </summary>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float hue, float saturation, float value)
=> Create(1.0f, hue, saturation, value);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using AHSV-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(byte a, float hue, float saturation, float value)
=> Create((float)a / byte.MaxValue, hue, saturation, value);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using AHSV-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(int a, float hue, float saturation, float value)
=> Create((float)a / byte.MaxValue, hue, saturation, value);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using AHSV-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float a, float hue, float saturation, float value)
{
(float r, float g, float b) = CalculateRGBFromHSV(hue, saturation, value);
return new Color(a, r, g, b);
}
#endregion
#region Helper
private static (float h, float s, float v) CaclulateHSVFromRGB(float r, float g, float b)
{
if (r.EqualsInTolerance(g) && g.EqualsInTolerance(b)) return (0, 0, r);
float min = Math.Min(Math.Min(r, g), b);
float max = Math.Max(Math.Max(r, g), b);
float hue;
if (max.EqualsInTolerance(min))
hue = 0;
else if (max.EqualsInTolerance(r)) // r is max
hue = (g - b) / (max - min);
else if (max.EqualsInTolerance(g)) // g is max
hue = 2.0f + ((b - r) / (max - min));
else // b is max
hue = 4.0f + ((r - g) / (max - min));
hue *= 60.0f;
hue = hue.Wrap(0, 360);
float saturation = max.EqualsInTolerance(0) ? 0 : 1.0f - (min / max);
float value = Math.Max(r, Math.Max(g, b));
return (hue, saturation, value);
}
private static (float r, float g, float b) CalculateRGBFromHSV(float h, float s, float v)
{
h = h.Wrap(0, 360);
s = s.Clamp(0, 1);
v = v.Clamp(0, 1);
if (s <= 0.0)
return (v, v, v);
float hh = h / 60.0f;
int i = (int)hh;
float ff = hh - i;
float p = v * (1.0f - s);
float q = v * (1.0f - (s * ff));
float t = v * (1.0f - (s * (1.0f - ff)));
return i switch
{
0 => (v, t, p),
1 => (q, v, p),
2 => (p, v, t),
3 => (p, q, v),
4 => (t, p, v),
_ => (v, p, q)
};
}
#endregion
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue + hue, cSaturation + saturation, cValue + value);
}
}
/// <summary>
/// Subtracts the specified HSV values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="hue">The hue value to subtract.</param>
/// <param name="saturation">The saturation value to subtract.</param>
/// <param name="value">The value value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractHSV(this in Color color, float hue = 0, float saturation = 0, float value = 0)
{
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue - hue, cSaturation - saturation, cValue - value);
}
/// <summary>
/// Multiplies the specified HSV values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="hue">The hue value to multiply.</param>
/// <param name="saturation">The saturation value to multiply.</param>
/// <param name="value">The value value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyHSV(this in Color color, float hue = 1, float saturation = 1, float value = 1)
{
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue * hue, cSaturation * saturation, cValue * value);
}
/// <summary>
/// Divides the specified HSV values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="hue">The hue value to divide.</param>
/// <param name="saturation">The saturation value to divide.</param>
/// <param name="value">The value value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideHSV(this in Color color, float hue = 1, float saturation = 1, float value = 1)
{
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue / hue, cSaturation / saturation, cValue / value);
}
/// <summary>
/// Sets the specified hue value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="hue">The hue value to set.</param>
/// <param name="saturation">The saturation value to set.</param>
/// <param name="value">The value value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetHSV(this in Color color, float? hue = null, float? saturation = null, float? value = null)
{
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, hue ?? cHue, saturation ?? cSaturation, value ?? cValue);
}
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using HSV-Values.
/// </summary>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float hue, float saturation, float value)
=> Create(1.0f, hue, saturation, value);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using AHSV-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(byte a, float hue, float saturation, float value)
=> Create((float)a / byte.MaxValue, hue, saturation, value);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using AHSV-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(int a, float hue, float saturation, float value)
=> Create((float)a / byte.MaxValue, hue, saturation, value);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using AHSV-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float a, float hue, float saturation, float value)
{
(float r, float g, float b) = CalculateRGBFromHSV(hue, saturation, value);
return new Color(a, r, g, b);
}
#endregion
#region Helper
private static (float h, float s, float v) CaclulateHSVFromRGB(float r, float g, float b)
{
if (r.EqualsInTolerance(g) && g.EqualsInTolerance(b)) return (0, 0, r);
float min = Math.Min(Math.Min(r, g), b);
float max = Math.Max(Math.Max(r, g), b);
float hue;
if (max.EqualsInTolerance(min))
hue = 0;
else if (max.EqualsInTolerance(r)) // r is max
hue = (g - b) / (max - min);
else if (max.EqualsInTolerance(g)) // g is max
hue = 2.0f + ((b - r) / (max - min));
else // b is max
hue = 4.0f + ((r - g) / (max - min));
hue *= 60.0f;
hue = hue.Wrap(0, 360);
float saturation = max.EqualsInTolerance(0) ? 0 : 1.0f - (min / max);
float value = Math.Max(r, Math.Max(g, b));
return (hue, saturation, value);
}
private static (float r, float g, float b) CalculateRGBFromHSV(float h, float s, float v)
{
h = h.Wrap(0, 360);
s = s.Clamp(0, 1);
v = v.Clamp(0, 1);
if (s <= 0.0)
return (v, v, v);
float hh = h / 60.0f;
int i = (int)hh;
float ff = hh - i;
float p = v * (1.0f - s);
float q = v * (1.0f - (s * ff));
float t = v * (1.0f - (s * (1.0f - ff)));
return i switch
{
0 => (v, t, p),
1 => (q, v, p),
2 => (p, v, t),
3 => (p, q, v),
4 => (t, p, v),
_ => (v, p, q)
};
}
#endregion
}

View File

@ -2,211 +2,210 @@
// ReSharper disable UnusedMember.Global
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Contains helper-methods and extension for the <see cref="Color"/>-type to work in the Hcl color space.
/// </summary>
public static class HclColor
{
#region Getter
/// <summary>
/// Contains helper-methods and extension for the <see cref="Color"/>-type to work in the Hcl color space.
/// Gets the H component value (Hcl-color space) of this <see cref="Color"/> in the range [0..360].
/// </summary>
public static class HclColor
/// <param name="color">The color to get the value from.</param>
/// <returns>The H component value of the color. </returns>
public static float GetHclH(this in Color color) => color.GetHcl().h;
/// <summary>
/// Gets the c component value (Hcl-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The c component value of the color. </returns>
public static float GetHclC(this in Color color) => color.GetHcl().c;
/// <summary>
/// Gets the l component value (Hcl-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The l component value of the color. </returns>
public static float GetHclL(this in Color color) => color.GetHcl().l;
/// <summary>
/// Gets the H, c and l component values (Hcl-color space) of this <see cref="Color"/>.
/// H in the range [0..360].
/// c in the range [0..1].
/// l in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the H, c and l component value of the color.</returns>
public static (float h, float c, float l) GetHcl(this in Color color)
=> CalculateHclFromRGB(color.R, color.G, color.B);
#endregion
#region Manipulation
/// <summary>
/// Adds the specified Hcl values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="h">The H value to add.</param>
/// <param name="c">The c value to add.</param>
/// <param name="l">The l value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddHcl(this in Color color, float h = 0, float c = 0, float l = 0)
{
#region Getter
/// <summary>
/// Gets the H component value (Hcl-color space) of this <see cref="Color"/> in the range [0..360].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The H component value of the color. </returns>
public static float GetHclH(this in Color color) => color.GetHcl().h;
/// <summary>
/// Gets the c component value (Hcl-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The c component value of the color. </returns>
public static float GetHclC(this in Color color) => color.GetHcl().c;
/// <summary>
/// Gets the l component value (Hcl-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The l component value of the color. </returns>
public static float GetHclL(this in Color color) => color.GetHcl().l;
/// <summary>
/// Gets the H, c and l component values (Hcl-color space) of this <see cref="Color"/>.
/// H in the range [0..360].
/// c in the range [0..1].
/// l in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the H, c and l component value of the color.</returns>
public static (float h, float c, float l) GetHcl(this in Color color)
=> CalculateHclFromRGB(color.R, color.G, color.B);
#endregion
#region Manipulation
/// <summary>
/// Adds the specified Hcl values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="h">The H value to add.</param>
/// <param name="c">The c value to add.</param>
/// <param name="l">The l value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddHcl(this in Color color, float h = 0, float c = 0, float l = 0)
{
(float cH, float cC, float cL) = color.GetHcl();
return Create(color.A, cH + h, cC + c, cL + l);
}
/// <summary>
/// Subtracts the specified Hcl values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="h">The H value to subtract.</param>
/// <param name="c">The c value to subtract.</param>
/// <param name="l">The l value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractHcl(this in Color color, float h = 0, float c = 0, float l = 0)
{
(float cH, float cC, float cL) = color.GetHcl();
return Create(color.A, cH - h, cC - c, cL - l);
}
/// <summary>
/// Multiplies the specified Hcl values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="h">The H value to multiply.</param>
/// <param name="c">The c value to multiply.</param>
/// <param name="l">The l value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyHcl(this in Color color, float h = 1, float c = 1, float l = 1)
{
(float cH, float cC, float cL) = color.GetHcl();
return Create(color.A, cH * h, cC * c, cL * l);
}
/// <summary>
/// Divides the specified Hcl values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="h">The H value to divide.</param>
/// <param name="c">The c value to divide.</param>
/// <param name="l">The l value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideHcl(this in Color color, float h = 1, float c = 1, float l = 1)
{
(float cH, float cC, float cL) = color.GetHcl();
return Create(color.A, cH / h, cC / c, cL / l);
}
/// <summary>
/// Sets the specified X value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="h">The H value to set.</param>
/// <param name="c">The c value to set.</param>
/// <param name="l">The l value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetHcl(this in Color color, float? h = null, float? c = null, float? l = null)
{
(float cH, float cC, float cL) = color.GetHcl();
return Create(color.A, h ?? cH, c ?? cC, l ?? cL);
}
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using Hcl-Values.
/// </summary>
/// <param name="h">The H component value of this <see cref="Color"/>.</param>
/// <param name="c">The c component value of this <see cref="Color"/>.</param>
/// <param name="l">The l component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float h, float c, float l)
=> Create(1.0f, h, c, l);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Hcl-Values.
/// </summary>
/// <param name="alpha">The alphc component value of this <see cref="Color"/>.</param>
/// <param name="h">The H component value of this <see cref="Color"/>.</param>
/// <param name="c">The c component value of this <see cref="Color"/>.</param>
/// <param name="l">The l component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(byte alpha, float h, float c, float l)
=> Create((float)alpha / byte.MaxValue, h, c, l);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Hcl-Values.
/// </summary>
/// <param name="alpha">The alphc component value of this <see cref="Color"/>.</param>
/// <param name="h">The H component value of this <see cref="Color"/>.</param>
/// <param name="c">The c component value of this <see cref="Color"/>.</param>
/// <param name="l">The l component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(int alpha, float h, float c, float l)
=> Create((float)alpha / byte.MaxValue, h, c, l);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Hcl-Values.
/// </summary>
/// <param name="alpha">The alphc component value of this <see cref="Color"/>.</param>
/// <param name="h">The H component value of this <see cref="Color"/>.</param>
/// <param name="c">The c component value of this <see cref="Color"/>.</param>
/// <param name="l">The l component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float alpha, float h, float c, float l)
{
(float r, float g, float b) = CalculateRGBFromHcl(h, c, l);
return new Color(alpha, r, g, b);
}
#endregion
#region Helper
private static (float h, float c, float l) CalculateHclFromRGB(float r, float g, float b)
{
const float RADIANS_DEGREES_CONVERSION = 180.0f / MathF.PI;
// ReSharper disable once InconsistentNaming - b is used above
(float l, float a, float _b) = LabColor.CalculateLabFromRGB(r, g, b);
float h, c;
if (r.EqualsInTolerance(g) && r.EqualsInTolerance(b)) //DarthAffe 26.02.2021: The cumulated rounding errors are big enough to cause problems in that case
{
h = 0;
c = 0;
}
else
{
h = MathF.Atan2(_b, a);
if (h >= 0) h *= RADIANS_DEGREES_CONVERSION;
else h = 360 - (-h * RADIANS_DEGREES_CONVERSION);
c = MathF.Sqrt((a * a) + (_b * _b));
}
return (h, c, l);
}
private static (float r, float g, float b) CalculateRGBFromHcl(float h, float c, float l)
{
const float DEGREES_RADIANS_CONVERSION = MathF.PI / 180.0f;
h *= DEGREES_RADIANS_CONVERSION;
float a = c * MathF.Cos(h);
float b = c * MathF.Sin(h);
return LabColor.CalculateRGBFromLab(l, a, b);
}
#endregion
(float cH, float cC, float cL) = color.GetHcl();
return Create(color.A, cH + h, cC + c, cL + l);
}
}
/// <summary>
/// Subtracts the specified Hcl values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="h">The H value to subtract.</param>
/// <param name="c">The c value to subtract.</param>
/// <param name="l">The l value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractHcl(this in Color color, float h = 0, float c = 0, float l = 0)
{
(float cH, float cC, float cL) = color.GetHcl();
return Create(color.A, cH - h, cC - c, cL - l);
}
/// <summary>
/// Multiplies the specified Hcl values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="h">The H value to multiply.</param>
/// <param name="c">The c value to multiply.</param>
/// <param name="l">The l value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyHcl(this in Color color, float h = 1, float c = 1, float l = 1)
{
(float cH, float cC, float cL) = color.GetHcl();
return Create(color.A, cH * h, cC * c, cL * l);
}
/// <summary>
/// Divides the specified Hcl values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="h">The H value to divide.</param>
/// <param name="c">The c value to divide.</param>
/// <param name="l">The l value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideHcl(this in Color color, float h = 1, float c = 1, float l = 1)
{
(float cH, float cC, float cL) = color.GetHcl();
return Create(color.A, cH / h, cC / c, cL / l);
}
/// <summary>
/// Sets the specified X value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="h">The H value to set.</param>
/// <param name="c">The c value to set.</param>
/// <param name="l">The l value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetHcl(this in Color color, float? h = null, float? c = null, float? l = null)
{
(float cH, float cC, float cL) = color.GetHcl();
return Create(color.A, h ?? cH, c ?? cC, l ?? cL);
}
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using Hcl-Values.
/// </summary>
/// <param name="h">The H component value of this <see cref="Color"/>.</param>
/// <param name="c">The c component value of this <see cref="Color"/>.</param>
/// <param name="l">The l component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float h, float c, float l)
=> Create(1.0f, h, c, l);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Hcl-Values.
/// </summary>
/// <param name="alpha">The alphc component value of this <see cref="Color"/>.</param>
/// <param name="h">The H component value of this <see cref="Color"/>.</param>
/// <param name="c">The c component value of this <see cref="Color"/>.</param>
/// <param name="l">The l component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(byte alpha, float h, float c, float l)
=> Create((float)alpha / byte.MaxValue, h, c, l);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Hcl-Values.
/// </summary>
/// <param name="alpha">The alphc component value of this <see cref="Color"/>.</param>
/// <param name="h">The H component value of this <see cref="Color"/>.</param>
/// <param name="c">The c component value of this <see cref="Color"/>.</param>
/// <param name="l">The l component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(int alpha, float h, float c, float l)
=> Create((float)alpha / byte.MaxValue, h, c, l);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Hcl-Values.
/// </summary>
/// <param name="alpha">The alphc component value of this <see cref="Color"/>.</param>
/// <param name="h">The H component value of this <see cref="Color"/>.</param>
/// <param name="c">The c component value of this <see cref="Color"/>.</param>
/// <param name="l">The l component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float alpha, float h, float c, float l)
{
(float r, float g, float b) = CalculateRGBFromHcl(h, c, l);
return new Color(alpha, r, g, b);
}
#endregion
#region Helper
private static (float h, float c, float l) CalculateHclFromRGB(float r, float g, float b)
{
const float RADIANS_DEGREES_CONVERSION = 180.0f / MathF.PI;
// ReSharper disable once InconsistentNaming - b is used above
(float l, float a, float _b) = LabColor.CalculateLabFromRGB(r, g, b);
float h, c;
if (r.EqualsInTolerance(g) && r.EqualsInTolerance(b)) //DarthAffe 26.02.2021: The cumulated rounding errors are big enough to cause problems in that case
{
h = 0;
c = 0;
}
else
{
h = MathF.Atan2(_b, a);
if (h >= 0) h *= RADIANS_DEGREES_CONVERSION;
else h = 360 - (-h * RADIANS_DEGREES_CONVERSION);
c = MathF.Sqrt((a * a) + (_b * _b));
}
return (h, c, l);
}
private static (float r, float g, float b) CalculateRGBFromHcl(float h, float c, float l)
{
const float DEGREES_RADIANS_CONVERSION = MathF.PI / 180.0f;
h *= DEGREES_RADIANS_CONVERSION;
float a = c * MathF.Cos(h);
float b = c * MathF.Sin(h);
return LabColor.CalculateRGBFromLab(l, a, b);
}
#endregion
}

View File

@ -2,227 +2,226 @@
// ReSharper disable UnusedMember.Global
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Contains helper-methods and extension for the <see cref="Color"/>-type to work in the Lab color space.
/// </summary>
public static class LabColor
{
#region Getter
/// <summary>
/// Contains helper-methods and extension for the <see cref="Color"/>-type to work in the Lab color space.
/// Gets the L component value (Lab-color space) of this <see cref="Color"/> in the range [0..100].
/// </summary>
public static class LabColor
/// <param name="color">The color to get the value from.</param>
/// <returns>The L component value of the color.</returns>
public static float GetLabL(this in Color color) => color.GetLab().l;
/// <summary>
/// Gets the a component value (Lab-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The a component value of the color.</returns>
public static float GetLabA(this in Color color) => color.GetLab().a;
/// <summary>
/// Gets the b component value (Lab-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The b component value of the color.</returns>
public static float GetLabB(this in Color color) => color.GetLab().b;
/// <summary>
/// Gets the L, a and b component values (Lab-color space) of this <see cref="Color"/>.
/// L in the range [0..100].
/// a in the range [0..1].
/// b in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the L, a and b component value of the color.</returns>
public static (float l, float a, float b) GetLab(this in Color color)
=> CalculateLabFromRGB(color.R, color.G, color.B);
#endregion
#region Manipulation
/// <summary>
/// Adds the specified Lab values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="l">The L value to add.</param>
/// <param name="a">The a value to add.</param>
/// <param name="b">The b value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddLab(this in Color color, float l = 0, float a = 0, float b = 0)
{
#region Getter
/// <summary>
/// Gets the L component value (Lab-color space) of this <see cref="Color"/> in the range [0..100].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The L component value of the color.</returns>
public static float GetLabL(this in Color color) => color.GetLab().l;
/// <summary>
/// Gets the a component value (Lab-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The a component value of the color.</returns>
public static float GetLabA(this in Color color) => color.GetLab().a;
/// <summary>
/// Gets the b component value (Lab-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The b component value of the color.</returns>
public static float GetLabB(this in Color color) => color.GetLab().b;
/// <summary>
/// Gets the L, a and b component values (Lab-color space) of this <see cref="Color"/>.
/// L in the range [0..100].
/// a in the range [0..1].
/// b in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the L, a and b component value of the color.</returns>
public static (float l, float a, float b) GetLab(this in Color color)
=> CalculateLabFromRGB(color.R, color.G, color.B);
#endregion
#region Manipulation
/// <summary>
/// Adds the specified Lab values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="l">The L value to add.</param>
/// <param name="a">The a value to add.</param>
/// <param name="b">The b value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddLab(this in Color color, float l = 0, float a = 0, float b = 0)
{
(float cL, float cA, float cB) = color.GetLab();
return Create(color.A, cL + l, cA + a, cB + b);
}
/// <summary>
/// Subtracts the specified Lab values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="l">The L value to subtract.</param>
/// <param name="a">The a value to subtract.</param>
/// <param name="b">The b value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractLab(this in Color color, float l = 0, float a = 0, float b = 0)
{
(float cL, float cA, float cB) = color.GetLab();
return Create(color.A, cL - l, cA - a, cB - b);
}
/// <summary>
/// Multiplies the specified Lab values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="l">The L value to multiply.</param>
/// <param name="a">The a value to multiply.</param>
/// <param name="b">The b value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyLab(this in Color color, float l = 1, float a = 1, float b = 1)
{
(float cL, float cA, float cB) = color.GetLab();
return Create(color.A, cL * l, cA * a, cB * b);
}
/// <summary>
/// Divides the specified Lab values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="l">The L value to divide.</param>
/// <param name="a">The a value to divide.</param>
/// <param name="b">The b value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideLab(this in Color color, float l = 1, float a = 1, float b = 1)
{
(float cL, float cA, float cB) = color.GetLab();
return Create(color.A, cL / l, cA / a, cB / b);
}
/// <summary>
/// Sets the specified X valueof this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="l">The L value to set.</param>
/// <param name="a">The a value to set.</param>
/// <param name="b">The b value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetLab(this in Color color, float? l = null, float? a = null, float? b = null)
{
(float cL, float cA, float cB) = color.GetLab();
return Create(color.A, l ?? cL, a ?? cA, b ?? cB);
}
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using Lab-Values.
/// </summary>
/// <param name="l">The L component value of this <see cref="Color"/>.</param>
/// <param name="a">The a component value of this <see cref="Color"/>.</param>
/// <param name="b">The b component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float l, float a, float b)
=> Create(1.0f, l, a, b);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Lab-Values.
/// </summary>
/// <param name="alpha">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="l">The L component value of this <see cref="Color"/>.</param>
/// <param name="a">The a component value of this <see cref="Color"/>.</param>
/// <param name="b">The b component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(byte alpha, float l, float a, float b)
=> Create((float)alpha / byte.MaxValue, l, a, b);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Lab-Values.
/// </summary>
/// <param name="alpha">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="l">The L component value of this <see cref="Color"/>.</param>
/// <param name="a">The a component value of this <see cref="Color"/>.</param>
/// <param name="b">The b component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(int alpha, float l, float a, float b)
=> Create((float)alpha / byte.MaxValue, l, a, b);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Lab-Values.
/// </summary>
/// <param name="alpha">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="l">The L component value of this <see cref="Color"/>.</param>
/// <param name="a">The a component value of this <see cref="Color"/>.</param>
/// <param name="b">The b component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float alpha, float l, float a, float b)
{
// ReSharper disable once InconsistentNaming - b is used above
(float r, float g, float _b) = CalculateRGBFromLab(l, a, b);
return new Color(alpha, r, g, _b);
}
#endregion
#region Helper
internal static (float l, float a, float b) CalculateLabFromRGB(float r, float g, float b)
{
(float x, float y, float z) = XYZColor.CaclulateXYZFromRGB(r, g, b);
return CaclulateLabFromXYZ(x, y, z);
}
internal static (float r, float g, float b) CalculateRGBFromLab(float l, float a, float b)
{
(float x, float y, float z) = CalculateXYZFromLab(l, a, b);
return XYZColor.CalculateRGBFromXYZ(x, y, z);
}
private static (float l, float a, float b) CaclulateLabFromXYZ(float x, float y, float z)
{
const float ONETHRID = 1.0f / 3.0f;
const float FACTOR2 = 16.0f / 116.0f;
x /= 95.047f;
y /= 100.0f;
z /= 108.883f;
x = ((x > 0.008856f) ? (MathF.Pow(x, ONETHRID)) : ((7.787f * x) + FACTOR2));
y = ((y > 0.008856f) ? (MathF.Pow(y, ONETHRID)) : ((7.787f * y) + FACTOR2));
z = ((z > 0.008856f) ? (MathF.Pow(z, ONETHRID)) : ((7.787f * z) + FACTOR2));
float l = (116.0f * y) - 16.0f;
float a = 500.0f * (x - y);
float b = 200.0f * (y - z);
return (l, a, b);
}
private static (float x, float y, float z) CalculateXYZFromLab(float l, float a, float b)
{
const float FACTOR2 = 16.0f / 116.0f;
float y = (l + 16.0f) / 116.0f;
float x = (a / 500.0f) + y;
float z = y - (b / 200.0f);
float powX = MathF.Pow(x, 3.0f);
float powY = MathF.Pow(y, 3.0f);
float powZ = MathF.Pow(z, 3.0f);
x = ((powX > 0.008856f) ? (powX) : ((x - FACTOR2) / 7.787f));
y = ((powY > 0.008856f) ? (powY) : ((y - FACTOR2) / 7.787f));
z = ((powZ > 0.008856f) ? (powZ) : ((z - FACTOR2) / 7.787f));
return (x * 95.047f, y * 100.0f, z * 108.883f);
}
#endregion
(float cL, float cA, float cB) = color.GetLab();
return Create(color.A, cL + l, cA + a, cB + b);
}
}
/// <summary>
/// Subtracts the specified Lab values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="l">The L value to subtract.</param>
/// <param name="a">The a value to subtract.</param>
/// <param name="b">The b value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractLab(this in Color color, float l = 0, float a = 0, float b = 0)
{
(float cL, float cA, float cB) = color.GetLab();
return Create(color.A, cL - l, cA - a, cB - b);
}
/// <summary>
/// Multiplies the specified Lab values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="l">The L value to multiply.</param>
/// <param name="a">The a value to multiply.</param>
/// <param name="b">The b value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyLab(this in Color color, float l = 1, float a = 1, float b = 1)
{
(float cL, float cA, float cB) = color.GetLab();
return Create(color.A, cL * l, cA * a, cB * b);
}
/// <summary>
/// Divides the specified Lab values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="l">The L value to divide.</param>
/// <param name="a">The a value to divide.</param>
/// <param name="b">The b value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideLab(this in Color color, float l = 1, float a = 1, float b = 1)
{
(float cL, float cA, float cB) = color.GetLab();
return Create(color.A, cL / l, cA / a, cB / b);
}
/// <summary>
/// Sets the specified X valueof this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="l">The L value to set.</param>
/// <param name="a">The a value to set.</param>
/// <param name="b">The b value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetLab(this in Color color, float? l = null, float? a = null, float? b = null)
{
(float cL, float cA, float cB) = color.GetLab();
return Create(color.A, l ?? cL, a ?? cA, b ?? cB);
}
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using Lab-Values.
/// </summary>
/// <param name="l">The L component value of this <see cref="Color"/>.</param>
/// <param name="a">The a component value of this <see cref="Color"/>.</param>
/// <param name="b">The b component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float l, float a, float b)
=> Create(1.0f, l, a, b);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Lab-Values.
/// </summary>
/// <param name="alpha">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="l">The L component value of this <see cref="Color"/>.</param>
/// <param name="a">The a component value of this <see cref="Color"/>.</param>
/// <param name="b">The b component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(byte alpha, float l, float a, float b)
=> Create((float)alpha / byte.MaxValue, l, a, b);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Lab-Values.
/// </summary>
/// <param name="alpha">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="l">The L component value of this <see cref="Color"/>.</param>
/// <param name="a">The a component value of this <see cref="Color"/>.</param>
/// <param name="b">The b component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(int alpha, float l, float a, float b)
=> Create((float)alpha / byte.MaxValue, l, a, b);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Lab-Values.
/// </summary>
/// <param name="alpha">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="l">The L component value of this <see cref="Color"/>.</param>
/// <param name="a">The a component value of this <see cref="Color"/>.</param>
/// <param name="b">The b component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float alpha, float l, float a, float b)
{
// ReSharper disable once InconsistentNaming - b is used above
(float r, float g, float _b) = CalculateRGBFromLab(l, a, b);
return new Color(alpha, r, g, _b);
}
#endregion
#region Helper
internal static (float l, float a, float b) CalculateLabFromRGB(float r, float g, float b)
{
(float x, float y, float z) = XYZColor.CaclulateXYZFromRGB(r, g, b);
return CaclulateLabFromXYZ(x, y, z);
}
internal static (float r, float g, float b) CalculateRGBFromLab(float l, float a, float b)
{
(float x, float y, float z) = CalculateXYZFromLab(l, a, b);
return XYZColor.CalculateRGBFromXYZ(x, y, z);
}
private static (float l, float a, float b) CaclulateLabFromXYZ(float x, float y, float z)
{
const float ONETHRID = 1.0f / 3.0f;
const float FACTOR2 = 16.0f / 116.0f;
x /= 95.047f;
y /= 100.0f;
z /= 108.883f;
x = ((x > 0.008856f) ? (MathF.Pow(x, ONETHRID)) : ((7.787f * x) + FACTOR2));
y = ((y > 0.008856f) ? (MathF.Pow(y, ONETHRID)) : ((7.787f * y) + FACTOR2));
z = ((z > 0.008856f) ? (MathF.Pow(z, ONETHRID)) : ((7.787f * z) + FACTOR2));
float l = (116.0f * y) - 16.0f;
float a = 500.0f * (x - y);
float b = 200.0f * (y - z);
return (l, a, b);
}
private static (float x, float y, float z) CalculateXYZFromLab(float l, float a, float b)
{
const float FACTOR2 = 16.0f / 116.0f;
float y = (l + 16.0f) / 116.0f;
float x = (a / 500.0f) + y;
float z = y - (b / 200.0f);
float powX = MathF.Pow(x, 3.0f);
float powY = MathF.Pow(y, 3.0f);
float powZ = MathF.Pow(z, 3.0f);
x = ((powX > 0.008856f) ? (powX) : ((x - FACTOR2) / 7.787f));
y = ((powY > 0.008856f) ? (powY) : ((y - FACTOR2) / 7.787f));
z = ((powZ > 0.008856f) ? (powZ) : ((z - FACTOR2) / 7.787f));
return (x * 95.047f, y * 100.0f, z * 108.883f);
}
#endregion
}

View File

@ -2,295 +2,294 @@
// ReSharper disable UnusedMember.Global
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Contains helper-methods and extension for the <see cref="Color"/>-type to work in the RGB color space.
/// </summary>
public static class RGBColor
{
#region Getter
/// <summary>
/// Contains helper-methods and extension for the <see cref="Color"/>-type to work in the RGB color space.
/// Gets the A component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
public static class RGBColor
/// <param name="color">The color to get the value from.</param>
/// <returns>The A component value of the color.</returns>
public static byte GetA(this in Color color) => color.A.GetByteValueFromPercentage();
/// <summary>
/// Gets the R component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The R component value of the color.</returns>
public static byte GetR(this in Color color) => color.R.GetByteValueFromPercentage();
/// <summary>
/// Gets the G component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The G component value of the color.</returns>
public static byte GetG(this in Color color) => color.G.GetByteValueFromPercentage();
/// <summary>
/// Gets the B component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The B component value of the color.</returns>
public static byte GetB(this in Color color) => color.B.GetByteValueFromPercentage();
/// <summary>
/// Gets the A, R, G and B component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the A, R, G and B component value of the color.</returns>
public static (byte a, byte r, byte g, byte b) GetRGBBytes(this in Color color)
=> (color.GetA(), color.GetR(), color.GetG(), color.GetB());
/// <summary>
/// Gets the A, R, G and B component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the A, R, G and B component value of the color.</returns>
public static (float a, float r, float g, float b) GetRGB(this in Color color)
=> (color.A, color.R, color.G, color.B);
#endregion
#region Manipulation
#region Add
/// <summary>
/// Adds the specified RGB values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to add.</param>
/// <param name="g">The green value to add.</param>
/// <param name="b">The blue value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddRGB(this in Color color, int r = 0, int g = 0, int b = 0)
=> new(color.A, color.GetR() + r, color.GetG() + g, color.GetB() + b);
/// <summary>
/// Adds the specified RGB-percent values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to add.</param>
/// <param name="g">The green value to add.</param>
/// <param name="b">The blue value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddRGB(this in Color color, float r = 0, float g = 0, float b = 0)
=> new(color.A, color.R + r, color.G + g, color.B + b);
/// <summary>
/// Adds the specified alpha value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddA(this in Color color, int a)
=> new(color.GetA() + a, color.R, color.G, color.B);
/// <summary>
/// Adds the specified alpha-percent value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddA(this in Color color, float a)
=> new(color.A + a, color.R, color.G, color.B);
#endregion
#region Subtract
/// <summary>
/// Subtracts the specified RGB values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to subtract.</param>
/// <param name="g">The green value to subtract.</param>
/// <param name="b">The blue value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractRGB(this in Color color, int r = 0, int g = 0, int b = 0)
=> new(color.A, color.GetR() - r, color.GetG() - g, color.GetB() - b);
/// <summary>
/// Subtracts the specified RGB values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to subtract.</param>
/// <param name="g">The green value to subtract.</param>
/// <param name="b">The blue value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractRGB(this in Color color, float r = 0, float g = 0, float b = 0)
=> new(color.A, color.R - r, color.G - g, color.B - b);
/// <summary>
/// Subtracts the specified alpha value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractA(this in Color color, int a)
=> new(color.GetA() - a, color.R, color.G, color.B);
/// <summary>
/// Subtracts the specified alpha-percent value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="aPercent">The alpha value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractA(this in Color color, float aPercent)
=> new(color.A - aPercent, color.R, color.G, color.B);
#endregion
#region Multiply
/// <summary>
/// Multiplies the specified RGB values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to multiply.</param>
/// <param name="g">The green value to multiply.</param>
/// <param name="b">The blue value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyRGB(this in Color color, float r = 1, float g = 1, float b = 1)
=> new(color.A, color.R * r, color.G * g, color.B * b);
/// <summary>
/// Multiplies the specified alpha value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyA(this in Color color, float a)
=> new(color.A * a, color.R, color.G, color.B);
#endregion
#region Divide
/// <summary>
/// Divides the specified RGB values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to divide.</param>
/// <param name="g">The green value to divide.</param>
/// <param name="b">The blue value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideRGB(this in Color color, float r = 1, float g = 1, float b = 1)
=> new(color.A, color.R / r, color.G / g, color.B / b);
/// <summary>
/// Divides the specified alpha value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideA(this in Color color, float a)
=> new(color.A / a, color.R, color.G, color.B);
#endregion
#region Set
/// <summary>
/// Sets the specified RGB value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to set.</param>
/// <param name="g">The green value to set.</param>
/// <param name="b">The blue value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetRGB(this in Color color, byte? r = null, byte? g = null, byte? b = null)
=> new(color.A, r ?? color.GetR(), g ?? color.GetG(), b ?? color.GetB());
/// <summary>
/// Sets the specified RGB value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to set.</param>
/// <param name="g">The green value to set.</param>
/// <param name="b">The blue value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetRGB(this in Color color, int? r = null, int? g = null, int? b = null)
=> new(color.A, r ?? color.GetR(), g ?? color.GetG(), b ?? color.GetB());
/// <summary>
/// Sets the specified RGB value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to set.</param>
/// <param name="g">The green value to set.</param>
/// <param name="b">The blue value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetRGB(this in Color color, float? r = null, float? g = null, float? b = null)
=> new(color.A, r ?? color.R, g ?? color.G, b ?? color.B);
/// <summary>
/// Sets the specified alpha value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetA(this in Color color, int a) => new(a, color.R, color.G, color.B);
/// <summary>
/// Sets the specified alpha value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetA(this in Color color, float a) => new(a, color.R, color.G, color.B);
#endregion
#endregion
#region Conversion
/// <summary>
/// Gets the current color as a RGB-HEX-string.
/// </summary>
/// <returns>The RGB-HEX-string.</returns>
public static string AsRGBHexString(this in Color color, bool leadingHash = true) => (leadingHash ? "#" : "") + ConversionHelper.ToHex(color.GetR(), color.GetG(), color.GetB());
/// <summary>
/// Gets the current color as a ARGB-HEX-string.
/// </summary>
/// <returns>The ARGB-HEX-string.</returns>
public static string AsARGBHexString(this in Color color, bool leadingHash = true) => (leadingHash ? "#" : "") + ConversionHelper.ToHex(color.GetA(), color.GetR(), color.GetG(), color.GetB());
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using a HEX-string.
/// </summary>
/// <param name="hexString">The HEX-representation of the color.</param>
/// <returns>The color created from the HEX-string.</returns>
public static Color FromHexString(string hexString)
{
#region Getter
if ((hexString == null) || (hexString.Length < 6))
throw new ArgumentException("Invalid hex string", nameof(hexString));
/// <summary>
/// Gets the A component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The A component value of the color.</returns>
public static byte GetA(this in Color color) => color.A.GetByteValueFromPercentage();
ReadOnlySpan<char> span = hexString.AsSpan();
if (span[0] == '#')
span = span[1..];
/// <summary>
/// Gets the R component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The R component value of the color.</returns>
public static byte GetR(this in Color color) => color.R.GetByteValueFromPercentage();
/// <summary>
/// Gets the G component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The G component value of the color.</returns>
public static byte GetG(this in Color color) => color.G.GetByteValueFromPercentage();
/// <summary>
/// Gets the B component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The B component value of the color.</returns>
public static byte GetB(this in Color color) => color.B.GetByteValueFromPercentage();
/// <summary>
/// Gets the A, R, G and B component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the A, R, G and B component value of the color.</returns>
public static (byte a, byte r, byte g, byte b) GetRGBBytes(this in Color color)
=> (color.GetA(), color.GetR(), color.GetG(), color.GetB());
/// <summary>
/// Gets the A, R, G and B component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the A, R, G and B component value of the color.</returns>
public static (float a, float r, float g, float b) GetRGB(this in Color color)
=> (color.A, color.R, color.G, color.B);
#endregion
#region Manipulation
#region Add
/// <summary>
/// Adds the specified RGB values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to add.</param>
/// <param name="g">The green value to add.</param>
/// <param name="b">The blue value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddRGB(this in Color color, int r = 0, int g = 0, int b = 0)
=> new(color.A, color.GetR() + r, color.GetG() + g, color.GetB() + b);
/// <summary>
/// Adds the specified RGB-percent values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to add.</param>
/// <param name="g">The green value to add.</param>
/// <param name="b">The blue value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddRGB(this in Color color, float r = 0, float g = 0, float b = 0)
=> new(color.A, color.R + r, color.G + g, color.B + b);
/// <summary>
/// Adds the specified alpha value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddA(this in Color color, int a)
=> new(color.GetA() + a, color.R, color.G, color.B);
/// <summary>
/// Adds the specified alpha-percent value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddA(this in Color color, float a)
=> new(color.A + a, color.R, color.G, color.B);
#endregion
#region Subtract
/// <summary>
/// Subtracts the specified RGB values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to subtract.</param>
/// <param name="g">The green value to subtract.</param>
/// <param name="b">The blue value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractRGB(this in Color color, int r = 0, int g = 0, int b = 0)
=> new(color.A, color.GetR() - r, color.GetG() - g, color.GetB() - b);
/// <summary>
/// Subtracts the specified RGB values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to subtract.</param>
/// <param name="g">The green value to subtract.</param>
/// <param name="b">The blue value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractRGB(this in Color color, float r = 0, float g = 0, float b = 0)
=> new(color.A, color.R - r, color.G - g, color.B - b);
/// <summary>
/// Subtracts the specified alpha value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractA(this in Color color, int a)
=> new(color.GetA() - a, color.R, color.G, color.B);
/// <summary>
/// Subtracts the specified alpha-percent value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="aPercent">The alpha value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractA(this in Color color, float aPercent)
=> new(color.A - aPercent, color.R, color.G, color.B);
#endregion
#region Multiply
/// <summary>
/// Multiplies the specified RGB values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to multiply.</param>
/// <param name="g">The green value to multiply.</param>
/// <param name="b">The blue value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyRGB(this in Color color, float r = 1, float g = 1, float b = 1)
=> new(color.A, color.R * r, color.G * g, color.B * b);
/// <summary>
/// Multiplies the specified alpha value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyA(this in Color color, float a)
=> new(color.A * a, color.R, color.G, color.B);
#endregion
#region Divide
/// <summary>
/// Divides the specified RGB values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to divide.</param>
/// <param name="g">The green value to divide.</param>
/// <param name="b">The blue value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideRGB(this in Color color, float r = 1, float g = 1, float b = 1)
=> new(color.A, color.R / r, color.G / g, color.B / b);
/// <summary>
/// Divides the specified alpha value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideA(this in Color color, float a)
=> new(color.A / a, color.R, color.G, color.B);
#endregion
#region Set
/// <summary>
/// Sets the specified RGB value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to set.</param>
/// <param name="g">The green value to set.</param>
/// <param name="b">The blue value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetRGB(this in Color color, byte? r = null, byte? g = null, byte? b = null)
=> new(color.A, r ?? color.GetR(), g ?? color.GetG(), b ?? color.GetB());
/// <summary>
/// Sets the specified RGB value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to set.</param>
/// <param name="g">The green value to set.</param>
/// <param name="b">The blue value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetRGB(this in Color color, int? r = null, int? g = null, int? b = null)
=> new(color.A, r ?? color.GetR(), g ?? color.GetG(), b ?? color.GetB());
/// <summary>
/// Sets the specified RGB value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to set.</param>
/// <param name="g">The green value to set.</param>
/// <param name="b">The blue value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetRGB(this in Color color, float? r = null, float? g = null, float? b = null)
=> new(color.A, r ?? color.R, g ?? color.G, b ?? color.B);
/// <summary>
/// Sets the specified alpha value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetA(this in Color color, int a) => new(a, color.R, color.G, color.B);
/// <summary>
/// Sets the specified alpha value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetA(this in Color color, float a) => new(a, color.R, color.G, color.B);
#endregion
#endregion
#region Conversion
/// <summary>
/// Gets the current color as a RGB-HEX-string.
/// </summary>
/// <returns>The RGB-HEX-string.</returns>
public static string AsRGBHexString(this in Color color, bool leadingHash = true) => (leadingHash ? "#" : "") + ConversionHelper.ToHex(color.GetR(), color.GetG(), color.GetB());
/// <summary>
/// Gets the current color as a ARGB-HEX-string.
/// </summary>
/// <returns>The ARGB-HEX-string.</returns>
public static string AsARGBHexString(this in Color color, bool leadingHash = true) => (leadingHash ? "#" : "") + ConversionHelper.ToHex(color.GetA(), color.GetR(), color.GetG(), color.GetB());
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using a HEX-string.
/// </summary>
/// <param name="hexString">The HEX-representation of the color.</param>
/// <returns>The color created from the HEX-string.</returns>
public static Color FromHexString(string hexString)
byte[] data = ConversionHelper.HexToBytes(span);
return data.Length switch
{
if ((hexString == null) || (hexString.Length < 6))
throw new ArgumentException("Invalid hex string", nameof(hexString));
ReadOnlySpan<char> span = hexString.AsSpan();
if (span[0] == '#')
span = span[1..];
byte[] data = ConversionHelper.HexToBytes(span);
return data.Length switch
{
3 => new Color(data[0], data[1], data[2]),
4 => new Color(data[0], data[1], data[2], data[3]),
_ => throw new ArgumentException($"Invalid hex string '{hexString}'", nameof(hexString))
};
}
#endregion
3 => new Color(data[0], data[1], data[2]),
4 => new Color(data[0], data[1], data[2], data[3]),
_ => throw new ArgumentException($"Invalid hex string '{hexString}'", nameof(hexString))
};
}
}
#endregion
}

View File

@ -2,207 +2,206 @@
// ReSharper disable UnusedMember.Global
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Contains helper-methods and extension for the <see cref="Color"/>-type to work in the XYZ color space.
/// </summary>
public static class XYZColor
{
#region Getter
/// <summary>
/// Contains helper-methods and extension for the <see cref="Color"/>-type to work in the XYZ color space.
/// Gets the X component value (XYZ-color space) of this <see cref="Color"/> in the range [0..95.047].
/// </summary>
public static class XYZColor
/// <param name="color">The color to get the value from.</param>
/// <returns>The X component value of the color.</returns>
public static float GetX(this in Color color) => color.GetXYZ().x;
/// <summary>
/// Gets the Y component value (XYZ-color space) of this <see cref="Color"/> in the range [0..100].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The Y component value of the color.</returns>
public static float GetY(this in Color color) => color.GetXYZ().y;
/// <summary>
/// Gets the Z component value (XYZ-color space) of this <see cref="Color"/> in the range [0..108.883].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The Z component value of the color.</returns>
public static float GetZ(this in Color color) => color.GetXYZ().z;
/// <summary>
/// Gets the X, Y and Z component values (XYZ-color space) of this <see cref="Color"/>.
/// X in the range [0..95.047].
/// Y in the range [0..100].
/// Z in the range [0..108.883].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the X, Y and Z component value of the color.</returns>
public static (float x, float y, float z) GetXYZ(this in Color color)
=> CaclulateXYZFromRGB(color.R, color.G, color.B);
#endregion
#region Manipulation
/// <summary>
/// Adds the specified XYZ values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="x">The X value to add.</param>
/// <param name="y">The Y value to add.</param>
/// <param name="z">The Z value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddXYZ(this in Color color, float x = 0, float y = 0, float z = 0)
{
#region Getter
/// <summary>
/// Gets the X component value (XYZ-color space) of this <see cref="Color"/> in the range [0..95.047].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The X component value of the color.</returns>
public static float GetX(this in Color color) => color.GetXYZ().x;
/// <summary>
/// Gets the Y component value (XYZ-color space) of this <see cref="Color"/> in the range [0..100].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The Y component value of the color.</returns>
public static float GetY(this in Color color) => color.GetXYZ().y;
/// <summary>
/// Gets the Z component value (XYZ-color space) of this <see cref="Color"/> in the range [0..108.883].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The Z component value of the color.</returns>
public static float GetZ(this in Color color) => color.GetXYZ().z;
/// <summary>
/// Gets the X, Y and Z component values (XYZ-color space) of this <see cref="Color"/>.
/// X in the range [0..95.047].
/// Y in the range [0..100].
/// Z in the range [0..108.883].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the X, Y and Z component value of the color.</returns>
public static (float x, float y, float z) GetXYZ(this in Color color)
=> CaclulateXYZFromRGB(color.R, color.G, color.B);
#endregion
#region Manipulation
/// <summary>
/// Adds the specified XYZ values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="x">The X value to add.</param>
/// <param name="y">The Y value to add.</param>
/// <param name="z">The Z value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddXYZ(this in Color color, float x = 0, float y = 0, float z = 0)
{
(float cX, float cY, float cZ) = color.GetXYZ();
return Create(color.A, cX + x, cY + y, cZ + z);
}
/// <summary>
/// Subtracts the specified XYZ values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="x">The X value to subtract.</param>
/// <param name="y">The Y value to subtract.</param>
/// <param name="z">The Z value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractXYZ(this in Color color, float x = 0, float y = 0, float z = 0)
{
(float cX, float cY, float cZ) = color.GetXYZ();
return Create(color.A, cX - x, cY - y, cZ - z);
}
/// <summary>
/// Multiplies the specified XYZ values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="x">The X value to multiply.</param>
/// <param name="y">The Y value to multiply.</param>
/// <param name="z">The Z value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyXYZ(this in Color color, float x = 1, float y = 1, float z = 1)
{
(float cX, float cY, float cZ) = color.GetXYZ();
return Create(color.A, cX * x, cY * y, cZ * z);
}
/// <summary>
/// Divides the specified XYZ values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="x">The X value to divide.</param>
/// <param name="y">The Y value to divide.</param>
/// <param name="z">The Z value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideXYZ(this in Color color, float x = 1, float y = 1, float z = 1)
{
(float cX, float cY, float cZ) = color.GetXYZ();
return Create(color.A, cX / x, cY / y, cZ / z);
}
/// <summary>
/// Sets the specified X valueof this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="x">The X value to set.</param>
/// <param name="y">The Y value to set.</param>
/// <param name="z">The Z value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetXYZ(this in Color color, float? x = null, float? y = null, float? z = null)
{
(float cX, float cY, float cZ) = color.GetXYZ();
return Create(color.A, x ?? cX, y ?? cY, z ?? cZ);
}
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using XYZ-Values.
/// </summary>
/// <param name="x">The X component value of this <see cref="Color"/>.</param>
/// <param name="y">The Y component value of this <see cref="Color"/>.</param>
/// <param name="z">The Z component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float x, float y, float z)
=> Create(1.0f, x, y, z);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and XYZ-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="x">The X component value of this <see cref="Color"/>.</param>
/// <param name="y">The Y component value of this <see cref="Color"/>.</param>
/// <param name="z">The Z component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(byte a, float x, float y, float z)
=> Create((float)a / byte.MaxValue, x, y, z);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and XYZ-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="x">The X component value of this <see cref="Color"/>.</param>
/// <param name="y">The Y component value of this <see cref="Color"/>.</param>
/// <param name="z">The Z component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(int a, float x, float y, float z)
=> Create((float)a / byte.MaxValue, x, y, z);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and XYZ-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="x">The X component value of this <see cref="Color"/>.</param>
/// <param name="y">The Y component value of this <see cref="Color"/>.</param>
/// <param name="z">The Z component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float a, float x, float y, float z)
{
(float r, float g, float b) = CalculateRGBFromXYZ(x, y, z);
return new Color(a, r, g, b);
}
#endregion
#region Helper
internal static (float x, float y, float z) CaclulateXYZFromRGB(float r, float g, float b)
{
r = ((r > 0.04045f) ? MathF.Pow(((r + 0.055f) / 1.055f), 2.4f) : (r / 12.92f)) * 100.0f;
g = ((g > 0.04045f) ? MathF.Pow(((g + 0.055f) / 1.055f), 2.4f) : (g / 12.92f)) * 100.0f;
b = ((b > 0.04045f) ? MathF.Pow(((b + 0.055f) / 1.055f), 2.4f) : (b / 12.92f)) * 100.0f;
float x = (r * 0.4124f) + (g * 0.3576f) + (b * 0.1805f);
float y = (r * 0.2126f) + (g * 0.7152f) + (b * 0.0722f);
float z = (r * 0.0193f) + (g * 0.1192f) + (b * 0.9505f);
return (x, y, z);
}
internal static (float r, float g, float b) CalculateRGBFromXYZ(float x, float y, float z)
{
const float INVERSE_EXPONENT = 1.0f / 2.4f;
x /= 100.0f;
y /= 100.0f;
z /= 100.0f;
float r = (x * 3.2406f) + (y * -1.5372f) + (z * -0.4986f);
float g = (x * -0.9689f) + (y * 1.8758f) + (z * 0.0415f);
float b = (x * 0.0557f) + (y * -0.2040f) + (z * 1.0570f);
r = ((r > 0.0031308f) ? ((1.055f * (MathF.Pow(r, INVERSE_EXPONENT))) - 0.055f) : (12.92f * r));
g = ((g > 0.0031308f) ? ((1.055f * (MathF.Pow(g, INVERSE_EXPONENT))) - 0.055f) : (12.92f * g));
b = ((b > 0.0031308f) ? ((1.055f * (MathF.Pow(b, INVERSE_EXPONENT))) - 0.055f) : (12.92f * b));
return (r, g, b);
}
#endregion
(float cX, float cY, float cZ) = color.GetXYZ();
return Create(color.A, cX + x, cY + y, cZ + z);
}
}
/// <summary>
/// Subtracts the specified XYZ values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="x">The X value to subtract.</param>
/// <param name="y">The Y value to subtract.</param>
/// <param name="z">The Z value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractXYZ(this in Color color, float x = 0, float y = 0, float z = 0)
{
(float cX, float cY, float cZ) = color.GetXYZ();
return Create(color.A, cX - x, cY - y, cZ - z);
}
/// <summary>
/// Multiplies the specified XYZ values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="x">The X value to multiply.</param>
/// <param name="y">The Y value to multiply.</param>
/// <param name="z">The Z value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyXYZ(this in Color color, float x = 1, float y = 1, float z = 1)
{
(float cX, float cY, float cZ) = color.GetXYZ();
return Create(color.A, cX * x, cY * y, cZ * z);
}
/// <summary>
/// Divides the specified XYZ values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="x">The X value to divide.</param>
/// <param name="y">The Y value to divide.</param>
/// <param name="z">The Z value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideXYZ(this in Color color, float x = 1, float y = 1, float z = 1)
{
(float cX, float cY, float cZ) = color.GetXYZ();
return Create(color.A, cX / x, cY / y, cZ / z);
}
/// <summary>
/// Sets the specified X valueof this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="x">The X value to set.</param>
/// <param name="y">The Y value to set.</param>
/// <param name="z">The Z value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetXYZ(this in Color color, float? x = null, float? y = null, float? z = null)
{
(float cX, float cY, float cZ) = color.GetXYZ();
return Create(color.A, x ?? cX, y ?? cY, z ?? cZ);
}
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using XYZ-Values.
/// </summary>
/// <param name="x">The X component value of this <see cref="Color"/>.</param>
/// <param name="y">The Y component value of this <see cref="Color"/>.</param>
/// <param name="z">The Z component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float x, float y, float z)
=> Create(1.0f, x, y, z);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and XYZ-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="x">The X component value of this <see cref="Color"/>.</param>
/// <param name="y">The Y component value of this <see cref="Color"/>.</param>
/// <param name="z">The Z component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(byte a, float x, float y, float z)
=> Create((float)a / byte.MaxValue, x, y, z);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and XYZ-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="x">The X component value of this <see cref="Color"/>.</param>
/// <param name="y">The Y component value of this <see cref="Color"/>.</param>
/// <param name="z">The Z component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(int a, float x, float y, float z)
=> Create((float)a / byte.MaxValue, x, y, z);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and XYZ-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="x">The X component value of this <see cref="Color"/>.</param>
/// <param name="y">The Y component value of this <see cref="Color"/>.</param>
/// <param name="z">The Z component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float a, float x, float y, float z)
{
(float r, float g, float b) = CalculateRGBFromXYZ(x, y, z);
return new Color(a, r, g, b);
}
#endregion
#region Helper
internal static (float x, float y, float z) CaclulateXYZFromRGB(float r, float g, float b)
{
r = ((r > 0.04045f) ? MathF.Pow(((r + 0.055f) / 1.055f), 2.4f) : (r / 12.92f)) * 100.0f;
g = ((g > 0.04045f) ? MathF.Pow(((g + 0.055f) / 1.055f), 2.4f) : (g / 12.92f)) * 100.0f;
b = ((b > 0.04045f) ? MathF.Pow(((b + 0.055f) / 1.055f), 2.4f) : (b / 12.92f)) * 100.0f;
float x = (r * 0.4124f) + (g * 0.3576f) + (b * 0.1805f);
float y = (r * 0.2126f) + (g * 0.7152f) + (b * 0.0722f);
float z = (r * 0.0193f) + (g * 0.1192f) + (b * 0.9505f);
return (x, y, z);
}
internal static (float r, float g, float b) CalculateRGBFromXYZ(float x, float y, float z)
{
const float INVERSE_EXPONENT = 1.0f / 2.4f;
x /= 100.0f;
y /= 100.0f;
z /= 100.0f;
float r = (x * 3.2406f) + (y * -1.5372f) + (z * -0.4986f);
float g = (x * -0.9689f) + (y * 1.8758f) + (z * 0.0415f);
float b = (x * 0.0557f) + (y * -0.2040f) + (z * 1.0570f);
r = ((r > 0.0031308f) ? ((1.055f * (MathF.Pow(r, INVERSE_EXPONENT))) - 0.055f) : (12.92f * r));
g = ((g > 0.0031308f) ? ((1.055f * (MathF.Pow(g, INVERSE_EXPONENT))) - 0.055f) : (12.92f * g));
b = ((b > 0.0031308f) ? ((1.055f * (MathF.Pow(b, INVERSE_EXPONENT))) - 0.055f) : (12.92f * b));
return (r, g, b);
}
#endregion
}

View File

@ -1,16 +1,15 @@
// ReSharper disable UnusedMember.Global
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic color-correction.
/// </summary>
public interface IColorCorrection
{
/// <summary>
/// Represents a generic color-correction.
/// Applies the <see cref="IColorCorrection"/> to the specified <see cref="Color"/>.
/// </summary>
public interface IColorCorrection
{
/// <summary>
/// Applies the <see cref="IColorCorrection"/> to the specified <see cref="Color"/>.
/// </summary>
/// <param name="color">The <see cref="Color"/> to correct.</param>
void ApplyTo(ref Color color);
}
}
/// <param name="color">The <see cref="Color"/> to correct.</param>
void ApplyTo(ref Color color);
}

View File

@ -2,69 +2,68 @@
using System.Collections.ObjectModel;
using System.Linq;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc cref="AbstractBindable" />
/// <inheritdoc cref="IDecoratable{T}" />
public abstract class AbstractDecoratable<T> : AbstractBindable, IDecoratable<T>
where T : IDecorator
{
/// <inheritdoc cref="AbstractBindable" />
/// <inheritdoc cref="IDecoratable{T}" />
public abstract class AbstractDecoratable<T> : AbstractBindable, IDecoratable<T>
where T : IDecorator
#region Properties & Fields
private readonly List<T> _decorators = new();
/// <inheritdoc />
public IReadOnlyList<T> Decorators { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractDecoratable{T}"/> class.
/// </summary>
protected AbstractDecoratable()
{
#region Properties & Fields
private readonly List<T> _decorators = new();
/// <inheritdoc />
public IReadOnlyList<T> Decorators { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractDecoratable{T}"/> class.
/// </summary>
protected AbstractDecoratable()
{
Decorators = new ReadOnlyCollection<T>(_decorators);
}
#endregion
#region Methods
/// <inheritdoc />
public void AddDecorator(T decorator)
{
lock (Decorators)
{
_decorators.Add(decorator);
_decorators.Sort((d1, d2) => d1.Order.CompareTo(d2.Order));
}
decorator.OnAttached(this);
}
/// <inheritdoc />
public void RemoveDecorator(T decorator)
{
lock (Decorators)
_decorators.Remove(decorator);
decorator.OnDetached(this);
}
/// <inheritdoc />
public void RemoveAllDecorators()
{
IEnumerable<T> decorators;
lock (Decorators)
decorators = Decorators.ToList();
foreach (T decorator in decorators)
RemoveDecorator(decorator);
}
#endregion
Decorators = new ReadOnlyCollection<T>(_decorators);
}
}
#endregion
#region Methods
/// <inheritdoc />
public void AddDecorator(T decorator)
{
lock (Decorators)
{
_decorators.Add(decorator);
_decorators.Sort((d1, d2) => d1.Order.CompareTo(d2.Order));
}
decorator.OnAttached(this);
}
/// <inheritdoc />
public void RemoveDecorator(T decorator)
{
lock (Decorators)
_decorators.Remove(decorator);
decorator.OnDetached(this);
}
/// <inheritdoc />
public void RemoveAllDecorators()
{
IEnumerable<T> decorators;
lock (Decorators)
decorators = Decorators.ToList();
foreach (T decorator in decorators)
RemoveDecorator(decorator);
}
#endregion
}

View File

@ -2,61 +2,60 @@
using System.Collections.Generic;
using System.Linq;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc cref="AbstractBindable" />
/// <inheritdoc cref="IDecorator" />
public abstract class AbstractDecorator : AbstractBindable, IDecorator
{
/// <inheritdoc cref="AbstractBindable" />
/// <inheritdoc cref="IDecorator" />
public abstract class AbstractDecorator : AbstractBindable, IDecorator
#region Properties & Fields
private bool _isEnabled = true;
/// <inheritdoc />
public bool IsEnabled
{
#region Properties & Fields
private bool _isEnabled = true;
/// <inheritdoc />
public bool IsEnabled
{
get => _isEnabled;
set => SetProperty(ref _isEnabled, value);
}
private int _order;
/// <inheritdoc />
public int Order
{
get => _order;
set => SetProperty(ref _order, value);
}
/// <summary>
/// Gets a readonly-list of all <see cref="IDecoratable"/> this decorator is attached to.
/// </summary>
protected List<IDecoratable> DecoratedObjects { get; } = new();
#endregion
#region Methods
/// <inheritdoc />
public virtual void OnAttached(IDecoratable decoratable) => DecoratedObjects.Add(decoratable);
/// <inheritdoc />
public virtual void OnDetached(IDecoratable decoratable) => DecoratedObjects.Remove(decoratable);
/// <summary>
/// Detaches the decorator from all <see cref="IDecoratable"/> it is currently attached to.
/// </summary>
protected virtual void Detach()
{
List<IDecoratable> decoratables = new(DecoratedObjects);
foreach (IDecoratable decoratable in decoratables)
{
IEnumerable<Type> types = decoratable.GetType().GetInterfaces().Where(t => t.IsGenericType
&& (t.Name == typeof(IDecoratable<>).Name)
&& t.GenericTypeArguments[0].IsInstanceOfType(this));
foreach (Type decoratableType in types)
decoratableType.GetMethod(nameof(IDecoratable<IDecorator>.RemoveDecorator))?.Invoke(decoratable, new object[] { this });
}
}
#endregion
get => _isEnabled;
set => SetProperty(ref _isEnabled, value);
}
}
private int _order;
/// <inheritdoc />
public int Order
{
get => _order;
set => SetProperty(ref _order, value);
}
/// <summary>
/// Gets a readonly-list of all <see cref="IDecoratable"/> this decorator is attached to.
/// </summary>
protected List<IDecoratable> DecoratedObjects { get; } = new();
#endregion
#region Methods
/// <inheritdoc />
public virtual void OnAttached(IDecoratable decoratable) => DecoratedObjects.Add(decoratable);
/// <inheritdoc />
public virtual void OnDetached(IDecoratable decoratable) => DecoratedObjects.Remove(decoratable);
/// <summary>
/// Detaches the decorator from all <see cref="IDecoratable"/> it is currently attached to.
/// </summary>
protected virtual void Detach()
{
List<IDecoratable> decoratables = new(DecoratedObjects);
foreach (IDecoratable decoratable in decoratables)
{
IEnumerable<Type> types = decoratable.GetType().GetInterfaces().Where(t => t.IsGenericType
&& (t.Name == typeof(IDecoratable<>).Name)
&& t.GenericTypeArguments[0].IsInstanceOfType(this));
foreach (Type decoratableType in types)
decoratableType.GetMethod(nameof(IDecoratable<IDecorator>.RemoveDecorator))?.Invoke(decoratable, new object[] { this });
}
}
#endregion
}

View File

@ -1,72 +1,71 @@
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents a basic decorator which is aware of the <see cref="E:RGB.NET.Core.RGBSurface.Updating" /> event.
/// </summary>
public abstract class AbstractUpdateAwareDecorator : AbstractDecorator
{
/// <inheritdoc />
#region Properties & Fields
/// <summary>
/// Represents a basic decorator which is aware of the <see cref="E:RGB.NET.Core.RGBSurface.Updating" /> event.
/// Gets the surface this decorator is attached to.
/// </summary>
public abstract class AbstractUpdateAwareDecorator : AbstractDecorator
protected RGBSurface Surface { get; }
/// <summary>
/// Gets or sets if the <see cref="AbstractUpdateAwareDecorator"/> should call <see cref="Update"/> even if the Decorator is disabled.
/// </summary>
protected bool UpdateIfDisabled { get; set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractUpdateAwareDecorator"/> class.
/// </summary>
/// <param name="surface">The surface this decorator is attached to.</param>
/// <param name="updateIfDisabled">Bool indicating if the <see cref="AbstractUpdateAwareDecorator"/> should call <see cref="Update"/> even if the Decorator is disabled.</param>
protected AbstractUpdateAwareDecorator(RGBSurface surface, bool updateIfDisabled = false)
{
#region Properties & Fields
/// <summary>
/// Gets the surface this decorator is attached to.
/// </summary>
protected RGBSurface Surface { get; }
/// <summary>
/// Gets or sets if the <see cref="AbstractUpdateAwareDecorator"/> should call <see cref="Update"/> even if the Decorator is disabled.
/// </summary>
protected bool UpdateIfDisabled { get; set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractUpdateAwareDecorator"/> class.
/// </summary>
/// <param name="surface">The surface this decorator is attached to.</param>
/// <param name="updateIfDisabled">Bool indicating if the <see cref="AbstractUpdateAwareDecorator"/> should call <see cref="Update"/> even if the Decorator is disabled.</param>
protected AbstractUpdateAwareDecorator(RGBSurface surface, bool updateIfDisabled = false)
{
this.Surface = surface;
this.UpdateIfDisabled = updateIfDisabled;
}
#endregion
#region Methods
/// <inheritdoc />
public override void OnAttached(IDecoratable decoratable)
{
if (DecoratedObjects.Count == 0)
Surface.Updating += OnSurfaceUpdating;
base.OnAttached(decoratable);
}
/// <inheritdoc />
public override void OnDetached(IDecoratable decoratable)
{
base.OnDetached(decoratable);
if (DecoratedObjects.Count == 0)
Surface.Updating -= OnSurfaceUpdating;
}
private void OnSurfaceUpdating(UpdatingEventArgs args)
{
if (IsEnabled || UpdateIfDisabled)
Update(args.DeltaTime);
}
/// <summary>
/// Updates this <see cref="AbstractUpdateAwareDecorator"/>.
/// </summary>
/// <param name="deltaTime">The elapsed time (in seconds) since the last update.</param>
protected abstract void Update(double deltaTime);
#endregion
this.Surface = surface;
this.UpdateIfDisabled = updateIfDisabled;
}
}
#endregion
#region Methods
/// <inheritdoc />
public override void OnAttached(IDecoratable decoratable)
{
if (DecoratedObjects.Count == 0)
Surface.Updating += OnSurfaceUpdating;
base.OnAttached(decoratable);
}
/// <inheritdoc />
public override void OnDetached(IDecoratable decoratable)
{
base.OnDetached(decoratable);
if (DecoratedObjects.Count == 0)
Surface.Updating -= OnSurfaceUpdating;
}
private void OnSurfaceUpdating(UpdatingEventArgs args)
{
if (IsEnabled || UpdateIfDisabled)
Update(args.DeltaTime);
}
/// <summary>
/// Updates this <see cref="AbstractUpdateAwareDecorator"/>.
/// </summary>
/// <param name="deltaTime">The elapsed time (in seconds) since the last update.</param>
protected abstract void Update(double deltaTime);
#endregion
}

View File

@ -1,17 +1,16 @@
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents a <see cref="T:RGB.NET.Core.IDecorator" /> decorating a <see cref="T:RGB.NET.Core.IBrush" />.
/// </summary>
public interface IBrushDecorator : IDecorator
{
/// <inheritdoc />
/// <summary>
/// Represents a <see cref="T:RGB.NET.Core.IDecorator" /> decorating a <see cref="T:RGB.NET.Core.IBrush" />.
/// Decorator-Method called by the <see cref="IBrush"/>.
/// </summary>
public interface IBrushDecorator : IDecorator
{
/// <summary>
/// Decorator-Method called by the <see cref="IBrush"/>.
/// </summary>
/// <param name="rectangle">The rectangle in which the <see cref="IBrush"/> should be drawn.</param>
/// <param name="renderTarget">The target (key/point) from which the <see cref="Color"/> should be taken.</param>
/// <param name="color">The <see cref="Color"/> to be modified.</param>
void ManipulateColor(in Rectangle rectangle, in RenderTarget renderTarget, ref Color color);
}
}
/// <param name="rectangle">The rectangle in which the <see cref="IBrush"/> should be drawn.</param>
/// <param name="renderTarget">The target (key/point) from which the <see cref="Color"/> should be taken.</param>
/// <param name="color">The <see cref="Color"/> to be modified.</param>
void ManipulateColor(in Rectangle rectangle, in RenderTarget renderTarget, ref Color color);
}

View File

@ -1,42 +1,41 @@
using System.Collections.Generic;
using System.ComponentModel;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a basic decoratable.
/// </summary>
public interface IDecoratable : INotifyPropertyChanged
{ }
/// <inheritdoc />
/// <summary>
/// Represents a basic decoratable for a specific type of <see cref="T:RGB.NET.Core.IDecorator" />
/// </summary>
/// <typeparam name="T">The type of decorators this decoratable can be decorated with.</typeparam>
public interface IDecoratable<T> : IDecoratable
where T : IDecorator
{
/// <summary>
/// Represents a basic decoratable.
/// Gets a readonly-list of all <see cref="IDecorator"/> attached to this <see cref="IDecoratable{T}"/>.
/// </summary>
public interface IDecoratable : INotifyPropertyChanged
{ }
IReadOnlyList<T> Decorators { get; }
/// <inheritdoc />
/// <summary>
/// Represents a basic decoratable for a specific type of <see cref="T:RGB.NET.Core.IDecorator" />
/// Adds an <see cref="IDecorator"/> to the <see cref="IDecoratable"/>.
/// </summary>
/// <typeparam name="T">The type of decorators this decoratable can be decorated with.</typeparam>
public interface IDecoratable<T> : IDecoratable
where T : IDecorator
{
/// <summary>
/// Gets a readonly-list of all <see cref="IDecorator"/> attached to this <see cref="IDecoratable{T}"/>.
/// </summary>
IReadOnlyList<T> Decorators { get; }
/// <param name="decorator">The <see cref="IDecorator"/> to be added.</param>
void AddDecorator(T decorator);
/// <summary>
/// Adds an <see cref="IDecorator"/> to the <see cref="IDecoratable"/>.
/// </summary>
/// <param name="decorator">The <see cref="IDecorator"/> to be added.</param>
void AddDecorator(T decorator);
/// <summary>
/// Removes an <see cref="IDecorator"/> from the <see cref="IDecoratable"/>.
/// </summary>
/// <param name="decorator">The <see cref="IDecorator"/> to be removed.</param>
void RemoveDecorator(T decorator);
/// <summary>
/// Removes an <see cref="IDecorator"/> from the <see cref="IDecoratable"/>.
/// </summary>
/// <param name="decorator">The <see cref="IDecorator"/> to be removed.</param>
void RemoveDecorator(T decorator);
/// <summary>
/// Removes all <see cref="IDecorator"/> from the <see cref="IDecoratable"/>.
/// </summary>
void RemoveAllDecorators();
}
}
/// <summary>
/// Removes all <see cref="IDecorator"/> from the <see cref="IDecoratable"/>.
/// </summary>
void RemoveAllDecorators();
}

View File

@ -1,39 +1,38 @@
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a basic decorator.
/// </summary>
public interface IDecorator
{
#region Properties & Fields
/// <summary>
/// Represents a basic decorator.
/// Gets or sets if the <see cref="IDecorator"/> is enabled and will be used.
/// </summary>
public interface IDecorator
{
#region Properties & Fields
bool IsEnabled { get; set; }
/// <summary>
/// Gets or sets if the <see cref="IDecorator"/> is enabled and will be used.
/// </summary>
bool IsEnabled { get; set; }
/// <summary>
/// Gets or sets the order in which multiple decorators should be applied on the same object.
/// Higher orders are processed first.
/// </summary>
int Order { get; set; }
/// <summary>
/// Gets or sets the order in which multiple decorators should be applied on the same object.
/// Higher orders are processed first.
/// </summary>
int Order { get; set; }
#endregion
#endregion
#region Methods
#region Methods
/// <summary>
/// Attaches this <see cref="IDecorator"/> to the specified target.
/// </summary>
/// <param name="decoratable">The object this <see cref="IDecorator"/> should be attached to.</param>
void OnAttached(IDecoratable decoratable);
/// <summary>
/// Attaches this <see cref="IDecorator"/> to the specified target.
/// </summary>
/// <param name="decoratable">The object this <see cref="IDecorator"/> should be attached to.</param>
void OnAttached(IDecoratable decoratable);
/// <summary>
/// Detaches this <see cref="IDecorator"/> from the specified target.
/// </summary>
/// <param name="decoratable">The object this <see cref="IDecorator"/> should be detached from.</param>
void OnDetached(IDecoratable decoratable);
/// <summary>
/// Detaches this <see cref="IDecorator"/> from the specified target.
/// </summary>
/// <param name="decoratable">The object this <see cref="IDecorator"/> should be detached from.</param>
void OnDetached(IDecoratable decoratable);
#endregion
}
}
#endregion
}

View File

@ -1,9 +1,8 @@
namespace RGB.NET.Core
{
/// <inheritdoc />
/// <summary>
/// Represents a basic decorator decorating a <see cref="T:RGB.NET.Core.ILedGroup" />.
/// </summary>
public interface ILedGroupDecorator : IDecorator
{ }
}
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents a basic decorator decorating a <see cref="T:RGB.NET.Core.ILedGroup" />.
/// </summary>
public interface ILedGroupDecorator : IDecorator
{ }

View File

@ -6,234 +6,233 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc cref="AbstractBindable" />
/// <inheritdoc cref="IRGBDevice{TDeviceInfo}" />
/// <summary>
/// Represents a generic RGB-device.
/// </summary>
public abstract class AbstractRGBDevice<TDeviceInfo> : Placeable, IRGBDevice<TDeviceInfo>
where TDeviceInfo : class, IRGBDeviceInfo
{
/// <inheritdoc cref="AbstractBindable" />
/// <inheritdoc cref="IRGBDevice{TDeviceInfo}" />
/// <summary>
/// Represents a generic RGB-device.
/// </summary>
public abstract class AbstractRGBDevice<TDeviceInfo> : Placeable, IRGBDevice<TDeviceInfo>
where TDeviceInfo : class, IRGBDeviceInfo
private RGBSurface? _surface;
#region Properties & Fields
RGBSurface? IRGBDevice.Surface
{
private RGBSurface? _surface;
#region Properties & Fields
RGBSurface? IRGBDevice.Surface
get => _surface;
set
{
get => _surface;
set
if (SetProperty(ref _surface, value))
{
if (SetProperty(ref _surface, value))
{
if (value == null) OnDetached();
else OnAttached();
}
if (value == null) OnDetached();
else OnAttached();
}
}
/// <inheritdoc />
public TDeviceInfo DeviceInfo { get; }
/// <inheritdoc />
IRGBDeviceInfo IRGBDevice.DeviceInfo => DeviceInfo;
/// <inheritdoc />
public IList<IColorCorrection> ColorCorrections { get; } = new List<IColorCorrection>();
/// <summary>
/// Gets or sets if the device needs to be flushed on every update.
/// </summary>
protected bool RequiresFlush { get; set; } = false;
/// <summary>
/// Gets a dictionary containing all <see cref="Led"/> of the <see cref="IRGBDevice"/>.
/// </summary>
protected Dictionary<LedId, Led> LedMapping { get; } = new();
/// <summary>
/// Gets the update queue used to update this device.
/// </summary>
protected IUpdateQueue UpdateQueue { get; }
#region Indexer
/// <inheritdoc />
Led? IRGBDevice.this[LedId ledId] => LedMapping.TryGetValue(ledId, out Led? led) ? led : null;
/// <inheritdoc />
Led? IRGBDevice.this[Point location] => LedMapping.Values.FirstOrDefault(x => x.Boundary.Contains(location));
/// <inheritdoc />
IEnumerable<Led> IRGBDevice.this[Rectangle referenceRect, double minOverlayPercentage]
=> LedMapping.Values.Where(x => referenceRect.CalculateIntersectPercentage(x.Boundary) >= minOverlayPercentage);
#endregion
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractRGBDevice{T}"/> class.
/// </summary>
/// <param name="deviceInfo">The device info of this device.</param>
/// <param name="updateQueue">The queue used to update this device.</param>
protected AbstractRGBDevice(TDeviceInfo deviceInfo, IUpdateQueue updateQueue)
{
this.DeviceInfo = deviceInfo;
this.UpdateQueue = updateQueue;
}
#endregion
#region Methods
/// <inheritdoc />
public virtual void Update(bool flushLeds = false)
{
// Device-specific updates
DeviceUpdate();
// Send LEDs to SDK
List<Led> ledsToUpdate = GetLedsToUpdate(flushLeds).ToList();
foreach (Led led in ledsToUpdate)
led.Update();
UpdateLeds(ledsToUpdate);
}
/// <summary>
/// Gets an enumerable of LEDs that are changed and requires an update.
/// </summary>
/// <param name="flushLeds">Forces all LEDs to be treated as dirty.</param>
/// <returns>The collection LEDs to update.</returns>
protected virtual IEnumerable<Led> GetLedsToUpdate(bool flushLeds) => ((RequiresFlush || flushLeds) ? LedMapping.Values : LedMapping.Values.Where(x => x.IsDirty)).Where(led => led.RequestedColor?.A > 0);
/// <summary>
/// Gets an enumerable of a custom data and color tuple for the specified leds.
/// </summary>
/// <remarks>
/// Applies all <see cref="ColorCorrections"/>.
/// if no <see cref="Led.CustomData"/> ist specified the <see cref="Led.Id"/> is used.
/// </remarks>
/// <param name="leds">The enumerable of leds to convert.</param>
/// <returns>The enumerable of custom data and color tuples for the specified leds.</returns>
protected virtual IEnumerable<(object key, Color color)> GetUpdateData(IEnumerable<Led> leds)
{
if (ColorCorrections.Count > 0)
{
foreach (Led led in leds)
{
Color color = led.Color;
object key = led.CustomData ?? led.Id;
foreach (IColorCorrection colorCorrection in ColorCorrections)
colorCorrection.ApplyTo(ref color);
yield return (key, color);
}
}
else
{
foreach (Led led in leds)
{
Color color = led.Color;
object key = led.CustomData ?? led.Id;
yield return (key, color);
}
}
}
/// <summary>
/// Sends all the updated <see cref="Led"/> to the device.
/// </summary>
protected virtual void UpdateLeds(IEnumerable<Led> ledsToUpdate) => UpdateQueue.SetData(GetUpdateData(ledsToUpdate));
/// <inheritdoc />
public virtual void Dispose()
{
try { UpdateQueue.Dispose(); } catch { /* :( */ }
try { LedMapping.Clear(); } catch { /* this really shouldn't happen */ }
IdGenerator.ResetCounter(GetType().Assembly);
}
/// <summary>
/// Performs device specific updates.
/// </summary>
protected virtual void DeviceUpdate()
{ }
/// <inheritdoc />
public virtual Led? AddLed(LedId ledId, in Point location, in Size size, object? customData = null)
{
if ((ledId == LedId.Invalid) || LedMapping.ContainsKey(ledId)) return null;
Led led = new(this, ledId, location, size, customData ?? GetLedCustomData(ledId));
LedMapping.Add(ledId, led);
return led;
}
/// <inheritdoc />
public virtual Led? RemoveLed(LedId ledId)
{
if (ledId == LedId.Invalid) return null;
if (!LedMapping.TryGetValue(ledId, out Led? led)) return null;
LedMapping.Remove(ledId);
return led;
}
/// <summary>
/// Gets the custom data associated with the specified LED.
/// </summary>
/// <param name="ledId">The id of the led.</param>
/// <returns>The custom data for the specified LED.</returns>
protected virtual object? GetLedCustomData(LedId ledId) => null;
/// <summary>
/// Called when the device is attached to a surface.
/// </summary>
/// <remarks>
/// When overriden base should be called to validate boundries.
/// </remarks>
protected virtual void OnAttached()
{
if (Location == Point.Invalid) Location = new Point(0, 0);
if (Size == Size.Invalid)
{
Rectangle ledRectangle = new(this.Select(x => x.Boundary));
Size = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y);
}
}
/// <summary>
/// Called when the device is detached from a surface.
/// </summary>
protected virtual void OnDetached() { }
#region Enumerator
/// <inheritdoc />
/// <summary>
/// Returns an enumerator that iterates over all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.
/// </summary>
/// <returns>An enumerator for all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.</returns>
public IEnumerator<Led> GetEnumerator() => LedMapping.Values.GetEnumerator();
/// <inheritdoc />
/// <summary>
/// Returns an enumerator that iterates over all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.
/// </summary>
/// <returns>An enumerator for all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.</returns>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
#endregion
#endregion
}
}
/// <inheritdoc />
public TDeviceInfo DeviceInfo { get; }
/// <inheritdoc />
IRGBDeviceInfo IRGBDevice.DeviceInfo => DeviceInfo;
/// <inheritdoc />
public IList<IColorCorrection> ColorCorrections { get; } = new List<IColorCorrection>();
/// <summary>
/// Gets or sets if the device needs to be flushed on every update.
/// </summary>
protected bool RequiresFlush { get; set; } = false;
/// <summary>
/// Gets a dictionary containing all <see cref="Led"/> of the <see cref="IRGBDevice"/>.
/// </summary>
protected Dictionary<LedId, Led> LedMapping { get; } = new();
/// <summary>
/// Gets the update queue used to update this device.
/// </summary>
protected IUpdateQueue UpdateQueue { get; }
#region Indexer
/// <inheritdoc />
Led? IRGBDevice.this[LedId ledId] => LedMapping.TryGetValue(ledId, out Led? led) ? led : null;
/// <inheritdoc />
Led? IRGBDevice.this[Point location] => LedMapping.Values.FirstOrDefault(x => x.Boundary.Contains(location));
/// <inheritdoc />
IEnumerable<Led> IRGBDevice.this[Rectangle referenceRect, double minOverlayPercentage]
=> LedMapping.Values.Where(x => referenceRect.CalculateIntersectPercentage(x.Boundary) >= minOverlayPercentage);
#endregion
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractRGBDevice{T}"/> class.
/// </summary>
/// <param name="deviceInfo">The device info of this device.</param>
/// <param name="updateQueue">The queue used to update this device.</param>
protected AbstractRGBDevice(TDeviceInfo deviceInfo, IUpdateQueue updateQueue)
{
this.DeviceInfo = deviceInfo;
this.UpdateQueue = updateQueue;
}
#endregion
#region Methods
/// <inheritdoc />
public virtual void Update(bool flushLeds = false)
{
// Device-specific updates
DeviceUpdate();
// Send LEDs to SDK
List<Led> ledsToUpdate = GetLedsToUpdate(flushLeds).ToList();
foreach (Led led in ledsToUpdate)
led.Update();
UpdateLeds(ledsToUpdate);
}
/// <summary>
/// Gets an enumerable of LEDs that are changed and requires an update.
/// </summary>
/// <param name="flushLeds">Forces all LEDs to be treated as dirty.</param>
/// <returns>The collection LEDs to update.</returns>
protected virtual IEnumerable<Led> GetLedsToUpdate(bool flushLeds) => ((RequiresFlush || flushLeds) ? LedMapping.Values : LedMapping.Values.Where(x => x.IsDirty)).Where(led => led.RequestedColor?.A > 0);
/// <summary>
/// Gets an enumerable of a custom data and color tuple for the specified leds.
/// </summary>
/// <remarks>
/// Applies all <see cref="ColorCorrections"/>.
/// if no <see cref="Led.CustomData"/> ist specified the <see cref="Led.Id"/> is used.
/// </remarks>
/// <param name="leds">The enumerable of leds to convert.</param>
/// <returns>The enumerable of custom data and color tuples for the specified leds.</returns>
protected virtual IEnumerable<(object key, Color color)> GetUpdateData(IEnumerable<Led> leds)
{
if (ColorCorrections.Count > 0)
{
foreach (Led led in leds)
{
Color color = led.Color;
object key = led.CustomData ?? led.Id;
foreach (IColorCorrection colorCorrection in ColorCorrections)
colorCorrection.ApplyTo(ref color);
yield return (key, color);
}
}
else
{
foreach (Led led in leds)
{
Color color = led.Color;
object key = led.CustomData ?? led.Id;
yield return (key, color);
}
}
}
/// <summary>
/// Sends all the updated <see cref="Led"/> to the device.
/// </summary>
protected virtual void UpdateLeds(IEnumerable<Led> ledsToUpdate) => UpdateQueue.SetData(GetUpdateData(ledsToUpdate));
/// <inheritdoc />
public virtual void Dispose()
{
try { UpdateQueue.Dispose(); } catch { /* :( */ }
try { LedMapping.Clear(); } catch { /* this really shouldn't happen */ }
IdGenerator.ResetCounter(GetType().Assembly);
}
/// <summary>
/// Performs device specific updates.
/// </summary>
protected virtual void DeviceUpdate()
{ }
/// <inheritdoc />
public virtual Led? AddLed(LedId ledId, in Point location, in Size size, object? customData = null)
{
if ((ledId == LedId.Invalid) || LedMapping.ContainsKey(ledId)) return null;
Led led = new(this, ledId, location, size, customData ?? GetLedCustomData(ledId));
LedMapping.Add(ledId, led);
return led;
}
/// <inheritdoc />
public virtual Led? RemoveLed(LedId ledId)
{
if (ledId == LedId.Invalid) return null;
if (!LedMapping.TryGetValue(ledId, out Led? led)) return null;
LedMapping.Remove(ledId);
return led;
}
/// <summary>
/// Gets the custom data associated with the specified LED.
/// </summary>
/// <param name="ledId">The id of the led.</param>
/// <returns>The custom data for the specified LED.</returns>
protected virtual object? GetLedCustomData(LedId ledId) => null;
/// <summary>
/// Called when the device is attached to a surface.
/// </summary>
/// <remarks>
/// When overriden base should be called to validate boundries.
/// </remarks>
protected virtual void OnAttached()
{
if (Location == Point.Invalid) Location = new Point(0, 0);
if (Size == Size.Invalid)
{
Rectangle ledRectangle = new(this.Select(x => x.Boundary));
Size = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y);
}
}
/// <summary>
/// Called when the device is detached from a surface.
/// </summary>
protected virtual void OnDetached() { }
#region Enumerator
/// <inheritdoc />
/// <summary>
/// Returns an enumerator that iterates over all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.
/// </summary>
/// <returns>An enumerator for all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.</returns>
public IEnumerator<Led> GetEnumerator() => LedMapping.Values.GetEnumerator();
/// <inheritdoc />
/// <summary>
/// Returns an enumerator that iterates over all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.
/// </summary>
/// <returns>An enumerator for all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.</returns>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
#endregion
#endregion
}

View File

@ -3,205 +3,204 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents the abstract base implementation for a <see cref="IRGBDeviceProvider"/>.
/// </summary>
public abstract class AbstractRGBDeviceProvider : IRGBDeviceProvider
{
#region Properties & Fields
private readonly double _defaultUpdateRateHardLimit;
/// <inheritdoc />
public bool IsInitialized { get; protected set; }
/// <inheritdoc />
public bool ThrowsExceptions { get; protected set; }
/// <inheritdoc />
public virtual IEnumerable<IRGBDevice> Devices { get; protected set; } = Enumerable.Empty<IRGBDevice>();
/// <summary>
/// Represents the abstract base implementation for a <see cref="IRGBDeviceProvider"/>.
/// Gets the dictionary containing the registered update triggers.
/// Normally <see cref="UpdateTriggers"/> should be used to access them.
/// </summary>
public abstract class AbstractRGBDeviceProvider : IRGBDeviceProvider
{
#region Properties & Fields
protected Dictionary<int, IDeviceUpdateTrigger> UpdateTriggerMapping { get; } = new();
private readonly double _defaultUpdateRateHardLimit;
/// <inheritdoc />
public IReadOnlyList<(int id, IDeviceUpdateTrigger trigger)> UpdateTriggers => new ReadOnlyCollection<(int id, IDeviceUpdateTrigger trigger)>(UpdateTriggerMapping.Select(x => (x.Key, x.Value)).ToList());
/// <inheritdoc />
public bool IsInitialized { get; protected set; }
#endregion
/// <inheritdoc />
public bool ThrowsExceptions { get; protected set; }
#region Events
/// <inheritdoc />
public virtual IEnumerable<IRGBDevice> Devices { get; protected set; } = Enumerable.Empty<IRGBDevice>();
/// <inheritdoc />
public event EventHandler<ExceptionEventArgs>? Exception;
/// <summary>
/// Gets the dictionary containing the registered update triggers.
/// Normally <see cref="UpdateTriggers"/> should be used to access them.
/// </summary>
protected Dictionary<int, IDeviceUpdateTrigger> UpdateTriggerMapping { get; } = new();
#endregion
/// <inheritdoc />
public IReadOnlyList<(int id, IDeviceUpdateTrigger trigger)> UpdateTriggers => new ReadOnlyCollection<(int id, IDeviceUpdateTrigger trigger)>(UpdateTriggerMapping.Select(x => (x.Key, x.Value)).ToList());
#endregion
#region Events
/// <inheritdoc />
public event EventHandler<ExceptionEventArgs>? Exception;
#endregion
#region Constructors
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractRGBDeviceProvider" /> class.
/// </summary>
/// <param name="defaultUpdateRateHardLimit">The update rate hard limit all update triggers for this device provider are initialized with.</param>
protected AbstractRGBDeviceProvider(double defaultUpdateRateHardLimit = 0)
{
this._defaultUpdateRateHardLimit = defaultUpdateRateHardLimit;
}
/// <summary>
/// Initializes a new instance of the <see cref="AbstractRGBDeviceProvider" /> class.
/// </summary>
/// <param name="defaultUpdateRateHardLimit">The update rate hard limit all update triggers for this device provider are initialized with.</param>
protected AbstractRGBDeviceProvider(double defaultUpdateRateHardLimit = 0)
{
this._defaultUpdateRateHardLimit = defaultUpdateRateHardLimit;
}
#endregion
#endregion
#region Methods
#region Methods
/// <inheritdoc />
public bool Initialize(RGBDeviceType loadFilter = RGBDeviceType.All, bool throwExceptions = false)
{
ThrowsExceptions = throwExceptions;
/// <inheritdoc />
public bool Initialize(RGBDeviceType loadFilter = RGBDeviceType.All, bool throwExceptions = false)
{
ThrowsExceptions = throwExceptions;
try
{
Reset();
InitializeSDK();
Devices = new ReadOnlyCollection<IRGBDevice>(GetLoadedDevices(loadFilter).ToList());
foreach (IDeviceUpdateTrigger updateTrigger in UpdateTriggerMapping.Values)
updateTrigger.Start();
IsInitialized = true;
}
catch (DeviceProviderException)
{
Reset();
throw;
}
catch (Exception ex)
{
Reset();
Throw(ex, true);
return false;
}
return true;
}
/// <summary>
/// Loads devices and returns a filtered list of them.
/// </summary>
/// <remarks>
/// The underlying loading of the devices happens in <see cref="LoadDevices"/>.
/// </remarks>
/// <param name="loadFilter"><see cref="RGBDeviceType"/>-flags to filter the device with.</param>
/// <returns>The filtered collection of loaded devices.</returns>
protected virtual IEnumerable<IRGBDevice> GetLoadedDevices(RGBDeviceType loadFilter)
{
List<IRGBDevice> devices = new();
foreach (IRGBDevice device in LoadDevices())
{
try
{
if (loadFilter.HasFlag(device.DeviceInfo.DeviceType))
devices.Add(device);
else
device.Dispose();
}
catch (Exception ex)
{
Throw(ex);
}
}
return devices;
}
/// <summary>
/// Initializes the underlying SDK.
/// </summary>
protected abstract void InitializeSDK();
/// <summary>
/// Loads all devices this device provider is capable of loading.
/// </summary>
/// <remarks>
/// Filtering happens in <see cref="GetLoadedDevices"/>.
/// </remarks>
/// <returns>A collection of loaded devices.</returns>
protected abstract IEnumerable<IRGBDevice> LoadDevices();
/// <summary>
/// Gets the <see cref="IDeviceUpdateTrigger"/> mapped to the specified id or a new one if the id wasn't requested before.
/// </summary>
/// <remarks>
/// The creation of the update trigger happens in <see cref="CreateUpdateTrigger"/>.
/// </remarks>
/// <param name="id">The id of the update trigger.</param>
/// <param name="updateRateHardLimit">The update rate hard limit to be set in the update trigger.</param>
/// <returns>The update trigger mapped to the specified id.</returns>
protected virtual IDeviceUpdateTrigger GetUpdateTrigger(int id = -1, double? updateRateHardLimit = null)
{
if (!UpdateTriggerMapping.TryGetValue(id, out IDeviceUpdateTrigger? updaeTrigger))
UpdateTriggerMapping[id] = (updaeTrigger = CreateUpdateTrigger(id, updateRateHardLimit ?? _defaultUpdateRateHardLimit));
return updaeTrigger;
}
/// <summary>
/// Creates a update trigger with the specified id and the specified update rate hard limit.
/// </summary>
/// <param name="id">The id of the update trigger.</param>
/// <param name="updateRateHardLimit">The update rate hard limit tobe set in the update trigger.</param>
/// <returns>The newly created update trigger.</returns>
protected virtual IDeviceUpdateTrigger CreateUpdateTrigger(int id, double updateRateHardLimit) => new DeviceUpdateTrigger(updateRateHardLimit);
/// <summary>
/// Resets the device provider and disposes all devices and update triggers.
/// </summary>
protected virtual void Reset()
{
foreach (IDeviceUpdateTrigger updateTrigger in UpdateTriggerMapping.Values)
updateTrigger.Dispose();
foreach (IRGBDevice device in Devices)
device.Dispose();
Devices = Enumerable.Empty<IRGBDevice>();
UpdateTriggerMapping.Clear();
IsInitialized = false;
}
/// <summary>
/// Triggers the <see cref="Exception"/>-event and throws the specified exception if <see cref="ThrowsExceptions"/> is true and it is not overriden in the event.
/// </summary>
/// <param name="ex">The exception to throw.</param>
/// <param name="isCritical">Indicates if the exception is critical for device provider to work correctly.</param>
protected virtual void Throw(Exception ex, bool isCritical = false)
{
ExceptionEventArgs args = new(ex, isCritical, ThrowsExceptions);
try { OnException(args); } catch { /* we don't want to throw due to bad event handlers */ }
if (args.Throw)
throw new DeviceProviderException(ex, isCritical);
}
/// <summary>
/// Throws the <see cref="Exception"/> event.
/// </summary>
/// <param name="args">The parameters passed to the event.</param>
protected virtual void OnException(ExceptionEventArgs args) => Exception?.Invoke(this, args);
/// <inheritdoc />
public virtual void Dispose()
try
{
Reset();
GC.SuppressFinalize(this);
InitializeSDK();
Devices = new ReadOnlyCollection<IRGBDevice>(GetLoadedDevices(loadFilter).ToList());
foreach (IDeviceUpdateTrigger updateTrigger in UpdateTriggerMapping.Values)
updateTrigger.Start();
IsInitialized = true;
}
catch (DeviceProviderException)
{
Reset();
throw;
}
catch (Exception ex)
{
Reset();
Throw(ex, true);
return false;
}
#endregion
return true;
}
}
/// <summary>
/// Loads devices and returns a filtered list of them.
/// </summary>
/// <remarks>
/// The underlying loading of the devices happens in <see cref="LoadDevices"/>.
/// </remarks>
/// <param name="loadFilter"><see cref="RGBDeviceType"/>-flags to filter the device with.</param>
/// <returns>The filtered collection of loaded devices.</returns>
protected virtual IEnumerable<IRGBDevice> GetLoadedDevices(RGBDeviceType loadFilter)
{
List<IRGBDevice> devices = new();
foreach (IRGBDevice device in LoadDevices())
{
try
{
if (loadFilter.HasFlag(device.DeviceInfo.DeviceType))
devices.Add(device);
else
device.Dispose();
}
catch (Exception ex)
{
Throw(ex);
}
}
return devices;
}
/// <summary>
/// Initializes the underlying SDK.
/// </summary>
protected abstract void InitializeSDK();
/// <summary>
/// Loads all devices this device provider is capable of loading.
/// </summary>
/// <remarks>
/// Filtering happens in <see cref="GetLoadedDevices"/>.
/// </remarks>
/// <returns>A collection of loaded devices.</returns>
protected abstract IEnumerable<IRGBDevice> LoadDevices();
/// <summary>
/// Gets the <see cref="IDeviceUpdateTrigger"/> mapped to the specified id or a new one if the id wasn't requested before.
/// </summary>
/// <remarks>
/// The creation of the update trigger happens in <see cref="CreateUpdateTrigger"/>.
/// </remarks>
/// <param name="id">The id of the update trigger.</param>
/// <param name="updateRateHardLimit">The update rate hard limit to be set in the update trigger.</param>
/// <returns>The update trigger mapped to the specified id.</returns>
protected virtual IDeviceUpdateTrigger GetUpdateTrigger(int id = -1, double? updateRateHardLimit = null)
{
if (!UpdateTriggerMapping.TryGetValue(id, out IDeviceUpdateTrigger? updaeTrigger))
UpdateTriggerMapping[id] = (updaeTrigger = CreateUpdateTrigger(id, updateRateHardLimit ?? _defaultUpdateRateHardLimit));
return updaeTrigger;
}
/// <summary>
/// Creates a update trigger with the specified id and the specified update rate hard limit.
/// </summary>
/// <param name="id">The id of the update trigger.</param>
/// <param name="updateRateHardLimit">The update rate hard limit tobe set in the update trigger.</param>
/// <returns>The newly created update trigger.</returns>
protected virtual IDeviceUpdateTrigger CreateUpdateTrigger(int id, double updateRateHardLimit) => new DeviceUpdateTrigger(updateRateHardLimit);
/// <summary>
/// Resets the device provider and disposes all devices and update triggers.
/// </summary>
protected virtual void Reset()
{
foreach (IDeviceUpdateTrigger updateTrigger in UpdateTriggerMapping.Values)
updateTrigger.Dispose();
foreach (IRGBDevice device in Devices)
device.Dispose();
Devices = Enumerable.Empty<IRGBDevice>();
UpdateTriggerMapping.Clear();
IsInitialized = false;
}
/// <summary>
/// Triggers the <see cref="Exception"/>-event and throws the specified exception if <see cref="ThrowsExceptions"/> is true and it is not overriden in the event.
/// </summary>
/// <param name="ex">The exception to throw.</param>
/// <param name="isCritical">Indicates if the exception is critical for device provider to work correctly.</param>
protected virtual void Throw(Exception ex, bool isCritical = false)
{
ExceptionEventArgs args = new(ex, isCritical, ThrowsExceptions);
try { OnException(args); } catch { /* we don't want to throw due to bad event handlers */ }
if (args.Throw)
throw new DeviceProviderException(ex, isCritical);
}
/// <summary>
/// Throws the <see cref="Exception"/> event.
/// </summary>
/// <param name="args">The parameters passed to the event.</param>
protected virtual void OnException(ExceptionEventArgs args) => Exception?.Invoke(this, args);
/// <inheritdoc />
public virtual void Dispose()
{
Reset();
GC.SuppressFinalize(this);
}
#endregion
}

View File

@ -1,99 +1,98 @@
using System;
using System.Collections.Generic;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc cref="IEnumerable{Led}" />
/// <inheritdoc cref="IBindable" />
/// <inheritdoc cref="IDisposable" />
/// <summary>
/// Represents a generic RGB-device.
/// </summary>
public interface IRGBDevice : IEnumerable<Led>, IPlaceable, IBindable, IDisposable
{
/// <inheritdoc cref="IEnumerable{Led}" />
/// <inheritdoc cref="IBindable" />
/// <inheritdoc cref="IDisposable" />
#region Properties
/// <summary>
/// Represents a generic RGB-device.
/// Gets the surface this device is attached to.
/// </summary>
public interface IRGBDevice : IEnumerable<Led>, IPlaceable, IBindable, IDisposable
{
#region Properties
RGBSurface? Surface { get; internal set; }
/// <summary>
/// Gets the surface this device is attached to.
/// </summary>
RGBSurface? Surface { get; internal set; }
/// <summary>
/// Gets generic information about the <see cref="IRGBDevice"/>.
/// </summary>
IRGBDeviceInfo DeviceInfo { get; }
/// <summary>
/// Gets a list of color corrections applied to this device.
/// </summary>
IList<IColorCorrection> ColorCorrections { get; }
#endregion
#region Indexer
/// <summary>
/// Gets the <see cref="Led"/> with the specified <see cref="LedId"/>.
/// </summary>
/// <param name="ledId">The <see cref="LedId"/> of the <see cref="Led"/> to get.</param>
/// <returns>The <see cref="Led"/> with the specified <see cref="LedId"/> or null if no <see cref="Led"/> is found.</returns>
Led? this[LedId ledId] { get; }
/// <summary>
/// Gets the <see cref="Led" /> at the specified physical location.
/// </summary>
/// <param name="location">The <see cref="Point"/> to get the location from.</param>
/// <returns>The <see cref="Led"/> at the specified <see cref="Point"/> or null if no location is found.</returns>
Led? this[Point location] { get; }
/// <summary>
/// Gets a list of <see cref="Led" /> inside the specified <see cref="Rectangle"/>.
/// </summary>
/// <param name="referenceRect">The <see cref="Rectangle"/> to check.</param>
/// <param name="minOverlayPercentage">The minimal percentage overlay a <see cref="Led"/> must have with the <see cref="Rectangle" /> to be taken into the list.</param>
/// <returns>A enumerable of leds inside the specified rectangle.</returns>
IEnumerable<Led> this[Rectangle referenceRect, double minOverlayPercentage = 0.5] { get; }
#endregion
#region Methods
/// <summary>
/// Perform an update for all dirty <see cref="Led"/>, or all <see cref="Led"/> if flushLeds is set to true.
/// </summary>
/// <param name="flushLeds">Specifies whether all <see cref="Led"/> (including clean ones) should be updated.</param>
void Update(bool flushLeds = false);
/// <summary>
/// Adds a led to the device.
/// </summary>
/// <param name="ledId">The id of the led.</param>
/// <param name="location">The location of the led on the device.</param>
/// <param name="size">The size of the led.</param>
/// <param name="customData">Custom data saved on the led.</param>
/// <returns>The newly added led or <c>null</c> if a led with this id is already added.</returns>
Led? AddLed(LedId ledId, in Point location, in Size size, object? customData = null);
/// <summary>
/// Removes the led with the specified id from the device.
/// </summary>
/// <param name="ledId">The id of the led to remove.</param>
/// <returns>The removed led or <c>null</c> if there was no led with the specified id.</returns>
Led? RemoveLed(LedId ledId);
#endregion
}
/// <inheritdoc />
/// <summary>
/// Represents a generic RGB-device with an known device-info type.
/// Gets generic information about the <see cref="IRGBDevice"/>.
/// </summary>
public interface IRGBDevice<out TDeviceInfo> : IRGBDevice
where TDeviceInfo : IRGBDeviceInfo
{
/// <summary>
/// Gets generic information about the <see cref="IRGBDevice"/>.
/// </summary>
new TDeviceInfo DeviceInfo { get; }
}
IRGBDeviceInfo DeviceInfo { get; }
/// <summary>
/// Gets a list of color corrections applied to this device.
/// </summary>
IList<IColorCorrection> ColorCorrections { get; }
#endregion
#region Indexer
/// <summary>
/// Gets the <see cref="Led"/> with the specified <see cref="LedId"/>.
/// </summary>
/// <param name="ledId">The <see cref="LedId"/> of the <see cref="Led"/> to get.</param>
/// <returns>The <see cref="Led"/> with the specified <see cref="LedId"/> or null if no <see cref="Led"/> is found.</returns>
Led? this[LedId ledId] { get; }
/// <summary>
/// Gets the <see cref="Led" /> at the specified physical location.
/// </summary>
/// <param name="location">The <see cref="Point"/> to get the location from.</param>
/// <returns>The <see cref="Led"/> at the specified <see cref="Point"/> or null if no location is found.</returns>
Led? this[Point location] { get; }
/// <summary>
/// Gets a list of <see cref="Led" /> inside the specified <see cref="Rectangle"/>.
/// </summary>
/// <param name="referenceRect">The <see cref="Rectangle"/> to check.</param>
/// <param name="minOverlayPercentage">The minimal percentage overlay a <see cref="Led"/> must have with the <see cref="Rectangle" /> to be taken into the list.</param>
/// <returns>A enumerable of leds inside the specified rectangle.</returns>
IEnumerable<Led> this[Rectangle referenceRect, double minOverlayPercentage = 0.5] { get; }
#endregion
#region Methods
/// <summary>
/// Perform an update for all dirty <see cref="Led"/>, or all <see cref="Led"/> if flushLeds is set to true.
/// </summary>
/// <param name="flushLeds">Specifies whether all <see cref="Led"/> (including clean ones) should be updated.</param>
void Update(bool flushLeds = false);
/// <summary>
/// Adds a led to the device.
/// </summary>
/// <param name="ledId">The id of the led.</param>
/// <param name="location">The location of the led on the device.</param>
/// <param name="size">The size of the led.</param>
/// <param name="customData">Custom data saved on the led.</param>
/// <returns>The newly added led or <c>null</c> if a led with this id is already added.</returns>
Led? AddLed(LedId ledId, in Point location, in Size size, object? customData = null);
/// <summary>
/// Removes the led with the specified id from the device.
/// </summary>
/// <param name="ledId">The id of the led to remove.</param>
/// <returns>The removed led or <c>null</c> if there was no led with the specified id.</returns>
Led? RemoveLed(LedId ledId);
#endregion
}
/// <inheritdoc />
/// <summary>
/// Represents a generic RGB-device with an known device-info type.
/// </summary>
public interface IRGBDevice<out TDeviceInfo> : IRGBDevice
where TDeviceInfo : IRGBDeviceInfo
{
/// <summary>
/// Gets generic information about the <see cref="IRGBDevice"/>.
/// </summary>
new TDeviceInfo DeviceInfo { get; }
}

View File

@ -1,37 +1,36 @@
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic information for a <see cref="IRGBDevice"/>
/// </summary>
public interface IRGBDeviceInfo
{
#region Properties & Fields
/// <summary>
/// Represents a generic information for a <see cref="IRGBDevice"/>
/// Gets the <see cref="RGBDeviceType"/> of the <see cref="IRGBDevice"/>.
/// </summary>
public interface IRGBDeviceInfo
{
#region Properties & Fields
RGBDeviceType DeviceType { get; }
/// <summary>
/// Gets the <see cref="RGBDeviceType"/> of the <see cref="IRGBDevice"/>.
/// </summary>
RGBDeviceType DeviceType { get; }
/// <summary>
/// Unique name of the <see cref="IRGBDevice"/>.
/// </summary>
string DeviceName { get; }
/// <summary>
/// Unique name of the <see cref="IRGBDevice"/>.
/// </summary>
string DeviceName { get; }
/// <summary>
/// Gets the manufacturer-name of the <see cref="IRGBDevice"/>.
/// </summary>
string Manufacturer { get; }
/// <summary>
/// Gets the manufacturer-name of the <see cref="IRGBDevice"/>.
/// </summary>
string Manufacturer { get; }
/// <summary>
/// Gets the model-name of the <see cref="IRGBDevice"/>.
/// </summary>
string Model { get; }
/// <summary>
/// Gets the model-name of the <see cref="IRGBDevice"/>.
/// </summary>
string Model { get; }
/// <summary>
/// Gets custom metadata added to the layout.
/// </summary>
object? LayoutMetadata { get; set; }
/// <summary>
/// Gets custom metadata added to the layout.
/// </summary>
object? LayoutMetadata { get; set; }
#endregion
}
}
#endregion
}

View File

@ -3,56 +3,55 @@
using System;
using System.Collections.Generic;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic device provider.
/// </summary>
public interface IRGBDeviceProvider : IDisposable
{
#region Properties & Fields
/// <summary>
/// Represents a generic device provider.
/// Indicates if the used SDK is initialized and ready to use.
/// </summary>
public interface IRGBDeviceProvider : IDisposable
{
#region Properties & Fields
bool IsInitialized { get; }
/// <summary>
/// Indicates if the used SDK is initialized and ready to use.
/// </summary>
bool IsInitialized { get; }
/// <summary>
/// Indicates if exceptions in the device provider are thrown or silently ignored.
/// </summary>
bool ThrowsExceptions { get; }
/// <summary>
/// Indicates if exceptions in the device provider are thrown or silently ignored.
/// </summary>
bool ThrowsExceptions { get; }
/// <summary>
/// Gets a collection of <see cref="IRGBDevice"/> loaded by this <see cref="IRGBDeviceProvider"/>.
/// </summary>
IEnumerable<IRGBDevice> Devices { get; }
/// <summary>
/// Gets a collection of <see cref="IRGBDevice"/> loaded by this <see cref="IRGBDeviceProvider"/>.
/// </summary>
IEnumerable<IRGBDevice> Devices { get; }
/// <summary>
/// Gets a collection <see cref="IDeviceUpdateTrigger"/> registered to this device provider.
/// </summary>
IReadOnlyList<(int id, IDeviceUpdateTrigger trigger)> UpdateTriggers { get; }
/// <summary>
/// Gets a collection <see cref="IDeviceUpdateTrigger"/> registered to this device provider.
/// </summary>
IReadOnlyList<(int id, IDeviceUpdateTrigger trigger)> UpdateTriggers { get; }
#endregion
#endregion
#region Events
#region Events
/// <summary>
/// Occurs when an exception is thrown in the device provider.
/// </summary>
event EventHandler<ExceptionEventArgs>? Exception;
/// <summary>
/// Occurs when an exception is thrown in the device provider.
/// </summary>
event EventHandler<ExceptionEventArgs>? Exception;
#endregion
#endregion
#region Methods
#region Methods
/// <summary>
/// Initializes the device provider and loads available devices.
/// </summary>
/// <param name="loadFilter"><see cref="RGBDeviceType"/>-flags to filter the devices to load.</param>
/// <param name="throwExceptions">Specifies if exceptions should be thrown or silently be ignored.</param>
/// <returns><c>true</c> if the initialization was successful; <c>false</c> otherwise.</returns>
bool Initialize(RGBDeviceType loadFilter = RGBDeviceType.All, bool throwExceptions = false);
/// <summary>
/// Initializes the device provider and loads available devices.
/// </summary>
/// <param name="loadFilter"><see cref="RGBDeviceType"/>-flags to filter the devices to load.</param>
/// <param name="throwExceptions">Specifies if exceptions should be thrown or silently be ignored.</param>
/// <returns><c>true</c> if the initialization was successful; <c>false</c> otherwise.</returns>
bool Initialize(RGBDeviceType loadFilter = RGBDeviceType.All, bool throwExceptions = false);
#endregion
}
}
#endregion
}

View File

@ -1,18 +1,17 @@
// ReSharper disable InconsistentNaming
#pragma warning disable 1591
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Contains a list of available keyboard layout types.
/// </summary>
public enum KeyboardLayoutType
{
/// <summary>
/// Contains a list of available keyboard layout types.
/// </summary>
public enum KeyboardLayoutType
{
Unknown = 0,
ANSI = 1,
ISO = 2,
JIS = 3,
ABNT = 4,
KS = 5
}
}
Unknown = 0,
ANSI = 1,
ISO = 2,
JIS = 3,
ABNT = 4,
KS = 5
}

View File

@ -1,101 +1,100 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Contains a list of different types of device.
/// </summary>
[Flags]
public enum RGBDeviceType
{
/// <summary>
/// Contains a list of different types of device.
/// Represents nothing.
/// </summary>
[Flags]
public enum RGBDeviceType
{
/// <summary>
/// Represents nothing.
/// </summary>
None = 0,
None = 0,
/// <summary>
/// Represents a keyboard.
/// </summary>
Keyboard = 1 << 0,
/// <summary>
/// Represents a keyboard.
/// </summary>
Keyboard = 1 << 0,
/// <summary>
/// Represents a mouse.
/// </summary>
Mouse = 1 << 1,
/// <summary>
/// Represents a mouse.
/// </summary>
Mouse = 1 << 1,
/// <summary>
/// Represents a headset.
/// </summary>
Headset = 1 << 2,
/// <summary>
/// Represents a headset.
/// </summary>
Headset = 1 << 2,
/// <summary>
/// Represents a mousepad.
/// </summary>
Mousepad = 1 << 3,
/// <summary>
/// Represents a mousepad.
/// </summary>
Mousepad = 1 << 3,
/// <summary>
/// Represents a LED-stipe.
/// </summary>
LedStripe = 1 << 4,
/// <summary>
/// Represents a LED-stipe.
/// </summary>
LedStripe = 1 << 4,
/// <summary>
/// Represents a LED-matrix.
/// </summary>
LedMatrix = 1 << 5,
/// <summary>
/// Represents a LED-matrix.
/// </summary>
LedMatrix = 1 << 5,
/// <summary>
/// Represents a Mainboard.
/// </summary>
Mainboard = 1 << 6,
/// <summary>
/// Represents a Mainboard.
/// </summary>
Mainboard = 1 << 6,
/// <summary>
/// Represents a Graphics card.
/// </summary>
GraphicsCard = 1 << 7,
/// <summary>
/// Represents a Graphics card.
/// </summary>
GraphicsCard = 1 << 7,
/// <summary>
/// Represents a DRAM-bank.
/// </summary>
DRAM = 1 << 8,
/// <summary>
/// Represents a DRAM-bank.
/// </summary>
DRAM = 1 << 8,
/// <summary>
/// Represents a headset stand.
/// </summary>
HeadsetStand = 1 << 9,
/// <summary>
/// Represents a headset stand.
/// </summary>
HeadsetStand = 1 << 9,
/// <summary>
/// Represents a keypad.
/// </summary>
Keypad = 1 << 10,
/// <summary>
/// Represents a keypad.
/// </summary>
Keypad = 1 << 10,
/// <summary>
/// Represents a fan.
/// </summary>
Fan = 1 << 11,
/// <summary>
/// Represents a fan.
/// </summary>
Fan = 1 << 11,
/// <summary>
/// Represents a speaker
/// </summary>
Speaker = 1 << 12,
/// <summary>
/// Represents a speaker
/// </summary>
Speaker = 1 << 12,
/// <summary>
/// Represents a cooler.
/// </summary>
Cooler = 1 << 13,
/// <summary>
/// Represents a cooler.
/// </summary>
Cooler = 1 << 13,
/// <summary>
/// Represents a monitor.
/// </summary>
Monitor = 1 << 14,
/// <summary>
/// Represents a monitor.
/// </summary>
Monitor = 1 << 14,
/// <summary>
/// Represents a device where the type is not known or not present in the list.
/// </summary>
Unknown = 1 << 31,
/// <summary>
/// Represents a device where the type is not known or not present in the list.
/// </summary>
Unknown = 1 << 31,
/// <summary>
/// Represents all devices.
/// </summary>
All = ~None
}
}
/// <summary>
/// Represents all devices.
/// </summary>
All = ~None
}

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a cooler-device
/// </summary>
public interface ICooler : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a cooler-device
/// </summary>
public interface ICooler : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a DRAM-device
/// </summary>
public interface IDRAM : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a DRAM-device
/// </summary>
public interface IDRAM : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// represents a fan-device
/// </summary>
public interface IFan : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// represents a fan-device
/// </summary>
public interface IFan : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a graphics-card-device
/// </summary>
public interface IGraphicsCard : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a graphics-card-device
/// </summary>
public interface IGraphicsCard : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a headset-device
/// </summary>
public interface IHeadset : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a headset-device
/// </summary>
public interface IHeadset : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a headset-stand-device
/// </summary>
public interface IHeadsetStand : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a headset-stand-device
/// </summary>
public interface IHeadsetStand : IRGBDevice
{ }

View File

@ -1,24 +1,23 @@
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic keyboard-device.
/// </summary>
public interface IKeyboard : IRGBDevice
{
/// <summary>
/// Represents a generic keyboard-device.
/// Gets the device information assiciated with this device.
/// </summary>
public interface IKeyboard : IRGBDevice
{
/// <summary>
/// Gets the device information assiciated with this device.
/// </summary>
new IKeyboardDeviceInfo DeviceInfo { get; }
}
/// <summary>
/// Represents a generic keyboard device information.
/// </summary>
public interface IKeyboardDeviceInfo : IRGBDeviceInfo
{
/// <summary>
/// Gets the <see cref="KeyboardLayoutType"/> of the keyboard.
/// </summary>
KeyboardLayoutType Layout { get; }
}
new IKeyboardDeviceInfo DeviceInfo { get; }
}
/// <summary>
/// Represents a generic keyboard device information.
/// </summary>
public interface IKeyboardDeviceInfo : IRGBDeviceInfo
{
/// <summary>
/// Gets the <see cref="KeyboardLayoutType"/> of the keyboard.
/// </summary>
KeyboardLayoutType Layout { get; }
}

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a keypad-device
/// </summary>
public interface IKeypad : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a keypad-device
/// </summary>
public interface IKeypad : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a led-matrix-device
/// </summary>
public interface ILedMatrix : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a led-matrix-device
/// </summary>
public interface ILedMatrix : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a led-stripe-device
/// </summary>
public interface ILedStripe : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a led-stripe-device
/// </summary>
public interface ILedStripe : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a mainboard-device
/// </summary>
public interface IMainboard : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a mainboard-device
/// </summary>
public interface IMainboard : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a mouse-device
/// </summary>
public interface IMouse : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a mouse-device
/// </summary>
public interface IMouse : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a mousepad-device
/// </summary>
public interface IMousepad : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a mousepad-device
/// </summary>
public interface IMousepad : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a speaker-device
/// </summary>
public interface ISpeaker : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a speaker-device
/// </summary>
public interface ISpeaker : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a device with unkown or not specified type.
/// </summary>
public interface IUnknownDevice : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a device with unkown or not specified type.
/// </summary>
public interface IUnknownDevice : IRGBDevice
{ }

View File

@ -3,49 +3,48 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.Exception" />-event.
/// </summary>
public class ExceptionEventArgs : EventArgs
{
#region Properties & Fields
/// <summary>
/// Gets the <see cref="System.Exception"/> which is responsible for the event-call.
/// </summary>
public Exception Exception { get; }
/// <summary>
/// Gets a bool indicating if the exception is critical for the thrower.
/// </summary>
public bool IsCritical { get; }
/// <summary>
/// Gets or sets if the exception should be thrown after the event is handled.
/// </summary>
public bool Throw { get; set; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.Exception" />-event.
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.ExceptionEventArgs" /> class.
/// </summary>
public class ExceptionEventArgs : EventArgs
/// <param name="exception">The <see cref="T:System.Exception" /> which is responsible for the event-call.</param>
/// <param name="isCritical">Indicates if the exception is critical for the thrower.</param>
/// <param name="throw">Indicates if the exception should be thrown after the event is handled.</param>
public ExceptionEventArgs(Exception exception, bool isCritical = false, bool @throw = false)
{
#region Properties & Fields
/// <summary>
/// Gets the <see cref="System.Exception"/> which is responsible for the event-call.
/// </summary>
public Exception Exception { get; }
/// <summary>
/// Gets a bool indicating if the exception is critical for the thrower.
/// </summary>
public bool IsCritical { get; }
/// <summary>
/// Gets or sets if the exception should be thrown after the event is handled.
/// </summary>
public bool Throw { get; set; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.ExceptionEventArgs" /> class.
/// </summary>
/// <param name="exception">The <see cref="T:System.Exception" /> which is responsible for the event-call.</param>
/// <param name="isCritical">Indicates if the exception is critical for the thrower.</param>
/// <param name="throw">Indicates if the exception should be thrown after the event is handled.</param>
public ExceptionEventArgs(Exception exception, bool isCritical = false, bool @throw = false)
{
this.Exception = exception;
this.IsCritical = isCritical;
this.Throw = @throw;
}
#endregion
this.Exception = exception;
this.IsCritical = isCritical;
this.Throw = @throw;
}
}
#endregion
}

View File

@ -3,65 +3,64 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.SurfaceLayoutChanged" />-event.
/// </summary>
public class SurfaceLayoutChangedEventArgs : EventArgs
{
#region Properties & Fields
/// <summary>
/// Gets the <see cref="IRGBDevice"/> that caused the change. Returns null if the change isn't caused by a <see cref="IRGBDevice"/>.
/// </summary>
public IRGBDevice? Devices { get; }
/// <summary>
/// Gets a value indicating if the event is caused by the addition of a new <see cref="IRGBDevice"/> to the <see cref="RGBSurface"/>.
/// </summary>
public bool DeviceAdded { get; }
/// <summary>
/// Gets a value indicating if the event is caused by the removal of a <see cref="IRGBDevice"/> to the <see cref="RGBSurface"/>.
/// </summary>
public bool DeviceRemoved { get; }
/// <summary>
/// Gets a value indicating if the event is caused by a changed location or size of one of the <see cref="IRGBDevice"/> on the <see cref="RGBSurface"/>.
/// </summary>
public bool DeviceChanged { get; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.SurfaceLayoutChanged" />-event.
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.SurfaceLayoutChangedEventArgs" /> class.
/// </summary>
public class SurfaceLayoutChangedEventArgs : EventArgs
/// <param name="devices">The <see cref="T:RGB.NET.Core.IRGBDevice" /> that caused the change.</param>
/// <param name="deviceAdded">A value indicating if the event is caused by the addition of a new <see cref="T:RGB.NET.Core.IRGBDevice" /> to the <see cref="T:RGB.NET.Core.RGBSurface" />.</param>
/// <param name="deviceRemoved">A value indicating if the event is caused by the removal of a <see cref="T:RGB.NET.Core.IRGBDevice" /> from the <see cref="T:RGB.NET.Core.RGBSurface" />.</param>
/// <param name="deviceChanged">A value indicating if the event is caused by a change to a <see cref="T:RGB.NET.Core.IRGBDevice" /> on the <see cref="T:RGB.NET.Core.RGBSurface" />.</param>
private SurfaceLayoutChangedEventArgs(IRGBDevice? devices, bool deviceAdded, bool deviceRemoved, bool deviceChanged)
{
#region Properties & Fields
/// <summary>
/// Gets the <see cref="IRGBDevice"/> that caused the change. Returns null if the change isn't caused by a <see cref="IRGBDevice"/>.
/// </summary>
public IRGBDevice? Devices { get; }
/// <summary>
/// Gets a value indicating if the event is caused by the addition of a new <see cref="IRGBDevice"/> to the <see cref="RGBSurface"/>.
/// </summary>
public bool DeviceAdded { get; }
/// <summary>
/// Gets a value indicating if the event is caused by the removal of a <see cref="IRGBDevice"/> to the <see cref="RGBSurface"/>.
/// </summary>
public bool DeviceRemoved { get; }
/// <summary>
/// Gets a value indicating if the event is caused by a changed location or size of one of the <see cref="IRGBDevice"/> on the <see cref="RGBSurface"/>.
/// </summary>
public bool DeviceChanged { get; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.SurfaceLayoutChangedEventArgs" /> class.
/// </summary>
/// <param name="devices">The <see cref="T:RGB.NET.Core.IRGBDevice" /> that caused the change.</param>
/// <param name="deviceAdded">A value indicating if the event is caused by the addition of a new <see cref="T:RGB.NET.Core.IRGBDevice" /> to the <see cref="T:RGB.NET.Core.RGBSurface" />.</param>
/// <param name="deviceRemoved">A value indicating if the event is caused by the removal of a <see cref="T:RGB.NET.Core.IRGBDevice" /> from the <see cref="T:RGB.NET.Core.RGBSurface" />.</param>
/// <param name="deviceChanged">A value indicating if the event is caused by a change to a <see cref="T:RGB.NET.Core.IRGBDevice" /> on the <see cref="T:RGB.NET.Core.RGBSurface" />.</param>
private SurfaceLayoutChangedEventArgs(IRGBDevice? devices, bool deviceAdded, bool deviceRemoved, bool deviceChanged)
{
this.Devices = devices;
this.DeviceAdded = deviceAdded;
this.DeviceRemoved = deviceRemoved;
this.DeviceChanged = deviceChanged;
}
#endregion
#region Factory
internal static SurfaceLayoutChangedEventArgs FromAddedDevice(IRGBDevice device) => new(device, true, false, false);
internal static SurfaceLayoutChangedEventArgs FromRemovedDevice(IRGBDevice device) => new(device, false, true, false);
internal static SurfaceLayoutChangedEventArgs FromChangedDevice(IRGBDevice device) => new(device, false, false, true);
internal static SurfaceLayoutChangedEventArgs Misc() => new(null, false, false, false);
#endregion
this.Devices = devices;
this.DeviceAdded = deviceAdded;
this.DeviceRemoved = deviceRemoved;
this.DeviceChanged = deviceChanged;
}
}
#endregion
#region Factory
internal static SurfaceLayoutChangedEventArgs FromAddedDevice(IRGBDevice device) => new(device, true, false, false);
internal static SurfaceLayoutChangedEventArgs FromRemovedDevice(IRGBDevice device) => new(device, false, true, false);
internal static SurfaceLayoutChangedEventArgs FromChangedDevice(IRGBDevice device) => new(device, false, false, true);
internal static SurfaceLayoutChangedEventArgs Misc() => new(null, false, false, false);
#endregion
}

View File

@ -1,11 +1,10 @@
using System;
namespace RGB.NET.Core
{
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.Updated" />-event.
/// </summary>
public class UpdatedEventArgs : EventArgs
{ }
}
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.Updated" />-event.
/// </summary>
public class UpdatedEventArgs : EventArgs
{ }

View File

@ -3,49 +3,48 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.Updating" />-event.
/// </summary>
public class UpdatingEventArgs : EventArgs
{
#region Properties & Fields
/// <summary>
/// Gets the elapsed time (in seconds) since the last update.
/// </summary>
public double DeltaTime { get; }
/// <summary>
/// Gets the trigger causing this update.
/// </summary>
public IUpdateTrigger? Trigger { get; }
/// <summary>
/// Gets the custom-data provided by the trigger for this update.
/// </summary>
public CustomUpdateData CustomData { get; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.Updating" />-event.
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.UpdatingEventArgs" /> class.
/// </summary>
public class UpdatingEventArgs : EventArgs
/// <param name="deltaTime">The elapsed time (in seconds) since the last update.</param>
/// <param name="trigger">The trigger causing this update.</param>
/// <param name="customData">The custom-data provided by the trigger for this update.</param>
public UpdatingEventArgs(double deltaTime, IUpdateTrigger? trigger, CustomUpdateData customData)
{
#region Properties & Fields
/// <summary>
/// Gets the elapsed time (in seconds) since the last update.
/// </summary>
public double DeltaTime { get; }
/// <summary>
/// Gets the trigger causing this update.
/// </summary>
public IUpdateTrigger? Trigger { get; }
/// <summary>
/// Gets the custom-data provided by the trigger for this update.
/// </summary>
public CustomUpdateData CustomData { get; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.UpdatingEventArgs" /> class.
/// </summary>
/// <param name="deltaTime">The elapsed time (in seconds) since the last update.</param>
/// <param name="trigger">The trigger causing this update.</param>
/// <param name="customData">The custom-data provided by the trigger for this update.</param>
public UpdatingEventArgs(double deltaTime, IUpdateTrigger? trigger, CustomUpdateData customData)
{
this.DeltaTime = deltaTime;
this.Trigger = trigger;
this.CustomData = customData;
}
#endregion
this.DeltaTime = deltaTime;
this.Trigger = trigger;
this.CustomData = customData;
}
}
#endregion
}

View File

@ -1,35 +1,34 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents an exception thrown by a <see cref="IRGBDeviceProvider" />.
/// </summary>
public class DeviceProviderException : ApplicationException
{
/// <inheritdoc />
#region Properties & Fields
/// <summary>
/// Represents an exception thrown by a <see cref="IRGBDeviceProvider" />.
/// Gets a bool indicating if the exception is critical and shouldn't be ingored.
/// </summary>
public class DeviceProviderException : ApplicationException
public bool IsCritical { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="DeviceProviderException" /> class.
/// </summary>
/// <param name="innerException">The exception that is the casue of the current exception or null if this exception was thrown on purpose.</param>
/// <param name="isCritical">A value indicating if the exception is critical and shouldn't be ignored.</param>
public DeviceProviderException(Exception? innerException, bool isCritical)
: base(innerException?.Message, innerException)
{
#region Properties & Fields
/// <summary>
/// Gets a bool indicating if the exception is critical and shouldn't be ingored.
/// </summary>
public bool IsCritical { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="DeviceProviderException" /> class.
/// </summary>
/// <param name="innerException">The exception that is the casue of the current exception or null if this exception was thrown on purpose.</param>
/// <param name="isCritical">A value indicating if the exception is critical and shouldn't be ignored.</param>
public DeviceProviderException(Exception? innerException, bool isCritical)
: base(innerException?.Message, innerException)
{
this.IsCritical = isCritical;
}
#endregion
this.IsCritical = isCritical;
}
}
#endregion
}

View File

@ -1,25 +1,24 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents an exception thrown by an <see cref="T:RGB.NET.Core.IRGBDevice" />.
/// </summary>
public class RGBDeviceException : ApplicationException
{
#region Constructors
/// <inheritdoc />
/// <summary>
/// Represents an exception thrown by an <see cref="T:RGB.NET.Core.IRGBDevice" />.
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.RGBDeviceException" /> class.
/// </summary>
public class RGBDeviceException : ApplicationException
{
#region Constructors
/// <param name="message">The message which describes the reason of throwing this exception.</param>
/// <param name="innerException">Optional inner exception, which lead to this exception.</param>
public RGBDeviceException(string message, Exception? innerException = null)
: base(message, innerException)
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.RGBDeviceException" /> class.
/// </summary>
/// <param name="message">The message which describes the reason of throwing this exception.</param>
/// <param name="innerException">Optional inner exception, which lead to this exception.</param>
public RGBDeviceException(string message, Exception? innerException = null)
: base(message, innerException)
{ }
#endregion
}
}
#endregion
}

View File

@ -1,25 +1,24 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents an exception thrown by an <see cref="T:RGB.NET.Core.RGBSurface" />.
/// </summary>
public class RGBSurfaceException : ApplicationException
{
#region Constructors
/// <inheritdoc />
/// <summary>
/// Represents an exception thrown by an <see cref="T:RGB.NET.Core.RGBSurface" />.
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.RGBSurfaceException" /> class.
/// </summary>
public class RGBSurfaceException : ApplicationException
{
#region Constructors
/// <param name="message">The message which describes the reason of throwing this exception.</param>
/// <param name="innerException">Optional inner exception, which lead to this exception.</param>
public RGBSurfaceException(string message, Exception? innerException = null)
: base(message, innerException)
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.RGBSurfaceException" /> class.
/// </summary>
/// <param name="message">The message which describes the reason of throwing this exception.</param>
/// <param name="innerException">Optional inner exception, which lead to this exception.</param>
public RGBSurfaceException(string message, Exception? innerException = null)
: base(message, innerException)
{ }
#endregion
}
}
#endregion
}

View File

@ -1,33 +1,32 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Offers some extensions and helper-methods for <see cref="Color"/> related things.
/// </summary>
public static class ColorExtensions
{
#region Methods
/// <summary>
/// Offers some extensions and helper-methods for <see cref="Color"/> related things.
/// Calculates the distance between the two specified colors using the redmean algorithm.
/// For more infos check https://www.compuphase.com/cmetric.htm
/// </summary>
public static class ColorExtensions
/// <param name="color1">The start color of the distance calculation.</param>
/// <param name="color2">The end color fot the distance calculation.</param>
/// <returns>The redmean distance between the two specified colors.</returns>
public static double DistanceTo(this in Color color1, in Color color2)
{
#region Methods
(_, byte r1, byte g1, byte b1) = color1.GetRGBBytes();
(_, byte r2, byte g2, byte b2) = color2.GetRGBBytes();
/// <summary>
/// Calculates the distance between the two specified colors using the redmean algorithm.
/// For more infos check https://www.compuphase.com/cmetric.htm
/// </summary>
/// <param name="color1">The start color of the distance calculation.</param>
/// <param name="color2">The end color fot the distance calculation.</param>
/// <returns>The redmean distance between the two specified colors.</returns>
public static double DistanceTo(this in Color color1, in Color color2)
{
(_, byte r1, byte g1, byte b1) = color1.GetRGBBytes();
(_, byte r2, byte g2, byte b2) = color2.GetRGBBytes();
long rmean = (r1 + r2) / 2;
long r = r1 - r2;
long g = g1 - g2;
long b = b1 - b2;
return Math.Sqrt((((512 + rmean) * r * r) >> 8) + (4 * g * g) + (((767 - rmean) * b * b) >> 8));
}
#endregion
long rmean = (r1 + r2) / 2;
long r = r1 - r2;
long g = g1 - g2;
long b = b1 - b2;
return Math.Sqrt((((512 + rmean) * r * r) >> 8) + (4 * g * g) + (((767 - rmean) * b * b) >> 8));
}
}
#endregion
}

View File

@ -1,111 +1,110 @@
using System;
using System.Runtime.CompilerServices;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Offers some extensions and helper-methods for the work with floats.
/// </summary>
public static class FloatExtensions
{
#region Constants
/// <summary>
/// Offers some extensions and helper-methods for the work with floats.
/// Defines the precision RGB.NET processes floating point comparisons in.
/// </summary>
public static class FloatExtensions
public const float TOLERANCE = 1E-7f;
#endregion
#region Methods
/// <summary>
/// Checks if two values are equal respecting the <see cref="TOLERANCE"/>.
/// </summary>
/// <param name="value1">The first value to compare.</param>
/// <param name="value2">The first value to compare.</param>
/// <returns><c>true</c> if the difference is smaller than the <see cref="TOLERANCE"/>; otherwise, <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EqualsInTolerance(this float value1, float value2) => Math.Abs(value1 - value2) < TOLERANCE;
/// <summary>
/// Clamps the provided value to be bigger or equal min and smaller or equal max.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The lower value of the range the value is clamped to.</param>
/// <param name="max">The higher value of the range the value is clamped to.</param>
/// <returns>The clamped value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Clamp(this float value, float min, float max)
{
#region Constants
/// <summary>
/// Defines the precision RGB.NET processes floating point comparisons in.
/// </summary>
public const float TOLERANCE = 1E-7f;
#endregion
#region Methods
/// <summary>
/// Checks if two values are equal respecting the <see cref="TOLERANCE"/>.
/// </summary>
/// <param name="value1">The first value to compare.</param>
/// <param name="value2">The first value to compare.</param>
/// <returns><c>true</c> if the difference is smaller than the <see cref="TOLERANCE"/>; otherwise, <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EqualsInTolerance(this float value1, float value2) => Math.Abs(value1 - value2) < TOLERANCE;
/// <summary>
/// Clamps the provided value to be bigger or equal min and smaller or equal max.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The lower value of the range the value is clamped to.</param>
/// <param name="max">The higher value of the range the value is clamped to.</param>
/// <returns>The clamped value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Clamp(this float value, float min, float max)
{
// ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10%
if (value < min) return min;
if (value > max) return max;
return value;
// ReSharper restore ConvertIfStatementToReturnStatement
}
/// <summary>
/// Clamps the provided value to be bigger or equal min and smaller or equal max.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The lower value of the range the value is clamped to.</param>
/// <param name="max">The higher value of the range the value is clamped to.</param>
/// <returns>The clamped value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Clamp(this int value, int min, int max)
{
// ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10%
if (value < min) return min;
if (value > max) return max;
return value;
// ReSharper restore ConvertIfStatementToReturnStatement
}
/// <summary>
/// Enforces the provided value to be in the specified range by wrapping it around the edges if it exceeds them.
/// </summary>
/// <param name="value">The value to wrap.</param>
/// <param name="min">The lower value of the range the value is wrapped into.</param>
/// <param name="max">The higher value of the range the value is wrapped into.</param>
/// <returns>The wrapped value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Wrap(this float value, float min, float max)
{
float range = max - min;
while (value >= max)
value -= range;
while (value < min)
value += range;
return value;
}
/// <summary>
/// Converts a normalized float value in the range [0..1] to a byte [0..255].
/// </summary>
/// <param name="percentage">The normalized float value to convert.</param>
/// <returns>The byte value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte GetByteValueFromPercentage(this float percentage)
{
if (float.IsNaN(percentage)) return 0;
percentage = percentage.Clamp(0, 1.0f);
return (byte)(percentage >= 1.0f ? 255 : percentage * 256.0f);
}
/// <summary>
/// Converts a byte value [0..255] to a normalized float value in the range [0..1].
/// </summary>
/// <param name="value">The byte value to convert.</param>
/// <returns>The normalized float value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float GetPercentageFromByteValue(this byte value)
=> value == 255 ? 1.0f : (value / 256.0f);
#endregion
// ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10%
if (value < min) return min;
if (value > max) return max;
return value;
// ReSharper restore ConvertIfStatementToReturnStatement
}
}
/// <summary>
/// Clamps the provided value to be bigger or equal min and smaller or equal max.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The lower value of the range the value is clamped to.</param>
/// <param name="max">The higher value of the range the value is clamped to.</param>
/// <returns>The clamped value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Clamp(this int value, int min, int max)
{
// ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10%
if (value < min) return min;
if (value > max) return max;
return value;
// ReSharper restore ConvertIfStatementToReturnStatement
}
/// <summary>
/// Enforces the provided value to be in the specified range by wrapping it around the edges if it exceeds them.
/// </summary>
/// <param name="value">The value to wrap.</param>
/// <param name="min">The lower value of the range the value is wrapped into.</param>
/// <param name="max">The higher value of the range the value is wrapped into.</param>
/// <returns>The wrapped value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Wrap(this float value, float min, float max)
{
float range = max - min;
while (value >= max)
value -= range;
while (value < min)
value += range;
return value;
}
/// <summary>
/// Converts a normalized float value in the range [0..1] to a byte [0..255].
/// </summary>
/// <param name="percentage">The normalized float value to convert.</param>
/// <returns>The byte value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte GetByteValueFromPercentage(this float percentage)
{
if (float.IsNaN(percentage)) return 0;
percentage = percentage.Clamp(0, 1.0f);
return (byte)(percentage >= 1.0f ? 255 : percentage * 256.0f);
}
/// <summary>
/// Converts a byte value [0..255] to a normalized float value in the range [0..1].
/// </summary>
/// <param name="value">The byte value to convert.</param>
/// <returns>The normalized float value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float GetPercentageFromByteValue(this byte value)
=> value == 255 ? 1.0f : (value / 256.0f);
#endregion
}

View File

@ -1,44 +1,43 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Offers some extensions and helper-methods for <see cref="Point"/> related things.
/// </summary>
public static class PointExtensions
{
#region Methods
/// <summary>
/// Offers some extensions and helper-methods for <see cref="Point"/> related things.
/// Moves the specified <see cref="Point"/> by the specified amount.
/// </summary>
public static class PointExtensions
/// <param name="point">The <see cref="Point"/> to move.</param>
/// <param name="x">The x-ammount to move.</param>
/// <param name="y">The y-ammount to move.</param>
/// <returns>The new location of the point.</returns>
public static Point Translate(this in Point point, float x = 0, float y = 0) => new(point.X + x, point.Y + y);
/// <summary>
/// Rotates the specified <see cref="Point"/> by the specified amuont around the specified origin.
/// </summary>
/// <param name="point">The <see cref="Point"/> to rotate.</param>
/// <param name="rotation">The rotation.</param>
/// <param name="origin">The origin to rotate around. [0,0] if not set.</param>
/// <returns>The new location of the point.</returns>
public static Point Rotate(this in Point point, in Rotation rotation, in Point origin = new())
{
#region Methods
float sin = MathF.Sin(rotation.Radians);
float cos = MathF.Cos(rotation.Radians);
/// <summary>
/// Moves the specified <see cref="Point"/> by the specified amount.
/// </summary>
/// <param name="point">The <see cref="Point"/> to move.</param>
/// <param name="x">The x-ammount to move.</param>
/// <param name="y">The y-ammount to move.</param>
/// <returns>The new location of the point.</returns>
public static Point Translate(this in Point point, float x = 0, float y = 0) => new(point.X + x, point.Y + y);
float x = point.X - origin.X;
float y = point.Y - origin.Y;
/// <summary>
/// Rotates the specified <see cref="Point"/> by the specified amuont around the specified origin.
/// </summary>
/// <param name="point">The <see cref="Point"/> to rotate.</param>
/// <param name="rotation">The rotation.</param>
/// <param name="origin">The origin to rotate around. [0,0] if not set.</param>
/// <returns>The new location of the point.</returns>
public static Point Rotate(this in Point point, in Rotation rotation, in Point origin = new())
{
float sin = MathF.Sin(rotation.Radians);
float cos = MathF.Cos(rotation.Radians);
x = (x * cos) - (y * sin);
y = (x * sin) + (y * cos);
float x = point.X - origin.X;
float y = point.Y - origin.Y;
x = (x * cos) - (y * sin);
y = (x * sin) + (y * cos);
return new Point(x + origin.X, y + origin.Y);
}
#endregion
return new Point(x + origin.X, y + origin.Y);
}
}
#endregion
}

View File

@ -1,177 +1,176 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Offers some extensions and helper-methods for the work with rectangles.
/// </summary>
public static class RectangleExtensions
{
#region Methods
/// <summary>
/// Offers some extensions and helper-methods for the work with rectangles.
/// Sets the <see cref="Rectangle.Location"/> of the specified rectangle.
/// </summary>
public static class RectangleExtensions
/// <param name="rect">The rectangle to modify.</param>
/// <param name="location">The new location of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetLocation(this in Rectangle rect, in Point location) => new(location, rect.Size);
/// <summary>
/// Sets the <see cref="Point.X"/> of the <see cref="Rectangle.Location"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="x">The new x-location of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetX(this in Rectangle rect, float x) => new(new Point(x, rect.Location.Y), rect.Size);
/// <summary>
/// Sets the <see cref="Point.Y"/> of the <see cref="Rectangle.Location"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="y">The new y-location of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetY(this in Rectangle rect, float y) => new(new Point(rect.Location.X, y), rect.Size);
/// <summary>
/// Sets the <see cref="Rectangle.Size"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="size">The new size of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetSize(this in Rectangle rect, in Size size) => new(rect.Location, size);
/// <summary>
/// Sets the <see cref="Size.Width"/> of the <see cref="Rectangle.Size"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="width">The new width of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetWidth(this in Rectangle rect, float width) => new(rect.Location, new Size(width, rect.Size.Height));
/// <summary>
/// Sets the <see cref="Size.Height"/> of the <see cref="Rectangle.Size"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="height">The new height of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetHeight(this in Rectangle rect, float height) => new(rect.Location, new Size(rect.Size.Width, height));
/// <summary>
/// Calculates the percentage of intersection of a rectangle.
/// </summary>
/// <param name="rect">The rectangle to calculate the intersection for.</param>
/// <param name="intersectingRect">The intersecting rectangle.</param>
/// <returns>The percentage of intersection.</returns>
public static float CalculateIntersectPercentage(this in Rectangle rect, in Rectangle intersectingRect)
{
#region Methods
if (rect.IsEmpty || intersectingRect.IsEmpty) return 0;
/// <summary>
/// Sets the <see cref="Rectangle.Location"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="location">The new location of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetLocation(this in Rectangle rect, in Point location) => new(location, rect.Size);
/// <summary>
/// Sets the <see cref="Point.X"/> of the <see cref="Rectangle.Location"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="x">The new x-location of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetX(this in Rectangle rect, float x) => new(new Point(x, rect.Location.Y), rect.Size);
/// <summary>
/// Sets the <see cref="Point.Y"/> of the <see cref="Rectangle.Location"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="y">The new y-location of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetY(this in Rectangle rect, float y) => new(new Point(rect.Location.X, y), rect.Size);
/// <summary>
/// Sets the <see cref="Rectangle.Size"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="size">The new size of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetSize(this in Rectangle rect, in Size size) => new(rect.Location, size);
/// <summary>
/// Sets the <see cref="Size.Width"/> of the <see cref="Rectangle.Size"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="width">The new width of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetWidth(this in Rectangle rect, float width) => new(rect.Location, new Size(width, rect.Size.Height));
/// <summary>
/// Sets the <see cref="Size.Height"/> of the <see cref="Rectangle.Size"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="height">The new height of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetHeight(this in Rectangle rect, float height) => new(rect.Location, new Size(rect.Size.Width, height));
/// <summary>
/// Calculates the percentage of intersection of a rectangle.
/// </summary>
/// <param name="rect">The rectangle to calculate the intersection for.</param>
/// <param name="intersectingRect">The intersecting rectangle.</param>
/// <returns>The percentage of intersection.</returns>
public static float CalculateIntersectPercentage(this in Rectangle rect, in Rectangle intersectingRect)
{
if (rect.IsEmpty || intersectingRect.IsEmpty) return 0;
Rectangle intersection = rect.CalculateIntersection(intersectingRect);
return (intersection.Size.Width * intersection.Size.Height) / (rect.Size.Width * rect.Size.Height);
}
/// <summary>
/// Calculates the <see cref="Rectangle"/> representing the intersection of this <see cref="Rectangle"/> and the one provided as parameter.
/// </summary>
/// <param name="rect">The rectangle to calculate the intersection for.</param>
/// <param name="intersectingRectangle">The intersecting <see cref="Rectangle"/>.</param>
/// <returns>A new <see cref="Rectangle"/> representing the intersection this <see cref="Rectangle"/> and the one provided as parameter.</returns>
public static Rectangle CalculateIntersection(this in Rectangle rect, in Rectangle intersectingRectangle)
{
float x1 = Math.Max(rect.Location.X, intersectingRectangle.Location.X);
float x2 = Math.Min(rect.Location.X + rect.Size.Width, intersectingRectangle.Location.X + intersectingRectangle.Size.Width);
float y1 = Math.Max(rect.Location.Y, intersectingRectangle.Location.Y);
float y2 = Math.Min(rect.Location.Y + rect.Size.Height, intersectingRectangle.Location.Y + intersectingRectangle.Size.Height);
if ((x2 >= x1) && (y2 >= y1))
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
return new Rectangle();
}
/// <summary>
/// Determines if the specified <see cref="Point"/> is contained within this <see cref="Rectangle"/>.
/// </summary>
/// <param name="rect">The containing rectangle.</param>
/// <param name="point">The <see cref="Point"/> to test.</param>
/// <returns><c>true</c> if the rectangle contains the specified point; otherwise <c>false</c>.</returns>
public static bool Contains(this in Rectangle rect, in Point point) => rect.Contains(point.X, point.Y);
/// <summary>
/// Determines if the specified location is contained within this <see cref="Rectangle"/>.
/// </summary>
/// <param name="rect">The containing rectangle.</param>
/// <param name="x">The X-location to test.</param>
/// <param name="y">The Y-location to test.</param>
/// <returns><c>true</c> if the rectangle contains the specified coordinates; otherwise <c>false</c>.</returns>
public static bool Contains(this in Rectangle rect, float x, float y) => (rect.Location.X <= x) && (x < (rect.Location.X + rect.Size.Width))
&& (rect.Location.Y <= y) && (y < (rect.Location.Y + rect.Size.Height));
/// <summary>
/// Determines if the specified <see cref="Rectangle"/> is contained within this <see cref="Rectangle"/>.
/// </summary>
/// <param name="rect">The containing rectangle.</param>
/// <param name="rect2">The <see cref="Rectangle"/> to test.</param>
/// <returns><c>true</c> if the rectangle contains the specified rect; otherwise <c>false</c>.</returns>
public static bool Contains(this in Rectangle rect, in Rectangle rect2) => (rect.Location.X <= rect2.Location.X) && ((rect2.Location.X + rect2.Size.Width) <= (rect.Location.X + rect.Size.Width))
&& (rect.Location.Y <= rect2.Location.Y) && ((rect2.Location.Y + rect2.Size.Height) <= (rect.Location.Y + rect.Size.Height));
/// <summary>
/// Moves the specified <see cref="Rectangle"/> by the specified amount.
/// </summary>
/// <param name="rect">The <see cref="Rectangle"/> to move.</param>
/// <param name="point">The amount to move.</param>
/// <returns>The moved rectangle.</returns>
public static Rectangle Translate(this in Rectangle rect, in Point point) => rect.Translate(point.X, point.Y);
/// <summary>
/// Moves the specified <see cref="Rectangle"/> by the specified amount.
/// </summary>
/// <param name="rect">The <see cref="Rectangle"/> to move.</param>
/// <param name="x">The x-ammount to move.</param>
/// <param name="y">The y-ammount to move.</param>
/// <returns>The moved rectangle.</returns>
public static Rectangle Translate(this in Rectangle rect, float x = 0, float y = 0) => new(rect.Location.Translate(x, y), rect.Size);
/// <summary>
/// Rotates the specified <see cref="Rectangle"/> by the specified amuont around the specified origin.
/// </summary>
/// <remarks>
/// The returned array of <see cref="Point"/> is filled with the new locations of the rectangle clockwise starting from the top left:
/// [0] = top left
/// [1] = top right
/// [2] = bottom right
/// [3] = bottom left
/// </remarks>
/// <param name="rect">The <see cref="Rectangle"/> to rotate.</param>
/// <param name="rotation">The rotation.</param>
/// <param name="origin">The origin to rotate around. [0,0] if not set.</param>
/// <returns>A array of <see cref="Point"/> containing the new locations of the corners of the original rectangle.</returns>
public static Point[] Rotate(this in Rectangle rect, in Rotation rotation, in Point origin = new())
{
Point[] points = {
rect.Location, // top left
new(rect.Location.X + rect.Size.Width, rect.Location.Y), // top right
new(rect.Location.X + rect.Size.Width, rect.Location.Y + rect.Size.Height), // bottom right
new(rect.Location.X, rect.Location.Y + rect.Size.Height), // bottom right
};
float sin = MathF.Sin(rotation.Radians);
float cos = MathF.Cos(rotation.Radians);
for (int i = 0; i < points.Length; i++)
{
Point point = points[i];
point = new Point(point.X - origin.X, point.Y - origin.Y);
point = new Point((point.X * cos) - (point.Y * sin), (point.X * sin) + (point.Y * cos));
points[i] = new Point(point.X + origin.X, point.Y + origin.Y);
}
return points;
}
#endregion
Rectangle intersection = rect.CalculateIntersection(intersectingRect);
return (intersection.Size.Width * intersection.Size.Height) / (rect.Size.Width * rect.Size.Height);
}
}
/// <summary>
/// Calculates the <see cref="Rectangle"/> representing the intersection of this <see cref="Rectangle"/> and the one provided as parameter.
/// </summary>
/// <param name="rect">The rectangle to calculate the intersection for.</param>
/// <param name="intersectingRectangle">The intersecting <see cref="Rectangle"/>.</param>
/// <returns>A new <see cref="Rectangle"/> representing the intersection this <see cref="Rectangle"/> and the one provided as parameter.</returns>
public static Rectangle CalculateIntersection(this in Rectangle rect, in Rectangle intersectingRectangle)
{
float x1 = Math.Max(rect.Location.X, intersectingRectangle.Location.X);
float x2 = Math.Min(rect.Location.X + rect.Size.Width, intersectingRectangle.Location.X + intersectingRectangle.Size.Width);
float y1 = Math.Max(rect.Location.Y, intersectingRectangle.Location.Y);
float y2 = Math.Min(rect.Location.Y + rect.Size.Height, intersectingRectangle.Location.Y + intersectingRectangle.Size.Height);
if ((x2 >= x1) && (y2 >= y1))
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
return new Rectangle();
}
/// <summary>
/// Determines if the specified <see cref="Point"/> is contained within this <see cref="Rectangle"/>.
/// </summary>
/// <param name="rect">The containing rectangle.</param>
/// <param name="point">The <see cref="Point"/> to test.</param>
/// <returns><c>true</c> if the rectangle contains the specified point; otherwise <c>false</c>.</returns>
public static bool Contains(this in Rectangle rect, in Point point) => rect.Contains(point.X, point.Y);
/// <summary>
/// Determines if the specified location is contained within this <see cref="Rectangle"/>.
/// </summary>
/// <param name="rect">The containing rectangle.</param>
/// <param name="x">The X-location to test.</param>
/// <param name="y">The Y-location to test.</param>
/// <returns><c>true</c> if the rectangle contains the specified coordinates; otherwise <c>false</c>.</returns>
public static bool Contains(this in Rectangle rect, float x, float y) => (rect.Location.X <= x) && (x < (rect.Location.X + rect.Size.Width))
&& (rect.Location.Y <= y) && (y < (rect.Location.Y + rect.Size.Height));
/// <summary>
/// Determines if the specified <see cref="Rectangle"/> is contained within this <see cref="Rectangle"/>.
/// </summary>
/// <param name="rect">The containing rectangle.</param>
/// <param name="rect2">The <see cref="Rectangle"/> to test.</param>
/// <returns><c>true</c> if the rectangle contains the specified rect; otherwise <c>false</c>.</returns>
public static bool Contains(this in Rectangle rect, in Rectangle rect2) => (rect.Location.X <= rect2.Location.X) && ((rect2.Location.X + rect2.Size.Width) <= (rect.Location.X + rect.Size.Width))
&& (rect.Location.Y <= rect2.Location.Y) && ((rect2.Location.Y + rect2.Size.Height) <= (rect.Location.Y + rect.Size.Height));
/// <summary>
/// Moves the specified <see cref="Rectangle"/> by the specified amount.
/// </summary>
/// <param name="rect">The <see cref="Rectangle"/> to move.</param>
/// <param name="point">The amount to move.</param>
/// <returns>The moved rectangle.</returns>
public static Rectangle Translate(this in Rectangle rect, in Point point) => rect.Translate(point.X, point.Y);
/// <summary>
/// Moves the specified <see cref="Rectangle"/> by the specified amount.
/// </summary>
/// <param name="rect">The <see cref="Rectangle"/> to move.</param>
/// <param name="x">The x-ammount to move.</param>
/// <param name="y">The y-ammount to move.</param>
/// <returns>The moved rectangle.</returns>
public static Rectangle Translate(this in Rectangle rect, float x = 0, float y = 0) => new(rect.Location.Translate(x, y), rect.Size);
/// <summary>
/// Rotates the specified <see cref="Rectangle"/> by the specified amuont around the specified origin.
/// </summary>
/// <remarks>
/// The returned array of <see cref="Point"/> is filled with the new locations of the rectangle clockwise starting from the top left:
/// [0] = top left
/// [1] = top right
/// [2] = bottom right
/// [3] = bottom left
/// </remarks>
/// <param name="rect">The <see cref="Rectangle"/> to rotate.</param>
/// <param name="rotation">The rotation.</param>
/// <param name="origin">The origin to rotate around. [0,0] if not set.</param>
/// <returns>A array of <see cref="Point"/> containing the new locations of the corners of the original rectangle.</returns>
public static Point[] Rotate(this in Rectangle rect, in Rotation rotation, in Point origin = new())
{
Point[] points = {
rect.Location, // top left
new(rect.Location.X + rect.Size.Width, rect.Location.Y), // top right
new(rect.Location.X + rect.Size.Width, rect.Location.Y + rect.Size.Height), // bottom right
new(rect.Location.X, rect.Location.Y + rect.Size.Height), // bottom right
};
float sin = MathF.Sin(rotation.Radians);
float cos = MathF.Cos(rotation.Radians);
for (int i = 0; i < points.Length; i++)
{
Point point = points[i];
point = new Point(point.X - origin.X, point.Y - origin.Y);
point = new Point((point.X * cos) - (point.Y * sin), (point.X * sin) + (point.Y * cos));
points[i] = new Point(point.X + origin.X, point.Y + origin.Y);
}
return points;
}
#endregion
}

View File

@ -3,83 +3,82 @@
using System.Collections.Generic;
using System.Linq;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Offers some extensions and helper-methods for the work with the surface.
/// </summary>
public static class SurfaceExtensions
{
#region Methods
/// <summary>
/// Offers some extensions and helper-methods for the work with the surface.
/// Initializes the specifiec device provider and attaches all devices.
/// </summary>
public static class SurfaceExtensions
/// <param name="surface">The surface to attach the devices to.</param>
/// <param name="deviceProvider">The device provider to load.</param>
/// <param name="loadFilter"><see cref="RGBDeviceType"/>-flags to filter the devices to load.</param>
/// <param name="throwExceptions">Specifies if exceptions should be thrown or silently be ignored.</param>
public static void Load(this RGBSurface surface, IRGBDeviceProvider deviceProvider, RGBDeviceType loadFilter = RGBDeviceType.All, bool throwExceptions = false)
{
#region Methods
if (!deviceProvider.IsInitialized)
deviceProvider.Initialize(loadFilter, throwExceptions);
/// <summary>
/// Initializes the specifiec device provider and attaches all devices.
/// </summary>
/// <param name="surface">The surface to attach the devices to.</param>
/// <param name="deviceProvider">The device provider to load.</param>
/// <param name="loadFilter"><see cref="RGBDeviceType"/>-flags to filter the devices to load.</param>
/// <param name="throwExceptions">Specifies if exceptions should be thrown or silently be ignored.</param>
public static void Load(this RGBSurface surface, IRGBDeviceProvider deviceProvider, RGBDeviceType loadFilter = RGBDeviceType.All, bool throwExceptions = false)
{
if (!deviceProvider.IsInitialized)
deviceProvider.Initialize(loadFilter, throwExceptions);
surface.Attach(deviceProvider.Devices);
}
/// <summary>
/// Attaches the specified devices to the surface.
/// </summary>
/// <param name="surface">The surface the devices are attached to.</param>
/// <param name="devices">The devices to attach.</param>
public static void Attach(this RGBSurface surface, IEnumerable<IRGBDevice> devices)
{
foreach (IRGBDevice device in devices)
surface.Attach(device);
}
/// <summary>
/// Detaches the specified devices from the surface.
/// </summary>
/// <param name="surface">The surface the devices are detached from.</param>
/// <param name="devices">The devices to detach.</param>
public static void Detach(this RGBSurface surface, IEnumerable<IRGBDevice> devices)
{
foreach (IRGBDevice device in devices)
surface.Detach(device);
}
/// <summary>
/// Gets all devices of a specific type.
/// </summary>
/// <typeparam name="T">The type of devices to get.</typeparam>
/// <returns>A collection of devices with the specified type.</returns>
public static IEnumerable<T> GetDevices<T>(this RGBSurface surface)
where T : class
=> surface.Devices.Where(x => x is T).Cast<T>();
/// <summary>
/// Gets all devices of the specified <see cref="RGBDeviceType"/>.
/// </summary>
/// <param name="surface">The surface to get the devices from.</param>
/// <param name="deviceType">The <see cref="RGBDeviceType"/> of the devices to get.</param>
/// <returns>A collection of devices matching the specified <see cref="RGBDeviceType"/>.</returns>
public static IEnumerable<IRGBDevice> GetDevices(this RGBSurface surface, RGBDeviceType deviceType)
=> surface.Devices.Where(d => deviceType.HasFlag(d.DeviceInfo.DeviceType));
/// <summary>
/// Automatically aligns all devices to prevent overlaps.
/// </summary>
public static void AlignDevices(this RGBSurface surface)
{
float posX = 0;
foreach (IRGBDevice device in surface.Devices)
{
device.Location += new Point(posX, 0);
posX += device.ActualSize.Width + 1;
}
}
#endregion
surface.Attach(deviceProvider.Devices);
}
}
/// <summary>
/// Attaches the specified devices to the surface.
/// </summary>
/// <param name="surface">The surface the devices are attached to.</param>
/// <param name="devices">The devices to attach.</param>
public static void Attach(this RGBSurface surface, IEnumerable<IRGBDevice> devices)
{
foreach (IRGBDevice device in devices)
surface.Attach(device);
}
/// <summary>
/// Detaches the specified devices from the surface.
/// </summary>
/// <param name="surface">The surface the devices are detached from.</param>
/// <param name="devices">The devices to detach.</param>
public static void Detach(this RGBSurface surface, IEnumerable<IRGBDevice> devices)
{
foreach (IRGBDevice device in devices)
surface.Detach(device);
}
/// <summary>
/// Gets all devices of a specific type.
/// </summary>
/// <typeparam name="T">The type of devices to get.</typeparam>
/// <returns>A collection of devices with the specified type.</returns>
public static IEnumerable<T> GetDevices<T>(this RGBSurface surface)
where T : class
=> surface.Devices.Where(x => x is T).Cast<T>();
/// <summary>
/// Gets all devices of the specified <see cref="RGBDeviceType"/>.
/// </summary>
/// <param name="surface">The surface to get the devices from.</param>
/// <param name="deviceType">The <see cref="RGBDeviceType"/> of the devices to get.</param>
/// <returns>A collection of devices matching the specified <see cref="RGBDeviceType"/>.</returns>
public static IEnumerable<IRGBDevice> GetDevices(this RGBSurface surface, RGBDeviceType deviceType)
=> surface.Devices.Where(d => deviceType.HasFlag(d.DeviceInfo.DeviceType));
/// <summary>
/// Automatically aligns all devices to prevent overlaps.
/// </summary>
public static void AlignDevices(this RGBSurface surface)
{
float posX = 0;
foreach (IRGBDevice device in surface.Devices)
{
device.Location += new Point(posX, 0);
posX += device.ActualSize.Width + 1;
}
}
#endregion
}

View File

@ -1,62 +1,61 @@
using System.Collections;
using System.Collections.Generic;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc cref="AbstractDecoratable{T}" />
/// <inheritdoc cref="ILedGroup" />
/// <summary>
/// Represents a generic <see cref="T:RGB.NET.Core.AbstractLedGroup" />.
/// </summary>
public abstract class AbstractLedGroup : AbstractDecoratable<ILedGroupDecorator>, ILedGroup
{
/// <inheritdoc cref="AbstractDecoratable{T}" />
/// <inheritdoc cref="ILedGroup" />
#region Properties & Fields
RGBSurface? ILedGroup.Surface { get; set; }
/// <inheritdoc cref="ILedGroup.Surface" />
public RGBSurface? Surface => ((ILedGroup)this).Surface;
/// <inheritdoc />
public IBrush? Brush { get; set; }
/// <inheritdoc />
public int ZIndex { get; set; } = 0;
#endregion
#region Constructors
/// <summary>
/// Represents a generic <see cref="T:RGB.NET.Core.AbstractLedGroup" />.
/// Initializes a new instance of the <see cref="AbstractLedGroup"/> class.
/// </summary>
public abstract class AbstractLedGroup : AbstractDecoratable<ILedGroupDecorator>, ILedGroup
protected AbstractLedGroup(RGBSurface? attachTo)
{
#region Properties & Fields
RGBSurface? ILedGroup.Surface { get; set; }
/// <inheritdoc cref="ILedGroup.Surface" />
public RGBSurface? Surface => ((ILedGroup)this).Surface;
/// <inheritdoc />
public IBrush? Brush { get; set; }
/// <inheritdoc />
public int ZIndex { get; set; } = 0;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractLedGroup"/> class.
/// </summary>
protected AbstractLedGroup(RGBSurface? attachTo)
{
attachTo?.Attach(this);
}
#endregion
#region Methods
/// <summary>
/// Gets a enumerable containing all leds in this group.
/// </summary>
/// <returns>A enumerable containing all leds of this group.</returns>
protected abstract IEnumerable<Led> GetLeds();
/// <inheritdoc />
public virtual void OnAttach() { }
/// <inheritdoc />
public virtual void OnDetach() { }
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <inheritdoc />
public IEnumerator<Led> GetEnumerator() => GetLeds().GetEnumerator();
#endregion
attachTo?.Attach(this);
}
}
#endregion
#region Methods
/// <summary>
/// Gets a enumerable containing all leds in this group.
/// </summary>
/// <returns>A enumerable containing all leds of this group.</returns>
protected abstract IEnumerable<Led> GetLeds();
/// <inheritdoc />
public virtual void OnAttach() { }
/// <inheritdoc />
public virtual void OnDetach() { }
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <inheritdoc />
public IEnumerator<Led> GetEnumerator() => GetLeds().GetEnumerator();
#endregion
}

View File

@ -3,41 +3,40 @@
using System.Collections.Generic;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic ledgroup.
/// </summary>
public interface ILedGroup : IDecoratable<ILedGroupDecorator>, IEnumerable<Led>
{
/// <summary>
/// Represents a generic ledgroup.
/// Gets the surface this group is attached to or <c>null</c> if it is not attached to any surface.
/// </summary>
public interface ILedGroup : IDecoratable<ILedGroupDecorator>, IEnumerable<Led>
{
/// <summary>
/// Gets the surface this group is attached to or <c>null</c> if it is not attached to any surface.
/// </summary>
RGBSurface? Surface { get; internal set; }
RGBSurface? Surface { get; internal set; }
/// <summary>
/// Gets a bool indicating if the group is attached to a surface.
/// </summary>
bool IsAttached => Surface != null;
/// <summary>
/// Gets a bool indicating if the group is attached to a surface.
/// </summary>
bool IsAttached => Surface != null;
/// <summary>
/// Gets or sets the <see cref="IBrush"/> which should be drawn over this <see cref="ILedGroup"/>.
/// </summary>
IBrush? Brush { get; set; }
/// <summary>
/// Gets or sets the <see cref="IBrush"/> which should be drawn over this <see cref="ILedGroup"/>.
/// </summary>
IBrush? Brush { get; set; }
/// <summary>
/// Gets or sets the z-index of this <see cref="ILedGroup"/> to allow ordering them before drawing. (lowest first) (default: 0)
/// </summary>
int ZIndex { get; set; }
/// <summary>
/// Gets or sets the z-index of this <see cref="ILedGroup"/> to allow ordering them before drawing. (lowest first) (default: 0)
/// </summary>
int ZIndex { get; set; }
/// <summary>
/// Called when the <see cref="ILedGroup"/> is attached to the <see cref="RGBSurface"/>.
/// </summary>
void OnAttach();
/// <summary>
/// Called when the <see cref="ILedGroup"/> is attached to the <see cref="RGBSurface"/>.
/// </summary>
void OnAttach();
/// <summary>
/// Called when the <see cref="ILedGroup"/> is detached from the <see cref="RGBSurface"/>.
/// </summary>
void OnDetach();
}
}
/// <summary>
/// Called when the <see cref="ILedGroup"/> is detached from the <see cref="RGBSurface"/>.
/// </summary>
void OnDetach();
}

View File

@ -1,58 +1,57 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Offers some extensions and helper-methods for <see cref="ILedGroup"/> related things.
/// </summary>
public static class LedGroupExtension
{
/// <summary>
/// Offers some extensions and helper-methods for <see cref="ILedGroup"/> related things.
/// Converts the specified <see cref="ILedGroup" /> to a <see cref="ListLedGroup" />.
/// </summary>
public static class LedGroupExtension
/// <param name="ledGroup">The <see cref="ILedGroup" /> to convert.</param>
/// <returns>The converted <see cref="ListLedGroup" />.</returns>
public static ListLedGroup ToListLedGroup(this ILedGroup ledGroup)
{
/// <summary>
/// Converts the specified <see cref="ILedGroup" /> to a <see cref="ListLedGroup" />.
/// </summary>
/// <param name="ledGroup">The <see cref="ILedGroup" /> to convert.</param>
/// <returns>The converted <see cref="ListLedGroup" />.</returns>
public static ListLedGroup ToListLedGroup(this ILedGroup ledGroup)
// ReSharper disable once InvertIf
if (ledGroup is not ListLedGroup listLedGroup)
{
// ReSharper disable once InvertIf
if (ledGroup is not ListLedGroup listLedGroup)
{
if (ledGroup.IsAttached)
ledGroup.Detach();
listLedGroup = new ListLedGroup(ledGroup.Surface, ledGroup) { Brush = ledGroup.Brush, ZIndex = ledGroup.ZIndex };
}
return listLedGroup;
if (ledGroup.IsAttached)
ledGroup.Detach();
listLedGroup = new ListLedGroup(ledGroup.Surface, ledGroup) { Brush = ledGroup.Brush, ZIndex = ledGroup.ZIndex };
}
/// <summary>
/// Returns a new <see cref="ListLedGroup" /> which contains all <see cref="Led"/> from the specified <see cref="ILedGroup"/> excluding the specified ones.
/// </summary>
/// <param name="ledGroup">The base <see cref="ILedGroup"/>.</param>
/// <param name="ledIds">The <see cref="Led"/> to exclude.</param>
/// <returns>The new <see cref="ListLedGroup" />.</returns>
public static ListLedGroup Exclude(this ILedGroup ledGroup, params Led[] ledIds)
{
ListLedGroup listLedGroup = ledGroup.ToListLedGroup();
foreach (Led led in ledIds)
listLedGroup.RemoveLed(led);
return listLedGroup;
}
// ReSharper disable once UnusedMethodReturnValue.Global
/// <summary>
/// Attaches the specified <see cref="ILedGroup"/> to the <see cref="RGBSurface"/>.
/// </summary>
/// <param name="ledGroup">The <see cref="ILedGroup"/> to attach.</param>
/// <param name="surface">The <see cref="RGBSurface"/> to attach this group to.</param>
/// <returns><c>true</c> if the <see cref="ILedGroup"/> could be attached; otherwise, <c>false</c>.</returns>
public static bool Attach(this ILedGroup ledGroup, RGBSurface surface) => surface.Attach(ledGroup);
/// <summary>
/// Detaches the specified <see cref="ILedGroup"/> from the <see cref="RGBSurface"/>.
/// </summary>
/// <param name="ledGroup">The <see cref="ILedGroup"/> to attach.</param>
/// <returns><c>true</c> if the <see cref="ILedGroup"/> could be detached; otherwise, <c>false</c>.</returns>
public static bool Detach(this ILedGroup ledGroup) => ledGroup.Surface?.Detach(ledGroup) ?? false;
return listLedGroup;
}
}
/// <summary>
/// Returns a new <see cref="ListLedGroup" /> which contains all <see cref="Led"/> from the specified <see cref="ILedGroup"/> excluding the specified ones.
/// </summary>
/// <param name="ledGroup">The base <see cref="ILedGroup"/>.</param>
/// <param name="ledIds">The <see cref="Led"/> to exclude.</param>
/// <returns>The new <see cref="ListLedGroup" />.</returns>
public static ListLedGroup Exclude(this ILedGroup ledGroup, params Led[] ledIds)
{
ListLedGroup listLedGroup = ledGroup.ToListLedGroup();
foreach (Led led in ledIds)
listLedGroup.RemoveLed(led);
return listLedGroup;
}
// ReSharper disable once UnusedMethodReturnValue.Global
/// <summary>
/// Attaches the specified <see cref="ILedGroup"/> to the <see cref="RGBSurface"/>.
/// </summary>
/// <param name="ledGroup">The <see cref="ILedGroup"/> to attach.</param>
/// <param name="surface">The <see cref="RGBSurface"/> to attach this group to.</param>
/// <returns><c>true</c> if the <see cref="ILedGroup"/> could be attached; otherwise, <c>false</c>.</returns>
public static bool Attach(this ILedGroup ledGroup, RGBSurface surface) => surface.Attach(ledGroup);
/// <summary>
/// Detaches the specified <see cref="ILedGroup"/> from the <see cref="RGBSurface"/>.
/// </summary>
/// <param name="ledGroup">The <see cref="ILedGroup"/> to attach.</param>
/// <returns><c>true</c> if the <see cref="ILedGroup"/> could be detached; otherwise, <c>false</c>.</returns>
public static bool Detach(this ILedGroup ledGroup) => ledGroup.Surface?.Detach(ledGroup) ?? false;
}

View File

@ -3,131 +3,130 @@
using System.Collections.Generic;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents a ledgroup containing arbitrary <see cref="T:RGB.NET.Core.Led" />.
/// </summary>
public class ListLedGroup : AbstractLedGroup
{
#region Properties & Fields
/// <summary>
/// Gets the list containing the <see cref="Led"/> of this <see cref="ListLedGroup"/>.
/// </summary>
protected IList<Led> GroupLeds { get; } = new List<Led>();
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Represents a ledgroup containing arbitrary <see cref="T:RGB.NET.Core.Led" />.
/// Initializes a new instance of the <see cref="T:RGB.NET.Groups.ListLedGroup" /> class.
/// </summary>
public class ListLedGroup : AbstractLedGroup
/// <param name="surface">Specifies the surface to attach this group to or <c>null</c> if the group should not be attached on creation.</param>
public ListLedGroup(RGBSurface? surface)
: base(surface)
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Groups.ListLedGroup" /> class.
/// </summary>
/// <param name="surface">Specifies the surface to attach this group to or <c>null</c> if the group should not be attached on creation.</param>
/// <param name="leds">The initial <see cref="T:RGB.NET.Core.Led" /> of this <see cref="T:RGB.NET.Groups.ListLedGroup" />.</param>
public ListLedGroup(RGBSurface? surface, IEnumerable<Led> leds)
: base(surface)
{
#region Properties & Fields
/// <summary>
/// Gets the list containing the <see cref="Led"/> of this <see cref="ListLedGroup"/>.
/// </summary>
protected IList<Led> GroupLeds { get; } = new List<Led>();
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Groups.ListLedGroup" /> class.
/// </summary>
/// <param name="surface">Specifies the surface to attach this group to or <c>null</c> if the group should not be attached on creation.</param>
public ListLedGroup(RGBSurface? surface)
: base(surface)
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Groups.ListLedGroup" /> class.
/// </summary>
/// <param name="surface">Specifies the surface to attach this group to or <c>null</c> if the group should not be attached on creation.</param>
/// <param name="leds">The initial <see cref="T:RGB.NET.Core.Led" /> of this <see cref="T:RGB.NET.Groups.ListLedGroup" />.</param>
public ListLedGroup(RGBSurface? surface, IEnumerable<Led> leds)
: base(surface)
{
AddLeds(leds);
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Groups.ListLedGroup" /> class.
/// </summary>
/// <param name="surface">Specifies the surface to attach this group to or <c>null</c> if the group should not be attached on creation.</param>
/// <param name="leds">The initial <see cref="T:RGB.NET.Core.Led" /> of this <see cref="T:RGB.NET.Groups.ListLedGroup" />.</param>
public ListLedGroup(RGBSurface? surface, params Led[] leds)
: base(surface)
{
AddLeds(leds);
}
#endregion
#region Methods
/// <summary>
/// Adds the specified LED(s) to this <see cref="ListLedGroup"/>.
/// </summary>
/// <param name="leds">The LED(s) to add.</param>
public void AddLed(params Led[] leds) => AddLeds(leds);
/// <summary>
/// Adds the specified <see cref="Led"/> to this <see cref="ListLedGroup"/>.
/// </summary>
/// <param name="leds">The <see cref="Led"/> to add.</param>
public void AddLeds(IEnumerable<Led> leds)
{
lock (GroupLeds)
foreach (Led led in leds)
if (!ContainsLed(led))
GroupLeds.Add(led);
}
/// <summary>
/// Removes the specified LED(s) from this <see cref="ListLedGroup"/>.
/// </summary>
/// <param name="leds">The LED(s) to remove.</param>
public void RemoveLed(params Led[] leds) => RemoveLeds(leds);
/// <summary>
/// Removes the specified <see cref="Led"/> from this <see cref="ListLedGroup"/>.
/// </summary>
/// <param name="leds">The <see cref="Led"/> to remove.</param>
public void RemoveLeds(IEnumerable<Led> leds)
{
lock (GroupLeds)
foreach (Led led in leds)
GroupLeds.Remove(led);
}
/// <summary>
/// Checks if a specified LED is contained by this ledgroup.
/// </summary>
/// <param name="led">The LED which should be checked.</param>
/// <returns><c>true</c> if the LED is contained by this ledgroup; otherwise, <c>false</c>.</returns>
public bool ContainsLed(Led led)
{
lock (GroupLeds)
return GroupLeds.Contains(led);
}
/// <summary>
/// Merges the <see cref="Led"/> from the specified ledgroup in this ledgroup.
/// </summary>
/// <param name="groupToMerge">The ledgroup to merge.</param>
public void MergeLeds(ILedGroup groupToMerge)
{
lock (GroupLeds)
foreach (Led led in groupToMerge)
if (!GroupLeds.Contains(led))
GroupLeds.Add(led);
}
/// <inheritdoc />
/// <summary>
/// Gets a list containing the <see cref="T:RGB.NET.Core.Led" /> from this group.
/// </summary>
/// <returns>The list containing the <see cref="T:RGB.NET.Core.Led" />.</returns>
protected override IEnumerable<Led> GetLeds()
{
lock (GroupLeds)
return new List<Led>(GroupLeds);
}
#endregion
AddLeds(leds);
}
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Groups.ListLedGroup" /> class.
/// </summary>
/// <param name="surface">Specifies the surface to attach this group to or <c>null</c> if the group should not be attached on creation.</param>
/// <param name="leds">The initial <see cref="T:RGB.NET.Core.Led" /> of this <see cref="T:RGB.NET.Groups.ListLedGroup" />.</param>
public ListLedGroup(RGBSurface? surface, params Led[] leds)
: base(surface)
{
AddLeds(leds);
}
#endregion
#region Methods
/// <summary>
/// Adds the specified LED(s) to this <see cref="ListLedGroup"/>.
/// </summary>
/// <param name="leds">The LED(s) to add.</param>
public void AddLed(params Led[] leds) => AddLeds(leds);
/// <summary>
/// Adds the specified <see cref="Led"/> to this <see cref="ListLedGroup"/>.
/// </summary>
/// <param name="leds">The <see cref="Led"/> to add.</param>
public void AddLeds(IEnumerable<Led> leds)
{
lock (GroupLeds)
foreach (Led led in leds)
if (!ContainsLed(led))
GroupLeds.Add(led);
}
/// <summary>
/// Removes the specified LED(s) from this <see cref="ListLedGroup"/>.
/// </summary>
/// <param name="leds">The LED(s) to remove.</param>
public void RemoveLed(params Led[] leds) => RemoveLeds(leds);
/// <summary>
/// Removes the specified <see cref="Led"/> from this <see cref="ListLedGroup"/>.
/// </summary>
/// <param name="leds">The <see cref="Led"/> to remove.</param>
public void RemoveLeds(IEnumerable<Led> leds)
{
lock (GroupLeds)
foreach (Led led in leds)
GroupLeds.Remove(led);
}
/// <summary>
/// Checks if a specified LED is contained by this ledgroup.
/// </summary>
/// <param name="led">The LED which should be checked.</param>
/// <returns><c>true</c> if the LED is contained by this ledgroup; otherwise, <c>false</c>.</returns>
public bool ContainsLed(Led led)
{
lock (GroupLeds)
return GroupLeds.Contains(led);
}
/// <summary>
/// Merges the <see cref="Led"/> from the specified ledgroup in this ledgroup.
/// </summary>
/// <param name="groupToMerge">The ledgroup to merge.</param>
public void MergeLeds(ILedGroup groupToMerge)
{
lock (GroupLeds)
foreach (Led led in groupToMerge)
if (!GroupLeds.Contains(led))
GroupLeds.Add(led);
}
/// <inheritdoc />
/// <summary>
/// Gets a list containing the <see cref="T:RGB.NET.Core.Led" /> from this group.
/// </summary>
/// <returns>The list containing the <see cref="T:RGB.NET.Core.Led" />.</returns>
protected override IEnumerable<Led> GetLeds()
{
lock (GroupLeds)
return new List<Led>(GroupLeds);
}
#endregion
}

View File

@ -1,62 +1,61 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Contains helper methods for converting things.
/// </summary>
public static class ConversionHelper
{
#region Methods
// Source: https://web.archive.org/web/20180224104425/https://stackoverflow.com/questions/623104/byte-to-hex-string/3974535
/// <summary>
/// Contains helper methods for converting things.
/// Converts an array of bytes to a HEX-representation.
/// </summary>
public static class ConversionHelper
/// <param name="bytes">The array of bytes.</param>
/// <returns>The HEX-representation of the provided bytes.</returns>
public static string ToHex(params byte[] bytes)
{
#region Methods
char[] c = new char[bytes.Length * 2];
// Source: https://web.archive.org/web/20180224104425/https://stackoverflow.com/questions/623104/byte-to-hex-string/3974535
/// <summary>
/// Converts an array of bytes to a HEX-representation.
/// </summary>
/// <param name="bytes">The array of bytes.</param>
/// <returns>The HEX-representation of the provided bytes.</returns>
public static string ToHex(params byte[] bytes)
for (int bx = 0, cx = 0; bx < bytes.Length; ++bx, ++cx)
{
char[] c = new char[bytes.Length * 2];
byte b = ((byte)(bytes[bx] >> 4));
c[cx] = (char)(b > 9 ? b + 0x37 : b + 0x30);
for (int bx = 0, cx = 0; bx < bytes.Length; ++bx, ++cx)
{
byte b = ((byte)(bytes[bx] >> 4));
c[cx] = (char)(b > 9 ? b + 0x37 : b + 0x30);
b = ((byte)(bytes[bx] & 0x0F));
c[++cx] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
return new string(c);
b = ((byte)(bytes[bx] & 0x0F));
c[++cx] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
// Source: https://web.archive.org/web/20180224104425/https://stackoverflow.com/questions/623104/byte-to-hex-string/3974535
/// <summary>
/// Converts the HEX-representation of a byte array to that array.
/// </summary>
/// <param name="hexString">The HEX-string to convert.</param>
/// <returns>The correspondending byte array.</returns>
public static byte[] HexToBytes(ReadOnlySpan<char> hexString)
{
if ((hexString.Length == 0) || ((hexString.Length % 2) != 0))
return Array.Empty<byte>();
byte[] buffer = new byte[hexString.Length / 2];
for (int bx = 0, sx = 0; bx < buffer.Length; ++bx, ++sx)
{
// Convert first half of byte
char c = hexString[sx];
buffer[bx] = (byte)((c > '9' ? (c > 'Z' ? ((c - 'a') + 10) : ((c - 'A') + 10)) : (c - '0')) << 4);
// Convert second half of byte
c = hexString[++sx];
buffer[bx] |= (byte)(c > '9' ? (c > 'Z' ? ((c - 'a') + 10) : ((c - 'A') + 10)) : (c - '0'));
}
return buffer;
}
#endregion
return new string(c);
}
}
// Source: https://web.archive.org/web/20180224104425/https://stackoverflow.com/questions/623104/byte-to-hex-string/3974535
/// <summary>
/// Converts the HEX-representation of a byte array to that array.
/// </summary>
/// <param name="hexString">The HEX-string to convert.</param>
/// <returns>The correspondending byte array.</returns>
public static byte[] HexToBytes(ReadOnlySpan<char> hexString)
{
if ((hexString.Length == 0) || ((hexString.Length % 2) != 0))
return Array.Empty<byte>();
byte[] buffer = new byte[hexString.Length / 2];
for (int bx = 0, sx = 0; bx < buffer.Length; ++bx, ++sx)
{
// Convert first half of byte
char c = hexString[sx];
buffer[bx] = (byte)((c > '9' ? (c > 'Z' ? ((c - 'a') + 10) : ((c - 'A') + 10)) : (c - '0')) << 4);
// Convert second half of byte
c = hexString[++sx];
buffer[bx] |= (byte)(c > '9' ? (c > 'Z' ? ((c - 'a') + 10) : ((c - 'A') + 10)) : (c - '0'));
}
return buffer;
}
#endregion
}

View File

@ -1,27 +1,26 @@
using System.Reflection;
using System.Runtime.CompilerServices;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Offsers some helper methods for device creation.
/// </summary>
public static class DeviceHelper
{
#region Methods
/// <summary>
/// Offsers some helper methods for device creation.
/// Creates a unique device name from a manufacturer and model name.
/// </summary>
public static class DeviceHelper
{
#region Methods
/// <remarks>
/// The id is made unique based on the assembly calling this method.
/// </remarks>
/// <param name="manufacturer">The manufacturer of the device.</param>
/// <param name="model">The model of the device.</param>
/// <returns>The unique identifier for this device.</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
public static string CreateDeviceName(string manufacturer, string model) => IdGenerator.MakeUnique(Assembly.GetCallingAssembly(), $"{manufacturer} {model}");
/// <summary>
/// Creates a unique device name from a manufacturer and model name.
/// </summary>
/// <remarks>
/// The id is made unique based on the assembly calling this method.
/// </remarks>
/// <param name="manufacturer">The manufacturer of the device.</param>
/// <param name="model">The model of the device.</param>
/// <returns>The unique identifier for this device.</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
public static string CreateDeviceName(string manufacturer, string model) => IdGenerator.MakeUnique(Assembly.GetCallingAssembly(), $"{manufacturer} {model}");
#endregion
}
}
#endregion
}

View File

@ -2,74 +2,73 @@
using System.Reflection;
using System.Runtime.CompilerServices;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Offers some methods to create and handle unique identifiers.
/// </summary>
public static class IdGenerator
{
#region Properties & Fields
// ReSharper disable InconsistentNaming
private static readonly HashSet<string> _registeredIds = new();
private static readonly Dictionary<Assembly, Dictionary<string, string>> _idMappings = new();
private static readonly Dictionary<Assembly, Dictionary<string, int>> _counter = new();
// ReSharper restore InconsistentNaming
#endregion
#region Methods
/// <summary>
/// Offers some methods to create and handle unique identifiers.
/// Makes the specified id unique based on the calling assembly by adding a counter if needed.
/// </summary>
public static class IdGenerator
/// <param name="id">The id to make unique.</param>
/// <returns>The unique id.</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
public static string MakeUnique(string id) => MakeUnique(Assembly.GetCallingAssembly(), id);
internal static string MakeUnique(Assembly callingAssembly, string id)
{
#region Properties & Fields
// ReSharper disable InconsistentNaming
private static readonly HashSet<string> _registeredIds = new();
private static readonly Dictionary<Assembly, Dictionary<string, string>> _idMappings = new();
private static readonly Dictionary<Assembly, Dictionary<string, int>> _counter = new();
// ReSharper restore InconsistentNaming
#endregion
#region Methods
/// <summary>
/// Makes the specified id unique based on the calling assembly by adding a counter if needed.
/// </summary>
/// <param name="id">The id to make unique.</param>
/// <returns>The unique id.</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
public static string MakeUnique(string id) => MakeUnique(Assembly.GetCallingAssembly(), id);
internal static string MakeUnique(Assembly callingAssembly, string id)
if (!_idMappings.TryGetValue(callingAssembly, out Dictionary<string, string>? idMapping))
{
if (!_idMappings.TryGetValue(callingAssembly, out Dictionary<string, string>? idMapping))
{
_idMappings.Add(callingAssembly, idMapping = new Dictionary<string, string>());
_counter.Add(callingAssembly, new Dictionary<string, int>());
}
Dictionary<string, int> counterMapping = _counter[callingAssembly];
if (!idMapping.TryGetValue(id, out string? mappedId))
{
mappedId = id;
int mappingCounter = 1;
while (_registeredIds.Contains(id))
mappedId = $"{id} ({++mappingCounter})";
_registeredIds.Add(mappedId);
idMapping.Add(id, mappedId);
}
if (!counterMapping.ContainsKey(mappedId))
counterMapping.Add(mappedId, 0);
int counter = ++counterMapping[mappedId];
return counter <= 1 ? mappedId : $"{mappedId} ({counter})";
_idMappings.Add(callingAssembly, idMapping = new Dictionary<string, string>());
_counter.Add(callingAssembly, new Dictionary<string, int>());
}
/// <summary>
/// Resets the counter used to create unique ids.
/// All previous generated ids are not garantueed to stay unique if this is called!
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ResetCounter() => ResetCounter(Assembly.GetCallingAssembly());
Dictionary<string, int> counterMapping = _counter[callingAssembly];
internal static void ResetCounter(Assembly callingAssembly)
if (!idMapping.TryGetValue(id, out string? mappedId))
{
if (_counter.TryGetValue(callingAssembly, out Dictionary<string, int>? counter))
counter.Clear();
mappedId = id;
int mappingCounter = 1;
while (_registeredIds.Contains(id))
mappedId = $"{id} ({++mappingCounter})";
_registeredIds.Add(mappedId);
idMapping.Add(id, mappedId);
}
#endregion
if (!counterMapping.ContainsKey(mappedId))
counterMapping.Add(mappedId, 0);
int counter = ++counterMapping[mappedId];
return counter <= 1 ? mappedId : $"{mappedId} ({counter})";
}
}
/// <summary>
/// Resets the counter used to create unique ids.
/// All previous generated ids are not garantueed to stay unique if this is called!
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ResetCounter() => ResetCounter(Assembly.GetCallingAssembly());
internal static void ResetCounter(Assembly callingAssembly)
{
if (_counter.TryGetValue(callingAssembly, out Dictionary<string, int>? counter))
counter.Clear();
}
#endregion
}

View File

@ -2,175 +2,174 @@
using System.Diagnostics;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents a single LED of a RGB-device.
/// </summary>
[DebuggerDisplay("{Id} {Color}")]
public class Led : Placeable
{
/// <inheritdoc />
#region Properties & Fields
/// <summary>
/// Represents a single LED of a RGB-device.
/// Gets the <see cref="IRGBDevice"/> this <see cref="Led"/> is associated with.
/// </summary>
[DebuggerDisplay("{Id} {Color}")]
public class Led : Placeable
public IRGBDevice Device { get; }
/// <summary>
/// Gets the <see cref="LedId"/> of the <see cref="Led" />.
/// </summary>
public LedId Id { get; }
private Shape _shape = Shape.Rectangle;
/// <summary>
/// Gets or sets the <see cref="Core.Shape"/> of the <see cref="Led"/>.
/// </summary>
public Shape Shape
{
#region Properties & Fields
get => _shape;
set => SetProperty(ref _shape, value);
}
/// <summary>
/// Gets the <see cref="IRGBDevice"/> this <see cref="Led"/> is associated with.
/// </summary>
public IRGBDevice Device { get; }
private string? _shapeData;
/// <summary>
/// Gets or sets the data used for by the <see cref="Core.Shape.Custom"/>-<see cref="Core.Shape"/>.
/// </summary>
public string? ShapeData
{
get => _shapeData;
set => SetProperty(ref _shapeData, value);
}
/// <summary>
/// Gets the <see cref="LedId"/> of the <see cref="Led" />.
/// </summary>
public LedId Id { get; }
private Rectangle _absoluteBoundary;
/// <summary>
/// Gets a rectangle representing the logical location of the <see cref="Led"/> on the <see cref="RGBSurface"/>.
/// </summary>
public Rectangle AbsoluteBoundary
{
get => _absoluteBoundary;
private set => SetProperty(ref _absoluteBoundary, value);
}
private Shape _shape = Shape.Rectangle;
/// <summary>
/// Gets or sets the <see cref="Core.Shape"/> of the <see cref="Led"/>.
/// </summary>
public Shape Shape
/// <summary>
/// Indicates whether the <see cref="Led" /> is about to change it's color.
/// </summary>
public bool IsDirty => RequestedColor.HasValue && (RequestedColor != Color);
private Color? _requestedColor;
/// <summary>
/// Gets a copy of the <see cref="Core.Color"/> the LED should be set to on the next update.
/// Null if there is no update-request for the next update.
/// </summary>
public Color? RequestedColor
{
get => _requestedColor;
private set
{
get => _shape;
set => SetProperty(ref _shape, value);
}
private string? _shapeData;
/// <summary>
/// Gets or sets the data used for by the <see cref="Core.Shape.Custom"/>-<see cref="Core.Shape"/>.
/// </summary>
public string? ShapeData
{
get => _shapeData;
set => SetProperty(ref _shapeData, value);
}
private Rectangle _absoluteBoundary;
/// <summary>
/// Gets a rectangle representing the logical location of the <see cref="Led"/> on the <see cref="RGBSurface"/>.
/// </summary>
public Rectangle AbsoluteBoundary
{
get => _absoluteBoundary;
private set => SetProperty(ref _absoluteBoundary, value);
}
/// <summary>
/// Indicates whether the <see cref="Led" /> is about to change it's color.
/// </summary>
public bool IsDirty => RequestedColor.HasValue && (RequestedColor != Color);
private Color? _requestedColor;
/// <summary>
/// Gets a copy of the <see cref="Core.Color"/> the LED should be set to on the next update.
/// Null if there is no update-request for the next update.
/// </summary>
public Color? RequestedColor
{
get => _requestedColor;
private set
{
SetProperty(ref _requestedColor, value);
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(nameof(IsDirty));
}
}
private Color _color = Color.Transparent;
/// <summary>
/// Gets the current <see cref="Core.Color"/> of the <see cref="Led"/>. Sets the <see cref="RequestedColor" /> for the next update.
/// </summary>
public Color Color
{
get => _color;
set
{
if (RequestedColor.HasValue)
RequestedColor = RequestedColor.Value + value;
else
RequestedColor = value;
}
}
/// <summary>
/// Gets the provider-specific data associated with this led.
/// </summary>
public object? CustomData { get; }
/// <summary>
/// Gets or sets some custom metadata of this led.
/// </summary>
public object? LayoutMetadata { get; set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Led"/> class.
/// </summary>
/// <param name="device">The <see cref="IRGBDevice"/> the <see cref="Led"/> is associated with.</param>
/// <param name="id">The <see cref="LedId"/> of the <see cref="Led"/>.</param>
/// <param name="location">The physical location of the <see cref="Led"/> relative to the <see cref="Device"/>.</param>
/// <param name="size">The size of the <see cref="Led"/>.</param>
/// <param name="customData">The provider-specific data associated with this led.</param>
internal Led(IRGBDevice device, LedId id, Point location, Size size, object? customData = null)
: base(device)
{
this.Device = device;
this.Id = id;
this.Location = location;
this.Size = size;
this.CustomData = customData;
}
#endregion
#region Methods
/// <inheritdoc />
protected override void UpdateActualPlaceableData()
{
base.UpdateActualPlaceableData();
AbsoluteBoundary = Boundary.Translate(Device.Location);
}
/// <summary>
/// Converts the <see cref="Id"/> and the <see cref="Color"/> of this <see cref="Led"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the <see cref="Id"/> and the <see cref="Color"/> of this <see cref="Led"/>. For example "Enter [A: 255, R: 255, G: 0, B: 0]".</returns>
public override string ToString() => $"{Id} {Color}";
/// <summary>
/// Updates the <see cref="Led"/> to the requested <see cref="Core.Color"/>.
/// </summary>
internal void Update()
{
if (!RequestedColor.HasValue) return;
_color = RequestedColor.Value;
RequestedColor = null;
SetProperty(ref _requestedColor, value);
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(nameof(Color));
OnPropertyChanged(nameof(IsDirty));
}
#endregion
#region Operators
/// <summary>
/// Converts a <see cref="Led" /> to a <see cref="Core.Color" />.
/// </summary>
/// <param name="led">The <see cref="Led"/> to convert.</param>
public static implicit operator Color(Led led) => led.Color;
/// <summary>
/// Converts a <see cref="Led" /> to a <see cref="Rectangle" />.
/// </summary>
/// <param name="led">The <see cref="Led"/> to convert.</param>
public static implicit operator Rectangle(Led led) => led.Boundary;
#endregion
}
}
private Color _color = Color.Transparent;
/// <summary>
/// Gets the current <see cref="Core.Color"/> of the <see cref="Led"/>. Sets the <see cref="RequestedColor" /> for the next update.
/// </summary>
public Color Color
{
get => _color;
set
{
if (RequestedColor.HasValue)
RequestedColor = RequestedColor.Value + value;
else
RequestedColor = value;
}
}
/// <summary>
/// Gets the provider-specific data associated with this led.
/// </summary>
public object? CustomData { get; }
/// <summary>
/// Gets or sets some custom metadata of this led.
/// </summary>
public object? LayoutMetadata { get; set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Led"/> class.
/// </summary>
/// <param name="device">The <see cref="IRGBDevice"/> the <see cref="Led"/> is associated with.</param>
/// <param name="id">The <see cref="LedId"/> of the <see cref="Led"/>.</param>
/// <param name="location">The physical location of the <see cref="Led"/> relative to the <see cref="Device"/>.</param>
/// <param name="size">The size of the <see cref="Led"/>.</param>
/// <param name="customData">The provider-specific data associated with this led.</param>
internal Led(IRGBDevice device, LedId id, Point location, Size size, object? customData = null)
: base(device)
{
this.Device = device;
this.Id = id;
this.Location = location;
this.Size = size;
this.CustomData = customData;
}
#endregion
#region Methods
/// <inheritdoc />
protected override void UpdateActualPlaceableData()
{
base.UpdateActualPlaceableData();
AbsoluteBoundary = Boundary.Translate(Device.Location);
}
/// <summary>
/// Converts the <see cref="Id"/> and the <see cref="Color"/> of this <see cref="Led"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the <see cref="Id"/> and the <see cref="Color"/> of this <see cref="Led"/>. For example "Enter [A: 255, R: 255, G: 0, B: 0]".</returns>
public override string ToString() => $"{Id} {Color}";
/// <summary>
/// Updates the <see cref="Led"/> to the requested <see cref="Core.Color"/>.
/// </summary>
internal void Update()
{
if (!RequestedColor.HasValue) return;
_color = RequestedColor.Value;
RequestedColor = null;
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(nameof(Color));
}
#endregion
#region Operators
/// <summary>
/// Converts a <see cref="Led" /> to a <see cref="Core.Color" />.
/// </summary>
/// <param name="led">The <see cref="Led"/> to convert.</param>
public static implicit operator Color(Led led) => led.Color;
/// <summary>
/// Converts a <see cref="Led" /> to a <see cref="Rectangle" />.
/// </summary>
/// <param name="led">The <see cref="Led"/> to convert.</param>
public static implicit operator Rectangle(Led led) => led.Boundary;
#endregion
}

File diff suppressed because it is too large Load Diff

View File

@ -2,148 +2,147 @@
using System.Collections.Generic;
using System.Linq;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a mapping from <see cref="LedId"/> to a custom identifier.
/// </summary>
/// <typeparam name="T">The identifier the <see cref="LedId"/> is mapped to.</typeparam>
public class LedMapping<T> : IEnumerable<(LedId ledId, T mapping)>
where T : notnull
{
#region Properties & Fields
private readonly Dictionary<LedId, T> _mapping = new();
private readonly Dictionary<T, LedId> _reverseMapping = new();
/// <summary>
/// Represents a mapping from <see cref="LedId"/> to a custom identifier.
/// Gets the number of entries in this mapping.
/// </summary>
/// <typeparam name="T">The identifier the <see cref="LedId"/> is mapped to.</typeparam>
public class LedMapping<T> : IEnumerable<(LedId ledId, T mapping)>
where T : notnull
public int Count => _mapping.Count;
/// <summary>
/// Gets a collection of all mapped ledids.
/// </summary>
public ICollection<LedId> LedIds => _mapping.Keys;
/// <summary>
/// Gets a collection of all mapped custom identifiers.
/// </summary>
public ICollection<T> Mappings => _reverseMapping.Keys;
#endregion
#region Indexer
/// <summary>
/// Gets the custom identifier mapped to the specified <see cref="LedId"/>.
/// </summary>
/// <param name="ledId">The led id to get the mapped identifier.</param>
/// <returns>The mapped ifentifier.</returns>
public T this[LedId ledId]
{
#region Properties & Fields
private readonly Dictionary<LedId, T> _mapping = new();
private readonly Dictionary<T, LedId> _reverseMapping = new();
/// <summary>
/// Gets the number of entries in this mapping.
/// </summary>
public int Count => _mapping.Count;
/// <summary>
/// Gets a collection of all mapped ledids.
/// </summary>
public ICollection<LedId> LedIds => _mapping.Keys;
/// <summary>
/// Gets a collection of all mapped custom identifiers.
/// </summary>
public ICollection<T> Mappings => _reverseMapping.Keys;
#endregion
#region Indexer
/// <summary>
/// Gets the custom identifier mapped to the specified <see cref="LedId"/>.
/// </summary>
/// <param name="ledId">The led id to get the mapped identifier.</param>
/// <returns>The mapped ifentifier.</returns>
public T this[LedId ledId]
get => _mapping[ledId];
set
{
get => _mapping[ledId];
set
{
_mapping[ledId] = value;
_reverseMapping[value] = ledId;
}
_mapping[ledId] = value;
_reverseMapping[value] = ledId;
}
/// <summary>
/// Gets the <see cref="LedId"/> mapped to the specified custom identifier.
/// </summary>
/// <param name="mapping">The custom identifier to get the mapped led id.</param>
/// <returns>The led id.</returns>
public LedId this[T mapping]
{
get => _reverseMapping[mapping];
set => this[value] = mapping;
}
#endregion
#region Methods
/// <summary>
/// Adds a new entry to the mapping.
/// </summary>
/// <param name="ledId">The <see cref="LedId"/> to map.</param>
/// <param name="mapping">The custom identifier to map.</param>
public void Add(LedId ledId, T mapping)
{
_mapping.Add(ledId, mapping);
_reverseMapping.Add(mapping, ledId);
}
/// <summary>
/// Checks if the specified <see cref="LedId"/> is mapped.
/// </summary>
/// <param name="ledId">The led id to check.</param>
/// <returns><c>true</c> if the led id is mapped; otherwise <c>false</c>.</returns>
public bool Contains(LedId ledId) => _mapping.ContainsKey(ledId);
/// <summary>
/// Checks if the specified custom identifier is mapped.
/// </summary>
/// <param name="mapping">The custom identifier to check.</param>
/// <returns><c>true</c> if the led id is mapped; otherwise <c>false</c>.</returns>
public bool Contains(T mapping) => _reverseMapping.ContainsKey(mapping);
/// <summary>
/// Gets the custom identifier mapped to the specified led id.
/// </summary>
/// <param name="ledId">The led id to get the custom identifier for.</param>
/// <param name="mapping">Contains the mapped custom identifier or null if there is no mapping for the specified led id.</param>
/// <returns><c>true</c> if there was a custom identifier for the specified led id; otherwise <c>false</c>.</returns>
public bool TryGetValue(LedId ledId, out T? mapping) => _mapping.TryGetValue(ledId, out mapping);
/// <summary>
/// Gets the led id mapped to the specified custom identifier.
/// </summary>
/// <param name="mapping">The custom identifier to get the led id for.</param>
/// <param name="ledId">Contains the mapped led id or null if there is no mapping for the specified led id.</param>
/// <returns><c>true</c> if there was a led id for the specified custom identifier; otherwise <c>false</c>.</returns>
public bool TryGetValue(T mapping, out LedId ledId) => _reverseMapping.TryGetValue(mapping, out ledId);
/// <summary>
/// Removes the specified led id and the mapped custom identifier.
/// </summary>
/// <param name="ledId">The led id to remove.</param>
/// <returns><c>true</c> if there was a mapping for the led id to remove; otherwise <c>false</c>.</returns>
public bool Remove(LedId ledId)
{
if (_mapping.TryGetValue(ledId, out T? mapping))
_reverseMapping.Remove(mapping);
return _mapping.Remove(ledId);
}
/// <summary>
/// Removes the specified custom identifier and the mapped led id.
/// </summary>
/// <param name="mapping">The custom identifier to remove.</param>
/// <returns><c>true</c> if there was a mapping for the custom identifier to remove; otherwise <c>false</c>.</returns>
public bool Remove(T mapping)
{
if (_reverseMapping.TryGetValue(mapping, out LedId ledId))
_mapping.Remove(ledId);
return _reverseMapping.Remove(mapping);
}
/// <summary>
/// Removes all registered mappings.
/// </summary>
public void Clear()
{
_mapping.Clear();
_reverseMapping.Clear();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <inheritdoc />
public IEnumerator<(LedId ledId, T mapping)> GetEnumerator() => _mapping.Select(x => (x.Key, x.Value)).GetEnumerator();
#endregion
}
}
/// <summary>
/// Gets the <see cref="LedId"/> mapped to the specified custom identifier.
/// </summary>
/// <param name="mapping">The custom identifier to get the mapped led id.</param>
/// <returns>The led id.</returns>
public LedId this[T mapping]
{
get => _reverseMapping[mapping];
set => this[value] = mapping;
}
#endregion
#region Methods
/// <summary>
/// Adds a new entry to the mapping.
/// </summary>
/// <param name="ledId">The <see cref="LedId"/> to map.</param>
/// <param name="mapping">The custom identifier to map.</param>
public void Add(LedId ledId, T mapping)
{
_mapping.Add(ledId, mapping);
_reverseMapping.Add(mapping, ledId);
}
/// <summary>
/// Checks if the specified <see cref="LedId"/> is mapped.
/// </summary>
/// <param name="ledId">The led id to check.</param>
/// <returns><c>true</c> if the led id is mapped; otherwise <c>false</c>.</returns>
public bool Contains(LedId ledId) => _mapping.ContainsKey(ledId);
/// <summary>
/// Checks if the specified custom identifier is mapped.
/// </summary>
/// <param name="mapping">The custom identifier to check.</param>
/// <returns><c>true</c> if the led id is mapped; otherwise <c>false</c>.</returns>
public bool Contains(T mapping) => _reverseMapping.ContainsKey(mapping);
/// <summary>
/// Gets the custom identifier mapped to the specified led id.
/// </summary>
/// <param name="ledId">The led id to get the custom identifier for.</param>
/// <param name="mapping">Contains the mapped custom identifier or null if there is no mapping for the specified led id.</param>
/// <returns><c>true</c> if there was a custom identifier for the specified led id; otherwise <c>false</c>.</returns>
public bool TryGetValue(LedId ledId, out T? mapping) => _mapping.TryGetValue(ledId, out mapping);
/// <summary>
/// Gets the led id mapped to the specified custom identifier.
/// </summary>
/// <param name="mapping">The custom identifier to get the led id for.</param>
/// <param name="ledId">Contains the mapped led id or null if there is no mapping for the specified led id.</param>
/// <returns><c>true</c> if there was a led id for the specified custom identifier; otherwise <c>false</c>.</returns>
public bool TryGetValue(T mapping, out LedId ledId) => _reverseMapping.TryGetValue(mapping, out ledId);
/// <summary>
/// Removes the specified led id and the mapped custom identifier.
/// </summary>
/// <param name="ledId">The led id to remove.</param>
/// <returns><c>true</c> if there was a mapping for the led id to remove; otherwise <c>false</c>.</returns>
public bool Remove(LedId ledId)
{
if (_mapping.TryGetValue(ledId, out T? mapping))
_reverseMapping.Remove(mapping);
return _mapping.Remove(ledId);
}
/// <summary>
/// Removes the specified custom identifier and the mapped led id.
/// </summary>
/// <param name="mapping">The custom identifier to remove.</param>
/// <returns><c>true</c> if there was a mapping for the custom identifier to remove; otherwise <c>false</c>.</returns>
public bool Remove(T mapping)
{
if (_reverseMapping.TryGetValue(mapping, out LedId ledId))
_mapping.Remove(ledId);
return _reverseMapping.Remove(mapping);
}
/// <summary>
/// Removes all registered mappings.
/// </summary>
public void Clear()
{
_mapping.Clear();
_reverseMapping.Clear();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <inheritdoc />
public IEnumerator<(LedId ledId, T mapping)> GetEnumerator() => _mapping.Select(x => (x.Key, x.Value)).GetEnumerator();
#endregion
}

View File

@ -1,62 +1,61 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents a basic bindable class which notifies when a property value changes.
/// </summary>
public abstract class AbstractBindable : IBindable
{
/// <inheritdoc />
#region Events
/// <summary>
/// Represents a basic bindable class which notifies when a property value changes.
/// Occurs when a property value changes.
/// </summary>
public abstract class AbstractBindable : IBindable
public event PropertyChangedEventHandler? PropertyChanged;
#endregion
#region Methods
/// <summary>
/// Checks if the property already matches the desired value or needs to be updated.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to the backing-filed.</param>
/// <param name="value">Value to apply.</param>
/// <returns><c>true</c> if the value needs to be updated; otherweise <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual bool RequiresUpdate<T>(ref T storage, T value) => !Equals(storage, value);
/// <summary>
/// Checks if the property already matches the desired value and updates it if not.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to the backing-filed.</param>
/// <param name="value">Value to apply.</param>
/// <param name="propertyName">Name of the property used to notify listeners. This value is optional
/// and can be provided automatically when invoked from compilers that support <see cref="CallerMemberNameAttribute"/>.</param>
/// <returns><c>true</c> if the value was changed, <c>false</c> if the existing value matched the desired value.</returns>
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string? propertyName = null)
{
#region Events
if (!RequiresUpdate(ref storage, value)) return false;
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
#endregion
#region Methods
/// <summary>
/// Checks if the property already matches the desired value or needs to be updated.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to the backing-filed.</param>
/// <param name="value">Value to apply.</param>
/// <returns><c>true</c> if the value needs to be updated; otherweise <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual bool RequiresUpdate<T>(ref T storage, T value) => !Equals(storage, value);
/// <summary>
/// Checks if the property already matches the desired value and updates it if not.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to the backing-filed.</param>
/// <param name="value">Value to apply.</param>
/// <param name="propertyName">Name of the property used to notify listeners. This value is optional
/// and can be provided automatically when invoked from compilers that support <see cref="CallerMemberNameAttribute"/>.</param>
/// <returns><c>true</c> if the value was changed, <c>false</c> if the existing value matched the desired value.</returns>
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string? propertyName = null)
{
if (!RequiresUpdate(ref storage, value)) return false;
storage = value;
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(propertyName);
return true;
}
/// <summary>
/// Triggers the <see cref="PropertyChanged"/>-event when a a property value has changed.
/// </summary>
/// <param name="propertyName">Name of the property used to notify listeners. This value is optional
/// and can be provided automatically when invoked from compilers that support <see cref="CallerMemberNameAttribute"/>.</param>
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
#endregion
storage = value;
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(propertyName);
return true;
}
}
/// <summary>
/// Triggers the <see cref="PropertyChanged"/>-event when a a property value has changed.
/// </summary>
/// <param name="propertyName">Name of the property used to notify listeners. This value is optional
/// and can be provided automatically when invoked from compilers that support <see cref="CallerMemberNameAttribute"/>.</param>
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
#endregion
}

View File

@ -1,11 +1,10 @@
using System.ComponentModel;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a basic bindable class which notifies when a property value changes.
/// </summary>
public interface IBindable : INotifyPropertyChanged
{
/// <summary>
/// Represents a basic bindable class which notifies when a property value changes.
/// </summary>
public interface IBindable : INotifyPropertyChanged
{
}
}
}

View File

@ -2,92 +2,91 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic placeable element.
/// </summary>
public interface IPlaceable
{
#region Properties & Fields
/// <summary>
/// Represents a generic placeable element.
/// Gets or sets the location of the <see cref="IPlaceable"/>.
/// </summary>
public interface IPlaceable
{
#region Properties & Fields
Point Location { get; set; }
/// <summary>
/// Gets or sets the location of the <see cref="IPlaceable"/>.
/// </summary>
Point Location { get; set; }
/// <summary>
/// Gets the size of the <see cref="IPlaceable"/>.
/// </summary>
Size Size { get; set; }
/// <summary>
/// Gets the size of the <see cref="IPlaceable"/>.
/// </summary>
Size Size { get; set; }
/// <summary>
/// Gets or sets the scale of the <see cref="IPlaceable"/>.
/// </summary>
Scale Scale { get; set; }
/// <summary>
/// Gets or sets the scale of the <see cref="IPlaceable"/>.
/// </summary>
Scale Scale { get; set; }
/// <summary>
/// Gets or sets the rotation of the <see cref="IPlaceable"/>.
/// </summary>
Rotation Rotation { get; set; }
/// <summary>
/// Gets or sets the rotation of the <see cref="IPlaceable"/>.
/// </summary>
Rotation Rotation { get; set; }
/// <summary>
/// Gets the actual location of the <see cref="IPlaceable"/>.
/// This includes the <see cref="Rotation"/>.
/// </summary>
Point ActualLocation { get; }
/// <summary>
/// Gets the actual location of the <see cref="IPlaceable"/>.
/// This includes the <see cref="Rotation"/>.
/// </summary>
Point ActualLocation { get; }
/// <summary>
/// Gets the actual <see cref="Size"/> of the <see cref="IPlaceable"/>.
/// This includes the <see cref="Scale"/>.
/// </summary>
Size ActualSize { get; }
/// <summary>
/// Gets the actual <see cref="Size"/> of the <see cref="IPlaceable"/>.
/// This includes the <see cref="Scale"/>.
/// </summary>
Size ActualSize { get; }
/// <summary>
/// Gets a rectangle containing the whole <see cref="IPlaceable"/>.
/// This includes <see cref="Location"/>, <see cref="Size"/>, <see cref="Scale"/> and <see cref="Rotation"/>.
/// </summary>
Rectangle Boundary { get; }
/// <summary>
/// Gets a rectangle containing the whole <see cref="IPlaceable"/>.
/// This includes <see cref="Location"/>, <see cref="Size"/>, <see cref="Scale"/> and <see cref="Rotation"/>.
/// </summary>
Rectangle Boundary { get; }
#endregion
#endregion
#region Events
#region Events
/// <summary>
/// Occurs when the <see cref="Location"/> property was changed.
/// </summary>
event EventHandler<EventArgs> LocationChanged;
/// <summary>
/// Occurs when the <see cref="Location"/> property was changed.
/// </summary>
event EventHandler<EventArgs> LocationChanged;
/// <summary>
/// Occurs when the <see cref="Size"/> property was changed.
/// </summary>
event EventHandler<EventArgs> SizeChanged;
/// <summary>
/// Occurs when the <see cref="Size"/> property was changed.
/// </summary>
event EventHandler<EventArgs> SizeChanged;
/// <summary>
/// Occurs when the <see cref="Scale"/> property was changed.
/// </summary>
event EventHandler<EventArgs> ScaleChanged;
/// <summary>
/// Occurs when the <see cref="Scale"/> property was changed.
/// </summary>
event EventHandler<EventArgs> ScaleChanged;
/// <summary>
/// Occurs when the <see cref="Rotation"/> property was changed.
/// </summary>
event EventHandler<EventArgs> RotationChanged;
/// <summary>
/// Occurs when the <see cref="Rotation"/> property was changed.
/// </summary>
event EventHandler<EventArgs> RotationChanged;
/// <summary>
/// Occurs when the <see cref="ActualLocation"/> property was changed.
/// </summary>
event EventHandler<EventArgs> ActualLocationChanged;
/// <summary>
/// Occurs when the <see cref="ActualLocation"/> property was changed.
/// </summary>
event EventHandler<EventArgs> ActualLocationChanged;
/// <summary>
/// Occurs when the <see cref="ActualSize"/> property was changed.
/// </summary>
event EventHandler<EventArgs> ActualSizeChanged;
/// <summary>
/// Occurs when the <see cref="ActualSize"/> property was changed.
/// </summary>
event EventHandler<EventArgs> ActualSizeChanged;
/// <summary>
/// Occurs when the <see cref="Boundary"/> property was changed.
/// </summary>
event EventHandler<EventArgs> BoundaryChanged;
/// <summary>
/// Occurs when the <see cref="Boundary"/> property was changed.
/// </summary>
event EventHandler<EventArgs> BoundaryChanged;
#endregion
}
}
#endregion
}

View File

@ -1,262 +1,261 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a placeable element.
/// </summary>
public class Placeable : AbstractBindable, IPlaceable
{
#region Properties & Fields
/// <summary>
/// Represents a placeable element.
/// Gets the parent this placeable is placed in.
/// </summary>
public class Placeable : AbstractBindable, IPlaceable
protected IPlaceable? Parent { get; }
private Point _location = Point.Invalid;
/// <inheritdoc />
public Point Location
{
#region Properties & Fields
/// <summary>
/// Gets the parent this placeable is placed in.
/// </summary>
protected IPlaceable? Parent { get; }
private Point _location = Point.Invalid;
/// <inheritdoc />
public Point Location
get => _location;
set
{
get => _location;
set
{
if (SetProperty(ref _location, value))
OnLocationChanged();
}
if (SetProperty(ref _location, value))
OnLocationChanged();
}
private Size _size = Size.Invalid;
/// <inheritdoc />
public Size Size
{
get => _size;
set
{
if (SetProperty(ref _size, value))
OnSizeChanged();
}
}
private Scale _scale = new(1);
/// <inheritdoc />
public Scale Scale
{
get => _scale;
set
{
if (SetProperty(ref _scale, value))
OnScaleChanged();
}
}
private Rotation _rotation = new(0);
/// <inheritdoc />
public Rotation Rotation
{
get => _rotation;
set
{
if (SetProperty(ref _rotation, value))
OnRotationChanged();
}
}
private Point _actualLocation = Point.Invalid;
/// <inheritdoc />
public Point ActualLocation
{
get => _actualLocation;
private set
{
if (SetProperty(ref _actualLocation, value))
OnActualLocationChanged();
}
}
private Size _actualSize = Size.Invalid;
/// <inheritdoc />
public Size ActualSize
{
get => _actualSize;
private set
{
if (SetProperty(ref _actualSize, value))
OnActualSizeChanged();
}
}
private Rectangle _boundary = new(Point.Invalid, Point.Invalid);
/// <inheritdoc />
public Rectangle Boundary
{
get => _boundary;
private set
{
if (SetProperty(ref _boundary, value))
OnBoundaryChanged();
}
}
#endregion
#region Events
/// <inheritdoc />
public event EventHandler<EventArgs>? LocationChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? SizeChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? ScaleChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? RotationChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? ActualLocationChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? ActualSizeChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? BoundaryChanged;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Placeable" /> class.
/// </summary>
public Placeable() { }
/// <summary>
/// Initializes a new instance of the <see cref="Placeable" /> class.
/// </summary>
/// <param name="parent">The parent this placeable is placed in.</param>
public Placeable(IPlaceable parent)
{
this.Parent = parent;
Parent.BoundaryChanged += (_, _) => UpdateActualPlaceableData();
}
/// <summary>
/// Initializes a new instance of the <see cref="Placeable" /> class.
/// </summary>
/// <param name="location">The location of this placeable.</param>
/// <param name="size">The size of this placeable.</param>
public Placeable(Point location, Size size)
{
this.Location = location;
this.Size = size;
}
/// <summary>
/// Initializes a new instance of the <see cref="Placeable" /> class.
/// </summary>
/// <param name="parent">The parent placeable this placeable is placed in.</param>
/// <param name="location">The location of this placeable.</param>
/// <param name="size">The size of this placeable.</param>
public Placeable(IPlaceable parent, Point location, Size size)
{
this.Parent = parent;
this.Location = location;
this.Size = size;
Parent.BoundaryChanged += (_, _) => UpdateActualPlaceableData();
}
#endregion
#region Methods
/// <summary>
/// Updates the <see cref="ActualSize"/>, <see cref="ActualLocation"/> and <see cref="Boundary"/> based on the <see cref="Size"/>, <see cref="Scale"/> and <see cref="Rotation"/>.
/// </summary>
protected virtual void UpdateActualPlaceableData()
{
if (Parent != null)
{
Size actualSize = Size * Parent.Scale;
Point actualLocation = (Location * Parent.Scale);
Rectangle boundary = new(actualLocation, actualSize);
if (Parent.Rotation.IsRotated)
{
Point parentCenter = new Rectangle(Parent.ActualSize).Center;
Point actualParentCenter = new Rectangle(Parent.Boundary.Size).Center;
Point centerOffset = new(actualParentCenter.X - parentCenter.X, actualParentCenter.Y - parentCenter.Y);
actualLocation = actualLocation.Rotate(Parent.Rotation, new Rectangle(Parent.ActualSize).Center) + centerOffset;
boundary = new Rectangle(boundary.Rotate(Parent.Rotation, new Rectangle(Parent.ActualSize).Center)).Translate(centerOffset);
}
ActualLocation = actualLocation;
ActualSize = actualSize;
Boundary = boundary;
}
else
{
ActualLocation = Location;
ActualSize = Size * Scale;
Boundary = new Rectangle(Location, new Rectangle(new Rectangle(Location, ActualSize).Rotate(Rotation)).Size);
}
}
/// <summary>
/// Called when the <see cref="Location"/> property was changed.
/// </summary>
protected virtual void OnLocationChanged()
{
LocationChanged?.Invoke(this, new EventArgs());
UpdateActualPlaceableData();
}
/// <summary>
/// Called when the <see cref="Size"/> property was changed.
/// </summary>
protected virtual void OnSizeChanged()
{
SizeChanged?.Invoke(this, new EventArgs());
UpdateActualPlaceableData();
}
/// <summary>
/// Called when the <see cref="Scale"/> property was changed.
/// </summary>
protected virtual void OnScaleChanged()
{
ScaleChanged?.Invoke(this, new EventArgs());
UpdateActualPlaceableData();
}
/// <summary>
/// Called when the <see cref="Rotation"/> property was changed.
/// </summary>
protected virtual void OnRotationChanged()
{
RotationChanged?.Invoke(this, new EventArgs());
UpdateActualPlaceableData();
}
/// <summary>
/// Called when the <see cref="ActualLocation"/> property was changed.
/// </summary>
protected virtual void OnActualLocationChanged() => ActualLocationChanged?.Invoke(this, new EventArgs());
/// <summary>
/// Called when the <see cref="ActualLocation"/> property was changed.
/// </summary>
protected virtual void OnActualSizeChanged() => ActualSizeChanged?.Invoke(this, new EventArgs());
/// <summary>
/// Called when the <see cref="Boundary"/> property was changed.
/// </summary>
protected virtual void OnBoundaryChanged() => BoundaryChanged?.Invoke(this, new EventArgs());
#endregion
}
}
private Size _size = Size.Invalid;
/// <inheritdoc />
public Size Size
{
get => _size;
set
{
if (SetProperty(ref _size, value))
OnSizeChanged();
}
}
private Scale _scale = new(1);
/// <inheritdoc />
public Scale Scale
{
get => _scale;
set
{
if (SetProperty(ref _scale, value))
OnScaleChanged();
}
}
private Rotation _rotation = new(0);
/// <inheritdoc />
public Rotation Rotation
{
get => _rotation;
set
{
if (SetProperty(ref _rotation, value))
OnRotationChanged();
}
}
private Point _actualLocation = Point.Invalid;
/// <inheritdoc />
public Point ActualLocation
{
get => _actualLocation;
private set
{
if (SetProperty(ref _actualLocation, value))
OnActualLocationChanged();
}
}
private Size _actualSize = Size.Invalid;
/// <inheritdoc />
public Size ActualSize
{
get => _actualSize;
private set
{
if (SetProperty(ref _actualSize, value))
OnActualSizeChanged();
}
}
private Rectangle _boundary = new(Point.Invalid, Point.Invalid);
/// <inheritdoc />
public Rectangle Boundary
{
get => _boundary;
private set
{
if (SetProperty(ref _boundary, value))
OnBoundaryChanged();
}
}
#endregion
#region Events
/// <inheritdoc />
public event EventHandler<EventArgs>? LocationChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? SizeChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? ScaleChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? RotationChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? ActualLocationChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? ActualSizeChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? BoundaryChanged;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Placeable" /> class.
/// </summary>
public Placeable() { }
/// <summary>
/// Initializes a new instance of the <see cref="Placeable" /> class.
/// </summary>
/// <param name="parent">The parent this placeable is placed in.</param>
public Placeable(IPlaceable parent)
{
this.Parent = parent;
Parent.BoundaryChanged += (_, _) => UpdateActualPlaceableData();
}
/// <summary>
/// Initializes a new instance of the <see cref="Placeable" /> class.
/// </summary>
/// <param name="location">The location of this placeable.</param>
/// <param name="size">The size of this placeable.</param>
public Placeable(Point location, Size size)
{
this.Location = location;
this.Size = size;
}
/// <summary>
/// Initializes a new instance of the <see cref="Placeable" /> class.
/// </summary>
/// <param name="parent">The parent placeable this placeable is placed in.</param>
/// <param name="location">The location of this placeable.</param>
/// <param name="size">The size of this placeable.</param>
public Placeable(IPlaceable parent, Point location, Size size)
{
this.Parent = parent;
this.Location = location;
this.Size = size;
Parent.BoundaryChanged += (_, _) => UpdateActualPlaceableData();
}
#endregion
#region Methods
/// <summary>
/// Updates the <see cref="ActualSize"/>, <see cref="ActualLocation"/> and <see cref="Boundary"/> based on the <see cref="Size"/>, <see cref="Scale"/> and <see cref="Rotation"/>.
/// </summary>
protected virtual void UpdateActualPlaceableData()
{
if (Parent != null)
{
Size actualSize = Size * Parent.Scale;
Point actualLocation = (Location * Parent.Scale);
Rectangle boundary = new(actualLocation, actualSize);
if (Parent.Rotation.IsRotated)
{
Point parentCenter = new Rectangle(Parent.ActualSize).Center;
Point actualParentCenter = new Rectangle(Parent.Boundary.Size).Center;
Point centerOffset = new(actualParentCenter.X - parentCenter.X, actualParentCenter.Y - parentCenter.Y);
actualLocation = actualLocation.Rotate(Parent.Rotation, new Rectangle(Parent.ActualSize).Center) + centerOffset;
boundary = new Rectangle(boundary.Rotate(Parent.Rotation, new Rectangle(Parent.ActualSize).Center)).Translate(centerOffset);
}
ActualLocation = actualLocation;
ActualSize = actualSize;
Boundary = boundary;
}
else
{
ActualLocation = Location;
ActualSize = Size * Scale;
Boundary = new Rectangle(Location, new Rectangle(new Rectangle(Location, ActualSize).Rotate(Rotation)).Size);
}
}
/// <summary>
/// Called when the <see cref="Location"/> property was changed.
/// </summary>
protected virtual void OnLocationChanged()
{
LocationChanged?.Invoke(this, new EventArgs());
UpdateActualPlaceableData();
}
/// <summary>
/// Called when the <see cref="Size"/> property was changed.
/// </summary>
protected virtual void OnSizeChanged()
{
SizeChanged?.Invoke(this, new EventArgs());
UpdateActualPlaceableData();
}
/// <summary>
/// Called when the <see cref="Scale"/> property was changed.
/// </summary>
protected virtual void OnScaleChanged()
{
ScaleChanged?.Invoke(this, new EventArgs());
UpdateActualPlaceableData();
}
/// <summary>
/// Called when the <see cref="Rotation"/> property was changed.
/// </summary>
protected virtual void OnRotationChanged()
{
RotationChanged?.Invoke(this, new EventArgs());
UpdateActualPlaceableData();
}
/// <summary>
/// Called when the <see cref="ActualLocation"/> property was changed.
/// </summary>
protected virtual void OnActualLocationChanged() => ActualLocationChanged?.Invoke(this, new EventArgs());
/// <summary>
/// Called when the <see cref="ActualLocation"/> property was changed.
/// </summary>
protected virtual void OnActualSizeChanged() => ActualSizeChanged?.Invoke(this, new EventArgs());
/// <summary>
/// Called when the <see cref="Boundary"/> property was changed.
/// </summary>
protected virtual void OnBoundaryChanged() => BoundaryChanged?.Invoke(this, new EventArgs());
#endregion
}

View File

@ -4,152 +4,151 @@
using System;
using System.Diagnostics;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a point consisting of a X- and a Y-position.
/// </summary>
[DebuggerDisplay("[X: {X}, Y: {Y}]")]
public readonly struct Point
{
#region Constants
private static readonly Point INVALID = new(float.NaN, float.NaN);
/// <summary>
/// Represents a point consisting of a X- and a Y-position.
/// Gets a [NaN,NaN]-Point.
/// </summary>
[DebuggerDisplay("[X: {X}, Y: {Y}]")]
public readonly struct Point
public static ref readonly Point Invalid => ref INVALID;
#endregion
#region Properties & Fields
/// <summary>
/// Gets the X-position of this <see cref="Point"/>.
/// </summary>
public float X { get; }
/// <summary>
/// Gets the Y-position of this <see cref="Point"/>.
/// </summary>
public float Y { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Point"/> class using the provided values.
/// </summary>
/// <param name="x">The value used for the X-position.</param>
/// <param name="y">The value used for the Y-position.</param>
public Point(float x, float y)
{
#region Constants
private static readonly Point INVALID = new(float.NaN, float.NaN);
/// <summary>
/// Gets a [NaN,NaN]-Point.
/// </summary>
public static ref readonly Point Invalid => ref INVALID;
#endregion
#region Properties & Fields
/// <summary>
/// Gets the X-position of this <see cref="Point"/>.
/// </summary>
public float X { get; }
/// <summary>
/// Gets the Y-position of this <see cref="Point"/>.
/// </summary>
public float Y { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Point"/> class using the provided values.
/// </summary>
/// <param name="x">The value used for the X-position.</param>
/// <param name="y">The value used for the Y-position.</param>
public Point(float x, float y)
{
this.X = x;
this.Y = y;
}
#endregion
#region Methods
/// <summary>
/// Converts the <see cref="X"/>- and <see cref="Y"/>-position of this <see cref="Point"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the <see cref="X"/> and <see cref="Y"/> of this <see cref="Point"/>. For example "[X: 100, Y: 20]".</returns>
public override string ToString() => $"[X: {X}, Y: {Y}]";
/// <summary>
/// Tests whether the specified object is a <see cref="Point" /> and is equivalent to this <see cref="Point" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Point" /> equivalent to this <see cref="Point" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj)
{
if (obj is not Point comparePoint) return false;
return ((float.IsNaN(X) && float.IsNaN(comparePoint.X)) || X.EqualsInTolerance(comparePoint.X))
&& ((float.IsNaN(Y) && float.IsNaN(comparePoint.Y)) || Y.EqualsInTolerance(comparePoint.Y));
}
/// <summary>
/// Returns a hash code for this <see cref="Point" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Point" />.</returns>
public override int GetHashCode() => HashCode.Combine(X, Y);
#endregion
#region Operators
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Point" /> are equal.
/// </summary>
/// <param name="point1">The first <see cref="Point" /> to compare.</param>
/// <param name="point2">The second <see cref="Point" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="point1" /> and <paramref name="point2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(in Point point1, in Point point2) => point1.Equals(point2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Point" /> are equal.
/// </summary>
/// <param name="point1">The first <see cref="Point" /> to compare.</param>
/// <param name="point2">The second <see cref="Point" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="point1" /> and <paramref name="point2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(in Point point1, in Point point2) => !(point1 == point2);
/// <summary>
/// Returns a new <see cref="Point"/> representing the addition of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the addition of the two provided <see cref="Point"/>.</returns>
public static Point operator +(in Point point1, in Point point2) => new(point1.X + point2.X, point1.Y + point2.Y);
/// <summary>
/// Returns a new <see cref="Rectangle"/> created from the provided <see cref="Point"/> and <see cref="Size"/>.
/// </summary>
/// <param name="point">The <see cref="Point"/> of the rectangle.</param>
/// <param name="size">The <see cref="Size"/> of the rectangle.</param>
/// <returns>The rectangle created from the provided <see cref="Point"/> and <see cref="Size"/>.</returns>
public static Rectangle operator +(in Point point, in Size size) => new(point, size);
/// <summary>
/// Returns a new <see cref="Point"/> representing the subtraction of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the subtraction of the two provided <see cref="Point"/>.</returns>
public static Point operator -(in Point point1, in Point point2) => new(point1.X - point2.X, point1.Y - point2.Y);
/// <summary>
/// Returns a new <see cref="Point"/> representing the multiplication of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the multiplication of the two provided <see cref="Point"/>.</returns>
public static Point operator *(in Point point1, in Point point2) => new(point1.X * point2.X, point1.Y * point2.Y);
/// <summary>
/// Returns a new <see cref="Point"/> representing the division of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the division of the two provided <see cref="Point"/>.</returns>
public static Point operator /(in Point point1, in Point point2)
{
if (point2.X.EqualsInTolerance(0) || point2.Y.EqualsInTolerance(0)) return Invalid;
return new Point(point1.X / point2.X, point1.Y / point2.Y);
}
/// <summary>
/// Returns a new <see cref="Point"/> representing the multiplication of the <see cref="Point"/> and the provided <see cref="Scale"/>.
/// </summary>
/// <param name="point">The <see cref="Point"/>.</param>
/// <param name="scale">The <see cref="Scale"/>.</param>
/// <returns>A new <see cref="Point"/> representing the multiplication of the <see cref="Point"/> and the provided <see cref="Scale"/>.</returns>
public static Point operator *(in Point point, in Scale scale) => new(point.X * scale.Horizontal, point.Y * scale.Vertical);
#endregion
this.X = x;
this.Y = y;
}
}
#endregion
#region Methods
/// <summary>
/// Converts the <see cref="X"/>- and <see cref="Y"/>-position of this <see cref="Point"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the <see cref="X"/> and <see cref="Y"/> of this <see cref="Point"/>. For example "[X: 100, Y: 20]".</returns>
public override string ToString() => $"[X: {X}, Y: {Y}]";
/// <summary>
/// Tests whether the specified object is a <see cref="Point" /> and is equivalent to this <see cref="Point" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Point" /> equivalent to this <see cref="Point" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj)
{
if (obj is not Point comparePoint) return false;
return ((float.IsNaN(X) && float.IsNaN(comparePoint.X)) || X.EqualsInTolerance(comparePoint.X))
&& ((float.IsNaN(Y) && float.IsNaN(comparePoint.Y)) || Y.EqualsInTolerance(comparePoint.Y));
}
/// <summary>
/// Returns a hash code for this <see cref="Point" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Point" />.</returns>
public override int GetHashCode() => HashCode.Combine(X, Y);
#endregion
#region Operators
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Point" /> are equal.
/// </summary>
/// <param name="point1">The first <see cref="Point" /> to compare.</param>
/// <param name="point2">The second <see cref="Point" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="point1" /> and <paramref name="point2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(in Point point1, in Point point2) => point1.Equals(point2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Point" /> are equal.
/// </summary>
/// <param name="point1">The first <see cref="Point" /> to compare.</param>
/// <param name="point2">The second <see cref="Point" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="point1" /> and <paramref name="point2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(in Point point1, in Point point2) => !(point1 == point2);
/// <summary>
/// Returns a new <see cref="Point"/> representing the addition of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the addition of the two provided <see cref="Point"/>.</returns>
public static Point operator +(in Point point1, in Point point2) => new(point1.X + point2.X, point1.Y + point2.Y);
/// <summary>
/// Returns a new <see cref="Rectangle"/> created from the provided <see cref="Point"/> and <see cref="Size"/>.
/// </summary>
/// <param name="point">The <see cref="Point"/> of the rectangle.</param>
/// <param name="size">The <see cref="Size"/> of the rectangle.</param>
/// <returns>The rectangle created from the provided <see cref="Point"/> and <see cref="Size"/>.</returns>
public static Rectangle operator +(in Point point, in Size size) => new(point, size);
/// <summary>
/// Returns a new <see cref="Point"/> representing the subtraction of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the subtraction of the two provided <see cref="Point"/>.</returns>
public static Point operator -(in Point point1, in Point point2) => new(point1.X - point2.X, point1.Y - point2.Y);
/// <summary>
/// Returns a new <see cref="Point"/> representing the multiplication of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the multiplication of the two provided <see cref="Point"/>.</returns>
public static Point operator *(in Point point1, in Point point2) => new(point1.X * point2.X, point1.Y * point2.Y);
/// <summary>
/// Returns a new <see cref="Point"/> representing the division of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the division of the two provided <see cref="Point"/>.</returns>
public static Point operator /(in Point point1, in Point point2)
{
if (point2.X.EqualsInTolerance(0) || point2.Y.EqualsInTolerance(0)) return Invalid;
return new Point(point1.X / point2.X, point1.Y / point2.Y);
}
/// <summary>
/// Returns a new <see cref="Point"/> representing the multiplication of the <see cref="Point"/> and the provided <see cref="Scale"/>.
/// </summary>
/// <param name="point">The <see cref="Point"/>.</param>
/// <param name="scale">The <see cref="Scale"/>.</param>
/// <returns>A new <see cref="Point"/> representing the multiplication of the <see cref="Point"/> and the provided <see cref="Scale"/>.</returns>
public static Point operator *(in Point point, in Scale scale) => new(point.X * scale.Horizontal, point.Y * scale.Vertical);
#endregion
}

View File

@ -6,237 +6,236 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a rectangle defined by it's position and it's size.
/// </summary>
[DebuggerDisplay("[Location: {Location}, Size: {Size}]")]
public readonly struct Rectangle
{
#region Properties & Fields
/// <summary>
/// Represents a rectangle defined by it's position and it's size.
/// Gets the <see cref="Point"/> representing the top-left corner of the <see cref="Rectangle"/>.
/// </summary>
[DebuggerDisplay("[Location: {Location}, Size: {Size}]")]
public readonly struct Rectangle
public Point Location { get; }
/// <summary>
/// Gets the <see cref="Size"/> of the <see cref="Rectangle"/>.
/// </summary>
public Size Size { get; }
/// <summary>
/// Gets a new <see cref="Point"/> representing the center-point of the <see cref="Rectangle"/>.
/// </summary>
public Point Center { get; }
/// <summary>
/// Gets a bool indicating if both, the width and the height of the rectangle is greater than zero.
/// <c>True</c> if the rectangle has a width or a height of zero; otherwise, <c>false</c>.
/// </summary>
public bool IsEmpty => (Size.Width <= FloatExtensions.TOLERANCE) || (Size.Height <= FloatExtensions.TOLERANCE);
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the provided values for <see cref="P:RGB.NET.Core.Rectangle.Location" /> ans <see cref="P:RGB.NET.Core.Rectangle.Size" />.
/// </summary>
/// <param name="x">The x-value of the <see cref="T:RGB.NET.Core.Location" /> of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="y">The y-value of the <see cref="T:RGB.NET.Core.Location" />-position of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="width">The width of the <see cref="T:RGB.NET.Core.Size"/> of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="height">The height of the <see cref="T:RGB.NET.Core.Size"/> of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
public Rectangle(float x, float y, float width, float height)
: this(new Point(x, y), new Size(width, height))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> class using the <see cref="Location"/>(0,0) and the specified <see cref="Core.Size"/>.
/// </summary>
/// <param name="size">The size of of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
public Rectangle(Size size) : this(new Point(), size)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> class using the specified <see cref="Point"/> and <see cref="Core.Size"/>.
/// </summary>
/// <param name="location">The location of this of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="size">The size of of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
public Rectangle(Point location, Size size)
{
#region Properties & Fields
this.Location = location;
this.Size = size;
/// <summary>
/// Gets the <see cref="Point"/> representing the top-left corner of the <see cref="Rectangle"/>.
/// </summary>
public Point Location { get; }
/// <summary>
/// Gets the <see cref="Size"/> of the <see cref="Rectangle"/>.
/// </summary>
public Size Size { get; }
/// <summary>
/// Gets a new <see cref="Point"/> representing the center-point of the <see cref="Rectangle"/>.
/// </summary>
public Point Center { get; }
/// <summary>
/// Gets a bool indicating if both, the width and the height of the rectangle is greater than zero.
/// <c>True</c> if the rectangle has a width or a height of zero; otherwise, <c>false</c>.
/// </summary>
public bool IsEmpty => (Size.Width <= FloatExtensions.TOLERANCE) || (Size.Height <= FloatExtensions.TOLERANCE);
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the provided values for <see cref="P:RGB.NET.Core.Rectangle.Location" /> ans <see cref="P:RGB.NET.Core.Rectangle.Size" />.
/// </summary>
/// <param name="x">The x-value of the <see cref="T:RGB.NET.Core.Location" /> of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="y">The y-value of the <see cref="T:RGB.NET.Core.Location" />-position of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="width">The width of the <see cref="T:RGB.NET.Core.Size"/> of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="height">The height of the <see cref="T:RGB.NET.Core.Size"/> of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
public Rectangle(float x, float y, float width, float height)
: this(new Point(x, y), new Size(width, height))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> class using the <see cref="Location"/>(0,0) and the specified <see cref="Core.Size"/>.
/// </summary>
/// <param name="size">The size of of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
public Rectangle(Size size) : this(new Point(), size)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> class using the specified <see cref="Point"/> and <see cref="Core.Size"/>.
/// </summary>
/// <param name="location">The location of this of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="size">The size of of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
public Rectangle(Point location, Size size)
{
this.Location = location;
this.Size = size;
Center = new Point(Location.X + (Size.Width / 2.0f), Location.Y + (Size.Height / 2.0f));
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the specified array of <see cref="T:RGB.NET.Core.Rectangle" />.
/// The <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /> is calculated to completely contain all rectangles provided as parameters.
/// </summary>
/// <param name="rectangles">The array of <see cref="T:RGB.NET.Core.Rectangle" /> used to calculate the <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" />.</param>
public Rectangle(params Rectangle[] rectangles)
: this(rectangles.AsEnumerable())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> class using the specified list of <see cref="Rectangle"/>.
/// The <see cref="Location"/> and <see cref="Size"/> is calculated to completely contain all rectangles provided as parameters.
/// </summary>
/// <param name="rectangles">The list of <see cref="Rectangle"/> used to calculate the <see cref="Location"/> and <see cref="Size"/>.</param>
public Rectangle(IEnumerable<Rectangle> rectangles)
{
bool hasPoint = false;
float posX = float.MaxValue;
float posY = float.MaxValue;
float posX2 = float.MinValue;
float posY2 = float.MinValue;
foreach (Rectangle rectangle in rectangles)
{
hasPoint = true;
posX = Math.Min(posX, rectangle.Location.X);
posY = Math.Min(posY, rectangle.Location.Y);
posX2 = Math.Max(posX2, rectangle.Location.X + rectangle.Size.Width);
posY2 = Math.Max(posY2, rectangle.Location.Y + rectangle.Size.Height);
}
(Point location, Size size) = hasPoint ? InitializeFromPoints(new Point(posX, posY), new Point(posX2, posY2)) : InitializeFromPoints(new Point(0, 0), new Point(0, 0));
Location = location;
Size = size;
Center = new Point(Location.X + (Size.Width / 2.0f), Location.Y + (Size.Height / 2.0f));
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the specified array of <see cref="T:RGB.NET.Core.Point" />.
/// The <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /> is calculated to contain all points provided as parameters.
/// </summary>
/// <param name="points">The array of <see cref="T:RGB.NET.Core.Point" /> used to calculate the <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" />.</param>
public Rectangle(params Point[] points)
: this(points.AsEnumerable())
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the specified list of <see cref="T:RGB.NET.Core.Point" />.
/// The <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /> is calculated to contain all points provided as parameters.
/// </summary>
/// <param name="points">The list of <see cref="T:RGB.NET.Core.Point" /> used to calculate the <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" />.</param>
public Rectangle(IEnumerable<Point> points)
: this()
{
bool hasPoint = false;
float posX = float.MaxValue;
float posY = float.MaxValue;
float posX2 = float.MinValue;
float posY2 = float.MinValue;
foreach (Point point in points)
{
hasPoint = true;
posX = Math.Min(posX, point.X);
posY = Math.Min(posY, point.Y);
posX2 = Math.Max(posX2, point.X);
posY2 = Math.Max(posY2, point.Y);
}
(Point location, Size size) = hasPoint ? InitializeFromPoints(new Point(posX, posY), new Point(posX2, posY2)) : InitializeFromPoints(new Point(0, 0), new Point(0, 0));
Location = location;
Size = size;
Center = new Point(Location.X + (Size.Width / 2.0f), Location.Y + (Size.Height / 2.0f));
}
#endregion
#region Methods
private static (Point location, Size size) InitializeFromPoints(in Point point1, in Point point2)
{
float posX = Math.Min(point1.X, point2.X);
float posY = Math.Min(point1.Y, point2.Y);
float width = Math.Max(point1.X, point2.X) - posX;
float height = Math.Max(point1.Y, point2.Y) - posY;
return (new Point(posX, posY), new Size(width, height));
}
/// <summary>
/// Converts the <see cref="Location"/>- and <see cref="Size"/>-position of this <see cref="Rectangle"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the <see cref="Location"/> and <see cref="Size"/> of this <see cref="Rectangle"/>. For example "[Location: [X: 100, Y: 10], Size: [Width: 20, Height: [40]]".</returns>
public override string ToString() => $"[Location: {Location}, Size: {Size}]";
/// <summary>
/// Tests whether the specified object is a <see cref="Rectangle" /> and is equivalent to this <see cref="Rectangle" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Rectangle" /> equivalent to this <see cref="Rectangle" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj)
{
if (obj is not Rectangle compareRect)
return false;
if (GetType() != compareRect.GetType())
return false;
return (Location == compareRect.Location) && (Size == compareRect.Size);
}
/// <summary>
/// Returns a hash code for this <see cref="Rectangle" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Rectangle" />.</returns>
public override int GetHashCode()
{
unchecked
{
int hashCode = Location.GetHashCode();
hashCode = (hashCode * 397) ^ Size.GetHashCode();
return hashCode;
}
}
#endregion
#region Operators
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Rectangle" /> are equal.
/// </summary>
/// <param name="rectangle1">The first <see cref="Rectangle" /> to compare.</param>
/// <param name="rectangle2">The second <see cref="Rectangle" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="rectangle1" /> and <paramref name="rectangle2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(in Rectangle rectangle1, in Rectangle rectangle2) => rectangle1.Equals(rectangle2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Rectangle" /> are equal.
/// </summary>
/// <param name="rectangle1">The first <see cref="Rectangle" /> to compare.</param>
/// <param name="rectangle2">The second <see cref="Rectangle" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="rectangle1" /> and <paramref name="rectangle2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(in Rectangle rectangle1, in Rectangle rectangle2) => !(rectangle1 == rectangle2);
// DarthAffe 20.02.2021: Used for normalization
/// <summary>
/// Returns a <see cref="Rectangle"/> normalized to the specified reference.
/// </summary>
/// <param name="rectangle1">The rectangle to nromalize.</param>
/// <param name="rectangle2">The reference used for normalization.</param>
/// <returns>A normalized rectangle.</returns>
public static Rectangle operator /(in Rectangle rectangle1, in Rectangle rectangle2)
{
float x = rectangle1.Location.X / (rectangle2.Size.Width - rectangle2.Location.X);
float y = rectangle1.Location.Y / (rectangle2.Size.Height - rectangle2.Location.Y);
Size size = rectangle1.Size / rectangle2.Size;
return new Rectangle(new Point(x, y), size);
}
#endregion
Center = new Point(Location.X + (Size.Width / 2.0f), Location.Y + (Size.Height / 2.0f));
}
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the specified array of <see cref="T:RGB.NET.Core.Rectangle" />.
/// The <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /> is calculated to completely contain all rectangles provided as parameters.
/// </summary>
/// <param name="rectangles">The array of <see cref="T:RGB.NET.Core.Rectangle" /> used to calculate the <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" />.</param>
public Rectangle(params Rectangle[] rectangles)
: this(rectangles.AsEnumerable())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> class using the specified list of <see cref="Rectangle"/>.
/// The <see cref="Location"/> and <see cref="Size"/> is calculated to completely contain all rectangles provided as parameters.
/// </summary>
/// <param name="rectangles">The list of <see cref="Rectangle"/> used to calculate the <see cref="Location"/> and <see cref="Size"/>.</param>
public Rectangle(IEnumerable<Rectangle> rectangles)
{
bool hasPoint = false;
float posX = float.MaxValue;
float posY = float.MaxValue;
float posX2 = float.MinValue;
float posY2 = float.MinValue;
foreach (Rectangle rectangle in rectangles)
{
hasPoint = true;
posX = Math.Min(posX, rectangle.Location.X);
posY = Math.Min(posY, rectangle.Location.Y);
posX2 = Math.Max(posX2, rectangle.Location.X + rectangle.Size.Width);
posY2 = Math.Max(posY2, rectangle.Location.Y + rectangle.Size.Height);
}
(Point location, Size size) = hasPoint ? InitializeFromPoints(new Point(posX, posY), new Point(posX2, posY2)) : InitializeFromPoints(new Point(0, 0), new Point(0, 0));
Location = location;
Size = size;
Center = new Point(Location.X + (Size.Width / 2.0f), Location.Y + (Size.Height / 2.0f));
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the specified array of <see cref="T:RGB.NET.Core.Point" />.
/// The <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /> is calculated to contain all points provided as parameters.
/// </summary>
/// <param name="points">The array of <see cref="T:RGB.NET.Core.Point" /> used to calculate the <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" />.</param>
public Rectangle(params Point[] points)
: this(points.AsEnumerable())
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the specified list of <see cref="T:RGB.NET.Core.Point" />.
/// The <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /> is calculated to contain all points provided as parameters.
/// </summary>
/// <param name="points">The list of <see cref="T:RGB.NET.Core.Point" /> used to calculate the <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" />.</param>
public Rectangle(IEnumerable<Point> points)
: this()
{
bool hasPoint = false;
float posX = float.MaxValue;
float posY = float.MaxValue;
float posX2 = float.MinValue;
float posY2 = float.MinValue;
foreach (Point point in points)
{
hasPoint = true;
posX = Math.Min(posX, point.X);
posY = Math.Min(posY, point.Y);
posX2 = Math.Max(posX2, point.X);
posY2 = Math.Max(posY2, point.Y);
}
(Point location, Size size) = hasPoint ? InitializeFromPoints(new Point(posX, posY), new Point(posX2, posY2)) : InitializeFromPoints(new Point(0, 0), new Point(0, 0));
Location = location;
Size = size;
Center = new Point(Location.X + (Size.Width / 2.0f), Location.Y + (Size.Height / 2.0f));
}
#endregion
#region Methods
private static (Point location, Size size) InitializeFromPoints(in Point point1, in Point point2)
{
float posX = Math.Min(point1.X, point2.X);
float posY = Math.Min(point1.Y, point2.Y);
float width = Math.Max(point1.X, point2.X) - posX;
float height = Math.Max(point1.Y, point2.Y) - posY;
return (new Point(posX, posY), new Size(width, height));
}
/// <summary>
/// Converts the <see cref="Location"/>- and <see cref="Size"/>-position of this <see cref="Rectangle"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the <see cref="Location"/> and <see cref="Size"/> of this <see cref="Rectangle"/>. For example "[Location: [X: 100, Y: 10], Size: [Width: 20, Height: [40]]".</returns>
public override string ToString() => $"[Location: {Location}, Size: {Size}]";
/// <summary>
/// Tests whether the specified object is a <see cref="Rectangle" /> and is equivalent to this <see cref="Rectangle" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Rectangle" /> equivalent to this <see cref="Rectangle" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj)
{
if (obj is not Rectangle compareRect)
return false;
if (GetType() != compareRect.GetType())
return false;
return (Location == compareRect.Location) && (Size == compareRect.Size);
}
/// <summary>
/// Returns a hash code for this <see cref="Rectangle" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Rectangle" />.</returns>
public override int GetHashCode()
{
unchecked
{
int hashCode = Location.GetHashCode();
hashCode = (hashCode * 397) ^ Size.GetHashCode();
return hashCode;
}
}
#endregion
#region Operators
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Rectangle" /> are equal.
/// </summary>
/// <param name="rectangle1">The first <see cref="Rectangle" /> to compare.</param>
/// <param name="rectangle2">The second <see cref="Rectangle" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="rectangle1" /> and <paramref name="rectangle2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(in Rectangle rectangle1, in Rectangle rectangle2) => rectangle1.Equals(rectangle2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Rectangle" /> are equal.
/// </summary>
/// <param name="rectangle1">The first <see cref="Rectangle" /> to compare.</param>
/// <param name="rectangle2">The second <see cref="Rectangle" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="rectangle1" /> and <paramref name="rectangle2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(in Rectangle rectangle1, in Rectangle rectangle2) => !(rectangle1 == rectangle2);
// DarthAffe 20.02.2021: Used for normalization
/// <summary>
/// Returns a <see cref="Rectangle"/> normalized to the specified reference.
/// </summary>
/// <param name="rectangle1">The rectangle to nromalize.</param>
/// <param name="rectangle2">The reference used for normalization.</param>
/// <returns>A normalized rectangle.</returns>
public static Rectangle operator /(in Rectangle rectangle1, in Rectangle rectangle2)
{
float x = rectangle1.Location.X / (rectangle2.Size.Width - rectangle2.Location.X);
float y = rectangle1.Location.Y / (rectangle2.Size.Height - rectangle2.Location.Y);
Size size = rectangle1.Size / rectangle2.Size;
return new Rectangle(new Point(x, y), size);
}
#endregion
}

View File

@ -4,159 +4,158 @@
using System;
using System.Diagnostics;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents an angular rotation.
/// </summary>
[DebuggerDisplay("[{" + nameof(Degrees) + "}°]")]
public readonly struct Rotation
{
#region Constants
private const float TWO_PI = MathF.PI * 2.0f;
private const float RADIANS_DEGREES_CONVERSION = 180.0f / MathF.PI;
private const float DEGREES_RADIANS_CONVERSION = MathF.PI / 180.0f;
#endregion
#region Properties & Fields
/// <summary>
/// Represents an angular rotation.
/// Gets the angle in degrees.
/// </summary>
[DebuggerDisplay("[{" + nameof(Degrees) + "}°]")]
public readonly struct Rotation
public float Degrees { get; }
/// <summary>
/// Gets the angle in radians.
/// </summary>
public float Radians { get; }
/// <summary>
/// Gets a bool indicating if the rotation is > 0.
/// </summary>
public bool IsRotated => !Degrees.EqualsInTolerance(0);
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Rotation"/> class using the provided values.
/// </summary>
/// <param name="degrees">The rotation in degrees.</param>
public Rotation(float degrees)
: this(degrees, degrees * DEGREES_RADIANS_CONVERSION)
{ }
private Rotation(float degrees, float radians)
{
#region Constants
private const float TWO_PI = MathF.PI * 2.0f;
private const float RADIANS_DEGREES_CONVERSION = 180.0f / MathF.PI;
private const float DEGREES_RADIANS_CONVERSION = MathF.PI / 180.0f;
#endregion
#region Properties & Fields
/// <summary>
/// Gets the angle in degrees.
/// </summary>
public float Degrees { get; }
/// <summary>
/// Gets the angle in radians.
/// </summary>
public float Radians { get; }
/// <summary>
/// Gets a bool indicating if the rotation is > 0.
/// </summary>
public bool IsRotated => !Degrees.EqualsInTolerance(0);
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Rotation"/> class using the provided values.
/// </summary>
/// <param name="degrees">The rotation in degrees.</param>
public Rotation(float degrees)
: this(degrees, degrees * DEGREES_RADIANS_CONVERSION)
{ }
private Rotation(float degrees, float radians)
{
this.Degrees = degrees % 360.0f;
this.Radians = radians % TWO_PI;
}
#endregion
#region Methods
/// <summary>
/// Creates a new Rotation out of the specified degree-angle.
/// </summary>
/// <param name="degrees">The angle in degrees.</param>
/// <returns>The new rotation.</returns>
public static Rotation FromDegrees(float degrees) => new(degrees);
/// <summary>
/// Creates a new Rotation out of the specified radian-angle.
/// </summary>
/// <param name="radians">The angle in radians.</param>
/// <returns>The new rotation.</returns>
public static Rotation FromRadians(float radians) => new(radians * RADIANS_DEGREES_CONVERSION, radians);
/// <summary>
/// Tests whether the specified <see cref="Rotation" /> is equivalent to this <see cref="Rotation" />.
/// </summary>
/// <param name="other">The rotation to test.</param>
/// <returns><c>true</c> if <paramref name="other" /> is equivalent to this <see cref="Rotation" />; otherwise, <c>false</c>.</returns>
public bool Equals(Rotation other) => Degrees.EqualsInTolerance(other.Degrees);
/// <summary>
/// Tests whether the specified object is a <see cref="Rotation" /> and is equivalent to this <see cref="Rotation" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Rotation" /> equivalent to this <see cref="Rotation" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj) => obj is Rotation other && Equals(other);
/// <summary>
/// Returns a hash code for this <see cref="Rotation" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Rotation" />.</returns>
public override int GetHashCode() => Degrees.GetHashCode();
#endregion
#region Operators
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Rotation" /> are equal.
/// </summary>
/// <param name="rotation1">The first <see cref="Rotation" /> to compare.</param>
/// <param name="rotation2">The second <see cref="Rotation" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="rotation1" /> and <paramref name="rotation2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(in Rotation rotation1, in Rotation rotation2) => rotation1.Equals(rotation2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Rotation" /> are equal.
/// </summary>
/// <param name="rotation1">The first <see cref="Rotation" /> to compare.</param>
/// <param name="rotation2">The second <see cref="Rotation" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="rotation1" /> and <paramref name="rotation2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(in Rotation rotation1, in Rotation rotation2) => !(rotation1 == rotation2);
/// <summary>
/// Returns a new <see cref="Rotation"/> representing the addition of the <see cref="Rotation"/> and the provided value.
/// </summary>
/// <param name="rotation">The <see cref="Rotation"/>.</param>
/// <param name="value">The value to add.</param>
/// <returns>A new <see cref="Rotation"/> representing the addition of the <see cref="Rotation"/> and the provided value.</returns>
public static Rotation operator +(in Rotation rotation, float value) => new(rotation.Degrees + value);
/// <summary>
/// Returns a new <see cref="Rotation"/> representing the subtraction of the <see cref="Rotation"/> and the provided value.
/// </summary>
/// <param name="rotation">The <see cref="Rotation"/>.</param>
/// <param name="value">The value to substract.</param>
/// <returns>A new <see cref="Rotation"/> representing the subtraction of the <see cref="Rotation"/> and the provided value.</returns>
public static Rotation operator -(in Rotation rotation, float value) => new(rotation.Degrees - value);
/// <summary>
/// Returns a new <see cref="Rotation"/> representing the multiplication of the <see cref="Rotation"/> and the provided value.
/// </summary>
/// <param name="rotation">The <see cref="Rotation"/>.</param>
/// <param name="value">The value to multiply with.</param>
/// <returns>A new <see cref="Rotation"/> representing the multiplication of the <see cref="Rotation"/> and the provided value.</returns>
public static Rotation operator *(in Rotation rotation, float value) => new(rotation.Degrees * value);
/// <summary>
/// Returns a new <see cref="Rotation"/> representing the division of the <see cref="Rotation"/> and the provided value.
/// </summary>
/// <param name="rotation">The <see cref="Rotation"/>.</param>
/// <param name="value">The value to device with.</param>
/// <returns>A new <see cref="Rotation"/> representing the division of the <see cref="Rotation"/> and the provided value.</returns>
public static Rotation operator /(in Rotation rotation, float value) => value.EqualsInTolerance(0) ? new Rotation(0) : new Rotation(rotation.Degrees / value);
/// <summary>
/// Converts a float to a <see cref="Rotation" />.
/// </summary>
/// <param name="rotation">The rotation in degrees to convert.</param>
public static implicit operator Rotation(float rotation) => new(rotation);
/// <summary>
/// Converts <see cref="Rotation" /> to a float representing the rotation in degrees.
/// </summary>
/// <param name="rotation">The rotatio to convert.</param>
public static implicit operator float(in Rotation rotation) => rotation.Degrees;
#endregion
this.Degrees = degrees % 360.0f;
this.Radians = radians % TWO_PI;
}
}
#endregion
#region Methods
/// <summary>
/// Creates a new Rotation out of the specified degree-angle.
/// </summary>
/// <param name="degrees">The angle in degrees.</param>
/// <returns>The new rotation.</returns>
public static Rotation FromDegrees(float degrees) => new(degrees);
/// <summary>
/// Creates a new Rotation out of the specified radian-angle.
/// </summary>
/// <param name="radians">The angle in radians.</param>
/// <returns>The new rotation.</returns>
public static Rotation FromRadians(float radians) => new(radians * RADIANS_DEGREES_CONVERSION, radians);
/// <summary>
/// Tests whether the specified <see cref="Rotation" /> is equivalent to this <see cref="Rotation" />.
/// </summary>
/// <param name="other">The rotation to test.</param>
/// <returns><c>true</c> if <paramref name="other" /> is equivalent to this <see cref="Rotation" />; otherwise, <c>false</c>.</returns>
public bool Equals(Rotation other) => Degrees.EqualsInTolerance(other.Degrees);
/// <summary>
/// Tests whether the specified object is a <see cref="Rotation" /> and is equivalent to this <see cref="Rotation" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Rotation" /> equivalent to this <see cref="Rotation" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj) => obj is Rotation other && Equals(other);
/// <summary>
/// Returns a hash code for this <see cref="Rotation" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Rotation" />.</returns>
public override int GetHashCode() => Degrees.GetHashCode();
#endregion
#region Operators
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Rotation" /> are equal.
/// </summary>
/// <param name="rotation1">The first <see cref="Rotation" /> to compare.</param>
/// <param name="rotation2">The second <see cref="Rotation" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="rotation1" /> and <paramref name="rotation2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(in Rotation rotation1, in Rotation rotation2) => rotation1.Equals(rotation2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Rotation" /> are equal.
/// </summary>
/// <param name="rotation1">The first <see cref="Rotation" /> to compare.</param>
/// <param name="rotation2">The second <see cref="Rotation" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="rotation1" /> and <paramref name="rotation2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(in Rotation rotation1, in Rotation rotation2) => !(rotation1 == rotation2);
/// <summary>
/// Returns a new <see cref="Rotation"/> representing the addition of the <see cref="Rotation"/> and the provided value.
/// </summary>
/// <param name="rotation">The <see cref="Rotation"/>.</param>
/// <param name="value">The value to add.</param>
/// <returns>A new <see cref="Rotation"/> representing the addition of the <see cref="Rotation"/> and the provided value.</returns>
public static Rotation operator +(in Rotation rotation, float value) => new(rotation.Degrees + value);
/// <summary>
/// Returns a new <see cref="Rotation"/> representing the subtraction of the <see cref="Rotation"/> and the provided value.
/// </summary>
/// <param name="rotation">The <see cref="Rotation"/>.</param>
/// <param name="value">The value to substract.</param>
/// <returns>A new <see cref="Rotation"/> representing the subtraction of the <see cref="Rotation"/> and the provided value.</returns>
public static Rotation operator -(in Rotation rotation, float value) => new(rotation.Degrees - value);
/// <summary>
/// Returns a new <see cref="Rotation"/> representing the multiplication of the <see cref="Rotation"/> and the provided value.
/// </summary>
/// <param name="rotation">The <see cref="Rotation"/>.</param>
/// <param name="value">The value to multiply with.</param>
/// <returns>A new <see cref="Rotation"/> representing the multiplication of the <see cref="Rotation"/> and the provided value.</returns>
public static Rotation operator *(in Rotation rotation, float value) => new(rotation.Degrees * value);
/// <summary>
/// Returns a new <see cref="Rotation"/> representing the division of the <see cref="Rotation"/> and the provided value.
/// </summary>
/// <param name="rotation">The <see cref="Rotation"/>.</param>
/// <param name="value">The value to device with.</param>
/// <returns>A new <see cref="Rotation"/> representing the division of the <see cref="Rotation"/> and the provided value.</returns>
public static Rotation operator /(in Rotation rotation, float value) => value.EqualsInTolerance(0) ? new Rotation(0) : new Rotation(rotation.Degrees / value);
/// <summary>
/// Converts a float to a <see cref="Rotation" />.
/// </summary>
/// <param name="rotation">The rotation in degrees to convert.</param>
public static implicit operator Rotation(float rotation) => new(rotation);
/// <summary>
/// Converts <see cref="Rotation" /> to a float representing the rotation in degrees.
/// </summary>
/// <param name="rotation">The rotatio to convert.</param>
public static implicit operator float(in Rotation rotation) => rotation.Degrees;
#endregion
}

View File

@ -3,142 +3,141 @@
using System.Diagnostics;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a scaling.
/// </summary>
[DebuggerDisplay("[Horizontal: {Horizontal}, Vertical: {Vertical}]")]
public readonly struct Scale
{
#region Properties & Fields
/// <summary>
/// Represents a scaling.
/// Gets the horizontal scaling value.
/// </summary>
[DebuggerDisplay("[Horizontal: {Horizontal}, Vertical: {Vertical}]")]
public readonly struct Scale
public float Horizontal { get; }
/// <summary>
/// Gets the vertical scaling value.
/// </summary>
public float Vertical { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Scale"/> class using the provided values.
/// </summary>
/// <param name="scale">The value used for horizontal and vertical scaling. 0 if not set.</param>
public Scale(float scale = 1.0f) : this(scale, scale)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Scale"/> class using the provided values.
/// </summary>
/// <param name="horizontal">The value used for horizontal scaling.</param>
/// <param name="vertical">The value used for vertical scaling.</param>
public Scale(float horizontal, float vertical)
{
#region Properties & Fields
/// <summary>
/// Gets the horizontal scaling value.
/// </summary>
public float Horizontal { get; }
/// <summary>
/// Gets the vertical scaling value.
/// </summary>
public float Vertical { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Scale"/> class using the provided values.
/// </summary>
/// <param name="scale">The value used for horizontal and vertical scaling. 0 if not set.</param>
public Scale(float scale = 1.0f) : this(scale, scale)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Scale"/> class using the provided values.
/// </summary>
/// <param name="horizontal">The value used for horizontal scaling.</param>
/// <param name="vertical">The value used for vertical scaling.</param>
public Scale(float horizontal, float vertical)
{
this.Horizontal = horizontal;
this.Vertical = vertical;
}
#endregion
#region Methods
/// <summary>
/// Tests whether the specified <see cref="Scale"/> is equivalent to this <see cref="Scale" />.
/// </summary>
/// <param name="other">The scale to test.</param>
/// <returns><c>true</c> if <paramref name="other" /> is equivalent to this <see cref="Scale" />; otherwise, <c>false</c>.</returns>
public bool Equals(Scale other) => Horizontal.EqualsInTolerance(other.Horizontal) && Vertical.EqualsInTolerance(other.Vertical);
/// <summary>
/// Tests whether the specified object is a <see cref="Scale" /> and is equivalent to this <see cref="Scale" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Scale" /> equivalent to this <see cref="Scale" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj) => obj is Scale other && Equals(other);
/// <summary>
/// Returns a hash code for this <see cref="Scale" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Scale" />.</returns>
public override int GetHashCode() { unchecked { return (Horizontal.GetHashCode() * 397) ^ Vertical.GetHashCode(); } }
/// <summary>
/// Deconstructs the scale into the horizontal and vertical value.
/// </summary>
/// <param name="horizontalScale">The horizontal scaling value.</param>
/// <param name="verticalScale">The vertical scaling value.</param>
public void Deconstruct(out float horizontalScale, out float verticalScale)
{
horizontalScale = Horizontal;
verticalScale = Vertical;
}
#endregion
#region Operators
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Scale" /> are equal.
/// </summary>
/// <param name="scale1">The first <see cref="Scale" /> to compare.</param>
/// <param name="scale2">The second <see cref="Scale" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="scale1" /> and <paramref name="scale2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(Scale scale1, Scale scale2) => scale1.Equals(scale2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Scale" /> are equal.
/// </summary>
/// <param name="scale1">The first <see cref="Scale" /> to compare.</param>
/// <param name="scale2">The second <see cref="Scale" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="scale1" /> and <paramref name="scale2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(Scale scale1, Scale scale2) => !(scale1 == scale2);
/// <summary>
/// Returns a new <see cref="Scale"/> representing the addition of the <see cref="Scale"/> and the provided value.
/// </summary>
/// <param name="scale">The <see cref="Scale"/>.</param>
/// <param name="value">The value to add.</param>
/// <returns>A new <see cref="Scale"/> representing the addition of the <see cref="Scale"/> and the provided value.</returns>
public static Scale operator +(Scale scale, float value) => new(scale.Horizontal + value, scale.Vertical + value);
/// <summary>
/// Returns a new <see cref="Scale"/> representing the subtraction of the <see cref="Scale"/> and the provided value.
/// </summary>
/// <param name="scale">The <see cref="Scale"/>.</param>
/// <param name="value">The value to substract.</param>
/// <returns>A new <see cref="Scale"/> representing the subtraction of the <see cref="Scale"/> and the provided value.</returns>
public static Scale operator -(Scale scale, float value) => new(scale.Horizontal - value, scale.Vertical - value);
/// <summary>
/// Returns a new <see cref="Scale"/> representing the multiplication of the <see cref="Scale"/> and the provided value.
/// </summary>
/// <param name="scale">The <see cref="Scale"/>.</param>
/// <param name="value">The value to multiply with.</param>
/// <returns>A new <see cref="Scale"/> representing the multiplication of the <see cref="Scale"/> and the provided value.</returns>
public static Scale operator *(Scale scale, float value) => new(scale.Horizontal * value, scale.Vertical * value);
/// <summary>
/// Returns a new <see cref="Scale"/> representing the division of the <see cref="Scale"/> and the provided value.
/// </summary>
/// <param name="scale">The <see cref="Scale"/>.</param>
/// <param name="value">The value to device with.</param>
/// <returns>A new <see cref="Scale"/> representing the division of the <see cref="Scale"/> and the provided value.</returns>
public static Scale operator /(Scale scale, float value) => value.EqualsInTolerance(0) ? new Scale(0) : new Scale(scale.Horizontal / value, scale.Vertical / value);
/// <summary>
/// Converts a float to a <see cref="Scale" />.
/// </summary>
/// <param name="scale">The scale value to convert.</param>
public static implicit operator Scale(float scale) => new(scale);
#endregion
this.Horizontal = horizontal;
this.Vertical = vertical;
}
}
#endregion
#region Methods
/// <summary>
/// Tests whether the specified <see cref="Scale"/> is equivalent to this <see cref="Scale" />.
/// </summary>
/// <param name="other">The scale to test.</param>
/// <returns><c>true</c> if <paramref name="other" /> is equivalent to this <see cref="Scale" />; otherwise, <c>false</c>.</returns>
public bool Equals(Scale other) => Horizontal.EqualsInTolerance(other.Horizontal) && Vertical.EqualsInTolerance(other.Vertical);
/// <summary>
/// Tests whether the specified object is a <see cref="Scale" /> and is equivalent to this <see cref="Scale" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Scale" /> equivalent to this <see cref="Scale" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj) => obj is Scale other && Equals(other);
/// <summary>
/// Returns a hash code for this <see cref="Scale" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Scale" />.</returns>
public override int GetHashCode() { unchecked { return (Horizontal.GetHashCode() * 397) ^ Vertical.GetHashCode(); } }
/// <summary>
/// Deconstructs the scale into the horizontal and vertical value.
/// </summary>
/// <param name="horizontalScale">The horizontal scaling value.</param>
/// <param name="verticalScale">The vertical scaling value.</param>
public void Deconstruct(out float horizontalScale, out float verticalScale)
{
horizontalScale = Horizontal;
verticalScale = Vertical;
}
#endregion
#region Operators
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Scale" /> are equal.
/// </summary>
/// <param name="scale1">The first <see cref="Scale" /> to compare.</param>
/// <param name="scale2">The second <see cref="Scale" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="scale1" /> and <paramref name="scale2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(Scale scale1, Scale scale2) => scale1.Equals(scale2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Scale" /> are equal.
/// </summary>
/// <param name="scale1">The first <see cref="Scale" /> to compare.</param>
/// <param name="scale2">The second <see cref="Scale" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="scale1" /> and <paramref name="scale2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(Scale scale1, Scale scale2) => !(scale1 == scale2);
/// <summary>
/// Returns a new <see cref="Scale"/> representing the addition of the <see cref="Scale"/> and the provided value.
/// </summary>
/// <param name="scale">The <see cref="Scale"/>.</param>
/// <param name="value">The value to add.</param>
/// <returns>A new <see cref="Scale"/> representing the addition of the <see cref="Scale"/> and the provided value.</returns>
public static Scale operator +(Scale scale, float value) => new(scale.Horizontal + value, scale.Vertical + value);
/// <summary>
/// Returns a new <see cref="Scale"/> representing the subtraction of the <see cref="Scale"/> and the provided value.
/// </summary>
/// <param name="scale">The <see cref="Scale"/>.</param>
/// <param name="value">The value to substract.</param>
/// <returns>A new <see cref="Scale"/> representing the subtraction of the <see cref="Scale"/> and the provided value.</returns>
public static Scale operator -(Scale scale, float value) => new(scale.Horizontal - value, scale.Vertical - value);
/// <summary>
/// Returns a new <see cref="Scale"/> representing the multiplication of the <see cref="Scale"/> and the provided value.
/// </summary>
/// <param name="scale">The <see cref="Scale"/>.</param>
/// <param name="value">The value to multiply with.</param>
/// <returns>A new <see cref="Scale"/> representing the multiplication of the <see cref="Scale"/> and the provided value.</returns>
public static Scale operator *(Scale scale, float value) => new(scale.Horizontal * value, scale.Vertical * value);
/// <summary>
/// Returns a new <see cref="Scale"/> representing the division of the <see cref="Scale"/> and the provided value.
/// </summary>
/// <param name="scale">The <see cref="Scale"/>.</param>
/// <param name="value">The value to device with.</param>
/// <returns>A new <see cref="Scale"/> representing the division of the <see cref="Scale"/> and the provided value.</returns>
public static Scale operator /(Scale scale, float value) => value.EqualsInTolerance(0) ? new Scale(0) : new Scale(scale.Horizontal / value, scale.Vertical / value);
/// <summary>
/// Converts a float to a <see cref="Scale" />.
/// </summary>
/// <param name="scale">The scale value to convert.</param>
public static implicit operator Scale(float scale) => new(scale);
#endregion
}

View File

@ -1,26 +1,25 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Contains a list of different shapes used to define the layout of a LED.
/// </summary>
[Serializable]
public enum Shape
{
/// <summary>
/// Contains a list of different shapes used to define the layout of a LED.
/// A custom shape defined by vector-data.
/// </summary>
[Serializable]
public enum Shape
{
/// <summary>
/// A custom shape defined by vector-data.
/// </summary>
Custom = 0,
Custom = 0,
/// <summary>
/// A simple rectangle.
/// </summary>
Rectangle = 1,
/// <summary>
/// A simple rectangle.
/// </summary>
Rectangle = 1,
/// <summary>
/// A simple circle.
/// </summary>
Circle = 2,
}
}
/// <summary>
/// A simple circle.
/// </summary>
Circle = 2,
}

View File

@ -3,195 +3,194 @@
using System.Diagnostics;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a size consisting of a width and a height.
/// </summary>
[DebuggerDisplay("[Width: {Width}, Height: {Height}]")]
public readonly struct Size
{
#region Constants
private static readonly Size INVALID = new(float.NaN, float.NaN);
/// <summary>
/// Represents a size consisting of a width and a height.
/// Gets a [NaN,NaN]-Size.
/// </summary>
[DebuggerDisplay("[Width: {Width}, Height: {Height}]")]
public readonly struct Size
public static ref readonly Size Invalid => ref INVALID;
#endregion
#region Properties & Fields
/// <summary>
/// Gets or sets the width component value of this <see cref="Size"/>.
/// </summary>
public float Width { get; }
/// <summary>
/// Gets or sets the height component value of this <see cref="Size"/>.
/// </summary>
public float Height { get; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Size" /> using the provided size to define a square.
/// </summary>
/// <param name="size">The size used for the <see cref="P:RGB.NET.Core.Size.Width" /> component value and the <see cref="P:RGB.NET.Core.Size.Height" /> component value.</param>
public Size(float size)
: this(size, size)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Size"/> class using the provided values.
/// </summary>
/// <param name="width">The size used for the <see cref="Width"/> component value.</param>
/// <param name="height">The size used for the <see cref="Height"/> component value.</param>
public Size(float width, float height)
{
#region Constants
private static readonly Size INVALID = new(float.NaN, float.NaN);
/// <summary>
/// Gets a [NaN,NaN]-Size.
/// </summary>
public static ref readonly Size Invalid => ref INVALID;
#endregion
#region Properties & Fields
/// <summary>
/// Gets or sets the width component value of this <see cref="Size"/>.
/// </summary>
public float Width { get; }
/// <summary>
/// Gets or sets the height component value of this <see cref="Size"/>.
/// </summary>
public float Height { get; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Size" /> using the provided size to define a square.
/// </summary>
/// <param name="size">The size used for the <see cref="P:RGB.NET.Core.Size.Width" /> component value and the <see cref="P:RGB.NET.Core.Size.Height" /> component value.</param>
public Size(float size)
: this(size, size)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Size"/> class using the provided values.
/// </summary>
/// <param name="width">The size used for the <see cref="Width"/> component value.</param>
/// <param name="height">The size used for the <see cref="Height"/> component value.</param>
public Size(float width, float height)
{
this.Width = width;
this.Height = height;
}
#endregion
#region Methods
/// <summary>
/// Converts the <see cref="Width"/> and <see cref="Height"/> of this <see cref="Size"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the <see cref="Width"/> and <see cref="Height"/> of this <see cref="Size"/>. For example "[Width: 100, Height: 20]".</returns>
public override string ToString() => $"[Width: {Width}, Height: {Height}]";
/// <summary>
/// Tests whether the specified object is a <see cref="Size" /> and is equivalent to this <see cref="Size" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Size" /> equivalent to this <see cref="Size" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj)
{
if (obj is not Size size) return false;
(float width, float height) = size;
return ((float.IsNaN(Width) && float.IsNaN(width)) || Width.EqualsInTolerance(width))
&& ((float.IsNaN(Height) && float.IsNaN(height)) || Height.EqualsInTolerance(height));
}
/// <summary>
/// Returns a hash code for this <see cref="Size" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Size" />.</returns>
public override int GetHashCode()
{
unchecked
{
int hashCode = Width.GetHashCode();
hashCode = (hashCode * 397) ^ Height.GetHashCode();
return hashCode;
}
}
/// <summary>
/// Deconstructs the size into the width and height value.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public void Deconstruct(out float width, out float height)
{
width = Width;
height = Height;
}
#endregion
#region Operators
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Size" /> are equal.
/// </summary>
/// <param name="size1">The first <see cref="Size" /> to compare.</param>
/// <param name="size2">The second <see cref="Size" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="size1" /> and <paramref name="size2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(in Size size1, in Size size2) => size1.Equals(size2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Size" /> are equal.
/// </summary>
/// <param name="size1">The first <see cref="Size" /> to compare.</param>
/// <param name="size2">The second <see cref="Size" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="size1" /> and <paramref name="size2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(in Size size1, in Size size2) => !(size1 == size2);
/// <summary>
/// Returns a new <see cref="Size"/> representing the addition of the two provided <see cref="Size"/>.
/// </summary>
/// <param name="size1">The first <see cref="Size"/>.</param>
/// <param name="size2">The second <see cref="Size"/>.</param>
/// <returns>A new <see cref="Size"/> representing the addition of the two provided <see cref="Size"/>.</returns>
public static Size operator +(in Size size1, in Size size2) => new(size1.Width + size2.Width, size1.Height + size2.Height);
/// <summary>
/// Returns a new <see cref="Rectangle"/> created from the provided <see cref="Point"/> and <see cref="Size"/>.
/// </summary>
/// <param name="size">The <see cref="Size"/> of the rectangle.</param>
/// <param name="point">The <see cref="Point"/> of the rectangle.</param>
/// <returns>The rectangle created from the provided <see cref="Point"/> and <see cref="Size"/>.</returns>
public static Rectangle operator +(in Size size, in Point point) => new(point, size);
/// <summary>
/// Returns a new <see cref="Size"/> representing the subtraction of the two provided <see cref="Size"/>.
/// </summary>
/// <param name="size1">The first <see cref="Size"/>.</param>
/// <param name="size2">The second <see cref="Size"/>.</param>
/// <returns>A new <see cref="Size"/> representing the subtraction of the two provided <see cref="Size"/>.</returns>
public static Size operator -(in Size size1, in Size size2) => new(size1.Width - size2.Width, size1.Height - size2.Height);
/// <summary>
/// Returns a new <see cref="Size"/> representing the multiplication of the two provided <see cref="Size"/>.
/// </summary>
/// <param name="size1">The first <see cref="Size"/>.</param>
/// <param name="size2">The second <see cref="Size"/>.</param>
/// <returns>A new <see cref="Size"/> representing the multiplication of the two provided <see cref="Size"/>.</returns>
public static Size operator *(in Size size1, in Size size2) => new(size1.Width * size2.Width, size1.Height * size2.Height);
/// <summary>
/// Returns a new <see cref="Size"/> representing the multiplication of the <see cref="Size"/> and the provided factor.
/// </summary>
/// <param name="size">The <see cref="Size"/>.</param>
/// <param name="factor">The factor by which the <see cref="Size"/> should be multiplied.</param>
/// <returns>A new <see cref="Size"/> representing the multiplication of the <see cref="Size"/> and the provided factor.</returns>
public static Size operator *(in Size size, float factor) => new(size.Width * factor, size.Height * factor);
/// <summary>
/// Returns a new <see cref="Size"/> representing the division of the two provided <see cref="Size"/>.
/// </summary>
/// <param name="size1">The first <see cref="Size"/>.</param>
/// <param name="size2">The second <see cref="Size"/>.</param>
/// <returns>A new <see cref="Size"/> representing the division of the two provided <see cref="Size"/>.</returns>
public static Size operator /(in Size size1, in Size size2)
=> size2.Width.EqualsInTolerance(0) || size2.Height.EqualsInTolerance(0)
? Invalid : new Size(size1.Width / size2.Width, size1.Height / size2.Height);
/// <summary>
/// Returns a new <see cref="Size"/> representing the division of the <see cref="Size"/> and the provided factor.
/// </summary>
/// <param name="size">The <see cref="Size"/>.</param>
/// <param name="factor">The factor by which the <see cref="Size"/> should be divided.</param>
/// <returns>A new <see cref="Size"/> representing the division of the <see cref="Size"/> and the provided factor.</returns>
public static Size operator /(in Size size, float factor) => factor.EqualsInTolerance(0) ? Invalid : new Size(size.Width / factor, size.Height / factor);
/// <summary>
/// Returns a new <see cref="Size"/> representing the multiplication of the <see cref="Size"/> and the specified <see cref="Scale"/>.
/// </summary>
/// <param name="size">The <see cref="Size"/> to scale.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>A new <see cref="Size"/> representing the multiplication of the <see cref="Size"/> and the specified <see cref="Scale"/>.</returns>
public static Size operator *(in Size size, in Scale scale) => new(size.Width * scale.Horizontal, size.Height * scale.Vertical);
#endregion
this.Width = width;
this.Height = height;
}
}
#endregion
#region Methods
/// <summary>
/// Converts the <see cref="Width"/> and <see cref="Height"/> of this <see cref="Size"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the <see cref="Width"/> and <see cref="Height"/> of this <see cref="Size"/>. For example "[Width: 100, Height: 20]".</returns>
public override string ToString() => $"[Width: {Width}, Height: {Height}]";
/// <summary>
/// Tests whether the specified object is a <see cref="Size" /> and is equivalent to this <see cref="Size" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Size" /> equivalent to this <see cref="Size" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj)
{
if (obj is not Size size) return false;
(float width, float height) = size;
return ((float.IsNaN(Width) && float.IsNaN(width)) || Width.EqualsInTolerance(width))
&& ((float.IsNaN(Height) && float.IsNaN(height)) || Height.EqualsInTolerance(height));
}
/// <summary>
/// Returns a hash code for this <see cref="Size" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Size" />.</returns>
public override int GetHashCode()
{
unchecked
{
int hashCode = Width.GetHashCode();
hashCode = (hashCode * 397) ^ Height.GetHashCode();
return hashCode;
}
}
/// <summary>
/// Deconstructs the size into the width and height value.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public void Deconstruct(out float width, out float height)
{
width = Width;
height = Height;
}
#endregion
#region Operators
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Size" /> are equal.
/// </summary>
/// <param name="size1">The first <see cref="Size" /> to compare.</param>
/// <param name="size2">The second <see cref="Size" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="size1" /> and <paramref name="size2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(in Size size1, in Size size2) => size1.Equals(size2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Size" /> are equal.
/// </summary>
/// <param name="size1">The first <see cref="Size" /> to compare.</param>
/// <param name="size2">The second <see cref="Size" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="size1" /> and <paramref name="size2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(in Size size1, in Size size2) => !(size1 == size2);
/// <summary>
/// Returns a new <see cref="Size"/> representing the addition of the two provided <see cref="Size"/>.
/// </summary>
/// <param name="size1">The first <see cref="Size"/>.</param>
/// <param name="size2">The second <see cref="Size"/>.</param>
/// <returns>A new <see cref="Size"/> representing the addition of the two provided <see cref="Size"/>.</returns>
public static Size operator +(in Size size1, in Size size2) => new(size1.Width + size2.Width, size1.Height + size2.Height);
/// <summary>
/// Returns a new <see cref="Rectangle"/> created from the provided <see cref="Point"/> and <see cref="Size"/>.
/// </summary>
/// <param name="size">The <see cref="Size"/> of the rectangle.</param>
/// <param name="point">The <see cref="Point"/> of the rectangle.</param>
/// <returns>The rectangle created from the provided <see cref="Point"/> and <see cref="Size"/>.</returns>
public static Rectangle operator +(in Size size, in Point point) => new(point, size);
/// <summary>
/// Returns a new <see cref="Size"/> representing the subtraction of the two provided <see cref="Size"/>.
/// </summary>
/// <param name="size1">The first <see cref="Size"/>.</param>
/// <param name="size2">The second <see cref="Size"/>.</param>
/// <returns>A new <see cref="Size"/> representing the subtraction of the two provided <see cref="Size"/>.</returns>
public static Size operator -(in Size size1, in Size size2) => new(size1.Width - size2.Width, size1.Height - size2.Height);
/// <summary>
/// Returns a new <see cref="Size"/> representing the multiplication of the two provided <see cref="Size"/>.
/// </summary>
/// <param name="size1">The first <see cref="Size"/>.</param>
/// <param name="size2">The second <see cref="Size"/>.</param>
/// <returns>A new <see cref="Size"/> representing the multiplication of the two provided <see cref="Size"/>.</returns>
public static Size operator *(in Size size1, in Size size2) => new(size1.Width * size2.Width, size1.Height * size2.Height);
/// <summary>
/// Returns a new <see cref="Size"/> representing the multiplication of the <see cref="Size"/> and the provided factor.
/// </summary>
/// <param name="size">The <see cref="Size"/>.</param>
/// <param name="factor">The factor by which the <see cref="Size"/> should be multiplied.</param>
/// <returns>A new <see cref="Size"/> representing the multiplication of the <see cref="Size"/> and the provided factor.</returns>
public static Size operator *(in Size size, float factor) => new(size.Width * factor, size.Height * factor);
/// <summary>
/// Returns a new <see cref="Size"/> representing the division of the two provided <see cref="Size"/>.
/// </summary>
/// <param name="size1">The first <see cref="Size"/>.</param>
/// <param name="size2">The second <see cref="Size"/>.</param>
/// <returns>A new <see cref="Size"/> representing the division of the two provided <see cref="Size"/>.</returns>
public static Size operator /(in Size size1, in Size size2)
=> size2.Width.EqualsInTolerance(0) || size2.Height.EqualsInTolerance(0)
? Invalid : new Size(size1.Width / size2.Width, size1.Height / size2.Height);
/// <summary>
/// Returns a new <see cref="Size"/> representing the division of the <see cref="Size"/> and the provided factor.
/// </summary>
/// <param name="size">The <see cref="Size"/>.</param>
/// <param name="factor">The factor by which the <see cref="Size"/> should be divided.</param>
/// <returns>A new <see cref="Size"/> representing the division of the <see cref="Size"/> and the provided factor.</returns>
public static Size operator /(in Size size, float factor) => factor.EqualsInTolerance(0) ? Invalid : new Size(size.Width / factor, size.Height / factor);
/// <summary>
/// Returns a new <see cref="Size"/> representing the multiplication of the <see cref="Size"/> and the specified <see cref="Scale"/>.
/// </summary>
/// <param name="size">The <see cref="Size"/> to scale.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>A new <see cref="Size"/> representing the multiplication of the <see cref="Size"/> and the specified <see cref="Scale"/>.</returns>
public static Size operator *(in Size size, in Scale scale) => new(size.Width * scale.Horizontal, size.Height * scale.Vertical);
#endregion
}

View File

@ -7,380 +7,379 @@ using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc cref="AbstractBindable" />
/// <inheritdoc cref="IDisposable" />
/// <summary>
/// Represents a RGB-surface containing multiple devices.
/// </summary>
public sealed class RGBSurface : AbstractBindable, IDisposable
{
/// <inheritdoc cref="AbstractBindable" />
/// <inheritdoc cref="IDisposable" />
#region Properties & Fields
private readonly Stopwatch _deltaTimeCounter;
private readonly IList<IRGBDevice> _devices = new List<IRGBDevice>();
private readonly IList<IUpdateTrigger> _updateTriggers = new List<IUpdateTrigger>();
private readonly List<ILedGroup> _ledGroups = new();
/// <summary>
/// Represents a RGB-surface containing multiple devices.
/// Gets a readonly list containing all loaded <see cref="IRGBDevice"/>.
/// This collection should be locked when enumerated in a multi-threaded application.
/// </summary>
public sealed class RGBSurface : AbstractBindable, IDisposable
public IReadOnlyList<IRGBDevice> Devices { get; }
/// <summary>
/// Gets a readonly list containing all registered <see cref="IUpdateTrigger"/>.
/// This collection should be locked when enumerated in a multi-threaded application.
/// </summary>
public IReadOnlyList<IUpdateTrigger> UpdateTriggers { get; }
/// <summary>
/// Gets a copy of the <see cref="Rectangle"/> representing this <see cref="RGBSurface"/>.
/// </summary>
public Rectangle Boundary { get; private set; } = new(new Point(0, 0), new Size(0, 0));
/// <summary>
/// Gets a list of all <see cref="Led"/> on this <see cref="RGBSurface"/>.
/// </summary>
public IEnumerable<Led> Leds
{
#region Properties & Fields
private readonly Stopwatch _deltaTimeCounter;
private readonly IList<IRGBDevice> _devices = new List<IRGBDevice>();
private readonly IList<IUpdateTrigger> _updateTriggers = new List<IUpdateTrigger>();
private readonly List<ILedGroup> _ledGroups = new();
/// <summary>
/// Gets a readonly list containing all loaded <see cref="IRGBDevice"/>.
/// This collection should be locked when enumerated in a multi-threaded application.
/// </summary>
public IReadOnlyList<IRGBDevice> Devices { get; }
/// <summary>
/// Gets a readonly list containing all registered <see cref="IUpdateTrigger"/>.
/// This collection should be locked when enumerated in a multi-threaded application.
/// </summary>
public IReadOnlyList<IUpdateTrigger> UpdateTriggers { get; }
/// <summary>
/// Gets a copy of the <see cref="Rectangle"/> representing this <see cref="RGBSurface"/>.
/// </summary>
public Rectangle Boundary { get; private set; } = new(new Point(0, 0), new Size(0, 0));
/// <summary>
/// Gets a list of all <see cref="Led"/> on this <see cref="RGBSurface"/>.
/// </summary>
public IEnumerable<Led> Leds
{
get
{
lock (Devices)
return _devices.SelectMany(x => x);
}
}
#endregion
#region EventHandler
/// <summary>
/// Represents the event-handler of the <see cref="Exception"/>-event.
/// </summary>
/// <param name="args">The arguments provided by the event.</param>
public delegate void ExceptionEventHandler(ExceptionEventArgs args);
/// <summary>
/// Represents the event-handler of the <see cref="Updating"/>-event.
/// </summary>
/// <param name="args">The arguments provided by the event.</param>
public delegate void UpdatingEventHandler(UpdatingEventArgs args);
/// <summary>
/// Represents the event-handler of the <see cref="Updated"/>-event.
/// </summary>
/// <param name="args">The arguments provided by the event.</param>
public delegate void UpdatedEventHandler(UpdatedEventArgs args);
/// <summary>
/// Represents the event-handler of the <see cref="SurfaceLayoutChanged"/>-event.
/// </summary>
/// <param name="args">The arguments provided by the event.</param>
public delegate void SurfaceLayoutChangedEventHandler(SurfaceLayoutChangedEventArgs args);
#endregion
#region Events
// ReSharper disable EventNeverSubscribedTo.Global
/// <summary>
/// Occurs when a catched exception is thrown inside the <see cref="RGBSurface"/>.
/// </summary>
public event ExceptionEventHandler? Exception;
/// <summary>
/// Occurs when the <see cref="RGBSurface"/> starts updating.
/// </summary>
public event UpdatingEventHandler? Updating;
/// <summary>
/// Occurs when the <see cref="RGBSurface"/> update is done.
/// </summary>
public event UpdatedEventHandler? Updated;
/// <summary>
/// Occurs when the layout of this <see cref="RGBSurface"/> changed.
/// </summary>
public event SurfaceLayoutChangedEventHandler? SurfaceLayoutChanged;
// ReSharper restore EventNeverSubscribedTo.Global
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="RGBSurface"/> class.
/// </summary>
public RGBSurface()
{
_deltaTimeCounter = Stopwatch.StartNew();
Devices = new ReadOnlyCollection<IRGBDevice>(_devices);
UpdateTriggers = new ReadOnlyCollection<IUpdateTrigger>(_updateTriggers);
}
#endregion
#region Methods
/// <summary>
/// Perform a full update for all devices. Updates only dirty <see cref="Led"/> by default, or all <see cref="Led"/>, if flushLeds is set to true.
/// </summary>
/// <param name="flushLeds">Specifies whether all <see cref="Led"/>, (including clean ones) should be updated.</param>
public void Update(bool flushLeds = false) => Update(null, new CustomUpdateData(("flushLeds", flushLeds)));
private void Update(object? updateTrigger, CustomUpdateData customData) => Update(updateTrigger as IUpdateTrigger, customData);
private void Update(IUpdateTrigger? updateTrigger, CustomUpdateData customData)
{
try
{
bool flushLeds = customData["flushLeds"] as bool? ?? false;
bool render = customData["render"] as bool? ?? true;
bool updateDevices = customData["updateDevices"] as bool? ?? true;
lock (UpdateTriggers)
lock (Devices)
{
OnUpdating(updateTrigger, customData);
if (render)
lock (_ledGroups)
{
// Render brushes
foreach (ILedGroup ledGroup in _ledGroups)
try { Render(ledGroup); }
catch (Exception ex) { OnException(ex); }
}
if (updateDevices)
foreach (IRGBDevice device in _devices)
try { device.Update(flushLeds); }
catch (Exception ex) { OnException(ex); }
OnUpdated();
}
}
catch (Exception ex)
{
OnException(ex);
}
}
/// <inheritdoc />
public void Dispose()
{
List<IRGBDevice> devices;
lock (Devices)
devices = new List<IRGBDevice>(_devices);
foreach (IRGBDevice device in devices)
try { Detach(device); }
catch { /* We do what we can */}
foreach (IUpdateTrigger updateTrigger in _updateTriggers)
try { updateTrigger.Dispose(); }
catch { /* We do what we can */}
_ledGroups.Clear();
}
/// <summary>
/// Renders a ledgroup.
/// </summary>
/// <param name="ledGroup">The led group to render.</param>
/// <exception cref="ArgumentException">Thrown if the <see cref="IBrush.CalculationMode"/> of the Brush is not valid.</exception>
private void Render(ILedGroup ledGroup)
{
IList<Led> leds = ledGroup.ToList();
IBrush? brush = ledGroup.Brush;
if ((brush == null) || !brush.IsEnabled) return;
IEnumerable<(RenderTarget renderTarget, Color color)> render;
switch (brush.CalculationMode)
{
case RenderMode.Relative:
Rectangle brushRectangle = new(leds.Select(led => led.AbsoluteBoundary));
Point offset = new(-brushRectangle.Location.X, -brushRectangle.Location.Y);
brushRectangle = brushRectangle.SetLocation(new Point(0, 0));
render = brush.Render(brushRectangle, leds.Select(led => new RenderTarget(led, led.AbsoluteBoundary.Translate(offset))));
break;
case RenderMode.Absolute:
render = brush.Render(Boundary, leds.Select(led => new RenderTarget(led, led.AbsoluteBoundary)));
break;
default:
throw new ArgumentException($"The CalculationMode '{brush.CalculationMode}' is not valid.");
}
foreach ((RenderTarget renderTarget, Color c) in render)
renderTarget.Led.Color = c;
}
/// <summary>
/// Attaches the specified <see cref="ILedGroup"/>.
/// </summary>
/// <param name="ledGroup">The <see cref="ILedGroup"/> to attach.</param>
/// <returns><c>true</c> if the <see cref="ILedGroup"/> could be attached; otherwise, <c>false</c>.</returns>
public bool Attach(ILedGroup ledGroup)
{
lock (_ledGroups)
{
if (ledGroup.Surface != null) return false;
ledGroup.Surface = this;
_ledGroups.Add(ledGroup);
_ledGroups.Sort((group1, group2) => group1.ZIndex.CompareTo(group2.ZIndex));
ledGroup.OnAttach();
return true;
}
}
/// <summary>
/// Detaches the specified <see cref="ILedGroup"/>.
/// </summary>
/// <param name="ledGroup">The <see cref="ILedGroup"/> to detache.</param>
/// <returns><c>true</c> if the <see cref="ILedGroup"/> could be detached; <c>false</c> otherwise.</returns>
public bool Detach(ILedGroup ledGroup)
{
lock (_ledGroups)
{
if (!_ledGroups.Remove(ledGroup)) return false;
ledGroup.OnDetach();
ledGroup.Surface = null;
return true;
}
}
/// <summary>
/// Attaches the specified <see cref="IRGBDevice"/>.
/// </summary>
/// <param name="device">The <see cref="IRGBDevice"/> to attach.</param>
public void Attach(IRGBDevice device)
get
{
lock (Devices)
{
if (string.IsNullOrWhiteSpace(device.DeviceInfo.DeviceName)) throw new RGBDeviceException($"The device '{device.DeviceInfo.Manufacturer} {device.DeviceInfo.Model}' has no valid name.");
if (device.Surface != null) throw new RGBSurfaceException($"The device '{device.DeviceInfo.DeviceName}' is already attached to a surface.");
device.Surface = this;
device.BoundaryChanged += DeviceOnBoundaryChanged;
_devices.Add(device);
OnSurfaceLayoutChanged(SurfaceLayoutChangedEventArgs.FromAddedDevice(device));
}
return _devices.SelectMany(x => x);
}
/// <summary>
/// Detaches the specified <see cref="IRGBDevice"/>.
/// </summary>
/// <param name="device">The <see cref="IRGBDevice"/> to detache.</param>
/// <returns><c>true</c> if the <see cref="IRGBDevice"/> could be detached; <c>false</c> otherwise.</returns>
public void Detach(IRGBDevice device)
{
lock (Devices)
{
if (!_devices.Contains(device)) throw new RGBSurfaceException($"The device '{device.DeviceInfo.DeviceName}' is not attached to this surface.");
device.BoundaryChanged -= DeviceOnBoundaryChanged;
device.Surface = null;
_devices.Remove(device);
OnSurfaceLayoutChanged(SurfaceLayoutChangedEventArgs.FromRemovedDevice(device));
}
}
// ReSharper restore UnusedMember.Global
private void DeviceOnBoundaryChanged(object? sender, EventArgs args)
=> OnSurfaceLayoutChanged((sender is IRGBDevice device) ? SurfaceLayoutChangedEventArgs.FromChangedDevice(device) : SurfaceLayoutChangedEventArgs.Misc());
private void OnSurfaceLayoutChanged(SurfaceLayoutChangedEventArgs args)
{
UpdateSurfaceRectangle();
SurfaceLayoutChanged?.Invoke(args);
}
private void UpdateSurfaceRectangle()
{
lock (Devices)
{
Rectangle devicesRectangle = new(_devices.Select(d => d.Boundary));
Boundary = Boundary.SetSize(new Size(devicesRectangle.Location.X + devicesRectangle.Size.Width, devicesRectangle.Location.Y + devicesRectangle.Size.Height));
}
}
/// <summary>
/// Registers the provided <see cref="IUpdateTrigger"/>.
/// </summary>
/// <param name="updateTrigger">The <see cref="IUpdateTrigger"/> to register.</param>
public void RegisterUpdateTrigger(IUpdateTrigger updateTrigger)
{
if (!_updateTriggers.Contains(updateTrigger))
{
_updateTriggers.Add(updateTrigger);
updateTrigger.Update += Update;
}
}
/// <summary>
/// Unregisters the provided <see cref="IUpdateTrigger"/>.
/// </summary>
/// <param name="updateTrigger">The <see cref="IUpdateTrigger"/> to unregister.</param>
public void UnregisterUpdateTrigger(IUpdateTrigger updateTrigger)
{
if (_updateTriggers.Remove(updateTrigger))
updateTrigger.Update -= Update;
}
/// <summary>
/// Handles the needed event-calls for an exception.
/// </summary>
/// <param name="ex">The exception previously thrown.</param>
private void OnException(Exception ex)
{
try
{
Exception?.Invoke(new ExceptionEventArgs(ex));
}
catch { /* Well ... that's not my fault */ }
}
/// <summary>
/// Handles the needed event-calls before updating.
/// </summary>
private void OnUpdating(IUpdateTrigger? trigger, CustomUpdateData customData)
{
try
{
double deltaTime = _deltaTimeCounter.Elapsed.TotalSeconds;
_deltaTimeCounter.Restart();
Updating?.Invoke(new UpdatingEventArgs(deltaTime, trigger, customData));
}
catch { /* Well ... that's not my fault */ }
}
/// <summary>
/// Handles the needed event-calls after an update.
/// </summary>
private void OnUpdated()
{
try
{
Updated?.Invoke(new UpdatedEventArgs());
}
catch { /* Well ... that's not my fault */ }
}
#endregion
}
}
#endregion
#region EventHandler
/// <summary>
/// Represents the event-handler of the <see cref="Exception"/>-event.
/// </summary>
/// <param name="args">The arguments provided by the event.</param>
public delegate void ExceptionEventHandler(ExceptionEventArgs args);
/// <summary>
/// Represents the event-handler of the <see cref="Updating"/>-event.
/// </summary>
/// <param name="args">The arguments provided by the event.</param>
public delegate void UpdatingEventHandler(UpdatingEventArgs args);
/// <summary>
/// Represents the event-handler of the <see cref="Updated"/>-event.
/// </summary>
/// <param name="args">The arguments provided by the event.</param>
public delegate void UpdatedEventHandler(UpdatedEventArgs args);
/// <summary>
/// Represents the event-handler of the <see cref="SurfaceLayoutChanged"/>-event.
/// </summary>
/// <param name="args">The arguments provided by the event.</param>
public delegate void SurfaceLayoutChangedEventHandler(SurfaceLayoutChangedEventArgs args);
#endregion
#region Events
// ReSharper disable EventNeverSubscribedTo.Global
/// <summary>
/// Occurs when a catched exception is thrown inside the <see cref="RGBSurface"/>.
/// </summary>
public event ExceptionEventHandler? Exception;
/// <summary>
/// Occurs when the <see cref="RGBSurface"/> starts updating.
/// </summary>
public event UpdatingEventHandler? Updating;
/// <summary>
/// Occurs when the <see cref="RGBSurface"/> update is done.
/// </summary>
public event UpdatedEventHandler? Updated;
/// <summary>
/// Occurs when the layout of this <see cref="RGBSurface"/> changed.
/// </summary>
public event SurfaceLayoutChangedEventHandler? SurfaceLayoutChanged;
// ReSharper restore EventNeverSubscribedTo.Global
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="RGBSurface"/> class.
/// </summary>
public RGBSurface()
{
_deltaTimeCounter = Stopwatch.StartNew();
Devices = new ReadOnlyCollection<IRGBDevice>(_devices);
UpdateTriggers = new ReadOnlyCollection<IUpdateTrigger>(_updateTriggers);
}
#endregion
#region Methods
/// <summary>
/// Perform a full update for all devices. Updates only dirty <see cref="Led"/> by default, or all <see cref="Led"/>, if flushLeds is set to true.
/// </summary>
/// <param name="flushLeds">Specifies whether all <see cref="Led"/>, (including clean ones) should be updated.</param>
public void Update(bool flushLeds = false) => Update(null, new CustomUpdateData(("flushLeds", flushLeds)));
private void Update(object? updateTrigger, CustomUpdateData customData) => Update(updateTrigger as IUpdateTrigger, customData);
private void Update(IUpdateTrigger? updateTrigger, CustomUpdateData customData)
{
try
{
bool flushLeds = customData["flushLeds"] as bool? ?? false;
bool render = customData["render"] as bool? ?? true;
bool updateDevices = customData["updateDevices"] as bool? ?? true;
lock (UpdateTriggers)
lock (Devices)
{
OnUpdating(updateTrigger, customData);
if (render)
lock (_ledGroups)
{
// Render brushes
foreach (ILedGroup ledGroup in _ledGroups)
try { Render(ledGroup); }
catch (Exception ex) { OnException(ex); }
}
if (updateDevices)
foreach (IRGBDevice device in _devices)
try { device.Update(flushLeds); }
catch (Exception ex) { OnException(ex); }
OnUpdated();
}
}
catch (Exception ex)
{
OnException(ex);
}
}
/// <inheritdoc />
public void Dispose()
{
List<IRGBDevice> devices;
lock (Devices)
devices = new List<IRGBDevice>(_devices);
foreach (IRGBDevice device in devices)
try { Detach(device); }
catch { /* We do what we can */}
foreach (IUpdateTrigger updateTrigger in _updateTriggers)
try { updateTrigger.Dispose(); }
catch { /* We do what we can */}
_ledGroups.Clear();
}
/// <summary>
/// Renders a ledgroup.
/// </summary>
/// <param name="ledGroup">The led group to render.</param>
/// <exception cref="ArgumentException">Thrown if the <see cref="IBrush.CalculationMode"/> of the Brush is not valid.</exception>
private void Render(ILedGroup ledGroup)
{
IList<Led> leds = ledGroup.ToList();
IBrush? brush = ledGroup.Brush;
if ((brush == null) || !brush.IsEnabled) return;
IEnumerable<(RenderTarget renderTarget, Color color)> render;
switch (brush.CalculationMode)
{
case RenderMode.Relative:
Rectangle brushRectangle = new(leds.Select(led => led.AbsoluteBoundary));
Point offset = new(-brushRectangle.Location.X, -brushRectangle.Location.Y);
brushRectangle = brushRectangle.SetLocation(new Point(0, 0));
render = brush.Render(brushRectangle, leds.Select(led => new RenderTarget(led, led.AbsoluteBoundary.Translate(offset))));
break;
case RenderMode.Absolute:
render = brush.Render(Boundary, leds.Select(led => new RenderTarget(led, led.AbsoluteBoundary)));
break;
default:
throw new ArgumentException($"The CalculationMode '{brush.CalculationMode}' is not valid.");
}
foreach ((RenderTarget renderTarget, Color c) in render)
renderTarget.Led.Color = c;
}
/// <summary>
/// Attaches the specified <see cref="ILedGroup"/>.
/// </summary>
/// <param name="ledGroup">The <see cref="ILedGroup"/> to attach.</param>
/// <returns><c>true</c> if the <see cref="ILedGroup"/> could be attached; otherwise, <c>false</c>.</returns>
public bool Attach(ILedGroup ledGroup)
{
lock (_ledGroups)
{
if (ledGroup.Surface != null) return false;
ledGroup.Surface = this;
_ledGroups.Add(ledGroup);
_ledGroups.Sort((group1, group2) => group1.ZIndex.CompareTo(group2.ZIndex));
ledGroup.OnAttach();
return true;
}
}
/// <summary>
/// Detaches the specified <see cref="ILedGroup"/>.
/// </summary>
/// <param name="ledGroup">The <see cref="ILedGroup"/> to detache.</param>
/// <returns><c>true</c> if the <see cref="ILedGroup"/> could be detached; <c>false</c> otherwise.</returns>
public bool Detach(ILedGroup ledGroup)
{
lock (_ledGroups)
{
if (!_ledGroups.Remove(ledGroup)) return false;
ledGroup.OnDetach();
ledGroup.Surface = null;
return true;
}
}
/// <summary>
/// Attaches the specified <see cref="IRGBDevice"/>.
/// </summary>
/// <param name="device">The <see cref="IRGBDevice"/> to attach.</param>
public void Attach(IRGBDevice device)
{
lock (Devices)
{
if (string.IsNullOrWhiteSpace(device.DeviceInfo.DeviceName)) throw new RGBDeviceException($"The device '{device.DeviceInfo.Manufacturer} {device.DeviceInfo.Model}' has no valid name.");
if (device.Surface != null) throw new RGBSurfaceException($"The device '{device.DeviceInfo.DeviceName}' is already attached to a surface.");
device.Surface = this;
device.BoundaryChanged += DeviceOnBoundaryChanged;
_devices.Add(device);
OnSurfaceLayoutChanged(SurfaceLayoutChangedEventArgs.FromAddedDevice(device));
}
}
/// <summary>
/// Detaches the specified <see cref="IRGBDevice"/>.
/// </summary>
/// <param name="device">The <see cref="IRGBDevice"/> to detache.</param>
/// <returns><c>true</c> if the <see cref="IRGBDevice"/> could be detached; <c>false</c> otherwise.</returns>
public void Detach(IRGBDevice device)
{
lock (Devices)
{
if (!_devices.Contains(device)) throw new RGBSurfaceException($"The device '{device.DeviceInfo.DeviceName}' is not attached to this surface.");
device.BoundaryChanged -= DeviceOnBoundaryChanged;
device.Surface = null;
_devices.Remove(device);
OnSurfaceLayoutChanged(SurfaceLayoutChangedEventArgs.FromRemovedDevice(device));
}
}
// ReSharper restore UnusedMember.Global
private void DeviceOnBoundaryChanged(object? sender, EventArgs args)
=> OnSurfaceLayoutChanged((sender is IRGBDevice device) ? SurfaceLayoutChangedEventArgs.FromChangedDevice(device) : SurfaceLayoutChangedEventArgs.Misc());
private void OnSurfaceLayoutChanged(SurfaceLayoutChangedEventArgs args)
{
UpdateSurfaceRectangle();
SurfaceLayoutChanged?.Invoke(args);
}
private void UpdateSurfaceRectangle()
{
lock (Devices)
{
Rectangle devicesRectangle = new(_devices.Select(d => d.Boundary));
Boundary = Boundary.SetSize(new Size(devicesRectangle.Location.X + devicesRectangle.Size.Width, devicesRectangle.Location.Y + devicesRectangle.Size.Height));
}
}
/// <summary>
/// Registers the provided <see cref="IUpdateTrigger"/>.
/// </summary>
/// <param name="updateTrigger">The <see cref="IUpdateTrigger"/> to register.</param>
public void RegisterUpdateTrigger(IUpdateTrigger updateTrigger)
{
if (!_updateTriggers.Contains(updateTrigger))
{
_updateTriggers.Add(updateTrigger);
updateTrigger.Update += Update;
}
}
/// <summary>
/// Unregisters the provided <see cref="IUpdateTrigger"/>.
/// </summary>
/// <param name="updateTrigger">The <see cref="IUpdateTrigger"/> to unregister.</param>
public void UnregisterUpdateTrigger(IUpdateTrigger updateTrigger)
{
if (_updateTriggers.Remove(updateTrigger))
updateTrigger.Update -= Update;
}
/// <summary>
/// Handles the needed event-calls for an exception.
/// </summary>
/// <param name="ex">The exception previously thrown.</param>
private void OnException(Exception ex)
{
try
{
Exception?.Invoke(new ExceptionEventArgs(ex));
}
catch { /* Well ... that's not my fault */ }
}
/// <summary>
/// Handles the needed event-calls before updating.
/// </summary>
private void OnUpdating(IUpdateTrigger? trigger, CustomUpdateData customData)
{
try
{
double deltaTime = _deltaTimeCounter.Elapsed.TotalSeconds;
_deltaTimeCounter.Restart();
Updating?.Invoke(new UpdatingEventArgs(deltaTime, trigger, customData));
}
catch { /* Well ... that's not my fault */ }
}
/// <summary>
/// Handles the needed event-calls after an update.
/// </summary>
private void OnUpdated()
{
try
{
Updated?.Invoke(new UpdatedEventArgs());
}
catch { /* Well ... that's not my fault */ }
}
#endregion
}

View File

@ -4,106 +4,105 @@
using System.Collections.Generic;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc cref="AbstractDecoratable{T}" />
/// <inheritdoc cref="IBrush" />
/// <summary>
/// Represents a basic brush.
/// </summary>
public abstract class AbstractBrush : AbstractDecoratable<IBrushDecorator>, IBrush
{
/// <inheritdoc cref="AbstractDecoratable{T}" />
/// <inheritdoc cref="IBrush" />
#region Properties & Fields
/// <inheritdoc />
public bool IsEnabled { get; set; } = true;
/// <inheritdoc />
public RenderMode CalculationMode { get; set; } = RenderMode.Relative;
/// <inheritdoc />
public float Brightness { get; set; }
/// <inheritdoc />
public float Opacity { get; set; }
#endregion
#region Constructors
/// <summary>
/// Represents a basic brush.
/// Initializes a new instance of the <see cref="AbstractBrush"/> class.
/// </summary>
public abstract class AbstractBrush : AbstractDecoratable<IBrushDecorator>, IBrush
/// <param name="brightness">The overall percentage brightness of the brush. (default: 1.0)</param>
/// <param name="opacity">The overall percentage opacity of the brush. (default: 1.0)</param>
protected AbstractBrush(float brightness = 1, float opacity = 1)
{
#region Properties & Fields
/// <inheritdoc />
public bool IsEnabled { get; set; } = true;
/// <inheritdoc />
public RenderMode CalculationMode { get; set; } = RenderMode.Relative;
/// <inheritdoc />
public float Brightness { get; set; }
/// <inheritdoc />
public float Opacity { get; set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractBrush"/> class.
/// </summary>
/// <param name="brightness">The overall percentage brightness of the brush. (default: 1.0)</param>
/// <param name="opacity">The overall percentage opacity of the brush. (default: 1.0)</param>
protected AbstractBrush(float brightness = 1, float opacity = 1)
{
this.Brightness = brightness;
this.Opacity = opacity;
}
#endregion
#region Methods
/// <summary>
/// Renders the brush to the specified list of <see cref="RenderTarget"/>.
/// </summary>
/// <param name="rectangle">The bounding box the brush is rendered in.</param>
/// <param name="renderTargets">The targets to render to.</param>
/// <returns>A enumerable containing the rendered <see cref="Color"/> for each <see cref="RenderTarget"/>.</returns>
public virtual IEnumerable<(RenderTarget renderTarget, Color color)> Render(Rectangle rectangle, IEnumerable<RenderTarget> renderTargets)
{
foreach (RenderTarget renderTarget in renderTargets)
{
Color color = GetColorAtPoint(rectangle, renderTarget);
ApplyDecorators(rectangle, renderTarget, ref color);
FinalizeColor(ref color);
yield return (renderTarget, color);
}
}
/// <summary>
/// Applies all attached and enabled decorators to the brush.
/// </summary>
/// <param name="rectangle">The rectangle in which the brush should be drawn.</param>
/// <param name="renderTarget">The target (key/point) from which the color should be taken.</param>
/// <param name="color">The <see cref="Color"/> to be modified.</param>
protected virtual void ApplyDecorators(in Rectangle rectangle, in RenderTarget renderTarget, ref Color color)
{
if (Decorators.Count == 0) return;
lock (Decorators)
foreach (IBrushDecorator decorator in Decorators)
if (decorator.IsEnabled)
decorator.ManipulateColor(rectangle, renderTarget, ref color);
}
/// <summary>
/// Gets the color at an specific point assuming the brush is drawn into the specified rectangle.
/// </summary>
/// <param name="rectangle">The rectangle in which the brush should be drawn.</param>
/// <param name="renderTarget">The target (key/point) from which the color should be taken.</param>
/// <returns>The color at the specified point.</returns>
protected abstract Color GetColorAtPoint(in Rectangle rectangle, in RenderTarget renderTarget);
/// <summary>
/// Finalizes the color by appliing the overall brightness and opacity.<br/>
/// </summary>
/// <param name="color">The color to finalize.</param>
/// <returns>The finalized color.</returns>
protected virtual void FinalizeColor(ref Color color)
{
// Since we use HSV to calculate there is no way to make a color 'brighter' than 100%
// Be carefull with the naming: Since we use HSV the correct term is 'value' but outside we call it 'brightness'
// THIS IS NOT A HSB CALCULATION!!!
if (Brightness < 1)
color = color.MultiplyHSV(value: Brightness.Clamp(0, 1));
if (Opacity < 1)
color = color.MultiplyA(Opacity.Clamp(0, 1));
}
#endregion
this.Brightness = brightness;
this.Opacity = opacity;
}
}
#endregion
#region Methods
/// <summary>
/// Renders the brush to the specified list of <see cref="RenderTarget"/>.
/// </summary>
/// <param name="rectangle">The bounding box the brush is rendered in.</param>
/// <param name="renderTargets">The targets to render to.</param>
/// <returns>A enumerable containing the rendered <see cref="Color"/> for each <see cref="RenderTarget"/>.</returns>
public virtual IEnumerable<(RenderTarget renderTarget, Color color)> Render(Rectangle rectangle, IEnumerable<RenderTarget> renderTargets)
{
foreach (RenderTarget renderTarget in renderTargets)
{
Color color = GetColorAtPoint(rectangle, renderTarget);
ApplyDecorators(rectangle, renderTarget, ref color);
FinalizeColor(ref color);
yield return (renderTarget, color);
}
}
/// <summary>
/// Applies all attached and enabled decorators to the brush.
/// </summary>
/// <param name="rectangle">The rectangle in which the brush should be drawn.</param>
/// <param name="renderTarget">The target (key/point) from which the color should be taken.</param>
/// <param name="color">The <see cref="Color"/> to be modified.</param>
protected virtual void ApplyDecorators(in Rectangle rectangle, in RenderTarget renderTarget, ref Color color)
{
if (Decorators.Count == 0) return;
lock (Decorators)
foreach (IBrushDecorator decorator in Decorators)
if (decorator.IsEnabled)
decorator.ManipulateColor(rectangle, renderTarget, ref color);
}
/// <summary>
/// Gets the color at an specific point assuming the brush is drawn into the specified rectangle.
/// </summary>
/// <param name="rectangle">The rectangle in which the brush should be drawn.</param>
/// <param name="renderTarget">The target (key/point) from which the color should be taken.</param>
/// <returns>The color at the specified point.</returns>
protected abstract Color GetColorAtPoint(in Rectangle rectangle, in RenderTarget renderTarget);
/// <summary>
/// Finalizes the color by appliing the overall brightness and opacity.<br/>
/// </summary>
/// <param name="color">The color to finalize.</param>
/// <returns>The finalized color.</returns>
protected virtual void FinalizeColor(ref Color color)
{
// Since we use HSV to calculate there is no way to make a color 'brighter' than 100%
// Be carefull with the naming: Since we use HSV the correct term is 'value' but outside we call it 'brightness'
// THIS IS NOT A HSB CALCULATION!!!
if (Brightness < 1)
color = color.MultiplyHSV(value: Brightness.Clamp(0, 1));
if (Opacity < 1)
color = color.MultiplyA(Opacity.Clamp(0, 1));
}
#endregion
}

View File

@ -4,38 +4,37 @@
using System.Collections.Generic;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a basic brush.
/// </summary>
public interface IBrush : IDecoratable<IBrushDecorator>
{
/// <summary>
/// Represents a basic brush.
/// Gets or sets if the <see cref="IBrush"/> is enabled and will be drawn on an update.
/// </summary>
public interface IBrush : IDecoratable<IBrushDecorator>
{
/// <summary>
/// Gets or sets if the <see cref="IBrush"/> is enabled and will be drawn on an update.
/// </summary>
bool IsEnabled { get; set; }
bool IsEnabled { get; set; }
/// <summary>
/// Gets or sets the calculation mode used for the rectangle/points used for color-selection in brushes.
/// </summary>
RenderMode CalculationMode { get; set; }
/// <summary>
/// Gets or sets the calculation mode used for the rectangle/points used for color-selection in brushes.
/// </summary>
RenderMode CalculationMode { get; set; }
/// <summary>
/// Gets or sets the overall percentage brightness of the <see cref="IBrush"/>.
/// </summary>
float Brightness { get; set; }
/// <summary>
/// Gets or sets the overall percentage brightness of the <see cref="IBrush"/>.
/// </summary>
float Brightness { get; set; }
/// <summary>
/// Gets or sets the overall percentage opacity of the <see cref="IBrush"/>.
/// </summary>
float Opacity { get; set; }
/// <summary>
/// Gets or sets the overall percentage opacity of the <see cref="IBrush"/>.
/// </summary>
float Opacity { get; set; }
/// <summary>
/// Performs the render pass of the <see cref="IBrush"/> and calculates the raw <see cref="Color"/> for all requested <see cref="RenderTarget"/>.
/// </summary>
/// <param name="rectangle">The <see cref="Rectangle"/> in which the brush should be drawn.</param>
/// <param name="renderTargets">The <see cref="RenderTarget"/> (keys/points) of which the color should be calculated.</param>
IEnumerable<(RenderTarget renderTarget, Color color)> Render(Rectangle rectangle, IEnumerable<RenderTarget> renderTargets);
}
/// <summary>
/// Performs the render pass of the <see cref="IBrush"/> and calculates the raw <see cref="Color"/> for all requested <see cref="RenderTarget"/>.
/// </summary>
/// <param name="rectangle">The <see cref="Rectangle"/> in which the brush should be drawn.</param>
/// <param name="renderTargets">The <see cref="RenderTarget"/> (keys/points) of which the color should be calculated.</param>
IEnumerable<(RenderTarget renderTarget, Color color)> Render(Rectangle rectangle, IEnumerable<RenderTarget> renderTargets);
}

View File

@ -1,62 +1,61 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents a brush drawing only a single color.
/// </summary>
public class SolidColorBrush : AbstractBrush
{
/// <inheritdoc />
#region Properties & Fields
private Color _color;
/// <summary>
/// Represents a brush drawing only a single color.
/// Gets or sets the <see cref="Color"/> drawn by this <see cref="SolidColorBrush"/>.
/// </summary>
public class SolidColorBrush : AbstractBrush
public Color Color
{
#region Properties & Fields
private Color _color;
/// <summary>
/// Gets or sets the <see cref="Color"/> drawn by this <see cref="SolidColorBrush"/>.
/// </summary>
public Color Color
{
get => _color;
set => SetProperty(ref _color, value);
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.SolidColorBrush" /> class.
/// </summary>
/// <param name="color">The <see cref="P:RGB.NET.Core.SolidColorBrush.Color" /> drawn by this <see cref="T:RGB.NET.Core.SolidColorBrush" />.</param>
public SolidColorBrush(Color color)
{
this.Color = color;
}
#endregion
#region Methods
/// <inheritdoc />
protected override Color GetColorAtPoint(in Rectangle rectangle, in RenderTarget renderTarget) => Color;
#endregion
#region Operators
/// <summary>
/// Converts a <see cref="Color" /> to a <see cref="SolidColorBrush" />.
/// </summary>
/// <param name="color">The <see cref="Color"/> to convert.</param>
public static explicit operator SolidColorBrush(Color color) => new(color);
/// <summary>
/// Converts a <see cref="SolidColorBrush" /> to a <see cref="Color" />.
/// </summary>
/// <param name="brush">The <see cref="Color"/> to convert.</param>
public static implicit operator Color(SolidColorBrush brush) => brush.Color;
#endregion
get => _color;
set => SetProperty(ref _color, value);
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.SolidColorBrush" /> class.
/// </summary>
/// <param name="color">The <see cref="P:RGB.NET.Core.SolidColorBrush.Color" /> drawn by this <see cref="T:RGB.NET.Core.SolidColorBrush" />.</param>
public SolidColorBrush(Color color)
{
this.Color = color;
}
#endregion
#region Methods
/// <inheritdoc />
protected override Color GetColorAtPoint(in Rectangle rectangle, in RenderTarget renderTarget) => Color;
#endregion
#region Operators
/// <summary>
/// Converts a <see cref="Color" /> to a <see cref="SolidColorBrush" />.
/// </summary>
/// <param name="color">The <see cref="Color"/> to convert.</param>
public static explicit operator SolidColorBrush(Color color) => new(color);
/// <summary>
/// Converts a <see cref="SolidColorBrush" /> to a <see cref="Color" />.
/// </summary>
/// <param name="brush">The <see cref="Color"/> to convert.</param>
public static implicit operator Color(SolidColorBrush brush) => brush.Color;
#endregion
}

View File

@ -1,47 +1,46 @@
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents a brush drawing a texture.
/// </summary>
public class TextureBrush : AbstractBrush
{
/// <inheritdoc />
#region Properties & Fields
private ITexture _texture = ITexture.Empty;
/// <summary>
/// Represents a brush drawing a texture.
/// Gets or sets the texture drawn by this brush.
/// </summary>
public class TextureBrush : AbstractBrush
public ITexture Texture
{
#region Properties & Fields
private ITexture _texture = ITexture.Empty;
/// <summary>
/// Gets or sets the texture drawn by this brush.
/// </summary>
public ITexture Texture
{
get => _texture;
set => SetProperty(ref _texture, value);
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="TextureBrush" /> class.
/// </summary>
/// <param name="texture">The texture drawn by this brush.</param>
public TextureBrush(ITexture texture)
{
this.Texture = texture;
}
#endregion
#region Methods
/// <inheritdoc />
protected override Color GetColorAtPoint(in Rectangle rectangle, in RenderTarget renderTarget)
{
Rectangle normalizedRect = renderTarget.Rectangle / rectangle;
return Texture[normalizedRect];
}
#endregion
get => _texture;
set => SetProperty(ref _texture, value);
}
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="TextureBrush" /> class.
/// </summary>
/// <param name="texture">The texture drawn by this brush.</param>
public TextureBrush(ITexture texture)
{
this.Texture = texture;
}
#endregion
#region Methods
/// <inheritdoc />
protected override Color GetColorAtPoint(in Rectangle rectangle, in RenderTarget renderTarget)
{
Rectangle normalizedRect = renderTarget.Rectangle / rectangle;
return Texture[normalizedRect];
}
#endregion
}

View File

@ -1,20 +1,19 @@
// ReSharper disable UnusedMember.Global
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Contains a list of all brush calculation modes.
/// </summary>
public enum RenderMode
{
/// <summary>
/// Contains a list of all brush calculation modes.
/// The calculation <see cref="Rectangle"/> for <see cref="IBrush"/> will be the rectangle around the <see cref="ILedGroup"/> the <see cref="IBrush"/> is applied to.
/// </summary>
public enum RenderMode
{
/// <summary>
/// The calculation <see cref="Rectangle"/> for <see cref="IBrush"/> will be the rectangle around the <see cref="ILedGroup"/> the <see cref="IBrush"/> is applied to.
/// </summary>
Relative,
Relative,
/// <summary>
/// The calculation <see cref="Rectangle"/> for <see cref="IBrush"/> will always be the whole <see cref="RGBSurface"/>.
/// </summary>
Absolute
}
}
/// <summary>
/// The calculation <see cref="Rectangle"/> for <see cref="IBrush"/> will always be the whole <see cref="RGBSurface"/>.
/// </summary>
Absolute
}

View File

@ -1,45 +1,44 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a single target of a brush render.
/// </summary>
public readonly struct RenderTarget
{
#region Properties & Fields
/// <summary>
/// Represents a single target of a brush render.
/// Gets the target-<see cref="Core.Led"/>.
/// </summary>
public readonly struct RenderTarget
public Led Led { get; }
/// <summary>
/// Gets the <see cref="Core.Rectangle"/> representing the area to render the target-<see cref="Core.Led"/>.
/// </summary>
public Rectangle Rectangle { get; }
/// <summary>
/// Gets the <see cref="Core.Point"/> representing the position to render the target-<see cref="Core.Led"/>.
/// </summary>
public Point Point => Rectangle.Center;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="RenderTarget"/> class.
/// </summary>
/// <param name="led">The target-<see cref="Core.Led"/>.</param>
/// <param name="rectangle">The <see cref="Core.Rectangle"/> representing the area to render the target-<see cref="Core.Led"/>.</param>
public RenderTarget(Led led, Rectangle rectangle)
{
#region Properties & Fields
/// <summary>
/// Gets the target-<see cref="Core.Led"/>.
/// </summary>
public Led Led { get; }
/// <summary>
/// Gets the <see cref="Core.Rectangle"/> representing the area to render the target-<see cref="Core.Led"/>.
/// </summary>
public Rectangle Rectangle { get; }
/// <summary>
/// Gets the <see cref="Core.Point"/> representing the position to render the target-<see cref="Core.Led"/>.
/// </summary>
public Point Point => Rectangle.Center;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="RenderTarget"/> class.
/// </summary>
/// <param name="led">The target-<see cref="Core.Led"/>.</param>
/// <param name="rectangle">The <see cref="Core.Rectangle"/> representing the area to render the target-<see cref="Core.Led"/>.</param>
public RenderTarget(Led led, Rectangle rectangle)
{
this.Led = led;
this.Rectangle = rectangle;
}
#endregion
this.Led = led;
this.Rectangle = rectangle;
}
}
#endregion
}

View File

@ -1,13 +1,12 @@
namespace RGB.NET.Core
namespace RGB.NET.Core;
internal class EmptyTexture : ITexture
{
internal class EmptyTexture : ITexture
{
#region Properties & Fields
#region Properties & Fields
public Size Size { get; } = new(0, 0);
public Color this[in Point point] => Color.Transparent;
public Color this[in Rectangle rectangle] => Color.Transparent;
public Size Size { get; } = new(0, 0);
public Color this[in Point point] => Color.Transparent;
public Color this[in Rectangle rectangle] => Color.Transparent;
#endregion
}
}
#endregion
}

View File

@ -1,32 +1,31 @@
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic texture.
/// </summary>
public interface ITexture
{
/// <summary>
/// Represents a generic texture.
/// Gets a empty texture.
/// </summary>
public interface ITexture
{
/// <summary>
/// Gets a empty texture.
/// </summary>
static ITexture Empty => new EmptyTexture();
static ITexture Empty => new EmptyTexture();
/// <summary>
/// Gets the size of the texture
/// </summary>
Size Size { get; }
/// <summary>
/// Gets the size of the texture
/// </summary>
Size Size { get; }
/// <summary>
/// Gets the color at the specified location.
/// </summary>
/// <param name="point">The location to get the color from.</param>
/// <returns>The color at the specified location.</returns>
Color this[in Point point] { get; }
/// <summary>
/// Gets the color at the specified location.
/// </summary>
/// <param name="point">The location to get the color from.</param>
/// <returns>The color at the specified location.</returns>
Color this[in Point point] { get; }
/// <summary>
/// Gets the sampled color inside the specified rectangle.
/// </summary>
/// <param name="rectangle">The rectangle to get the color from.</param>
/// <returns>The sampled color.</returns>
Color this[in Rectangle rectangle] { get; }
}
}
/// <summary>
/// Gets the sampled color inside the specified rectangle.
/// </summary>
/// <param name="rectangle">The rectangle to get the color from.</param>
/// <returns>The sampled color.</returns>
Color this[in Rectangle rectangle] { get; }
}

View File

@ -2,228 +2,227 @@
using System.Buffers;
using System.Runtime.CompilerServices;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents a texture made of pixels (like a common image).
/// </summary>
/// <typeparam name="T">The type of the pixels.</typeparam>
public abstract class PixelTexture<T> : ITexture
where T : unmanaged
{
/// <inheritdoc />
#region Constants
private const int STACK_ALLOC_LIMIT = 1024;
#endregion
#region Properties & Fields
private readonly int _dataPerPixel;
private readonly int _stride;
/// <summary>
/// Represents a texture made of pixels (like a common image).
/// Gets or sets the sampler used to get the color of a region.
/// </summary>
/// <typeparam name="T">The type of the pixels.</typeparam>
public abstract class PixelTexture<T> : ITexture
where T : unmanaged
public ISampler<T> Sampler { get; set; }
/// <inheritdoc />
public Size Size { get; }
/// <summary>
/// Gets the underlying pixel data.
/// </summary>
protected abstract ReadOnlySpan<T> Data { get; }
/// <inheritdoc />
public virtual Color this[in Point point]
{
#region Constants
private const int STACK_ALLOC_LIMIT = 1024;
#endregion
#region Properties & Fields
private readonly int _dataPerPixel;
private readonly int _stride;
/// <summary>
/// Gets or sets the sampler used to get the color of a region.
/// </summary>
public ISampler<T> Sampler { get; set; }
/// <inheritdoc />
public Size Size { get; }
/// <summary>
/// Gets the underlying pixel data.
/// </summary>
protected abstract ReadOnlySpan<T> Data { get; }
/// <inheritdoc />
public virtual Color this[in Point point]
get
{
get
{
if (Data.Length == 0) return Color.Transparent;
if (Data.Length == 0) return Color.Transparent;
int x = (int)MathF.Round(Size.Width * point.X.Clamp(0, 1));
int y = (int)MathF.Round(Size.Height * point.Y.Clamp(0, 1));
return GetColor(GetPixelData(x, y));
}
int x = (int)MathF.Round(Size.Width * point.X.Clamp(0, 1));
int y = (int)MathF.Round(Size.Height * point.Y.Clamp(0, 1));
return GetColor(GetPixelData(x, y));
}
/// <inheritdoc />
public virtual Color this[in Rectangle rectangle]
{
get
{
if (Data.Length == 0) return Color.Transparent;
int x = (int)MathF.Round(Size.Width * rectangle.Location.X.Clamp(0, 1));
int y = (int)MathF.Round(Size.Height * rectangle.Location.Y.Clamp(0, 1));
int width = (int)MathF.Round(Size.Width * rectangle.Size.Width.Clamp(0, 1));
int height = (int)MathF.Round(Size.Height * rectangle.Size.Height.Clamp(0, 1));
return this[x, y, width, height];
}
}
/// <summary>
/// Gets the sampled color inside the specified region.
/// </summary>
/// <param name="x">The x-location of the region.</param>
/// <param name="y">The y-location of the region.</param>
/// <param name="width">The with of the region.</param>
/// <param name="height">The height of the region.</param>
/// <returns>The sampled color.</returns>
public virtual Color this[int x, int y, int width, int height]
{
get
{
if (Data.Length == 0) return Color.Transparent;
if ((width == 0) || (height == 0)) return Color.Transparent;
if ((width == 1) && (height == 1)) return GetColor(GetPixelData(x, y));
int bufferSize = width * height * _dataPerPixel;
if (bufferSize <= STACK_ALLOC_LIMIT)
{
Span<T> buffer = stackalloc T[bufferSize];
GetRegionData(x, y, width, height, buffer);
Span<T> pixelData = stackalloc T[_dataPerPixel];
Sampler.Sample(new SamplerInfo<T>(width, height, buffer), pixelData);
return GetColor(pixelData);
}
else
{
T[] rent = ArrayPool<T>.Shared.Rent(bufferSize);
Span<T> buffer = new Span<T>(rent)[..bufferSize];
GetRegionData(x, y, width, height, buffer);
Span<T> pixelData = stackalloc T[_dataPerPixel];
Sampler.Sample(new SamplerInfo<T>(width, height, buffer), pixelData);
ArrayPool<T>.Shared.Return(rent);
return GetColor(pixelData);
}
}
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="PixelTexture{T}" /> class.
/// </summary>
/// <param name="with">The width of the texture.</param>
/// <param name="height">The height of the texture.</param>
/// <param name="dataPerPixel">The amount of data-entries per pixel.</param>
/// <param name="sampler">The sampler used to get the color of a region.</param>
/// <param name="stride">The stride of the data or -1 if the width should be used.</param>
public PixelTexture(int with, int height, int dataPerPixel, ISampler<T> sampler, int stride = -1)
{
this._stride = stride == -1 ? with : stride;
this._dataPerPixel = dataPerPixel;
this.Sampler = sampler;
Size = new Size(with, height);
}
#endregion
#region Methods
/// <summary>
/// Converts the pixel-data to a color.
/// </summary>
/// <param name="pixel">The pixel-data to convert.</param>
/// <returns>The color represented by the specified pixel-data.</returns>
protected abstract Color GetColor(in ReadOnlySpan<T> pixel);
/// <summary>
/// Gets the pixel-data at the specified location.
/// </summary>
/// <param name="x">The x-location.</param>
/// <param name="y">The y-location.</param>
/// <returns>The pixel-data on the specified location.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual ReadOnlySpan<T> GetPixelData(int x, int y) => Data.Slice((y * _stride) + x, _dataPerPixel);
/// <summary>
/// Writes the pixel-data of the specified region to the passed buffer.
/// </summary>
/// <param name="x">The x-location of the region to get the data for.</param>
/// <param name="y">The y-location of the region to get the data for.</param>
/// <param name="width">The width of the region to get the data for.</param>
/// <param name="height">The height of the region to get the data for.</param>
/// <param name="buffer">The buffer to write the data to.</param>
protected virtual void GetRegionData(int x, int y, int width, int height, in Span<T> buffer)
{
int dataWidth = width * _dataPerPixel;
ReadOnlySpan<T> data = Data;
for (int i = 0; i < height; i++)
{
ReadOnlySpan<T> dataSlice = data.Slice((((y + i) * _stride) + x) * _dataPerPixel, dataWidth);
Span<T> destination = buffer.Slice(i * dataWidth, dataWidth);
dataSlice.CopyTo(destination);
}
}
#endregion
}
/// <inheritdoc />
/// <summary>
/// Represents a texture made of color-pixels.
/// </summary>
public sealed class PixelTexture : PixelTexture<Color>
public virtual Color this[in Rectangle rectangle]
{
#region Properties & Fields
private readonly Color[] _data;
/// <inheritdoc />
protected override ReadOnlySpan<Color> Data => _data;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="PixelTexture" /> class.
/// A <see cref="AverageColorSampler"/> is used.
/// </summary>
/// <param name="with">The width of the texture.</param>
/// <param name="height">The height of the texture.</param>
/// <param name="data">The pixel-data of the texture.</param>
public PixelTexture(int with, int height, Color[] data)
: this(with, height, data, new AverageColorSampler())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="PixelTexture" /> class.
/// </summary>
/// <param name="with">The width of the texture.</param>
/// <param name="height">The height of the texture.</param>
/// <param name="data">The pixel-data of the texture.</param>
/// <param name="sampler">The sampler used to get the color of a region.</param>
public PixelTexture(int with, int height, Color[] data, ISampler<Color> sampler)
: base(with, height, 1, sampler)
get
{
this._data = data;
if (Data.Length == 0) return Color.Transparent;
if (Data.Length != (with * height)) throw new ArgumentException($"Data-Length {Data.Length} differs from the specified size {with}x{height} ({with * height}).");
int x = (int)MathF.Round(Size.Width * rectangle.Location.X.Clamp(0, 1));
int y = (int)MathF.Round(Size.Height * rectangle.Location.Y.Clamp(0, 1));
int width = (int)MathF.Round(Size.Width * rectangle.Size.Width.Clamp(0, 1));
int height = (int)MathF.Round(Size.Height * rectangle.Size.Height.Clamp(0, 1));
return this[x, y, width, height];
}
#endregion
#region Methods
/// <inheritdoc />
protected override Color GetColor(in ReadOnlySpan<Color> pixel) => pixel[0];
#endregion
}
/// <summary>
/// Gets the sampled color inside the specified region.
/// </summary>
/// <param name="x">The x-location of the region.</param>
/// <param name="y">The y-location of the region.</param>
/// <param name="width">The with of the region.</param>
/// <param name="height">The height of the region.</param>
/// <returns>The sampled color.</returns>
public virtual Color this[int x, int y, int width, int height]
{
get
{
if (Data.Length == 0) return Color.Transparent;
if ((width == 0) || (height == 0)) return Color.Transparent;
if ((width == 1) && (height == 1)) return GetColor(GetPixelData(x, y));
int bufferSize = width * height * _dataPerPixel;
if (bufferSize <= STACK_ALLOC_LIMIT)
{
Span<T> buffer = stackalloc T[bufferSize];
GetRegionData(x, y, width, height, buffer);
Span<T> pixelData = stackalloc T[_dataPerPixel];
Sampler.Sample(new SamplerInfo<T>(width, height, buffer), pixelData);
return GetColor(pixelData);
}
else
{
T[] rent = ArrayPool<T>.Shared.Rent(bufferSize);
Span<T> buffer = new Span<T>(rent)[..bufferSize];
GetRegionData(x, y, width, height, buffer);
Span<T> pixelData = stackalloc T[_dataPerPixel];
Sampler.Sample(new SamplerInfo<T>(width, height, buffer), pixelData);
ArrayPool<T>.Shared.Return(rent);
return GetColor(pixelData);
}
}
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="PixelTexture{T}" /> class.
/// </summary>
/// <param name="with">The width of the texture.</param>
/// <param name="height">The height of the texture.</param>
/// <param name="dataPerPixel">The amount of data-entries per pixel.</param>
/// <param name="sampler">The sampler used to get the color of a region.</param>
/// <param name="stride">The stride of the data or -1 if the width should be used.</param>
public PixelTexture(int with, int height, int dataPerPixel, ISampler<T> sampler, int stride = -1)
{
this._stride = stride == -1 ? with : stride;
this._dataPerPixel = dataPerPixel;
this.Sampler = sampler;
Size = new Size(with, height);
}
#endregion
#region Methods
/// <summary>
/// Converts the pixel-data to a color.
/// </summary>
/// <param name="pixel">The pixel-data to convert.</param>
/// <returns>The color represented by the specified pixel-data.</returns>
protected abstract Color GetColor(in ReadOnlySpan<T> pixel);
/// <summary>
/// Gets the pixel-data at the specified location.
/// </summary>
/// <param name="x">The x-location.</param>
/// <param name="y">The y-location.</param>
/// <returns>The pixel-data on the specified location.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual ReadOnlySpan<T> GetPixelData(int x, int y) => Data.Slice((y * _stride) + x, _dataPerPixel);
/// <summary>
/// Writes the pixel-data of the specified region to the passed buffer.
/// </summary>
/// <param name="x">The x-location of the region to get the data for.</param>
/// <param name="y">The y-location of the region to get the data for.</param>
/// <param name="width">The width of the region to get the data for.</param>
/// <param name="height">The height of the region to get the data for.</param>
/// <param name="buffer">The buffer to write the data to.</param>
protected virtual void GetRegionData(int x, int y, int width, int height, in Span<T> buffer)
{
int dataWidth = width * _dataPerPixel;
ReadOnlySpan<T> data = Data;
for (int i = 0; i < height; i++)
{
ReadOnlySpan<T> dataSlice = data.Slice((((y + i) * _stride) + x) * _dataPerPixel, dataWidth);
Span<T> destination = buffer.Slice(i * dataWidth, dataWidth);
dataSlice.CopyTo(destination);
}
}
#endregion
}
/// <inheritdoc />
/// <summary>
/// Represents a texture made of color-pixels.
/// </summary>
public sealed class PixelTexture : PixelTexture<Color>
{
#region Properties & Fields
private readonly Color[] _data;
/// <inheritdoc />
protected override ReadOnlySpan<Color> Data => _data;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="PixelTexture" /> class.
/// A <see cref="AverageColorSampler"/> is used.
/// </summary>
/// <param name="with">The width of the texture.</param>
/// <param name="height">The height of the texture.</param>
/// <param name="data">The pixel-data of the texture.</param>
public PixelTexture(int with, int height, Color[] data)
: this(with, height, data, new AverageColorSampler())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="PixelTexture" /> class.
/// </summary>
/// <param name="with">The width of the texture.</param>
/// <param name="height">The height of the texture.</param>
/// <param name="data">The pixel-data of the texture.</param>
/// <param name="sampler">The sampler used to get the color of a region.</param>
public PixelTexture(int with, int height, Color[] data, ISampler<Color> sampler)
: base(with, height, 1, sampler)
{
this._data = data;
if (Data.Length != (with * height)) throw new ArgumentException($"Data-Length {Data.Length} differs from the specified size {with}x{height} ({with * height}).");
}
#endregion
#region Methods
/// <inheritdoc />
protected override Color GetColor(in ReadOnlySpan<Color> pixel) => pixel[0];
#endregion
}

View File

@ -1,35 +1,34 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a sampled that averages multiple color to a single color.
/// </summary>
/// <remarks>
/// Averages all components (A, R, G, B) of the colors separately which isn't ideal in cases where multiple different colors are combined.
/// </remarks>
public class AverageColorSampler : ISampler<Color>
{
/// <summary>
/// Represents a sampled that averages multiple color to a single color.
/// </summary>
/// <remarks>
/// Averages all components (A, R, G, B) of the colors separately which isn't ideal in cases where multiple different colors are combined.
/// </remarks>
public class AverageColorSampler : ISampler<Color>
#region Methods
/// <inheritdoc />
public void Sample(in SamplerInfo<Color> info, in Span<Color> pixelData)
{
#region Methods
int count = info.Width * info.Height;
if (count == 0) return;
/// <inheritdoc />
public void Sample(in SamplerInfo<Color> info, in Span<Color> pixelData)
float a = 0, r = 0, g = 0, b = 0;
foreach (Color color in info.Data)
{
int count = info.Width * info.Height;
if (count == 0) return;
float a = 0, r = 0, g = 0, b = 0;
foreach (Color color in info.Data)
{
a += color.A;
r += color.R;
g += color.G;
b += color.B;
}
pixelData[0] = new Color(a / count, r / count, g / count, b / count);
a += color.A;
r += color.R;
g += color.G;
b += color.B;
}
#endregion
pixelData[0] = new Color(a / count, r / count, g / count, b / count);
}
}
#endregion
}

View File

@ -1,18 +1,17 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic sampler to combine multipel data entries to a single one.
/// </summary>
/// <typeparam name="T">The type of the data to sample.</typeparam>
public interface ISampler<T>
{
/// <summary>
/// Represents a generic sampler to combine multipel data entries to a single one.
/// Samples the specified data to a single pixel-buffer.
/// </summary>
/// <typeparam name="T">The type of the data to sample.</typeparam>
public interface ISampler<T>
{
/// <summary>
/// Samples the specified data to a single pixel-buffer.
/// </summary>
/// <param name="info">The information containing the data to sample.</param>
/// <param name="pixelData">The buffer used to write the resulting pixel to.</param>
void Sample(in SamplerInfo<T> info, in Span<T> pixelData);
}
}
/// <param name="info">The information containing the data to sample.</param>
/// <param name="pixelData">The buffer used to write the resulting pixel to.</param>
void Sample(in SamplerInfo<T> info, in Span<T> pixelData);
}

View File

@ -1,47 +1,46 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents the information used to sample data.
/// </summary>
/// <typeparam name="T">The type of the data to sample.</typeparam>
public readonly ref struct SamplerInfo<T>
{
#region Properties & Fields
/// <summary>
/// Represents the information used to sample data.
/// Gets the width of the region the data comes from.
/// </summary>
/// <typeparam name="T">The type of the data to sample.</typeparam>
public readonly ref struct SamplerInfo<T>
public int Width { get; }
/// <summary>
/// Gets the height of region the data comes from.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the data to sample.
/// </summary>
public ReadOnlySpan<T> Data { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SamplerInfo{T}" /> class.
/// </summary>
/// <param name="width">The width of the region the data comes from.</param>
/// <param name="height">The height of region the data comes from.</param>
/// <param name="data">The data to sample.</param>
public SamplerInfo(int width, int height, ReadOnlySpan<T> data)
{
#region Properties & Fields
/// <summary>
/// Gets the width of the region the data comes from.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the height of region the data comes from.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the data to sample.
/// </summary>
public ReadOnlySpan<T> Data { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SamplerInfo{T}" /> class.
/// </summary>
/// <param name="width">The width of the region the data comes from.</param>
/// <param name="height">The height of region the data comes from.</param>
/// <param name="data">The data to sample.</param>
public SamplerInfo(int width, int height, ReadOnlySpan<T> data)
{
this.Width = width;
this.Height = height;
this.Data = data;
}
#endregion
this.Width = width;
this.Height = height;
this.Data = data;
}
}
#endregion
}

View File

@ -1,48 +1,47 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic update trigger.
/// </summary>
public abstract class AbstractUpdateTrigger : AbstractBindable, IUpdateTrigger
{
#region Properties & Fields
/// <inheritdoc />
public abstract double LastUpdateTime { get; protected set; }
#endregion
#region Events
/// <inheritdoc />
public event EventHandler<CustomUpdateData>? Starting;
/// <inheritdoc />
public event EventHandler<CustomUpdateData>? Update;
#endregion
#region Methods
/// <summary>
/// Represents a generic update trigger.
/// Invokes the <see cref="Starting"/>-event.
/// </summary>
public abstract class AbstractUpdateTrigger : AbstractBindable, IUpdateTrigger
{
#region Properties & Fields
/// <param name="updateData">Optional custom-data passed to the subscribers of the <see cref="Starting"/>.event.</param>
protected virtual void OnStartup(CustomUpdateData? updateData = null) => Starting?.Invoke(this, updateData ?? new CustomUpdateData());
/// <inheritdoc />
public abstract double LastUpdateTime { get; protected set; }
/// <summary>
/// Invokes the <see cref="Update"/>-event.
/// </summary>
/// <param name="updateData">Optional custom-data passed to the subscribers of the <see cref="Update"/>.event.</param>
protected virtual void OnUpdate(CustomUpdateData? updateData = null) => Update?.Invoke(this, updateData ?? new CustomUpdateData());
#endregion
/// <inheritdoc />
public abstract void Start();
#region Events
/// <inheritdoc />
public abstract void Dispose();
/// <inheritdoc />
public event EventHandler<CustomUpdateData>? Starting;
/// <inheritdoc />
public event EventHandler<CustomUpdateData>? Update;
#endregion
#region Methods
/// <summary>
/// Invokes the <see cref="Starting"/>-event.
/// </summary>
/// <param name="updateData">Optional custom-data passed to the subscribers of the <see cref="Starting"/>.event.</param>
protected virtual void OnStartup(CustomUpdateData? updateData = null) => Starting?.Invoke(this, updateData ?? new CustomUpdateData());
/// <summary>
/// Invokes the <see cref="Update"/>-event.
/// </summary>
/// <param name="updateData">Optional custom-data passed to the subscribers of the <see cref="Update"/>.event.</param>
protected virtual void OnUpdate(CustomUpdateData? updateData = null) => Update?.Invoke(this, updateData ?? new CustomUpdateData());
/// <inheritdoc />
public abstract void Start();
/// <inheritdoc />
public abstract void Dispose();
#endregion
}
}
#endregion
}

View File

@ -1,51 +1,50 @@
using System.Collections.Generic;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a set of custom data, each indexed by a string-key.
/// </summary>
public class CustomUpdateData
{
#region Properties & Fields
private Dictionary<string, object?> _data = new();
#endregion
#region Indexer
/// <summary>
/// Represents a set of custom data, each indexed by a string-key.
/// Gets or sets the value for a specific key.
/// </summary>
public class CustomUpdateData
/// <param name="key">The key of the value.</param>
/// <returns>The value represented by the specified key.</returns>
public object? this[string key]
{
#region Properties & Fields
private Dictionary<string, object?> _data = new();
#endregion
#region Indexer
/// <summary>
/// Gets or sets the value for a specific key.
/// </summary>
/// <param name="key">The key of the value.</param>
/// <returns>The value represented by the specified key.</returns>
public object? this[string key]
{
get => _data.TryGetValue(key.ToUpperInvariant(), out object? data) ? data : default;
set => _data[key.ToUpperInvariant()] = value;
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="CustomUpdateData"/> class.
/// </summary>
public CustomUpdateData()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="CustomUpdateData"/> class.
/// </summary>
/// <param name="values">A params-list of tuples containing the key (string) and the value of a specific custom-data.</param>
public CustomUpdateData(params (string key, object value)[] values)
{
foreach ((string key, object value) in values)
this[key] = value;
}
#endregion
get => _data.TryGetValue(key.ToUpperInvariant(), out object? data) ? data : default;
set => _data[key.ToUpperInvariant()] = value;
}
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="CustomUpdateData"/> class.
/// </summary>
public CustomUpdateData()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="CustomUpdateData"/> class.
/// </summary>
/// <param name="values">A params-list of tuples containing the key (string) and the value of a specific custom-data.</param>
public CustomUpdateData(params (string key, object value)[] values)
{
foreach ((string key, object value) in values)
this[key] = value;
}
#endregion
}

View File

@ -4,178 +4,177 @@ using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents an update-trigger used to update devices with a maximum update-rate.
/// </summary>
public class DeviceUpdateTrigger : AbstractUpdateTrigger, IDeviceUpdateTrigger
{
#region Properties & Fields
/// <summary>
/// Represents an update-trigger used to update devices with a maximum update-rate.
/// Gets or sets the timeout used by the blocking wait for data availability.
/// </summary>
public class DeviceUpdateTrigger : AbstractUpdateTrigger, IDeviceUpdateTrigger
public int Timeout { get; set; } = 100;
/// <summary>
/// Gets the update frequency used by the trigger if not limited by data shortage.
/// </summary>
public double UpdateFrequency { get; private set; }
private double _maxUpdateRate;
/// <summary>
/// Gets or sets the maximum update rate of this trigger (is overwriten if the <see cref="UpdateRateHardLimit"/> is smaller).
/// &lt;= 0 removes the limit.
/// </summary>
public double MaxUpdateRate
{
#region Properties & Fields
/// <summary>
/// Gets or sets the timeout used by the blocking wait for data availability.
/// </summary>
public int Timeout { get; set; } = 100;
/// <summary>
/// Gets the update frequency used by the trigger if not limited by data shortage.
/// </summary>
public double UpdateFrequency { get; private set; }
private double _maxUpdateRate;
/// <summary>
/// Gets or sets the maximum update rate of this trigger (is overwriten if the <see cref="UpdateRateHardLimit"/> is smaller).
/// &lt;= 0 removes the limit.
/// </summary>
public double MaxUpdateRate
get => _maxUpdateRate;
set
{
get => _maxUpdateRate;
set
_maxUpdateRate = value;
UpdateUpdateFrequency();
}
}
private double _updateRateHardLimit;
/// <summary>
/// Gets the hard limit of the update rate of this trigger. Updates will never perform faster then then this value if it's set.
/// &lt;= 0 removes the limit.
/// </summary>
public double UpdateRateHardLimit
{
get => _updateRateHardLimit;
protected set
{
_updateRateHardLimit = value;
UpdateUpdateFrequency();
}
}
/// <inheritdoc />
public override double LastUpdateTime { get; protected set; }
/// <summary>
/// Gets or sets the event to trigger when new data is available (<see cref="TriggerHasData"/>).
/// </summary>
protected AutoResetEvent HasDataEvent { get; set; } = new(false);
/// <summary>
/// Gets or sets a bool indicating if the trigger is currently updating.
/// </summary>
protected bool IsRunning { get; set; }
/// <summary>
/// Gets or sets the update loop of this trigger.
/// </summary>
protected Task? UpdateTask { get; set; }
/// <summary>
/// Gets or sets the cancellation token source used to create the cancellation token checked by the <see cref="UpdateTask"/>.
/// </summary>
protected CancellationTokenSource? UpdateTokenSource { get; set; }
/// <summary>
/// Gets or sets the cancellation token checked by the <see cref="UpdateTask"/>.
/// </summary>
protected CancellationToken UpdateToken { get; set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="DeviceUpdateTrigger"/> class.
/// </summary>
public DeviceUpdateTrigger()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DeviceUpdateTrigger"/> class.
/// </summary>
/// <param name="updateRateHardLimit">The hard limit of the update rate of this trigger.</param>
public DeviceUpdateTrigger(double updateRateHardLimit)
{
this.UpdateRateHardLimit = updateRateHardLimit;
}
#endregion
#region Methods
/// <summary>
/// Starts the trigger.
/// </summary>
public override void Start()
{
if (IsRunning) return;
IsRunning = true;
UpdateTokenSource?.Dispose();
UpdateTokenSource = new CancellationTokenSource();
UpdateTask = Task.Factory.StartNew(UpdateLoop, (UpdateToken = UpdateTokenSource.Token), TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
/// <summary>
/// Stops the trigger.
/// </summary>
public async void Stop()
{
if (!IsRunning) return;
IsRunning = false;
UpdateTokenSource?.Cancel();
if (UpdateTask != null)
await UpdateTask;
UpdateTask?.Dispose();
UpdateTask = null;
}
/// <summary>
/// The update loop called by the <see cref="UpdateTask"/>.
/// </summary>
protected virtual void UpdateLoop()
{
OnStartup();
while (!UpdateToken.IsCancellationRequested)
{
if (HasDataEvent.WaitOne(Timeout))
{
_maxUpdateRate = value;
UpdateUpdateFrequency();
}
}
long preUpdateTicks = Stopwatch.GetTimestamp();
private double _updateRateHardLimit;
/// <summary>
/// Gets the hard limit of the update rate of this trigger. Updates will never perform faster then then this value if it's set.
/// &lt;= 0 removes the limit.
/// </summary>
public double UpdateRateHardLimit
{
get => _updateRateHardLimit;
protected set
{
_updateRateHardLimit = value;
UpdateUpdateFrequency();
}
}
OnUpdate();
/// <inheritdoc />
public override double LastUpdateTime { get; protected set; }
double lastUpdateTime = ((Stopwatch.GetTimestamp() - preUpdateTicks) / 10000.0);
LastUpdateTime = lastUpdateTime;
/// <summary>
/// Gets or sets the event to trigger when new data is available (<see cref="TriggerHasData"/>).
/// </summary>
protected AutoResetEvent HasDataEvent { get; set; } = new(false);
/// <summary>
/// Gets or sets a bool indicating if the trigger is currently updating.
/// </summary>
protected bool IsRunning { get; set; }
/// <summary>
/// Gets or sets the update loop of this trigger.
/// </summary>
protected Task? UpdateTask { get; set; }
/// <summary>
/// Gets or sets the cancellation token source used to create the cancellation token checked by the <see cref="UpdateTask"/>.
/// </summary>
protected CancellationTokenSource? UpdateTokenSource { get; set; }
/// <summary>
/// Gets or sets the cancellation token checked by the <see cref="UpdateTask"/>.
/// </summary>
protected CancellationToken UpdateToken { get; set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="DeviceUpdateTrigger"/> class.
/// </summary>
public DeviceUpdateTrigger()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DeviceUpdateTrigger"/> class.
/// </summary>
/// <param name="updateRateHardLimit">The hard limit of the update rate of this trigger.</param>
public DeviceUpdateTrigger(double updateRateHardLimit)
{
this.UpdateRateHardLimit = updateRateHardLimit;
}
#endregion
#region Methods
/// <summary>
/// Starts the trigger.
/// </summary>
public override void Start()
{
if (IsRunning) return;
IsRunning = true;
UpdateTokenSource?.Dispose();
UpdateTokenSource = new CancellationTokenSource();
UpdateTask = Task.Factory.StartNew(UpdateLoop, (UpdateToken = UpdateTokenSource.Token), TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
/// <summary>
/// Stops the trigger.
/// </summary>
public async void Stop()
{
if (!IsRunning) return;
IsRunning = false;
UpdateTokenSource?.Cancel();
if (UpdateTask != null)
await UpdateTask;
UpdateTask?.Dispose();
UpdateTask = null;
}
/// <summary>
/// The update loop called by the <see cref="UpdateTask"/>.
/// </summary>
protected virtual void UpdateLoop()
{
OnStartup();
while (!UpdateToken.IsCancellationRequested)
{
if (HasDataEvent.WaitOne(Timeout))
if (UpdateFrequency > 0)
{
long preUpdateTicks = Stopwatch.GetTimestamp();
OnUpdate();
double lastUpdateTime = ((Stopwatch.GetTimestamp() - preUpdateTicks) / 10000.0);
LastUpdateTime = lastUpdateTime;
if (UpdateFrequency > 0)
{
int sleep = (int)((UpdateFrequency * 1000.0) - lastUpdateTime);
if (sleep > 0)
Thread.Sleep(sleep);
}
int sleep = (int)((UpdateFrequency * 1000.0) - lastUpdateTime);
if (sleep > 0)
Thread.Sleep(sleep);
}
}
}
/// <inheritdoc />
public void TriggerHasData() => HasDataEvent.Set();
private void UpdateUpdateFrequency()
{
UpdateFrequency = MaxUpdateRate;
if ((UpdateFrequency <= 0) || ((UpdateRateHardLimit > 0) && (UpdateRateHardLimit < UpdateFrequency)))
UpdateFrequency = UpdateRateHardLimit;
}
/// <inheritdoc />
public override void Dispose() => Stop();
#endregion
}
}
/// <inheritdoc />
public void TriggerHasData() => HasDataEvent.Set();
private void UpdateUpdateFrequency()
{
UpdateFrequency = MaxUpdateRate;
if ((UpdateFrequency <= 0) || ((UpdateRateHardLimit > 0) && (UpdateRateHardLimit < UpdateFrequency)))
UpdateFrequency = UpdateRateHardLimit;
}
/// <inheritdoc />
public override void Dispose() => Stop();
#endregion
}

View File

@ -1,13 +1,12 @@
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents an update trigger used to trigger device-updates.
/// </summary>
public interface IDeviceUpdateTrigger : IUpdateTrigger
{
/// <summary>
/// Represents an update trigger used to trigger device-updates.
/// Indicates that there's data available to process.
/// </summary>
public interface IDeviceUpdateTrigger : IUpdateTrigger
{
/// <summary>
/// Indicates that there's data available to process.
/// </summary>
void TriggerHasData();
}
}
void TriggerHasData();
}

View File

@ -1,32 +1,31 @@
using System;
using System.Collections.Generic;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic update queue.
/// </summary>
/// <typeparam name="TIdentifier">The identifier used to identify the data processed by this queue.</typeparam>
/// <typeparam name="TData">The type of the data processed by this queue.</typeparam>
public interface IUpdateQueue<TIdentifier, TData> : IDisposable
where TIdentifier : notnull
{
/// <summary>
/// Represents a generic update queue.
/// Sets or merges the provided data set in the current dataset and notifies the trigger that there is new data available.
/// </summary>
/// <typeparam name="TIdentifier">The identifier used to identify the data processed by this queue.</typeparam>
/// <typeparam name="TData">The type of the data processed by this queue.</typeparam>
public interface IUpdateQueue<TIdentifier, TData> : IDisposable
where TIdentifier : notnull
{
/// <summary>
/// Sets or merges the provided data set in the current dataset and notifies the trigger that there is new data available.
/// </summary>
/// <param name="dataSet">The set of data.</param>
// ReSharper disable once MemberCanBeProtected.Global
void SetData(IEnumerable<(TIdentifier, TData)> dataSet);
/// <summary>
/// Resets the current data set.
/// </summary>
void Reset();
}
/// <param name="dataSet">The set of data.</param>
// ReSharper disable once MemberCanBeProtected.Global
void SetData(IEnumerable<(TIdentifier, TData)> dataSet);
/// <summary>
/// Represents a generic update queue processing <see cref="Color"/>-data using <see cref="object"/>-identifiers.
/// Resets the current data set.
/// </summary>
public interface IUpdateQueue : IUpdateQueue<object, Color>
{ }
void Reset();
}
/// <summary>
/// Represents a generic update queue processing <see cref="Color"/>-data using <see cref="object"/>-identifiers.
/// </summary>
public interface IUpdateQueue : IUpdateQueue<object, Color>
{ }

View File

@ -3,137 +3,136 @@ using System.Buffers;
using System.Collections.Generic;
using System.Linq;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic update queue.
/// </summary>
/// <typeparam name="TIdentifier">The type of the key used to identify some data.</typeparam>
/// <typeparam name="TData">The type of the data.</typeparam>
public abstract class UpdateQueue<TIdentifier, TData> : IUpdateQueue<TIdentifier, TData>
where TIdentifier : notnull
{
#region Properties & Fields
private readonly object _dataLock = new();
private readonly IDeviceUpdateTrigger _updateTrigger;
private readonly Dictionary<TIdentifier, TData> _currentDataSet = new();
#endregion
#region Constructors
/// <summary>
/// Represents a generic update queue.
/// Initializes a new instance of the <see cref="UpdateQueue{TIdentifier,TData}"/> class.
/// </summary>
/// <typeparam name="TIdentifier">The type of the key used to identify some data.</typeparam>
/// <typeparam name="TData">The type of the data.</typeparam>
public abstract class UpdateQueue<TIdentifier, TData> : IUpdateQueue<TIdentifier, TData>
where TIdentifier : notnull
/// <param name="updateTrigger">The <see cref="IDeviceUpdateTrigger"/> causing this queue to update.</param>
protected UpdateQueue(IDeviceUpdateTrigger updateTrigger)
{
#region Properties & Fields
this._updateTrigger = updateTrigger;
private readonly object _dataLock = new();
private readonly IDeviceUpdateTrigger _updateTrigger;
private readonly Dictionary<TIdentifier, TData> _currentDataSet = new();
_updateTrigger.Starting += OnStartup;
_updateTrigger.Update += OnUpdate;
}
#endregion
#endregion
#region Constructors
#region Methods
/// <summary>
/// Initializes a new instance of the <see cref="UpdateQueue{TIdentifier,TData}"/> class.
/// </summary>
/// <param name="updateTrigger">The <see cref="IDeviceUpdateTrigger"/> causing this queue to update.</param>
protected UpdateQueue(IDeviceUpdateTrigger updateTrigger)
/// <summary>
/// Event handler for the <see cref="IUpdateTrigger.Update"/>-event.
/// </summary>
/// <param name="sender">The <see cref="IUpdateTrigger"/> causing this update.</param>
/// <param name="customData"><see cref="CustomUpdateData"/> provided by the trigger.</param>
protected virtual void OnUpdate(object? sender, CustomUpdateData customData)
{
(TIdentifier, TData)[] dataSet;
Span<(TIdentifier, TData)> data;
lock (_dataLock)
{
this._updateTrigger = updateTrigger;
if (_currentDataSet.Count == 0) return;
_updateTrigger.Starting += OnStartup;
_updateTrigger.Update += OnUpdate;
dataSet = ArrayPool<(TIdentifier, TData)>.Shared.Rent(_currentDataSet.Count);
data = new Span<(TIdentifier, TData)>(dataSet)[.._currentDataSet.Count];
int i = 0;
foreach ((TIdentifier key, TData value) in _currentDataSet)
data[i++] = (key, value);
_currentDataSet.Clear();
}
#endregion
Update(data);
#region Methods
/// <summary>
/// Event handler for the <see cref="IUpdateTrigger.Update"/>-event.
/// </summary>
/// <param name="sender">The <see cref="IUpdateTrigger"/> causing this update.</param>
/// <param name="customData"><see cref="CustomUpdateData"/> provided by the trigger.</param>
protected virtual void OnUpdate(object? sender, CustomUpdateData customData)
{
(TIdentifier, TData)[] dataSet;
Span<(TIdentifier, TData)> data;
lock (_dataLock)
{
if (_currentDataSet.Count == 0) return;
dataSet = ArrayPool<(TIdentifier, TData)>.Shared.Rent(_currentDataSet.Count);
data = new Span<(TIdentifier, TData)>(dataSet)[.._currentDataSet.Count];
int i = 0;
foreach ((TIdentifier key, TData value) in _currentDataSet)
data[i++] = (key, value);
_currentDataSet.Clear();
}
Update(data);
ArrayPool<(TIdentifier, TData)>.Shared.Return(dataSet);
}
/// <summary>
/// Event handler for the <see cref="IUpdateTrigger.Starting"/>-event.
/// </summary>
/// <param name="sender">The starting <see cref="IUpdateTrigger"/>.</param>
/// <param name="customData"><see cref="CustomUpdateData"/> provided by the trigger.</param>
protected virtual void OnStartup(object? sender, CustomUpdateData customData) { }
/// <summary>
/// Performs the update this queue is responsible for.
/// </summary>
/// <param name="dataSet">The set of data that needs to be updated.</param>
protected abstract void Update(in ReadOnlySpan<(TIdentifier key, TData color)> dataSet);
/// <summary>
/// Sets or merges the provided data set in the current dataset and notifies the trigger that there is new data available.
/// </summary>
/// <param name="dataSet">The set of data.</param>
// ReSharper disable once MemberCanBeProtected.Global
public virtual void SetData(IEnumerable<(TIdentifier, TData)> dataSet)
{
IList<(TIdentifier, TData)> data = dataSet.ToList();
if (data.Count == 0) return;
lock (_dataLock)
{
foreach ((TIdentifier key, TData value) in data)
_currentDataSet[key] = value;
}
_updateTrigger.TriggerHasData();
}
/// <summary>
/// Resets the current data set.
/// </summary>
public virtual void Reset()
{
lock (_dataLock)
_currentDataSet.Clear();
}
/// <inheritdoc />
public virtual void Dispose()
{
_updateTrigger.Starting -= OnStartup;
_updateTrigger.Update -= OnUpdate;
Reset();
GC.SuppressFinalize(this);
}
#endregion
ArrayPool<(TIdentifier, TData)>.Shared.Return(dataSet);
}
/// <summary>
/// Represents a generic <see cref="UpdateQueue{TIdentifier,TData}"/> using an object as the key and a color as the value.
/// Event handler for the <see cref="IUpdateTrigger.Starting"/>-event.
/// </summary>
public abstract class UpdateQueue : UpdateQueue<object, Color>, IUpdateQueue
/// <param name="sender">The starting <see cref="IUpdateTrigger"/>.</param>
/// <param name="customData"><see cref="CustomUpdateData"/> provided by the trigger.</param>
protected virtual void OnStartup(object? sender, CustomUpdateData customData) { }
/// <summary>
/// Performs the update this queue is responsible for.
/// </summary>
/// <param name="dataSet">The set of data that needs to be updated.</param>
protected abstract void Update(in ReadOnlySpan<(TIdentifier key, TData color)> dataSet);
/// <summary>
/// Sets or merges the provided data set in the current dataset and notifies the trigger that there is new data available.
/// </summary>
/// <param name="dataSet">The set of data.</param>
// ReSharper disable once MemberCanBeProtected.Global
public virtual void SetData(IEnumerable<(TIdentifier, TData)> dataSet)
{
#region Constructors
IList<(TIdentifier, TData)> data = dataSet.ToList();
if (data.Count == 0) return;
/// <inheritdoc />
protected UpdateQueue(IDeviceUpdateTrigger updateTrigger)
: base(updateTrigger)
{ }
lock (_dataLock)
{
foreach ((TIdentifier key, TData value) in data)
_currentDataSet[key] = value;
}
#endregion
_updateTrigger.TriggerHasData();
}
/// <summary>
/// Resets the current data set.
/// </summary>
public virtual void Reset()
{
lock (_dataLock)
_currentDataSet.Clear();
}
/// <inheritdoc />
public virtual void Dispose()
{
_updateTrigger.Starting -= OnStartup;
_updateTrigger.Update -= OnUpdate;
Reset();
GC.SuppressFinalize(this);
}
#endregion
}
/// <summary>
/// Represents a generic <see cref="UpdateQueue{TIdentifier,TData}"/> using an object as the key and a color as the value.
/// </summary>
public abstract class UpdateQueue : UpdateQueue<object, Color>, IUpdateQueue
{
#region Constructors
/// <inheritdoc />
protected UpdateQueue(IDeviceUpdateTrigger updateTrigger)
: base(updateTrigger)
{ }
#endregion
}

View File

@ -1,30 +1,29 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a trigger causing an update.
/// </summary>
public interface IUpdateTrigger : IDisposable
{
/// <summary>
/// Represents a trigger causing an update.
/// Gets the time spent for the last update.
/// </summary>
public interface IUpdateTrigger : IDisposable
{
/// <summary>
/// Gets the time spent for the last update.
/// </summary>
double LastUpdateTime { get; }
double LastUpdateTime { get; }
/// <summary>
/// Occurs when the trigger is starting up.
/// </summary>
event EventHandler<CustomUpdateData>? Starting;
/// <summary>
/// Occurs when the trigger is starting up.
/// </summary>
event EventHandler<CustomUpdateData>? Starting;
/// <summary>
/// Occurs when the trigger wants to cause an update.
/// </summary>
event EventHandler<CustomUpdateData>? Update;
/// <summary>
/// Occurs when the trigger wants to cause an update.
/// </summary>
event EventHandler<CustomUpdateData>? Update;
/// <summary>
/// Starts the update trigger.
/// </summary>
void Start();
}
}
/// <summary>
/// Starts the update trigger.
/// </summary>
void Start();
}

View File

@ -4,93 +4,92 @@ using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents an update trigger that is manully triggered by calling <see cref="TriggerUpdate"/>.
/// </summary>
public sealed class ManualUpdateTrigger : AbstractUpdateTrigger
{
/// <inheritdoc />
#region Properties & Fields
private readonly AutoResetEvent _mutex = new(false);
private Task? UpdateTask { get; set; }
private CancellationTokenSource? UpdateTokenSource { get; set; }
private CancellationToken UpdateToken { get; set; }
/// <summary>
/// Represents an update trigger that is manully triggered by calling <see cref="TriggerUpdate"/>.
/// Gets the time it took the last update-loop cycle to run.
/// </summary>
public sealed class ManualUpdateTrigger : AbstractUpdateTrigger
public override double LastUpdateTime { get; protected set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ManualUpdateTrigger"/> class.
/// </summary>
public ManualUpdateTrigger()
{
#region Properties & Fields
private readonly AutoResetEvent _mutex = new(false);
private Task? UpdateTask { get; set; }
private CancellationTokenSource? UpdateTokenSource { get; set; }
private CancellationToken UpdateToken { get; set; }
/// <summary>
/// Gets the time it took the last update-loop cycle to run.
/// </summary>
public override double LastUpdateTime { get; protected set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ManualUpdateTrigger"/> class.
/// </summary>
public ManualUpdateTrigger()
{
Start();
}
#endregion
#region Methods
/// <summary>
/// Starts the trigger if needed, causing it to performing updates.
/// </summary>
public override void Start()
{
if (UpdateTask == null)
{
UpdateTokenSource?.Dispose();
UpdateTokenSource = new CancellationTokenSource();
UpdateTask = Task.Factory.StartNew(UpdateLoop, (UpdateToken = UpdateTokenSource.Token), TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
}
/// <summary>
/// Stops the trigger if running, causing it to stop performing updates.
/// </summary>
private void Stop()
{
if (UpdateTask != null)
{
UpdateTokenSource?.Cancel();
// ReSharper disable once MethodSupportsCancellation
UpdateTask.Wait();
UpdateTask.Dispose();
UpdateTask = null;
}
}
/// <summary>
/// Triggers an update.
/// </summary>
public void TriggerUpdate() => _mutex.Set();
private void UpdateLoop()
{
OnStartup();
while (!UpdateToken.IsCancellationRequested)
{
if (_mutex.WaitOne(100))
{
long preUpdateTicks = Stopwatch.GetTimestamp();
OnUpdate();
LastUpdateTime = ((Stopwatch.GetTimestamp() - preUpdateTicks) / 10000.0);
}
}
}
/// <inheritdoc />
public override void Dispose() => Stop();
#endregion
Start();
}
}
#endregion
#region Methods
/// <summary>
/// Starts the trigger if needed, causing it to performing updates.
/// </summary>
public override void Start()
{
if (UpdateTask == null)
{
UpdateTokenSource?.Dispose();
UpdateTokenSource = new CancellationTokenSource();
UpdateTask = Task.Factory.StartNew(UpdateLoop, (UpdateToken = UpdateTokenSource.Token), TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
}
/// <summary>
/// Stops the trigger if running, causing it to stop performing updates.
/// </summary>
private void Stop()
{
if (UpdateTask != null)
{
UpdateTokenSource?.Cancel();
// ReSharper disable once MethodSupportsCancellation
UpdateTask.Wait();
UpdateTask.Dispose();
UpdateTask = null;
}
}
/// <summary>
/// Triggers an update.
/// </summary>
public void TriggerUpdate() => _mutex.Set();
private void UpdateLoop()
{
OnStartup();
while (!UpdateToken.IsCancellationRequested)
{
if (_mutex.WaitOne(100))
{
long preUpdateTicks = Stopwatch.GetTimestamp();
OnUpdate();
LastUpdateTime = ((Stopwatch.GetTimestamp() - preUpdateTicks) / 10000.0);
}
}
}
/// <inheritdoc />
public override void Dispose() => Stop();
#endregion
}

View File

@ -5,139 +5,138 @@ using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents an update trigger that triggers in a set interval.
/// </summary>
public class TimerUpdateTrigger : AbstractUpdateTrigger
{
/// <inheritdoc />
#region Properties & Fields
private readonly object _lock = new();
/// <summary>
/// Represents an update trigger that triggers in a set interval.
/// Gets or sets the update loop of this trigger.
/// </summary>
public class TimerUpdateTrigger : AbstractUpdateTrigger
protected Task? UpdateTask { get; set; }
/// <summary>
/// Gets or sets the cancellation token source used to create the cancellation token checked by the <see cref="UpdateTask"/>.
/// </summary>
protected CancellationTokenSource? UpdateTokenSource { get; set; }
/// <summary>
/// Gets or sets the cancellation token checked by the <see cref="UpdateTask"/>.
/// </summary>
protected CancellationToken UpdateToken { get; set; }
private double _updateFrequency = 1.0 / 30.0;
/// <summary>
/// Gets or sets the update-frequency in seconds. (Calculate by using '1.0 / updates per second')
/// </summary>
public double UpdateFrequency
{
#region Properties & Fields
private readonly object _lock = new();
/// <summary>
/// Gets or sets the update loop of this trigger.
/// </summary>
protected Task? UpdateTask { get; set; }
/// <summary>
/// Gets or sets the cancellation token source used to create the cancellation token checked by the <see cref="UpdateTask"/>.
/// </summary>
protected CancellationTokenSource? UpdateTokenSource { get; set; }
/// <summary>
/// Gets or sets the cancellation token checked by the <see cref="UpdateTask"/>.
/// </summary>
protected CancellationToken UpdateToken { get; set; }
private double _updateFrequency = 1.0 / 30.0;
/// <summary>
/// Gets or sets the update-frequency in seconds. (Calculate by using '1.0 / updates per second')
/// </summary>
public double UpdateFrequency
{
get => _updateFrequency;
set => SetProperty(ref _updateFrequency, value);
}
/// <summary>
/// Gets the time it took the last update-loop cycle to run.
/// </summary>
public override double LastUpdateTime { get; protected set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="TimerUpdateTrigger"/> class.
/// </summary>
/// <param name="autostart">A value indicating if the trigger should automatically <see cref="Start"/> right after construction.</param>
public TimerUpdateTrigger(bool autostart = true)
{
if (autostart)
// ReSharper disable once VirtualMemberCallInConstructor - HACK DarthAffe 01.06.2021: I've no idea how to correctly handle that case, for now just disable it
Start();
}
#endregion
#region Methods
/// <summary>
/// Starts the trigger if needed, causing it to performing updates.
/// </summary>
public override void Start()
{
lock (_lock)
{
if (UpdateTask == null)
{
UpdateTokenSource?.Dispose();
UpdateTokenSource = new CancellationTokenSource();
UpdateTask = Task.Factory.StartNew(UpdateLoop, (UpdateToken = UpdateTokenSource.Token), TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
}
}
/// <summary>
/// Stops the trigger if running, causing it to stop performing updates.
/// </summary>
public void Stop()
{
lock (_lock)
{
if (UpdateTask != null)
{
UpdateTokenSource?.Cancel();
try
{
// ReSharper disable once MethodSupportsCancellation
UpdateTask.Wait();
}
catch (AggregateException)
{
// ignored
}
finally
{
UpdateTask.Dispose();
UpdateTask = null;
}
}
}
}
private void UpdateLoop()
{
OnStartup();
while (!UpdateToken.IsCancellationRequested)
{
long preUpdateTicks = Stopwatch.GetTimestamp();
OnUpdate();
if (UpdateFrequency > 0)
{
double lastUpdateTime = ((Stopwatch.GetTimestamp() - preUpdateTicks) / 10000.0);
LastUpdateTime = lastUpdateTime;
int sleep = (int)((UpdateFrequency * 1000.0) - lastUpdateTime);
if (sleep > 0)
Thread.Sleep(sleep);
}
}
}
/// <inheritdoc />
public override void Dispose()
{
Stop();
GC.SuppressFinalize(this);
}
#endregion
get => _updateFrequency;
set => SetProperty(ref _updateFrequency, value);
}
}
/// <summary>
/// Gets the time it took the last update-loop cycle to run.
/// </summary>
public override double LastUpdateTime { get; protected set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="TimerUpdateTrigger"/> class.
/// </summary>
/// <param name="autostart">A value indicating if the trigger should automatically <see cref="Start"/> right after construction.</param>
public TimerUpdateTrigger(bool autostart = true)
{
if (autostart)
// ReSharper disable once VirtualMemberCallInConstructor - HACK DarthAffe 01.06.2021: I've no idea how to correctly handle that case, for now just disable it
Start();
}
#endregion
#region Methods
/// <summary>
/// Starts the trigger if needed, causing it to performing updates.
/// </summary>
public override void Start()
{
lock (_lock)
{
if (UpdateTask == null)
{
UpdateTokenSource?.Dispose();
UpdateTokenSource = new CancellationTokenSource();
UpdateTask = Task.Factory.StartNew(UpdateLoop, (UpdateToken = UpdateTokenSource.Token), TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
}
}
/// <summary>
/// Stops the trigger if running, causing it to stop performing updates.
/// </summary>
public void Stop()
{
lock (_lock)
{
if (UpdateTask != null)
{
UpdateTokenSource?.Cancel();
try
{
// ReSharper disable once MethodSupportsCancellation
UpdateTask.Wait();
}
catch (AggregateException)
{
// ignored
}
finally
{
UpdateTask.Dispose();
UpdateTask = null;
}
}
}
}
private void UpdateLoop()
{
OnStartup();
while (!UpdateToken.IsCancellationRequested)
{
long preUpdateTicks = Stopwatch.GetTimestamp();
OnUpdate();
if (UpdateFrequency > 0)
{
double lastUpdateTime = ((Stopwatch.GetTimestamp() - preUpdateTicks) / 10000.0);
LastUpdateTime = lastUpdateTime;
int sleep = (int)((UpdateFrequency * 1000.0) - lastUpdateTime);
if (sleep > 0)
Thread.Sleep(sleep);
}
}
}
/// <inheritdoc />
public override void Dispose()
{
Stop();
GC.SuppressFinalize(this);
}
#endregion
}

View File

@ -6,90 +6,89 @@ using System.Collections.Generic;
using AuraServiceLib;
using RGB.NET.Core;
namespace RGB.NET.Devices.Asus
namespace RGB.NET.Devices.Asus;
/// <inheritdoc />
/// <summary>
/// Represents a device provider responsible for Cooler Master devices.
/// </summary>
public class AsusDeviceProvider : AbstractRGBDeviceProvider
{
/// <inheritdoc />
#region Properties & Fields
private static AsusDeviceProvider? _instance;
/// <summary>
/// Represents a device provider responsible for Cooler Master devices.
/// Gets the singleton <see cref="AsusDeviceProvider"/> instance.
/// </summary>
public class AsusDeviceProvider : AbstractRGBDeviceProvider
public static AsusDeviceProvider Instance => _instance ?? new AsusDeviceProvider();
private IAuraSdk2? _sdk;
private IAuraSyncDeviceCollection? _devices; //HACK DarthAffe 05.04.2021: Due to some researches this might fix the access violation in the asus-sdk
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AsusDeviceProvider"/> class.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if this constructor is called even if there is already an instance of this class.</exception>
public AsusDeviceProvider()
{
#region Properties & Fields
private static AsusDeviceProvider? _instance;
/// <summary>
/// Gets the singleton <see cref="AsusDeviceProvider"/> instance.
/// </summary>
public static AsusDeviceProvider Instance => _instance ?? new AsusDeviceProvider();
private IAuraSdk2? _sdk;
private IAuraSyncDeviceCollection? _devices; //HACK DarthAffe 05.04.2021: Due to some researches this might fix the access violation in the asus-sdk
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AsusDeviceProvider"/> class.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if this constructor is called even if there is already an instance of this class.</exception>
public AsusDeviceProvider()
{
if (_instance != null) throw new InvalidOperationException($"There can be only one instance of type {nameof(AsusDeviceProvider)}");
_instance = this;
}
#endregion
#region Methods
/// <inheritdoc />
protected override void InitializeSDK()
{
// ReSharper disable once SuspiciousTypeConversion.Global
_sdk = (IAuraSdk2)new AuraSdk();
_sdk.SwitchMode();
}
/// <inheritdoc />
protected override IEnumerable<IRGBDevice> LoadDevices()
{
if (_sdk == null) yield break;
_devices = _sdk.Enumerate(0);
for (int i = 0; i < _devices.Count; i++)
{
IAuraSyncDevice device = _devices[i];
yield return (AsusDeviceType)device.Type switch
{
AsusDeviceType.MB_RGB => new AsusMainboardRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.Mainboard, device, WMIHelper.GetMainboardInfo()?.model ?? device.Name), GetUpdateTrigger()),
AsusDeviceType.MB_ADDRESABLE => new AsusUnspecifiedRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.LedStripe, device), LedId.LedStripe1, GetUpdateTrigger()),
AsusDeviceType.VGA_RGB => new AsusGraphicsCardRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.GraphicsCard, device), GetUpdateTrigger()),
AsusDeviceType.HEADSET_RGB => new AsusHeadsetRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.Headset, device), GetUpdateTrigger()),
AsusDeviceType.DRAM_RGB => new AsusDramRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.DRAM, device), GetUpdateTrigger()),
AsusDeviceType.KEYBOARD_RGB => new AsusKeyboardRGBDevice(new AsusKeyboardRGBDeviceInfo(device), LedMappings.KeyboardMapping, GetUpdateTrigger()),
AsusDeviceType.NB_KB_RGB => new AsusKeyboardRGBDevice(new AsusKeyboardRGBDeviceInfo(device), LedMappings.KeyboardMapping, GetUpdateTrigger()),
AsusDeviceType.NB_KB_4ZONE_RGB => new AsusKeyboardRGBDevice(new AsusKeyboardRGBDeviceInfo(device), null, GetUpdateTrigger()),
AsusDeviceType.MOUSE_RGB => new AsusMouseRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.Mouse, device), GetUpdateTrigger()),
_ => new AsusUnspecifiedRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.Unknown, device), LedId.Custom1, GetUpdateTrigger())
};
}
}
/// <inheritdoc />
public override void Dispose()
{
base.Dispose();
try { _sdk?.ReleaseControl(0); }
catch { /* at least we tried */ }
_devices = null;
_sdk = null;
GC.SuppressFinalize(this);
}
#endregion
if (_instance != null) throw new InvalidOperationException($"There can be only one instance of type {nameof(AsusDeviceProvider)}");
_instance = this;
}
}
#endregion
#region Methods
/// <inheritdoc />
protected override void InitializeSDK()
{
// ReSharper disable once SuspiciousTypeConversion.Global
_sdk = (IAuraSdk2)new AuraSdk();
_sdk.SwitchMode();
}
/// <inheritdoc />
protected override IEnumerable<IRGBDevice> LoadDevices()
{
if (_sdk == null) yield break;
_devices = _sdk.Enumerate(0);
for (int i = 0; i < _devices.Count; i++)
{
IAuraSyncDevice device = _devices[i];
yield return (AsusDeviceType)device.Type switch
{
AsusDeviceType.MB_RGB => new AsusMainboardRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.Mainboard, device, WMIHelper.GetMainboardInfo()?.model ?? device.Name), GetUpdateTrigger()),
AsusDeviceType.MB_ADDRESABLE => new AsusUnspecifiedRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.LedStripe, device), LedId.LedStripe1, GetUpdateTrigger()),
AsusDeviceType.VGA_RGB => new AsusGraphicsCardRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.GraphicsCard, device), GetUpdateTrigger()),
AsusDeviceType.HEADSET_RGB => new AsusHeadsetRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.Headset, device), GetUpdateTrigger()),
AsusDeviceType.DRAM_RGB => new AsusDramRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.DRAM, device), GetUpdateTrigger()),
AsusDeviceType.KEYBOARD_RGB => new AsusKeyboardRGBDevice(new AsusKeyboardRGBDeviceInfo(device), LedMappings.KeyboardMapping, GetUpdateTrigger()),
AsusDeviceType.NB_KB_RGB => new AsusKeyboardRGBDevice(new AsusKeyboardRGBDeviceInfo(device), LedMappings.KeyboardMapping, GetUpdateTrigger()),
AsusDeviceType.NB_KB_4ZONE_RGB => new AsusKeyboardRGBDevice(new AsusKeyboardRGBDeviceInfo(device), null, GetUpdateTrigger()),
AsusDeviceType.MOUSE_RGB => new AsusMouseRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.Mouse, device), GetUpdateTrigger()),
_ => new AsusUnspecifiedRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.Unknown, device), LedId.Custom1, GetUpdateTrigger())
};
}
}
/// <inheritdoc />
public override void Dispose()
{
base.Dispose();
try { _sdk?.ReleaseControl(0); }
catch { /* at least we tried */ }
_devices = null;
_sdk = null;
GC.SuppressFinalize(this);
}
#endregion
}

View File

@ -1,41 +1,40 @@
using RGB.NET.Core;
namespace RGB.NET.Devices.Asus
namespace RGB.NET.Devices.Asus;
/// <inheritdoc cref="AsusRGBDevice{TDeviceInfo}" />
/// <summary>
/// Represents a Asus dram.
/// </summary>
public class AsusDramRGBDevice : AsusRGBDevice<AsusRGBDeviceInfo>, IDRAM
{
/// <inheritdoc cref="AsusRGBDevice{TDeviceInfo}" />
#region Constructors
/// <inheritdoc />
/// <summary>
/// Represents a Asus dram.
/// Initializes a new instance of the <see cref="T:RGB.NET.Devices.Asus.AsusDramRGBDevice" /> class.
/// </summary>
public class AsusDramRGBDevice : AsusRGBDevice<AsusRGBDeviceInfo>, IDRAM
/// <param name="info">The specific information provided by Asus for the DRAM.</param>
/// <param name="updateTrigger">The update trigger used to update this device.</param>
internal AsusDramRGBDevice(AsusRGBDeviceInfo info, IDeviceUpdateTrigger updateTrigger)
: base(info, updateTrigger)
{
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Devices.Asus.AsusDramRGBDevice" /> class.
/// </summary>
/// <param name="info">The specific information provided by Asus for the DRAM.</param>
/// <param name="updateTrigger">The update trigger used to update this device.</param>
internal AsusDramRGBDevice(AsusRGBDeviceInfo info, IDeviceUpdateTrigger updateTrigger)
: base(info, updateTrigger)
{
InitializeLayout();
}
#endregion
#region Methods
private void InitializeLayout()
{
int ledCount = DeviceInfo.Device.Lights.Count;
for (int i = 0; i < ledCount; i++)
AddLed(LedId.DRAM1 + i, new Point(i * 10, 0), new Size(10, 10));
}
/// <inheritdoc />
protected override object? GetLedCustomData(LedId ledId) => (int)ledId - (int)LedId.DRAM1;
#endregion
InitializeLayout();
}
}
#endregion
#region Methods
private void InitializeLayout()
{
int ledCount = DeviceInfo.Device.Lights.Count;
for (int i = 0; i < ledCount; i++)
AddLed(LedId.DRAM1 + i, new Point(i * 10, 0), new Size(10, 10));
}
/// <inheritdoc />
protected override object? GetLedCustomData(LedId ledId) => (int)ledId - (int)LedId.DRAM1;
#endregion
}

View File

@ -1,25 +1,24 @@
// ReSharper disable InconsistentNaming
namespace RGB.NET.Devices.Asus
namespace RGB.NET.Devices.Asus;
internal enum AsusDeviceType : uint
{
internal enum AsusDeviceType : uint
{
ALL = 0,
MB_RGB = 0x10000,
MB_ADDRESABLE = 0x11000,
DESKTOP_RGB = 0x12000,
VGA_RGB = 0x20000,
DISPLAY_RGB = 0x30000,
HEADSET_RGB = 0x40000,
MICROPHONE_RGB = 0x50000,
EXTERNAL_HARD_DRIVER_RGB = 0x60000,
EXTERNAL_BLUE_RAY_RGB = 0x61000,
DRAM_RGB = 0x70000,
KEYBOARD_RGB = 0x80000,
NB_KB_RGB = 0x81000,
NB_KB_4ZONE_RGB = 0x81001,
MOUSE_RGB = 0x90000,
CHASSIS_RGB = 0xB0000,
PROJECTOR_RGB = 0xC0000
}
}
ALL = 0,
MB_RGB = 0x10000,
MB_ADDRESABLE = 0x11000,
DESKTOP_RGB = 0x12000,
VGA_RGB = 0x20000,
DISPLAY_RGB = 0x30000,
HEADSET_RGB = 0x40000,
MICROPHONE_RGB = 0x50000,
EXTERNAL_HARD_DRIVER_RGB = 0x60000,
EXTERNAL_BLUE_RAY_RGB = 0x61000,
DRAM_RGB = 0x70000,
KEYBOARD_RGB = 0x80000,
NB_KB_RGB = 0x81000,
NB_KB_4ZONE_RGB = 0x81001,
MOUSE_RGB = 0x90000,
CHASSIS_RGB = 0xB0000,
PROJECTOR_RGB = 0xC0000
}

View File

@ -1,165 +1,164 @@
// ReSharper disable InconsistentNaming
#pragma warning disable 1591
namespace RGB.NET.Devices.Asus
{
/// <summary>
/// Represents a LED ID as they are known by the ASUS SDK
/// </summary>
public enum AsusLedId : ushort
{
KEY_ESCAPE = 0x01,
KEY_1 = 0x02,
KEY_2 = 0x03,
KEY_3 = 0x04,
KEY_4 = 0x05,
KEY_5 = 0x06,
KEY_6 = 0x07,
KEY_7 = 0x08,
KEY_8 = 0x09,
KEY_9 = 0x0A,
KEY_0 = 0x0B,
KEY_MINUS = 0x0C, // - on main keyboard
KEY_EQUALS = 0x0D,
KEY_BACK = 0x0E, // backspace
KEY_TAB = 0x0F,
KEY_Q = 0x10,
KEY_W = 0x11,
KEY_E = 0x12,
KEY_R = 0x13,
KEY_T = 0x14,
KEY_Y = 0x15,
KEY_U = 0x16,
KEY_I = 0x17,
KEY_O = 0x18,
KEY_P = 0x19,
KEY_LBRACKET = 0x1A,
KEY_RBRACKET = 0x1B,
KEY_RETURN = 0x1C, // Enter on main keyboard
KEY_LCONTROL = 0x1D,
KEY_A = 0x1E,
KEY_S = 0x1F,
KEY_D = 0x20,
KEY_F = 0x21,
KEY_G = 0x22,
KEY_H = 0x23,
KEY_J = 0x24,
KEY_K = 0x25,
KEY_L = 0x26,
KEY_SEMICOLON = 0x27,
KEY_APOSTROPHE = 0x28,
KEY_GRAVE = 0x29, // accent grave
KEY_LSHIFT = 0x2A,
KEY_BACKSLASH = 0x2B,
KEY_Z = 0x2C,
KEY_X = 0x2D,
KEY_C = 0x2E,
KEY_V = 0x2F,
KEY_B = 0x30,
KEY_N = 0x31,
KEY_M = 0x32,
KEY_COMMA = 0x33,
KEY_PERIOD = 0x34, // . on main keyboard
KEY_SLASH = 0x35, // / on main keyboard
KEY_RSHIFT = 0x36,
KEY_MULTIPLY = 0x37, // * on numeric keypad
KEY_LMENU = 0x38, // left Alt
KEY_SPACE = 0x39,
KEY_CAPITAL = 0x3A,
KEY_F1 = 0x3B,
KEY_F2 = 0x3C,
KEY_F3 = 0x3D,
KEY_F4 = 0x3E,
KEY_F5 = 0x3F,
KEY_F6 = 0x40,
KEY_F7 = 0x41,
KEY_F8 = 0x42,
KEY_F9 = 0x43,
KEY_F10 = 0x44,
KEY_NUMLOCK = 0x45,
KEY_SCROLL = 0x46, // Scroll Lock
KEY_NUMPAD7 = 0x47,
KEY_NUMPAD8 = 0x48,
KEY_NUMPAD9 = 0x49,
KEY_SUBTRACT = 0x4A, // - on numeric keypad
KEY_NUMPAD4 = 0x4B,
KEY_NUMPAD5 = 0x4C,
KEY_NUMPAD6 = 0x4D,
KEY_ADD = 0x4E, // + on numeric keypad
KEY_NUMPAD1 = 0x4F,
KEY_NUMPAD2 = 0x50,
KEY_NUMPAD3 = 0x51,
KEY_NUMPAD0 = 0x52,
KEY_DECIMAL = 0x53, // . on numeric keypad
KEY_OEM_102 = 0x56, // < > | on UK/Germany keyboards
KEY_F11 = 0x57,
KEY_F12 = 0x58,
KEY_F13 = 0x64, // (NEC PC98)
KEY_F14 = 0x65, // (NEC PC98)
KEY_F15 = 0x66, // (NEC PC98)
KEY_KANA = 0x70, // (Japanese keyboard)
KEY_ABNT_C1 = 0x73, // / ? on Portugese (Brazilian) keyboards
KEY_CONVERT = 0x79, // (Japanese keyboard)
KEY_NOCONVERT = 0x7B, // (Japanese keyboard)
KEY_YEN = 0x7D, // (Japanese keyboard)
KEY_ABNT_C2 = 0x7E, // Numpad . on Portugese (Brazilian) keyboards
KEY_NUMPADEQUALS = 0x8D, // = on numeric keypad (NEC PC98)
KEY_CIRCUMFLEX = 0x90, // (Japanese keyboard)
KEY_AT = 0x91, // (NEC PC98)
KEY_COLON = 0x92, // (NEC PC98)
KEY_UNDERLINE = 0x93, // (NEC PC98)
KEY_KANJI = 0x94, // (Japanese keyboard)
KEY_STOP = 0x95, // (NEC PC98)
KEY_AX = 0x96, // (Japan AX)
KEY_UNLABELED = 0x97, // (J3100)
KEY_NEXTTRACK = 0x99, // Next Track
KEY_NUMPADENTER = 0x9C, // Enter on numeric keypad
KEY_RCONTROL = 0x9D, //
KEY_MUTE = 0xA0, // Mute
KEY_CALCULATOR = 0xA1, // Calculator
KEY_PLAYPAUSE = 0xA2, // Play / Pause
KEY_MEDIASTOP = 0xA4, // Media Stop
KEY_VOLUMEDOWN = 0xAE, // Volume -
KEY_VOLUMEUP = 0xB0, // Volume +
KEY_WEBHOME = 0xB2, // Web home
KEY_NUMPADCOMMA = 0xB3, // , on numeric keypad (NEC PC98)
KEY_DIVIDE = 0xB5, // / on numeric keypad
KEY_SYSRQ = 0xB7, //
KEY_RMENU = 0xB8, // right Alt
KEY_PAUSE = 0xC5, // Pause
KEY_HOME = 0xC7, // Home on arrow keypad
KEY_UP = 0xC8, // UpArrow on arrow keypad
KEY_PRIOR = 0xC9, // PgUp on arrow keypad
KEY_LEFT = 0xCB, // LeftArrow on arrow keypad
KEY_RIGHT = 0xCD, // RightArrow on arrow keypad
KEY_END = 0xCF, // End on arrow keypad
KEY_DOWN = 0xD0, // DownArrow on arrow keypad
KEY_NEXT = 0xD1, // PgDn on arrow keypad
KEY_INSERT = 0xD2, // Insert on arrow keypad
KEY_DELETE = 0xD3, // Delete on arrow keypad
KEY_LWIN = 0xDB, // Left Windows key
KEY_RWIN = 0xDC, // Right Windows key
KEY_APPS = 0xDD, // AppMenu key
KEY_POWER = 0xDE, //
KEY_SLEEP = 0xDF, //
KEY_WAKE = 0xE3, // System Wake
KEY_WEBSEARCH = 0xE5, // Web Search
KEY_WEBFAVORITES = 0xE6, // Web Favorites
KEY_WEBREFRESH = 0xE7, // Web Refresh
KEY_WEBSTOP = 0xE8, // Web Stop
KEY_WEBFORWARD = 0xE9, // Web Forward
KEY_WEBBACK = 0xEA, // Web Back
KEY_MYCOMPUTER = 0xEB, // My Computer
KEY_MAIL = 0xEC, // Mail
KEY_MEDIASELECT = 0xED, // Media Select
KEY_FN = 0x100, // Function key
namespace RGB.NET.Devices.Asus;
// Undocumented
UNDOCUMENTED_1 = 0x59,
UNDOCUMENTED_2 = 0x56,
UNDOCUMENTED_3 = 0x101,
UNDOCUMENTED_4 = 0x102,
UNDOCUMENTED_5 = 0x103,
UNDOCUMENTED_6 = 0xDA, // Bottom-left function on the ROG Zephyrus Duo 15
}
}
/// <summary>
/// Represents a LED ID as they are known by the ASUS SDK
/// </summary>
public enum AsusLedId : ushort
{
KEY_ESCAPE = 0x01,
KEY_1 = 0x02,
KEY_2 = 0x03,
KEY_3 = 0x04,
KEY_4 = 0x05,
KEY_5 = 0x06,
KEY_6 = 0x07,
KEY_7 = 0x08,
KEY_8 = 0x09,
KEY_9 = 0x0A,
KEY_0 = 0x0B,
KEY_MINUS = 0x0C, // - on main keyboard
KEY_EQUALS = 0x0D,
KEY_BACK = 0x0E, // backspace
KEY_TAB = 0x0F,
KEY_Q = 0x10,
KEY_W = 0x11,
KEY_E = 0x12,
KEY_R = 0x13,
KEY_T = 0x14,
KEY_Y = 0x15,
KEY_U = 0x16,
KEY_I = 0x17,
KEY_O = 0x18,
KEY_P = 0x19,
KEY_LBRACKET = 0x1A,
KEY_RBRACKET = 0x1B,
KEY_RETURN = 0x1C, // Enter on main keyboard
KEY_LCONTROL = 0x1D,
KEY_A = 0x1E,
KEY_S = 0x1F,
KEY_D = 0x20,
KEY_F = 0x21,
KEY_G = 0x22,
KEY_H = 0x23,
KEY_J = 0x24,
KEY_K = 0x25,
KEY_L = 0x26,
KEY_SEMICOLON = 0x27,
KEY_APOSTROPHE = 0x28,
KEY_GRAVE = 0x29, // accent grave
KEY_LSHIFT = 0x2A,
KEY_BACKSLASH = 0x2B,
KEY_Z = 0x2C,
KEY_X = 0x2D,
KEY_C = 0x2E,
KEY_V = 0x2F,
KEY_B = 0x30,
KEY_N = 0x31,
KEY_M = 0x32,
KEY_COMMA = 0x33,
KEY_PERIOD = 0x34, // . on main keyboard
KEY_SLASH = 0x35, // / on main keyboard
KEY_RSHIFT = 0x36,
KEY_MULTIPLY = 0x37, // * on numeric keypad
KEY_LMENU = 0x38, // left Alt
KEY_SPACE = 0x39,
KEY_CAPITAL = 0x3A,
KEY_F1 = 0x3B,
KEY_F2 = 0x3C,
KEY_F3 = 0x3D,
KEY_F4 = 0x3E,
KEY_F5 = 0x3F,
KEY_F6 = 0x40,
KEY_F7 = 0x41,
KEY_F8 = 0x42,
KEY_F9 = 0x43,
KEY_F10 = 0x44,
KEY_NUMLOCK = 0x45,
KEY_SCROLL = 0x46, // Scroll Lock
KEY_NUMPAD7 = 0x47,
KEY_NUMPAD8 = 0x48,
KEY_NUMPAD9 = 0x49,
KEY_SUBTRACT = 0x4A, // - on numeric keypad
KEY_NUMPAD4 = 0x4B,
KEY_NUMPAD5 = 0x4C,
KEY_NUMPAD6 = 0x4D,
KEY_ADD = 0x4E, // + on numeric keypad
KEY_NUMPAD1 = 0x4F,
KEY_NUMPAD2 = 0x50,
KEY_NUMPAD3 = 0x51,
KEY_NUMPAD0 = 0x52,
KEY_DECIMAL = 0x53, // . on numeric keypad
KEY_OEM_102 = 0x56, // < > | on UK/Germany keyboards
KEY_F11 = 0x57,
KEY_F12 = 0x58,
KEY_F13 = 0x64, // (NEC PC98)
KEY_F14 = 0x65, // (NEC PC98)
KEY_F15 = 0x66, // (NEC PC98)
KEY_KANA = 0x70, // (Japanese keyboard)
KEY_ABNT_C1 = 0x73, // / ? on Portugese (Brazilian) keyboards
KEY_CONVERT = 0x79, // (Japanese keyboard)
KEY_NOCONVERT = 0x7B, // (Japanese keyboard)
KEY_YEN = 0x7D, // (Japanese keyboard)
KEY_ABNT_C2 = 0x7E, // Numpad . on Portugese (Brazilian) keyboards
KEY_NUMPADEQUALS = 0x8D, // = on numeric keypad (NEC PC98)
KEY_CIRCUMFLEX = 0x90, // (Japanese keyboard)
KEY_AT = 0x91, // (NEC PC98)
KEY_COLON = 0x92, // (NEC PC98)
KEY_UNDERLINE = 0x93, // (NEC PC98)
KEY_KANJI = 0x94, // (Japanese keyboard)
KEY_STOP = 0x95, // (NEC PC98)
KEY_AX = 0x96, // (Japan AX)
KEY_UNLABELED = 0x97, // (J3100)
KEY_NEXTTRACK = 0x99, // Next Track
KEY_NUMPADENTER = 0x9C, // Enter on numeric keypad
KEY_RCONTROL = 0x9D, //
KEY_MUTE = 0xA0, // Mute
KEY_CALCULATOR = 0xA1, // Calculator
KEY_PLAYPAUSE = 0xA2, // Play / Pause
KEY_MEDIASTOP = 0xA4, // Media Stop
KEY_VOLUMEDOWN = 0xAE, // Volume -
KEY_VOLUMEUP = 0xB0, // Volume +
KEY_WEBHOME = 0xB2, // Web home
KEY_NUMPADCOMMA = 0xB3, // , on numeric keypad (NEC PC98)
KEY_DIVIDE = 0xB5, // / on numeric keypad
KEY_SYSRQ = 0xB7, //
KEY_RMENU = 0xB8, // right Alt
KEY_PAUSE = 0xC5, // Pause
KEY_HOME = 0xC7, // Home on arrow keypad
KEY_UP = 0xC8, // UpArrow on arrow keypad
KEY_PRIOR = 0xC9, // PgUp on arrow keypad
KEY_LEFT = 0xCB, // LeftArrow on arrow keypad
KEY_RIGHT = 0xCD, // RightArrow on arrow keypad
KEY_END = 0xCF, // End on arrow keypad
KEY_DOWN = 0xD0, // DownArrow on arrow keypad
KEY_NEXT = 0xD1, // PgDn on arrow keypad
KEY_INSERT = 0xD2, // Insert on arrow keypad
KEY_DELETE = 0xD3, // Delete on arrow keypad
KEY_LWIN = 0xDB, // Left Windows key
KEY_RWIN = 0xDC, // Right Windows key
KEY_APPS = 0xDD, // AppMenu key
KEY_POWER = 0xDE, //
KEY_SLEEP = 0xDF, //
KEY_WAKE = 0xE3, // System Wake
KEY_WEBSEARCH = 0xE5, // Web Search
KEY_WEBFAVORITES = 0xE6, // Web Favorites
KEY_WEBREFRESH = 0xE7, // Web Refresh
KEY_WEBSTOP = 0xE8, // Web Stop
KEY_WEBFORWARD = 0xE9, // Web Forward
KEY_WEBBACK = 0xEA, // Web Back
KEY_MYCOMPUTER = 0xEB, // My Computer
KEY_MAIL = 0xEC, // Mail
KEY_MEDIASELECT = 0xED, // Media Select
KEY_FN = 0x100, // Function key
// Undocumented
UNDOCUMENTED_1 = 0x59,
UNDOCUMENTED_2 = 0x56,
UNDOCUMENTED_3 = 0x101,
UNDOCUMENTED_4 = 0x102,
UNDOCUMENTED_5 = 0x103,
UNDOCUMENTED_6 = 0xDA, // Bottom-left function on the ROG Zephyrus Duo 15
}

View File

@ -1,18 +1,17 @@
namespace RGB.NET.Devices.Asus
namespace RGB.NET.Devices.Asus;
/// <summary>
/// Represents a type of ASUS LED as known by the ASUS SDK
/// </summary>
public enum AsusLedType
{
/// <summary>
/// Represents a type of ASUS LED as known by the ASUS SDK
/// An ASUS LED that is present on a device's IAuraSyncKeyboard.Keys enumerable
/// </summary>
public enum AsusLedType
{
/// <summary>
/// An ASUS LED that is present on a device's IAuraSyncKeyboard.Keys enumerable
/// </summary>
Key,
Key,
/// <summary>
/// An ASUS LED that is present on a device's IAuraSyncDevice.Lights enumerable
/// </summary>
Light
}
}
/// <summary>
/// An ASUS LED that is present on a device's IAuraSyncDevice.Lights enumerable
/// </summary>
Light
}

View File

@ -1,25 +1,24 @@
using RGB.NET.Core;
namespace RGB.NET.Devices.Asus
namespace RGB.NET.Devices.Asus;
/// <inheritdoc cref="AbstractRGBDevice{TDeviceInfo}" />
/// <summary>
/// Represents a generic Asus-device. (keyboard, mouse, headset, mousepad).
/// </summary>
public abstract class AsusRGBDevice<TDeviceInfo> : AbstractRGBDevice<TDeviceInfo>, IAsusRGBDevice
where TDeviceInfo : AsusRGBDeviceInfo
{
/// <inheritdoc cref="AbstractRGBDevice{TDeviceInfo}" />
#region Constructors
/// <summary>
/// Represents a generic Asus-device. (keyboard, mouse, headset, mousepad).
/// Initializes a new instance of the <see cref="AsusRGBDevice{TDeviceInfo}"/> class.
/// </summary>
public abstract class AsusRGBDevice<TDeviceInfo> : AbstractRGBDevice<TDeviceInfo>, IAsusRGBDevice
where TDeviceInfo : AsusRGBDeviceInfo
{
#region Constructors
/// <param name="info">The generic information provided by Asus for the device.</param>
/// <param name="updateTrigger">The update trigger used to update this device.</param>
protected AsusRGBDevice(TDeviceInfo info, IDeviceUpdateTrigger updateTrigger)
: base(info, new AsusUpdateQueue(updateTrigger, info.Device))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="AsusRGBDevice{TDeviceInfo}"/> class.
/// </summary>
/// <param name="info">The generic information provided by Asus for the device.</param>
/// <param name="updateTrigger">The update trigger used to update this device.</param>
protected AsusRGBDevice(TDeviceInfo info, IDeviceUpdateTrigger updateTrigger)
: base(info, new AsusUpdateQueue(updateTrigger, info.Device))
{ }
#endregion
}
}
#endregion
}

View File

@ -1,57 +1,56 @@
using AuraServiceLib;
using RGB.NET.Core;
namespace RGB.NET.Devices.Asus
namespace RGB.NET.Devices.Asus;
/// <inheritdoc />
/// <summary>
/// Represents a generic information for a Corsair-<see cref="T:RGB.NET.Core.IRGBDevice" />.
/// </summary>
public class AsusRGBDeviceInfo : IRGBDeviceInfo
{
#region Properties & Fields
/// <inheritdoc />
public RGBDeviceType DeviceType { get; }
/// <inheritdoc />
public string DeviceName { get; }
/// <inheritdoc />
public string Manufacturer { get; }
/// <inheritdoc />
public string Model { get; }
/// <inheritdoc />
public object? LayoutMetadata { get; set; }
/// <summary>
/// Represents a generic information for a Corsair-<see cref="T:RGB.NET.Core.IRGBDevice" />.
/// Gets the SDK-aura-device this device represents.
/// </summary>
public class AsusRGBDeviceInfo : IRGBDeviceInfo
public IAuraSyncDevice Device { get; }
#endregion
#region Constructors
/// <summary>
/// Internal constructor of managed <see cref="AsusRGBDeviceInfo"/>.
/// </summary>
/// <param name="deviceType">The type of the <see cref="IRGBDevice"/>.</param>
/// <param name="device">The <see cref="IAuraSyncDevice"/> backing this RGB.NET device.</param>
/// <param name="manufacturer">The manufacturer-name of the <see cref="IRGBDevice"/>.</param>
/// <param name="model">The model-name of the <see cref="IRGBDevice"/>.</param>
internal AsusRGBDeviceInfo(RGBDeviceType deviceType, IAuraSyncDevice device, string? model = null, string manufacturer = "Asus")
{
#region Properties & Fields
this.DeviceType = deviceType;
this.Device = device;
this.Model = model ?? device.Name;
this.Manufacturer = manufacturer;
/// <inheritdoc />
public RGBDeviceType DeviceType { get; }
/// <inheritdoc />
public string DeviceName { get; }
/// <inheritdoc />
public string Manufacturer { get; }
/// <inheritdoc />
public string Model { get; }
/// <inheritdoc />
public object? LayoutMetadata { get; set; }
/// <summary>
/// Gets the SDK-aura-device this device represents.
/// </summary>
public IAuraSyncDevice Device { get; }
#endregion
#region Constructors
/// <summary>
/// Internal constructor of managed <see cref="AsusRGBDeviceInfo"/>.
/// </summary>
/// <param name="deviceType">The type of the <see cref="IRGBDevice"/>.</param>
/// <param name="device">The <see cref="IAuraSyncDevice"/> backing this RGB.NET device.</param>
/// <param name="manufacturer">The manufacturer-name of the <see cref="IRGBDevice"/>.</param>
/// <param name="model">The model-name of the <see cref="IRGBDevice"/>.</param>
internal AsusRGBDeviceInfo(RGBDeviceType deviceType, IAuraSyncDevice device, string? model = null, string manufacturer = "Asus")
{
this.DeviceType = deviceType;
this.Device = device;
this.Model = model ?? device.Name;
this.Manufacturer = manufacturer;
DeviceName = DeviceHelper.CreateDeviceName(Manufacturer, Model);
}
#endregion
DeviceName = DeviceHelper.CreateDeviceName(Manufacturer, Model);
}
}
#endregion
}

View File

@ -1,50 +1,49 @@
using RGB.NET.Core;
namespace RGB.NET.Devices.Asus
namespace RGB.NET.Devices.Asus;
/// <inheritdoc cref="AsusRGBDevice{TDeviceInfo}" />
/// <summary>
/// Represents a Asus headset.
/// </summary>
public class AsusUnspecifiedRGBDevice : AsusRGBDevice<AsusRGBDeviceInfo>, IUnknownDevice
{
/// <inheritdoc cref="AsusRGBDevice{TDeviceInfo}" />
#region Properties & Fields
private LedId _baseLedId;
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Represents a Asus headset.
/// Initializes a new instance of the <see cref="T:RGB.NET.Devices.Asus.AsusHeadsetRGBDevice" /> class.
/// </summary>
public class AsusUnspecifiedRGBDevice : AsusRGBDevice<AsusRGBDeviceInfo>, IUnknownDevice
/// <param name="info">The specific information provided by Asus for the headset.</param>
/// <param name="baseLedId">The ledId of the first led of this device. All other leds are created by incrementing this base-id by 1.</param>
/// <param name="updateTrigger">The update trigger used to update this device.</param>
internal AsusUnspecifiedRGBDevice(AsusRGBDeviceInfo info, LedId baseLedId, IDeviceUpdateTrigger updateTrigger)
: base(info, updateTrigger)
{
#region Properties & Fields
this._baseLedId = baseLedId;
private LedId _baseLedId;
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Devices.Asus.AsusHeadsetRGBDevice" /> class.
/// </summary>
/// <param name="info">The specific information provided by Asus for the headset.</param>
/// <param name="baseLedId">The ledId of the first led of this device. All other leds are created by incrementing this base-id by 1.</param>
/// <param name="updateTrigger">The update trigger used to update this device.</param>
internal AsusUnspecifiedRGBDevice(AsusRGBDeviceInfo info, LedId baseLedId, IDeviceUpdateTrigger updateTrigger)
: base(info, updateTrigger)
{
this._baseLedId = baseLedId;
InitializeLayout();
}
#endregion
#region Methods
private void InitializeLayout()
{
int ledCount = DeviceInfo.Device.Lights.Count;
for (int i = 0; i < ledCount; i++)
AddLed(_baseLedId + i, new Point(i * 10, 0), new Size(10, 10));
}
/// <inheritdoc />
protected override object? GetLedCustomData(LedId ledId) => (int)ledId - (int)_baseLedId;
#endregion
InitializeLayout();
}
}
#endregion
#region Methods
private void InitializeLayout()
{
int ledCount = DeviceInfo.Device.Lights.Count;
for (int i = 0; i < ledCount; i++)
AddLed(_baseLedId + i, new Point(i * 10, 0), new Size(10, 10));
}
/// <inheritdoc />
protected override object? GetLedCustomData(LedId ledId) => (int)ledId - (int)_baseLedId;
#endregion
}

Some files were not shown because too many files have changed in this diff Show More